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
AfterReceive2, use Flatten() and read DataObject.properties:
Example
Flatten() on Base. The BIM fields live in properties, not in typed Door classes.
DataObject and Collection
Most Speckle connectors emitDataObject (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 fromBase. It supports both static (typed C# properties) and dynamic (runtime, string-keyed) properties on the same object.
Example
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 (fromSpeckle.Objects) declare static properties with full C# type support, but every Base-derived type also accepts arbitrary dynamic properties:
Example
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
Complete example
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
UseGetMembers() (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, nestedBase objects serialize inline with their parent. Detachment stores a nested object separately and keeps a reference — useful for large or frequently-shared sub-graphs.
- Dynamic (@ prefix)
- Typed ([DetachProperty])
Example
Chunking
Pair[Chunkable] with [DetachProperty] to split very large lists across multiple stored objects:
Example
[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].Custom Types
Before defining a fully custom type, consider whetherDataObject already fits. Reach for a custom type when you want compile-time-checked properties, or when the object is internal to your own application.
Example
Display Values
When you send data and expect it in the 3D viewer, the object must expose adisplayValue property containing renderable geometry. Schema details: Object Schema: displayValue.
The recommended container is DataObject:
Example
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:
Example
displayValue. PascalCase DisplayValue serializes under a different key and the viewer will not recognize it.
Traversing the Object Graph
The simplest walk isBaseExtensions.Flatten or Traverse:
Example
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, useGraphTraversal with DefaultTraversal:
Example
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
Why did my custom type deserialize as plain Base?
Why did my custom type deserialize as plain Base?
The type’s assembly was not passed to
AddSpeckleSdk. Register it in the assemblies argument
— see Dependency injection
(connectors).When should I use Flatten vs GraphTraversal?
When should I use Flatten vs GraphTraversal?
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.Sent an object but nothing shows in the viewer
Sent an object but nothing shows in the viewer
Check that the object (or one of its
elements) has a displayValue containing Mesh, Line,
or Point geometry.InvalidPropNameException when setting a dynamic property
InvalidPropNameException when setting a dynamic property
The key is empty, starts with
@@, or contains .//. Use IsPropNameValid to check before
setting if the key comes from untrusted input.obj.id is null right after construction
obj.id is null right after construction
id is only populated after send or deserialization — it’s a content hash, not something you
set.Detached property comes back null after receive
Detached property comes back null after receive
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