Generate PDFs from reusable function components, JSX, or static HTML and CSS in Node.js or a browser worker. The package includes the Fullbleed Rust engine compiled to WebAssembly and four font faces, so installing it does not require Python, Rust, a browser, or system fonts. MIT licensed.
Import fullbleed for Node.js or fullbleed/browser for a web application.
Use fullbleed/ui to compose documents without writing HTML
strings. Named components, typed props, ordinary conditions and arrays, reusable
styles, and an optional JSX/TSX runtime work with either renderer. No React
runtime or build step is required for ordinary JavaScript functions.
Try a complete project online: JavaScript + Vite or React + TypeScript. The React project starts with a typed component invoice and editable line items; the JavaScript project includes HTML/CSS templates. Preview the result and download a PDF. Each starter also includes a ZIP for local development.
Using Vue? The Vue and TypeScript starter includes editable
templates, automatic previews, downloads, and a reusable usePdfPreview composable.
This is an optional integration around the published Fullbleed 2.5.29 engine. The package has its own version, 0.7.1. Node applications can use a shared render queue to bound active renders and waiting requests, include queue time in deadlines, cancel waiting jobs, and stop rendering cleanly during shutdown. The browser entry renders in a Web Worker, and the Node API supports worker and process isolation. See the browser guide, inline wrapping checks, process isolation guide, font-family checks, and embedded-font checks for the retained fixtures.
Version 0.7.1 brings the stable engine's SVG stylesheet and inset-shadow fixes to Node.js and browser workers. The paint checks cover stylesheet selectors, inherited SVG colors and opacity, and clipped, blurred, translated and negative-spread inset shadows. The component and static HTML APIs remain unchanged.
Version 0.7.0 added static HTML export with or without styles:
doc.toHtml({ includeStyles: true }) embeds the document stylesheet;
doc.toHtml({ includeStyles: false }) removes CSS while preserving class names
and content. Documents, fragments and JSX use the same export options. Importing
fullbleed/ui does not load the PDF renderer or add JavaScript to the output.
Version 0.6.0 added component authoring, attached stylesheets and explicit document rendering. The component guide includes zero-build JavaScript and TSX versions of a complete designed invoice.
Version 0.5.1 brought the engine's hidden-text and font-decoration fixes to both
JavaScript entries. Script/style elements and display:none descendants stay
hidden, while explicit author display overrides remain supported. Font-selected
decoration thickness uses the selected face's metrics. The
engine update checks
cover source/control PDFs, font metrics, and browser downloads. Embedded
stylesheets are still ignored; supply template CSS through the css option.
Version 0.5.0 adds defaultStyles: false to both JavaScript entries. Use it to
preserve a template's original CSS, including font inheritance from html,
low-specificity selectors, and cascade layers. Existing calls keep the Inter
body rule. Retained styling checks cover both choices.
Version 0.4.1 brought the engine's recent layout fixes to both JavaScript entries. The engine update checks cover nested counters, clipped effects, border-image tiling, and floated initials with independent text, color, and Chrome print expectations.
Use Node.js 22 or newer. Install the npm package:
npm install fullbleedFor a version-pinned installation, use npm install --save-exact fullbleed@0.7.1.
Package archives and retained PDF/PNG evidence are attached to each
GitHub release.
The installation verification records
retain published-package checks by version, including output hashes and failure
recovery. Use the record for the version you are evaluating.
Save this as invoice.mjs and run node invoice.mjs:
import { writeFile } from 'node:fs/promises';
import { renderPdf } from 'fullbleed';
import { Document, Heading, Paragraph, Strong, Stack, StyleSheet } from 'fullbleed/ui';
const Line = ({ label, amount }) => Paragraph(Strong(label), ': ', amount);
const invoice = Document({ title: 'Invoice', styles: new StyleSheet({
'@page': { size: 'A4', margin: '20mm' },
h1: { color: '#175c52' },
}) }, Stack({ gap: '12pt' },
Heading({ level: 1 }, 'Invoice NS-1042'),
Line({ label: 'Consulting', amount: 'USD 1,200.00' }),
));
const result = await invoice.renderPdf(renderPdf, { previewDpi: 96 });
await writeFile('invoice.pdf', result.pdf);
await writeFile('invoice.png', result.previews[0]);
console.log(`${result.pages} page; engine ${result.engineVersion}`);Inter is the default font. CommonJS also works:
const { renderPdf } = require('fullbleed'). TypeScript declarations are included.
Existing templates can still call renderPdf({ html, css }) directly.
Try the browser playground · Fullbleed documentation · CSS coverage · Python and Rust engine
The runnable example uses these same invoice sources and the bundled Inter, DM Serif Display, and Bebas Neue families. It escapes the customer name before inserting it into HTML. The data is fictional.
From a source checkout, after the build steps below:
npm run exampleOpen output/invoice/invoice.pdf and page-1.png. The three-page report
sources demonstrate pagination, tables, and vector artwork.
For a web application, start with the Next.js PDF download example. It includes a designed invoice, an App Router handler, production build settings for the worker and bundled assets, and a standalone-server verification script. The route bounds rendering and returns private PDF attachments from fictional data; connect your own authenticated record lookup when adapting it.
For React, use the React and TypeScript starter with a component-authored invoice, automatic previews, editable data, cancellation and a reusable preview hook. Its dedicated print TSX files use Fullbleed's JSX runtime.
Start with the complete browser application: editable HTML/CSS, invoice and report designs, previews, cancellation, and PDF downloads.
Copy the browser runtime into your application's static directory:
npx fullbleed-browser-assets public/fullbleedThen use the separate browser entry from a page served over HTTPS or localhost:
import { createRenderer } from 'fullbleed/browser';
const renderer = createRenderer({ assetBaseUrl: '/fullbleed/' });
const result = await renderer.renderPdf({
html: '<h1>Your document</h1>',
css: 'h1 { color: #175c52 }',
maxPages: 20,
});
const pdf = new Blob([result.pdf], { type: 'application/pdf' });The result uses browser byte arrays. The worker loads the shipped engine/fonts from your site and renders locally, with cancellation, deadlines and structured errors. See the browser API, download example and hosting requirements.
Supply file bytes explicitly. Additional fonts are registered alongside the
bundled fonts; use their actual family names in CSS. Asset names are relative to
the virtual assets/ directory:
import { readFile } from 'node:fs/promises';
import { renderPdf } from 'fullbleed';
const result = await renderPdf({
html: '<img src="assets/brand/logo.svg"><h1>Quarterly report</h1>',
css: "body { font-family: 'Your Font Family', Inter } img { width: 80pt }",
fonts: [await readFile('your-font.ttf')],
assets: { 'brand/logo.svg': await readFile('logo.svg') },
});The WebAssembly engine sees an in-memory filesystem containing the supplied inputs and bundled fonts. It does not fetch remote URLs or read application files. Use the asset map or data URIs for document images. Render and review previews when adapting a template; unsupported CSS or an unavailable asset can affect output.
Use registered font families in CSS. The engine can substitute an unavailable family. Its missing-glyph report checks resolved fonts and is not an exhaustive font-substitution or visual-validation report.
Native previews of unembedded Standard 14 fonts use fixed, bundled OFL outline substitutes starting with 0.3.2. Register your chosen font files for a specific branded appearance in both the PDF and its preview. The Standard 14 checks cover the twelve Latin faces, an embedded-font control, and the blank-preview failure in public 0.3.1. Font provenance and complete license notices ship with both the npm package and copied browser runtime.
Create one queue per application process and reuse it across requests:
import { createRenderQueue } from 'fullbleed';
const documents = createRenderQueue({ concurrency: 2, maxQueue: 4 });
const result = await documents.renderPdf({
html: '<h1>Queued invoice</h1>',
isolation: 'process',
timeoutMs: 30_000,
});
// Return result.pdf from your request handler.
// At application shutdown, cancel outstanding work and await cleanup:
await documents.close();This queue admits two active renders and four waiting requests. Further requests
reject with QUEUE_FULL, so a server can return a busy response. Waiting jobs
can be aborted and their deadlines include the wait. Slots stay occupied until
their worker or child process has exited. close() rejects new work, cancels
accepted jobs with QUEUE_CLOSED, and waits for cleanup.
Choose limits for your host and document sizes. The queue bounds job counts, not total memory, and each application process has its own queue. It is an in-memory admission control; use your application's durable job system for persistence and delivery. See the queue guide and retained checks and the six-invoice batch example.
This section describes the Node entry. The browser entry returns browser byte arrays through a configured renderer; see its API guide.
await renderPdf(options) returns { pdf, previews, pages, missingGlyphs, engineVersion }. PDF and PNG values are Node Buffer objects. Previews are
generated from the finalized PDF and are empty when disabled.
The report example uses gradient chart fills. Its finalized PDF previews and separate linear, translucent, and hard radial fixtures are checked against the native engine.
| Option | Behavior |
|---|---|
html |
Required nonempty HTML string. |
css |
CSS string; defaults to empty. |
defaultStyles |
Default true: prepend body { font-family: Inter; }. Set false to pass CSS unchanged; bundled fonts remain available. |
fonts |
Additional TrueType font bytes as Uint8Array[] or Buffer[]. |
assets |
Map relative asset names to nonempty byte arrays. |
previewDpi |
0 disables previews; otherwise an integer from 36 to 300. |
maxPages |
Reject larger documents. Default: 1000. |
timeoutMs |
Deadline including initial engine loading. Default: 30000 ms. |
signal |
An AbortSignal to cancel rendering. |
isolation |
'worker' (default) or 'process' for a separate Node process per call. |
allowMissingGlyphs |
Default false: reject engine-reported missing glyphs. |
Rendering runs in a dedicated worker. Concurrent calls use separate workers and document state. The returned promise waits for that worker to exit on success, render failure, timeout, or cancellation. Awaiting jobs in sequence therefore does not overlap their worker lifetimes. A timeout or abort starts termination; settlement includes the time needed to stop the worker.
Use isolation: 'process' when a render-process failure should become a rejected
request while the calling application stays running. This mode runs the engine
and its worker in a fresh Node process, and waits for process exit and IPC
disconnection before settling. It requires a host that permits child processes
and adds startup and memory overhead. It does not pool processes or retry jobs.
See the guide and failure checks.
Bound concurrency in a server according to its available memory; each worker's WebAssembly memory has a 512 MiB ceiling. HTML and CSS together are limited to 4,000,000 UTF-8 bytes; supplied fonts and assets together to 64 MiB. Preview images can use substantially more memory than PDF-only rendering.
import { FullbleedError, renderPdf } from 'fullbleed';
try {
const result = await renderPdf({
html: '<h1>Monthly statement</h1>',
maxPages: 20,
timeoutMs: 10_000,
isolation: 'process',
signal: AbortSignal.timeout(8_000),
});
// Return result.pdf from your HTTP handler, or write it to your storage.
} catch (error) {
if (error instanceof FullbleedError) console.error(error.code, error.message);
else throw error;
}Error codes include INVALID_INPUT, MISSING_GLYPHS, PAGE_LIMIT, TIMEOUT,
ABORTED, ENGINE_LOAD_FAILED, ENGINE_LIMIT, RENDER_FAILED,
PREVIEW_FAILED, WORKER_FAILED, and PROCESS_FAILED. Process failures may also
include exitCode and signal. Failed or cancelled calls do not return a partial
PDF. Caller-owned asset buffers are copied when a call starts.
createRenderQueue({ concurrency?, maxQueue? }) exposes renderPdf() with the
same options and result. Defaults are one active render and sixteen waiting
jobs. maxQueue: 0 rejects buffering. Accepted calls snapshot their strings and
asset/font bytes immediately. The queue adds QUEUE_FULL and QUEUE_CLOSED,
read-only activeCount, pendingCount, and closed properties, and an
idempotent asynchronous close() method. It does not reuse workers or change
the standalone renderPdf() concurrency behavior.
This API covers ordinary document generation and previews. It does not expose PDF/A, PDF/UA, PDF/X, template overlays, or compiled VDP. Use the Python or Rust APIs for those workflows and their verification requirements. Unknown options are rejected. Fullbleed uses static HTML/CSS for print layout; it does not execute document JavaScript or reproduce arbitrary browser pages.
Contributors need Node.js 22+ and Rust 1.97.0 with the WASI target. The independent PDF checks also use Python with the test-only readers listed below. Package users do not need a compiler or Python. Dependencies are pinned in both lockfiles.
rustup target add wasm32-wasip1
npm ci --ignore-scripts
npm run build
npm test
npm run check:types
npm run verify:native
npm run verify:pack
python -m pip install pypdf==6.19.0 pypdfium2==5.14.0 fonttools==4.65.0 pillow==12.3.0
npm run verify:text
npm run render:fonts
npm run verify:fonts
npm run render:families
npm run verify:families
npm run verify:browser:prepare
python -m pip install -r tools/browser-requirements.txt
python -m playwright install chrome firefox webkit
python tools/check-browser.py --browser chrome --label local
python tools/check-browser.py --browser firefox --label local
python tools/check-browser.py --browser webkit --label localThe build records engine metadata, licenses, and artifact hashes in dist/.
The verification suite renders actual invoices, a three-page report, custom
fonts, and image assets; compares native and WebAssembly PDF/PNG bytes; checks
error recovery; and installs the packed tarball into a separate directory with
spaces in its path. CI exercises Node 22, 24, and 26 on Windows, Linux, and macOS.
Retained release evidence describes the verified fixtures and scope.
Browser CI uses the same packed artifact in Chrome, Firefox and Playwright WebKit,
including actual downloads, finalized previews, asset failures and recovery.
The font check reads PDFs from the isolated tarball installation, checks the
embedded TrueType programs against their source fonts, and compares two
independent text readers. CI also compares text and native/PDFium pixels with
the previous public npm release.
An intermittent process crash observed on Linux with Node 24.21.0 remains under investigation in issue #7. Version 0.1.3 fixes worker shutdown timing, but does not establish that this crash is resolved. Version 0.2.0 adds optional process fault containment; it does not claim to fix the native crash. See the runtime investigation and diagnostic for the observed scope, retained results, and a synthetic reproducer.
The adapter and Fullbleed engine are MIT licensed. Bundled fonts use SIL OFL 1.1.
The WASI shim is used under its MIT option. Complete notices ship with the package
in dist/THIRD_PARTY_NOTICES.txt and assets/fonts/.
Report reproducible integration bugs in this repository's issues. Include the package and Node versions, platform, minimal synthetic inputs, and error code. For engine behavior and general usage, use the Fullbleed discussion forum.
