JDF
JSON document standard

the AI-native document standard

Built for the AI era — a JSON document format that LLMs read without a translator. PDFs were designed for humans and printers; JDF is designed for the systems that read, write, and reason over documents next. Beautiful pages. Editable in any text editor. Diffable in git. Generated with one line of code. Searchable with grep. Validatable with a JSON Schema. Embeddable on any web page.

★ NEW JDF Forms — fillable in the browser, save back as JSON
Download for macOS View on GitHub

Linux, Windows, and Intel Macs: grab a build from the latest release.

Live render — this is a real <jdf src="hello-world.jdf"> tag, rendered by jdf.js. View source, embed it on your own page in one line.

Why JDF

It's JSON. Every consequence below falls out of that.

AI

LLMs read it natively

JSON is the language LLMs already speak. No PDF parsing, no broken columns, no lost tables. Hand a .jdf to GPT or Claude and it sees the structure exactly as you wrote it. More →

$

Every tool already works

cat, grep, jq, VS Code, every linter, every diff tool. No plugin, no learning curve.

git

git diff is real

Change one heading, the diff is one line. Documents review the same way as code.

{ }

Generate with one line

JSON.stringify(doc). No PDF library, no font dictionaries, no encoding rules.

JSON Schema validates

Autocomplete in your editor. CLI validation in CI. Type-safe generation.

Search is text search

grep "TODO" *.jdf works. jq queries pull every table cell on the page.

No vendor, no lock-in

Opens the same way today and in 20 years. JSON has no proprietary owner.

RAG / AI ingestion — why JSON beats PDF

A retrieval pipeline that ingests JDF skips most of the work it would do on a PDF. The structure that PDF parsers try to reconstruct is already in the file.

Pipeline stagePDFJDF
Parse / extract pdfplumber / pymupdf / unstructured — layout analysis, font heuristics, OCR fallback for image-only pages JSON.parse(content) — no layout reconstruction, the structure is already in the file
Chunking Token-windowed splits that frequently slice through tables, lists, footnotes Each element (text / richtext / table / list / image) is a natural retrieval unit — no chunker config
Metadata Synthesised after the fact (page number, "is this a heading?") and often wrong First-class on every element: type, heading, page index, position, link target
Embedding noise Repeated page headers / footers / page numbers leak into chunks header and footer live in their own tree, never in content chunks
Re-indexing on edit Re-parse + re-chunk + re-embed the whole PDF Diff the JSON, re-embed only the changed elements
Tables Cells smear across columns; multi-row headers collapse { headers: [...], rows: [[...]] } — every cell at its real coordinate
Images / figures Dropped or stubbed as [image] Stored in resources.images with alt text and a stable anchor — a vision step can fetch it at the exact retrieval point

Not a plan — it ships in the CLI today

Chunking and embedding are built in. jdf chunk reads JDF's heading hierarchy for section-aware chunks (tables serialized as Header: value rows, so column meaning survives). jdf embed turns them into vectors — locally via Ollama by default, so no data leaves your machine — and --incremental re-embeds only the chunks whose content hash changed. Edit one paragraph in a 500-page doc → one embedding call, not five hundred.

# deterministic, offline — same doc → byte-identical chunks & stable hashes
$ jdf chunk report.jdf
  {"id":"p3e7","text":"…","path":["Report","Pricing"],"page":3,"types":["text","table"],"tokens":142,"hash":"ab12cd"}

# local embeddings; Ollama auto-starts via Docker if needed; skips unchanged chunks
$ jdf embed report.jdf --incremental   # → report.embeddings.json
Benchmarks coming. The wins above are structural — pipeline stages JDF removes entirely — not measured timings. We're running benchmarks on a public corpus (academic PDFs, financial filings, scanned reports) and will publish parse / chunk / embed / retrieval numbers here as soon as they're ready. If you run a comparison on your own corpus, please open an issue — we'd like to include it.

The endgame — documents stop being storage, start being interfaces

If an AI-native document format becomes the default, documents themselves become a base layer for knowledge — not files you store, but interfaces software talks to.

01

Documents stop being “static files”

A .jdf is a live data structure. Every paragraph, table, and figure has a stable identity that other systems can reference, query, and react to.

02

Everything becomes machine-readable by default

No more PDF parsers, no more layout heuristics, no more OCR fallbacks. The structure is in the file — every tool reads the same tree.

03

AI search becomes near-instant and accurate

Retrieval skips the parse-and-reconstruct stage. An embedding pipeline ingests the document tree directly. The cited span is a real element, not a guess.

04

PDFs fade out like fax

The way fax gave way to email and email to chat, the binary print-archive format gives way to the format every machine and model already speaks.

05

Every app plugs into the same structure

One schema, every consumer. Editors, viewers, summarisers, exporters, agents — all working off the same tree, no glue code per format.

The shift

Instead of “AI trying to understand humans’ files”, humans start writing in a format already built for AI reasoning.

What that unlocks

  • Education content becomes instantly interactive — every figure, table, and term is addressable.
  • Legal & finance documents become queryable in real time — clause-level retrieval, not page-level grep.
  • Knowledge graphs form automatically — the structure is already there, no extraction stage.
  • Every system becomes interoperable by default — one format, every tool, no adapters.

The real endgame: documents stop being storage → they become interfaces.

The CLI is how PDFs and AI plug in

Three unattended workflows the desktop app can't cover: legacy PDFs entering a pipeline, LLM-emitted JSON becoming a renderable document, and RAG ingestion — chunking and embedding, incrementally. All run in CI, all validate against the schema, all fail loudly on malformed input.

PDF → JDF

Headless PDF ingestion

Same algorithm the desktop reader uses, packaged as a node CLI. Run it on a build server, in a Lambda, in a CI step — output is the structured tree your retriever wants instead of `pdfplumber` heuristics.

# 1360-page AWS API spec → 60k structured elements in 90s
$ jdf convert partner-api.pdf -o api.jdf --json
$ jdf validate api.jdf  # schema gate — exit 1 fails CI
JSON → JDF

Wrap LLM output

Models emit JSON. Hand them the JDF schema, drop the response into the CLI, and you get a validated `.jdf` you can render, ship, or audit. Three input shapes are accepted: a full document, a bare element array, or a partial.

# model output → validated, renderable document
$ openai-cli generate > report.json
$ jdf convert report.json -o report.jdf
# exit 1 if the model violated the schema
JDF → RAG

Chunk & embed, incrementally

JDF already carries the heading hierarchy, so chunking reads structure instead of guessing. Deterministic chunks → stable hashes → --incremental re-embeds only what changed. Embed locally via Ollama (no data leaves the machine) or a remote API.

# section-aware chunks (tables kept as Header: value)
$ jdf chunk report.jdf  # → report.chunks.jsonl
# local embeddings, skip unchanged chunks
$ jdf embed report.jdf --incremental

Drop-in CI gate

One step in your workflow rejects malformed JSON or PDFs with weird structure before they reach production:

- name: Convert & validate generated document
  run: |
    npx @uurtech/jdf-cli convert dist/output.json -o dist/output.jdf
    npx @uurtech/jdf-cli validate dist/output.jdf

CLI reference →

JDF Forms — fillable, downloadable, machine-readable

Embed a `.jdf` form on any web page, let users fill it, click Save, and they get the same `.jdf` back with their answers baked in. The downloaded file is still pure JSON — your backend, your RAG, your audit log all read the same tree.

1 · Author the form

One JSON document. type: "input" | "textarea" | "checkbox" | "select" | "signature". Each field has a stable name — the value lookup key. Same schema validates, same CLI imports, same TOC and search work.

2 · Embed it on your site

One tag. save-button attribute drops a button in the corner. The user fills the form right in the browser; jdf.js mutates the in-memory document on every keystroke.

3 · User clicks Save

Browser downloads customer-form.jdf. Open it in any text editor — values are inline. Re-open it in jdf.js and the form starts pre-filled. Convert it to PDF with jdf export; pipe it to your RAG with jdf validate.

<link rel="stylesheet" href="https://unpkg.com/@uurtech/jdf@latest/dist/jdfjs.css">
<script type="module" src="https://unpkg.com/@uurtech/jdf@latest"></script>

<jdf src="customer-form.jdf"
     save-button="Save form"
     save-filename="filled-form.jdf"
     height="640"></jdf>
↓ Live form — fill and click Save to download

JDF Forms — full guide →

What a JDF file looks like

{
  "$jdf": "1.0.0",
  "meta": { "title": "Hello", "pageSize": "A4" },
  "styles": {
    "h1": { "fontSize": 22, "fontWeight": "bold" }
  },
  "pages": [{
    "elements": [
      { "type": "text", "content": "Hello, JDF",
        "heading": 1, "style": "h1",
        "position": { "x": 0, "y": 5 },
        "width": 166 },
      { "type": "list", "listType": "unordered",
        "items": [
          { "content": "Just JSON" },
          { "content": "Diffable" },
          { "content": "Editable anywhere" }
        ],
        "position": { "x": 0, "y": 25 }, "width": 166 }
    ]
  }]
}

Hello, JDF

  • Just JSON
  • Diffable
  • Editable anywhere
→ rendered

Features

Edit in place

Double-click any paragraph, heading, list item, table cell, image — inline editor opens for that element only. Enter commits. Auto-saves to disk in 150ms.

Restructure on hover

Mouse over any element → floating bar appears with ↑ Move up · ↓ Move down · ⧉ Duplicate · × Delete. No right-click, no menu hunting.

Insert anything

Insert bar at the top of every page. Click to add: text, rich text, list, table, shape, image, collapsible section, auto-generated TOC.

Live JSON view

Toggle View ↔ JSON. Two-way bound. Edit JSON directly with Cmd+S — visual render follows. Edit visually — JSON updates live.

PDF import — full fidelity

Drag a PDF on the viewer. Every text run keeps its position, font, weight, color, opacity, link. Embedded images extracted. Vector shapes (rect/line/path) preserved with fills and strokes. Looks identical to the original — and it's editable.

PDF export

Round-trip back to .pdf. Honors page size, orientation, text colors, real TOC, embedded images. Renders text/list/table/collapsible/shape.

Markdown viewer

Open .md for a continuous-scroll, GitHub-style render with full GFM. Toggle to paged JDF view. Cmd+F highlights matches inline.

Undo / redo · multi-window

⌘Z · ⌘⇧Z — 100-step history covering every text edit, structural change, and JSON commit. ⌘N for a new window — compare two documents side-by-side.

JSON Schema + CLI

Draft-07 schema. jdf validate file.jdf reports path-level errors with Ajv. CI on three OSes (macOS, Linux, Windows) on every PR.

PDF vs JDF

PDF
JDF
Format
Binary blob
Human-readable JSON
Open with
Adobe Reader (~250 MB)
Any text editor or JDF Reader (~14 MB)
Edit a typo
Acrobat or Word roundtrip
Double-click → type → done
Auto-save while editing
✓ debounced, with status indicator
Raw view
Live two-way JSON view
git diff
Meaningless binary
Clean, line-level
Generate from code
reportlab, pdfkit, weeks
JSON.stringify(doc)
Schema validation
None
JSON Schema + Ajv CLI
Search programmatically
Custom binary parsing
grep, jq, ripgrep
Vendor lock-in
Adobe
None — it's JSON

Also runs on the web — jdf.js

A small JavaScript library that turns any .jdf URL into a fully styled, embedded viewer in your web page. Drop in one tag and you're done.

<link rel="stylesheet" href="https://unpkg.com/@uurtech/jdf@0.1.23/dist/jdfjs.css">
<script type="module" src="https://unpkg.com/@uurtech/jdf@0.1.23"></script>

<jdf src="examples/article.jdf"></jdf>
↓ The tag above, live on this page

Embed docs → Full documentation →

Format

Every JDF document has these top-level fields:

FieldRequiredWhat it does
$jdfyesFormat version (semver)
metayestitle, author, page size, margins, language
stylesnoReusable named style definitions
resourcesnoEmbedded fonts and base64 images
header · footernoRepeating header/footer with template vars or full element trees
pagesyesArray of pages, each with its own elements

Install

Desktop · macOS

brew tap uurtech/jdf
brew install jdf

Upgrade later with brew upgrade --cask jdf. Homebrew strips the macOS quarantine attribute automatically — the app launches on first run.

No Homebrew? Download the .dmg from the latest release, drag JDF Reader.app into /Applications, then either right-click → Open once, or run:

xattr -cr "/Applications/JDF Reader.app"
open "/Applications/JDF Reader.app"

The build is unsigned — these commands clear the macOS quarantine flag added to downloads. The app itself is unchanged.

Web · jdf.js

npm install @uurtech/jdf

Or via CDN. See embed docs →

Linux / Windows

Grab a build from the latest release.deb, .AppImage, .rpm, .msi, .exe all built by GitHub Actions on every tag.

From source

git clone https://github.com/uurtech/jdf.git
cd jdf
pnpm install
pnpm tauri build

Requires Node 20+, pnpm 9+, Rust stable.