← dompdf.js Studio

Generate Quote PDF in Browser: A Complete Guide

A quote is one of the most common business documents in the sales workflow. When a prospect asks for pricing, the sales team needs to send a clean, professional, archivable PDF quote as quickly as possible, because every hour of delay can cost a deal. The traditional approach pushes quote data to a backend server, where a template engine renders a PDF and streams it back. That works, but it brings real costs: you deploy and maintain a rendering service, template changes require a release cycle, requests queue under high load, and server-side environments frequently lack Chinese fonts, producing PDFs full of garbled characters. dompdf.js offers a fundamentally different path. It is a pure frontend DOM-to-PDF library with a Rust and WebAssembly core that renders HTML directly to vector PDF inside the browser, with no backend involvement at all. This article walks through the complete workflow of generating professional quote PDFs with dompdf.js, covering table design, Chinese typography, automatic pagination, headers and footers, and includes runnable code examples you can adapt today. Beyond the mechanics, we will also talk about how to keep the template maintainable, how to validate quote data before rendering so mistakes never reach the customer, and how to avoid the classic layout traps such as misaligned columns and missing borders. Whether you are building a quoting module for a SaaS product, adding export capability to a CRM, or simply want to stop printing web pages to PDF, the patterns here will save you hours of trial and error. By the end you will have a production-ready mental model: design the template once, drive it with data, render with two API calls, and ship.

Business Scenarios and Pain Points of Quote Generation

Quotes are a daily necessity across sales, foreign trade, engineering, and software services. A standard quote contains customer information, product line items, unit prices, quantities, discounts, tax rates, and a total amount. Because sales representatives usually need to send a quote immediately after a prospect asks for pricing, generation speed directly affects win rates and deal velocity.

Backend generation has well-known drawbacks. It requires deploying and operating a PDF rendering service, template updates must go through the release pipeline, and APIs tend to time out under high concurrency. Chinese fonts are a notorious pain point: when the server environment lacks proper CJK fonts, the generated PDF shows tofu blocks or mojibake, and fixing those issues after the fact is expensive.

Frontend generation eliminates all of this. dompdf.js ships with Source Han Sans SC built in, so Chinese text renders correctly without any font configuration. Quote data is assembled locally in the browser, never touching the server, which means near-instant responses and natural horizontal scaling. This makes it an excellent fit for SaaS platforms, CRM systems, and e-commerce back offices.

Version control is another quiet problem. The same customer often receives several rounds of revised pricing, and each version should be traceable in case of disputes. By writing the quote version and validity period directly into the template, and by adopting a consistent file naming convention such as quote-QT-2026-0818-v2.pdf, you give every revision a clear identity that salespeople and customers can refer to unambiguously.

Data accuracy matters as much as speed. A quote with a miscalculated subtotal or a stale price destroys trust instantly. Before rendering, validate the input: check that prices are present, quantities are positive integers, discounts stay within allowed ranges, and the total equals the sum of the line items. Catching these problems before the PDF exists is far cheaper than explaining them to an angry customer afterwards.

One more angle worth considering is the sales team's workflow. Salespeople often work from templates in spreadsheets or email drafts, and copying a quote into a different format every time invites typos. When the quote PDF is generated from the same data that drives the CRM record, the numbers travel from the system of record to the customer's inbox untouched by human retyping, which is both faster and safer.

If your business frequently negotiates, the quote should reflect the negotiation state: draft, sent, accepted, expired. Rendering that status into the PDF header keeps everyone honest about where a deal stands, and regenerating the PDF when the status changes is trivial because the template is deterministic and the data is the only input.

Why dompdf.js Instead of jsPDF or html2canvas

The common frontend PDF options are jsPDF, html2canvas, and dompdf.js. jsPDF is coordinate-based: building a table-heavy quote means hand-computing the position of every cell, which produces verbose, hard-to-maintain code, and multi-page tables are a nightmare. html2canvas takes a screenshot of the page as a bitmap and embeds that image into a PDF, so text becomes blurry when zoomed, file sizes balloon, and print quality falls short of business standards.

dompdf.js takes a completely different approach. Its Rust and WebAssembly core parses the DOM and CSS right in the browser and emits true vector PDF. Vector output means text and lines stay razor sharp at any zoom level, files are small and open quickly, and the resulting PDFs are searchable and selectable by readers, which is a huge practical advantage in business communication.

dompdf.js supports table borders, flex and grid layout, image embedding, and the @page pagination media properties, making structured business documents like quotes its sweet spot. It is open source on GitHub (lmn1919/dompdf.js, around 1.5k stars), and its API is refreshingly small: addPage plus save is the entire flow from HTML to PDF.

From a development standpoint, dompdf.js also keeps the team efficient. The quote template is ordinary HTML and CSS, so any frontend engineer can pick it up without learning a server-side template engine. Templates live in version control, go through code review like any other frontend change, and can be regression-tested visually, which is rarely practical with backend template systems.

There is also the matter of total cost of ownership. No rendering service to operate, no font packages to install on servers, no queue to tune during traffic spikes. The feature ships as part of the frontend bundle, scales with the number of browsers rather than server instances, and fails softly: if the library misbehaves, only the export button breaks, not the whole checkout flow.

Designing the Quote Template: Tables and Styling

A quote template should follow business document conventions: the company logo and quote title at the top, customer information and a quote number in the middle, the product line-item table as the main body, and a footer with totals, validity period, and signature area. Use a table element with border-collapse: collapse so the borders render cleanly and uniformly.

For styling, use a style tag or inline styles to control column widths, alignment, and font sizes. dompdf.js supports flex and grid layout, so the header can arrange the logo and quote number side by side with flex. As a general rule, keep body text at 10pt or larger and headings around 16pt so the document reads well both on screen and in print.

Pay attention to alignment conventions: amounts right-aligned, quantities centered, and product names left-aligned with a generous column width. When the line items exceed one page, dompdf.js paginates automatically and repeats the table header on the new page, so the client can keep reading the remaining pages without losing context.

In practice, extract the quote template into a dedicated function or component that takes a quote data object and returns an HTML string. The same function can feed a live preview area on the web page and be passed straight to addPage for the PDF, guaranteeing that what the salesperson previews is exactly what the customer receives. That single-source-of-truth approach eliminates a whole class of preview-versus-output discrepancies.

Plan for reuse from day one. Many businesses issue quotes in multiple currencies, apply customer-specific discount tiers, or include payment terms and bank details. Keep those blocks as optional sections in the template and let the data decide what gets rendered. The result is one template covering a dozen quote variants instead of a pile of near-duplicate HTML files that drift apart over time.

Code Example: From Data to PDF in One Click

Here is a complete quote generation example. We build the quote HTML as a string, create a DomPDF instance, add a page with A4 format and a 20mm margin, and save the file. The entire core logic takes only a handful of lines.

When quote data comes from an API, fetch the JSON first, then use template literals or an array map to generate the table rows, and assemble the full HTML. The quote becomes fully data-driven: when customers, products, or prices change, no code needs to be modified.

The example uses a light gray header row to separate it from the data rows, right-aligned amounts with thousands separators, and a merged colspan for the total row, all standard practice in business quotes. Extend it with a company logo image, payment terms, a validity period, or a discount column driven by the customer tier, and you have a template that looks like it came from a design agency rather than a quick script.

When line items vary wildly in length, sort and format the data before assembly: render empty values as dashes, filter out zero-quantity rows, and normalize currency formatting in one place. Keep that logic in a separate, testable helper so you can unit-test it and run a validation pass right before rendering. Data problems should be caught at the data layer, not discovered by a customer opening the PDF.

import { DomPDF } from 'dompdf.js';

// 1. Build the quote HTML
const quoteHtml = `
  <h1 style='text-align:center'>Product Quote</h1>
  <p>Customer: Acme Technology Co., Ltd.    Quote No: QT-2026-0818</p>
  <table style='width:100%;border-collapse:collapse'>
    <tr style='background:#f5f5f5'>
      <th style='border:1px solid #333;padding:8px'>Product</th>
      <th style='border:1px solid #333;padding:8px'>Unit Price</th>
      <th style='border:1px solid #333;padding:8px'>Qty</th>
      <th style='border:1px solid #333;padding:8px'>Subtotal</th>
    </tr>
    <tr>
      <td style='border:1px solid #333;padding:8px'>Enterprise License</td>
      <td style='border:1px solid #333;padding:8px'>$1,200</td>
      <td style='border:1px solid #333;padding:8px'>10</td>
      <td style='border:1px solid #333;padding:8px'>$12,000</td>
    </tr>
    <tr>
      <td style='border:1px solid #333;padding:8px'>Annual Maintenance</td>
      <td style='border:1px solid #333;padding:8px'>$800</td>
      <td style='border:1px solid #333;padding:8px'>10</td>
      <td style='border:1px solid #333;padding:8px'>$8,000</td>
    </tr>
    <tr>
      <td colspan='3' style='border:1px solid #333;padding:8px;text-align:right'>Total (incl. tax)</td>
      <td style='border:1px solid #333;padding:8px'>$20,000</td>
    </tr>
  </table>
`;

// 2. Create the PDF and add a page
const pdf = new DomPDF();
pdf.addPage(quoteHtml, { format: 'A4', margin: '20mm' });

// 3. Save the file
pdf.save('quote-QT-2026-0818.pdf');

Automatic Pagination, Headers, Footers, and Page Numbers

Business quotes usually need headers, footers, and page numbers. dompdf.js supports headers, footers, and page numbering out of the box, and the @page media properties give you precise control over page size and margins. Combined with automatic pagination, even a quote with dozens of line items splits into clean, readable pages.

Declaring @page { size: A4; margin: 20mm; } in the stylesheet standardizes the page geometry. Put the company name in the header and the page number plus quote number in the footer, so multi-page quotes look professional and are easy to file and print.

For multi-page quotes, call addPage several times in a row, for example one page for the cover, one for the product details, and one for the terms and conditions, then save once to produce a single PDF file. addPage also accepts a DOM element, so an already-rendered quote section on the page can be passed directly for a what-you-see-is-what-you-get result.

One subtle pagination detail is worth mentioning: try to keep table rows from splitting across page boundaries. Repeating header rows on each page helps, and ensuring at least two data rows move together prevents a lonely header or a single orphaned cell at the bottom of a page. dompdf.js handles most of this automatically, but avoid oversized fonts and extremely wide images in the template so the paginator has room to work.

When a quote runs to several pages, readers appreciate consistent furniture: the same header on every page, the quote number and page X of Y in the footer, and the total amount repeated on the final page. This small attention to detail makes a multi-page quote feel like one designed document rather than a stack of loose sheets, which matters when the CFO of the prospect company is the one reading it.

import { DomPDF } from 'dompdf.js';

const html = `
  <style>
    @page { size: A4; margin: 20mm; }
  </style>
  <h1>Quote</h1>
  <!-- more content -->
`;

const pdf = new DomPDF();
pdf.addPage(html, { format: 'A4', margin: '20mm' });
pdf.addPage(secondPageHtml); // append a second page
pdf.save('quote-multipage.pdf');

Frequently Asked Questions and Best Practices

Problem one: Chinese characters render as tofu blocks. dompdf.js bundles Source Han Sans SC, so Chinese works without any configuration in normal cases. If you load custom fonts, make sure the font files actually load, and prefer the built-in Chinese font family to keep things predictable.

Problem two: table borders do not show up. Check that border-collapse is set and that cell borders are explicitly declared. dompdf.js fully supports table borders, but they must be explicit, and watch out for global CSS reset rules that might strip borders.

Problem three: images fail to render or get distorted. dompdf.js supports image embedding; use base64 data URLs or same-origin URLs and always set explicit width and height. Cross-origin images may need to be converted to data URLs first to avoid browser security policies blocking them.

One more practical point: quotes contain commercial information that competitors would love to see. Prefer downloading directly from the user's browser over uploading to a public store; if the system must keep an archive, send it back over an encrypted channel and control access permissions so pricing data does not leak through a shared bucket or a loose API endpoint.

Finally, keep the user experience in mind. Add a short loading state while the PDF renders, disable the button during generation to prevent double clicks, and provide a fallback message if the download is blocked by the browser. Small touches like these determine whether a feature feels polished or fragile, and they cost almost nothing to implement.

Also worth noting is the fallback path for older browsers. dompdf.js targets modern browsers, but if a small percentage of your users are on legacy engines, detect support up front and show a graceful message instead of a broken export button. A one-line capability check beats a support ticket from a frustrated prospect.

Finally, measure and iterate. Track how often the export button is used, whether users re-download the same quote, and whether customers open the PDF at all. Those signals tell you whether the feature is serving its purpose, and because the template is pure frontend, iterating on it is cheap and fast.

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。