EmbedPDF

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`);
});
FieldWhat it is
origin.kind'local' when the change was made through this doc, 'remote' when it wasn’t.
origin.subThe user who made it (on the cloud).
origin.tsWhen it happened, in milliseconds.
origin.txThe write it came from: its opId, and its place among that write’s events.
origin.undoOfOn 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#

TypeWhen
annotations.created, .updated, .deletedAn annotation changed.
annotations.reorderedA page’s annotations were restacked. order is the page’s new order, bottom to top.
annotations.restoredAn undo brought back deleted annotations.
annotations.flattenedAnnotations were baked into a page.
pages.reordered, .rotated, .deleted, .insertedThe page order or rotation changed.
pages.namedA page name was added or removed.
pages.flattenedAll annotations of pages were baked in.
pages.scaleSetA page’s measurement scale changed.
forms.valueSetA field was filled in.
forms.created, .updated, .deleted, .widgetAdded, .widgetRemovedA field’s structure changed.
forms.widgetUpdated, forms.widgetsReorderedA widget was moved or restyled, or a page’s widgets were restacked.
forms.widgetDeleted, forms.widgetRestoredA widget was deleted, or an undo brought it back.
forms.calculationsReorderedThe order calculate scripts run in changed.
forms.restoredAn undo brought back a deleted field.
forms.repairedMany values or fields changed at once.
attachments.created, .deletedAn attached file was added or removed.
metadata.updatedThe title, author or another standard metadata field.
metadata.customUpdatedYour own metadata keys.
redaction.appliedRedactions were applied.
signatures.prepared, .completed, .cancelledA signature was started or finished.
document.versionedA 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.kind is about the open document, not the user. “Did I do this?” (undo, optimistic UI): check origin.kind. “Did this user do it?”: check origin.sub.
Was this page helpful?

Your feedback goes directly to the documentation team.