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:

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, import Html2Pdf and its stylesheet, place your document in the #pdf-content slot, and call generatePdf() from a Vue template ref. Use render-mode="text" for native searchable/selectable body text; choose render-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-ignore for 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-export functionality 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-ignore for 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 html2canvas finishes 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 logo

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 = Source:https://dev.to/saurabhzaiswal/building-a-better-pdf-generator-in-vue-3-with-vue-pdf-export-416

Related posts