# PDF forms

Render interactive fields and download a PDF containing the filled values.

Source: https://anara.com/lector/docs/code/pdf-form

These docs describe the source deployed with this site, tracking main rather than a versioned npm release. Check your installed @anaralabs/lector and pdfjs-dist versions before applying examples.

Add `AnnotationLayer` with `renderForms` to render the PDF's interactive fields. Form state belongs to PDF.js's document annotation storage. Use the document proxy's `saveDocument()` method when you need PDF bytes with the edits applied.

## Fill and download

Use a PDF with actual interactive form fields, such as `public/form.pdf`, and the `pdf-setup.ts` from [installation](https://anara.com/lector/docs/installation).

```tsx title="form-viewer.tsx"
"use client";

import { AnnotationLayer, CanvasLayer, Page, Pages, Root, TextLayer, usePdf } from "@anaralabs/lector";
import { useState } from "react";
import "./pdf-setup";

function DownloadFilledPDF() {
  const document = usePdf((state) => state.pdfDocumentProxy);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function download() {
    setSaving(true);
    setError(null);
    try {
      const bytes = await document.saveDocument();
      const blob = new Blob([new Uint8Array(bytes)], { type: "application/pdf" });
      const url = URL.createObjectURL(blob);
      const link = window.document.createElement("a");
      link.href = url;
      link.download = "filled-form.pdf";
      window.document.body.appendChild(link);
      link.click();
      link.remove();
      window.setTimeout(() => URL.revokeObjectURL(url), 1000);
    } catch {
      setError("Couldn't save this PDF. Please try again.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <div>
      <button type="button" disabled={saving} onClick={() => void download()}>
        {saving ? "Preparing download…" : "Download filled PDF"}
      </button>
      {error && <p role="alert">{error}</p>}
    </div>
  );
}

export default function FormViewer() {
  return (
    <Root source="/form.pdf" style={{ height: 600, display: "flex", flexDirection: "column" }}>
      <DownloadFilledPDF />
      <div style={{ flex: 1, minHeight: 0 }}>
        <Pages>
          <Page>
            <CanvasLayer />
            <TextLayer />
            <AnnotationLayer renderForms />
          </Page>
        </Pages>
      </div>
    </Root>
  );
}
```

`saveDocument()` returns a promise for the edited PDF bytes. Downloading the source URL or calling `getData()` is not the same operation. See the [PDF.js document proxy reference](https://mozilla.github.io/pdf.js/api/draft/module-pdfjsLib-PDFDocumentProxy.html#saveDocument).

## Reading values in your application

Do not use `new FormData(viewerElement)` as a complete document export. `Pages` virtualizes the document, so offscreen widgets may not exist in the DOM. `FormData` also follows HTML successful-control rules; unchecked or disabled controls can be absent.

The proxy exposes `annotationStorage` for PDF.js annotation state and `getFieldObjects()` for field metadata. If you need a business-domain JSON object, map PDF field identifiers to your schema explicitly and validate the values in your app. Storage is PDF.js state, not a ready-made object keyed by your application's field names.

The live example below inspects currently mounted DOM form controls for demonstration. Use the download recipe above for document-wide PDF output.

## Boundaries

This supports interactive PDF form widgets, not arbitrary PDF text editing. A printed blank line in a scanned document is not a field. Test the specific form types your app accepts and verify the downloaded file in another PDF reader.

Lector's custom highlight and comment overlays are separate from PDF.js form storage. `saveDocument()` does not automatically embed those overlays in the saved PDF.

## Live example

Open the documentation page linked above to use this interactive example.
