Open Artifacts

Runtime API

Let HTML artifacts save and read data with window.openArtifacts.

An HTML artifact can keep data with window.openArtifacts. Polls, trackers, and checklists all work this way. The data belongs to the artifact, not a version, so a new version sees the same data.

Ready and theme

openArtifacts.ready is a promise that resolves once the viewer shell has connected; wait for it before your first call. In the raw view, with no shell, it resolves at once.

openArtifacts.theme is "light" or "dark". It starts from the viewer’s system setting and follows the shell’s theme once it connects. onTheme(callback) calls back whenever it changes and returns a function that stops listening.

await openArtifacts.ready;
document.documentElement.dataset.theme = openArtifacts.theme;
openArtifacts.onTheme((t) => (document.documentElement.dataset.theme = t));

Capabilities

An artifact declares what it uses when it’s published, for example capabilities: { db: true, viewer: true }. Calls to anything it didn’t declare are rejected.

Capability Gives the page
db Shared data that every viewer sees
viewer Who is viewing
storage Private data for each viewer

Shared data: db

db.collection(name) returns a collection with:

  • get(id) to read one document, or null
  • list({ cursor, limit }) and query(filter) to read many. Each returns a page, { items, nextCursor }; pass nextCursor back as cursor for the next page.
  • set(id, doc), update(id, patch), and delete(id) to write
  • subscribe(callback) for live updates when anyone changes the collection. It returns a function that unsubscribes.

Each document is { id, data, updatedAt, updatedBy }, with your value in data.

Each subscribe callback gets { docs, deleted }: the documents that changed and the ids of those deleted. If the live connection drops and reconnects, changes may have been missed, so the callback gets { docs: [], deleted: [], resync: true } instead. On resync, refetch with list() or query().

const votes = openArtifacts.db.collection("votes");
const all = new Map();
const refetch = async () => {
  all.clear();
  for (const doc of (await votes.list()).items) all.set(doc.id, doc);
  render([...all.values()]);
};
votes.subscribe((e) => {
  if (e.resync) return refetch();
  for (const doc of e.docs) all.set(doc.id, doc);
  for (const id of e.deleted) all.delete(id);
  render([...all.values()]);
});
await refetch();
await votes.set(crypto.randomUUID(), { option: "Lisbon" });

The viewer: viewer

viewer.get() returns { email, name }, or null for an anonymous viewer of a public artifact.

Per-viewer data: storage

storage keeps documents keyed to the current viewer, such as which boxes they ticked, with get(key), set(key, value), delete(key), and list(), which returns the viewer’s keys.

Who can write

  • Signed-in viewers who can see the artifact can read and write.
  • Anonymous viewers of a public artifact can read. They can write only if the publisher turned on allowAnonymousWrites, and those writes are rate-limited per IP address.

Limits

Limit Default
Document size 1 MB
Data per artifact 50 MB
Writes per viewer 100 per minute
Anonymous writes per IP 30 per minute

How it works

The artifact runs in a sandboxed iframe with no credentials. Each call goes by postMessage to the viewer shell around it, which checks the call and makes it with the viewer’s session. Raw view drops the shell, so the runtime API isn’t available there.