# Load and save a DOCX

> Load a DOCX from your application and save the edited file back to your backend.



The [Quickstart](/editor/quickstart) downloads your edits. To keep them when someone returns, load and save the DOCX
through your application API:

1. Fetch the DOCX from your API as a `Blob`.
2. Open and edit the `Blob` in SuperDoc.
3. Export the edited DOCX as a new `Blob` and send it back to your API.

SuperDoc handles the DOCX in the browser. Your application owns the API and storage.

## 1. Add a document endpoint [#1-add-a-document-endpoint]

The examples use `/api/documents/sample`. This is an application route, not a SuperDoc route.

| Request | Your endpoint must                                                          |
| ------- | --------------------------------------------------------------------------- |
| `GET`   | Return the current DOCX bytes                                               |
| `PUT`   | Store the request body and return a success status after the write finishes |

The Quickstart does not create this endpoint. Add it to your backend with the sample DOCX as its initial document.
Proxy `/api/documents/sample` from Vite to your backend, or change `endpoint` below to your API URL. For a different
origin, allow GET and PUT through CORS. Keep storage credentials and access checks in the backend.

Restart Vite after adding the proxy. Check that the GET response contains DOCX bytes, not your application's HTML
page: a fallback page can return `200` and still fail to open as a document.

## 2. Add a save action [#2-add-a-save-action]

For Vanilla, replace the export button and Editor container in `index.html` with the following markup. Keep the
`<script type="module" src="/src/main.ts"></script>` tag:

```html
<button id="save-docx" type="button" disabled>Save DOCX</button>
<output id="document-status" aria-live="polite">Opening…</output>
<div id="editor"></div>
```

The React example below renders its own controls.

## 3. Load and save the document [#3-load-and-save-the-document]

Replace `src/main.ts` in Vanilla or `src/App.tsx` in React. Keep the Quickstart styles and React entry point. Copy any
user, mode, or viewing options you chose on the previous pages into this Editor configuration; the examples otherwise
use the default editing mode.

**Vanilla — `src/main.ts`**

```ts
import { DOCX, SuperDoc } from 'superdoc';
import 'superdoc/style.css';

function requireElement<ElementType extends Element>(selector: string) {
  const element = document.querySelector<ElementType>(selector);
  if (!element) throw new Error(`${selector} not found.`);
  return element;
}

const saveButton = requireElement<HTMLButtonElement>('#save-docx');
const status = requireElement<HTMLOutputElement>('#document-status');

const endpoint = '/api/documents/sample';
let isReady = false;
let editRevision = 0;
let superdoc: SuperDoc | undefined;

function showOpenError(error: unknown) {
  console.error('Could not open the document.', error);
  if (isReady) return;

  status.value = 'Could not open the document. Reload to try again.';
  saveButton.disabled = true;
}

try {
  const response = await fetch(endpoint);
  if (!response.ok) throw new Error(`Could not load the document: ${response.status}`);

  const docx = new Blob([await response.arrayBuffer()], { type: DOCX });
  superdoc = new SuperDoc({
    selector: '#editor',
    document: docx,
    onReady: () => {
      isReady = true;
      status.value = 'Ready';
      saveButton.disabled = false;
    },
    onEditorUpdate: () => {
      editRevision += 1;
      status.value = 'Unsaved changes';
    },
    onContentError: ({ error }) => showOpenError(error),
    onException: ({ error }) => showOpenError(error),
  });
} catch (error) {
  showOpenError(error);
}

saveButton.addEventListener('click', async () => {
  if (!superdoc || !isReady) return;

  const savedRevision = editRevision;
  saveButton.disabled = true;
  status.value = 'Saving…';

  try {
    const editedDocx = await superdoc.export({
      exportType: ['docx'],
      triggerDownload: false,
    });
    if (!(editedDocx instanceof Blob)) throw new Error('Expected one DOCX file.');

    const saveResponse = await fetch(endpoint, {
      method: 'PUT',
      headers: { 'content-type': DOCX },
      body: editedDocx,
    });
    if (!saveResponse.ok) throw new Error(`Could not save the document: ${saveResponse.status}`);
    status.value = editRevision === savedRevision ? 'Saved' : 'Unsaved changes';
  } catch (error) {
    status.value = 'Save failed. Try again.';
    console.error('Could not confirm the document was saved.', error);
  } finally {
    saveButton.disabled = false;
  }
});

window.addEventListener('beforeunload', () => superdoc?.destroy());

```

**React — `src/App.tsx`**

```tsx
import { useEffect, useRef, useState } from 'react';
import { SuperDocEditor, type SuperDocRef } from '@superdoc/react';
import '@superdoc/react/style.css';

const endpoint = '/api/documents/sample';
const docxType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';

export default function App() {
  const editorRef = useRef<SuperDocRef>(null);
  const editRevisionRef = useRef(0);
  const savingRef = useRef(false);
  const [document, setDocument] = useState<Blob>();
  const [loadError, setLoadError] = useState(false);
  const [ready, setReady] = useState(false);
  const [saving, setSaving] = useState(false);
  const [saveStatus, setSaveStatus] = useState('');

  useEffect(() => {
    const controller = new AbortController();

    void fetch(endpoint, { signal: controller.signal })
      .then(async (response) => {
        if (!response.ok) throw new Error(`Could not load the document: ${response.status}`);
        setDocument(new Blob([await response.arrayBuffer()], { type: docxType }));
      })
      .catch((error: unknown) => {
        if (!controller.signal.aborted) {
          setLoadError(true);
          console.error(error);
        }
      });

    return () => controller.abort();
  }, []);

  function showOpenError({ error }: { error: unknown }) {
    console.error('Could not open the document.', error);
    setReady(false);
    setLoadError(true);
  }

  async function saveDocument() {
    if (savingRef.current) return;

    const superdoc = editorRef.current?.getInstance();
    if (!superdoc) return;

    const savedRevision = editRevisionRef.current;
    savingRef.current = true;
    setSaving(true);
    setSaveStatus('Saving…');
    try {
      const editedDocx = await superdoc.export({
        exportType: ['docx'],
        triggerDownload: false,
      });
      if (!(editedDocx instanceof Blob)) throw new Error('Expected one DOCX file.');

      const response = await fetch(endpoint, {
        method: 'PUT',
        headers: { 'content-type': docxType },
        body: editedDocx,
      });
      if (!response.ok) throw new Error(`Could not save the document: ${response.status}`);
      setSaveStatus(editRevisionRef.current === savedRevision ? 'Saved' : 'Unsaved changes');
    } catch (error) {
      setSaveStatus('Save failed. Try again.');
      console.error('Could not confirm the document was saved.', error);
    } finally {
      savingRef.current = false;
      setSaving(false);
    }
  }

  if (loadError) return <p>Could not open the document. Reload to try again.</p>;
  if (!document) return <p>Opening document…</p>;

  return (
    <>
      <button disabled={!ready || saving} onClick={() => void saveDocument()} type='button'>
        Save DOCX
      </button>
      <output aria-live='polite'>{saveStatus}</output>
      <SuperDocEditor
        document={document}
        onContentError={showOpenError}
        onEditorUpdate={() => {
          editRevisionRef.current += 1;
          setSaveStatus('Unsaved changes');
        }}
        onException={showOpenError}
        onReady={() => setReady(true)}
        ref={editorRef}
      />
    </>
  );
}

```


Both examples:

* fetch the DOCX before mounting the Editor;
* enable saving after `onReady`;
* call `export({ triggerDownload: false })` to receive the edited `Blob`;
* show **Saved** only after the `PUT` succeeds. Edits made during that request remain unsaved.

The revision counter tracks local edits only. This is manual saving, not autosave or protection against another user
overwriting the file.

## Use another source or destination [#use-another-source-or-destination]

| Task                           | Use                                                        |
| ------------------------------ | ---------------------------------------------------------- |
| Open a public or signed URL    | `document: url`                                            |
| Open a file selected by a user | `document: file`                                           |
| Switch the mounted Editor      | `await superdoc.replaceFile(nextDocx)`                     |
| Download instead of saving     | `await superdoc.export({ exportedName: 'sample-edited' })` |

Fetch the file yourself and pass a `Blob` when the request needs custom headers.

### Preview: check a document replacement [#preview-check-a-document-replacement]

`replaceDocument()` is a source-preview API, not part of the published Quickstart release yet.
It keeps the mounted Editor and reports whether the requested document opened. `replaceFile()` remains available
with its existing raw return value.

After fetching the next DOCX as a `Blob`, use the ready `superdoc` instance from your application:

```ts
async function switchDocument(nextDocx: Blob): Promise<void> {
  const result = await superdoc.replaceDocument(nextDocx);
  if (!result.ok) {
    throw new Error(result.detail ?? result.reason ?? 'Could not open the next document.');
  }
}
```

Save or discard current edits before switching. Disable document actions while awaiting this function and handle
rejections in your application's error UI. Update the active document name only after it succeeds.

Do not use `onReady` to decide whether a replacement succeeded: recovery can make the previous document ready again.
A failed replacement does not guarantee that the previous document survived. Custom controls can call
`superdoc.ui.document.replaceDocument(nextDocx)` for the same typed result.

## Verify the round trip [#verify-the-round-trip]

Change the effective date from `September 1, 2026` to `October 1, 2026`. Select **Save DOCX**, then reload the page. The
new date should still be present.

Then make your endpoint reject a save before writing. Confirm **Save failed** appears and your edits remain available
to retry. A network failure can happen after the server stores the file, so an error alone does not prove nothing was
saved.

## Choose your interface [#choose-your-interface]

[Choose your interface](/editor/who-renders-the-ui) to keep SuperDoc's built-in controls or replace the ones your
application needs to own. Loading and saving stay the same.

For more storage options, see [Version history](/editor/version-history) and [Export options](/editor/export-options).
