docduck

DOCX EDITOR SDK · V1

Put a real Word editor in your website.

Load a DOCX, edit it in an embedded editor, and download the finished Word file. Connect your own backend when you want edits saved to your app.

01 · GET STARTED

Quickstart

Add the SDK and a container to your page. The SDK creates a sandboxed iframe on a separate editor origin and connects it to your website origin. Create a blank document in the editor or open a DOCX supplied by your app.

HTML + JavaScript
<script nonce="YOUR_RESPONSE_CSP_NONCE" src="https://docduck.io/embed.js"></script>
<button id="new-docx" type="button" disabled>New blank document</button>
<input id="docx-file" type="file" accept=".docx" disabled>
<button id="export-docx" type="button" disabled>Download edited document</button>
<p id="editor-status" role="status" aria-live="polite">Connecting…</p>
<div id="docduck-editor" style="height: 850px"></div>

<script nonce="YOUR_RESPONSE_CSP_NONCE" type="module">
  const newButton = document.querySelector("#new-docx");
  const fileInput = document.querySelector("#docx-file");
  const exportButton = document.querySelector("#export-docx");
  const status = document.querySelector("#editor-status");
  let connected = false;
  let hasDocument = false;
  let reconnecting = false;
  let sessionGeneration = 0;
  const updateControls = () => {
    newButton.disabled = !connected;
    fileInput.disabled = !connected;
    exportButton.disabled = !connected || !hasDocument;
  };
  const editor = window.DocDuck.mount({
    element: "#docduck-editor",
    url: "https://docduck.io/embed"
  });

  editor.on("change", () => {
    status.textContent = "Unsaved edits are ready to export.";
  });
  editor.on("ready", () => {
    connected = true;
    updateControls();
    status.textContent = reconnecting
      ? "Reconnected. Reopen the latest DOCX saved by your website, or create a new document."
      : "Connected. Create a blank document or choose a Word file.";
    reconnecting = false;
  });
  editor.on("error", ({ code, message }) => {
    if (code === "RELOADED") {
      sessionGeneration += 1;
      reconnecting = true;
      connected = false;
      hasDocument = false;
      updateControls();
      status.textContent = "The editor reloaded. Reopen the latest DOCX saved by your website. Unsaved edits were lost.";
      return;
    }
    status.textContent = message;
  });

  try {
    await editor.ready;
    connected = true;
    updateControls();
    if (!reconnecting) status.textContent = "Create a blank document or choose a Word file.";
  } catch (error) {
    status.textContent = error.message;
  }

  newButton.addEventListener("click", async () => {
    const generation = sessionGeneration;
    try {
      await editor.createBlank();
      if (generation !== sessionGeneration) return;
      hasDocument = true;
      updateControls();
      exportButton.disabled = false;
      status.textContent = "Edit the document, then download your changes.";
    } catch (error) {
      if (generation !== sessionGeneration) return;
      status.textContent = error.message;
    }
  });

  fileInput.addEventListener("change", async () => {
    const file = fileInput.files?.[0];
    if (!file) return;
    const generation = sessionGeneration;
    try {
      const bytes = await file.arrayBuffer();
      if (generation !== sessionGeneration) return;
      await editor.load({ bytes, fileName: file.name });
      if (generation !== sessionGeneration) return;
      hasDocument = true;
      updateControls();
      exportButton.disabled = false;
      status.textContent = "Edit the document, then download your changes.";
    } catch (error) {
      if (generation !== sessionGeneration) return;
      status.textContent = error.message;
    } finally {
      if (generation === sessionGeneration) fileInput.value = "";
    }
  });

  exportButton.addEventListener("click", async () => {
    const generation = sessionGeneration;
    try {
      const { bytes, fileName } = await editor.export();
      if (generation !== sessionGeneration) return;
      downloadDocument({ bytes, fileName });
      status.textContent = "Your edited Word file is downloading.";
    } catch (error) {
      if (generation !== sessionGeneration) return;
      status.textContent = error.message;
    }
  });

  // Replace this with your authenticated storage flow to save in your app.
  function downloadDocument({ bytes, fileName }) {
    const blob = new Blob([bytes], {
      type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    });
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = fileName;
    link.click();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  // Call editor.destroy() when removing this editor from the page.
</script>

Generate a fresh, unpredictable CSP nonce on your server for each response, add it to your script-src policy, and replace YOUR_RESPONSE_CSP_NONCE on both script tags. Never hard-code or reuse a nonce. If your policy uses 'strict-dynamic', allowing the DocDuck hostname in script-src alone does not authorize the SDK script. Add https://docduck.io to your frame-src policy too. The quickstart downloads the edited Word file right away. Replace downloadDocument with your authenticated storage flow to save edits in your app. The SDK does not create an account, store a file in docduck, or provide a backend endpoint.

02 · DATA FLOW

Your app keeps control of the file.

  1. Start a document.Create a blank Word file with editor.createBlank(), or read a DOCX from your upload, database, or storage service and pass its bytes to editor.load().
  2. Edit inside the frame.The editor holds the current document in its session. The saved event means its latest bytes are ready in memory.
  3. Export and keep it.The quickstart downloads the returned DOCX. For in-app persistence, call editor.export() and save those bytes through your app. Your app owns authorization, storage, and versioning.

Export the current document before loading another one if you need to keep its edits. Loading replaces the document in the editor session.

03 · SDK

API reference

DocDuck.mount(options)

Creates the editor iframe. Pass element as a CSS selector or an existing DOM element, and url as the exact HTTPS /embed URL. An optional timeout is in milliseconds, from 100 to 120000. It defaults to 60000. If the first connection takes longer, the SDK emits CONNECT_TIMEOUT through the error event and keeps retrying.

editor.ready

A promise that resolves when the secure iframe handshake succeeds. It remains pending through a connection timeout while the SDK retries. Wait for it before enabling document actions.

editor.createBlank({ fileName, readOnly })

Creates and opens a blank Word document without requiring a DOCX library in your app. The file name defaults to Untitled document.docx. Optional readOnly: true opens it read-only. Like load(), it will not replace edits that have not been exported.

editor.load({ bytes, fileName, readOnly })

Loads an ArrayBuffer or Uint8Array and a .docx filename. Set readOnly: true to prevent editing. It resolves after the document opens.

editor.export()

Resolves to { bytes, fileName } with the current DOCX. Wait for this promise before persisting or replacing the document.

editor.on(event, callback)

Subscribes to an event and returns an unsubscribe function. Supported events: ready, change, saving, saved, focusExit, and error.

editor.destroy()

Removes the iframe, message listener, and pending requests. Call it when the containing view is removed.

The iframe is exposed as editor.frame. The SDK sets its width to 100%, height to 100%, and minimum height to 600px. Set a height on the container, such as min-height: 700px; height: 85vh.

Keyboard users can choose Return to website in the editor header. The SDK restores focus to the last focused host-page control, or to the embedding container if none was focused, then emits focusExit.

TypeScript users can add the SDK declaration file to their project for editor and event types.

04 · LIFECYCLE

Listen for document state.

ready
The iframe connected and accepts requests.
change
The document content changed. Treat your stored copy as out of date.
saving
The editor is processing its current edit.
saved
The latest editor state is ready in memory. Call export() and save the returned bytes in your app.
focusExit
The embedded editor's “Return to website” control moved keyboard focus back to the last focused control in the host page. If no host control was focused, the SDK focuses the embedding container.
error
An asynchronous editor or connection problem. The callback receives an object with code and message. CONNECT_TIMEOUT means the initial connection is slow; the SDK keeps retrying and emits ready after it connects. REQUEST_TIMEOUT means the operation may still finish. The SDK keeps its in-flight reservation, blocks new loads and blank documents, and allows an export to recover the current file. RELOADED means the iframe lost its in-memory document; after the next ready event, load the latest DOCX persisted by your app.

05 · TRUST BOUNDARY

Security and privacy

  • Use an exact HTTPS origin for your app and an exact HTTPS URL for the editor. HTTP is accepted for localhost development.
  • Host the editor on a different origin from your website. The SDK refuses same-origin framing so its sandbox keeps a real boundary.
  • The iframe accepts messages only from the parent origin supplied by the SDK. The SDK checks both the iframe source and its origin.
  • Document bytes travel through the window message channel, never through a URL parameter.
  • The integration does not send your app's account credentials or tokens to docduck. Keep authorization and storage in your own app.
  • Use the sandbox and referrerPolicy set by the SDK. Do not modify the generated frame's security attributes.

Load the SDK from https://docduck.io/embed.js and point the frame to https://docduck.io/embed. Your website must allow that origin in its Content Security Policy frame-src and script-src directives. This starting policy was checked against the live SDK from a separate host using a fresh nonce for every response.

Example host response header

Content Security Policy
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-YOUR_RESPONSE_CSP_NONCE'; style-src 'self' 'nonce-YOUR_RESPONSE_CSP_NONCE'; frame-src https://docduck.io; object-src 'none'; base-uri 'self'

Generate one unpredictable nonce for each response and use the same value in this header and both script tags. Merge these directives into your existing policy and preserve any additional sources your website needs. If you use 'strict-dynamic', keep the nonce on the SDK script tag; the editor still needs to be allowed by frame-src.

06 · SUPPORTED FILES

DOCX files up to 20 MB

The embed interface accepts Word Open XML .docx files up to 20,000,000 bytes. Legacy .doc and PDF files are not editable. Validate upload size and file type in your app as well as handling SDK errors.

The SDK and frame limit in-flight document data to 40 MB across requests. Each pending export reserves its maximum 20 MB response size. Queue overflow returns a BUSY error, so wait for current document operations to finish before sending more requests.

The editor may use substitute fonts in its preview. Ask people to check page breaks and layout in their downloaded document using Microsoft Word or another DOCX-compatible app.

07 · RECOVERY

Handle a reload without losing track.

The editor session holds the working copy in memory. If its iframe reloads, the SDK rejects pending requests and emits an error event. Keep your last persisted DOCX in your own storage so the host can load it again.

REQUEST_TIMEOUT has an uncertain outcome: the operation may still finish in the iframe. Keep the frame open and call export() to recover the current document. The SDK blocks further loads and blank-document creation until that export succeeds. If the iframe reloads, its in-memory document is gone. The SDK emits RELOADED, reconnects, and allows a new load after ready. Load the latest DOCX your app persisted before continuing. A reload cannot recover edits that your app had not persisted.

NEXT

Try the integration before you wire it in.

The interactive demo lets you open a DOCX, edit it in an iframe, and export it in the current browser session.

Open embed demo