# Custom PDF backend > Swap the bundled jsPDF pipeline for a mocked / Worker / pdf-lib renderer via Editor.Root's pdf prop. Source: https://docs.docmosaic.com/docs/examples/custom-pdf-backend `Editor.Root` accepts a `pdf` prop with two optional functions - `generate` and `estimate`. Anything omitted falls back to the bundled `@docmosaic/core` implementation. That's the seam for swapping `jspdf` for `pdf-lib`, moving generation to a Worker, sending the document to your own backend, or - most usefully during development - mocking the whole thing out. This example wires a mocked backend so you can see the seam without standing up a renderer. ## Code ```tsx 'use client'; import { Editor } from '@docmosaic/react'; import type { generatePDF, estimatePDFSize } from '@docmosaic/core'; import '@docmosaic/react/styles.css'; // Mock generator - produces a tiny fake "PDF" blob so callers see a download. // Replace with pdf-lib, a Worker, or a server endpoint when you're ready. const mockGenerate: typeof generatePDF = async (sections, options, onProgress) => { for (let i = 0; i <= 10; i++) { if (options.signal?.aborted) throw new Error('PDF generation cancelled'); await new Promise((r) => setTimeout(r, 50)); onProgress?.(i / 10); } const text = `Mocked PDF - ${sections.length} sections\n`; return new Blob([text], { type: 'application/pdf' }); }; // Cheap heuristic: 4 KB per section. The bundled estimator is ~50 lines of // jspdf-specific math; for a mock we just need *something* monotonic. const mockEstimate: typeof estimatePDFSize = (sections) => sections.length * 4096; export default function CustomPdfBackendEditor() { return ( ); } ``` `generate` must honor `options.signal` - users can cancel mid-export via the bundled progress overlay, and the editor expects an `AbortError`-shaped throw (`Error('PDF generation cancelled')`) when they do. `onProgress(0 → 1)` feeds the `Editor.GenerationProgress` overlay. `estimate` runs every time the document changes. Keep it cheap - the bundled badge re-evaluates on every drag. If your renderer can't produce a fast estimate, omit `estimate` and the file-size badge falls back to the bundled heuristic. ## Worker offload (sketch) For real renderers, the same pattern moves the work off the main thread: ```tsx const generate: typeof generatePDF = async (sections, options, onProgress) => { const worker = new Worker(new URL('./pdf-worker.ts', import.meta.url), { type: 'module' }); try { return await new Promise((resolve, reject) => { worker.addEventListener('message', (e) => { if (e.data.type === 'progress') onProgress?.(e.data.value); if (e.data.type === 'done') resolve(e.data.blob); if (e.data.type === 'error') reject(new Error(e.data.message)); }); worker.postMessage({ sections, options }); options.signal?.addEventListener('abort', () => { worker.terminate(); reject(new Error('PDF generation cancelled')); }); }); } finally { worker.terminate(); } }; ``` ## Related - [Custom PDF backend recipe](/docs/recipes/custom-pdf-backend) - narrative version - [Editor.Root](/docs/primitives/root) - the `pdf` prop signature - [usePdfGeneration hook](/docs/reference/hooks) - the underlying generation state