Releases: danmolitor/forme
v0.10.4
Forme 0.10.4
Four layout bug fixes, all user-reported. The
table header one is silent — upgrade if you use
<Row header>.
Fixed
Tables with <Row header> no longer inflate
page count 3–5×
When a table started low enough on a page that
the header didn't fit before a page break, the
engine emitted multiple near-duplicate pages —
the same body rows repeated, with the header
visibly "doubling and sliding one column to the
right" on each successive page. A 24-row,
5-column table produced 8 pages where the same
content without a header row produced 3.
// Before: 8 pages, garbled doubled headers
// After: 3 pages, correct
{headerCells}
{rows.map(r => {...})}
<View> wrapping a <Table> no longer
auto-grows to roughly the page height
measure_node_height had no handler for
Table or TableRow, so they fell into a
generic path that summed cell heights instead
of taking the max — a 3-column row of 16pt
cells measured to 48pt, and any wrapping
<View> inherited that inflation. Now
delegates to the same helpers layout_table
already uses, so measurement matches what
renders.
<Svg viewBox="…"> content scales to fit
the display box
SVG paths previously rendered at raw viewBox
coordinates and overflowed — the viewBox
parameters were parsed but unused, and the
PDF scale was always 1.0. Now implements the
SVG viewport algorithm with xMidYMid meet
as the default preserveAspectRatio (uniform
min(sx, sy) scale + centering).
// Before: paths spilled outside the 200×80 box
// After: scaled to fit
…paths…marginTop: 'auto' works in column layouts
Previously a no-op in flexDirection: 'column'
parents — only the horizontal version worked.
Now distributes slack the same way: top-only
pushes to bottom, both autos center, bottom-only
carries forward. Auto margins consume slack
before justifyContent, per the CSS spec.
// "Sign here" now sits at the bottom, not the top
Sign here
Upgrade
npm install @formepdf/core@0.10.4 @formepdf/react@0.10.4
# plus any of: cli renderer hono next mcp resend sdk tailwind templatesOther consumers:
- Rust:
cargo add forme-pdf@0.10.4 - Python:
pip install formepdf==0.10.4 - Go:
go get github.com/formepdf/forme-go@v0.10.4 - Docker:
docker pull formepdf/forme:0.10.4
docker pull formepdf/rasterizer:0.10.4 - VS Code: update "Forme PDF Preview" in the
Extensions panel
v0.10.3
Forme 0.10.3
Bug-fix release. If you're on 0.10.2, upgrade - it shipped a silent text-layout regression that caused right-aligned text to render off-page.
Fixed
<Text style={{ width }}> inside a flex row now renders at the correct width
A 0.10.2 regression caused text elements with an explicit width inside a flex row to be sized to the full row width instead. With textAlign: 'right', glyphs were aligned to the right edge of the oversized box and pushed off the page — clipped by PDF viewers and effectively invisible.
The bug was easy to miss: PDF bytes were deterministic, so byte-hash snapshot tests still passed. The corruption only appeared when opening the file.
// Broken on 0.10.2 — "$10.00" rendered off-page
// Correct again on 0.10.3
<View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
<Text style={{ width: 120, textAlign: 'right' }}>Tax:</Text>
<Text style={{ width: 80, textAlign: 'right' }}>$10.00</Text>
</View>layout_text and the text branch of measure_node_height now honor a resolved fixed style.width, matching how <View> and <Image> already behaved. The <View style={{ width: N }}> wrapper workaround is no longer needed.
The 0.10.2 flex-percentage and grid-page-break fixes are unaffected.
Upgrade
npm install @formepdf/core@0.10.3 @formepdf/react@0.10.3
# plus any of: cli renderer hono next mcp resend sdk tailwind templatesOther consumers:
- Rust:
cargo add forme-pdf@0.10.3 - Python:
pip install formepdf==0.10.3 - Go:
go get github.com/formepdf/forme-go@v0.10.3 - Docker:
docker pull formepdf/forme:0.10.3
docker pull formepdf/rasterizer:0.10.3 - VS Code: update "Forme PDF Preview" in the
Extensions panel
Full changelog: engine/CHANGELOG.md
v0.10.2
Forme 0.10.2
Two engine layout bug fixes. No API changes —
upgrade is drop-in.
Fixed
Flex row percentage widths resolve against
the parent
Children of a flexDirection: 'row' container
with explicit percentage widths were being
double-resolved — width: '30%' was computed
as 30% of the child's already-distributed width
(~9% of the row) instead of 30% of the row itself.
<View style={{ flexDirection: 'row' }}>
<View style={{ width: '30%' }}><Text>30%</Text></View>
<View style={{ width: '70%' }}><Text>70%</Text></View>
</View>
// Before: 30% child was ~44pt wide on a 487pt row
// After: 30% child is 146pt, 70% child is 341ptGrid page-break keeps columns aligned
Grid containers that didn't fit on the current
page were scattering each column onto its own
page. An off-by-one guard in the first row's
page-break check suppressed the break, causing
each cell's content to individually overflow
and trigger its own new page. The entire row
now moves to the next page together, keeping
columns at the same y position.
Upgrade
npm install @formepdf/core@0.10.2 @formepdf/react@0.10.2
# plus any of: cli renderer hono next mcp resend sdk tailwind templatesOther consumers:
- Rust:
cargo add forme-pdf@0.10.2 - Python:
pip install formepdf==0.10.2 - Go:
go get github.com/formepdf/forme-go@v0.10.2 - Docker:
docker pull formepdf/forme:0.10.2
Full changelog: engine/CHANGELOG.md
v0.10.1
Forme 0.10.1
Patch release fixing two regressions introduced in 0.10.0. No engine or rendering changes — existing PDFs render identically.
Fixed
Cloudflare Workers crash on import
@formepdf/core@0.10.0 called wasm.__wbindgen_start() at the top level of the bundler-target build. Wrangler passes import wasm from '*.wasm' back as { default: WebAssembly.Module } rather than an instantiated namespace, so this threw immediately:
TypeError: wasm.__wbindgen_start is not a function
Fixed by shipping a third build (--target web → pkg-web/) and a new dist/worker.js entry that takes an explicit init(wasmModule) call. The worker / edge-light / deno conditional exports now route here. Your 0.9.x pattern works again:
import { init, renderDocument } from '@formepdf/core'
import wasm from '@formepdf/core/pkg-web/forme_bg.wasm' // recommended
// or:
import wasm from '@formepdf/core/pkg/forme_bg.wasm' // legacy, also works
await init(wasm)Missing pkg-node/ in the published tarball
wasm-pack's --target nodejs output ships with a .gitignore containing *. npm publish honored it and silently dropped the entire directory. The 0.10.0 Node entry tried to import from ../pkg-node/forme.js, which wasn't there:
Cannot find module '../pkg-node/forme.js' from
'node_modules/@formepdf/core/dist/index.js'
Fixed by stripping .gitignore from all three pkg dirs during the WASM build, and adding a prepublishOnly assertion that re-packs the tarball and fails if anything required is missing.
VS Code extension build path mismatch
The bundled extension read ${__dirname}/forme_bg.wasm but the esbuild config still copied the WASM to the old pkg/ location. Now copies to dist/forme_bg.wasm. Only affected fresh 0.10.x rebuilds of the extension.
Added
@formepdf/core/workersubpath export@formepdf/core/pkg-web/forme.jsand@formepdf/core/pkg-web/forme_bg.wasmsubpath exportspackages/core/scripts/assert-tarball.sh— runs asprepublishOnlyand on every CI PR. Asserts every entry-point file is present in the tarball and that nopkgdir ships a stray.gitignore. Would have caught both regressions above at PR time.- Workerd smoke test (
@cloudflare/vitest-pool-workers) undernpm run test:workers. Runs inside real workerd, callsinit(wasm)+renderPdf(), and verifies the result has a%PDFheader. Catches the exact regression class that Node-based unit tests can't see.
Migration
You don't need to change anything. Existing 0.10.0 code works on 0.10.1.
If you're on Cloudflare Workers and were hitting the __wbindgen_start crash, upgrade to 0.10.1 — the import + init() pattern from 0.9.x is restored. pkg-web/forme_bg.wasm is recommended over pkg/forme_bg.wasm for new Workers code, but the legacy path still works.
Packages updated
| Package | Reason |
|---|---|
@formepdf/core |
The fix |
@formepdf/renderer, @formepdf/cli, @formepdf/hono, @formepdf/next, @formepdf/mcp, @formepdf/resend |
Updated to pull the core fix transitively |
@formepdf/react, @formepdf/sdk, @formepdf/tailwind, @formepdf/templates |
Version parity bump only — no code changes |
| VS Code extension | Build config fix for new core layout |
Not changed: Rust engine (same WASM bytecode across all three targets), Rust crate, Python SDK, Go SDK, Docker images, rasterizer — all still on 0.10.0.
v0.10.0
Forme 0.10.0
New visual properties
Six new style features in the engine, available in React JSX and JSON document inputs.
opacity cascades to children
Previously, opacity: 0.5 on a <View> faded the background but left text at full alpha. Opacity now wraps the full element subtree — children inherit the fade, and nested opacities multiply correctly.
<View style={{ opacity: 0.5 }}>
<Text>Fades with the background</Text>
</View>wordSpacing
Maps to the PDF Tw operator. Stacks additively with text-align: 'justify'.
<Text style={{ wordSpacing: 4 }}>extra space between words</Text>boxShadow
Offset drop shadow behind any element. Honors borderRadius. Accepts an object or CSS shorthand string.
<View style={{
borderRadius: 12,
boxShadow: { offsetX: 2, offsetY: 4, blur: 0, color: '#00000033' },
}}>...</View>
// or:
<View style={{ boxShadow: '4 6 0 #00000040' }}>...</View>borderRadius + rounded clipping
borderRadius now also rounds the overflow clip path when overflow: 'hidden' is set. Children that exceed the parent's bounds clip to the rounded corners, not a sharp rectangle.
<View style={{ overflow: 'hidden', borderRadius: 16 }}>
<Image src="..." /> {/* clipped to rounded corners */}
</View>Page backgroundImage
Watermark-style background images on every page. Supports backgroundSize, backgroundPosition, and backgroundOpacity. The same URL across multiple pages shares a single embedded XObject.
<Page
backgroundImage="https://cdn.example.com/logo.png"
backgroundSize="cover"
backgroundPosition="center"
backgroundOpacity={0.08}
>...</Page>CSS gradients via background
Linear and radial gradients with CSS-compatible syntax. Multi-stop gradients use PDF Type 3 stitching functions. Supports deg, turn, rad, grad units and to <side> keywords.
<View style={{ background: 'linear-gradient(135deg, #667eea, #764ba2)' }} />
<View style={{ background: 'radial-gradient(circle, #10b981, #059669)' }} />
<View style={{ background: 'linear-gradient(180deg, #ff0000 0%, #00ff00 50%, #0000ff 100%)' }} />Build fix: Next.js / Webpack / Turbopack
@formepdf/core@0.9.x shipped a --target web WASM build that referenced ./forme_bg.js — a file wasm-pack doesn't emit for that target. Static-analysis bundlers (Next.js Webpack and Turbopack) failed on the missing file.
@formepdf/core@0.10.0 now ships two WASM builds:
pkg/(--target bundler) — for Vite, Webpack, Turbopack, Wranglerpkg-node/(--target nodejs) — for Node SSR; self-initializes viafs
@formepdf/next and @formepdf/hono drop their previous init(wasm) workaround. The browser entry now instantiates WASM implicitly at module load.
Vite users: action required
npm install -D vite-plugin-wasm vite-plugin-top-level-await// vite.config.ts
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
export default defineConfig({
plugins: [wasm(), topLevelAwait()],
worker: {
format: 'es',
plugins: () => [wasm(), topLevelAwait()],
},
});Without these plugins, Vite throws "ESM integration proposal for Wasm" is not supported currently.
Security: @formepdf/mcp sandbox hardening
The render_custom_pdf sandbox has been rebuilt from the ground up.
What was wrong: The previous new Function(...) evaluator was bypassable in one line via new Function('return process')(). The 30-second timeout only covered the WASM render step — a while(true){} template hung the MCP server indefinitely. validateOutputPath was effectively a no-op.
What changed:
- Worker-thread isolation with 128 MB memory cap and crash containment
vm.ContextwithcodeGeneration: false— blocksevaland string-basedFunctionvm.runInContextwith a 5-second sync timeout that actually interrupts infinite loops- 10-second wall-clock timeout backed by
worker.terminate() - AST denylist (acorn) — clear error messages for blocked patterns before the worker starts
- Post-eval asset sanitizer — font/image
srcmust bedata:URIs; closes the file-path exfiltration vector - Output path allowlist — writes restricted to CWD by default; opt-in via
FORME_MCP_OUTPUT_DIRS
Trust model: This sandbox is hardened for accidental misuse on a trusted local machine. It is not a service-grade boundary for arbitrary attacker code — for that, use containers or isolated-vm. The README now says this explicitly.
Known limitation: The 5-second sync timeout does not interrupt async hangs. A template that awaits an unresolved Promise is caught by the outer 10-second wall-clock timeout instead.
Install
# JavaScript / TypeScript
npm install @formepdf/react@0.10.0 @formepdf/core@0.10.0
# Python
pip install formepdf==0.10.0
# Rust
cargo add forme-pdf@0.10.0
# Go
go get github.com/formepdf/forme-go@v0.10.0
# Docker
docker pull formepdf/forme:0.10.0
docker pull formepdf/rasterizer:0.10.0Per-package changelogs: [engine](engine/CHANGELOG.md) · [core](packages/core/CHANGELOG.md) · [react](packages/react/CHANGELOG.md) · [mcp](packages/mcp/CHANGELOG.md) · [next](packages/next/CHANGELOG.md) · [hono](packages/hono/CHANGELOG.md)
v0.9.2
[0.9.2] - 2026-04-28
Fixed
- Redaction precision: Text-stripping now uses real per-CID glyph advances when locating regions, so partial-line redactions match the visible overlay precisely. Previously, redacting
MolitorinDear Daniel Molitorwould also stripDear Daniel - CID font handling: Decode CID/Type0 fonts in the redaction text extractor, with parsing that survives binary font streams
- Multi-style text grouping:
text_decorationis now part of the glyph style key, so aline-throughspan inside an otherwise plain text node is no longer merged with its neighbors during PDF emission
Changed
- Rasterizer body limit: Default Axum 2 MB request body limit removed — large PDFs now flow through the rasterizer sidecar without 413 errors
- MCP tool surfaces: Synced with the current
@formepdf/reactcomponent set so generated prompts reflect shipping components
v0.9.1
0.9.1 — React Compiler compatibility
Patch release. No breaking changes.
What's fixed
If you enabled React Compiler in your Next.js app (or any
React app) and passed a component reference directly to a
Forme entry point like pdfResponse(MyInvoice), you'd get
a cryptic Invalid hook call error with no indication of
what caused it or how to fix it.
Root cause: React Compiler injects a useMemoCache hook
into compiled functions. Forme's serializer calls user
components as plain functions outside of React's render
cycle — which is illegal for any function containing hooks.
Fix: 0.9.1 catches that error at every call site and
rethrows with a clear, actionable message:
Component "MyInvoice" appears to be compiled by React
Compiler, which injects hooks that cannot run outside of
React's render cycle.
Fix: Add 'use no memo' at the top of the function to opt it out:
function MyInvoice() {
'use no memo';
return ...;
}
Alternatively, wrap it in an inline arrow:
pdfResponse(() => )
The diagnostic is wired into:
@formepdf/react—serialize()(covers all consumers)@formepdf/next—renderPdf(),pdfResponse()@formepdf/hono—pdfResponse()@formepdf/resend—sendPdf(),renderAndAttach()
Who needs this
Anyone using React Compiler (reactCompiler: true in
Next.js 16, or the experimental Vite plugin) who passes
component references directly to Forme entry points.
The common inline pattern was already fine:
// ✅ This was never affected
pdfResponse(() => <MyInvoice data={data} />)Only the direct reference form tripped the bug:
// ❌ This threw a cryptic error in 0.9.0
pdfResponse(MyInvoice)Other changes
- Docker image
formepdf/formenow bases on
formepdf/rasterizer:0.9.1 RELEASE.mdupdated with--provenance=true --sbom=true
flags for supply-chain attestations on Docker builds
Upgrade
npm install @formepdf/react@0.9.1 @formepdf/core@0.9.1
# Plus any framework integrations you use:
npm install @formepdf/next@0.9.1
npm install @formepdf/hono@0.9.1
npm install @formepdf/resend@0.9.1Docker: formepdf/forme:0.9.1, formepdf/rasterizer:0.9.1
Crates: forme-pdf = "0.9.1"
v0.9.0
Forme 0.9.0
Released April 4, 2026
The biggest release since launch. 0.9.0 adds PDF operations
(redact, merge, certify, rasterize), a Documents archive,
audit trail, certificate storage, and text-search redaction
— transforming Forme from a PDF generation library into a
full PDF operations platform.
⚠️ Breaking Changes
sign → certify rename
All signing API surfaces renamed to better reflect the
cryptographic nature of the operation:
| Old | New |
|---|---|
signPdf() |
certifyPdf() |
SignatureConfig |
CertificationConfig |
signature prop |
certification prop |
POST /v1/sign |
POST /v1/certify |
certificatePem field |
certificate field |
privateKeyPem field |
privateKey field |
Sign() (Go) |
Certify() (Go) |
sign_pdf() (Python) |
certify_pdf() (Python) |
Old names continue to work via deprecation shims and serde
aliases. /v1/sign is removed from the self-hosted server.
See the migration guide.
New Features
True PDF Redaction
Content-stream text removal — not just a visual overlay.
Text operators are removed from the PDF byte stream,
metadata is scrubbed automatically on every redaction
(author, creator, edit history), and a black rectangle
is drawn over the redacted area.
65% of "redacted" PDFs produced by other tools still
expose the underlying text. Forme removes it entirely.
Available via POST /v1/redact or engine functions
redact_pdf(), redact_text(), find_text_regions().
curl -X POST https://api.formepdf.com/v1/redact \
-H "Authorization: Bearer $FORME_API_KEY" \
-d '{ "pdf": "...", "presets": ["ssn", "email"] }'Text-Search Redaction
Redact by literal string, regex pattern, or built-in preset
— no coordinate boxes required. Patterns can be scoped to
specific pages.
Built-in presets: ssn, email, phone, date-of-birth,
credit-card
{
"pdf": "...",
"patterns": [
{ "pattern": "John Smith", "pattern_type": "Literal" },
{ "pattern": "\\d{3}-\\d{2}-\\d{4}", "pattern_type": "Regex" }
],
"presets": ["email"]
}Redaction Templates
Save named pattern sets on the hosted API and reference
them by slug:
POST /v1/redact
{ "pdf": "...", "template": "hipaa-patient-record" }Create and manage templates in the dashboard under Redaction.
Docs →
PDF Merging
Combine up to 20 PDFs into one via POST /v1/merge or
the merge_pdfs() engine function. PDFs merged in array order.
curl -X POST https://api.formepdf.com/v1/merge \
-H "Authorization: Bearer $FORME_API_KEY" \
-d '{ "pdfs": ["", ""] }'PDF Rasterization
Convert PDF pages to high-quality PNG images. Powered by
PDFium — the same engine Chrome uses. Configurable DPI (72–300).
curl -X POST https://api.formepdf.com/v1/rasterize \
-H "Authorization: Bearer $FORME_API_KEY" \
-d '{ "pdf": "...", "dpi": 150 }'Returns { "pages": ["<base64 PNG>", ...] } — one per page.
Docs →
Documents Archive
Every hosted API render is saved automatically to your
Documents archive. Upload existing PDFs, redact them,
merge them, certify them — all from the dashboard or API.
- Source tracking:
generated,uploaded,redacted,
merged,certified - Retention: Free 30d · Pro 90d · Team 1yr · Business unlimited
- Storage: Cloudflare R2
- Opt out with
save: falseon any render
Tag documents with developer metadata for filtering:
{ "metadata": { "customerId": "cust_123", "department": "legal" } }GET /v1/documents?metadata.customerId=cust_123
Certificate Storage
Save X.509 certificates in the dashboard, reference by ID
at certify time. Private keys encrypted at rest (AES-256-GCM).
The plaintext key is never returned after saving.
POST /v1/certify
{ "pdf": "...", "certificateId": "cert_abc123" }Plan limits: Free 1 · Pro 5 · Team/Business unlimited
Docs →
Audit Trail
Full operation history on every document — who did what
and when, whether via dashboard or API key. Events logged:
uploaded, generated, redacted, merged, certified,
downloaded, deleted. Events survive document deletion.
Visible in the History panel in DocumentEditor and
queryable via API.
Docs →
Resource Listing Endpoints
List and retrieve your resources programmatically via API key:
GET /v1/templates
GET /v1/templates/:slug
GET /v1/documents
GET /v1/documents/:id
GET /v1/redaction-templates
GET /v1/redaction-templates/:slug
GET /v1/certificates
Async AI Template Generation
AI template generation no longer blocks the UI. The modal
closes immediately, the template appears in the list with
a shimmer while generating, and the thumbnail populates
automatically when complete. Powered by BullMQ + Redis.
Dashboard — DocumentEditor Redesign
Tools moved from a crowded top bar to a left sidebar panel:
- Redact — Draw mode (click and drag) + Search mode
(text patterns, presets, load template) - Merge — add PDFs, merge in order
- Certify — select saved certificate or paste PEM
- Organize — coming soon
History panel slides out from the right — full audit
trail per document without leaving the editor.
Self-Hosted Server Parity
The formepdf/forme Docker image is updated to 0.9.0
with improved parity on all operation endpoints:
POST /v1/certify(renamed from/v1/sign)POST /v1/redact— coordinate regions + text patterns + presetsPOST /v1/mergePOST /v1/rasterize— PDFium sidecar includedflattenForms=truequery parameter on render endpointsContent-Dispositionheader on slug renders- Consistent
{ "error": "...", "code": "NOT_IMPLEMENTED" }
shape on hosted-only feature stubs - Preset name validation, max 20 presets, regex compilation
validation on/v1/redact
Self-hosted intentionally excludes: Documents archive,
certificateId, redaction template slugs, save/saveName.
Pass credentials and patterns directly.
docker pull formepdf/forme:0.9.0Python SDK (0.9.1)
- New API client methods:
certify(),redact(),
merge(),rasterize() - Local WASM rendering via wasmtime with full component DSL
sign()removed,certify_pdf()replacessign_pdf()- Full docs at docs.formepdf.com/python-sdk
Go SDK (0.9.1)
- New API methods:
Redact(),Rasterize() Sign()deprecated shim removed — useCertify()- Full docs at docs.formepdf.com/go-sdk
Docs Restructure
- Per-endpoint API reference pages (render, certify,
redact, merge, rasterize) - New concepts section (documents, credentials, redaction,
redaction templates, digital certification, audit trail) - Migration guide for 0.8 → 0.9
- Bring Your Own UI guide
Bug Fixes
- WASM time panic —
certifyandredactpreviously
panicked in browser WASM with "time not implemented on
this platform". Fixed with#[cfg]conditional using
js_sys::Date::now()for WASM targets,
SystemTime::now()for WASI targets (Python/Go SDKs) - PKCS#1 auto-conversion —
certify_pdf()now accepts
both PKCS#8 (BEGIN PRIVATE KEY) and PKCS#1
(BEGIN RSA PRIVATE KEY) formats automatically __formeTypeversion guard — prevents misleading
"Top-level element must be<Document>" errors when
@formepdf/coreand@formepdf/reactare on different versions- SVG children — JSX children inside
<Svg>now
correctly serialized - SVG opacity —
opacity,fill-opacity,
stroke-opacitynow work correctly via ExtGState - Page style inheritance —
<Page style={{ fontFamily }}>
now correctly resolves to child nodes
Packages
| Package | Version |
|---|---|
@formepdf/core |
0.9.0 |
@formepdf/react |
0.9.0 |
@formepdf/cli |
0.9.0 |
@formepdf/renderer |
0.9.0 |
@formepdf/hono |
0.9.0 |
@formepdf/next |
0.9.0 |
@formepdf/resend |
0.9.0 |
@formepdf/mcp |
0.9.0 |
@formepdf/sdk |
0.9.0 |
@formepdf/tailwind |
0.9.0 |
@formepdf/templates |
0.9.0 |
forme-pdf (VS Code) |
0.9.0 |
formepdf (PyPI) |
0.9.1 |
forme-go (Go SDK) |
v0.9.1 |
forme-pdf (crates.io) |
0.9.0 |
formepdf/forme (Docker) |
0.9.0 |
formepdf/rasterizer (Docker) |
0.9.0 |
v0.8.3
Forme 0.8.3
New Features
SVG Children API
<Svg> now accepts JSX children as an alternative to the raw content string prop. This restores intellisense, syntax highlighting, and proper JSX formatting for SVG content.
Before:
<Svg width={794} height={200} viewBox="0 0 794 200"
content={`<path fill="#e7f2fe" d="M0 120C80 80 160 60 260 75..."/>`}
/>After:
<Svg width={794} height={200} viewBox="0 0 794 200">
<path fill="#e7f2fe" d="M0 120C80 80 160 60 260 75..." />
<path fill="#d7e8fe" d="M0 150C100 120 180 100 280 120..." />
</Svg>Both forms continue to work. camelCase props are automatically converted to kebab-case SVG attributes (strokeWidth → stroke-width, fillOpacity → fill-opacity, etc.). Nested children via <g> are supported.
SVG Opacity Support
opacity, fill-opacity, and stroke-opacity attributes on SVG elements are now applied correctly via PDF ExtGState. Previously these attributes were silently ignored.
<Svg width={400} height={200} viewBox="0 0 400 200">
<path fill="#60a5fa" opacity="0.3" d="..." />
<rect fill="#ef4444" fill-opacity="0.5" x="0" y="0" width="100" height="100" />
</Svg>Opacity inherits through <g> groups correctly.
VS Code Extension — Python Preview
The VS Code extension now supports previewing Python SDK templates. Open a .py file that imports formepdf and the "Preview PDF" button appears in the editor toolbar. The extension runs the script using your Python interpreter and displays the output PDF in the preview panel.
The script should write PDF bytes to stdout:
import sys
import formepdf
doc = formepdf.Document(...)
pdf = doc.render()
sys.stdout.buffer.write(pdf)Uses the Python interpreter from the VS Code Python extension if installed, falls back to python3 / python.
Bug Fixes
Page style prop now applies correctly
<Page style={{ fontFamily: 'Outfit' }}> was silently dropped — the style was not inherited by child elements. Fixed in both the React serializer and the layout engine. Page-level styles now propagate to children as expected.
Upgrading
npm install @formepdf/react@0.8.3 @formepdf/core@0.8.3
# Docker
docker pull formepdf/forme:0.8.3
# Go SDK
go get github.com/formepdf/forme-go@v0.8.3
# Rust crate
cargo add forme-pdf@0.8.3No breaking changes. Drop-in upgrade from 0.8.2.
All Packages
@formepdf/react · @formepdf/core · @formepdf/cli · @formepdf/renderer · @formepdf/hono · @formepdf/next · @formepdf/resend · @formepdf/mcp · @formepdf/sdk · @formepdf/tailwind · @formepdf/templates · forme-pdf (VS Code) · forme-pdf (crates.io) · formepdf (PyPI) · formepdf/forme (Docker) · github.com/formepdf/forme-go
v0.8.2
Forme 0.8.2
Fixed
Custom font weight resolution
When multiple weights are registered for the same font family, the PDF now correctly embeds and renders each weight as a distinct font. Previously, all weights were silently collapsed to 400 or 700 regardless of what was registered.
Font.register({ family: 'Geist', fontWeight: 200, src: '...Geist-UltraLight.ttf' });
Font.register({ family: 'Geist', fontWeight: 300, src: '...Geist-Light.ttf' });
Font.register({ family: 'Geist', fontWeight: 400, src: '...Geist-Regular.ttf' });
// All three now render with their correct font files
<Text style={{ fontFamily: 'Geist', fontWeight: 200 }}>Ultra Light</Text>
<Text style={{ fontFamily: 'Geist', fontWeight: 300 }}>Light</Text>
<Text style={{ fontFamily: 'Geist', fontWeight: 400 }}>Regular</Text>The bug was in the PDF serializer — four sites were snapping font_weight to 400 or 700 before building the font lookup key, discarding the actual registered weight. All four sites now use the raw weight value, with a fallback chain (exact → snapped → Helvetica) for cases where the requested weight has no registered variant.
Improved
- Docker build — Added
.dockerignoreto excludetarget/andnode_modules/from the build context, fixing out-of-space errors during multi-platform builds. - Release process — Documented SDK WASM rebuild steps (Python + Go), PyPI dist cleanup, and README update checklist.
Upgrading
npm install @formepdf/react@0.8.2 @formepdf/core@0.8.2
# Docker
docker pull formepdf/forme:0.8.2
# Go SDK
go get github.com/formepdf/forme-go@v0.8.2
# Rust crate
cargo add forme-pdf@0.8.2No breaking changes. Drop-in upgrade from 0.8.1.
All Packages
@formepdf/react · @formepdf/core · @formepdf/cli · @formepdf/renderer · @formepdf/hono · @formepdf/next · @formepdf/resend · @formepdf/mcp · @formepdf/sdk · @formepdf/tailwind · @formepdf/templates · forme-pdf (VS Code) · forme-pdf (crates.io) · formepdf (PyPI) · formepdf/forme (Docker) · github.com/formepdf/forme-go