@formepdf/svelte is the Svelte adapter for Forme.
It ships the same components with the same props as @formepdf/react, authored as ordinary .svelte files, and serializes to the identical document model.
{#each}, {#if}, snippets, and text interpolation just work - templates are plain Svelte 5 components evaluated on the server, with no special template language.
Install
^5.30.0) as a peer dependency.
@formepdf/core is an optional peer: it is only needed to render PDF bytes locally (renderDocument, the preview helper).
If you serialize templates and POST the JSON to the hosted API, skip it - serialize works with zero WASM.
Quickstart
Create a template as a normal Svelte component,src/lib/Invoice.svelte:
src/routes/invoice/+server.ts:
renderDocument serializes the template and renders it through the WASM engine in one call.
Using the hosted API instead?
Serialize without rendering - no @formepdf/core install needed:
Component parity
The adapter is 1:1 with@formepdf/react: the same components with the same props.
- Layout:
Document,Page,View,Text,Image,Fixed,PageBreak - Semantics:
H1-H6,OrderedList,UnorderedList,ListItem,Strong,Em,Code,Link - Tables:
Table,Row,Cell - Graphics:
Svg,QrCode,Barcode,Canvas,Watermark - Charts:
BarChart,LineChart,PieChart,AreaChart,DotPlot - Form fields:
TextField,Checkbox,Dropdown,RadioButton
metadata, lang, pdfUa, pdfa, certification, fonts), CSS string shorthands (border: "1px solid #000", padding: "8 16"), StyleSheet.create(), and the Style type are identical.
Only the syntax around the components changes.
Nested <Text> spans become styled text runs, so mixed-style lines work exactly as in react:
Compiled templates (
forme build --template, the $ref/$each/$if expression system for rendering without a JavaScript runtime) are TSX-only today.
Svelte templates always serialize by evaluating the component server-side; to use stored templates with the hosted API, author them in TSX.Page numbers
The engine substitutes the placeholders{{pageNumber}} and {{totalPages}} in text at render time.
In JSX the braces can be typed as a string literal ({'{{pageNumber}}'}), but in a Svelte template they cannot - Svelte parses {{pageNumber}} as an expression containing an object literal, not as text.
Interpolate the exported PAGE_NUMBER and TOTAL_PAGES constants instead:
Fonts
Font.register() has the same API as react and feeds the same process-wide store.
Register in a <script module> block so registration runs once, not on every render:
<Document fonts={[...]}> works too.
See the fonts guide for sources, weights, and fallback chains.
Tailwind
@formepdf/tailwind’s tw() works unchanged - it returns a plain style object:
Custom graphics
<Canvas> takes a draw callback that records vector operations:
The
draw callback runs during server-side serialization, not at PDF render time.
It must be synchronous and pure: no await, no side effects, no browser or runtime APIs.Live preview
formePreview() gives SvelteKit the same in-browser preview (layout overlays, click-to-inspect) that forme dev gives react users, mounted on a catch-all dev route.
Create src/routes/dev/pdf/[...forme]/+server.ts:
/dev/pdf in the browser.
The preview polls for changes and reloads shortly after you save the template (default 1000 ms in dev; polling is disabled when NODE_ENV is production, and the interval is configurable via pollMs).
The helper renders through @formepdf/core, so the optional peer must be installed.
Render options
renderDocument and renderDocumentWithLayout forward all @formepdf/core render options unchanged, so features like embedded data and form flattening work exactly as documented for react:
Migrating from TSX
Migration is mechanical: component names, props, and style objects are identical, so most of a template moves over as-is. The react quickstart maps to Svelte like this:
Two differences to note.
First, react’s
renderDocument takes an element (<Document>...), while Svelte components cannot be pre-bound to props - so the Svelte adapter’s renderDocument, serialize, and render take the component plus a props option.
Second, whitespace in .svelte templates is normalized to the same rules JSX uses, so indentation never leaks stray spaces into rendered text.