> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superdoc.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

SuperDoc uses an event system for lifecycle hooks and change notifications.

<Note>
  **Driving live React UI state? Prefer `superdoc/ui` subscriptions.** `useSuperDocSelection`, `useSuperDocComments`, `useSuperDocTrackChanges`, `useSuperDocDocument`, and `useSuperDocCommand` give you typed, memoized, per-slice state with one re-render per change instead of a manual `superdoc.on('editor-update', ...)` loop. See [Custom UI](/editor/custom-ui/overview).

  The events on this page are for lifecycle (`ready`, `editor-create`), integration (`error`, `comment-changed`), analytics, and compatibility with code that doesn't use the React surface.
</Note>

## Subscribing to events

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('ready', handler);     // Subscribe
  superdoc.once('ready', handler);   // One-time listener
  superdoc.off('ready', handler);    // Unsubscribe
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  /** @param {import('superdoc').SuperDocReadyPayload} payload */
  const handler = ({ superdoc }) => {
    console.log('Handler called');
  };

  superdoc.on('ready', handler);     // Subscribe
  superdoc.once('ready', handler);   // One-time listener
  superdoc.off('ready', handler);    // Unsubscribe
  ```
</CodeGroup>

## Lifecycle events

### `ready`

Fired when SuperDoc is fully initialized.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('ready', ({ superdoc }) => {
    // Safe to use all features
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('ready', ({ superdoc }) => {
    // Safe to use all features
  });
  ```
</CodeGroup>

### `editorBeforeCreate`

Fired before an editor is created. Use this to configure extensions or set up services.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('editorBeforeCreate', ({ editor }) => {
    // Configure before editor mounts
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('editorBeforeCreate', ({ editor }) => {
    // Configure before editor mounts
  });
  ```
</CodeGroup>

### `editorCreate`

When an editor is created.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('editorCreate', ({ editor }) => {
    editor.focus();
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('editorCreate', ({ editor }) => {
    editor.focus();
  });
  ```
</CodeGroup>

### `editorDestroy`

When an editor is destroyed.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('editorDestroy', () => {
    cleanup();
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('editorDestroy', () => {
    cleanup();
  });
  ```
</CodeGroup>

## Content events

### `editor-update`

When editor content changes. Use this to refresh live UI state like word counts or auto-save.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('editor-update', ({ editor }) => {
    if (!editor) return;
    autoSave(editor.getJSON());
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('editor-update', ({ editor }) => {
    if (!editor) return;
    autoSave(editor.getJSON());
  });
  ```
</CodeGroup>

**Live counter example:** Read `editor.doc.info()` inside the handler to build a live document-stats panel without polling.

```javascript theme={null}
superdoc.on('editor-update', ({ editor }) => {
  const { counts } = editor.doc.info();
  document.getElementById('stats').textContent =
    `${counts.words} words, ${counts.characters} characters, ` +
    `${counts.trackedChanges} tracked changes, ${counts.lists} lists`;
});
```

### `content-error`

When content processing fails.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('content-error', ({ error, editor }) => {
    console.error('Content error:', error);
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('content-error', ({ error, editor }) => {
    console.error('Content error:', error);
  });
  ```
</CodeGroup>

### `fonts-resolved`

When document fonts are resolved.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('fonts-resolved', ({ documentFonts, unsupportedFonts }) => {
    if (unsupportedFonts.length > 0) {
      console.warn('Unsupported fonts:', unsupportedFonts.join(', '));
    }
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('fonts-resolved', ({ documentFonts, unsupportedFonts }) => {
    if (unsupportedFonts.length > 0) {
      console.warn('Unsupported fonts:', unsupportedFonts.join(', '));
    }
  });
  ```
</CodeGroup>

## Content controls (SDT fields)

### `content-control:active-change`

Fires when selection enters a content control, switches between controls, or leaves all controls.

```javascript theme={null}
superdoc.on('content-control:active-change', ({ active, previous, activePath, source }) => {
  // source: 'keyboard' | 'pointer'
  // active / previous: SdtRef | null
  // activePath: SdtRef[] - full active stack, innermost first ([] when none)
});
```

`SdtRef` shape:

```ts theme={null}
type SdtRef = {
  id: string;
  tag?: string;
  alias?: string;
  controlType: string;
  scope: 'inline' | 'block';
};
```

`active` is the deepest control (`activePath[0]`); `activePath` holds the full stack, innermost first. Controls without an `id` do not emit these events.

How to interpret:

1. Focus (`null -> A`): `previous === null && active !== null`
2. Switch (`A -> B`): `previous !== null && active !== null && previous.id !== active.id`
3. Blur (`A -> null`): `previous !== null && active === null`

### `content-control:click`

Fires on pointer click inside a content control.

```javascript theme={null}
superdoc.on('content-control:click', ({ target, source }) => {
  // source is always 'pointer'
  // target: SdtRef
});
```

Both are also available as config callbacks: `onContentControlActiveChange` and `onContentControlClick`.

To build your own content-control UI on these events, see [Custom UI > Content controls](/editor/custom-ui/content-controls).

## Comments events

### `comments-update`

When comments are modified.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('comments-update', ({ type, comment, changes, pendingSelection }) => {
    // type: 'pending', 'add', 'update', 'deleted', 'resolved'
    // comment: the comment object (when applicable)
    // changes: per-field change set (when the update is a mutation)
    // pendingSelection: SelectionInfo | null on 'pending'
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('comments-update', ({ type, comment, changes, pendingSelection }) => {
    // type: 'pending', 'add', 'update', 'deleted', 'resolved'
    // comment: the comment object (when applicable)
    // changes: per-field change set (when the update is a mutation)
    // pendingSelection: SelectionInfo | null on 'pending'
  });
  ```
</CodeGroup>

## Collaboration events

### `collaboration-ready`

When collaboration is initialized.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('collaboration-ready', ({ editor }) => {
    showOnlineUsers();
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('collaboration-ready', ({ editor }) => {
    showOnlineUsers();
  });
  ```
</CodeGroup>

### `awareness-update`

When user presence changes.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('awareness-update', ({ states, added, removed }) => {
    updateUserCursors(states);
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('awareness-update', ({ states, added, removed }) => {
    updateUserCursors(states);
  });
  ```
</CodeGroup>

### `locked`

When document lock state changes.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('locked', ({ isLocked, lockedBy }) => {
    if (isLocked && lockedBy) {
      showLockBanner(`Locked by ${lockedBy.name}`);
    }
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('locked', ({ isLocked, lockedBy }) => {
    if (isLocked && lockedBy) {
      showLockBanner(`Locked by ${lockedBy.name}`);
    }
  });
  ```
</CodeGroup>

## Pagination events

### `pagination-update`

Fired after each layout pass with the current page count. Use this to know when page data is available: `activeEditor.currentTotalPages` is only populated after the first layout completes, which happens *after* the `ready` event.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('pagination-update', ({ totalPages, superdoc }) => {
    console.log(`Document has ${totalPages} pages`);
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('pagination-update', ({ totalPages, superdoc }) => {
    console.log(`Document has ${totalPages} pages`);
  });
  ```
</CodeGroup>

## UI events

### `zoomChange`

When the zoom level changes, from any source: `setZoom()`, the toolbar zoom control, or [`fit-width` mode](/editor/superdoc/configuration#param-zoom). The payload carries the value and the mode that produced it. Also available as the `onZoomChange` config callback.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('zoomChange', ({ zoom, mode }) => {
    console.log(`Zoom: ${zoom}% (${mode})`);
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('zoomChange', ({ zoom, mode }) => {
    console.log(`Zoom: ${zoom}% (${mode})`);
  });
  ```
</CodeGroup>

### `viewport-change`

When the fit-width calculation changes. Pixel-level width changes that do not affect the rounded fit are deduped. `getViewportMetrics()` always reads the latest measurements.

* `availableWidth` - container width in pixels, minus the comments sidebar when visible
* `documentWidth` - the widest document page width in pixels at 100% zoom (zoom-independent; DOCX from laid-out pages with page-styles fallback, PDF from rendered pages)
* `fitZoom` - the unclamped zoom percentage that fits the page into the available width

HTML documents reflow to the container, so an HTML-only instance reports no metrics.

For most use cases, prefer [`zoom.mode: 'fit-width'`](/editor/superdoc/configuration#param-zoom). Subscribe to this event only when you want to apply custom zoom behavior.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('viewport-change', ({ fitZoom }) => {
    superdoc.setZoom(Math.min(100, Math.max(35, fitZoom)));
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    onViewportChange: ({ availableWidth, documentWidth, fitZoom }) => {
      console.log(`Need ${fitZoom}% to fit ${documentWidth}px into ${availableWidth}px`);
    },
  });
  ```
</CodeGroup>

### `sidebar-toggle`

When the comments sidebar is toggled.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('sidebar-toggle', (isOpened) => {
    adjustLayout(isOpened);
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('sidebar-toggle', (isOpened) => {
    adjustLayout(isOpened);
  });
  ```
</CodeGroup>

## Error events

### `exception`

When an error occurs during document processing or runtime.

<CodeGroup>
  ```javascript Usage theme={null}
  superdoc.on('exception', (payload) => {
    // `payload` is a discriminated union; narrow before reading variant-specific fields.
    // `code` is only on the editor-lifecycle variant; `stage` is only on document-init.
    console.error('SuperDoc error:', payload.error);
  });
  ```

  ```javascript Full Example theme={null}
  import { SuperDoc } from 'superdoc';
  import 'superdoc/style.css';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
  });

  superdoc.on('exception', (payload) => {
    // `payload` is a discriminated union; narrow before reading variant-specific fields.
    // `code` is only on the editor-lifecycle variant; `stage` is only on document-init.
    console.error('SuperDoc error:', payload.error);
  });
  ```
</CodeGroup>

## Configuration-based events

Events can also be set during initialization:

```javascript theme={null}
new SuperDoc({
  selector: '#editor',
  document: 'document.docx',
  onReady: ({ superdoc }) => { },
  onEditorBeforeCreate: ({ editor }) => { },
  onEditorCreate: ({ editor }) => { },
  onEditorUpdate: ({ editor }) => { },
  onFontsResolved: ({ documentFonts, unsupportedFonts }) => { },
  onPaginationUpdate: ({ totalPages, superdoc }) => { },
  onSidebarToggle: (isOpened) => { },
  onException: ({ error }) => { },
});
```

## Event order

1. `editorBeforeCreate`: Before editor mounts
2. `editorCreate`: Editor ready
3. `ready`: All editors ready
4. `collaboration-ready`: If collaboration enabled
5. `pagination-update`: After each layout pass (page count available)
6. Runtime events (`editor-update`, `comments-update`, `sidebar-toggle`, etc.)
7. `editorDestroy`: Cleanup
