Building Court-Ready PDFs: What Legal Tech Taught Me About Deterministic Software
Most AI products are allowed to be a little unpredictable. A generated illustration can look different each time. A marketing draft can choose a different adjective. Even a coding assistant can offer several valid implementations. Court documents are different. A filing can contain a persuasive argument and still create problems because it uses the wrong margins, caption structure, line spacing, page numbering, or pleading-paper format. While building Pleadwise, a court-document formatting tool for self-represented litigants, I learned that legal technology needs a different architecture from the typical βsend everything to an LLMβ product. The most important lesson was simple: Use AI where variation is useful. Use deterministic software where precision is mandatory.
The document is structured data
A court filing may look like a long block of text, but treating it as one string quickly becomes painful. It is better represented as a collection of fields:
const filing = {
court: {
jurisdiction: "federal",
district: "northern-district-of-california"
},
case: {
number: "3:26-cv-00000",
plaintiff: "Example Plaintiff",
defendant: "Example Defendant"
},
document: {
type: "motion",
title: "Motion to Dismiss",
body: [],
signature: {}
}
};
This separation makes validation possible before a PDF is generated. For example:
function validateCaption(filing) {
const errors = [];
if (!filing.case.number) {
errors.push("A case number is required.");
}
if (!filing.case.plaintiff || !filing.case.defendant) {
errors.push("Both parties must appear in the caption.");
}
return errors;
}
Structured input also makes it easier to change renderers later. The same case data could eventually produce a PDF, editable source file, filing checklist, or accessible HTML preview. The renderer should not need to rediscover what every paragraph means.
Court rules belong in a rules engine
Formatting rules vary between courts. A static Word template cannot reliably represent all those differences. A more scalable approach is to store formatting requirements as data:
const courtProfile = {
id: "example-district",
typography: {
fontFamily: "Times New Roman",
minimumFontSize: 12,
lineSpacing: 2
},
page: {
marginTop: 1,
marginBottom: 1,
marginLeft: 1,
marginRight: 1
},
caption: {
requiresAttorneyBlock: true,
requiresCaseNumber: true
}
};
The rendering layer can then consume the selected court profile:
function buildDocument(filing, courtProfile) {
return renderPdf({
content: filing.document,
caption: filing.case,
styles: courtProfile.typography,
pageLayout: courtProfile.page
});
}
This gives the application one source of truth for each supported court. It also creates an important operational benefit: when a court changes a requirement, the rule can be updated without rebuilding the entire document workflow.
Of course, court rules are not ordinary configuration. They need sources, review dates, and change tracking. A production rule record should include metadata such as:
{
"sourceUrl": "https://court.example.gov/local-rules",
"reviewedAt": "2026-07-01",
"effectiveAt": "2026-06-15",
"reviewedBy": "human-reviewer"
}
That provenance is as important as the rule itself.
AI should not control the final layout
AI can help organize user-provided facts, explain unfamiliar terminology, or create a first draft for review. It should not decide whether a document needs numbered pleading paper or whether a caption fits the selected court. Those are deterministic questions. In Pleadwise, guided drafting and source-linked case-law search are treated as separate tools. Generated legal content must still be reviewed, and citations must be checked against their original sources.
The final formatting pipeline should behave more like a compiler:
Structured input
β
Validation
β
Court-rule lookup
β
Document assembly
β
PDF compilation
β
Preflight checks
β
User review
Given the same input and rule version, the system should produce the same result. That predictability makes testing and debugging dramatically easier.
Build a preflight checker
A useful document generator should inspect its own output. Depending on the format, preflight checks could verify:
- Required caption fields are present
- Page margins match the selected court profile
- Font sizes meet the configured minimum
- Page numbers appear in the expected location
- No placeholder text remains
- Signature information is included
- Content does not overflow the printable area
- A PDF was generated successfully and can be opened
Some checks can happen before rendering. Others require inspecting the generated artifact.
async function preflight(document) {
return {
missingFields: checkRequiredFields(document),
unresolvedTokens: findTokens(document, /\{\{.+?\}\}/g),
layoutWarnings: await inspectRenderedPages(document),
passed: true
};
}
Failure states deserve first-class design
PDF compilation can fail. A user can close the browser midway through a workflow. A network request can time out after payment but before the document appears. These are not edge cases when the person using the product may have an approaching filing deadline.
A resilient workflow should:
- Save structured progress before starting generation.
- Make generation jobs safe to retry.
- Avoid charging twice for the same request.
- Return a document credit when generation fails.
- Show a useful error instead of a generic failure message.
- Keep enough diagnostic information to reproduce the problem without unnecessarily exposing sensitive document content.
The unhappy path is part of the product.
Privacy changes the architecture
Legal documents can contain names, addresses, financial information, medical details, and allegations that users never intended to make broadly accessible. That means document storage cannot be treated like a folder of ordinary downloadable assets. Useful safeguards include:
- Private storage by default
- Short-lived signed download links
- Strict authorization checks
- Minimal retention
- Clear deletion behavior
- Limited logging of document contents
- Sending only necessary context to external AI providers
Security is not a feature that can be added after the document pipeline is finished. It shapes the pipeline.
What I would tell other developers
If you are building software for law, finance, healthcare, compliance, or another rules-heavy field, start by separating three concerns:
- User-provided facts
- Generated or suggested content
- Authoritative formatting and validation rules
Do not let probabilistic output silently cross into the authoritative layer. AI is excellent at helping people move from a blank page to a structured draft. Deterministic software is excellent at applying known constraints repeatedly. The strongest products use both-and make the boundary visible.
That is the approach behind Pleadwise: guided drafting where it helps, deterministic court formatting where consistency matters, and explicit user review before anything is filed. The interesting engineering challenge is not getting an AI model to produce more text. It is building a system people can inspect, correct, and trust.
Disclosure: Iβm building Pleadwise. It is a document-formatting and drafting tool, not a law firm, and it does not provide legal advice.
Comments
No comments yet. Start the discussion.