Embedded JavaScript
PDFs can ship embedded JavaScript that drives interactive form behavior — toggling one checkbox can auto-toggle another, a sum field can recompute when its inputs change, a text field can format itself on blur, and so on. By default kviewer ignores this scripting. Set the scripting prop to true to opt in.
<KViewer source="/contract.pdf" :scripting="true" />
What runs
When enabled, the following PDF script entry points fire:
- Field actions — every widget's
/A(primary action, typically Mouse Up) and/AA(additional actions: Mouse Down, Mouse Up, Focus, Blur, Enter, Exit, Keystroke). - Field-value scripts —
Calculate,Format,Validate,Keystrokefor text and dropdown fields. Calculation order respects the document's/COarray, so dependent fields update in the right sequence. - Document actions —
/OpenActionruns after the document loads;WillSave,DidSave,WillPrint,DidPrintfire around save/print events. - Page actions —
PageOpenandPageClosefire as pages render or scroll out.
Scripts interact with kviewer's form state through pdf.js's standard PDF JavaScript object model: this.getField(name), event.value, event.target, app.alert(...), etc.
Security model
Scripts execute in pdf.js's QuickJS WASM sandbox. The sandbox has no access to the DOM, network, host JavaScript runtime, or filesystem — it can only read and write the document's own form fields and call a fixed set of PDF-spec APIs. That isolation is the same one Mozilla's official pdf.js viewer relies on.
Arbitrary JavaScript from an untrusted PDF is still real attack surface even when sandboxed, which is why scripting defaults to false. Only enable it for PDFs you trust to behave reasonably, and prefer scoping it to specific documents rather than turning it on globally.
When the prop is read
scripting is treated as read-once per document load. Toggling it at runtime won't reinitialize the sandbox against the current document; reload by changing the source prop or remounting the component (e.g. with a :key bump).
<!-- Reload to take a scripting change into effect -->
<KViewer :key="`${pdfUrl}-${scriptingEnabled}`" :source="pdfUrl" :scripting="scriptingEnabled" />
Multi-tab usage
KViewerTabs accepts scripting as a top-level prop (applies to every tab) and as a per-tab override via ViewerTabItem.scripting. The per-tab value wins when set.
const tabs: ViewerTabItem[] = [
{ id: 'static', label: 'Static.pdf', source: '/static.pdf' }, // scripting off (inherits default false)
{ id: 'calc', label: 'Invoice.pdf', source: '/invoice.pdf', scripting: true }, // scripting on for this one
]
<KViewerTabs :items="tabs" />
What is NOT supported
- External resource fetches from scripts — the sandbox has no network access.
app.launchURL, file attachments,Doc.print(...)with side effects — these are stubbed.- XFA forms — kviewer renders AcroForm widgets, not XFA. The scripting sandbox handles AcroForm field scripts only.
- Reading the host page — scripts can't see the surrounding Vue app, cookies, or storage.
Example: checkbox mirror
A canonical small example — clicking checkbox A mirrors its state into checkbox B via the PDF's own JS:
CheckboxA → /A → /S /JavaScript /JS (this.getField("CheckboxB").value = event.target.value)
With scripting: true, clicking A in kviewer fires the script in the sandbox, the sandbox sets B's value, and kviewer re-renders B with the new state — round-trip in a single click.
Troubleshooting
- Nothing happens when I enable
scripting. Confirm the PDF actually contains script actions — open it in Adobe Reader or Acrobat and verify the interaction works there. If the PDF uses XFA, scripting won't apply. - Scripts run but a field doesn't update. Field updates flow through kviewer's form-field state. If you've placed or detected fields that aren't part of the PDF's AcroForm, the sandbox can't address them (and we intentionally ignore sandbox writes to non-parsed fields).
pdf.sandbox.mjs404s. The sandbox bundle is served at/_kviewer/pdfjs/pdf.sandbox.mjsby the Nuxt module. If you've customized your asset routes or are running the module in a unusual setup, ensure that path resolves.