Saving and Short URLs
Since all code and state is stored in the URL hash, metaframe URLs can get very long. The built-in URL shortener compresses these into compact, shareable links.
There are two forms of short URL persistence:
- Expiring immutable URLs
- Durable editable URLs
Howto: Expiring immutable URLs
- Click the Create expiring snapshot button in the editor header

- The current hash parameters (code, inputs, options) are stored in R2
- You get a short URL
framejs.io/j/<SHA256-based short ID> - Title, description, and image are supported for nice bookmarks and previews
The short URL is automatically copied to your clipboard.
The content is immutable — the same content always produces the same short URL. Short URLs are a free convenience snapshot, kept for a month and then garbage-collected. They are perfect for quick sharing, previews, and QR codes. If you need a link that lasts, save the content to a durable Frame on framejs.app — see below and Persistence & Data Retention for the full retention model across every kind of URL.
Howto: Durable editable URLs
- Click the Save to framejs.app button in the editor header

- The current hash parameters (code, inputs, options) are persisted
- The durable editable short URL
framejs.app/j/<uuid>opens - You can claim this URL for your account if authenticated
Longer discussion between the two short-URL forms: /j/<sha256> vs /j/<uuid>
Both forms look alike — framejs.io/j/… and framejs.app/j/… — but they make very different persistence promises. The Create expiring snapshot button above creates the ephemeral sha256 form; saving into an account creates the durable uuid form.
framejs.io/j/<sha256> | framejs.app/j/<uuid> | |
|---|---|---|
| Identifier | SHA-256 of the content (content-addressed) | Random UUID (a stable document handle) |
| Backed by | Immutable blob in object storage (R2) | Versioned rows in a database, tied to an account |
| Mutable? | No — different content always yields a different URL | Yes — the same URL, edited over time (append-only version history) |
| Account? | None required | Owned by an account (free tier included) |
| Persistence | Free, best-effort, kept ~30 days, then garbage-collected | Durable & guaranteed while the account is active |
| Best for | Quick sharing, previews, QR codes, paste-into-chat | Anything you need to keep, keep editing, or make private |
In short: sha256 is a disposable snapshot; uuid is a durable document. You get the best of both by publishing an exact immutable version of a durable Frame (Frame menu → Publish version), which mints framejs.app/j/<uuid>?v=<sha256> — a stable handle and an unchanging snapshot. A published version is permanently public and citable, like a DOI: it keeps resolving even if you later make the Frame private or delete it, and both apps serve it (framejs.io/j/<uuid>?v=<sha256> renders the same snapshot on the runtime). See Persistence & Data Retention for the complete model.
Example
A full URL like this:
https://framejs.io/#?js=Y29uc3QgZGl2ID0gZG9jdW1lbnQuY3Jl...&options=eyJhdXRvcnVuIjp0cnVlfQ%3D%3DBecomes:
https://framejs.io/j/8a3b1c9f4e2d7a6b5c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0bWhen someone opens the short URL, the page loads with all the original code and state intact — no redirect, the browser stays on the /j/... path.
API
You can also shorten URLs programmatically.
POST /api/shorten — Shorten from hash params
Store raw hash params and get a short URL ID back.
curl -X POST https://framejs.io/api/shorten \
-H "Content-Type: application/json" \
-d '{"hashParams": "?js=Y29uc29sZS5sb2coImhlbGxvIik%3D"}'Response:
{
"success": true,
"id": "8a3b1c9f...",
"path": "/j/8a3b1c9f..."
}POST /api/shorten/json — Shorten from JSON
A convenience endpoint that encodes JavaScript and options into hash params for you.
curl -X POST https://framejs.io/api/shorten/json \
-H "Content-Type: application/json" \
-d '{"js": "console.log(\"hello\")"}'Response:
{
"id": "8a3b1c9f...",
"shortUrl": "https://framejs.io/j/8a3b1c9f...",
"fullUrl": "https://framejs.io/#?js=Y29uc29sZS5sb2coImhlbGxvIik%3D",
"hashParams": "?js=Y29uc29sZS5sb2coImhlbGxvIik%3D"
}GET /api/j/:sha256 — Retrieve decoded hash params
Returns the short URL ID and decoded hash parameters as a JSON object.
curl https://framejs.io/api/j/8a3b1c9f...Response:
{
"id": "8a3b1c9f...",
"hashParams": {
"js": "console.log(\"hello\")"
}
}GET /api/j/:sha256/url — Retrieve full URL
Returns the full URL (with hash params) as plain text.
curl https://framejs.io/api/j/8a3b1c9f.../urlResponse:
https://framejs.io/#?js=Y29uc29sZS5sb2coImhlbGxvIik%3DGET /j/:sha256/qrcode.png — QR code image
Returns a PNG QR code that encodes the short URL https://framejs.io/j/<sha256>. The response has CORS open (Access-Control-Allow-Origin: *) and is cached immutably, so the image can be embedded directly anywhere — <img> tags, Markdown, slides, printed material:
<img src="https://framejs.io/j/8a3b1c9f.../qrcode.png" alt="QR code" />Any extra query parameters on the request are appended to the encoded URL. This lets you generate QR codes that open the short URL with additional hash/query state, for example to point at a specific tab or view:
https://framejs.io/j/8a3b1c9f.../qrcode.png?foo=barencodes https://framejs.io/j/8a3b1c9f...?foo=bar.
The encoded URL always uses the canonical framejs.io origin, regardless of which host served the image — so QR codes stay valid even when generated from an alias or preview domain.
POST /api/upload/presign — Get presigned upload URL
Generate a presigned S3 URL for direct browser-to-storage file upload. Files are content-addressed by SHA256.
curl -X POST https://framejs.io/api/upload/presign \
-H "Content-Type: application/json" \
-d '{"contentType": "image/png", "sha256": "abcd1234..."}'Response:
{
"presignedUrl": "https://s3.../f/abcd1234...?X-Amz-Signature=...",
"id": "abcd1234...",
"canonicalPath": "/f/abcd1234..."
}GET /f/:id — Download a file
Redirects to the public storage URL for a previously uploaded file.
curl -L https://framejs.io/f/abcd1234...TIP
Short URLs are content-addressed — the same code always produces the same short URL. They are free and kept for about a month; for a link that lasts, save it to a durable Frame.