Load and save a DOCX
Load a DOCX from your application and save the edited file back to your backend.
The Quickstart downloads your edits. To keep them when someone returns, load and save the DOCX through your application API:
- Fetch the DOCX from your API as a
Blob. - Open and edit the
Blobin SuperDoc. - Export the edited DOCX as a new
Bloband 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
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
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:
<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
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.
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());
Both examples:
- fetch the DOCX before mounting the Editor;
- enable saving after
onReady; - call
export({ triggerDownload: false })to receive the editedBlob; - show Saved only after the
PUTsucceeds. 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
| 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.
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 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 and Export options.