Polotno
Export & Import

PDF Export

Export raster and vector PDFs from Polotno, with bleed, crop marks, PDF/X-4, PDF/X-1a, and spot color support

Polotno offers two PDF export paths:

  • Rasterstore.saveAsPDF() / store.toPDFDataURL() in the browser, or polotno-node on the server. Each page is a flattened image embedded in the PDF.
  • Vector@polotno/pdf-export (browser or Node), or the Cloud Render API with vector: true. Resolution-independent output with selectable text, including print-ready PDF/X.
RasterVector
OutputFlattened image per pageSelectable text, scalable shapes
Visual fidelityIdentical to canvasMay differ slightly (font shaping, effects)
File sizeLarger at high pixelRatioSmaller, resolution-independent
Print complianceGood with high pixelRatioRequired for PDF/X, spot colors
Where it runsBrowser, or polotno-node on the serverBrowser, Node, or Cloud Render API

Pick raster when you need pixel-perfect parity with the editor canvas. Pick vector for selectable text, smaller files, or print-shop deliverables. Both run fully client-side.

Demo

Every path in one editor — vector by default, a Flatten pages option for raster, and a Print-ready (PDF/X-4) option with CMYK conversion:

Try it without code: The SVG to PDF converter exposes both paths — drop a file and compare before integrating.

Raster export

Built into Polotno — no extra package. Same options apply to store.toPDFDataURL().

await store.saveAsPDF({
  fileName: 'design.pdf',
  pixelRatio: 2, // resolution; use 2 or 4 for print
  includeBleed: true,
  cropMarkSize: 20,
});

pixelRatio controls image quality; dpi affects PDF page dimensions, not quality. See Store API for the complete reference and Units and Measures for DPI details.

Vector export

All vector entries accept the same Polotno JSON and produce equivalent output — pick the one that matches your hosting model.

Browser

Convert a design entirely in the user's browser — no server, no API key.

npm install @polotno/pdf-export
import { jsonToPDFBlob } from '@polotno/pdf-export/browser';

const blob = await jsonToPDFBlob(store.toJSON());
// hand the blob to a download link, IndexedDB, navigator.share, …

jsonToPDFBytes(json, attrs): Promise<Uint8Array> is also exported for raw bytes.

Remote images, fonts, and SVG sources are fetched at export time, so they must be reachable with CORS. Google Fonts work out of the box; custom asset URLs need Access-Control-Allow-Origin, or inline them as data: URLs in the JSON.

The full option set works in the browser, including PDF/X and spot colors. Two runtime requirements: PDF/X-1a flattening needs OffscreenCanvas (Safari 16.4+), and a strict CSP must allow script-src 'wasm-unsafe-eval' for CMYK conversion.

Node

The same package inside Node.js 22.13+. Fits CI jobs, backend services, and desktop tools.

import { jsonToPDF } from '@polotno/pdf-export';

await jsonToPDF(designJson, './output.pdf');

jsonToPDFBytes(data, attrs) returns raw bytes without writing to disk.

Cloud Render API

Hosted rendering. Send the JSON as a render job with format: 'pdf' and vector: true. See Cloud Render API for options, polling, webhooks, and CMYK color profiles.

Fonts

Custom fonts registered in the JSON and Google Fonts are embedded automatically, as subsetted font files — text stays selectable and renders identically on any machine. System fonts (Arial, Times New Roman, Courier, …) map to the standard PDF base-14 fonts in a plain export; PDF/X embeds substitutes for them too, as the spec requires.

When a font has no glyph for a character — CJK text or an emoji in a Latin-only family — the exporter picks a font for that character's script. Set fallbackFont: 'Noto Sans TC' to pin the choice where the script alone cannot decide it.

Print-ready PDF/X

Written natively in both runtimes — no Ghostscript.

ModeOptionUseTransparency
Regular PDFOmit pdfxGeneral vector PDFPreserved
PDF/X-4pdfx: 'x-4'Preferred print formatPreserved
PDF/X-1a (legacy)pdfx: 'x-1a'Vendors that require X-1aTransparent pages become opaque CMYK images

Both PDF/X modes require an outputIntent — the ICC printing condition the file is prepared for. Ask your print vendor for the profile (FOGRA39 / PSO Coated v3 in Europe, GRACoL in North America).

import { readFile } from 'node:fs/promises';

const profile = new Uint8Array(await readFile('./CoatedFOGRA39.icc'));
await jsonToPDF(data, './print-ready.pdf', {
  pdfx: 'x-4',
  outputIntent: { profile, identifier: 'FOGRA39' },
  colorMode: 'cmyk', // optional for X-4; default 'preserve-rgb'
});

Options:

  • outputIntent — required with pdfx. { profile, identifier }, where profile is the ICC bytes (Uint8Array). In the browser, fetch the profile and pass new Uint8Array(await response.arrayBuffer()).
  • colorMode — X-4 only. 'preserve-rgb' (default) keeps colors as ICC-tagged sRGB for the RIP to convert; 'cmyk' converts every fill, stroke and gradient through the output profile.
  • pdfxRasterDpi — resolution at which X-1a flattens a page carrying transparency. Default 300. Opaque pages stay vector at any setting.
  • onWarning — called when the export degrades something to stay conforming (today: X-1a flattening a page). Defaults to console.warn.

An X-1a page that combines transparency with a spot color is refused rather than flattened, because flattening would destroy the separation.

Changed in 0.8.0: pdfx1a: true is deprecated — use pdfx: 'x-1a' and pass an outputIntent.

Spot colors and overprint

Map specific fills or strokes to separation inks for foil, Pantone, or varnish workflows. Any element using the matched color is exported with the correct separation ink; CMYK fallbacks ensure predictable previews.

await jsonToPDF(data, './foil-cover.pdf', {
  pdfx: 'x-4',
  outputIntent: { profile, identifier: 'FOGRA39' },
  spotColors: {
    'rgba(255,215,0,1)': {
      name: 'Gold Foil',
      pantoneCode: 'Pantone 871 C', // optional reference
      cmyk: [0, 0.15, 0.5, 0],      // 0–1 range, used as fallback
      overprint: true,
    },
  },
});

Color matching is flexible. '#FFD700', '#ffd700', 'rgb(255,215,0)', and 'rgba(255,215,0,1)' all match the same color. Works on text, lines, figures, and SVG elements.

overprint: true tells the press to print the spot ink on top of the underlying CMYK rather than knocking it out — the standard choice for foil and varnish, avoiding white halos from misregistration.

Tip: Verify spot colors in Adobe Acrobat Pro: Tools → Print Production → Output Preview → Separations. Use Simulate Overprinting to preview how the press will composite the layers.

Caveat: PDF/X-4 preserves spot colors alongside transparency. PDF/X-1a cannot — it refuses a page that combines a spot color with transparency (opacity, shadows, soft image alpha; SVG <clipPath> and <mask> count as transparency). For foil regions under X-1a, use plain shapes or simple SVG paths — or export X-4.

Page geometry

Bleed and crop marks

Set bleed (pixels) on each page in the JSON, then enable bleed and crop marks at export time:

const data = {
  width: 1080,
  height: 1080,
  pages: [{ background: '...', bleed: 36, children: [/* ... */] }],
};

await jsonToPDF(data, './print-ready.pdf', {
  includeBleed: true,
  cropMarkSize: 18, // reserve 18px around the bleed for crop marks
});

The output carries correct PDF/X page boxes (TrimBox ⊆ BleedBox ⊆ MediaBox), so RIPs and proofers identify the live area without guessing. Element coordinates stay relative to the trim corner; backgrounds extend automatically into the bleed strip. See Page Bleed for working with bleed in the editor.

DPI

Coordinates in the JSON are pixels at the specified DPI; PDFs use points (1pt = 1/72 inch). The library converts: points = pixels × (72 / dpi).

await jsonToPDF(data, './output.pdf', { dpi: 150 }); // override JSON dpi (default 72)

A 1920×1080 canvas at 300 DPI exports as 6.4″ × 3.6″; the same canvas at 72 DPI exports as 26.67″ × 15″.

Image detail

dpi sets how big the page is. imagePpi caps how sharp its images are, in pixels per printed inch — 450 by default, 300 for print, 150 for proofs. It only ever removes pixels. Set it from the output medium, not to shrink the file — a lower cap can make the PDF larger.

Progress and cancellation

onProgress reports how far along a long export is, and signal cancels it.

const controller = new AbortController();

await jsonToPDF(data, './output.pdf', {
  onProgress: (progress) => res.write(`data: ${progress}\n\n`), // e.g. SSE
  signal: controller.signal,
});

onProgress receives a monotonic 0..1 counting work items, not time — treat it as a position, not an ETA. signal rejects with signal.reason (an AbortError by default), removes temporary files, and leaves no output file behind. An abort after 0.95 is ignored; font downloads are not cancellable because they share a process-level cache.

Restricted media hosts

Node only. Cloud-metadata addresses such as 169.254.169.254 are blocked by default; normal URLs, localhost and private hosts still work. Restrict further when the design JSON comes from your users:

await jsonToPDF(data, './output.pdf', {
  blockPrivateNetwork: true, // also block loopback and private addresses
  fetchGuard: (url) => isAllowed(url), // your own rule, replaces the checks above
});

Error handling

Failures throw a plain Error with a codeDESIGN_INVALID (the schema pre-flight every render runs first), IMAGE_FAILED, FONT_FAILED, FETCH_FAILED, or EXPORT_FAILED — plus structured details such as elementId, mimeType, and a fine-grained details.reason. Branch on the code, not the message text. See Error Handling for the full vocabulary.

Troubleshooting

Bleed not visible on canvas? Use store.toggleBleed(true).

Raster export looks low quality? Increase pixelRatio to 2 or 4. dpi controls page dimensions, not quality.

Wrong page size? Adjust the JSON dpi value (or pass dpi to jsonToPDF).

Export refused with a spot color on a transparent page? That is PDF/X-1a: it cannot carry both. Remove the transparency (<clipPath> and <mask> count), or export pdfx: 'x-4', which preserves spot colors alongside transparency.

A page came back as one big image in PDF/X-1a? The page carries transparency, so it was flattened to CMYK at pdfxRasterDpi (default 300). onWarning reports each flattened page. Remove the transparency to keep the page vector, or use PDF/X-4.

For all options and the latest changes, see the @polotno/pdf-export README on npm.

On this page