Skip to main content
  1. Yjs Internals/

Chapter 5: Snapshot #

In Chapter 4: Update Encoding / Decoding, we learned how changes can be packed into binary updates and sent to other documents. Updates are like a movie: they show a story of changes over time. But sometimes you don’t want the whole movie. You want one still frame — a photograph of the document at a single moment. That’s exactly what a Snapshot is.


What problem does Snapshot solve? #

Imagine you are building a notes app. A user writes a thoughtful paragraph, then deletes it. Later, they realize they need it back. If you only saved updates, you would have to replay the entire history from the beginning and stop at the right moment, being careful to undo the deletion. That is complicated and slow.

A Snapshot lets you press a “save version” button. It captures the whole document state in one frozen picture. Later, you can restore that picture, compare it with another version, or check whether a certain update belongs to that moment.

Snapshots are also useful for:

  • Comparing documents — do two docs have the exact same content?
  • Computing differences — what changed between two versions?
  • Checking updates — is this update already included in the snapshot?
  • Version history — show a document as it looked yesterday.

Key Concept: A Snapshot is a photograph of the Doc #

A Snapshot captures the state of a Yjs document at one moment in time.

It does not freeze the original document. You can keep editing the document, and the snapshot will stay exactly as it was. It’s like taking a photo of a whiteboard: you can keep drawing on the whiteboard, but the photo still shows what was there when you took it.

In code, creating a snapshot is very simple:

const doc = new Y.Doc()
const note = doc.getText('note')
note.insert(0, 'Hello')

const versionA = Y.snapshot(doc)

After this code runs, versionA contains the state of the document when the text was "Hello".


Key Concept: A Snapshot has three parts #

Inside Yjs, a Snapshot is made of three pieces:

  1. A miniature StructStore — the warehouse of all data structs that existed at snapshot time.
  2. A DeleteSet — a list of tombstones saying which structs were deleted before the snapshot.
  3. A state vector — a set of “clock readings” for each client, saying how much of each peer’s history is in the snapshot.

You don’t need to memorize these names yet. They will appear in later chapters. For now, think of a Snapshot as:

warehouse + tombstones + clock readings.

This combination is important because a document isn’t just a list of added items. It also contains deletions. If an item was created and then deleted before the snapshot, that item should not appear in the snapshot.


How to use Snapshots #

Let’s build a tiny version history example.

Step 1: Take a snapshot #

Start with a document and save a version:

const doc = new Y.Doc()
const note = doc.getText('note')
note.insert(0, 'Hello')

const versionA = Y.snapshot(doc)

Now versionA is a frozen picture of the note with the text "Hello".

Step 2: Keep editing #

The user keeps typing. The original document changes, but the snapshot does not:

note.insert(5, ' world')

const versionB = Y.snapshot(doc)

console.log(note.toString())
// 'Hello world'

console.log(Y.equalSnapshots(versionA, versionB))
// false

versionB captures the new state with the text "Hello world". versionA still remembers only "Hello".

Step 3: Restore an old snapshot #

Later, the user wants to see version A again. We can create a new document from the snapshot:

const restoredA = Y.createDocFromSnapshot(doc, versionA)

console.log(restoredA.getText('note').toString())
// 'Hello'

restoredA is a brand new Doc that contains the old state. The original current document is not changed.

Step 4: Save and load snapshots #

You can encode a snapshot into binary bytes and store it somewhere, like a database or a file:

const bytes = Y.encodeSnapshot(versionA)

const decoded = Y.decodeSnapshot(bytes)
const restored = Y.createDocFromSnapshot(doc, decoded)

console.log(restored.getText('note').toString())
// 'Hello'

bytes is a Uint8Array, just like updates. The decoded snapshot is equal to the original snapshot:

console.log(Y.equalSnapshots(versionA, decoded))
// true

Step 5: Check whether an update is already included #

If a remote friend sends you an update, you may want to know: “Do I already have this?” Yjs can help with that:

const remoteDoc = new Y.Doc()
remoteDoc.getText('note').insert(0, 'Hello')

const remoteUpdate = Y.encodeStateAsUpdate(remoteDoc)

console.log(Y.isUpdateApplied(doc, remoteUpdate))
// true

This checks whether the update is already inside the current document. Snapshots use the same idea internally: because a snapshot records the state vector, Yjs can tell which updates are already included in that saved moment.


What happens under the hood? #

Now let’s peek inside the camera.

When you call Y.snapshot(doc), Yjs does something like this:

  1. It looks at the document’s current StructStore — the warehouse of all structs.
  2. It records the current state vector — for each client, how many structs are included.
  3. It records the current DeleteSet — which structs are currently marked as deleted.
  4. It wraps all three pieces into a Snapshot object.

Here is a simple diagram of versionA being created and later restored:

sequenceDiagram participant You participant D as Your Doc participant S as Snapshot participant R as Restored Doc You->>D: insert "Hello" You->>D: Y.snapshot(doc) D->>S: save store + delete set + state vector You->>D: insert " world" You->>S: Y.createDocFromSnapshot(doc, snapshot) S->>R: apply saved structs & tombstones You->>R: read text

The Snapshot class itself lives in src/utils/Snapshot.js. It is intentionally small:

// simplified from src/utils/Snapshot.js
class Snapshot {
  constructor(store, ds, sm) {
    this.store = store // miniature warehouse
    this.ds = ds       // tombstones
    this.sm = sm       // state vector
  }
}

The store is a StructStore — you’ll learn more about it in StructStore.
The ds is a DeleteSet — it records which structs are invisible because they were deleted.
The sm is a state vector — a Map from clientID to a clock number. If it says client 5 → 10, it means “this snapshot includes all structs from client 5 with clocks 0 through 9.”

A simplified version of creating a snapshot might look like this:

// simplified idea from src/utils/Snapshot.js
const snapshot = (doc) => {
  const store = doc.store
  const ds = collectDeleteSet(doc)
  const sm = computeStateVector(store)

  return new Snapshot(store, ds, sm)
}

The real function is more careful, but the idea is exactly this.

Encoding a snapshot is also about writing those three pieces into a binary format:

// simplified idea from src/utils/Snapshot.js
const encodeSnapshot = (snapshot) => {
  const encoder = new UpdateEncoderV1()

  encoder.writeStructs(snapshot.store)
  encoder.writeDeleteSet(snapshot.ds)
  encoder.writeStateVector(snapshot.sm)

  return encoder.toUint8Array()
}

Decoding is the reverse: read the structs, read the delete set, read the state vector, and rebuild the Snapshot.


Common beginner questions #

Can I edit a Snapshot? #

No. A snapshot is a frozen view. You can’t call .insert() or .set() on it. If you want to edit a snapshot, create a document from it first with Y.createDocFromSnapshot(), and edit that new document.

Is a Snapshot the same as an update? #

No. An update is a list of changes. A snapshot is a full saved state. Updates are useful for sending changes; snapshots are useful for saving and comparing versions.

Do I need to understand the internals to use snapshots? #

Not at all. You can use the four main functions:

  • Y.snapshot(doc)
  • Y.encodeSnapshot(snapshot)
  • Y.decodeSnapshot(bytes)
  • Y.createDocFromSnapshot(doc, snapshot)

The internal details are just there to help you trust what happens behind the scenes.


Conclusion #

Snapshots are one of the friendliest features in Yjs. They let you capture a moment in time and restore it later.

  • A Snapshot is a frozen picture of a Doc.
  • It contains a StructStore, a DeleteSet, and a state vector.
  • Use Y.snapshot(doc) to take a picture.
  • Use Y.createDocFromSnapshot(doc, snapshot) to restore it.
  • Use Y.encodeSnapshot() and Y.decodeSnapshot() to save and load snapshots.
  • Use Y.equalSnapshots() to compare two versions.

Now that we know snapshots contain a store, let’s open the warehouse door and look at the StructStore.

Next up: StructStore


Generated by AI Codebase Knowledge Builder