How to add annotations to PDF using Vue.js
Table of contents
Add PDF annotations in Vue.js using pdf-lib for basic text overlays, or Nutrient for 17+ annotation types with persistence.
This tutorial covers two approaches to PDF annotations in Vue.js:
- pdf-lib — Add basic text overlays to PDFs (client-side only, limited annotation types)
- Nutrient — Full annotation support with 17+ types, persistence, and collaboration features
What are PDF annotations?
PDF annotations are interactive elements added to a PDF document — text, graphics, highlights, stamps — without modifying the original content. They’re used for document review, collaboration, and form filling.
pdf-lib overview
pdf-lib(opens in a new tab) is a JavaScript library for creating and modifying PDFs. It runs entirely client-side and supports basic operations like adding text, images, and drawing shapes. However, it doesn’t support standard PDF annotation types that persist across viewers like Adobe Acrobat or Apple Preview.
Prerequisites
- Node.js(opens in a new tab) and npm(opens in a new tab)
- Basic Vue.js and JavaScript knowledge
Step 1 — Project setup
Install Vue CLI(opens in a new tab) globally:
Terminal window npm install -g @vue/cliCreate a Vue project:
Terminal window vue create pdf-modifier-appSelect Default (Vue 3) when prompted.
Change to the project directory:
Terminal window cd pdf-modifier-appCreate
src/components/PdfModifier.vue.
Step 2 — Install dependencies
npm install pdf-lib axiosStep 3 — Build the PdfModifier component
Add the template:
<template> <div> <button @click="handleButtonClick">Add Text and Download PDF</button> </div></template>Add the script section with the PDF URL:
<script>import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib';import axios from 'axios';
export default { name: 'PdfModifier', data() { return { pdfUrl: 'https://pdf-lib.js.org/assets/with_update_sections.pdf', // URL to the PDF with update sections }; }, methods: { // ... (explained below) },};</script>The modifyPdf method fetches the PDF, adds rotated text to the first page, and returns the modified bytes:
async modifyPdf() { const url = this.pdfUrl;
// Fetch the PDF using axios. const response = await axios.get(url, { responseType: 'arraybuffer' }); const existingPdfBytes = response.data;
// Load the PDF using pdf-lib. const pdfDoc = await PDFDocument.load(existingPdfBytes); const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
// Modify the PDF. const firstPage = pdfDoc.getPages()[0]; const { height } = firstPage.getSize(); firstPage.drawText('This text was added with JavaScript!', { x: 5, y: height / 2 + 300, size: 50, font: helveticaFont, color: rgb(0.95, 0.1, 0.1), rotate: degrees(-45), });
// Save the modified PDF to a variable (`pdfBytes`). const pdfBytes = await pdfDoc.save();
// Now, you can use `pdfBytes` for any additional operations (e.g. downloading the modified PDF). return pdfBytes;},handleButtonClick calls modifyPdf and triggers the download:
async handleButtonClick() { try { const pdfBytes = await this.modifyPdf(); this.downloadPdf(pdfBytes); } catch (error) { console.error('Error modifying PDF:', error); }},downloadPdf creates a blob and triggers the browser download:
downloadPdf(pdfBytes) { const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'modified_pdf.pdf'; link.click();},Step 4 — Update App.vue
Update src/App.vue to use the PdfModifier component:
<template> <div id="app"> <PdfModifier /> </div> </template>
<script> import PdfModifier from './components/PdfModifier.vue'
export default { name: 'App', components: { PdfModifier } } </script>Step 5 — Run the app
npm run serveClick Add Text and Download PDF to download the modified PDF.
Nutrient: Full PDF annotation support
pdf-lib adds text overlays to PDFs, but these aren’t true PDF annotations — they won’t appear in annotation panels or sync across devices. Nutrient’s Vue.js PDF annotation library provides standard PDF annotations that work with Adobe Acrobat, Apple Preview, and other PDF readers.
What you can build:
- Document review workflows with highlights, comments, and stamps
- Approval processes with signature fields and checkboxes
- Collaborative editing with real-time annotation syncing
- Redaction workflows for sensitive document handling
Annotation types: Text, highlight, underline, strikeout, squiggle, ink, shapes (rectangle, ellipse, line, polyline, polygon), stamps, images, notes, callouts, links, and redactions.
Integrating Nutrient with Vue.js
This section walks through setting up Nutrient in a Vue.js project, from installation to creating a reusable PDF viewer component.
Step 1 — Create a Vue.js project
Requires Node.js 20.19+ or 22.12+. Check your version with node -v.
npm create vue@latest nutrient-vue-projectcd nutrient-vue-projectnpm installStep 2 — Install Nutrient
npm install @nutrient-sdk/viewerThe useCDN: true option loads SDK assets from Nutrient’s CDN, so no manual asset copying is required.
Step 3 — Display a PDF
Add a PDF to the public directory (use our demo document if needed).
Create src/components/NutrientViewer.vue:
<script setup>import { ref, onMounted, onUnmounted, watch } from "vue";
const props = defineProps({ pdfFile: { type: String, required: true, },});
const emit = defineEmits(["loaded"]);const containerRef = ref(null);let NutrientViewer = null;let instance = null;
async function loadViewer() { if (!containerRef.value) return;
// Unload existing instance. if (NutrientViewer && instance) { NutrientViewer.unload(containerRef.value); }
// Load `NutrientViewer` dynamically. NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
instance = await NutrientViewer.load({ container: containerRef.value, document: props.pdfFile, useCDN: true, });
emit("loaded", instance); return instance;}
onMounted(() => { loadViewer();});
watch( () => props.pdfFile, () => { loadViewer(); },);
onUnmounted(() => { if (NutrientViewer && containerRef.value) { NutrientViewer.unload(containerRef.value); }});</script>
<template> <div ref="containerRef" class="pdf-container" /></template>
<style scoped>.pdf-container { width: 100%; height: 100vh;}</style>Step 4 — Create the app component
Replace the contents of src/App.vue:
<script setup>import { ref } from "vue";import NutrientViewer from "@/components/NutrientViewer.vue";
const pdfFile = ref("/example.pdf");
function handleLoaded(instance) { console.log("Nutrient loaded:", instance);}
function openDocument(event) { const file = event.target.files[0]; if (file) { if (pdfFile.value.startsWith("blob:")) { URL.revokeObjectURL(pdfFile.value); } pdfFile.value = URL.createObjectURL(file); }}</script>
<template> <div id="app"> <label for="file-upload" class="file-upload-btn">Open PDF</label> <input id="file-upload" type="file" accept=".pdf" @change="openDocument" /> <NutrientViewer :pdf-file="pdfFile" @loaded="handleLoaded" /> </div></template>
<style>#app { text-align: center;}
body { margin: 0;}
input[type="file"] { display: none;}
.file-upload-btn { display: inline-block; padding: 10px 16px; background: #4a8fed; color: #fff; border-radius: 4px; cursor: pointer; font-weight: bold;}</style>Step 5 — Run the app
npm run devOpen http://localhost:5173 in your browser.
Click Open PDF to load documents. The viewer includes annotation tools for signatures, stamps, and highlights.
The complete code is available on GitHub(opens in a new tab).
Adding text annotations programmatically
Update loadViewer in NutrientViewer.vue to add a text annotation on load:
<script setup>import { ref, onMounted, onUnmounted, watch } from "vue";
// ... existing code ...
async function loadViewer() { if (!containerRef.value) return;
if (NutrientViewer && instance) { NutrientViewer.unload(containerRef.value); }
NutrientViewer = (await import("@nutrient-sdk/viewer")).default;
instance = await NutrientViewer.load({ container: containerRef.value, document: props.pdfFile, useCDN: true, });
// Add text annotation after loading. await createTextAnnotation(instance);
emit("loaded", instance); return instance;}
async function createTextAnnotation(instance) { const annotation = new NutrientViewer.Annotations.TextAnnotation({ pageIndex: 0, text: { format: "plain", value: "Welcome to Nutrient" }, font: "Helvetica", isBold: true, horizontalAlign: "left", boundingBox: new NutrientViewer.Geometry.Rect({ left: 50, top: 200, width: 200, height: 80, }), fontColor: NutrientViewer.Color.BLUE, });
return instance.create(annotation);}
// ... rest of component ...</script>Adding ink annotations programmatically
async function createInkAnnotation(instance) { const { List } = NutrientViewer.Immutable; const { DrawingPoint, Rect } = NutrientViewer.Geometry; const { InkAnnotation } = NutrientViewer.Annotations;
const annotation = new InkAnnotation({ pageIndex: 0, boundingBox: new Rect({ width: 400, height: 100 }), strokeColor: new NutrientViewer.Color({ r: 255, g: 0, b: 255 }), lines: List([ List([ new DrawingPoint({ x: 5, y: 5 }), new DrawingPoint({ x: 100, y: 100 }), ]), ]), });
return instance.create(annotation);}Call after loading:
instance = await NutrientViewer.load({ container: containerRef.value, document: props.pdfFile, useCDN: true,});
await createInkAnnotation(instance);Conclusion
This tutorial covered two approaches: pdf-lib for basic text overlays, and Nutrient for standard PDF annotations that persist across viewers. Nutrient’s annotations work with Adobe Acrobat, Apple Preview, and other PDF readers.
Try the free trial or explore the demo.
FAQ
With pdf-lib: text overlays, images, and basic shapes. With Nutrient: 17+ standard annotation types, including highlights, text, ink, stamps, shapes, links, and redactions.
No. Both pdf-lib and Nutrient run client-side. Nutrient optionally supports server deployment for annotation syncing and collaboration.
pdf-lib adds content directly to a PDF, and not as standard annotations. Nutrient creates proper PDF annotations that appear in annotation panels in Adobe Acrobat, Apple Preview, and other viewers.
With pdf-lib, convert to a blob and trigger download. With Nutrient, use instance.exportPDF() to get the PDF with embedded annotations.