On this page
Publish anything your agent makes.
Drawer is the share button for the agent era. Tell any agent — Claude, Cursor, Codex, ChatGPT — "publish this to drawer" and it gets a stable URL at <you>.drawer.so. Versioned, password-gateable, yours.
Why it matters: your agent's output goes from copy-pasted-and-lost to a real URL the moment it's made. Every change is a new version (nothing is lost), every artifact is something the agent can read back, edit in place, and re-share without setup. Anything that runs in a browser is publishable.
Quickstart
- Sign in at drawer.so with GitHub or Google. You get a personal subdomain (
<you>.drawer.so). - Connect your agent (see below — one line for Claude Code, two fields for Claude Desktop / ChatGPT).
- Ask your agent: "publish this to drawer." You get back a URL.
That's it. Everything else on this page is optional depth.
Connect your agent
OAuth is the default — point your client at https://drawer.so/mcp with no key. First tool call opens a browser, you sign in, done. No keys to paste or rotate. API keys still work for CLI scripts / CI; swap in --header "Authorization: Bearer YOUR_DRAWER_KEY" wherever you see the URL below.
Drawerhttps://drawer.so/mcpWalkthrough: Claude Desktop setup
claude mcp add --transport http drawer https://drawer.so/mcp
Drawerhttps://drawer.so/mcpWalkthrough: ChatGPT setup
npm install -g drawer-so && drawer auth login
Older Claude Desktop without custom-connector UI? Use the mcp-remote stdio bridge in claude_desktop_config.json:
{ "mcpServers": { "drawer": { "command": "npx", "args": ["-y", "mcp-remote", "https://drawer.so/mcp"] } } }First publish
"Publish this HTML to drawer:
<h1>hello</h1>"
The agent calls create_artifact and hands back your URL. Default visibility is private — only you can view it, signed in to drawer.so in the same browser. Say unlisted or public to share.
What you can publish
| Format | Notes |
|---|---|
| Single HTML file | Preferred. Inline CSS + JS. |
| Markdown | Rendered at view time. Tables, code, images. |
| Multi-file site | HTML + CSS + JS + assets. Pass {files: {...}} or upload a zip. Entry is index.html. Sub-folders with index.html serve at the folder URL; .html files also serve extensionless. |
| Interactive mini-app | Browser JS, canvas, audio, localStorage, fetch to any HTTPS API. No server code. |
| Image / PDF | Single-file artifacts. Use request_upload or the CLI. |
Size: 100 MB per artifact (200 MB uncompressed for zips). MCP create_artifact is capped around 30 KB by LLM output budget — use patch_artifact, request_upload, or the CLI for anything bigger.
Drawer is a static host with browser-side dynamic capability. Anything the browser can do, calls to any HTTPS API, localStorage for per-visitor state. No server code, no shared DB, no server-side auth other than the password tier.
MCP tools
Your agent has these on connect. JSON in, JSON out. Each tool's full description and parameters are returned by MCP tools/list.
Create + edit
| Tool | One-liner |
|---|---|
create_artifact |
New artifact (raw HTML/markdown or {files: {...}}). Defaults to private. |
update_artifact |
Full content replace. Old version preserved at /v/<version_id>. |
patch_artifact |
Surgical find/replace edits. Same semantics as Claude Code's Edit. |
regex_replace_artifact |
Regex find/replace on one file. Cheap for repeated patterns. |
add_files_to_artifact |
Merge new files into an existing artifact (you supply the bytes). |
rename_file_in_artifact |
Move a file inside an artifact. Preserves entry_file. |
delete_file_from_artifact |
Remove a file from an artifact. |
Read + navigate
| Tool | One-liner |
|---|---|
list_artifacts |
Your artifacts with titles, URLs, tags, favorite state. favorited:true to filter. |
list_tags |
Your tags with counts. |
get_artifact |
Metadata + version history. Prefer read_artifact_file / grep_artifact for content. |
read_artifact_file |
Read ONE file (or a line window). Pair with grep_artifact. |
grep_artifact |
Search across an artifact's text files. The primary navigation tool. |
diff_artifact |
Unified diff between two versions. |
debug_artifact |
Diagnostic snapshot: files, render health, warnings, quota. |
Visibility, slugs, and favorites
| Tool | One-liner |
|---|---|
publish_artifact |
Flip visibility (private / unlisted / public). public requires confirmation. |
unpublish_artifact |
Make an artifact private. Sister of publish_artifact. |
rename_slug |
Promote /abc123 to a custom slug like /docs or /resume. |
favorite_artifact / unfavorite_artifact |
Star / unstar. Surfaces under Favorites in the dashboard. |
Versions, uploads, and lifecycle
| Tool | One-liner |
|---|---|
revert_artifact |
Undo. Defaults to "the version before current." |
delete_artifact |
Permanent. Always confirm. |
request_upload |
Hand the user a drop-a-file page that creates a NEW artifact. |
request_upload_to_artifact |
Hand the user a drop-a-file page that becomes a NEW VERSION of an existing artifact. |
await_upload |
Long-poll for the user's drop to land. Up to 300 s per call. |
The agent loop
Save → iterate → publish → undo. update_artifact / patch_artifact / regex_replace_artifact push new versions at the same URL — but never change who can see it. publish_artifact is the only path to flip visibility. Old versions stay reachable at /v/<version_id> forever. If a patch fails (text not found, not unique), no new version is created and the artifact is unchanged.
The CLI
For publishing files from disk, scripting, or anything over the ~30 KB MCP payload limit.
npm install -g drawer-so
drawer auth login # paste API key (also: logout, whoami)
drawer create ./report.html # private by default
drawer create ./report.html --unlisted # create + share with a URL in one step
drawer create ./site --title "demo" # directory zipped + uploaded
drawer update ABC ./report.html # push a new version
drawer publish ABC --visibility unlisted # flip an existing artifact
drawer publish ABC --public --yes # confirm-public in one step
drawer unpublish ABC # back to private
drawer patch ABC --find "v1" --replace "v2" # surgical edit (see also --force)
drawer regex ABC "\\bv1\\b" "v2" # regex find/replace on one file
drawer read ABC index.html --lines 50:120 # read one file (or slice)
drawer grep ABC "TODO" # search without pulling bodies
drawer get ABC # show metadata + version list
drawer list # your artifacts
drawer open ABC # open the URL in your browser
drawer revert ABC # supports --force
drawer diff ABC v1 v2
drawer debug ABC # diagnostics: files, render, quota
drawer favorite ABC # star (also: fav, star)
drawer unfavorite ABC # unstar (also: unfav, unstar)
drawer tags # list your tags with counts
drawer tag ABC docs client-x # add tags (preserves existing)
drawer untag ABC client-x # remove tags
drawer files add ABC ./logo.png # add a file to an existing artifact
drawer files rename ABC old.png new.png # rename a file inside an artifact
drawer files rm ABC stale.png # remove a file from an artifact
drawer upload-request ABC # mint a user-facing upload URL and wait
drawer delete ABC # permanent (asks first)--force on update, patch, and revert skips the implicit drift check. Use only when you mean it. --expected-version <id> pins the write to a known current_version_id (returns 409 stale_artifact on miss).
Visibility tiers
Default is private. Set visibility on create_artifact (one-shot) or call publish_artifact(id, visibility) later to flip an existing artifact. Switching to public always requires a confirmation round-trip — the first call returns needs_confirmation with the exact follow-up payload to send.
| Tier | Who can view | On profile | Indexed |
|---|---|---|---|
private (default) |
Only the signed-in owner. Anyone else gets a 404 — existence isn't leaked. | No | No |
unlisted |
Anyone with the URL. | No | No |
public |
Anyone with the URL. | Yes | Yes |
Password modifier
password is an independent field, not a tier. Stack it on unlisted or public — visitors hit a password form before the artifact loads. Combining password with private is rejected (private is already owner-only).
curl -X POST https://drawer.so/api/artifacts \
-H "Authorization: Bearer YOUR_DRAWER_KEY" \
-H "Content-Type: application/json" \
--data '{"content":"<h1>WIP</h1>","visibility":"unlisted","password":"hunter2"}'The owner is always exempt from the password gate when signed in. To remove a password, call publish_artifact(id, visibility, password: null) — or drawer publish ABC --unlisted --clear-password from the CLI.
Versions & state
Every change creates a new immutable version. Nothing is lost unless you delete_artifact.
- Undo last change →
revert_artifact({artifact_id}) - Revert to a specific version →
revert_artifact({artifact_id, to_version_id}) - What changed? →
diff_artifact({artifact_id, from_version_id}) - View any past version directly →
<you>.drawer.so/<id>/v/<version_id>
Drift detection. Drawer tracks "what version did this principal last actually read?" on the server. Mutating calls (update_artifact, patch_artifact, regex_replace_artifact, revert_artifact, add_files_to_artifact, rename_file_in_artifact, delete_file_from_artifact) check it automatically. If the artifact has moved since your last read, you get version_drift (HTTP 409) — re-read with read_artifact_file / grep_artifact and retry, or pass force: true to overwrite. A principal is your API key or dashboard session; two agents on one key are one principal. For atomic edits against a known version, pass expected_version_id — returns 409 stale_artifact on miss.
The CLI exposes --force on update, patch, and revert for the force: true case.
Dashboard workspace
The dashboard is a file-manager view over your artifacts.
- Sidebar quick views. All / Favorites / Recent (last 7 days). Tags listed underneath with counts; click to filter.
- Star to favorite. Click the star next to any artifact. Agents and the CLI can star too:
favorite_artifact,drawer favorite <id>. - List or grid view. Toggle in the toolbar; the choice persists across sessions.
- Sort + group. Recently updated / created / name / views. Group by tag or visibility for a foldered feel without the rigidity of folders.
- Multi-select. Click a checkbox, cmd/ctrl-click a row, or shift-click a range. Bulk-tag, change visibility, favorite, or delete from the action bar.
Plans
- Free — 5 private artifacts, 1 API key, 25 MB / file, 100 MB / artifact, 1 GB account. Drawer badge on every artifact. Forever.
- Pro — $20 / mo or $180 / yr — unlimited private artifacts, unlimited named API keys (one per agent), 250 MB / file, 1 GB / artifact, 25 GB account. No Drawer branding.
Full breakdown on the pricing page. Billing is Stripe; cancel anytime.
REST API
All endpoints at https://drawer.so/api/* with Bearer auth.
curl -X POST https://drawer.so/api/artifacts \
-H "Authorization: Bearer YOUR_DRAWER_KEY" \
-H "Content-Type: application/json" \
--data '{"content":"<h1>hi</h1>","title":"test","visibility":"private"}'Raw file upload (skips JSON-encoding overhead — dispatches by Content-Type: HTML / markdown / text become viewer-rendered artifacts; images / PDFs become single-file artifacts; zip bodies are unpacked server-side with index.html as the entry):
curl -X POST https://drawer.so/api/artifacts/upload \
-H "Authorization: Bearer YOUR_DRAWER_KEY" \
-H "Content-Type: text/html" \
-H "X-Drawer-Title: Big File" \
-H "X-Drawer-Visibility: unlisted" \
--data-binary @./big-file.htmlUpdates go to POST /api/artifacts/:id/upload and accept an optional X-Drawer-Expected-Version-Id header for atomic version-pinned writes (returns 409 stale_artifact on miss).
Limits
Per-file, per-artifact, and account storage caps are plan-dependent — see Plans above.
| Mutating API calls per key | 60 / min, 1000 / hour |
| Read API calls per key | 300 / min |
| OAuth start per IP | 10 / min |
Reserved subdomains: www, api, app, admin, content, mail, docs, auth, dashboard, plus thousands of brand names and trademarks. If your OAuth login collides, signup auto-suggests <name>-2.
Not supported: server-side code (Node, Python, PHP), shared databases, custom OAuth/SAML, WebSockets to Drawer (you can open them from your artifact JS to any external server).
FAQ
Where do files live?
Globally distributed object storage with versioned snapshots. Files are isolated per account — nothing is shared, mixed, or used to train models. Export anything via the API at any time. No egress fees.
Can I change my subdomain?
Yes, on the dashboard. Old URLs 301 to the new handle for 30 days, then the released handle becomes claimable. Your own old handle is reclaimable at any time with no wait.
Can I publish a folder?
Yes — zip it, or drawer create ./folder (CLI auto-zips). Entry is index.html; assets at <url>/<asset-path>. Sub-folders containing their own index.html serve at the folder URL (<url>/quickstart/ → quickstart/index.html), and root-level .html files also serve extensionless (<url>/about → about.html).
Why are agent-published files so big?
MCP transports content as a JSON-escaped string — every newline and quote bloats the payload, and the LLM emits every byte. For files over 30 KB, use the CLI, REST upload, or request_upload. For small edits to big files, patch_artifact not update_artifact.
Can others see my artifact id?
Only if you share the URL. Drawer doesn't surface IDs in any public listing. Profile pages show only public artifacts.
What happens to deleted artifacts?
Artifact, all versions, all R2 files removed. Not reversible.
Can my agent automate the dashboard?
Yes — MCP tools cover everything the dashboard does except changing your handle.
Security details?
Per-user data isolation enforced at the storage layer. Credentials are hashed at rest; password-gated artifacts use a key-stretching function. CSRF on session writes. Detailed security overview available on request.