PaperStudio Universe — Cell Providers
A universe papercell is the generalization of the template cell: instead
of embedding a template by id, it names a provider that — given the studio
context — returns an SVG.
That single indirection is the whole idea: one cell type, N providers, zero front-end code per provider. A new kind of cell (a map viewer, a route renderer, a statistical chart, a data inspector, a calculator) becomes an endpoint that returns SVG, not a change to PaperStudio.
The same SVG is used on the live canvas, in the Typst preview and in the exported PDF, so what you see is what you print.
The contract (v1)
A provider is an HTTPS endpoint that accepts POST with JSON and answers
with an SVG.
Request
{
"provider_id": "my-provider",
"flavor": "thumb",
"params": { "…": "whatever your provider defines" },
"context": {
"vars": { "…": "the studio's reactive data bag" },
"size_mm": { "w": 90, "h": 60 },
"locale": "es",
"page": { "n": 2, "total": 8 },
"studio": { "title": "Reporte trimestral" }
}
}
| Field | Meaning |
|---|---|
params |
Yours. Whatever the author edits in the cell (the editor shows a JSON field seeded from your params_example). |
context.size_mm |
The cell box in millimetres. Size your SVG to this aspect (the convention is ~4px per mm). |
context.vars |
The studio's reactive data (studio data + cell ports + _page/_studio). Use it if your provider is data-driven. |
flavor |
thumb = the fast canvas preview · full = the final export. Return the same picture; use it to trade detail for speed if you want. |
Response
Either the SVG directly:
200 OK
Content-Type: image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="240" viewBox="0 0 360 240">…</svg>
…or JSON with an svg key:
{ "svg": "<svg …>…</svg>" }
Rules your SVG must follow
- Self-contained. No external references. Images must be inlined as
data:URIs — any<image>/<use>pointing at an http(s) URL is stripped (we will not let a document render pull a third-party URL). - Give it dimensions. Include
widthandheighton the root<svg>, not justviewBox. A viewBox-only SVG renders blank as an image source in Safari/Firefox and in Typst/resvg. (We add them if missing, but don't rely on it.) - No scripts, no HTML.
<script>,on*handlers,<foreignObject>andjavascript:URLs are stripped. A document is a static artifact. - Stay small and quick. Hard caps: 12 s timeout, 4 MB response.
- Be deterministic. The same request should give the same picture — the PDF is rendered separately from the canvas.
Errors
Return a non-2xx and the cell falls back to a placeholder — it never breaks the
page. Prefer answering 200 with an SVG that says what's wrong (e.g. "missing
lat/lng") — the author sees the problem in place. All the reference
providers do this.
Security — what the platform does with your SVG
Your code runs on your infrastructure; we only call it and embed the result. So the result is treated as untrusted input:
- HTTPS only, and the URL is checked against private/loopback/link-local ranges including a DNS resolve-then-check (a hostname that resolves to a private IP is refused).
- Redirects are refused — a redirect could point somewhere the check already cleared.
- Timeout + size caps (12 s / 4 MB), and the
Content-Typemust look like SVG/XML/JSON. - The returned SVG is sanitized (scripts, event handlers,
foreignObject,javascript:, external refs) before it reaches the canvas or the PDF.
Trust tiers
| Tier | What it means |
|---|---|
| 0 | Static — a baked SVG/PNG, no computation. |
| 1 | Declarative — a spec rendered by our engine. The built-in providers are tier 1. |
| 2 | Bounded expressions — restricted, no I/O. |
| 3 | Full compute — external providers are tier 3 by nature: the code is not ours. |
External providers are badged in the editor so an author always knows a third party renders that cell.
Getting listed
The registry is curated: the platform never calls an arbitrary URL. A
provider becomes available when its entry is added to the catalog with an id,
title, description, a params_example (what the editor seeds when the
author picks it), kind: "external", its url and a tier. Being in the
catalog is the allowlist.
A minimal provider (copy-paste)
Any language works — it's just HTTP. In Deno:
Deno.serve(async (req) => {
const { params, context } = await req.json();
const w = Math.round((context?.size_mm?.w ?? 80) * 4);
const h = Math.round((context?.size_mm?.h ?? 50) * 4);
const text = String(params?.text ?? "¡Hola, Universe!");
const esc = (s: string) =>
s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">` +
`<rect width="${w}" height="${h}" rx="8" fill="#eef2ff"/>` +
`<text x="${w / 2}" y="${
h / 2
}" text-anchor="middle" dominant-baseline="middle" ` +
`font-family="Inter,sans-serif" font-size="${
Math.round(h * 0.16)
}" fill="#4338ca">${esc(text)}</text>` +
`</svg>`;
return new Response(svg, { headers: { "Content-Type": "image/svg+xml" } });
});
That is a complete cell type. No PaperStudio code changed.
Reference providers (built in)
These ship with the platform and double as worked examples of the contract:
| Provider | What it renders | Notable |
|---|---|---|
plot |
Line / bar / area chart | Series take data[] or a whitelisted fn (sin, cos, sqrt, log, square, linear) the provider samples — compute, not just drawing |
scatter |
Scatter plot | Optional linear regression (least squares) with the fitted equation and R² |
gauge |
KPI arc gauge | Colored threshold zones, needle, value + unit |
timeline |
Timeline / gantt bars | Accepts ISO dates or numbers for start/end |
location |
A real map with a marker | Reuses the GIS engine (basemap tiles + pin) from lat/lng |
flowchart LR
C["celda universe<br/>{ provider_id, params }"] -->|"contexto: vars · size_mm · locale"| P["proveedor (HTTPS)<br/>tuyo o built-in"]
P -->|"SVG (thumb | full)"| S["saneo + caps"]
S --> R["canvas · Typst β · PDF"]
Related
- PaperStudio Variables — the
{{ }}data available to cells.