This is 2026.9 documentation. Coverage here is incremental: a page exists only where 2026.9
differs or is newly documented. For any other topic, switch to Current (2026.8 and earlier) in
the version selector.
Publish
Open an ingestion, write and upload a bundle, and wait for the version. The wire sequence, the
correctness rules, and a build order.
Load
List a version’s artifacts, download the files you need, and rebuild objects, geometry,
instances, grouping, and appearance from the bundle.
What changes
What a bundle builder is
Every Speckle producer has a component that plays the role the SDKs call a bundle builder. In specklepy and Speckle.Sdk it isBundleBuilder. The Archicad connector implements the same role in
C++ without an SDK. It is the producer-side model of a version, and it has four jobs.
- Intern identities into dense integer keys. Objects are keyed by
applicationId. Geometry is keyed by a value you derive from the owning object, such aswall-1:g0. Structural nodes such as definitions, instances, materials, colors, levels, and grouping containers are keyed by whatever stable key your host gives them. Each of the three key spaces counts from zero on its own. A relation’s type is what says which space each end refers to. - Record typed relations. Every link is one row of
(relation, source, target, ord). Membership, hosting, connectivity, appearance, and instancing are all relations, never nested fields. - Stream rows to parquet as you add them. Geometry is encoded on the spot and flushed in row groups, so memory does not grow with the model.
- Stamp provenance. The builder writes the
metarow and the self-describing catalogs from the bundle spec’s generated constants, never from literals you typed in.
- Get-or-add is interning. Asking for a key that already exists returns the same handle and writes nothing. Asking for it again with different attributes is an error. One key means one node.
- Add is append. Geometry, model properties, results, and camera views append a row on every call.
- Edges cannot be retracted. A single-valued relation is write-once. Decide before you write.
- Units and producer are required up front. The default unit is stamped on every object, and the
producer slug and version go into
metaand onto the ingestion. - A default scene view is injected when you declare none, grouping by
IN_COLLECTION. - Build once, then rename. The builder writes files under a temporary stem and renames them to the reserved version id once the ingestion hands it out.
What a bundle contains
A bundle is a flat set of files sharing one stem, the version id, with no manifest file. Consumers know the filenames from the spec and discover the stem from themeta file.
Two files you do not write:
- The viewer
.dat. The server builds{id}.viewer.datfrom your bundle after completion. That build is part of why the version is not visible the moment the upload completes. - A manifest or index. The
metafile is the anchor. Everything else is found by name.
Optional files are feature-detected by presence. An empty optional table is a bug, not a
placeholder. The three type tables are the exception: the viewer needs all three present to build
its property view, so write them even when they hold no rows.
Relations you do not need, and relations you add
Two questions come up as soon as a team maps its own domain onto the relational layer. Both have a short answer. You do not have to emit every relation type. The catalog describes what Speckle can carry, not what every model must contain. A producer for a host with no levels, no instances, or no MEP systems emits none of those relations, and a consumer treats the absence as “not in this model”, never as an error. Emit what your domain has and nothing else. You can add your own links, with the same guarantee interoperability has always had: none. The relation catalog is Speckle’s. Its numbers are reserved or retired on purpose, the validator rejects a relations file that uses a number outside the catalog, and every Speckle consumer ignores edges it does not know. Custom links therefore go in your own parquet file, uploaded with the bundle under the same version id, keyed byapplicationId so they survive across versions. Speckle stores and
lists that file and never reads it. The viewer, Speckle Intelligence, and the SDK receive path will
not see those links, and a receiving application supports them only if you wrote that support. Say
so in your own documentation.
Before you add a file, check which of these three shapes your case is. Most turn out to be the
first two.
- A relation Speckle already has. A structural “supports” link, a duct run, a pipe network:
CONNECTS_TOwith a scope,IN_SYSTEM, orSUBELEMENTusually carries it, and then every consumer understands it. Read the relation catalog first. - A grouping, not a link. A classification hierarchy, a cost breakdown, a zone scheme: put the code on each object as a property and declare a scene view over that property path. The viewer then offers your hierarchy as a tree without any new relation.
- A link to something outside the model. An issue, a clash, a record in an asset register: when the other end is not an object in the bundle, an id on the object as a property is the portable form. Use a custom edge file only when both ends are objects in the same bundle.
Open-source producers to learn from
Every Speckle connector is open source. Read them as worked examples of the publish and load rules, in the language closest to yours. The SDKs are the reference builders; the non-SDK producers show the same contract implemented from scratch.
The user interface is separate from publishing. The shared connector UI,
speckle-connectors-dui (TypeScript), is
what the .NET desktop connectors, Archicad, and SketchUp embed. Using it is optional. It is worth
reading for two things: the bindings show the read and write interactions a host has to expose,
including how the UI follows an ingestion until the version exists, and reusing it keeps the
experience familiar to people who already use other Speckle connectors. Blender ships its own native
panels instead. Grasshopper has no UI at all and splits publish and load into components.
Navisworks has no 2026.9 desktop connector. Large NWC and NWD files go through direct
uploads, where a server-side converter produces the
bundle. Older archived repositories such as the QGIS, Unity, and Excel connectors publish the
object graph of 2026.8 and earlier and are not a pattern for 2026.9.
FAQ
Is the bundle spec open source?
Is the bundle spec open source?
Yes. It lives at speckle-bundle-spec:
the SQL source that defines every table, relation, and node kind, with the reasoning recorded
beside each entry, plus the code generators, a validator, and conformance fixtures. Generated
constants for C++, C#, Python, and TypeScript are under
generated/. Pin to a commit, build
against the constants for your language, and run the validator on every bundle in CI. To propose
a relation, open an issue there with the two node kinds it joins and an example model.What happens if my host has no levels, materials, or instances?
What happens if my host has no levels, materials, or instances?
Emit nothing for them. An absent relation means “not in this model”. Consumers must never treat
a missing relation type as an error.
Where do units go if there is no root object?
Where do units go if there is no root object?
On every object as a root scalar, on every instance node, in every SGEO header, and per property
row where a value has a unit. There is no bundle-wide unit.