Guide

Export & Import

Export PDFs and save annotation drafts

KViewer supports exporting PDFs with annotations baked in, and saving/restoring annotation state as JSON for draft workflows.

Export a PDF

Use the template ref to export the current document with annotations:

<template>
  <div class="h-screen">
    <KViewer ref="viewer" :source="pdfUrl" />
    <button @click="exportPdf">Download PDF</button>
  </div>
</template>

<script setup lang="ts">
const viewer = ref()
const pdfUrl = '/sample.pdf'

async function exportPdf() {
  const bytes = await viewer.value?.exportPdf({
    flatten: true,
    download: true,
    fileName: 'annotated-document.pdf',
  })
}
</script>

Export Options

OptionTypeDefaultDescription
flattenbooleanfalseBurn annotations into page content (non-editable)
downloadbooleanfalseTrigger a browser file download
fileNamestringauto-generatedCustom filename for download
preserveOriginalAnnotationsbooleanfalseKeep unmodified native PDF annotations

Get Bytes for Upload

Export without downloading to get raw bytes for server upload:

const bytes = await viewer.value?.exportPdf({ flatten: true })

// Upload to your server
await fetch('/api/documents/save', {
  method: 'POST',
  body: bytes,
  headers: { 'Content-Type': 'application/pdf' },
})

Flatten Annotations

When flatten: true, annotations are burned into the PDF page content and become part of the rendered page. They can no longer be edited or removed.

When flatten: false (default), annotations are added as standard PDF annotation objects that can be edited in other PDF viewers.

Handle Native PDF Annotations

When a PDF has existing annotations that were auto-imported by KViewer:

// Recommended: don't preserve originals to avoid duplicates
const bytes = await viewer.value?.exportPdf({
  flatten: false,
  preserveOriginalAnnotations: false,
})
Setting preserveOriginalAnnotations: true when native annotations were auto-imported may create duplicate annotations in the exported PDF. Use false in this workflow.

Author attribution

Each non-flattened annotation can carry an author name, written to the PDF annotation's /T field. KViewer takes the author from the userName prop:

<!-- Annotations are attributed to "Jane Doe" -->
<KViewer :source="pdfUrl" user-name="Jane Doe" />

<!-- No userName → annotations carry no author -->
<KViewer :source="pdfUrl" />

When userName is omitted, no /T is written at all — KViewer does not fall back to a placeholder name. This matches how Firefox's built-in PDF viewer (pdf.js) behaves, and it matters for downstream systems: archiving/DMS tools often render an author + timestamp caption over each markup annotation. A placeholder author (e.g. a generic "User") would show up burned into that caption, overlapping the page. Leaving userName unset keeps the rendered preview clean.

The date is written as /CreationDate for newly authored annotations and /M (modification date) for edits to annotations that already existed in the source PDF — again mirroring pdf.js.

Pass a real author (the signed-in user's name) via userName when you want attribution preserved in the exported PDF; leave it unset when you want anonymous, caption-free annotations.
This applies to non-flattened exports (flatten: false). With flatten: true, annotations are rasterized into the page, so there are no annotation objects and no /T/date metadata at all.

Save Annotation Drafts

Save the current annotation state as JSON for later restoration:

// Save draft
const annotations = viewer.value?.getAnnotations() ?? []
const draft = JSON.stringify(annotations)
localStorage.setItem('document-draft', draft)

// Or save to your API
await fetch('/api/drafts', {
  method: 'POST',
  body: draft,
  headers: { 'Content-Type': 'application/json' },
})

Restore Annotation Drafts

Import previously saved annotations:

// Load draft
const draft = localStorage.getItem('document-draft')
if (draft) {
  const annotations = JSON.parse(draft)
  const result = await viewer.value?.importAnnotations(annotations, {
    mode: 'replace',
  })
  console.log(`Loaded ${result.loaded}, skipped ${result.skipped}`)
}

Import Modes

ModeDescription
replaceClear all existing annotations, then add the imported ones
mergeAdd imported annotations alongside existing ones, skipping collisions

Full Round-Trip Example

<template>
  <div class="h-screen">
    <KViewer ref="viewer" :source="pdfUrl" text-layer />
    <div class="flex gap-2 p-4">
      <button @click="saveDraft">Save Draft</button>
      <button @click="loadDraft">Load Draft</button>
      <button @click="exportFlattened">Export PDF</button>
    </div>
  </div>
</template>

<script setup lang="ts">
const viewer = ref()
const pdfUrl = '/sample.pdf'

function saveDraft() {
  const annotations = viewer.value?.getAnnotations() ?? []
  localStorage.setItem('draft', JSON.stringify(annotations))
}

async function loadDraft() {
  const saved = localStorage.getItem('draft')
  if (saved) {
    await viewer.value?.importAnnotations(JSON.parse(saved), { mode: 'replace' })
  }
}

async function exportFlattened() {
  await viewer.value?.exportPdf({
    flatten: true,
    download: true,
    fileName: 'final-document.pdf',
  })
}
</script>
Copyright © 2026