Robutler

host.content

host.content persists bytes for an app instance: uploads, recorded audio, generated images, exported video, a cloned-voice sample. Unlike host.kv (small JSON values), content is stored as real content under a per-instance workspace folder, so it survives reloads and rides the workspace share-cascade (everyone who can see the app can read its content).

Reach for it when a value is too big for KV, is binary, or needs to be handed to an <img>, <audio>, or <video> element.

At a glance

interface ContentNamespace {
  put(
    data: Blob | ArrayBuffer | ArrayBufferView | string,
    opts?: { name?: string; mime?: string; scope?: ContentScope },
  ): Promise<{ id: string; key: string }>;
  get(key: string, opts?: { scope?: ContentScope }): Promise<Blob | null>;
  list(opts?: { scope?: ContentScope }): Promise<ContentItem[]>;
  delete(key: string, opts?: { scope?: ContentScope }): Promise<void>;
  pick(opts?: {
    accept?: string[];
    multi?: boolean;
    kinds?: ('file' | 'url')[];
  }): Promise<Array<{ kind: 'file' | 'url'; id?: string; name?: string; mime?: string; size?: number; url?: string }> | null>;
  read(id: string): Promise<Blob | null>;
  export(
    data: Blob | ArrayBuffer | string,
    opts?: { name?: string; mime?: string; visibility?: 'private' | 'public' | 'unlisted' },
  ): Promise<{ id: string; url?: string }>;
}

interface ContentItem { id: string; name: string; mime: string; size: number; createdAt: string }
MethodUse
put(data, opts?)Store bytes; returns { id, key }.
get(key, opts?)Read bytes back as a Blob (or null).
list(opts?)Enumerate this instance's stored items.
delete(key, opts?)Remove an item.
pick(opts?)Open the host's library picker; returns what the user chose.
read(id)Read the bytes of a picked library item.
export(data, opts?)Save app output into the user's library (MY LIBRARY).

put accepts a Blob, ArrayBuffer, TypedArray, data: URI, or plain string. get always resolves a Blob. Wrap it in URL.createObjectURL() for a media element src.

Store and read back

await host.ready();

// `key` is the stable handle you persist (e.g. in host.kv).
const { id, key } = await host.content.put(recordingBlob, {
  name: 'take-01.webm',
  mime: 'audio/webm',
});
await host.kv.set('lastTake', key);

// ...after a reload:
const stored = await host.kv.get('lastTake');
const blob = stored && (await host.content.get(stored));
if (blob) {
  audioEl.src = URL.createObjectURL(blob); // revokeObjectURL when done
}

List and delete

const items = await host.content.list();
// → [{ id, name, mime, size, createdAt }, ...]

for (const it of items) {
  if (it.mime.startsWith('image/')) await host.content.delete(it.id);
}

Let the user pick from their library

pick opens the host's picker over the canvas and returns only what the user selects. The app never sees the rest of the library. Read a picked file's bytes with read(id) (the host grant-gates the access).

const picks = await host.content.pick({ accept: ['image/*'], multi: false });
if (picks?.length) {
  const file = picks[0]; // { kind, id?, name?, mime?, size?, url? }
  if (file.kind === 'file' && file.id) {
    const blob = await host.content.read(file.id);
    if (blob) imgEl.src = URL.createObjectURL(blob);
  } else if (file.kind === 'url' && file.url) {
    imgEl.src = file.url;
  }
}

Export to the user's library

export saves app output as a top-level item in MY LIBRARY (a shareable content row), distinct from the instance-scoped put.

const { id, url } = await host.content.export(renderedPngBlob, {
  name: 'poster.png',
  mime: 'image/png',
  visibility: 'unlisted', // 'private' (default) | 'unlisted' | 'public'
});

Scopes

Every method takes an optional scope. Today only instance (this app instance's own folder, the default) is implemented. widget (shared across instances of an app type) and user (the viewer's own content) are reserved and currently rejected with invalid_args. Do not depend on them yet.

await host.content.put(blob, { scope: 'instance' }); // explicit default

Notes

  • Content is per instance. For cross-instance shared data, publish a document with host.documents or call a shared host.fn.
  • Stored content participates in the workspace share-cascade. Treat it as visible to every collaborator on the workspace, not as private to the author.
  • Revoke object URLs (URL.revokeObjectURL) when you swap a media src to avoid leaking blobs.
  • host.kv: small JSON values
  • host.documents: user-owned, version-tracked documents
  • host.fn: server-side functions (shared, billable)
  • Error codes: invalid_args, quota_exceeded

On this page