Authoring
me().permissions.canCreateCanvas reports whether page-driven creation is enabled
for this member. It does not grant permission to edit or publish an existing canvas;
target-canvas management roles still apply. Resource participation defaults do not
enable authoring. See Permissions and defaults.
Let a signed-in viewer of your canvas create and manage canvases from the page —
as themselves, with real ownership and no secret in the browser. A share created here
is a managed artifact: you publish it once, update it in place (same URL) as its
content changes, list your shares with their live status, and revoke one when it's
done. The canvasdrop.canvases namespace wraps publish, update, list, and revoke.
This is the authoring capability. It is off by default and higher-privilege than the other primitives: the owner turns it on in the Backend tab and the operator must enable it for the instance. Guests and public-link visitors can't use it — creation needs a signed-in org member.
// Create a durable share once.
const share = await canvasdrop.canvases.publish({
title: "Team roadmap — Q3 snapshot",
access: "public_link", // audience; defaults to Restricted (`private`)
password: "optional-extra-lock", // independent of the audience
tags: ["roadmap"],
metadata: { sourceApp: "product-roadmap", sourceKind: "roadmap-share", theme: "light" },
expiresAt: Date.now() + 7 * 864e5, // optional; the operator may require/limit it
bundle: zipBlob, // a static-site zip (Blob or ArrayBuffer)
});
// share.url is the live canvas; it now appears in the viewer's own dashboard.
// Later — update IN PLACE. The URL never changes; a new version is deployed.
await canvasdrop.canvases.update(share.id, {
bundle: newZipBlob, // omit to change only settings/metadata
metadata: { ...share.metadata, itemCount: 42, generatedAt: Date.now() },
});
// The management view — filter to this app's shares.
const mine = await canvasdrop.canvases.list({ sourceApp: "product-roadmap" });
// → [{ id, url, title, status, access, expiresAt, updatedAt, metadata, ... }]
// Retire a share. Its URL goes dead, but it STAYS in list() as status "revoked".
await canvasdrop.canvases.revoke(share.id);
Methods
| Method | Returns |
|---|---|
publish(options) |
Promise<AuthoredCanvas> — creates, deploys, and configures a new share |
update(id, options) |
Promise<AuthoredCanvas> — replaces the bundle and/or settings at the same URL |
list(filter?) |
Promise<AuthoredCanvas[]> — the viewer's shares (incl. revoked/expired), optionally filtered |
revoke(id) |
Promise<void> — make a share's public URL unavailable; it stays listed as revoked |
AuthoredCanvas
What publish, update, and each list entry return — a share plus its management state:
| Field | Type | Notes |
|---|---|---|
id / url |
string |
stable id and public URL (the URL never changes across updates) |
title / tags |
string / string[] |
|
access |
string |
the persisted General-access value (private, whole_org, public_link, or a legacy alias of private: specific_people / team) |
accessMode |
"restricted" | "whole_org" | "public_link" |
who else can open it beyond the people-and-teams list, which applies at every value; private and its aliases read restricted. Branch on this, not on access |
publicationStatus |
"draft" | "published" | "expired" | "unpublished" | "archived" | "disabled" |
whether it is published, independent of the audience; precedence disabled › archived › unpublished (revoked) › draft (no version) › expired › published. Deleted canvases are omitted from every authoring response |
hasPassword |
boolean |
whether an additional password lock is currently set |
discoverability |
"link_only" | "listed" |
whether eligible org/team shares appear in discovery surfaces |
galleryTemplatable |
boolean |
whether gallery viewers may use the canvas as a template |
viewerRole |
"owner" | "editor" | "admin" |
why the current viewer may manage this record |
audienceSummary |
{ count: number | null; names: string[] } |
safe summary of the viewer people and teams on the list (count = both; names = the teams) |
status |
"live" | "expired" | "revoked" | "private" |
deprecated; frozen for older clients. private means the persisted value is literally private (the people on the list can still open it), and the legacy aliases read live. Read accessMode + publicationStatus instead |
createdAt / updatedAt |
number |
unix ms |
expiresAt / revokedAt |
number | null |
share expiry; when it was revoked |
galleryListed |
boolean |
whether the canvas is explicitly listed in the gallery |
createdBy |
string |
the creator (owner) id |
version |
string | null |
the current version id — advances on every bundle deploy (a change signal) |
bundleUpdatedAt |
number |
legacy last-change stamp; use version to detect bundle changes |
sourceApp / sourceKind |
string | null |
pulled from metadata (the filterable dimensions) |
metadata |
Record<string, unknown> |
your free-form blob, round-tripped verbatim |
publish options
| Field | Type | Notes |
|---|---|---|
title |
string |
required |
bundle |
Blob | ArrayBuffer |
required — the static-site zip |
slug |
string? |
omit for a readable-random slug |
tags |
string[]? |
|
access |
"private" | "specific_people" | "whole_org" | "public_link" | "password"? |
General access; defaults to "private" (Restricted: the people-and-teams list only). "specific_people" is a legacy alias of "private"; "password" remains a compatibility shorthand for public link + password |
password |
string? |
optional extra lock on any audience; required with the "password" shorthand |
expiresAt |
number? |
unix ms; the operator may require an expiry and cap how far out it may be |
metadata |
Record<string, unknown>? |
free-form structured state (sourceApp/sourceKind/theme/…), bounded in size |
update options
Every field is optional — omitted fields leave the share unchanged. password and
expiresAt accept null to explicitly clear them. Omit bundle to change only
settings/metadata (the URL and current version stay put).
| Field | Type | Notes |
|---|---|---|
bundle |
Blob | ArrayBuffer? |
a new static-site zip → a new immutable version at the same URL |
title |
string? |
|
tags |
string[]? |
|
access |
"private" | "specific_people" | "whole_org" | "public_link" | "password"? |
re-checks the same operator gates as publish |
password |
string | null? |
set, or null to clear |
expiresAt |
number | null? |
set, or null to clear |
metadata |
Record<string, unknown>? |
replaces the stored blob |
expectedUpdatedAt |
number? |
compare-and-swap token from the last read; stale updates reject with SHARE_CONFLICT |
publish and update each send one multipart/form-data request
(credentials: "include"): a JSON metadata part plus (optionally, for update) the
zip bundle part.
list filter
list() returns every canvas record the viewer can currently manage as owner or editor,
including draft, unpublished, expired, archived, and admin-disabled rows. Deleted
canvases are omitted. publicationStatus makes those lifecycle states explicit. Pass a
filter to narrow server-side:
await canvasdrop.canvases.list({ sourceApp: "product-roadmap" });
await canvasdrop.canvases.list({ sourceKind: "roadmap-share", tags: ["q3"] });
{ sourceApp?, sourceKind?, tags? } — sourceApp/sourceKind match the values inside
metadata; tags matches shares carrying every listed tag.
Update, revoke, and status
- Update is in place.
updatedeploys a new immutable version to the same canvas — the public URL never changes, and version history + one-click rollback are preserved. Use it instead ofpublishwhen re-sharing the same artifact. - Unpublish keeps the record.
revokemakes the public URL unavailable (readers get not-found) and stampsrevokedAt, but the share stays inlist()asstatus: "revoked". Callingupdatewith a bundle publishes it again at the same URL and clearsrevokedAt. A settings-only update while unpublished rejects withSHARE_REVOKED. - Two independent facts, not one status.
accessModesays who else can open the share (restricted,whole_org,public_link);publicationStatussays whether it is published (draft,published,expired,unpublished,archived,disabled). Arestrictedshare that ispublishedis open to everyone on its people-and-teams list. The olderstatusfield (revoked›expired›private›live) conflated the two and is kept only for existing clients: itsprivatedoes not mean owner-only. - Reader isolation. A public/permitted reader receives only the shared static
content — never
metadata,createdBy,status, or any management field. Those are assembled only on the authenticated management API.
Ownership, quotas, and authorization
- The new share is owned by the viewer who called
publish. It counts against their normal canvas ownership and a per-viewer authoring quota (a daily and an all-time cap the operator sets). - Quota is consumed the moment the canvas is created by
publish— aPublishFailedErrorstill counts, since the canvas exists.updatedoes not consume quota (it edits an existing share).revokedoes not refund quota. listis scoped to shares the viewer currently manages as owner or editor. An administrator may still update or revoke a known share id through the explicit admin allowance; an unauthorized id reads as not-found.expiresAtuses the same share-expiry mechanism as the dashboard, so it only means something for people other than the owner and editors.
Errors
Every method rejects with a CanvasdropError subclass — catch the one you care about,
or read err.code / err.status.
- Not a signed-in member (a guest / public visitor) →
NotAuthenticatedError(status: 401,code: "NOT_AUTHENTICATED"). - Authoring off (backend off, the per-canvas toggle off, or the operator switch off) →
CapabilityDisabledError(status: 403,code: "CAPABILITY_DISABLED"). - Per-viewer daily or all-time cap hit →
QuotaExceededError(status: 429,code: "QUOTA_EXCEEDED", with ascopeofuser_dailyoruser_total). - Invalid request (bundle or metadata too large, a disallowed access rung, a
missing/over-max expiry) →
CanvasdropError(status: 400/413,code: "INVALID_BODY"). - settings-only
updateon a share that's unpublished →CanvasdropError(status: 409,code: "SHARE_REVOKED") — include a bundle to publish it again. - An
expectedUpdatedAttoken is stale →CanvasdropError(status: 409,code: "SHARE_CONFLICT") with the current share state; refresh before retrying. - The canvas was created but its deploy or share-config failed →
PublishFailedError(status: 502,code: "PUBLISH_FAILED"). Its.idis the new canvas's id, so you can retry the publish orcanvasdrop.canvases.revoke(id). updatesaved the settings but the bundle deploy then failed →UpdatePartialError(status: 502,code: "UPDATE_PARTIAL")..stagenames the failed stage and.currentis the saved share state, so nothing you changed is lost; retry theupdatewith the bundle.
See error codes. The underlying HTTP endpoints live under
/v1/c/<slug>/authoring — see the Runtime API.