diff --git a/src/index.js b/src/index.js
index 66fe70da..91d34893 100644
--- a/src/index.js
+++ b/src/index.js
@@ -47,6 +47,7 @@ export {
   findRootTypeKey,
   typeListToArraySnapshot,
   typeMapGetSnapshot,
+  createDocFromSnapshot,
   iterateDeletedStructs,
   applyUpdate,
   applyUpdateV2,
diff --git a/src/utils/Snapshot.js b/src/utils/Snapshot.js
index cd47e424..ccd923e8 100644
--- a/src/utils/Snapshot.js
+++ b/src/utils/Snapshot.js
@@ -12,13 +12,17 @@ import {
   createDeleteSet,
   createID,
   getState,
-  AbstractDSDecoder, AbstractDSEncoder, DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
+  findIndexSS,
+  UpdateEncoderV2,
+  DefaultDSEncoder,
+  applyUpdateV2,
+  AbstractDSDecoder, AbstractDSEncoder, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
 } from '../internals.js'
 
 import * as map from 'lib0/map.js'
 import * as set from 'lib0/set.js'
 import * as decoding from 'lib0/decoding.js'
-import { DefaultDSEncoder } from './encoding.js'
+import * as encoding from 'lib0/encoding.js'
 
 export class Snapshot {
   /**
@@ -148,3 +152,51 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
     meta.add(snapshot)
   }
 }
+
+/**
+ * @param {Doc} originDoc
+ * @param {Snapshot} snapshot
+ * @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc
+ * @return {Doc}
+ */
+export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => {
+  if (originDoc.gc) {
+    // we should not try to restore a GC-ed document, because some of the restored items might have their content deleted
+    throw new Error('originDoc must not be garbage collected')
+  }
+  const { sv, ds } = snapshot
+
+  const encoder = new UpdateEncoderV2()
+  originDoc.transact(transaction => {
+    let size = 0
+    sv.forEach(clock => {
+      if (clock > 0) {
+        size++
+      }
+    })
+    encoding.writeVarUint(encoder.restEncoder, size)
+    // splitting the structs before writing them to the encoder
+    for (const [client, clock] of sv) {
+      if (clock === 0) {
+        continue
+      }
+      if (clock < getState(originDoc.store, client)) {
+        getItemCleanStart(transaction, createID(client, clock))
+      }
+      const structs = originDoc.store.clients.get(client) || []
+      const lastStructIndex = findIndexSS(structs, clock - 1)
+      // write # encoded structs
+      encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1)
+      encoder.writeClient(client)
+      // first clock written is 0
+      encoding.writeVarUint(encoder.restEncoder, 0)
+      for (let i = 0; i <= lastStructIndex; i++) {
+        structs[i].write(encoder, 0)
+      }
+    }
+    writeDeleteSet(encoder, ds)
+  })
+
+  applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot')
+  return newDoc
+}
diff --git a/tests/index.js b/tests/index.js
index 710b6edb..aec3ae5a 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -7,6 +7,7 @@ import * as encoding from './encoding.tests.js'
 import * as undoredo from './undo-redo.tests.js'
 import * as compatibility from './compatibility.tests.js'
 import * as doc from './doc.tests.js'
+import * as snapshot from './snapshot.tests.js'
 
 import { runTests } from 'lib0/testing.js'
 import { isBrowser, isNode } from 'lib0/environment.js'
@@ -16,7 +17,7 @@ if (isBrowser) {
   log.createVConsole(document.body)
 }
 runTests({
-  doc, map, array, text, xml, encoding, undoredo, compatibility
+  doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot
 }).then(success => {
   /* istanbul ignore next */
   if (isNode) {
diff --git a/tests/snapshot.tests.js b/tests/snapshot.tests.js
new file mode 100644
index 00000000..b52cb67c
--- /dev/null
+++ b/tests/snapshot.tests.js
@@ -0,0 +1,171 @@
+import { createDocFromSnapshot, Doc, snapshot, YMap } from '../src/internals'
+import * as t from 'lib0/testing.js'
+import { init } from './testHelper'
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testBasicRestoreSnapshot = tc => {
+  const doc = new Doc({ gc: false })
+  doc.getArray('array').insert(0, ['hello'])
+  const snap = snapshot(doc)
+  doc.getArray('array').insert(1, ['world'])
+
+  const docRestored = createDocFromSnapshot(doc, snap)
+
+  t.compare(docRestored.getArray('array').toArray(), ['hello'])
+  t.compare(doc.getArray('array').toArray(), ['hello', 'world'])
+}
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testEmptyRestoreSnapshot = tc => {
+  const doc = new Doc({ gc: false })
+  const snap = snapshot(doc)
+  snap.sv.set(9999, 0)
+  doc.getArray().insert(0, ['world'])
+
+  const docRestored = createDocFromSnapshot(doc, snap)
+
+  t.compare(docRestored.getArray().toArray(), [])
+  t.compare(doc.getArray().toArray(), ['world'])
+
+  // now this snapshot reflects the latest state. It shoult still work.
+  const snap2 = snapshot(doc)
+  const docRestored2 = createDocFromSnapshot(doc, snap2)
+  t.compare(docRestored2.getArray().toArray(), ['world'])
+}
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testRestoreSnapshotWithSubType = tc => {
+  const doc = new Doc({ gc: false })
+  doc.getArray('array').insert(0, [new YMap()])
+  const subMap = doc.getArray('array').get(0)
+  subMap.set('key1', 'value1')
+
+  const snap = snapshot(doc)
+  subMap.set('key2', 'value2')
+
+  const docRestored = createDocFromSnapshot(doc, snap)
+
+  t.compare(docRestored.getArray('array').toJSON(), [{
+    key1: 'value1'
+  }])
+  t.compare(doc.getArray('array').toJSON(), [{
+    key1: 'value1',
+    key2: 'value2'
+  }])
+}
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testRestoreDeletedItem1 = tc => {
+  const doc = new Doc({ gc: false })
+  doc.getArray('array').insert(0, ['item1', 'item2'])
+
+  const snap = snapshot(doc)
+  doc.getArray('array').delete(0)
+
+  const docRestored = createDocFromSnapshot(doc, snap)
+
+  t.compare(docRestored.getArray('array').toArray(), ['item1', 'item2'])
+  t.compare(doc.getArray('array').toArray(), ['item2'])
+}
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testRestoreLeftItem = tc => {
+  const doc = new Doc({ gc: false })
+  doc.getArray('array').insert(0, ['item1'])
+  doc.getMap('map').set('test', 1)
+  doc.getArray('array').insert(0, ['item0'])
+
+  const snap = snapshot(doc)
+  doc.getArray('array').delete(1)
+
+  const docRestored = createDocFromSnapshot(doc, snap)
+
+  t.compare(docRestored.getArray('array').toArray(), ['item0', 'item1'])
+  t.compare(doc.getArray('array').toArray(), ['item0'])
+}
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testDeletedItemsBase = tc => {
+  const doc = new Doc({ gc: false })
+  doc.getArray('array').insert(0, ['item1'])
+  doc.getArray('array').delete(0)
+  const snap = snapshot(doc)
+  doc.getArray('array').insert(0, ['item0'])
+
+  const docRestored = createDocFromSnapshot(doc, snap)
+
+  t.compare(docRestored.getArray('array').toArray(), [])
+  t.compare(doc.getArray('array').toArray(), ['item0'])
+}
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testDeletedItems2 = tc => {
+  const doc = new Doc({ gc: false })
+  doc.getArray('array').insert(0, ['item1', 'item2', 'item3'])
+  doc.getArray('array').delete(1)
+  const snap = snapshot(doc)
+  doc.getArray('array').insert(0, ['item0'])
+
+  const docRestored = createDocFromSnapshot(doc, snap)
+
+  t.compare(docRestored.getArray('array').toArray(), ['item1', 'item3'])
+  t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3'])
+}
+
+/**
+ * @param {t.TestCase} tc
+ */
+export const testDependentChanges = tc => {
+  const { array0, array1, testConnector } = init(tc, { users: 2 })
+
+  if (!array0.doc) {
+    throw new Error('no document 0')
+  }
+  if (!array1.doc) {
+    throw new Error('no document 1')
+  }
+
+  /**
+   * @type Doc
+   */
+  const doc0 = array0.doc
+  /**
+   * @type Doc
+   */
+  const doc1 = array1.doc
+
+  doc0.gc = false
+  doc1.gc = false
+
+  array0.insert(0, ['user1item1'])
+  testConnector.syncAll()
+  array1.insert(1, ['user2item1'])
+  testConnector.syncAll()
+
+  const snap = snapshot(array0.doc)
+
+  array0.insert(2, ['user1item2'])
+  testConnector.syncAll()
+  array1.insert(3, ['user2item2'])
+  testConnector.syncAll()
+
+  const docRestored0 = createDocFromSnapshot(array0.doc, snap)
+  t.compare(docRestored0.getArray('array').toArray(), ['user1item1', 'user2item1'])
+
+  const docRestored1 = createDocFromSnapshot(array1.doc, snap)
+  t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1'])
+}