Every B2B SaaS platform eventually hits the same customer request: “Can we customize our invoices, reports, or contracts to match our branding?”
At first, you might offer a few toggle switches: upload a logo PNG, select a primary hex color, or type custom footer notes. But as enterprise customers join, their requirements escalate:
- “We need our tax registration number displayed in a specific layout for regional compliance.”
- “We need custom line-item columns with discount percentages.”
- “We need our legal terms in bilingual Arabic and English on the final page.”
At this crossroads, engineering teams face a daunting choice: spend 6–9 months building a fragile in-house drag-and-drop document builder, or integrate an embedded document editor API.
In this guide, we will walk through how to build a production-grade, white-label in-app PDF editor using React and the pdfs.build Embedded SDK (@pdfsbuild/react).
The Build vs. Buy Dilemma: Why Canvas Editors Fail at PDFs
Building an in-app document editor from scratch appears deceptively simple until you encounter print pagination.
Browser canvas libraries (Fabric.js, Konva, or HTML contenteditable blocks) treat pages as continuous web viewports or single-page canvases. When your users export a 5-page financial statement or a 40-item invoice, classical issues emerge:
- Broken Table Pagination: Tables split arbitrarily across pages, cutting text rows in half.
- Missing Running Headers: Page numbers like “Page 2 of 4” require complex DOM measuring hacks.
- Rendering Inconsistency: What the user sees on their 4K monitor does not match what prints on 300 DPI A4 paper.
- Heavy Headless Browsers: Converting the client DOM to PDF requires spinning up headless Chromium (Puppeteer/Playwright) on your backend, costing 150MB+ RAM per render.
With an embedded document editor API, your frontend embeds a native document designer backed by Typst—a typesetting engine designed specifically for paginated print documents. Your backend controls tenant permissions and handles rendering via a deterministic REST API.
Architectural Overview
The embedded document editor architecture operates on a zero-trust, scoped-session security model:
- Your Backend: Authenticates the tenant in your application (e.g., Acme Corp user #42).
- Session Exchange: Your backend calls the
pdfs.buildAPI using your secret API key to mint a short-lived (15-minute) session token scoped to a single template. - Frontend Mount: Your React frontend receives the session token and mounts the
<PdfReportEditor>component. - Direct Studio Interaction: Your user designs the template with AI assistance or visual controls. Changes compile in WebAssembly (Wasm) in their browser with instant live preview.
- Programmatic Rendering: When your application needs to generate the final PDF (e.g., during monthly billing runs), your backend calls the render API passing dynamic JSON data against the user’s saved template.
Your root API key never leaves your secure server environment.
Step 1: Minting a Tenant-Scoped Session Token
On your server, expose an endpoint (e.g., /api/templates/session) that checks your user’s permissions and requests an ephemeral session token from pdfs.build:
// app/api/templates/session/route.ts (Next.js 15 Server Route)
import { NextResponse } from "next/server";
export async function POST(request: Request) {
// 1. Verify user authentication in your application
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { templateSlug } = await request.json();
// 2. Request a scoped session token from pdfs.build
const response = await fetch(
`https://api.pdfs.build/v2/organizations/${process.env.PDFS_BUILD_ORG_ID}/templates/${templateSlug}/sessions`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PDFS_BUILD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
// Attribute usage and enforce isolation per tenant
externalTenantId: user.organizationId,
ttlSeconds: 900, // 15 minutes
}),
}
);
if (!response.ok) {
const error = await response.json();
return NextResponse.json({ error: error.message }, { status: response.status });
}
const { sessionToken, expiresAt } = await response.json();
return NextResponse.json({ sessionToken, expiresAt });
}
Step 2: Installing the React SDK
In your React or Next.js web application, install the official component package:
npm install @pdfsbuild/react
@pdfsbuild/react includes the full visual editor: AI chat assistant, Typst syntax canvas, live compiled PDF preview, JSON schema validator, and sample data controls.
Step 3: Mounting the White-Label Editor Component
Create an editor view in your frontend application. Wrap the editor inside <PdfReportProvider> and pass your session token and theming options:
// components/DocumentEditorModal.tsx
"use client";
import React, { useEffect, useState } from "react";
import { PdfReportProvider, PdfReportEditor } from "@pdfsbuild/react";
interface Props {
templateSlug: string;
onClose: () => void;
}
export function DocumentEditorModal({ templateSlug, onClose }: Props) {
const [sessionToken, setSessionToken] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchSession() {
try {
const res = await fetch("/api/templates/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ templateSlug }),
});
const data = await res.json();
setSessionToken(data.sessionToken);
} finally {
setLoading(false);
}
}
fetchSession();
}, [templateSlug]);
if (loading) {
return <div className="p-8 text-center text-sm text-gray-500">Loading document editor...</div>;
}
if (!sessionToken) {
return <div className="p-8 text-center text-red-500">Failed to start editor session.</div>;
}
return (
<div className="fixed inset-0 z-50 bg-slate-900/80 backdrop-blur-sm flex flex-col">
<div className="h-14 border-b border-gray-200 bg-white px-6 flex items-center justify-between">
<h2 className="font-semibold text-gray-900">Custom Invoice Designer</h2>
<button
onClick={onClose}
className="px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-100 rounded-lg"
>
Exit Editor
</button>
</div>
<div className="flex-1 w-full overflow-hidden">
<PdfReportProvider
token={sessionToken}
theme={{
primaryColor: "#2563eb", // Match your SaaS brand
borderRadius: "0.5rem",
fontFamily: "Inter, sans-serif",
}}
>
<PdfReportEditor className="w-full h-full" />
</PdfReportProvider>
</div>
</div>
);
}
Step 4: Composable Parts vs. Out-of-the-Box Studio
While <PdfReportEditor /> provides the complete designer experience with toolbar, preview, chat, and code panels, @pdfsbuild/react also offers composable parts if you prefer a customized layout:
<EditorToolbar />: Action bar with undo/redo, compile triggers, and save checkpoints.<EditorPreview />: Reactive PDF canvas compiling Typst at 60 FPS in WebAssembly.<EditorChat />: AI document co-pilot allowing non-technical users to say “Change the table header to dark navy and add a VAT column”.useEditorStores(): Headless React hook exposing compilation errors, active variables, and checkpoint history.
Step 5: Rendering User Templates at Scale
Once your customer has customized and saved their template, your backend generates production documents by calling the standard render endpoint:
// Example: Generating an invoice on schedule
const renderResponse = await fetch(
`https://api.pdfs.build/v2/organizations/${ORG_ID}/templates/${templateSlug}/render`,
{
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
data: {
invoice_number: "INV-2026-9021",
date: "2026-09-21",
customer: { name: "Global Enterprise Ltd", tax_id: "US-849201" },
items: [
{ description: "Enterprise SaaS Subscription (Annual)", amount: 12000 },
],
},
}),
}
);
const pdfBlob = await renderResponse.arrayBuffer();
If the customer customized their template layout, their changes are automatically reflected in the output. If the JSON data violates the template’s schema contract, the render fails cleanly before any document is generated.
Conclusion
Shipping an in-app document designer no longer requires building a custom canvas from scratch or managing heavy headless browsers.
With the embedded document editor API, you get:
- Zero pdfs.build branding: completely white-label UI.
- Complete tenant security: short-lived session tokens and per-tenant attribution.
- Fast Typst compilation: about 40ms to compile a one-page invoice, about 220ms median for a whole API render, and no browser to boot.
Explore the complete Embedded Editor Guide and start building today with @pdfsbuild/react.