Publishing a document in Seed means creating and storing signed blobs that describe the document state. A document is not “saved” as one mutable database row. Instead, publishing creates content-addressed blobs that the daemon indexes and syncs.
Core Concepts
1. Change blob
A Change blob contains document operations:
set metadata/title
add or replace blocks
move blocks
delete blocks
The Change is signed by the author/signer.
For a new document, the first Change can also be the document genesis.
2. Ref blob
A Ref blob points a document path to a version.
Example:
hm://account/path
-> Ref
-> heads: [Change CID]
-> genesisBlob: Change CIDWithout a valid indexed Ref, the document may exist as raw blobs, but it will not appear as a normal document resource.
3. Capability blob
A Capability blob proves that a signer can write under another account.
This is required when publishing to an account different from the signer’s own account.
Example:
Seed Surveys account grants WRITER to Nodos signerFor cross-account publishing, the publish payload should include:
Capability blob + Change blob + Ref blobSDK Publishing Flow
The TypeScript SDK flow is the preferred model when the client/server code signs the document itself.
This is the same general model used by the CLI.
Step 1: Build document operations
Document content is converted into operations.
Example operation types:
SetAttributes
ReplaceBlock
MoveBlocks
DeleteBlocksFor markdown, the CLI/SDK parses markdown into Seed blocks and then flattens them into document operations.
Step 2: Create unsigned change
const {unsignedBytes, ts} = createChangeOps({
ops,
})This creates unsigned CBOR bytes for the Change.
Step 3: Sign the change
const changeBlock = await createChange(unsignedBytes, signer)This produces:
{
bytes,
cid,
}The CID becomes the document version.
Step 4: Create signed Ref
const refInput = await createVersionRef(
{
space: accountUid,
path: '/document-path',
genesis: changeCid,
version: changeCid,
generation: Number(ts),
capability: capabilityCid,
visibility: 'Private', // only for private docs
},
signer,
)The Ref makes the document path point to the new version.
Step 5: Publish blobs
await client.publish({
blobs: [
capabilityBlob, // when publishing cross-account
{cid: changeCid, data: changeBlock.bytes},
...refInput.blobs,
],
})This calls:
POST /api/PublishBlobswhich maps to daemon:
daemon.storeBlobs(...)The daemon stores and indexes the blobs.
Cross-Account Publishing
If signer and destination account are different:
signerAccountUid !== destinationAccountUidthen the signed Ref must include a valid capability CID.
Also, the capability blob itself must be present in the daemon/index. Otherwise the Ref may not index as a valid writable resource.
So publish:
Capability + Change + Refnot only:
Change + RefPublic vs Private Documents
Public documents omit visibility in the Ref.
Private documents include:
visibility: 'Private'Private documents must use a simple one-segment path, for example:
/private-feedback-abcnot:
/folder/private-feedback-abcDaemon CreateDocumentChange Alternative
There is also a daemon-native path:
grpcClient.documents.createDocumentChange(...)This asks the daemon to create/sign/index the Change and Ref.
However, this path checks write permissions before publishing. That means the daemon must already know the signer has write access.
For cross-account publishing, this can fail if the capability is not already indexed locally:
permission_denied: key is not allowed to write to spaceThat is why SDK signing can be better for browser/server workflows where we explicitly publish the capability blob together with the document blobs.
Syncing After Publish
After local publish, the document may need to be pushed to another peer:
grpcClient.resources.pushResourcesToPeer({
resources: [documentId],
addrs: peerAddrs,
recursive: false,
})This is a sync optimization, not the core publish step.
A document can be successfully created locally even if push announces zero blobs or fails. The important thing is that the daemon stored and indexed:
Capability + Change + RefMental Model
Publishing a document means:
Build operations
-> Create Change
-> Sign Change
-> Create Ref
-> Sign Ref
-> Publish blobs
-> Daemon indexes blobs
-> Optional peer push
For cross-account publishing:
Fetch/include Capability
-> Publish Capability + Change + Ref togetherThe Ref is the key object that makes a document visible at an hm://account/path resource.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime