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, ornulllist({ cursor, limit })andquery(filter)to read many. Each returns a page,{ items, nextCursor }; passnextCursorback ascursorfor the next page.set(id, doc),update(id, patch), anddelete(id)to writesubscribe(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.