npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@quorbe/xfa.js

v0.4.1

Published

The first open source XFA (XML Forms Architecture) renderer for the web. Parse, render, script, and fill Adobe XFA dynamic PDF forms in JavaScript/TypeScript.

Readme

xfa.js

The first open source XFA forms renderer for the web.

License: MIT Status: Phase 4 (visual viewer)

XFA (XML Forms Architecture) is Adobe's format for dynamic PDF forms. It is used extensively by governments, military, and security organizations worldwide — yet no working open source implementation has existed. xfa.js fills that gap: parse, render, script, and fill XFA forms entirely in JavaScript/TypeScript, in the browser or Node.

npm install @quorbe/xfa.js

⚠️ Early days. Phases 1 (parser), 2 (React renderer), 3 (writer), and 4 (visual document viewer) are implemented and tested — the full round-trip works: parse → render → fill → download a completed PDF, in a flat form or in faithful document layout. Reflow/pagination lands next; see the roadmap.

Quick start

Parse an XFA PDF into a clean, typed JSON model:

import { XfaParser, parseXfaPdf } from '@quorbe/xfa.js';

// In the browser
const bytes = new Uint8Array(await file.arrayBuffer());
const form = await parseXfaPdf(bytes);

console.log(form.locale);        // "en_US"
console.log(form.fields.length); // 42

for (const field of form.fields) {
  console.log(field.somExpression, field.fieldType, '=', field.value);
  // form1.header.firstName  text  =  Ada
}

Need finer control, or want the raw packets?

const parser = await XfaParser.fromPdf(bytes);
parser.getPacket('template'); // raw template XML
parser.getPacket('datasets'); // raw datasets XML (current values)
const form = parser.parse();

Already have the XML (e.g. from another extractor)?

const form = XfaParser.fromXml({ template, datasets }).parse();
// or a single <xdp:xdp> document:
const form2 = XfaParser.fromXml(xdpString).parse();

Detect whether a PDF is XFA at all:

import { isXfaPdf } from '@quorbe/xfa.js';
if (await isXfaPdf(bytes)) { /* … */ }

Render an interactive form (Phase 2)

The @quorbe/xfa.js/renderer entry turns a parsed form into native, interactive HTML — not a PDF viewer. React is an optional peer dependency, so parser-only users never pull it in.

import { parseXfaPdf } from '@quorbe/xfa.js';
import { XfaForm } from '@quorbe/xfa.js/renderer';
import '@quorbe/xfa.js/styles.css';

const form = await parseXfaPdf(bytes);

function App() {
  return (
    <XfaForm
      form={form}
      onSubmit={(values) => console.log(values)}
      onChange={(key, value) => console.log(key, '→', value)}
    />
  );
}

What the renderer does today:

  • Maps every field type to a real control (text, numeric, date/time, checkbox, radio groups from exclusion groups, dropdown/listbox, signature, etc.).
  • Repeatable sections (<occur>): add/remove instances, with values kept per-instance and re-indexed on removal.
  • Show/hide: respects the presence attribute, plus an isVisible hook so dynamic visibility can be driven externally (and by the Phase 3 engine).
  • Controlled or uncontrolled value state; per-field render overrides via renderField; read-only mode. See docs/renderer.md.

Visual document mode (Phase 4)

XfaForm renders a flat list of fields. XfaViewer renders the form as the document it is — preserving the original layout — while staying fully interactive. Submitted values are identical, so it drops straight into the same fill-and-download cycle.

import { XfaViewer } from '@quorbe/xfa.js/renderer';
import '@quorbe/xfa.js/styles.css';

<XfaViewer form={form} width={816} onSubmit={handleSubmit}>
  <button type="submit">Fill &amp; download PDF</button>
</XfaViewer>;

// Or flip the existing component into visual mode (backward compatible):
<XfaForm form={form} visualMode />;

How it works: real XFA forms are laid out by flow, not absolute coordinates (on DS-7801, ~80% of nodes carry no x/y at all). So XfaViewer maps each container's XFA layout onto CSS — tb→column, lr-tb→row+wrap, table/row→table flow, position→absolute — and lets the browser place and wrap children. Fidelity comes from correct flow, not a pixel overlay. The page frame (size, margins) is read from the template's <medium>/<contentArea>.

Scope: Phase 4 renders the form as a single continuous, zoomable sheet. True multi-page reflow/pagination (breaking content across physical pages, measuring heights) is Phase 5.

Fill & save a PDF (Phase 3)

@quorbe/xfa.js/writer writes submitted values back into the PDF's XFA datasets stream, producing a filled PDF that opens with the values populated. It takes exactly what XfaForm's onSubmit produces — closing the loop:

import { XfaWriter } from '@quorbe/xfa.js/writer';

const writer = new XfaWriter();
const filledPdf = await writer.write(originalPdfBuffer, values); // Uint8Array

// In the browser: download it
const url = URL.createObjectURL(new Blob([filledPdf], { type: 'application/pdf' }));
// optionally make it non-editable for archiving:
const flat = await writer.flatten(filledPdf);

Full cycle, wired through the renderer:

<XfaForm
  form={form}
  onSubmit={async (values) => {
    const filled = await new XfaWriter().write(originalPdfBuffer, values);
    /* download / upload `filled` */
  }}
>
  <button type="submit">Fill & download</button>
</XfaForm>

Guarantees: the original buffer is never mutated, repeatable-section instances (alias[0], alias[1], …) and missing nodes are created as needed, and on any failure the original PDF is returned unchanged. See docs/writer.md.

What you get back

parse() returns an XfaForm:

interface XfaForm {
  root: XfaSubform;     // the form tree (subforms → fields/exclGroups/draws)
  fields: XfaField[];   // every field, flattened, in document order
  locale: string | null;
  packets: XfaPackets;  // raw XML, in case you need the source
}

Each XfaField is normalized into a renderer-friendly shape — Adobe's many <ui> widgets collapse into a small fieldType set (text, numeric, date, checkbox, radio, dropdown, listbox, …), with caption, value (merged from the datasets packet), items, readOnly, required, geometry, and a SOM-style somExpression path. See docs/parser.md for the full model.

Why this is hard (and why it didn't exist)

XFA is not "PDF with form fields" (that's AcroForm). It is a complete XML application embedded in the PDF: a template grammar, a separate live data DOM, a layout engine, and two scripting languages (FormCalc + JavaScript). Adobe deprecated it, browsers never supported it, and pdf.js explicitly does not render it. xfa.js rebuilds the stack piece by piece — starting with a faithful parser. See docs/xfa-format.md for a primer.

Roadmap

| Phase | Module | Status | | ----- | ------------- | ----------------- | | 1 | Parser | ✅ Implemented | | 2 | Renderer (React) | ✅ Implemented | | 3 | Writer (fill & save) | ✅ Implemented | | 4 | Visual viewer (CSS-flow layout) | ✅ Implemented — XfaViewer | | 5 | Reflow & pagination | 🚧 Next — multi-page layout, content measurement | | — | Scripting (FormCalc + JS) | Planned — dynamic show/hide, calculations |

Development

npm install
npm test           # vitest
npm run typecheck  # tsc --noEmit (strict)
npm run build      # vite lib build + .d.ts
npm run demo       # Next.js visual playground (see demo/)

Inspect a local XFA PDF from the CLI:

npx tsx scripts/inspect.ts samples/your-form.pdf
npx tsx scripts/inspect.ts samples/your-form.pdf --json

Tech stack

TypeScript (strict) · Vite (lib build) · Vitest · React (peer, for the renderer) · fast-xml-parser · pdf-lib. No other heavy dependencies.

License

MIT © quorbe