Hey Vue developers đź‘‹
I’ve been building vue-pdf-export, an open-source Vue 3 + TypeScript library for generating PDFs directly in the browser.
I started this project because I kept rebuilding the same PDF features across Vue applications: pagination, preview, printing, headers, footers, progress tracking, and long-document support. I wanted a reusable component and composable rather than repeating that infrastructure in every project.
In short: Generate PDFs from Vue 3 components in the browser using either Canvas rendering or selectable, searchable text. The package also supports smart pagination, tables, headers, footers, watermarks, preview, download, print, and SSR-safe imports for Nuxt 3 and Nuxt 4.
You can explore the actual PDF output without setting up a project:
- Live playground: https://vue-pdf-export-playground.vercel.app/
- Full documentation: https://vue-pdf-export-docs.vercel.app/
Here is what the published package supports:
In this guide
- Features and quick start
- Rendering modes: Canvas vs selectable text
- Smart PDF pagination
- Nuxt 3 and Nuxt 4 SSR integration
- API, headers, footers, watermarks, and security
- Programmatic output and TypeScript
- Styling, composition, and playground demos
- Production, performance, and troubleshooting
- Testing and browser support
- Developer FAQ and next steps
Quick answer - How do I generate a PDF in Vue 3? Install
vue-pdf-export, importHtml2Pdfand its stylesheet, place your document in the#pdf-contentslot, and callgeneratePdf()from a Vue template ref. Userender-mode="text"for native searchable/selectable body text; chooserender-mode="canvas"when complex browser CSS fidelity matters most. Both modes generate PDFs in the browser.
Features
- Powerful: choose Canvas or selectable-text output, use your own Vue document components, and control page breaks.
- Modular: use the component or composable, and add only the slots and options your document needs. PDF engines load when generation runs.
- Customizable: style exported content with your CSS and export-only options; theme the built-in preview and loader with CSS variables.
- Canvas and selectable-text PDF rendering
- Smart automatic/manual pagination, keep-together blocks and container-aware splitting
- Explicit page breaks, repeated table headers and opt-in pagination diagnostics
- Preview, download, and native browser print
- Blob, ArrayBuffer, Base64, and PDF data-URL output
- Header/footer object options, Vue slots and page targeting
- PDF composition: prepend or append existing PDF pages
- Standard paper formats and custom dimensions in portrait or landscape
- Text, image and trusted HTML watermarks with optional design presets; text marks use reusable vector outlines instead of selectable PDF text
- Password protection, permissions, metadata, and compression
- Clickable PDF links
- Progress reporting, generation metrics, and cancellation
-
data-pdf-ignorefor export-only DOM control - PDF-only page/body styling and custom CSS
- Large-document rendering optimizations
- Font readiness, optional TTF/TrueType-outline OTF embedding, chart snapshots and SVG fallback
- Opt-in image optimization with explicit quality controls and per-export asset reuse metrics
- Nuxt 3 and Nuxt 4 SSR-safe imports
- TypeScript declarations
- npm, pnpm, Yarn, Yarn PnP, and Bun consumer validation
For detailed API options, examples, renderer notes, styling, pagination,
security, SSR guidance, and production recommendations, see the
full documentation.
Security note: Header, footer, and slot HTML is sanitized before entering export staging. Treat caller-provided HTML as untrusted and apply your application’s input validation policy.
Privacy and network boundary: Ordinary PDF generation happens in your browser and does not upload document content to a package-operated server. URL-based templates, remote fonts/images, and external export stylesheets may still be fetched from their configured origins.
Install
npm install vue-pdf-export
Small example
<script setup lang="ts">
import { ref } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const pdfRef = ref<InstanceType<typeof Html2Pdf> | null>(null);
</script>
<template>
<button @click="pdfRef?.generatePdf()">Generate PDF</button>
<Html2Pdf ref="pdfRef" filename="report.pdf">
<template #pdf-content>
<h1>Hello from Vue</h1>
<p>This content will be exported to PDF.</p>
</template>
</Html2Pdf>
</template>
Try it
Playground:
https://vue-pdf-export-playground.vercel.app/
Documentation:
https://vue-pdf-export-docs.vercel.app/
npm:
https://www.npmjs.com/package/vue-pdf-export
I’d really like feedback from Vue developers who have worked with PDFs in production - especially around pagination, large documents, selectable text, and API ergonomics.
Complete Vue 3 PDF generation guide
Scope: This guide describes the currently published
vue-pdf-exportfunctionality and combines practical examples with API detail. Code samples and recorded benchmarks reflect the documented test scenarios; run your own target-browser tests before shipping production PDFs. For the maintained source of truth, use the official documentation and GitHub repository.
This complete technical guide covers installation, JavaScript and TypeScript usage, components and composables, rendering modes, pagination, styling, page layers, programmatic output, performance, testing, production checks, and browser limitations. It also links to the detailed performance engineering report for reproducible measurements.
Get Started
Introduction
vue-pdf-export is a Vue 3 HTML-to-PDF component and composable for browser applications. Use it from JavaScript or TypeScript.
The API includes smart split controls, row-aware tables, header/footer objects and Vue slots, PDF composition, expanded paper sizes, font readiness, chart snapshots, SVG fallback, and opt-in image optimization.
It supports two rendering modes:
-
Canvas mode - maximum HTML/CSS visual fidelity through
html2pdf.js+html2canvas+jsPDF. - Selectable text mode - browser-side vector PDF rendering with real selectable/searchable/copyable text, clickable PDF links, password protection, metadata, compression, page-aware headers/footers, and watermarks.
It also includes manual and automatic pagination, preview, download, native browser print, Blob/ArrayBuffer/Base64 output, export-only page/body styling, data-pdf-ignore, custom export CSS, external print stylesheets, forced light/dark themes, width auto-fit, safe filename normalization, progress stages, opt-in generation metrics, current-page progress, cancellation, page targeting, PDF permissions, SSR-safe imports, large-document optimizations, and advanced renderer configuration.
Default rendering mode:
renderMode="canvas". The live playground may select another default for demonstration purposes.
Highlights
- Browser-side PDF generation with no package-managed upload service
- Canvas and selectable-text rendering
- Blob, ArrayBuffer, raw Base64, and PDF data-URL output
- Native browser print support in both rendering modes
- Incremental page-by-page Canvas rendering when page-break markers are present
- Cooperative cancellation for long-running exports
- Page-aware progress with
currentPage/totalPages - Opt-in total, stage, and per-page generation timing metrics
-
data-pdf-ignorefor screen-only UI that must stay out of generated PDFs - Export-only page/body styling without mutating the visible Vue DOM
- Repeated-image caching in selectable-text rendering
- Optional TTF/TrueType-outline OTF embedding for selectable text
- Export-only chart snapshots, SVG fallback and explicit image quality controls
- Text, image and trusted HTML watermark designs with per-export reuse
- Temporary DOM/canvas cleanup between expensive rendering steps
- Password protection, owner password, PDF permission flags, metadata, and compression
- Headers, footers, logos, watermarks, page targeting, and clickable links
- SSR-safe package imports with Nuxt 3 and Nuxt 4 validation
- Packed-consumer verification across npm, pnpm, Yarn, Yarn Plug'n'Play, and Bun
- Large-document stress presets from 5 through 1000 pages, plus custom counts
Presets are test inputs, not a guarantee that every page count will succeed on every device. Test representative documents in your target browsers.
Try it before integrating: The live playground exposes package options, output APIs, and diagnostics interactively.
Open the interactive PDF playground
Open playground
Installation
Install the package using your preferred package manager:
npm install vue-pdf-export
# or
pnpm add vue-pdf-export
# or
yarn add vue-pdf-export
# or
bun add vue-pdf-export
Import
TypeScript:
import {
Html2Pdf,
type PdfBase64Options,
type PdfMetadataOptions,
type PdfOutputFormat,
type PdfProgressState,
type PdfRenderMode,
type PdfSecurityOptions,
type PdfSerializedOutput,
type PdfStylingOptions,
type WatermarkOptions,
} from "vue-pdf-export";
import "vue-pdf-export/style.css";
JavaScript:
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
Package-manager compatibility
The packed package is validated as a consumer installation with:
- npm
- pnpm
- Yarn
- Yarn Plug'n'Play
- Bun
The compatibility fixtures verify installation, TypeScript resolution, production builds, CSS imports, and browser-side PDF generation.
Nuxt
vue-pdf-export can be imported during SSR, while PDF generation itself remains browser-only.
See Nuxt 3 & Nuxt 4 SSR for <ClientOnly>, lazy-import, and client-event patterns.
Quick Start
<script setup lang="ts">
import { ref } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const pdfRef = ref<InstanceType<typeof Html2Pdf> | null>(null);
async function generatePdf() {
const blob = await pdfRef.value?.generatePdf();
console.log(blob);
}
</script>
<template>
<button type="button" @click="generatePdf">Generate PDF</button>
<Html2Pdf
ref="pdfRef"
filename="my-report"
render-mode="canvas"
:manual-pagination="true"
:enable-download="true"
>
<template #pdf-content>
<div>
<h1>My Report</h1>
<p>This content will be rendered into the PDF.</p>
</div>
</template>
</Html2Pdf>
</template>
Plain JavaScript with the Vue Options API
TypeScript is optional. Register Html2Pdf locally and call generatePdf() through a template ref. PDF generation runs in the browser after the component mounts.
<script>
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
export default {
components: { Html2Pdf },
methods: {
async generate() {
const blob = await this.$refs.pdf.generatePdf();
console.log("PDF bytes:", blob.size);
},
},
};
</script>
<template>
<button type="button" @click="generate">Generate PDF</button>
<Html2Pdf ref="pdf" :enable-download="false">
<template #pdf-content
><article><h1>JavaScript report</h1></article></template
>
</Html2Pdf>
</template>
Without a pagination height, the renderer handles basic page flow. Set a positive paginateElementsByHeight when you want the smart DOM planner; see pagination. The Options API example is based on the library's npm consumer fixture.
Rendering Modes
Canvas mode
Canvas mode is the default and uses the package's existing html2pdf.js / html2canvas / jsPDF pipeline.
<Html2Pdf render-mode="canvas" manual-pagination />
Use it when the highest possible browser CSS fidelity is more important than selectable body text.
Canvas keeps inline SVG in its export staging by default, but the final PDF page is still a raster image. Chart canvases are snapshotted on the private export clone before capture. See fonts, charts, SVG and images.
Best for:
- complex CSS
- gradients
- shadows
- transforms
- custom web fonts
- visual dashboards
- designs where browser appearance is the priority
Body content is rasterized, so normal body text is not selectable/searchable.
A viewer such as Chrome may apply OCR and let users select apparent text in an image-only PDF. A foreground text watermark or some numbering paths can also add native text over a Canvas body. Inspect PDF text operators or extract text with a PDF tool to distinguish native text from viewer OCR.
Incremental Canvas rendering
When the export DOM contains .html2pdf__page-break markers, Canvas mode uses an incremental page-by-page renderer instead of capturing the entire document as one giant canvas.
This path:
- clones and renders one logical PDF page at a time
- keeps raster dimensions page-bounded
- releases temporary page DOM after
html2canvasfinishes reading it - releases canvas backing stores after insertion into jsPDF
- reports page-level progress
- yields to the browser between expensive steps
- supports cooperative cancellation before the next page begins
Canvas documents without page-break markers use the whole-document html2pdf.js worker. Paginated Canvas documents can use page-bounded incremental captures.
Oversized splittable Canvas sections are measured and re-paginated on private staging DOM before capture. Reused fitting fragments and page-by-page capture reduce repeated work; very large indivisible media can still scale or split. Vue header/footer slots use image captures in Canvas mode and supported text/shape operations in text mode.
Selectable text mode
<Html2Pdf render-mode="text" manual-pagination />
Selectable text mode converts the live browser layout into PDF vector/text operations instead of turning the entire document into a screenshot.
In selectable-text mode, inline SVG shapes are rasterized; svgMode="preserve" cannot turn them into native PDF vectors. Optional TTF or TrueType-outline OTF registration can embed a matching face for real PDF text. Without a registered font, unrecognized CSS families use built-in PDF faces.
It supports:
- selectable body text
- searchable body text
- copyable body text
- selectable header/footer text
- selectable footer page numbers
- clickable PDF hyperlinks
- images
- backgrounds
- borders
- basic border radius
- text color, weight, italic, decoration, alignment, and spacing
- multi-page output
- manual page-break markers
- PDF password protection
- PDF permissions
- metadata
- compression
- headers, footers, and watermarks
Example:
<Html2Pdf render-mode="text" manual-pagination>
<template #pdf-content>
<main>
<h1>Selectable report</h1>
<p>You can select and search this text in the generated PDF.</p>
<a href="https://www.npmjs.com/package/vue-pdf-export">
Open vue-pdf-export on npm
</a>
</main>
</template>
</Html2Pdf>
Selectable-text mode intentionally favors real PDF text over pixel-perfect parity with every browser CSS feature. See Selectable Text Mode Notes.
Component Registration
Local import - recommended
With <script setup>:
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
</script>
No components registration is required.
Options API
<script lang="ts">
import { defineComponent } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
export default defineComponent({
components: {
Html2Pdf,
},
});
</script>
Global registration
import { createApp } from "vue";
import App from "./App.vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const app = createApp(App);
app.component("Html2Pdf", Html2Pdf);
app.mount("#app");
Preview & Download
Add one of these prop combinations to the complete quick-start component. Keep manual-pagination for a simple document, or configure a usable page-height budget for automatic pagination.
Preview only:
<Html2Pdf :enable-download="false" :preview-modal="true" />
Download only:
<Html2Pdf :enable-download="true" :preview-modal="false" />
Download and preview:
<Html2Pdf :enable-download="true" :preview-modal="true" />
enableDownload and previewModal are independent.
Smart PDF Pagination
Html2Pdf runs the shared DOM pagination planner when manualPagination is false and paginateElementsByHeight is supplied. That number is the usable CSS pixel height after margins and header/footer reservations, calculated for your chosen paper size and orientation. It is not a universal A4 constant. Direct useHtml2Pdf() generation does not run the component planner.
The component also supports a simple Options API or Composition API PDF without pagination props: the selected renderer handles basic flow. Set a positive finite height to use the smart DOM planner for keep-together rules and table rows. In the playground, manual pagination is the demo default, and the optional media/chart section has an authored break before it. A large section may continue onto another physical page. Oversized rows and unusual CSS still need PDF inspection.
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
// Example budget for this application's measured paper geometry and insets.
const usablePageHeightPx = 960;
</script>
<template>
<Html2Pdf
:manual-pagination="false"
:paginate-elements-by-height="usablePageHeightPx"
>
<template #pdf-content>
<section data-pdf-split="avoid">
<h2>Summary</h2>
<p>Keep this block together if it fits.</p>
</section>
<section data-pdf-split="container">
<header data-pdf-split-header>Repeated section heading</header>
<article>First child</article>
<article>Second child</article>
</section>
</template>
</Html2Pdf>
</template>
Recalculate the budget when format, orientation, explicit margins, content width or layer heights change. The paper format guide explains geometry.
Split controls
| Markup | Effect |
|---|---|
data-pdf-split="avoid" |
Move a fitting block together; oversized blocks can fall back to child boundaries. |
data-pdf-split="container" |
Split long and nested sections around children, accounting for computed padding and flex/grid gaps. |
data-pdf-split-header |
Mark a child heading for repetition when its container continues. |
.pdf-keep-together, .pdf-no-break, img
|
Existing default avoidance rules. |
In supported selectable-text layouts, continuation fragments can retain a frame with page-edge spacing and rounded borders. Renderer CSS support varies. Indivisible oversized media, rowspans and complex nested layouts may still split or scale; inspect representative exports.
Explicit page breaks
<section style="break-before: page">Start on a new page.</section>
<section style="break-after: page">
The next block starts on a new page.
</section>
<div class="html2pdf__page-break"></div>
CSS page-break-before and page-break-after are supported alongside modern break properties. Do not put an explicit CSS break on the .html2pdf__page-break marker: that can produce two breaks or blank pages. manual-pagination="true" is useful when you author your own markers. Elements without special attributes retain their existing behavior.
Long tables
Use one semantic heading and ordinary rows. The planner can split across multiple <tbody> groups, honor explicit row breaks, move an orphan heading with its first row and repeat <thead> on continuation pages. Do not manually copy the heading.
<template>
<table>
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Amount</th>
</tr>
</thead>
<tbody>
<tr v-for="item in 40" :key="item">
<td>Item {{ item }}</td>
<td>${{ item * 10 }}</td>
</tr>
</tbody>
</table>
</template>
Selectable-text rendering carries the computed heading background into repeated rows. Canvas continuation fragments suppress a second native heading repetition. Check complex rowspan, unsupported CSS and oversized rows in the output.
Diagnose page boundaries
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import type { PdfPaginationDiagnostic } from "vue-pdf-export";
import "vue-pdf-export/style.css";
function onBreak(decision: PdfPaginationDiagnostic) {
console.info(decision.type, decision.page, decision.reason);
}
</script>
<template>
<Html2Pdf
:manual-pagination="false"
:paginate-elements-by-height="960"
:pagination-debug="true"
@pagination-break="onBreak"
@pagination-warning="onBreak"
@pagination-complete="(all) => console.info(all)"
>
<template #pdf-content><article>Measured report content</article></template>
</Html2Pdf>
</template>
Possible diagnostic types are automatic, manual-break, break-before, break-after, avoid-moved, container-split, table-split, and oversized. The opt-in overlay and events show planner decisions on the live DOM. The overlay is not a PDF layer or proof of final viewer alignment. Pagination has a finite fallback and cancellation checks; it cannot guarantee that arbitrary text, SVG or CSS never tears.
Nuxt 3 & Nuxt 4 SSR
Nuxt 3 + Nuxt 4 compatible
vue-pdf-export supports SSR-safe package imports in both Nuxt 3 and Nuxt 4. PDF generation itself uses browser APIs and must run on the client.
Validated integration paths include:
- server-side package import
- Nuxt 3 SSR build/runtime
- Nuxt 4 SSR build/runtime
- hydration
- client navigation
- lazy-loaded PDF consumers
-
<ClientOnly>integration - client-event composable generation
Browser-heavy PDF dependencies are loaded from generation paths instead of being eagerly evaluated during the server import path.
Recommended component pattern
Use <ClientOnly> when the export UI itself does not need to render on the server:
<script setup lang="ts">
import { ref } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const pdfRef = ref<InstanceType<typeof Html2Pdf> | null>(null);
async function exportPdf() {
await pdfRef.value?.generatePdf();
}
</script>
<template>
<ClientOnly>
<Html2Pdf
ref="pdfRef"
filename="nuxt-report.pdf"
:enable-download="true"
manual-pagination
>
<template #pdf-content>
<article>
<h1>Nuxt report</h1>
<p>This PDF is generated in the browser.</p>
</article>
</template>
</Html2Pdf>
<button @click="exportPdf">Export PDF</button>
</ClientOnly>
</template>
Lazy import on a client event
For routes where PDF functionality is only used after a user action, you can lazy-load the package from a client event:
async function loadPdfTools() {
if (!import.meta.client) return;
const pdf = await import("vue-pdf-export");
return pdf;
}
This is optional. A normal package import is SSR-safe, but lazy loading can help keep PDF-specific code out of paths that never use it.
Composable generation
Call browser-side generation from mounted/client UI events rather than server setup work.
import { useHtml2Pdf } from "vue-pdf-export";
const { generate, loading, error } = useHtml2Pdf();
async function onExportClick() {
if (!import.meta.client) return;
await generate(/* browser DOM/content options */);
}
What not to do
Do not start PDF generation while Nuxt is rendering on the server:
// Avoid server-side generation calls.
if (import.meta.server) {
// generatePdf() should not be called here.
}
The supported boundary is simple: imports may happen during SSR; PDF generation happens in the browser.
Nuxt compatibility: Nuxt 3 and Nuxt 4 have automated SSR import/hydration fixture coverage. Package import is SSR-safe; actual PDF generation must run in the browser.
PDF Paper Formats and Orientation
The component accepts a named pdf-format string and pdf-orientation="portrait|landscape" (also p|l). Default format is a4, default orientation is portrait.
<Html2Pdf pdf-format="a3" pdf-orientation="landscape" :manual-pagination="true">
<template #pdf-content><article>Landscape document</article></template>
</Html2Pdf>
The source normalizes A0–A10, B0–B10, C0–C10, dl, letter, government-letter, legal, junior-legal, ledger, tabloid, and credit-card. The selectable-text adapter and playground picker use these names. Small paper needs compact content; large or complex layouts still need viewer checks.
For custom dimensions, pass a numeric pair through the lower-level jsPDF options. The component's pdfFormat prop is a string, so do not bind an array to pdf-format.
<Html2Pdf
:manual-pagination="true"
:html-to-pdf-options="{
jsPDF: { unit: 'mm', format: [100, 180], orientation: 'portrait' },
}"
>
<template #pdf-content><article>Custom-size document</article></template>
</Html2Pdf>
When using automatic component pagination, calculate paginate-elements-by-height as the usable CSS pixel budget for the effective format, orientation, margins and header/footer heights. The playground computes this for its selected geometry. The selectable-text A3 fractional-boundary loop fix prevents a former repeated-page hang; final visual acceptance for all combinations still needs browser inspection.
API
Html2Pdf Component
Core props
| Prop | Type | Default | Description |
|---|---|---|---|
renderMode |
`'canvas' \ | 'text'` | 'canvas' |
showLayout |
boolean |
false |
Show export layout. |
floatLayout |
boolean |
false |
Use fixed/floating export layout. |
enableDownload |
boolean |
true |
Download after generation. |
previewModal |
boolean |
false |
Show PDF preview modal. |
filename |
string |
timestamp fallback | PDF filename. |
pdfQuality |
number |
2 |
Default html2canvas scale; mainly relevant to canvas mode/images. |
pdfFormat |
PDF format | 'a4' |
PDF format. |
pdfOrientation |
`'portrait' \ | 'landscape'` | 'portrait' |
pdfContentWidth |
string |
'800px' |
Export wrapper width. |
htmlToPdfOptions |
Html2PdfOptions |
package defaults | Advanced options. |
metadata |
PdfMetadataOptions |
undefined | PDF metadata. |
compress |
boolean |
undefined | jsPDF stream compression override. |
security |
PdfSecurityOptions |
undefined | Password/security configuration. |
manualPagination |
boolean |
false |
Manual pagination mode. |
paginateElementsByHeight |
number |
undefined | Positive finite height enables smart DOM pagination; otherwise the renderer handles basic page flow. |
showLoader |
boolean |
true |
Show loader. |
loaderText |
string |
'Generating PDF...' |
Loader text. |
watermark |
WatermarkOptions |
disabled | Watermark configuration. |
collectMetrics |
boolean |
false |
Collect total/stage/per-page generation timing metrics. |
styling |
PdfStylingOptions |
disabled/defaults | Apply export-only page/body styling, scoped CSS, theme and auto-fit controls. |
Header and footer props are documented in their dedicated sections.
Additional component options include header?: HeaderOptions, footer?: FooterOptions, composition?: PdfCompositionOptions, paginationDebug?: boolean, pdfTemplate?: PdfTemplateSource, and templatePosition?: "prepend" | "append" | "both". See headers, footers, composition, and pagination.
The component also accepts fonts, autoOptimize, and svgMode. See the media and typography guide.
Exposed methods
interface Html2PdfInstance {
generatePdf: (overrides?: {
composition?: PdfCompositionOptions;
}) => Promise<Blob>;
generatePdfArrayBuffer: () => Promise<ArrayBuffer>;
generatePdfBase64: (options?: PdfBase64Options) => Promise<string>;
print: () => Promise<void>;
closePreview: () => void;
resetPagination: () => void;
cancelGeneration: () => void;
}
generatePdfArrayBuffer() and generatePdfBase64() reuse the generated PDF Blob and do not run a second renderer.
See Programmatic API for output examples and cancellation details.
Core Props
| Prop | Type | Default | Description |
|---|---|---|---|
renderMode |
`'canvas' \ | 'text'` | 'canvas' |
showLayout |
boolean |
false |
Show export layout. |
floatLayout |
boolean |
false |
Use fixed/floating export layout. |
enableDownload |
boolean |
true |
Download after generation. |
previewModal |
boolean |
false |
Show PDF preview modal. |
filename |
string |
timestamp fallback | PDF filename. |
pdfQuality |
number |
2 |
Default html2canvas scale; mainly relevant to canvas mode/images. |
pdfFormat |
PDF format | 'a4' |
PDF format. |
pdfOrientation |
`'portrait' \ | 'landscape'` | 'portrait' |
pdfContentWidth |
string |
'800px' |
Export wrapper width. |
htmlToPdfOptions |
Html2PdfOptions |
package defaults | Advanced options. |
metadata |
PdfMetadataOptions |
undefined | PDF metadata. |
compress |
boolean |
undefined | jsPDF stream compression override. |
security |
PdfSecurityOptions |
undefined | Password/security configuration. |
manualPagination |
boolean |
false |
Manual pagination mode. |
paginateElementsByHeight |
number |
undefined | Positive finite height activates the smart DOM pagination planner; otherwise the renderer handles basic page flow. |
showLoader |
boolean |
true |
Show loader. |
loaderText |
string |
'Generating PDF...' |
Loader text. |
watermark |
WatermarkOptions |
disabled | Watermark configuration. |
collectMetrics |
boolean |
false |
Collect total, stage, and per-page generation metrics. |
fonts |
PdfFontSource[] |
undefined | Register TTF/TrueType-outline OTF faces for selectable text. |
autoOptimize |
`boolean \ | PdfImageOptimizationOptions` | disabled |
svgMode |
`'auto' \ | 'preserve' \ | 'rasterize'` |
styling |
PdfStylingOptions |
disabled/defaults | Export-only page/body styles, scoped CSS, external stylesheets, theme and auto-fit controls. |
Header and footer props are documented in their dedicated sections.
See fonts, charts, SVG and images for examples and limitations of the media options above.
| Additional prop | Type | Purpose |
|---|---|---|
header |
HeaderOptions |
Built-in header or slot layout and page targeting. |
footer |
FooterOptions |
Built-in footer or slot layout. |
composition |
PdfCompositionOptions |
Prepend and/or append existing PDF pages. |
pdfTemplate, templatePosition
|
PdfTemplateSource, `'prepend' |
'append' |
{% raw %}paginationDebug
|
boolean, default false
|
Live planner overlay and diagnostic events. |
Advanced html2pdf Options
const htmlToPdfOptions = {
margin: [0, 0, 0, 0],
enableLinks: true,
pagebreak: {
mode: ["css", "legacy"],
},
image: {
type: "jpeg",
quality: 1,
},
html2canvas: {
scale: 3,
useCORS: true,
backgroundColor: "#FFFFFF",
},
jsPDF: {
unit: "px",
format: "a4",
orientation: "p",
hotfixes: ["px_scaling"],
optimizeForBrowser: true,
},
};
<Html2Pdf :html-to-pdf-options="htmlToPdfOptions" />
Important precedence rules:
-
metadataoverrides rawhtmlToPdfOptions.metadatawhen provided. -
compressoverrides rawhtmlToPdfOptions.jsPDF.compresswhen provided. -
security.enabled: trueoverrides raw jsPDF encryption. -
security.enabled: falseremoves raw encryption. - Non-empty
headerHtmltakes precedence overheaderText. - In HTML footer mode, non-empty
footerHtmltakes precedence overfooterText. -
headerLogoEnabledcontrols only header logo visibility. -
footerLogoEnabledcontrols only footer logo visibility. - Explicit user margins remain even when header/footer rendering is disabled.
-
enableLinks: trueenables generated PDF link annotations.
PDF Headers in Vue
Set enabled: true for a built-in header:
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import type { HeaderOptions } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const header: HeaderOptions = {
enabled: true,
height: 58,
pages: "all",
text: "Quarterly report",
showLogo: true,
logoUrl: "/logo.png",
logoWidth: 120,
logoHeight: 32,
backgroundColor: "#F5FBF9",
textColor: "#17324A",
};
</script>
<template>
<Html2Pdf :header="header" :manual-pagination="true">
<template #pdf-content><article>Document content</article></template>
</Html2Pdf>
</template>
| Field | Default | Meaning |
|---|---|---|
enabled |
false |
Render the header. |
height |
58 via component |
Reserved height in the active jsPDF unit (px by default). |
pages |
'all' |
'all', 'first', 'last', 'except-first', or a 1-based page number array. |
text / html
|
empty | Plain text or trusted HTML; non-empty HTML takes precedence. |
showLogo |
true |
Show an available logo. |
logoUrl / logoDataUrl
|
empty | Logo URL or data URL. |
logoWidth / logoHeight
|
28 CSS px |
Maximum logo box, preserving aspect ratio. |
backgroundColor / textColor
|
#F8F9FA / #5F6368
|
Built-in layer colors. |
Header height reserves space only on targeted generated pages. Explicit top margins still apply. Sanitize user-authored HTML before assigning it to html.
Vue header slot
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import ReportHeader from "./ReportHeader.vue";
import "vue-pdf-export/style.css";
</script>
<template>
<Html2Pdf
:header="{ height: 58, pages: 'except-first' }"
:manual-pagination="true"
>
<template #pdf-content><article>Report body</article></template>
<template #pdf-header="{ pageNumber, totalPages, orientation }">
<ReportHeader
:page-number="pageNumber"
:total-pages="totalPages"
:orientation="orientation"
/>
</template>
</Html2Pdf>
</template>
For example, ReportHeader.vue can keep its own logo and scoped styles:
<script setup lang="ts">
defineProps<{ pageNumber: number; totalPages: number; orientation: string }>();
</script>
<template>
<div class="report-header">
<img src="/logo.png" alt="Company" width="100" height="28" />
<strong>Quarterly report</strong>
<small>{{ orientation }} · {{ pageNumber }}/{{ totalPages }}</small>
</div>
</template>
<style scoped>
.report-header {
box-sizing: border-box;
display: flex;
align-items: center;
gap: 16px;
width: 100%;
padding: 10px 20px;
background: #f5fbf9;
color: #17324a;
}
.report-header small {
margin-left: auto;
}
</style>
The slot activates the header and owns visible markup. Object text, HTML, logo and colors are not injected into it; object height and pages still control layout and targeting. Give the component root width: 100% and appropriate padding. Scoped and inherited computed styles are snapshotted, subject to renderer support. Text mode emits supported text and shapes as PDF operations; Canvas mode captures an image. Inspect complex CSS and fonts in a viewer.
See footers and pagination for related configuration.
PDF Footers in Vue
The built-in footer appears on every generated page. Footers have no separate pages selector.
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import type { FooterOptions } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const footer: FooterOptions = {
enabled: true,
height: 58,
useHtml: true,
html: "<strong>My company</strong>",
template: "Page {n} of {total}",
};
</script>
<template>
<Html2Pdf :footer="footer" :manual-pagination="true">
<template #pdf-content><article>Document content</article></template>
</Html2Pdf>
</template>
| Field | Default | Meaning |
|---|---|---|
enabled |
false |
Render the footer. |
height |
58 via component |
Reserved height in the active jsPDF unit (px by default). |
useHtml |
component compatibility default true
|
HTML/image or plain-text built-in footer. |
template |
Page {n} of {total} |
Generated-page number text. |
text / html
|
empty | Body content; non-empty HTML takes precedence in HTML mode. |
showLogo |
true |
Show an available logo. |
logoUrl / logoDataUrl
|
empty | Logo URL or data URL. |
logoWidth / logoHeight
|
26 CSS px |
Maximum logo box. |
backgroundColor / textColor
|
#F8F9FA / #5F6368
|
Built-in layer colors. |
Only use trusted HTML, or sanitize user-authored content first. A remote logo must be accessible under browser CORS rules.
Vue footer slot
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
</script>
<template>
<Html2Pdf :footer="{ height: 58 }" :manual-pagination="true">
<template #pdf-content><article>Document content</article></template>
<template #pdf-footer="{ pageNumber, totalPages }">
<div class="report-footer">
<span>My company</span>
<span>Page {{ pageNumber }} of {{ totalPages }}</span>
</div>
</template>
</Html2Pdf>
</template>
<style scoped>
.report-footer {
box-sizing: border-box;
display: flex;
justify-content: space-between;
width: 100%;
padding: 10px 20px;
}
</style>
The slot activates the footer and owns visible markup. Object text, HTML, logo and template do not appear unless you render them. orientation is also a slot prop. Text mode renders supported slot text as PDF text; Canvas mode captures an image. Chrome may allow OCR selection on an image PDF, so selection alone does not prove native text is present. Scoped and inherited styles are snapshotted within renderer limits.
The built-in {total} and slot totalPages count generated pages before composition, excluding inserted template pages.
Security
Create password-protected PDFs with the security prop.
Security options
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
compatibility-resolved | Explicitly enable/disable the direct security layer. |
userPassword |
string |
'' |
Password required to open the PDF. |
ownerPassword |
string |
'' |
Owner-level password used by the PDF encryption layer. |
passwordParts |
`Array<string \ | number>` | [] |
passwordSeparator |
string |
'' |
Separator used when joining passwordParts. |
permissions |
PdfPermission[] |
viewer-dependent | Requested PDF permission flags. |
import type { PdfSecurityOptions } from "vue-pdf-export";
const security: PdfSecurityOptions = {
enabled: true,
userPassword: "open123",
ownerPassword: "owner456",
permissions: ["print", "copy"],
};
<Html2Pdf :security="security" />
Password protection works in both canvas and selectable-text render modes.
Permission flags
type PdfPermission = "print" | "modify" | "copy" | "annot-forms";
| Permission | What it allows |
|---|---|
print |
Allows printing when the PDF viewer enforces document permissions. |
copy |
Allows text/content copying and extraction when the viewer enforces permissions. |
modify |
Allows document modification/editing when supported by the viewer. |
annot-forms |
Allows annotations and form-related changes when supported by the viewer. |
PDF permission enforcement depends on the PDF viewer. Password encryption is part of the PDF itself, while permission flags rely on viewer compliance.
Security guidance
The package can generate encrypted/password-protected PDFs, but PDF security should be treated as one layer of application security rather than a replacement for access control.
Important boundaries:
- User/open password controls opening an encrypted PDF.
- Owner password is used by the PDF encryption layer for owner-level access/permission configuration.
- Permission flags are viewer-dependent. Some PDF viewers may not enforce them identically.
- Caller-supplied header, footer, and slot HTML is sanitized with DOMPurify before insertion into export staging. Supply trusted, intentional document content; sanitization is not a substitute for application security.
- Remote images are fetched by the browser. Use trusted origins, correct CORS headers, and avoid exposing secrets in image URLs.
- Passwords exist in browser memory while a PDF is being generated. Do not hard-code sensitive production passwords into public frontend source.
- Generated Blob URLs are cleaned up by the component/composable lifecycle, but applications should still avoid retaining generated Blobs longer than necessary.
vue-pdf-export is a client-side library and does not require a package-operated server to generate a PDF. Your application, remote assets, analytics, and surrounding infrastructure may still perform their own network requests.
Password parts
const security = {
enabled: true,
passwordParts: ["15081999", 4821],
passwordSeparator: "-",
};
This creates one final open password:
15081999-4821
passwordParts do not create multiple independent passwords.
When security is omitted, compatible raw htmlToPdfOptions.jsPDF.encryption values are preserved.
When security.enabled is explicitly false, encryption is removed.
Security Model & Boundaries
vue-pdf-export provides PDF security features and conservative browser-side cleanup, but no client-side PDF package can make an application "100% secure" by itself.
Verified outcomes
The final regression suite verified:
- 50-page Canvas generation
- 100-page Canvas generation
- Canvas cancellation and recovery
- selectable-text cancellation and recovery
- repeated-image caching in selectable-text mode
- page-bounded Canvas rendering in image-heavy documents
- temporary rendering cleanup after cancellation/generation
The final unit suite completed with:
19 test files passed
106 / 106 tests passed
What the package provides
- jsPDF-backed PDF encryption/password protection
- separate user/open and owner password inputs
- supported permission flags for print, modify, copy, and annotation/forms
- explicit
security.enabled: falsehandling to remove inherited raw encryption - metadata/compression processing without sending the document to a package-operated backend
- Blob URL cleanup in the component/composable lifecycle
- cancellation cleanup for temporary rendering structures
- filename normalization that removes unsafe path/filename characters and protects Windows reserved names
What remains the application's responsibility
- sanitizing any untrusted HTML before passing it to
headerHtmlorfooterHtml - protecting passwords, tokens, customer data, and remote asset URLs
- server-side authorization before sensitive data reaches the browser
- selecting CORS-safe and trusted image origins
- deciding whether client-side PDF generation is appropriate for highly sensitive documents
- validating the behavior of permission flags in the PDF viewers used by your customers
Important client-side security note
Anything available to frontend JavaScript can potentially be inspected by a user who controls that browser session. Do not treat a frontend bundle, minification, or obfuscation as a secret-storage mechanism.
Minifying the npm distribution is useful for package size and distribution hygiene, but it is not a security boundary.
Watermarks
The watermark controls below are available in the published package. Choose an explicit layer when placement matters.
An enabled watermark without an explicit layer uses foreground. Set layer: "background" if the mark should appear behind supported selectable-text page content. Opaque content can cover a background watermark.
Design presets
design supplies defaults; every explicit option overrides its preset. For example, classic can still be used with type: 'image' and mode: 'repeat'.
| Design | Default appearance |
|---|---|
classic |
Existing text/image defaults |
diagonal |
Single angled status text |
microtext |
Small, visibly repeated text |
tile |
Repeated medium text |
logo |
Single subtle image |
page-background |
Faint image covering selected pages |
custom |
Explicit fields only |
import type { WatermarkOptions } from "vue-pdf-export";
const watermark: WatermarkOptions = {
enabled: true,
design: "classic",
type: "image",
mode: "repeat",
layer: "foreground",
imageDataUrl: logoPngDataUrl,
opacity: 0.14,
rotation: -32,
width: 20,
gapX: 5,
gapY: 5,
pages: "all",
};
imageDataUrl supports PNG or JPEG data URLs; imageUrl supports a reachable URL with appropriate CORS. Width and gaps use the configured jsPDF units. The source aspect ratio determines height when omitted. x and y position a single mark; repeated marks use a grid and gaps. A transparent PNG usually gives the cleanest foreground logo. Dense repeated images are composed into a reusable transparent page sheet. Full-page PNG backgrounds can increase file size.
For a styled stamp, pass a trusted mounted element, for example watermark: { enabled: true, type: 'html', htmlElement: stampRef.value } after the ref exists. The element is captured as an image once per export. Do not pass an untrusted HTML string. microtext is visible decoration, not a digital signature or tamper protection.
Selectable-text output can draw a real background mark behind its PDF content. Canvas output has opaque raster pages and uses a light foreground fallback to keep a requested background mark visible. An opaque content card can cover a selectable-text background mark. See media and typography.
Text watermark
import type { WatermarkOptions } from "vue-pdf-export";
const watermark: WatermarkOptions = {
enabled: true,
layer: "background",
type: "text",
mode: "repeat",
text: "CONFIDENTIAL",
opacity: 0.1,
rotation: -35,
fontSize: 36,
gapX: 100,
gapY: 90,
};
<Html2Pdf :watermark="watermark" />
Foreground watermark
const watermark = {
enabled: true,
layer: "foreground",
type: "text",
mode: "single",
text: "DRAFT",
opacity: 0.15,
rotation: -30,
fontSize: 60,
x: 0.5,
y: 0.5,
};
Foreground watermarks are drawn onto final PDF pages with jsPDF.
Image watermark
const watermark = {
enabled: true,
layer: "foreground",
type: "image",
mode: "single",
imageUrl: "/logo.png",
opacity: 0.1,
rotation: -25,
width: 180,
x: 0.5,
y: 0.5,
};
A data URL can also be used:
const watermark = {
enabled: true,
type: "image",
imageDataUrl: "data:image/png;base64,...",
};
imageDataUrl takes priority over imageUrl.
Per-page watermark targeting
const watermark = {
enabled: true,
type: "text",
text: "INTERNAL",
pages: "except-first",
};
Explicit pages:
const watermark = {
enabled: true,
text: "INTERNAL",
pages: [1, 3, 5],
};
Supported targets:
all
first
last
except-first
number[]
For selective targeting, a requested background watermark may be applied during final PDF post-processing so selected-page behavior can be preserved.
Watermark options
| Option | Type | Description |
|---|---|---|
enabled |
boolean |
Enable/disable watermark. |
type |
`'text' \ | 'image' \ |
{% raw %}design
|
preset name | Optional design defaults; explicit options take precedence. |
layer |
`'background' \ | 'foreground'` |
mode |
`'single' \ | 'repeat'` |
text |
string |
Text content. |
fontSize |
number |
Text size. |
fontFamily |
string |
Font family. |
fontWeight |
`string \ | number` |
fontStyle |
`'normal' \ | 'bold' \ |
{% raw %}color
|
string |
Watermark color. |
imageUrl |
string |
Image URL. |
imageDataUrl |
string |
Data URL/base64 image. |
width |
number |
Image width. |
height |
number |
Optional image height. |
opacity |
number |
Clamped to 0..1. |
rotation |
number |
Rotation in degrees. |
x |
number |
Horizontal position. |
y |
number |
Vertical position. |
gapX |
number |
Horizontal repeat gap. |
gapY |
number |
Vertical repeat gap. |
pages |
PdfPageTarget |
Target pages. |
Metadata & Compression
Metadata options
| Option | Type | Description |
|---|---|---|
title |
string |
PDF document title. |
author |
string |
PDF author. |
subject |
string |
PDF subject. |
keywords |
string[] |
PDF keywords. |
creator |
string |
PDF creator/application value. |
import type { PdfMetadataOptions } from "vue-pdf-export";
const metadata: PdfMetadataOptions = {
title: "Invoice #1024",
author: "Acme Inc.",
subject: "Customer invoice",
keywords: ["invoice", "2026"],
creator: "vue-pdf-export",
};
<Html2Pdf :metadata="metadata" />
Metadata is written to PDF document properties and is not rendered as normal visible page content.
The direct metadata prop takes priority over htmlToPdfOptions.metadata.
Compression
<Html2Pdf :compress="true" />
compress controls jsPDF stream compression.
It does not lower html2canvas resolution or JPEG image quality.
If omitted, compatible raw htmlToPdfOptions.jsPDF.compress is preserved.
Programmatic API
Component methods
| Method | Return | Purpose |
|---|---|---|
generatePdf({ composition? }?) |
Promise<Blob> |
Generate the PDF and optionally override composition. |
generatePdfArrayBuffer() |
Promise<ArrayBuffer> |
Generate once, then serialize the resulting Blob to binary bytes. |
generatePdfBase64(options?) |
Promise<string> |
Generate once, then return raw Base64 or an optional PDF data URL. |
print() |
Promise<void> |
Generate/open the native browser print flow. |
closePreview() |
void |
Close the active preview flow. |
resetPagination() |
void |
Remove automatically inserted page breaks. |
cancelGeneration() |
void |
Request cooperative cancellation of active generation. |
interface Html2PdfInstance {
generatePdf: (overrides?: {
composition?: PdfCompositionOptions;
}) => Promise<Blob>;
generatePdfArrayBuffer: () => Promise<ArrayBuffer>;
generatePdfBase64: (options?: PdfBase64Options) => Promise<string>;
print: () => Promise<void>;
closePreview: () => void;
resetPagination: () => void;
cancelGeneration: () => void;
}
Recommended ref:
const pdfRef = ref<InstanceType<typeof Html2Pdf> | null>(null);
Generate Blob
const blob = await pdfRef.value?.generatePdf();
To insert existing PDF pages for this call, use await pdfRef.value?.generatePdf({ composition: { prepend: templatePdf, append: templatePdf } }). The method option wins over the component prop. See PDF composition.
Blob remains the recommended default for downloads, previews, object URLs, and very large PDFs.
Generate ArrayBuffer
const arrayBuffer = await pdfRef.value?.generatePdfArrayBuffer();
Use ArrayBuffer when you need raw PDF bytes for binary APIs, workers, storage, cryptographic processing, or transports that accept binary data.
Generate Base64
Raw Base64:
const base64 = await pdfRef.value?.generatePdfBase64();
PDF data URL:
const dataUrl = await pdfRef.value?.generatePdfBase64({
dataUrl: true,
});
Base64 is convenient for string-only transports, but it is larger than the binary payload and requires additional conversion memory. Prefer Blob or ArrayBuffer for large PDFs.
The ArrayBuffer and Base64 methods reuse the normal PDF generation flow and serialize the generated Blob. They do not run a second PDF renderer.
For pure programmatic output, disable automatic browser-side download/preview with
:enable-download="false"and:preview-modal="false".
Low-level output serialization
The output helpers are also available directly:
import {
pdfBlobToArrayBuffer,
pdfBlobToBase64,
serializePdfOutput,
} from "vue-pdf-export";
const bytes = await pdfBlobToArrayBuffer(blob);
const base64 = await pdfBlobToBase64(blob);
const dataUrl = await pdfBlobToBase64(blob, { dataUrl: true });
const output = await serializePdfOutput(blob, "arraybuffer");
Supported output formats are blob, arraybuffer, and base64.
await pdfRef.value?.print();
Print works through the browser print flow in both Canvas and selectable-text rendering modes.
Close preview
pdfRef.value?.closePreview();
Reset automatic pagination
pdfRef.value?.resetPagination();
This removes only automatically inserted page breaks.
Cancel active generation
pdfRef.value?.cancelGeneration();
cancelGeneration() requests cooperative cancellation of the current export.
- Incremental Canvas rendering checks cancellation around page capture, encoding, insertion, and browser-yield points.
- Selectable-text rendering checks cancellation while walking the DOM, materializing images, paginating, and rendering pages.
- Synchronous browser/jsPDF work such as an already-running
toDataURL()or finalpdf.output('blob')call cannot be interrupted in the middle; cancellation takes effect at the next safe checkpoint. - Cancellation is treated as an expected abort flow and is not emitted as the component's normal
errorevent.
Composable
import { useHtml2Pdf } from "vue-pdf-export";
const {
loading,
progress,
stage,
progressState,
error,
pdfUrl,
lastResult,
generate,
download,
print,
cleanup,
} = useHtml2Pdf();
Loader, Progress, Metrics, Events & Slots
Built-in loader
<Html2Pdf :show-loader="true" loader-text="Generating PDF..." />
Disable it:
<Html2Pdf :show-loader="false" />
Numeric progress
<Html2Pdf @progress="onProgress" />
function onProgress(value: number) {
console.log(value);
}
This is generation-stage progress, not byte-level network progress.
Rich progress stages
<Html2Pdf @progress-stage="onProgressStage" />
import type { PdfProgressState } from "vue-pdf-export";
function onProgressStage(state: PdfProgressState) {
console.log(state.stage, state.progress);
}
Current stages:
idle
pagination
images
preparing
rendering
watermark
header
footer
metadata
serializing
complete
error
Generation metrics
Metrics are opt-in. When disabled, metrics collection does not add generation work.
<Html2Pdf :collect-metrics="true" @generation-complete="onGenerationComplete" />
import type { PdfGenerationCompletePayload } from "vue-pdf-export";
function onGenerationComplete(payload: PdfGenerationCompletePayload) {
console.log(payload.metrics);
}
A completed generation can expose total generation time, stage timings, per-page timings, render mode, page count, and generated Blob size.
interface GeneratePdfResult {
pdf: jsPDF;
blob: Blob;
url: string;
metrics?: PdfGenerationMetrics;
}
Use timing data for profiling and regression checks rather than as a hard real-time performance guarantee.
Custom loader
<Html2Pdf>
<template #loader="{ progress, loading, stage, progressState }">
<div v-if="loading">
{{ stage }} - {{ progress }}%
</div>
</template>
</Html2Pdf>
Slot values:
| Value | Type |
|---|---|
progress |
number |
loading |
boolean |
stage |
PdfProgressStage |
progressState |
PdfProgressState |
Events
<Html2Pdf
@progress="onProgress"
@progress-stage="onProgressStage"
@generation-complete="onGenerationComplete"
@startPagination="onStartPagination"
@hasPaginated="onPaginated"
@beforeDownload="onBeforeDownload"
@hasDownloaded="onDownloaded"
@closed="onClosed"
@error="onError"
/>
| Event | Payload | Description |
|---|---|---|
progress |
number |
Numeric generation progress. |
progressStage / @progress-stage
|
PdfProgressState |
Named stage and progress. |
generationComplete / @generation-complete
|
PdfGenerationCompletePayload |
Generation finished; includes opt-in metrics when enabled. |
startPagination |
none | Pagination started. |
hasPaginated |
none | Pagination completed. |
beforeDownload |
BeforeDownloadPayload |
Emitted before generation. |
hasDownloaded |
Blob |
Generated Blob is available. |
closed |
none | Preview/generation flow closed. |
error |
Error |
Generation or print error. |
paginationBreak / @pagination-break
|
PdfPaginationDiagnostic |
A planned break; emitted with paginationDebug. |
paginationWarning / @pagination-warning
|
PdfPaginationDiagnostic |
An oversized or other planning warning; debug only. |
paginationComplete / @pagination-complete
|
PdfPaginationDiagnostic[] |
All decisions for this planning pass; debug only. |
Slots
| Slot | Required | Description |
|---|---|---|
pdf-content |
yes | HTML converted to PDF. |
loader |
no | Custom loader with { progress, loading, stage, progressState }. |
pdf-header |
no | Vue markup for each targeted header page; { pageNumber, totalPages, orientation }. |
pdf-footer |
no | Vue markup for each generated footer page; same context. |
The layer slots activate their layers, own visible content and render differently in Canvas versus text mode. Give the slot root full width and explicit padding. header.height, header.pages and footer.height still apply. See copyable header and footer examples, and pagination diagnostics.
TypeScript
The package ships TypeScript declarations.
The TypeScript declarations include PdfCompositionOptions, PdfTemplateSource, PdfPaginationDiagnostic, and PdfPaginationDiagnosticType. See the current TypeScript documentation for additional details.
Common types:
import type {
BeforeDownloadPayload,
FooterMode,
FooterOptions,
PdfCompositionOptions,
PdfTemplateSource,
PdfPaginationDiagnostic,
PdfPaginationDiagnosticType,
GeneratePdfResult,
HeaderOptions,
Html2CanvasOptions,
Html2PdfOptions,
Html2PdfProps,
JsPdfOptions,
PdfAutoFitOptions,
PdfBase64Options,
PdfEncryptionOptions,
PdfFormat,
PdfGenerationCompletePayload,
PdfGenerationMetrics,
PdfImageOptions,
PdfMargin,
PdfMetadataOptions,
PdfOrientation,
PdfOutputFormat,
PdfPageBreakOptions,
PdfPageContext,
PdfPageTarget,
PdfPermission,
PdfProgressStage,
PdfProgressState,
PdfRenderMode,
PdfSecurityOptions,
PdfSerializedOutput,
PdfStyle,
PdfStylingOptions,
WatermarkLayer,
WatermarkMode,
WatermarkOptions,
WatermarkType,
} from "vue-pdf-export";
Render mode type:
export type PdfRenderMode = "canvas" | "text";
Output types:
export type PdfOutputFormat = "blob" | "arraybuffer" | "base64";
export interface PdfBase64Options {
dataUrl?: boolean;
}
export type PdfSerializedOutput = Blob | ArrayBuffer | string;
Useful helpers:
import {
createPdfOptions,
normalizePdfFilename,
pdfBlobToArrayBuffer,
pdfBlobToBase64,
resolvePdfPages,
serializePdfOutput,
} from "vue-pdf-export";
Filename Normalization
import { normalizePdfFilename } from "vue-pdf-export";
normalizePdfFilename("invoice:/customer?2026");
// invoice--customer-2026.pdf
Normalization:
- adds exactly one
.pdfextension - prevents duplicate
.pdf - sanitizes unsafe filename/path characters
- trims trailing dots and spaces
- falls back to a timestamp for empty names
- protects Windows reserved device names such as
CONandLPT1 - preserves Unicode
- limits excessively long base names
Examples:
normalizePdfFilename("report");
// report.pdf
normalizePdfFilename("report.PDF");
// report.pdf
normalizePdfFilename("CON");
// _CON.pdf
PDF Styling & data-pdf-ignore
Exclude content from the PDF
Use data-pdf-ignore for controls, helper UI, debug panels, buttons, or any subtree that should stay visible in the application but not appear in the generated PDF.
<div>
<h1>Invoice</h1>
<button data-pdf-ignore>Edit invoice</button>
<aside data-pdf-ignore="true">
This entire subtree is excluded from the PDF.
</aside>
<p data-pdf-ignore="false">Explicit false keeps this content in the PDF.</p>
</div>
| Markup | PDF behavior |
|---|---|
data-pdf-ignore |
excluded |
data-pdf-ignore="true" |
excluded |
data-pdf-ignore="false" |
included |
Ignored content is removed only from the temporary export copy and is also excluded from export pagination measurements.
Export-only styling
import type {
PdfPageContext,
PdfStyle,
PdfStylingOptions,
} from "vue-pdf-export";
const bodyStyle: PdfStyle = {
padding: "16px",
fontSize: "14px",
color: "#17324A",
};
const styling: PdfStylingOptions = {
enabled: true,
pageBackground: "#FFF8E7",
bodyBackground: "#F8FAFC",
pageClass: "invoice-page",
bodyClass: "invoice-body",
pageStyle: ({ pageNumber }: PdfPageContext) => ({
padding: "24px",
border: "2px solid #DCE7E9",
borderRadius: "12px",
backgroundColor: pageNumber === 1 ? "#FFF7ED" : "#FFFFFF",
}),
bodyStyle,
cssText: `
.invoice-title {
font-size: 28px;
letter-spacing: -0.02em;
}
.screen-only {
display: none;
}
`,
printStylesheetUrls: ["/pdf/invoice-print.css"],
theme: "light",
autoFit: {
enabled: true,
minScale: 0.6,
maxScale: 1,
allowUpscale: false,
},
};
<Html2Pdf :styling="styling">
<template #pdf-content>
<article>
<h1 class="invoice-title">Invoice</h1>
<button class="screen-only">Application action</button>
</article>
</template>
</Html2Pdf>
Styling options
| Option | Type | Purpose |
|---|---|---|
enabled |
boolean |
Master styling switch. |
pageBackground |
string |
PDF page surface background. |
bodyBackground |
string |
Inner body/content surface background. |
pageClass |
string |
Export-only page class names. |
bodyClass |
string |
Export-only body class names. |
pageStyle |
`PdfStyle \ | PdfStyleResolver` |
bodyStyle |
PdfStyle |
Static body styles. |
cssText |
string |
Export-only scoped CSS text. |
stylesheetUrls |
string[] |
External export stylesheet URLs. |
printStylesheetUrls |
string[] |
External print/export stylesheet URLs. |
theme |
`'current' \ | 'light' \ |
{% raw %}autoFit
|
`boolean \ | PdfAutoFitOptions` |
Temporary export styles, themes, stylesheets, and staging DOM are cleaned up after generation.
Customize the built-in preview and loader
In the current documentation version, the built-in preview and loader inherit --vue-pdf-* CSS variables from an ancestor of <Html2Pdf>. They have defaults, so theming is optional. These variables style package UI; they do not style the content inside #pdf-content or the generated PDF. Use your document CSS and the styling option above for the exported pages.
<script setup lang="ts">
import { ref } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const pdfRef = ref<InstanceType<typeof Html2Pdf> | null>(null);
</script>
<template>
<div class="report-export">
<button type="button" @click="pdfRef?.generatePdf()">Preview PDF</button>
<Html2Pdf
ref="pdfRef"
preview-modal
:enable-download="false"
manual-pagination
>
<template #pdf-content>
<article>
<h1>My report</h1>
<p>Content from my Vue app.</p>
</article>
</template>
</Html2Pdf>
</div>
</template>
<style>
.report-export {
--vue-pdf-preview-width: 900px;
--vue-pdf-preview-surface: #f8fafc;
--vue-pdf-preview-close-background: #17324a;
--vue-pdf-preview-focus-color: #0b9668;
--vue-pdf-loader-surface: #f8fafc;
--vue-pdf-loader-spinner-accent: #0b9668;
}
</style>
| Variable | Built-in UI area |
|---|---|
--vue-pdf-preview-width, --vue-pdf-preview-height
|
Desktop dialog size. |
--vue-pdf-preview-mobile-width, --vue-pdf-preview-mobile-height
|
Preview size at widths up to 640px; defaults fill the viewport. |
--vue-pdf-preview-radius, --vue-pdf-preview-backdrop
|
Dialog corners and modal backdrop. |
--vue-pdf-preview-surface, --vue-pdf-preview-color
|
Preview surface and foreground. |
--vue-pdf-preview-close-background, --vue-pdf-preview-close-color, --vue-pdf-preview-focus-color
|
Close button colors and focus outline. |
--vue-pdf-loader-backdrop, --vue-pdf-loader-surface, --vue-pdf-loader-color
|
Loader overlay, panel and text. |
--vue-pdf-loader-radius, --vue-pdf-loader-spinner-track, --vue-pdf-loader-spinner-accent
|
Loader corners and spinner. |
--vue-pdf-layout-background |
Optional visible fixed export layout background. |
The preview uses a native dialog: Escape or its labeled close button closes it, focus returns to the previous control, and the dialog fills the mobile viewport by default. The default loader exposes status and bounded progress values and stops its spinner when reduced motion is requested. These controls support keyboard use, but you should still test your document markup and the browser's PDF viewer with your target assistive technology. Replace the default loader with the #loader slot if your app needs different UI.
Compose Existing PDF Pages
composition.prepend and composition.append insert whole PDF pages before and after generated pages. They do not overlay a page. A two-page template on both sides adds four pages.
<script setup lang="ts">
import { ref } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import type { PdfCompositionOptions } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const pdfRef = ref<InstanceType<typeof Html2Pdf> | null>(null);
const templatePdf = ref<Blob | null>(null);
const composition = ref<PdfCompositionOptions>({});
function chooseTemplate(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (file) {
templatePdf.value = file;
composition.value = { prepend: file, append: file };
}
}
async function exportPdf() {
if (!templatePdf.value) return;
await pdfRef.value?.generatePdf({ composition: composition.value });
}
</script>
<template>
<input type="file" accept="application/pdf,.pdf" @change="chooseTemplate" />
<button type="button" :disabled="!templatePdf" @click="exportPdf">
Export combined PDF
</button>
<Html2Pdf
ref="pdfRef"
:manual-pagination="true"
:enable-download="true"
:composition="composition"
>
<template #pdf-content><article>Generated document</article></template>
</Html2Pdf>
</template>
PdfTemplateSource accepts Blob, ArrayBuffer, Uint8Array, or a URL string pointing to a real PDF. Browser fetch() of remote URLs needs CORS access. Password-protected template PDFs may not load; verify the source before export. The same source used twice is resolved once per generation. The composition engine loads only when needed.
Method generatePdf({ composition }) takes precedence over the composition prop. The object prop takes precedence over the convenience pair pdf-template and template-position="prepend|append|both"; that pair defaults to prepend. Composition can also be passed through the lower-level options. Generated headers, footers, watermarks, and {total} are applied before inserted pages, so their page numbering excludes templates. Cancellation is checked around fetch, parsing, and serialization. The implementation reapplies metadata and encryption to the combined output when configured, but test the resulting permissions and metadata in your target viewers.
For Blob/ArrayBuffer/Base64 output, see the programmatic API. The playground offers composition upload and page-order checks.
Use Cases
Playground
The live playground is the fastest way to compare Canvas and selectable-text rendering and exercise the package without installing it.
Open the interactive PDF playground
Open the live playground
What you can exercise
Upload an existing PDF to try page composition and compare first, last and both insertion. Switch headers and footers between typed objects and Vue slots, inspect smart splitting with table fixtures, show pagination boundaries with the debug toggle, and try different paper sizes. The playground works on desktop, tablet and mobile. Its demo controls and fixtures are not npm exports.
Choose a renderer and paper size
Compare Canvas visual capture with selectable-text output, then choose A/B/C, Letter, Legal or other paper formats and orientation. Use the smart-split and table fixtures to inspect real pagination, including repeated headings.
Try headers, footers and composition
Switch between the typed header object and Vue slot and footer object and Vue slot. Upload a real PDF for composition, then compare first/last/both insertion and component-prop, imperative or legacy configuration. Check the generated page count because optional media and authored breaks change the report length.
Inspect output and document options
Try Blob, ArrayBuffer and Base64 output, PDF preview, download and print. Test auto-fit and export-only styling, passwords and permissions, watermarks, metadata and compression. The debug toggle shows pagination boundaries; its overlay is not embedded in the PDF.
The playground has an optional four-chart ApexCharts sample (bar, line, area, and donut), a custom font picker, image controls, and watermark design presets. Numeric labels and fixed chart callouts can differ between Canvas and selectable text; compare the generated PDFs.
- Canvas and selectable-text rendering modes
- selectable/searchable/copyable PDF text
- preview, download and native browser print
- Blob, ArrayBuffer, raw Base64 and PDF data-URL output
- manual and automatic pagination
- headers, footers and logos
- watermarks and page targeting
- passwords, permissions, metadata and compression
- clickable links
- progress stages and page-aware progress
- opt-in total, stage and per-page generation timings
- cancellation and recovery
data-pdf-ignore- export-only page/body backgrounds, classes and styles
- per-page styling callbacks
- scoped custom PDF CSS
- external print stylesheets
- forced current/light/dark themes
- width auto-fit
- generated usage code
Stress playground
The interactive /stress.html presets configure a run; selecting a preset does not start PDF generation. Start a run explicitly and review browser memory and output for your device.
The dedicated stress page exposes page-count presets for large-document validation:
- 5
- 10
- 25
- 50
- 100
- 200
- 500
- 1000
Open the large-document stress playground
Open the stress playground
Heavy and extreme presets can consume significant browser CPU and memory. The stress UI includes warnings and preserves cancellation/recovery behavior.
The package default remains Canvas mode for backwards compatibility even if the playground chooses text mode for demonstration.
Selectable Text Mode
Selectable-text mode uses a browser-layout-to-vector-PDF renderer rather than html2canvas for the main body content.
How do I generate a searchable PDF in Vue 3?
Install vue-pdf-export, import Html2Pdf and its stylesheet, set render-mode="text", then call generatePdf() from a component ref after the Vue component mounts. The returned value is a PDF Blob; with enable-download enabled, the component can also download it. This example uses the current documented package API.
npm install vue-pdf-export
<script setup lang="ts">
import { ref } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const pdf = ref<InstanceType<typeof Html2Pdf> | null>(null);
async function downloadReport() {
const blob = await pdf.value?.generatePdf();
if (blob) console.log(`Generated ${blob.size} PDF bytes`);
}
</script>
<template>
<button type="button" @click="downloadReport">Download searchable PDF</button>
<Html2Pdf
ref="pdf"
render-mode="text"
filename="searchable-report.pdf"
:enable-download="true"
>
<template #pdf-content>
<article>
<h1>Monthly report</h1>
<p>This body text is selectable and searchable.</p>
</article>
</template>
</Html2Pdf>
</template>
The PDF body text can be selected, copied, and searched in a compatible viewer. Test the result with your fonts and browser: not every CSS effect or glyph has a native-text equivalent. In Nuxt, follow the SSR guide and start generation only in the browser.
Vue PDF export: which renderer should I use?
Use renderMode="canvas" when matching complex browser CSS matters most. It uses the html2pdf.js / html2canvas / jsPDF path, so PDF body text is drawn as an image. Use renderMode="text" when users need to select, search or copy body text in the PDF. Both modes work through the same Vue component and support preview, download and Blob output. Start with the Vue quick start, then compare rendering modes.
The package adds Vue components, typed options, slots and pagination controls around these workflows. Selectable-text mode has its own CSS limits below; it is not a pixel-for-pixel replacement for the Canvas path.
What it is designed to preserve
- real PDF text
- selection/search/copy
- text line placement based on browser layout
- common font weight/style/color/alignment
- backgrounds
- borders
- images
- opacity
- overflow clipping
- multi-page layout
- clickable links
Current limitations
Some browser CSS/features are not fully reproduced in selectable-text mode, including or depending on renderer support:
- complex stacking-context /
z-indexreordering - background images and gradients
- shadows
- CSS transforms
- CFF-outline OTF and WOFF/WOFF2 embedding (registered TTF and TrueType-outline OTF are supported)
- some non-WinAnsi glyphs such as CJK/emoji
- pseudo-elements such as
::before/::after - native list markers in some layouts
- RTL text
position: fixed- complex rounded clipping
- native
break-inside: avoidsemantics inside the vector renderer
For those cases, prefer renderMode="canvas".
The fonts option can register TTF or TrueType-outline OTF files for selectable text. Without a registered file, unrecognized CSS font families map to built-in PDF fonts. Inline SVG shapes are rasterized in this mode. See fonts, charts, SVG and images.
Font metric differences
Selectable text uses PDF font metrics, which can differ slightly from browser web-font metrics. Most normal text is visually close, but highly stylized button/link text with large letter spacing or fixed widths can look different.
Header/footer text
In selectable-text mode, plain header/footer text and footer page-number text are emitted as real selectable PDF text. Complex HTML styling may be simplified to preserve selectability.
Security
Selectable-text mode supports PDF encryption because the vendored vector renderer passes the package's jsPDF constructor options at document creation time.
Rendering modes reference
Canvas mode
Canvas mode is the default and uses the package's existing html2pdf.js / html2canvas / jsPDF pipeline.
<Html2Pdf render-mode="canvas" />
Use it when the highest possible browser CSS fidelity is more important than selectable body text.
Best for:
- complex CSS
- gradients
- shadows
- transforms
- custom web fonts
- visual dashboards
- designs where browser appearance is the priority
Body content is rasterized, so normal body text is not selectable/searchable.
Incremental Canvas rendering
When the export DOM contains .html2pdf__page-break markers, Canvas mode uses an incremental page-by-page renderer instead of capturing the entire document as one giant canvas.
This path:
- clones and renders one logical PDF page at a time
- keeps raster dimensions page-bounded
- releases temporary page DOM after
html2canvasfinishes reading it - releases canvas backing stores after insertion into jsPDF
- reports page-level progress
- yields to the browser between expensive steps
- supports cooperative cancellation before the next page begins
Canvas documents without page-break markers use the whole-document html2pdf.js worker; paginated documents can use incremental page captures.
Selectable text mode
<Html2Pdf render-mode="text" />
Selectable text mode converts the live browser layout into PDF vector/text operations instead of turning the entire document into a screenshot.
It supports:
- selectable body text
- searchable body text
- copyable body text
- selectable header/footer text
- selectable footer page numbers
- clickable PDF hyperlinks
- images
- backgrounds
- borders
- basic border radius
- text color, weight, italic, decoration, alignment, and spacing
- multi-page output
- manual page-break markers
- PDF password protection
- PDF permissions
- metadata
- compression
- headers, footers, and watermarks
Example:
<Html2Pdf render-mode="text" manual-pagination>
<template #pdf-content>
<main>
<h1>Selectable report</h1>
<p>You can select and search this text in the generated PDF.</p>
<a href="https://www.npmjs.com/package/vue-pdf-export">
Open vue-pdf-export on npm
</a>
</main>
</template>
</Html2Pdf>
Selectable-text mode intentionally favors real PDF text over pixel-perfect parity with every browser CSS feature. See Selectable Text Mode Notes.
Clickable PDF Links
Enable PDF links through htmlToPdfOptions.enableLinks:
const htmlToPdfOptions = {
enableLinks: true,
};
<Html2Pdf :html-to-pdf-options="htmlToPdfOptions">
<template #pdf-content>
<a href="https://www.npmjs.com/package/vue-pdf-export">
Open vue-pdf-export on npm
</a>
</template>
</Html2Pdf>
In selectable-text mode, anchors are converted into real PDF link annotations, so compatible PDF viewers can show the link cursor and open the URL when clicked.
For best selectable-text rendering, keep PDF link styling straightforward. Very large letter-spacing, transforms, or highly compressed fixed-width buttons can create visual differences because browser and PDF font metrics are not identical.
Recommended PDF-friendly link CSS:
.pdf-link {
display: inline-flex;
align-items: center;
width: auto;
max-width: 100%;
padding: 8px 12px;
font-weight: 700;
line-height: 1.2;
letter-spacing: 0;
white-space: nowrap;
text-decoration: underline;
}
Images & CORS
Remote images must be accessible to the browser during rendering.
Recommended:
html2canvas: {
useCORS: true,
}
For external images:
- configure appropriate CORS headers
- avoid inaccessible authenticated image URLs
- prefer data URLs for critical logos when practical
- preserve image aspect ratio
Normal content image CSS:
.pdf-root img {
max-width: 100%;
height: auto;
object-fit: contain;
}
Header/footer logo sizing is already aspect-ratio-safe internally. Their width and height props define maximum bounds instead of forced image dimensions.
Image optimization
Opt-in autoOptimize provides target DPI, JPEG quality, alpha preservation, size caps, and per-export repeated-image reuse. Existing library image quality remains unchanged when it is disabled. A cross-origin image that cannot be decoded is left unchanged and may still be subject to the renderer’s CORS restrictions.
Production Guide
Use a deterministic export width
A PDF should not unexpectedly reflow because it was generated from a narrow mobile viewport.
.pdf-root {
width: 794px;
box-sizing: border-box;
}
Keep export CSS separate from responsive application CSS
The web UI and the PDF document have different layout goals.
Choose the renderer intentionally
Use canvas when exact visual fidelity is the priority.
Use text when selectable/searchable/copyable PDF text is the priority and the document uses CSS supported by the vector renderer.
Avoid large fixed heights
Prefer min-height over large fixed height values where possible.
Protect important blocks
Use .pdf-keep-together, .pdf-no-break, or carefully chosen pagebreak.avoid selectors.
Preserve image aspect ratios
img {
max-width: 100%;
height: auto;
object-fit: contain;
}
Keep header/footer content inside configured heights
Very large text or HTML content can overlap PDF content if it exceeds headerHeight or footerHeight.
Use trusted HTML only
headerHtml and footerHtml render developer-provided HTML. Header, footer, and slot HTML is sanitized with DOMPurify before export staging. Supply intentional, trusted document markup; sanitization does not turn arbitrary input into trusted application content.
Test fonts and media on the target browser
Wait for project fonts and register supported font bytes when exact selectable-text glyphs matter. Keep autoOptimize opt-in and choose DPI/JPEG quality appropriate to your output. Test SVG icons, chart snapshots, and font fallback in your target browsers.
Keep selectable-mode link styles simple
Avoid excessive letter spacing, transforms, or tightly fixed widths for PDF links/buttons when using selectable-text mode.
Use reasonable canvas quality
Higher html2canvas scale values consume more memory and CPU.
Generate one large document at a time
Multi-page PDF generation can be CPU and memory intensive.
Provide cancellation for expensive exports
Expose cancelGeneration() in long-running workflows so users can stop an export without waiting for the entire document to finish.
Cancellation is cooperative: already-running synchronous browser/jsPDF work finishes before the next cancellation checkpoint.
Test representative content
Test:
- shortest document
- longest document
- missing fields
- very long text
- wide/square/tall logos
- remote images
- links
- manual and automatic pagination
- header/footer enabled and disabled
- logo enabled and disabled
- trusted HTML header/footer
- selective header pages
- watermark targeting
- password protection
- every permission combination important to your app
- metadata
- compression
- both render modes
Performance & Large Documents
Large client-side PDF generation is constrained by browser memory, canvas limits, image size, document complexity, encryption overhead, and the device running the page. The package therefore uses multiple strategies rather than assuming one renderer is safe for every document.
Large-document work completed
The performance/stability work includes:
- 10 / 25 / 50 / 100-page benchmark and regression fixtures
- generation-time and PDF-size measurement
- browser-memory experiments and repeated-generation testing
- raw Canvas/jsPDF isolation tests to separate package behavior from dependency behavior
- page-by-page incremental Canvas rendering for paginated documents
- temporary DOM cleanup after page capture
- canvas backing-store release after raster insertion
- selectable-text image materialization with per-generation repeated-image caching
- cooperative browser yielding during expensive loops
- cancellation and recovery testing
- page-aware progress reporting
- long-document Playwright stress/regression coverage
- image-heavy, mixed-content, header/footer, watermark, metadata, compression, links, and security scenarios
Measured performance improvements
The performance work resulted in several measurable improvements in the benchmark fixtures:
| Area | Before | After | Improvement |
|---|---|---|---|
| HTML footer without logo | 7.82 s | 1.43 s | ~81.7% less time |
| HTML footer with logo | 11.81 s | 1.29 s | ~89.1% less time |
| Footer-heavy PDF size | ~46.7 MB | ~1.0 MB | ~97.9% smaller |
| Footer no-logo peak heap | 102.6 MB | 26.5 MB | ~74.2% lower |
| Long Canvas capture surface | ~99.96M px | ~1.74M px per page | ~98.26% smaller peak capture geometry |
These measurements are tied to the project's benchmark fixtures and test environment. They should not be interpreted as a universal percentage improvement for every PDF.
The architectural changes behind these results include:
- rendering static HTML footers once and reusing them across pages
- page-by-page Canvas rendering for paginated documents
- releasing temporary DOM and canvas backing stores earlier
- caching repeated selectable-mode images within one generation
- yielding between expensive rendering steps
- adding cancellation and page-aware progress
Incremental Canvas strategy
For Canvas documents with .html2pdf__page-break markers, each logical page is rendered independently.
This avoids the old failure mode where a long document could become one extremely tall browser canvas. In regression testing, the incremental path restored correct 50-page and 100-page Canvas PDF output while keeping individual captures page-bounded.
The whole-document html2pdf.js path is used for Canvas documents without page-break markers.
Selectable-text image optimization
Selectable-text rendering uses a per-generation image cache for repeated real <img> sources when the source, target raster dimensions, format, and JPEG quality match.
Why the cache is intentionally per-generation:
- avoids carrying stale DOM/image state across exports
- keeps cache lifetime bounded
- prevents a global cache from becoming a long-lived memory owner
Canvas elements are not cached because their pixels may change even when their dimensions do not.
Cleanup and browser responsiveness
Long exports explicitly release temporary resources where practical:
- off-screen incremental page DOM is removed after
html2canvasno longer needs it - temporary canvas backing stores are reset after serialization/insertion
- selectable image-materialization canvases are released after encoding
- per-generation image caches are cleared after use
- rendering loops periodically yield with browser scheduling checkpoints
Yielding does not make CPU-heavy work free, but it gives the browser opportunities to process progress updates and cancellation rather than monopolizing the main thread for the entire export.
Cancellation and recovery
Both rendering modes support cooperative cancellation.
The regression suite checks that:
- an active long export can be cancelled,
- temporary rendering DOM is cleaned up,
- no successful Blob is exposed for the cancelled run, and
- a new PDF can be generated successfully afterwards.
Page progress
Page-aware renderers can report:
{
stage: 'rendering',
progress: number,
currentPage?: number,
totalPages?: number,
}
This is useful for long reports where a percentage alone does not explain what the renderer is doing.
Encrypted Canvas memory behavior
Large encrypted Canvas PDFs can retain substantially more memory than equivalent unencrypted Canvas PDFs in Chromium.
Isolation testing reproduced the severe encrypted-raster retention pattern with raw jsPDF usage, outside the Vue component lifecycle. For that reason this is tracked as an upstream/dependency concern rather than described as a confirmed Vue memory leak.
Practical guidance:
- avoid unnecessarily high Canvas scale values
- resize oversized images before PDF generation
- generate one very large document at a time
- prefer selectable-text mode when it meets the document's visual requirements
- test encrypted long documents on representative target devices
- consider splitting exceptionally large documents when browser memory is constrained
Upstream jsPDF tracking: large encrypted Canvas PDFs can show substantially higher memory retention than equivalent unencrypted Canvas PDFs in Chromium. The same encrypted-raster pattern was reproduced in a raw jsPDF-only test outside the Vue component lifecycle, so this is tracked as an upstream/dependency concern rather than described as a confirmed Vue memory leak. See jsPDF issue #4017.
Upstream tracking: jsPDF #4017 - encrypted raster PDF memory retention.
This link is included so developers using large encrypted Canvas PDFs can follow the dependency-level investigation directly.
Stress/regression coverage
| Area | Coverage |
|---|---|
| Long Canvas documents | 10 / 25 / 50 / 100-page regression fixtures |
| Heavy cross-browser smoke | Chromium plus Firefox/WebKit coverage, with the heaviest 50/100-page cases concentrated in Chromium |
| Selectable-text long documents | long multi-page rendering and cancellation/recovery |
| Image-heavy selectable mode | repeated image cache / raster-call regression |
| Image-heavy Canvas mode | page-bounded canvas regression |
| Cancellation | Canvas and selectable-text cancellation + recovery |
| Cleanup | temporary page/snapshot/fixture cleanup assertions |
| Features | headers, footers, watermarks, links, metadata, compression, security |
These tests validate the package's test fixtures and supported paths; they are not a universal guarantee that every arbitrary 100-page web document will fit every browser/device memory budget.
Recommended large-document configuration
For large Canvas reports:
<Html2Pdf render-mode="canvas" :manual-pagination="true" :pdf-quality="1.5" />
Use explicit page markers:
<div class="html2pdf__page-break"></div>
For text-heavy reports where supported CSS is sufficient:
<Html2Pdf render-mode="text" />
Selectable-text mode avoids rasterizing the entire body and can be a better fit for long text-first documents.
About documents larger than 100 pages
The current automated stress and regression suite verifies large-document behavior through 100-page test fixtures.
This does not mean the package has a hard 100-page limit. PDF generation does not stop at page 100, and larger documents may also work successfully.
However, documents beyond the tested range depend increasingly on the actual workload, including:
- DOM size and complexity
- number and dimensions of images
- whether images are unique or repeatedly reused
- renderer choice
- browser and device memory
- headers, footers, and watermarks
- encryption and compression
- final PDF serialization cost
For very large text-first documents, renderMode="text" is generally the preferred starting point when its supported CSS is sufficient, because body text is represented as PDF text/vector operations instead of full-page raster screenshots.
For large Canvas documents, use deterministic pagination/page-break markers so the incremental renderer can keep individual Canvas captures page-bounded.
The package also provides page-aware progress and cooperative cancellation so applications can give users meaningful feedback during long exports:
Rendering page 74 of 100
Troubleshooting
Selectable text is not pixel-identical to the browser
Use render-mode="canvas" for maximum CSS fidelity.
Selectable-text mode intentionally prioritizes real PDF text and vector output.
Link looks like text but is not clickable
Make sure links are enabled:
const htmlToPdfOptions = {
enableLinks: true,
};
Also verify the source element is a real anchor with a valid href.
Link text looks stretched
Reduce large letter-spacing, transforms, and tight fixed widths in selectable-text mode.
Logo looks stretched
Header/footer logo dimensions are maximum bounds and preserve the source aspect ratio.
For normal content images:
img {
width: auto;
height: auto;
max-width: 100%;
object-fit: contain;
}
Header/footer overlaps content
Increase headerHeight / footerHeight, or reduce content size.
For selectively targeted headers, only target pages receive the extra header space. If excluded pages still have top spacing, check explicit htmlToPdfOptions.margin.
Header or footer image is missing
Check:
- URL accessibility
- CORS
- data URL validity
headerLogoEnabledfooterLogoEnabled
A large source logo displayed at a small size is capped to a proportional raster size during selectable-text header/footer preparation. The source file and caller options remain unchanged.
HTML header/footer is not visible
Check that:
- related header/footer is enabled
- HTML string is non-empty
- footer uses
footer-mode="html" - content fits inside configured height
Blank page around a manual break
Use only:
.html2pdf__page-break {
display: block;
height: 0;
clear: both;
}
Do not add break-before or page-break-before to the same helper.
Background watermark is hidden
Opaque PDF content can cover a DOM background watermark.
In selectable-text mode, a requested background watermark is drawn behind PDF content, but opaque cards can cover it. Canvas pages are opaque images, so a requested background mark uses a light foreground fallback to remain visible.
PDF is blurry
In canvas mode, increase pdfQuality or htmlToPdfOptions.html2canvas.scale carefully.
Selectable-text body text remains vector text and does not depend on html2canvas resolution.
Large document generation is slow
Reduce:
- canvas scale where relevant
- image dimensions
- document complexity
- simultaneous PDF generation
Large encrypted Canvas PDFs can show substantially higher memory retention than unencrypted Canvas PDFs in Chromium. Raw jsPDF-only isolation reproduced the same encrypted-raster retention pattern outside the Vue lifecycle, so this is tracked as an upstream/dependency concern rather than described as a confirmed permanent Vue memory leak. Selectable-text mode did not show the same severe raster-memory pattern in the package benchmarks.
A header or footer appears empty
The component accepts direct props as well as header and footer objects. Explicit object fields take priority, while omitted fields may fall back to matching direct props. Check the enabled flag, slot content, object settings, and reserved height. For a new integration, prefer the grouped object API or Vue slots.
Table heading repeats twice, or a row crosses a page cut
Use one semantic <thead> and normal <tbody> rows. Avoid manually duplicated headings. Try smart pagination and its diagnostic overlay. Large rowspans, oversized rows and unusual CSS need manual review in both renderers.
Canvas PDF appears to have selectable text
Chrome can OCR page images. Foreground text watermarks or some numbering paths may also add native text. Inspect extracted PDF text or text-showing operators rather than relying only on mouse selection. Use render-mode="text" for native selectable body text.
Testing & Browser Support
The package targets modern browsers supported by Vue and the underlying PDF/browser APIs.
Recommended testing:
- Chromium / Chrome / Edge
- Firefox
- WebKit
- Safari when Safari is important to production users
Playwright WebKit tests the WebKit engine; it is not identical to testing the Safari application itself.
Testing
The project uses unit, browser, and integration tests for smart splitting, table continuation, slots, PDF composition, page geometry, cancellation, and renderer behavior. Some documented verification runs were followed by targeted fixes; they are not a substitute for rerunning the current suite before release. Inspect representative PDFs, especially continued tables, nested frames, slots, and format/orientation combinations.
A documentation build does not run the package browser suites. For testing strategy, see the testing and browser support guide.
Media and typography verification
Documented media verification includes a 380-test unit run, TypeScript checks, library and playground builds, focused Chromium/WebKit browser exports, and selected rendered-page visual checks. A format/orientation matrix covered 82 combinations. Follow-up pagination and footer changes have narrower checks, including an inspected nine-page selectable-text report. Local Firefox verification remained open because the headless graphics compositor failed at launch. These are named test records rather than a fresh full-suite claim. See media and typography for the tested boundaries.
The release verification covers major public workflows, including:
- Canvas and selectable-text PDF generation
- Blob, ArrayBuffer and Base64 output APIs
- preview Blob creation
- download
- native browser print flow
- rendering mode routing
- selectable-text generation
- PDF link annotations
- pagination and page-break mapping
data-pdf-ignore- export-only page/body styling
- themes, custom CSS, external stylesheets and auto-fit
- metadata
- compression
- security
- protected/unprotected PDFs
- header page targeting
- selective header spacing
- optional header/footer logos
- custom logo sizing
- header/footer HTML rendering
- selectable header/footer text
- footer page numbering
- per-page watermark targeting
- progress stages
- page-aware
currentPage/totalPages - opt-in generation metrics
- cooperative cancellation and recovery
- portrait and landscape layouts
- large-document regression/stress scenarios
- selectable repeated-image caching
- page-bounded Canvas image-heavy rendering
- temporary rendering DOM/snapshot cleanup
- SSR-safe package import
- Nuxt 3 and Nuxt 4 SSR/hydration/client navigation
Package-manager consumer matrix
Packed-package consumer fixtures run against:
- npm
- pnpm
- Yarn
- Yarn Plug'n'Play
- Bun
These fixtures verify install behavior, TypeScript resolution, production builds, CSS imports, and runtime PDF generation.
Because PDF rendering is CPU-heavy, a small Playwright worker count is recommended:
npx playwright test --workers=2
Use a single worker for heavy stress/regression suites:
npx playwright test --workers=1
Run the package's release checks before publishing changes to the PDF library itself. Application consumers should also test representative real documents on the browsers and devices that matter to their users.
Performance Engineering
The large-document milestone was not only a benchmark exercise. It changed the runtime architecture and added repeatable correctness, cleanup and memory diagnostics.
The measurements below are historical fixtures. The latest full browser and stress reruns were not part of this docs update; measure your own document on target devices.
Rendering architecture
The package dynamically loads PDF engines and the optional composition engine, resolves a shared template once, reuses unchanged header/footer and styled slot captures, reduces repeated image waiting, and prunes unrelated app DOM while capturing a layer. Oversized splittable Canvas sections are preflight paginated on private DOM, then fitting fragments are reused. Cooperative yields avoid waiting for hidden-tab animation frames. Generation reuses one final Blob for preview and download, with cleanup, cancellation and opt-in timing metrics.
A local nine-page Canvas/slot export with watermark off was inspected: 27 image objects and zero PDF text-showing commands; a separate text-mode PDF contained 423 text commands. One local Canvas slot session changed from 26.89 seconds to 12.55 seconds after the refinements, without a controlled benchmark. A separate nine-page text run was about 4.35 seconds; no text-mode speedup was established. These observations do not predict your document's runtime or fidelity. Record browser/device, page count, renderer, assets, quality and cold/warm state when comparing exports.
Executive outcome
- paginated Canvas documents can render page by page instead of one giant document-height canvas
- 50-page and 100-page Canvas exports are verified as real, non-empty PDFs on the incremental path
- static HTML footer content is rasterized once and reused across pages
- selectable-text mode caches repeated image sources within one generation
- long work can be cancelled and then safely regenerated
- progress can expose
stage,progress,currentPageandtotalPages - temporary DOM, Canvas backing stores and encoded image references are released earlier
- browser yielding was added between expensive chunks
- encrypted raster memory retention was isolated and reproduced directly in jsPDF
Phase map
| Phase | Focus | Result |
|---|---|---|
| Phase 1 | Benchmark/stress foundation | Actual page count, Blob size, Canvas geometry, timing, memory and progress telemetry. |
| Phase 2.1 | Static HTML footer bottleneck | Render once + reuse; major time, output-size and heap reductions in the fixture. |
| Phase 2.2 | Clean memory isolation | Fresh-context methodology separated temporary peaks from retained heap. |
| Phase 2.3A | Security isolation | Basic user-password encryption identified as the trigger for high raster retention. |
| Phase 2.3B | Large-PDF correctness | Page count alone was proven insufficient; Blob size and Canvas telemetry were added. |
| Phase 2.4 | Scaling and cliff diagnostics | Raster payload + jsPDF encryption pattern isolated; legacy long-canvas failure instrumented. |
| Phase 3.1 | Incremental Canvas | Page-bounded captures replaced the giant capture for paginated Canvas documents. |
| Phase 3.2 | Security re-test | Giant-canvas issue addressed; encrypted-raster retention remained dependency-level. |
| Phase 3.2.1 | Raw jsPDF reproduction | Strongest retention pattern reproduced without Vue component lifecycle. |
| Phase 3.3 | Cancellation + progress | Cooperative cancellation, recovery and current-page progress added. |
| Phase 3.4 | Repeated assets + cleanup | Per-generation image cache and shorter DOM/Canvas lifetimes. |
| Phase 3.5 | Routing regression hardening | Correct incremental Canvas branch restored and protected by 50/100-page E2E tests. |
Headline measured results
| Metric | Before | After | Improvement |
|---|---|---|---|
| HTML footer, no logo | 7.82 s | 1.43 s | ~81.7% less time / 5.47Ă— faster |
| HTML footer + logo | 11.81 s | 1.29 s | ~89.1% less time / 9.16Ă— faster |
| Footer-heavy PDF | ~46.7 MB | ~1.0 MB | ~97.9% smaller |
| No-logo peak heap | 102.6 MB | 26.5 MB | ~74.2% lower |
| Giant Canvas capture geometry | ~99.96M px | ~1.74M px/page | ~98.26% lower peak capture area |
These percentages describe specific benchmark fixtures or capture geometry; they are not universal whole-package speed or memory guarantees.
Current incremental Canvas correctness
| Requested pages | Actual pages | Time | PDF size | Largest encoded Canvas |
|---|---|---|---|---|
| 10 | 10 | 1.53 s | 1.5 MB | 1191 Ă— 1461 |
| 25 | 25 | 4.33 s | 3.9 MB | 1191 Ă— 1461 |
| 40 | 40 | 7.54 s | 6.2 MB | 1191 Ă— 1461 |
| 50 | 50 | 9.68 s | 7.8 MB | 1191 Ă— 1461 |
| 60 | 60 | 12.56 s | 9.3 MB | 1191 Ă— 1461 |
| 75 | 75 | 16.87 s | 11.7 MB | 1191 Ă— 1461 |
| 100 | 100 | 25.25 s | 15.8 MB | 1191 Ă— 1461 |
The maximum encoded Canvas remained page-sized as the requested page count increased.
Encrypted raster PDFs
The strongest high-retention behavior was reproduced with raw jsPDF, so the project documents it as memory retention rather than mislabeling it as a permanent Vue memory leak.
Upstream issue: jsPDF #4017 - encrypted raster PDF memory retention
For large text-heavy secured documents whose CSS is supported by the vector renderer, selectable-text mode can be much lighter than raster Canvas mode. This is workload-dependent, not a universal renderer benchmark.
Verification mindset
Large exports are checked with more than “the Promise resolved”: actual page count, Blob size, Canvas dimensions, cancellation/recovery and cleanup signals are part of the regression strategy.
Read the complete phase-by-phase engineering report.
Fonts, Charts, SVG and Image Optimization
The following media controls are part of the published vue-pdf-export API. Use the current official reference when integrating them.
vue-pdf-export generates PDFs in the browser from Vue 3 content. It prepares media in a private export clone: it waits for fonts, snapshots chart canvases, applies the SVG policy and optionally optimizes images. The live Vue page stays intact, and temporary DOM, font registrations and caches are cleaned after completion, failure or cancellation.
Configure media for a Vue PDF
<script setup lang="ts">
import { Html2Pdf } from "vue-pdf-export";
import type {
PdfFontSource,
PdfImageOptimizationOptions,
} from "vue-pdf-export";
const fonts: PdfFontSource[] = [
{
family: "Report Sans",
source: "/fonts/report-sans.ttf",
format: "ttf",
weight: 400,
},
];
const autoOptimize: PdfImageOptimizationOptions = {
targetDpi: 144,
jpegQuality: 0.85,
preserveAlpha: true,
cacheRepeated: true,
};
</script>
<template>
<Html2Pdf
render-mode="text"
:fonts="fonts"
:auto-optimize="autoOptimize"
svg-mode="auto"
>
<template #pdf-content><article>Report content</article></template>
</Html2Pdf>
</template>
fonts, autoOptimize and svgMode can also be passed through htmlToPdfOptions. Explicit component props win. autoOptimize is off unless you enable it, so existing image quality stays as configured.
Custom fonts and selectable PDF text
Export waits for document.fonts.ready before layout. With no custom fonts list, Canvas uses the page's computed CSS font. Selectable-text mode still measures the CSS layout but maps unregistered font families to built-in PDF faces. Register a matching file when the exact project font must appear as selectable text.
Each PdfFontSource takes family, optional style (normal or italic), optional weight, source (URL, ArrayBuffer or Uint8Array), and optional format (ttf or otf). The CSS font-family must match family. Register the bold and italic files separately if the document uses them. The supported OTF path requires TrueType outlines; CFF-outline OTF, WOFF and WOFF2 cannot be embedded by this path. Remote URLs need working CORS, CSP and permission to use the font. Test the actual glyphs and language in a PDF viewer because shaping remains renderer-dependent.
Canvas charts and inline SVG
Canvas charts are captured as export-only PNG snapshots. Static numeric labels and fixed callouts can be present in the PDF; live hover tooltips cannot remain interactive in a static document. The four-chart ApexCharts sample is playground-only and does not add ApexCharts to the library's runtime requirements.
svgMode |
Canvas PDF | Selectable-text PDF |
|---|---|---|
auto (default) |
Keep inline SVG in staging; the final page is rasterized | Rasterize SVG because the text renderer does not draw SVG shapes |
preserve |
Keep inline SVG in staging | Rasterize unsupported SVG shapes |
rasterize |
Snapshot SVG into an image first | Rasterize SVG |
External SVG resources, filters, animation and foreignObject can differ from the live browser. Test representative charts, logos and icons in both rendering modes.
Image quality and per-export reuse
With autoOptimize, oversized raster images are downscaled from their displayed size and targetDpi (default 144). maxWidth and maxHeight cap output dimensions. jpegQuality defaults to 0.85 and is limited to 0.1–1. preserveAlpha defaults to true, choosing PNG for suitable non-JPEG sources; setting it to false flattens transparency onto white for JPEG. cacheRepeated defaults to true and reuses matching prepared sources, dimensions, format and quality during one export. Selectable-text rendering also has its own per-render image cache. Canvas pages remain raster page images, so object-level reuse is different.
Repeated image watermarks reuse the prepared source within one export. Sparse layouts reuse a PDF image alias; dense tiled layouts reuse one transparent sheet per page size. Static Canvas footer HTML and unchanged slot captures also reuse preparation. Set collectMetrics to inspect metrics.media operation counts, including chart snapshots, SVG rasterizations, optimized images, cache hits, watermark reuse and footer captures. These counters are not a speed or byte-saving guarantee. See watermarks and performance engineering.
CORS, failure and cleanup
Use same-origin media, a server that permits the page origin through CORS, or suitable data URLs. If image optimization cannot decode a cross-origin image, it leaves the staging image in place and warns; the renderer may still fail under its own CORS policy. A CORS-tainted watermark raises an error. Remote fonts can also fail during fetch. Private staging and temporary font registrations are cleaned on success, error and cancellation. See images and CORS for recovery steps.
Media, pagination and cleanup improvements
- Selectable-text table continuation now places an automatic split and repeated heading before the first row that crowds a measured page boundary. The inspected playground fixture no longer has a row-only page at that boundary. Oversized indivisible cells remain a limit.
- A large source logo used at a small header/footer size is rasterized proportionally for selectable-text output. A local nine-page fixture shrank from about 973 KB to about 300 KB; other documents can differ.
- Canvas page encoding uses
toBloband binary bytes where available, with a data-URL fallback. The playground’s JPEG quality was set to 0.88 in the measured demo configuration; that setting is not a library default. - Export HTML paths for headers, footers and slots are sanitized before staging insertion. A cancelled preflight and late-result guard prevent a cancelled generation from opening a completed preview.
See pagination, security and project flow for their boundaries.
Verification scope
The library's media verification records unit, TypeScript and build checks, Chromium and WebKit exports, and selected visual PDF inspections. Local Firefox verification failed at the headless graphics launch and remains open. Later pagination and footer fixes have narrower checks. These results cover named fixtures, not arbitrary HTML/CSS or a universal speed target. The testing guide explains how to verify your own content.
The generation-flow guide shows where preparation, rendering and cleanup happen in the package.
PDF Generation Flow
vue-pdf-export exposes an Html2Pdf component and a useHtml2Pdf composable. The component coordinates props, slots, pagination, preview and download. The composable manages generation state, progress, cancellation and output URLs. The core prepares content and chooses a renderer; renderer modules own Canvas or selectable-text output.
From Vue content to PDF Blob
- The component resolves options and, when a positive pagination height is supplied, plans DOM page breaks. Direct composable calls provide their own prepared element and page breaks.
- Core creates a private export staging clone, applies export-only styles and ignore markers, waits for fonts and images, and prepares chart, SVG and image assets. The live Vue page is left alone.
- Selectable-text mode maps supported DOM text, shapes, images and links into PDF operations. Canvas mode captures page images, one page at a time when markers are available, or uses its classic html2pdf path otherwise.
- Page styling, watermarks, headers, footers, metadata, optional page composition and security are applied to the PDF.
- One serialized Blob feeds preview, download, print or output conversion. Cleanup releases staging content, temporary fonts and media caches; the UI later revokes its owned Blob URLs.
The public library defaults to Canvas. The playground can use its own demo defaults. Nuxt imports are SSR safe, but PDF generation needs a browser DOM. See rendering modes, pagination, media preparation and programmatic output.
For maintainers, the project repository includes source code and engineering context. Check the installed package APIs against the current official documentation.
Find answers in the official vue-pdf-export documentation
For integration questions, search the current official documentation and its API reference. Check the package API and browser limitations before copying an example.
What can this answer?
Ask about selectable-text PDFs, Canvas rendering, Nuxt integration, page breaks, long tables, headers, footers, watermarks, fonts, image quality, and programmatic output. Cross-check answers against the current API reference.
Documentation examples cannot verify arbitrary application CSS or guarantee that a PDF will match every browser layout. Check your own exported PDF. For hands-on examples, see the playground, quick start, rendering modes, and troubleshooting.
Vue to PDF in Vue 3 and Nuxt
To turn a Vue component into a PDF in the browser, install vue-pdf-export, put the document inside its #pdf-content slot, and call generatePdf() from a component ref. Choose Canvas rendering when visual CSS fidelity matters most, or selectable-text rendering when people must search and copy body text.
Generate a PDF from a Vue component
<script setup lang="ts">
import { ref } from "vue";
import { Html2Pdf } from "vue-pdf-export";
import "vue-pdf-export/style.css";
const pdf = ref<InstanceType<typeof Html2Pdf> | null>(null);
async function downloadReport() {
await pdf.value?.generatePdf();
}
</script>
<template>
<button type="button" @click="downloadReport">Download report</button>
<Html2Pdf ref="pdf" filename="report.pdf" render-mode="canvas">
<template #pdf-content>
<article>
<h1>Report</h1>
<p>Rendered from a Vue component.</p>
</article>
</template>
</Html2Pdf>
</template>
The package's default is Canvas. The explicit render-mode above makes the choice visible. For a JavaScript Options API example and download settings, use the quick start. For Nuxt 3 and Nuxt 4, import the component safely during SSR and generate only after the browser mounts; see the Nuxt guide.
Which Vue PDF approach fits the document?
| Requirement | Start with | Tradeoff |
|---|---|---|
| Match browser CSS, charts and visual reports | Canvas mode | The page body becomes an image; readers cannot select its text. |
| Search, select or copy report text | Selectable-text mode | Some CSS, glyph and SVG behavior differs from the browser; test the actual document. |
| Print an entire server-rendered route without a browser user action | A server browser such as Playwright page.pdf() in an appropriate Nuxt server route
|
You operate a browser service and handle its server resources and access controls. |
| Display an existing PDF rather than create one | A PDF viewer | Viewing and generating are separate tasks. |
The html2pdf.js project documents that its Canvas path renders content as an image, so text is not searchable or selectable. vue-pdf-export exposes that style of capture as its Canvas mode and also provides a selectable-text renderer. These modes share the component API, pagination controls, preview, download and print flow.
Can Vue-generated PDFs be searched?
Yes, when you use render-mode="text" for the document body. PDF viewers can then select and search the emitted text. Canvas mode keeps the body as page images. Custom TTF or TrueType-outline OTF files can be registered for selectable text; CFF-outline OTF and WOFF/WOFF2 are not supported for embedding. See fonts, charts, SVG and images for the renderer-specific limits.
Can I combine PDFs in Vue?
You can prepend or append existing PDF pages to a generated document using the published composition options. This combines whole pages; it does not provide arbitrary editing of their internal content.
What should I test before shipping?
Export a representative document in each target browser. Check page breaks, font loading, remote-image CORS, output size, links and text selection as applicable. For long reports, use page-aware progress, cancellation and generation metrics and test a realistic page count. The production guide covers release checks.
Links and feedback
If you are using Vue 3 or Nuxt and have run into PDF pagination, selectable-text, CORS, headers/footers, or browser-memory issues, I would love to hear what you are building and which edge cases are hardest.
If this project has been useful, you can support its development.
Top comments (0)