Loading and saving
Load DOCX bytes, replace documents, and save edits through the React and Vue editor APIs.
Pass DOCX bytes to the document prop to load a document. Call save() to get the edited DOCX bytes. Your application owns storage and network requests.
Load and save a document
This example loads a URL and downloads the saved file:
import { useRef } from 'react';
import { DocxEditor, useDocxSource, type DocxEditorRef } from '@docx-editor.dev/react';
export function Editor({ url }: { url: string }) {
const ref = useRef<DocxEditorRef>(null);
const { document, error } = useDocxSource(url);
async function download() {
const buf = await ref.current?.save();
if (!buf) return;
const blob = new Blob([buf], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
const link = window.document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'document.docx';
link.click();
URL.revokeObjectURL(link.href);
}
if (error) return <p role="alert">The document failed to load.</p>;
return (
<>
<button onClick={download}>Download</button>
<DocxEditor ref={ref} document={document} />
</>
);
}<script setup lang="ts">
import { ref } from 'vue';
import { DocxEditor, useDocxSource, type DocxEditorRef } from '@docx-editor.dev/vue';
const props = defineProps<{ url: string }>();
const editorRef = ref<DocxEditorRef | null>(null);
const { document, error } = useDocxSource(() => props.url);
async function download() {
const buf = await editorRef.value?.save();
if (!buf) return;
const blob = new Blob([buf], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
const link = window.document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'document.docx';
link.click();
URL.revokeObjectURL(link.href);
}
</script>
<template>
<p v-if="error" role="alert">The document failed to load.</p>
<template v-else>
<button type="button" @click="download">Download</button>
<DocxEditor ref="editorRef" :document="document" />
</template>
</template>useDocxSource() fetches the bytes and ignores stale responses when the URL changes. If the bytes arrive but the editor cannot open them, see Handle files that do not open.
| Save path | Returns |
|---|---|
Ref save() | Promise<ArrayBuffer | null>. Resolves null when no editor is mounted. |
Core Editor.save() | Promise<ArrayBuffer>. Rejects when saving fails. |
| File > Save | Calls onSave in React or emits save in Vue. |
Choose a document source
document value | Result |
|---|---|
Uint8Array | Opens the DOCX bytes. |
undefined | Shows the loading screen and disables every control. Use it while you fetch bytes. |
'blank' | Opens an editable empty page in Calibri 11 pt with the standard built-in styles. |
useDocxSource() accepts a URL string, URL, Uint8Array, or ArrayBuffer. For a file input, read the file into bytes first:
const file = input.files?.[0];
if (file) setDoc(new Uint8Array(await file.arrayBuffer()));Show your own loading indicator
Pass loadingIndicator in React, or fill the #loadingIndicator slot in Vue. The editor shows it on the blank page while it reads the file, then below the laid-out pages while it lays out the rest.
- Include screen reader text, such as a
role="status"element. - The indicator needs the packaged chrome. With
chrome={false}, see Custom loading screen.
Replace the open document
Call load() on the ref to replace the document without remounting:
import { blankDocumentBytes } from '@docx-editor.dev/core/editor';
ref.current?.load(nextBytes);
// A repeatable File > New command:
ref.current?.load(blankDocumentBytes());- Repeating
'blank'indocumentdoes not create another document, because the value does not change. blankDocumentBytes()returns new bytes on each call, so call it in an event handler or store the result.- An inline
blankDocumentBytes()call indocumentcreates a new document identity and rebuilds the editor on every render.
Pass { direction: 'rtl' } for a right-to-left document.
To accept a complete updated file from a server, use the Document refresh API.
Save a document
A save captures the document when you call save(), then writes the file in short steps while the page stays responsive. Edits made during a save belong to the next save. Later saves reuse unchanged paragraphs and table rows, so they cost less than the first save.
Save also validates and updates fields:
- Pending protected form input is validated and formatted. Invalid input rejects the save with
invalidArgs. - Collaborative sessions reject saves that require form-field formatting.
- Stale
REFandNOTEREFresults refresh as one undo step, so a save can change undo history.
For field rules, see Fields and cross-references.
Autosave safely
Debounce saves on change, run one save at a time, and save again if the document changed meanwhile:
const saving = useRef(false);
const again = useRef(false);
async function persist() {
if (saving.current) {
again.current = true;
return;
}
saving.current = true;
try {
const buf = await ref.current?.save();
if (!buf) return;
const response = await fetch(`/api/documents/${encodeURIComponent(docId)}`, {
method: 'PUT',
body: buf,
});
if (!response.ok) throw new Error(`Save failed: ${response.status}`);
} catch (error) {
showSaveError(error);
} finally {
saving.current = false;
if (again.current) {
again.current = false;
void persist();
}
}
}
// Call persist() from a debounced onChange handler (Vue: @change).- Render the autosave component with a key based on
docId, so timers and pending saves stay with one document. - Use server-side revision checks to reject stale writes.
- Show save failures so users can retry.
- Upload the
ArrayBufferas is; it is already compressed.
Limits
save()captures the document when it starts. The bytes describe that document even if you load another one before they are ready.- If
save()waits for a pending edit and another document loads first, it rejects withinvalidState(save.document-changed). load()invalidates pending document refresh submissions.