# Add version history

> Keep each saved DOCX as an immutable version and restore one without deleting newer work.



Keep a saved DOCX you can return to without discarding newer work. SuperDoc exports and opens the files; your
application decides when to create a version and where to store it.

Autosave describes when saving happens. Version history describes which saved states you retain. Saving automatically
does not create a history unless your storage keeps those versions.

## Try saving and restoring [#try-saving-and-restoring]

Run the [version-history example](https://go.superdoc.dev/examples/version-history). It includes a document and the
controls needed for this walkthrough:

1. Choose **Save version** to keep Version 1.
2. Edit a sentence and save Version 2.
3. Choose **Restore version 1**. The earlier text returns as Version 3; Version 2 remains available.
4. Export the current DOCX, or restore Version 2 to return to the newer text.

This example stores real DOCX snapshots in browser memory. Reloading clears its history; it does not provide a server
or cross-tab conflict handling. Add those next using your [load and save flow](/editor/load-and-save-documents).

## Keep every saved version [#keep-every-saved-version]

For this history, each save creates a new snapshot instead of overwriting the previous file.

Suppose a document has two versions:

```text
Version 1 → Version 2 (current)
```

Restoring Version 1 should create Version 3 with the same document content. Version 2 remains available:

```text
Version 1 → Version 2 → Version 3 (restored from Version 1)
```

SuperDoc produces and opens the DOCX files. Your backend owns the version IDs, timestamps, authors, retention rules,
and current-version pointer.

## Add version endpoints [#add-version-endpoints]

Extend the document endpoint from the Load and save guide. These are application routes, not SuperDoc routes:

| Route                                         | Job                                               |
| --------------------------------------------- | ------------------------------------------------- |
| `GET /api/documents/sample`                   | Return the current DOCX                           |
| `POST /api/documents/sample/versions`         | Store a new immutable version and make it current |
| `GET /api/documents/sample/versions`          | List version metadata, newest first               |
| `GET /api/documents/sample/versions/:version` | Return one immutable DOCX snapshot                |

Record the creation time and author from the trusted backend. Do not accept either value from browser input.

Return the current version ID with the current DOCX, for example in an `x-version-id` response header. Return the new
`DocumentVersion` metadata from each successful save. Keep that ID as the base for the next save or restore.

Send the current version ID with each save. The backend must compare it with the current pointer in the same operation
that makes the new version current. Return `409 Conflict` if another tab or user saved first. Validate every supplied
version ID against this document.

## Save, list, and restore [#save-list-and-restore]

The following helper uses the running Editor from the Load and save guide:

```ts
import { DOCX, type SuperDoc } from 'superdoc';

const documentEndpoint = '/api/documents/sample';
const versionsEndpoint = `${documentEndpoint}/versions`;

export type DocumentVersion = {
  id: string;
  createdAt: string;
  createdBy: string;
  restoredFromVersionId?: string;
};

export async function listVersions(): Promise<DocumentVersion[]> {
  const response = await fetch(versionsEndpoint);
  if (!response.ok) throw new Error(`Could not list versions: ${response.status}`);

  return response.json() as Promise<DocumentVersion[]>;
}

async function exportDocx(superdoc: SuperDoc): Promise<Blob> {
  const docx = await superdoc.export({
    exportType: ['docx'],
    triggerDownload: false,
  });
  if (!(docx instanceof Blob)) throw new Error('Expected one DOCX file.');

  return docx;
}

export async function saveVersion(
  superdoc: SuperDoc,
  baseVersionId: string,
  restoredFromVersionId?: string,
): Promise<DocumentVersion> {
  const docx = await exportDocx(superdoc);

  const response = await fetch(versionsEndpoint, {
    method: 'POST',
    headers: {
      'content-type': DOCX,
      'x-base-version-id': baseVersionId,
      ...(restoredFromVersionId ? { 'x-restored-from-version-id': restoredFromVersionId } : {}),
    },
    body: docx,
  });
  if (response.status === 409) throw new Error('A newer version exists. Reload before saving.');
  if (!response.ok) throw new Error(`Could not save the version: ${response.status}`);

  return response.json() as Promise<DocumentVersion>;
}

async function openDocument(superdoc: SuperDoc, docx: Blob): Promise<void> {
  const result = await superdoc.replaceFile(docx);
  const state = result && typeof result === 'object' ? ((result as { state?: unknown }).state ?? null) : null;
  if (state !== null && state !== 'review-ready' && state !== 'editing-ready') {
    throw new Error('SuperDoc could not open the DOCX file.');
  }
}

export async function restoreVersion(
  superdoc: SuperDoc,
  versionId: string,
  baseVersionId: string,
): Promise<DocumentVersion> {
  const activeDocx = await exportDocx(superdoc);
  const response = await fetch(`${versionsEndpoint}/${encodeURIComponent(versionId)}`);
  if (!response.ok) throw new Error(`Could not restore the version: ${response.status}`);

  const docx = new Blob([await response.arrayBuffer()], { type: DOCX });

  try {
    await openDocument(superdoc, docx);
    return await saveVersion(superdoc, baseVersionId, versionId);
  } catch (error) {
    const rollbackDocx = await fetch(documentEndpoint)
      .then(async (current) => (current.ok ? new Blob([await current.arrayBuffer()], { type: DOCX }) : activeDocx))
      .catch(() => activeDocx);
    await openDocument(superdoc, rollbackDocx);
    throw error;
  }
}

```

In Vanilla, pass the `superdoc` instance you created earlier. In React, get that same instance from
`editorRef.current?.getInstance()`.

To restore a version, the helper opens its snapshot first and then saves it as a new version. Bytes the Editor cannot
open never become current. If the save is rejected, it tries to reload the backend's current document. If that request
fails, it attempts to restore the document that was open before the attempt. Recovery can fail too; show the error and
require a successful reload before allowing another version action.

Refresh the version list after every save or restore. Disable version actions while a request is running.
Before calling `restoreVersion`, also prevent editing: set `inert` on a wrapper containing the Editor and its toolbar,
and pause application-driven document mutations. Keep that lock through saving and recovery. Otherwise, recovery can
discard edits made while the restore request was pending. Remove the lock after a successful restore; on failure,
show the error outside the inert wrapper and require a successful reload before enabling editing and version actions.
The helper does not manage this application UI lock. The backend version check handles saves from other tabs and users.

## Verify the history [#verify-the-history]

Save Version 1, edit the statement of work, and save Version 2. Restore Version 1. The backend should create Version 3,
the Editor should show the Version 1 text, and Version 2 should remain in the list. Reload the page to confirm Version 3
is current.

Open a second tab before saving Version 2. After restoring Version 1 as Version 3, a save from that stale tab should
return `409` and leave Version 3 current.

## Restore shared documents separately [#restore-shared-documents-separately]

These helpers restore a standalone Editor, not a live collaboration room. A DOCX snapshot is not the room's saved
Yjs state. In collaboration, replacing the file can replace shared content for everyone. Do not treat a local
Restore button as a coordinated room-recovery policy.

Your application needs a server-owned recovery policy for connected editors and writes arriving during recovery.
See [Save and restore a room](/editor/collaboration/save-and-restore-a-room) for room persistence. It demonstrates
reopening saved state, not rolling a live room back to an earlier version.

Use [Secure integration](/editor/secure-integration) to protect the version routes. Use
[Export options](/editor/export-options) when saved versions need a specific comments or tracked-changes policy.
