Ask Anvil

Answers to questions about automating PDFs, e-signatures, Webforms, and other paperwork problems.
PDFs
Categories

How do I generate NDAs programmatically from a template?

The standard pattern

You need three pieces: a template with named placeholders, an input record per NDA, and a renderer that turns the merged result into a PDF. Production NDA generators almost always look like that under the hood. What varies is whether the template lives as a string in code, a .docx file, or a tagged PDF that a non-engineer can edit.

A minimal Python example

from jinja2 import Template

NDA_TEMPLATE = """
NON-DISCLOSURE AGREEMENT

This NDA is entered into between {{ disclosing_party }} ("Disclosing Party")
and {{ receiving_party }} ("Receiving Party") on {{ effective_date }}.

1. Confidential Information. Information disclosed by the Disclosing Party
   in connection with {{ purpose }} is "Confidential Information."

2. Term. The Receiving Party's obligations continue for {{ term_years }}
   years from the Effective Date.

3. Governing Law. This Agreement is governed by the laws of {{ jurisdiction }}.

Signed:
_____________________      _____________________
{{ disclosing_party }}                  {{ receiving_party }}
"""

record = {
    "disclosing_party": "Acme Corp",
    "receiving_party": "Beta LLC",
    "effective_date": "2026-05-11",
    "purpose": "evaluating a partnership",
    "term_years": 3,
    "jurisdiction": "Delaware",
}

print(Template(NDA_TEMPLATE).render(**record))

Feed records from a CSV, a CRM export, or your database in a loop and you have bulk NDA generation in roughly twenty lines.

Going from text to PDF

Two common renders. For a one-off, pipe the rendered HTML through WeasyPrint:

from weasyprint import HTML

rendered = Template(NDA_TEMPLATE).render(**record)
HTML(string=f"<pre>{rendered}</pre>").write_pdf("nda.pdf")

When legal needs to own the layout, a tagged PDF plus a PDF fill API is the cleaner path. Anvil's PDF Filling API (the @anvilco/anvil package, fillPDF method) is $0.10 per fill on metered usage with 500 free fills per month as of May 2026:

import fs from 'fs'
import Anvil from '@anvilco/anvil'

const anvilClient = new Anvil({ apiKey: process.env.ANVIL_API_KEY })

const payload = {
  title: 'NDA - Acme / Beta',
  data: {
    disclosingParty: 'Acme Corp',
    receivingParty: 'Beta LLC',
    effectiveDate: '2026-05-11',
    termYears: 3,
    jurisdiction: 'Delaware',
  },
}

const { statusCode, data } = await anvilClient.fillPDF('YOUR_TEMPLATE_EID', payload)
if (statusCode === 200) fs.writeFileSync('nda.pdf', data, { encoding: null })

Replace YOUR_TEMPLATE_EID with the template ID from the dashboard. The keys in data match the field IDs you set when tagging the PDF.

Two gotchas

A literal {{disclosing_party}} in your output means the template engine never ran on that section, not that the data was missing. Jinja2, Handlebars, and Mustache all emit empty space (not the raw tag) when a key is undefined, so a visible {{tag}} usually means the file was served before substitution, or the delimiters do not match the engine.

And "generate programmatically" is not the same as "have legal review." Templates are fine for the structural fields (parties, term, jurisdiction). Anything substantive (trade-secret carve-outs, IP assignment, residual-knowledge clauses, jurisdiction-specific defaults) belongs in a clause library that counsel has signed off on, not in the rendering layer.

Back to All Questions

The fastest way to build software for documents

Anvil Document SDK is a comprehensive toolbox for product teams launching document flows where PDF filling, signing, and complex conditional scenarios are necessary.
Explore Anvil
Anvil Webforms