This project supports embedding into any web application via iframe. The recommended pattern is: the parent system handles auth, file fetching, and upload; the iframe handles editing only. Tokens, cookies, and business APIs stay in the parent — the editor never sees them.
A working demo is available at /embed-demo.html (includes sha256 logging for debugging).
<iframe
id="documentEditor"
src="https://your-deployment/?embed=1"
style="width: 100%; height: 720px; border: 0"
></iframe>To restrict messages to a specific origin, add embedOrigin:
<iframe id="documentEditor" src="https://your-deployment/?embed=1&embedOrigin=https://your-system.example.com"></iframe>Include an id on each command to match it to the response:
const iframe = document.getElementById('documentEditor');
const editorOrigin = 'https://your-deployment';
function sendEditorCommand(type, payload = {}) {
const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
iframe.contentWindow.postMessage({ id, type, payload }, editorOrigin);
return id;
}
window.addEventListener('message', (event) => {
if (event.origin !== editorOrigin) return;
const { id, type, payload } = event.data || {};
if (!type?.startsWith('document:')) return;
switch (type) {
case 'document:ready':
console.log('Editor ready');
break;
case 'document:opened':
console.log('Opened', id, payload);
break;
case 'document:saved':
console.log('Saved', payload.fileName, payload.file);
break;
case 'document:error':
console.error('Error', payload.message);
break;
}
});sendEditorCommand('document:open-url', {
url: 'https://example.com/files/demo.xlsx',
fileName: 'demo.xlsx',
readonly: false,
});If the URL requires auth headers, pass fetchOptions. For protected files it is preferable to fetch in the parent system and pass the binary:
sendEditorCommand('document:open-url', {
url: 'https://example.com/api/files/1',
fileName: 'demo.xlsx',
fetchOptions: { headers: { Authorization: `Bearer ${token}` } },
});const input = document.createElement('input');
input.type = 'file';
input.accept = '.xlsx,.xls,.csv,.docx,.doc,.pptx,.ppt';
input.onchange = () => {
sendEditorCommand('document:open-file', { file: input.files[0], readonly: false });
};
input.click();const response = await fetch('/api/files/1', {
headers: { Authorization: `Bearer ${token}` },
});
const buffer = await response.arrayBuffer();
sendEditorCommand('document:open-buffer', { fileName: 'demo.xlsx', buffer, readonly: false });Set at open time via the readonly field, or toggle at any time:
sendEditorCommand('document:set-readonly', { readonly: true });In read-only mode, editing is disabled and document:save returns document:error.
The save command exports the current document and returns a File via document:saved. Default format is XLSX; pass targetExt to change it.
sendEditorCommand('document:save', { targetExt: 'XLSX' }); // XLSX, DOCX, PPTX, CSVBy default the command waits for the editor to return the edited file. If it times out, document:error is returned — this prevents accidentally uploading the original unchanged file. To opt in to returning the original on timeout:
sendEditorCommand('document:save', { targetExt: 'XLSX', returnOriginalOnTimeout: true });Upload from the parent:
window.addEventListener('message', async (event) => {
if (event.origin !== editorOrigin) return;
const { type, payload } = event.data || {};
if (type !== 'document:saved') return;
await fetch('/api/files/1', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: payload.file,
});
});Note: Do not rely on
file.sizealone to detect changes..xlsxis a zip archive — a minor edit can produce the exact same byte count. The built-in/embed-demo.htmllogs asha256hash on every save for easier debugging.
sendEditorCommand('document:get-state');
// Response: { type: 'document:state', payload: { readonly: false, hasDocument: true } }| Direction | Type | Description |
|---|---|---|
| parent → iframe | document:open-url |
Open document from URL |
| parent → iframe | document:open-file |
Open document from File / Blob |
| parent → iframe | document:open-buffer |
Open document from ArrayBuffer / Uint8Array |
| parent → iframe | document:set-readonly |
Set read-only or editable |
| parent → iframe | document:save |
Save and return File |
| parent → iframe | document:get-state |
Query current state |
| iframe → parent | document:ready |
Editor initialised |
| iframe → parent | document:opened |
Document opened |
| iframe → parent | document:readonly-changed |
Read-only state changed |
| iframe → parent | document:saved |
Save complete, file returned |
| iframe → parent | document:state |
Current state response |
| iframe → parent | document:error |
Operation failed |