A letter of intent (LOI) is a good fit for template-based generation: the wording stays the same across every deal, and only a handful of fields change (the parties, the date, the purchase price, and the key terms). Store the LOI once as a template with placeholders, merge in your data at runtime, and render the result to a PDF.
Generate the LOI PDF from an HTML template
import Anvil from '@anvilco/anvil'
import fs from 'fs'
const anvilClient = new Anvil({ apiKey: process.env.ANVIL_API_KEY })
// Your LOI template. Placeholders are filled from `loi` at runtime.
function buildLoiHtml (loi) {
return `
<h1>Letter of Intent</h1>
<p>Date: ${loi.date}</p>
<p>This Letter of Intent is made between ${loi.buyer}
("Buyer") and ${loi.seller} ("Seller").</p>
<p>Buyer intends to purchase ${loi.asset} for a total purchase
price of ${loi.price}, subject to the terms below.</p>
<p>This letter expresses the parties' intent and is non-binding,
except for the Confidentiality and Exclusivity sections,
which are binding.</p>
`
}
const payload = {
title: 'Letter of Intent',
type: 'html',
data: {
html: buildLoiHtml({
date: 'July 3, 2026',
buyer: 'Acme Holdings LLC',
seller: 'Northwind Trading Co.',
asset: 'the assets of the Widget division',
price: '$1,250,000',
}),
css: 'body { font-family: sans-serif; font-size: 12px; }',
},
}
const { statusCode, data } = await anvilClient.generatePDF(payload)
if (statusCode === 200) {
fs.writeFileSync('letter-of-intent.pdf', data, { encoding: null })
}The generatePDF call sends your merged HTML to Anvil's PDF generation API and returns the finished PDF as binary data, which you write straight to disk (saving with no encoding, so the file is not corrupted). Swap the placeholder values for records from your CRM or database and you produce a correctly worded LOI on every request. The same template and merge pattern works with any HTML to PDF renderer; only the render call changes.
Keep binding and non-binding wording in the template
An LOI usually mixes non-binding intent with a few clauses that are meant to bind (commonly confidentiality, exclusivity or no-shop, and governing law). Keep that language in the template itself, not in per-request data, so every generated document carries the exact reviewed wording. Treat the merged fields as data only: names, dates, and amounts. That keeps legal review focused on the template rather than on each file you generate.
Two practical notes. Requests made with a development API key are free but stamp a watermark on the output, so use your production key for real documents. And PDF generation is billed per document (on Anvil, $0.10 per PDF fill or generation over the API, with 2,500 free starter credits), so batch or cache where it makes sense.
Back to All Questions