Artifacts reference
Follow Share pages from Claude to share and open pages. The HTTP routes are in the endpoint reference, under privacy and observability and the connect contract. Who owns a page's data, and what an erase removes, is in Data boundary.
What can be shared
| Type | Files | Largest |
|---|---|---|
| HTML | .html, .htm | 5 MiB |
| Markdown | .md, .markdown | 5 MiB |
| SVG | .svg | 5 MiB. Shown as an image, so its scripts do not run |
| React | .jsx, .tsx | 200 KiB of source, compiled into an HTML page of at most 2 MiB |
A React file needs a default-exported component and gets five seconds to build. Imports may use
only react, react/jsx-runtime, react-dom and react-dom/client; others, including recharts
and lucide-react, are refused. Use inline styles or include your CSS in the component.
Tailwind-style classes compile and render unstyled because no Tailwind stylesheet is included.
| Limit | Value |
|---|---|
| Title | 200 characters |
| Share list | 100 people and 50 teams |
| Publishing | 10 a minute per key |
| Kept per owner | 500 pages or 250 MiB |
What a page can load
The console shows a page in a sandboxed frame with an origin of its own. The page can't read your
console session, call the console's API, or reach other pages. HTML pages run under a content
security policy: inline scripts (with eval) and inline styles only, fonts as data: URLs,
images and media as data: or blob: URLs, and no network requests, frames, workers or form
posts, including requests to the console itself.
Anything else it loads from outside shows blank.
Markdown pages run under a stricter policy with no scripts at all. Images must use data: or
blob: URLs, including images in raw HTML within Markdown. SVG is shown as an image, so its
scripts do not run.
When a page loads one of these libraries with a classic <script src> from jsDelivr, unpkg, cdnjs,
cdn.tailwindcss.com, d3js.org or code.jquery.com, at the file path that library publishes as
its browser build (such as chart.js@4/dist/chart.umd.min.js or react@18/umd/react.production.min.js),
the console embeds its own copy in the page before showing it. The version the page asks for is
ignored. Other file paths,
ES module imports and other libraries stay external, and the policy blocks them.
| Library | Version served |
|---|---|
| Chart.js | 4.5.1 |
| D3 | 7.9.0 |
| React and ReactDOM | 18.3.1 |
| Mermaid | 12.2.0 |
| Babel standalone | 7.29.10 |
| Tailwind (browser build) | 4.3.3 |
| jQuery | 3.7.1 |
Pages using the Tailwind v3 CDN get Tailwind 4, without CDN plugins such as ?plugins=forms. A
type="module" script is not replaced.
A Google Fonts <link> or @import for these families is embedded in the page (Latin and Latin
Extended): Archivo, Geist, Geist Mono, Hanken Grotesk, IBM Plex Mono, IBM Plex Sans, IBM Plex Sans
Condensed, JetBrains Mono, Newsreader, Public Sans and Schibsted Grotesk.
Browsers give a page no way to block WebRTC, so a page's own code could still use it to send what it reads elsewhere. Share a page that holds sensitive data only with people you trust with that data.
Page data
An HTML page keeps data through the claude.ai runtime. claude.use(name) returns a promise:
| Name | Resolves to |
|---|---|
db | The page's documents. null on a gateway older than page data |
user | The viewer |
permissions | state(name) returns granted for db and user and unavailable for anything else; state() returns { db: "granted", user: "granted" }. request(names) returns a name-to-status object (request() the same default object). manage() rejects with unavailable |
| Any other name | null |
If the page gets no answer about its data within 10 seconds of loading, every name resolves null
until the page reloads. If that first answer is a refusal instead, db resolves null while user
and permissions still work, and user.id() returns null.
<script>
(async () => {
const db = await claude.use("db");
const user = await claude.use("user");
if (!db) return; // no page data on this gateway
const me = await user.id();
await db.doc("votes/" + me).set({ choice: "blue" });
db.collection("votes").onSnapshot((snap) => {
document.title = snap.size + " votes";
});
})();
</script>
Documents
Paths alternate collection and document: votes is a collection, votes/a1 a document in it, and
votes/a1/notes a collection under that document.
| Call | What it gives |
|---|---|
db.collection(path) | .doc(id) (a new random id when omitted), .add(data), .where(field, op, value), .orderBy(field, "asc" or "desc"), .limit(n), .get(), .onSnapshot(next, error) |
db.doc(path) | .get(), .set(data), .update(data), .delete(), .acquire({ holder, ttlMs, data }), .onSnapshot(next, error), .collection(path) |
where compares a top-level field with ==, !=, <, <=, >, >=, in, not-in or
array-contains, and a query reads only the collection's own documents. set replaces a document;
update merges into one that exists. acquire takes a short lease (30 seconds unless ttlMs says
otherwise) and answers acquired and expiresAt; a busy lease never says who holds it.
onSnapshot re-reads when the page's data changes, checking every 5 seconds while the tab is
visible. A document snapshot has id, exists and data(); a query snapshot has docs, size,
empty and docChanges().
The viewer
user.id() is the id this page sees for the viewer. It differs on every page, and it is the <id>
in data/users/<id>. isOwner() and canEdit() are true for the author only, and
can("data.write") says whether publishing is on and the rules let the viewer write at the page's
root. A deeper rule can still refuse or allow a single document, so handle a refused write either way. Names, avatars and emails stay empty:
name() is "", email() and avatarUrl() are null, and search() finds nobody.
Errors
A data call that fails rejects with { code, message }. db.doc(), db.collection() and
.doc(id) throw a TypeError at once for a path or id that breaks the path rules. A read the rules
hide is not an error: the document reads as absent and drops out of queries.
code | Cause |
|---|---|
invalid_argument | A bad value, a write the access rules refuse, or a write while publishing is off |
quota_exceeded | The page holds its limit of documents or bytes |
resource_exhausted | Too many calls, or a query on a collection holding more than 5,000 documents or 8 MiB. Listeners back off and retry on their own, but a retry cannot fix an oversized collection |
revoked | The page is no longer shared with the viewer, or their sign-in ended |
unavailable | The gateway did not answer, or the page is a read-only Desktop copy |
Access rules
Without rules, everyone who can open the page reads and writes all of its data except each
person's data/users/<id> part, which nobody else can read or write, the author included. The
author sets rules as JSON in the page's Data access rules section in the console (Save, or
Clear to remove them). Publishing through
POST /connect/artifacts takes them
as dataRules; Claude's anyray_share sends none.
[
{ "path": "settings", "write": "owner" },
{ "path": "votes", "read": "owner", "write": "owner" },
{ "path": "votes/{self}", "read": "interact", "write": "interact" }
]
Everyone reads settings and only the author changes it. Each person reads and writes only their
own votes/<id> document, and the author reads all of them.
| Part | Rule |
|---|---|
path | A collection or document path, or "" for the whole page. A rule covers everything below its path; the deepest matching rule wins, for read and write separately |
{self} | As the last segment, the viewer's own id. Other people's documents at that level stay hidden unless a rule on the parent path sets read, and that parent rule must then set write too |
| Levels | view, interact, admin, owner, lowest first. The author holds owner and everyone else the page is shared with holds interact, so admin and owner both mean the author |
| Defaults | read is view, write is interact |
| Checks | A rule sets read, write or both; write is never view and never resolves lower than read; a path appears once; at most 64 rules. A list that fails is refused whole |
Saving rules moves the page's data version, so open views re-read under the new rules on their next
poll (a view in a hidden tab waits until it is visible again). If the save answers 503 with reason
retry, the rules were saved but open views were not told: save again. Rules need a content key. They can still be
changed while only the Artifacts switch is off, but not while content mode is off.
Limits
| Limit | Value |
|---|---|
| Path | 16 segments and 1,000 bytes. A segment is 1 to 200 of A-Z a-z 0-9 _ . ~ : @ + -, never . or ... A document path has an even number of segments, a collection an odd number |
| Document | 256 KiB of JSON, nested at most 32 deep. For update, the merged document |
| Request | 300 KiB |
| Query | 10 filters, 30 values in an in or not-in list, limit 1 to 1,000 (1,000 when omitted) |
| Query reads | 5,000 documents or 8 MiB from one collection, else resource_exhausted |
| Page | 25,000 documents and 50 MiB |
| Lease | 1 second to 10 minutes |
| Calls | 300 a minute per person on one page, 1,200 a minute per person across pages, on each gateway replica |
Claude Desktop copies
Tools on managed Claude Desktop
On MDM-managed Macs, Connect adds a local connector named anyray-share for publishing when
publishing is on and the profile has a personal key. Keep Anyray Connect running and restart Claude
Desktop after setup. This connector has not been verified on Windows. A static profile with no
anyray row gets Connect's full anyray server instead, which shares too
(static profiles).
On a Claude Desktop managed with the console's MDM profile, Mac or
Windows, anyray_list_shared and anyray_open_shared come from the managed anyray server your
gateway serves, so no local connector is needed. That server is left out while content mode is
off (details). The list shows
title, owner, date and console link, 25 pages by default and up to 50, and a page opens as text up to 64 KiB.
The Artifacts page
Connect copies pages in on its refresh and only while Desktop is closed, because Desktop overwrites that list while it runs. The Connect tray says how many new pages arrived ("2 new pages shared with you"); the first sync after you enroll adds your existing pages without a notice.
Connect keeps up to the newest 50 pages within 25 MiB in all, and only ever touches the ones it
added. A page that drops out of that window is removed. A page you can no longer see is removed
after two complete listings in a row leave it out; a listing that fails or is cut short counts for
nothing. Signing out or offboarding
removes them. If that happens while Desktop is open, the page text is deleted at once and its
title is removed the next time connect runs with Desktop closed. status --json shows the last run
and why it skipped, if it did (reasons).
Markdown shows as plain text in Desktop. Desktop's page store is not a public format, so a Desktop update can stop the copies until connect catches up. This was verified on macOS; it has not been verified on Windows yet.
Pages with data
An HTML page with data carries a snapshot of what you may read: what its rules allow, and your own
data/users/<id> part. Connect refreshes it, while Desktop is closed, when the data changes. Writes
from the copy fail with unavailable. Edit in Anyray opens the page in the console when the
gateway knows its console URL. A snapshot over 1,000 documents or 1 MiB of what you may read is left
out, and so is any snapshot of a page holding more than 5,000 documents or 8 MiB in all. The copy
then shows the page without its data.
A copy with data runs under the same content security policy as the console, but without the console's libraries and fonts, so a page that loads them from a CDN renders without them in Desktop. Each snapshot fetched by someone other than the author is audited.
Signed in to claude.ai
When Desktop is signed in to claude.ai (Claude Desktop is not turned on in Connect), the tray asks Show your Anyray pages in Claude Desktop? before copying anything. Your pages are listed under Yours. If someone else signs in to Desktop, the tray asks again and connect removes your pages from the previous account. Stop showing in the tray removes them too.
These copies carry no page data and no Edit in Anyray link, because Share in Desktop republishes the page to claude.ai. The claude.ai copy runs on macOS only.
claude.ai import
The import brings the current version of each page. A page that was private on claude.ai stays private to its owner. One shared with the organization, or by public link, is shared with the organization. Each page lands in its owner's Mine tab and their Claude Desktop.
A page's shared data comes with it, up to 25,000 documents; each person's private data and the comments stay behind. The export holds no rules for that data, so everyone the page is shared with can change it, as claude.ai allows by default.
Pages made of several files are not imported, and neither is a page whose owner is deactivated in your identity provider or missing from the export. Importing again skips what is already here, but brings back a page that was deleted since. The export stays in your browser: the console sends your gateway one page at a time.