Skip to main content
When you load model data from Speckle, you work with DataObject (BIM elements), Collection (hierarchy), and Base (foundation type). Connector-published walls, doors, and rooms are usually DataObject instances with BIM fields in properties. When to read this: before filtering or exporting model data. Read next: Find objects by property for filter recipes, or BIM data patterns for connector data structure.
For filtering and indexing recipes, see Find objects by property. Bootstrap: Automate with scripts.

Find doors and walls in received data

After Receive2, use Flatten() and read DataObject.properties:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Objects.Data;
using Speckle.Sdk.Models.Extensions;

var doors = root.Flatten()
    .OfType<DataObject>()
    .Where(d => d.properties.GetValueOrDefault("category")?.ToString()?.Contains("Door") == true);

foreach (var door in doors)
{
    var name = door.name;
    var level = door.properties.GetValueOrDefault("level");
    var fireRating = door.properties.GetValueOrDefault("fireRating");
    Console.WriteLine($"{name} — level: {level}, fire rating: {fireRating}");
}
The traversal API is Flatten() on Base. The BIM fields live in properties, not in typed Door classes.

DataObject and Collection

Most Speckle connectors emit DataObject (Speckle.Objects.Data) — name, properties, and displayValue — rather than bespoke typed classes. Use Collection (Speckle.Sdk.Models.Collections) to organize hierarchies. Plain Base or custom subclasses still work for self-contained apps; prefer DataObject/Collection when interoperating with connectors and the viewer. See Object Schema for field definitions.

The Base Class

Everything sent to or received from Speckle inherits from Base. It supports both static (typed C# properties) and dynamic (runtime, string-keyed) properties on the same object.
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Sdk.Models;

var custom = new Base();
custom["name"] = "My Object";
custom["count"] = 42;
Objects are content-addressed: once sent, an object’s id is a hash of its serialized content. Identical content always produces the same id.

Static vs Dynamic Properties

Typed geometry classes (from Speckle.Objects) declare static properties with full C# type support, but every Base-derived type also accepts arbitrary dynamic properties:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Objects.Geometry;
using Speckle.Sdk.Common;

var point = new Point(1, 2, 3, Units.Meters);
point["customId"] = "POINT_001";
Use a typed subclass when you control the shape and want compile-time safety; use plain Base with the indexer when the shape is unknown or ad hoc. Both produce the same wire format.
Property names starting with __ are ignored entirely (not hashed, not serialized). Property names cannot contain . or /. Avoid dynamic names starting with _ or @ unless you intend to detach that property — a double @@ prefix or invalid characters throw InvalidPropNameException.

Complete Example

https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Complete example
using Speckle.Sdk.Models;

public class Room : Base
{
    public required string name { get; set; }
    public double area { get; set; }

    [DetachProperty]
    public List<Base>? elements { get; set; }
}

var room = new Room { name = "Conference Room A", area = 32.5 };
room["material"] = "Concrete";
room["fireRating"] = "2 hour";

room.elements = new List<Base>();
for (var i = 0; i < 3; i++)
{
    var chair = new Base();
    chair["name"] = $"Chair {i}";
    chair["category"] = "Furniture";
    room.elements.Add(chair);
}

foreach (var (key, value) in room.GetMembers())
{
    Console.WriteLine($"{key}: {value}");
}
The Room example teaches Base mechanics directly. For real BIM-like data, model with DataObject and Collection instead — the same detachment and introspection rules apply.

Discovering Properties at Runtime

Use GetMembers() (typed + dynamic), GetDynamicMemberNames(), or DynamicPropertyKeys. Safe access: obj["material"] as string ?? "Unknown", obj.TryGetName(), obj.TryGetDisplayValue(). GetDetachedProp/SetDetachedProp extension methods read or write detached properties without knowing whether they’re typed or dynamic.

Detachment

By default, nested Base objects serialize inline with their parent. Detachment stores a nested object separately and keeps a reference — useful for large or frequently-shared sub-graphs.
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
parent["@heavyMesh"] = meshObject;

Chunking

Pair [Chunkable] with [DetachProperty] to split very large lists across multiple stored objects:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
[DetachProperty]
[Chunkable(maxObjCountPerChunk: 5000)]
public required List<double> Measurements { get; set; }
[Chunkable] is honored by Serialize/Send/Send2. The connector-scale SendPipeline path (see Publish large models (connectors)) detaches on [DetachProperty] but does not currently act on [Chunkable].
[DetachProperty(false)] is obsolete — use the parameterless [DetachProperty] only.

Custom Types

Before defining a fully custom type, consider whether DataObject already fits. Reach for a custom type when you want compile-time-checked properties, or when the object is internal to your own application.
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
[SpeckleType("MyCompany.Wall")]
public class Wall : Base
{
    public required double height { get; set; }
    public required double thickness { get; set; }
    public required string material { get; set; }
}
Most Speckle connectors will not recognize custom types during host-app conversion. Include a displayValue property so the object is at least visible in the 3D viewer.

Display Values

When you send data and expect it in the 3D viewer, the object must expose a displayValue property containing renderable geometry. Schema details: Object Schema: displayValue. The recommended container is DataObject:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Objects.Data;
using Speckle.Sdk.Models;

var wall = new DataObject
{
    name = "Wall 01",
    properties = new Dictionary<string, object?> { ["material"] = "Concrete" },
    displayValue = new List<Base> { wallMesh },
};
You can also set displayValue dynamically on plain Base for quick scripts. For a root Base with nested Point/Line/Mesh properties (as in the Quickstart), the viewer can render that geometry without an explicit displayValue. Connector-style DataObject graphs need an explicit displayValue — bare primitives at the root of the version object often won’t render. Use BaseExtensions after receive:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Sdk.Models.Extensions;

if (someObject.IsDisplayableObject())
{
    var displayGeometry = someObject.TryGetDisplayValue();
}
For custom types, the property name must be exact camelCase displayValue. PascalCase DisplayValue serializes under a different key and the viewer will not recognize it.

Traversing the Object Graph

The simplest walk is BaseExtensions.Flatten or Traverse:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Sdk.Models.Extensions;

IEnumerable<Base> allObjects = receivedData.Flatten();

foreach (var obj in allObjects)
{
    Console.WriteLine(obj.speckle_type);
}
Flatten follows Base values and list values. For dictionaries it only inspects keys — a Dictionary<string, Base> will not have its values traversed.

Rule-Based Traversal

For connector-style traversal, use GraphTraversal with DefaultTraversal:
https://mintcdn.com/speckle/VtRPWzmN-ULoLfIv/images/developers/sdks/csharp.svg?fit=max&auto=format&n=VtRPWzmN-ULoLfIv&q=85&s=f2970e3b8bd4a2c7899b46211a42f251Example
using Speckle.Sdk.Models.GraphTraversal;

var traversalFunc = DefaultTraversal.CreateTraversalFunc();
var results = traversalFunc.Traverse(receivedData);

foreach (var traversalContext in results)
{
    if (DefaultTraversal.HasDisplayValue(traversalContext.Current))
    {
        // This object has geometry to convert
    }
}
DefaultTraversal understands elements/@elements, displayValue/@displayValue, and definition/@definition.
Platform traversal patterns: Traversal Recipes. .NET filter/index recipes: Find objects by property.

FAQ and Pitfalls

The type’s assembly was not passed to AddSpeckleSdk. Register it in the assemblies argument — see Dependency injection (connectors).
Use Flatten for simple filtering or indexing on small-to-medium graphs. Use GraphTraversal with DefaultTraversal when you need connector-style rules on large BIM graphs.
Check that the object (or one of its elements) has a displayValue containing Mesh, Line, or Point geometry.
The key is empty, starts with @@, or contains .//. Use IsPropNameValid to check before setting if the key comes from untrusted input.
id is only populated after send or deserialization — it’s a content hash, not something you set.
Confirm the property was marked with [DetachProperty] (typed) or an @ prefix (dynamic) before sending.

Next Steps

Core concepts

Projects, models, and version lifecycle

Find objects by property

Flatten, filter, and index recipes

Operations API

Send and receive object graphs
Last modified on July 10, 2026