Events
Every change to a document produces an event: an annotation created, a page rotated, a field filled in. Listen to them to keep your interface in sync without reading the document again.
const stop = doc.events.on('annotations.created', (event) => {
addToSidebar(event.annotation);
});
// later
stop();on() returns a function that stops listening. To hear every event, use subscribe():
const stop = doc.events.subscribe((event) => {
console.log(event.type);
});What an event contains#
An event carries exactly what the call that made the change returned, plus its type and where
it came from (origin). The event for create() has the same annotation that create()
resolved with:
doc.events.on('annotations.updated', ({ annotation, origin }) => {
if (origin.kind === 'remote') showToast(`${annotation.author} changed a comment`);
});| Field | What it is |
|---|---|
origin.kind | 'local' when the change was made through this doc, 'remote' when it wasn’t. |
origin.sub | The user who made it (on the cloud). |
origin.ts | When it happened, in milliseconds. |
origin.tx | The write it came from: its opId, and its place among that write’s events. |
origin.undoOf | On an undo’s events: the opId of the change it undid. |
An event fires after the change is saved, never before, and before the call’s promise resolves.
By the time await page.annotations.create(…) returns, your listeners have run.
Every event#
| Type | When |
|---|---|
annotations.created, .updated, .deleted | An annotation changed. |
annotations.reordered | A page’s annotations were restacked. order is the page’s new order, bottom to top. |
annotations.restored | An undo brought back deleted annotations. |
annotations.flattened | Annotations were baked into a page. |
pages.reordered, .rotated, .deleted, .inserted | The page order or rotation changed. |
pages.named | A page name was added or removed. |
pages.flattened | All annotations of pages were baked in. |
pages.scaleSet | A page’s measurement scale changed. |
forms.valueSet | A field was filled in. |
forms.created, .updated, .deleted, .widgetAdded, .widgetRemoved | A field’s structure changed. |
forms.widgetUpdated, forms.widgetsReordered | A widget was moved or restyled, or a page’s widgets were restacked. |
forms.widgetDeleted, forms.widgetRestored | A widget was deleted, or an undo brought it back. |
forms.calculationsReordered | The order calculate scripts run in changed. |
forms.restored | An undo brought back a deleted field. |
forms.repaired | Many values or fields changed at once. |
attachments.created, .deleted | An attached file was added or removed. |
metadata.updated | The title, author or another standard metadata field. |
metadata.customUpdated | Your own metadata keys. |
redaction.applied | Redactions were applied. |
signatures.prepared, .completed, .cancelled | A signature was started or finished. |
document.versioned | A new version of the document was saved, such as after signing. Read what you show again. |
stream.desynced | (cloud) Events were missed. Read what you show again. |
Changes that touch one page (annotations) carry that page, so you can update only what’s on
screen.
Which write an event came from#
Every write takes an opId in its options, a name for the write, and every event it fires carries
it as origin.tx.id. Leave it out and the engine makes one up. That’s how you match an event to a
change you made:
const opId = crypto.randomUUID();
doc.events.on('annotations.updated', ({ origin }) => {
if (origin.tx?.id === opId) markSaved();
});
await page.annotations.update(ref, { contents: 'Seen' }, { opId });An opId is 1 to 255 visible ASCII characters (what an HTTP header can carry), on every engine.
On the cloud, a write retried with the same opId is applied once, and the retry gets the first
answer. The cloud engine retries a write lost on the network itself, under the same opId, so use
a fresh opId for every write. A change, and the
annotation, form and metadata calls a change can hold, do the same on the local engine.
The opId is also how you undo a write: doc.apply({ undoOf: opId }). See
Changes & undo.
Some calls make many changes at once. An import of 50
annotations fires 50 annotations.created events, one after the other, all with the same
origin.tx:
doc.events.on('annotations.created', ({ annotation, origin }) => {
addToSidebar(annotation);
if (origin.tx && origin.tx.index === origin.tx.count - 1) redrawSidebar(); // the last of the batch
});tx.id is the same for all of them, and tx.index counts from 0 to tx.count - 1. A write
that makes one change fires one event, with tx.index 0 and tx.count 1.
Who hears what#
The local engine tells you about changes made through this engine. Two tabs with the same file are two separate copies: a change in one doesn’t reach the other.
Good to know#
- Each change arrives once. Your own changes aren’t sent back to you a second time.
- A listener that throws doesn’t stop the other listeners, and doesn’t make the call fail.
origin.kindis about the open document, not the user. “Did I do this?” (undo, optimistic UI): checkorigin.kind. “Did this user do it?”: checkorigin.sub.
Your feedback goes directly to the documentation team.