Skip to main content
All Docs
FeaturesPurple PepperUpdated April 7, 2026

Tenancy Agreement PDF Generation

Tenancy Agreement PDF Generation

Purple Pepper can generate fully branded, legally structured tenancy agreement PDFs entirely on the server — no headless browser or external rendering service required. The pipeline covers template management, PDF generation, cloud storage upload, and eSigning dispatch.

Overview

The pipeline consists of three main components:

  1. Contract Templates — Reusable, versioned agreement templates managed by admins
  2. PDF Generator — Server-side PDFKit renderer that merges template content with live tenancy data
  3. Storage & eSigning — Direct S3/Tigris upload and optional Signable envelope linking

Contract Templates

Contract templates are org-scoped and define the structure and content of tenancy agreements. Each template has a type corresponding to the tenancy arrangement:

TypeDescription
astAssured Shorthold Tenancy
non_astNon-AST agreements
company_letTenancies where the tenant is a company
hmoHouse in Multiple Occupation

Each template includes:

  • Standard clauses with placeholder support
  • Special conditions (templated and custom)
  • Prescribed information sections (Housing Act 2004)
  • Rent schedule configuration
  • Break clause settings
  • Deposit protection information

Managing Templates

Template management is available to admins only via the contractTemplate tRPC router.

Create a template

trpc.contractTemplate.create.mutate({
  name: "Standard AST 2024",
  type: "ast",
  clauses: [ /* ... */ ],
  specialConditions: [ /* ... */ ],
  prescribedInformation: { /* ... */ },
  isDefault: true,
});

List templates

trpc.contractTemplate.list.query({
  type: "ast",    // optional filter
  status: "active" // optional filter
});

Update a template

Updates automatically increment the template version number.

trpc.contractTemplate.update.mutate({
  id: "<template-id>",
  clauses: [ /* updated clauses */ ],
});

Archive a template

trpc.contractTemplate.archive.mutate({ id: "<template-id>" });

Generating a Tenancy Agreement PDF

Call generateAgreement with the tenancy term ID and template ID. The router automatically collects tenancy, property, landlord, company settings, and branding data, merges them into the template, and renders the PDF.

const result = await trpc.contractTemplate.generateAgreement.mutate({
  tenancyTermId: "<tenancy-term-id>",
  templateId: "<template-id>",
});
// result.documentId — ID of the created tenancy_term_documents record
// result.storageUrl — URL of the uploaded PDF

What the PDF contains

  • Agency branding — Company name and primary colour
  • Property details — Address and description
  • Tenancy term details — Start/end dates, rent amount, payment schedule
  • Tenant names — All tenants on the agreement
  • Landlord details — Name and contact information
  • Standard clauses — From the template, with placeholder values substituted
  • Special conditions — Templated and custom
  • Prescribed information — As required by the Housing Act 2004
  • Break clause — If applicable
  • Deposit protection information
  • Regulatory footer
  • Signature blocks — For tenants and landlord

Placeholder system

Template clauses support the following placeholders, which are replaced with live data at generation time:

PlaceholderValue
{{PROPERTY_ADDRESS}}Full property address
{{LANDLORD_NAME}}Landlord full name or company name
{{TENANT_NAMES}}Comma-separated list of tenant names
{{MONTHLY_RENT}}Monthly rent amount (formatted)

Storage

Generated PDFs are uploaded directly from the server to S3 or Tigris (not via browser presigned URLs). The generated document is then recorded in the tenancy_term_documents table with type tenancy_agreement, making it immediately available in the document management system.

Development fallback

If no storage provider is configured, a warning is logged and generation continues without uploading. This allows the pipeline to run in local development without cloud credentials.


eSigning Integration

Once a PDF has been generated and sent for signing via Signable, link the generated document to its envelope:

await trpc.contractTemplate.markSentForSigning.mutate({
  generatedDocumentId: "<document-id>",
  signableEnvelopeId: "<envelope-id>",
});

To mark a document as superseded by a newer version:

await trpc.contractTemplate.supersede.mutate({
  generatedDocumentId: "<old-document-id>",
});

Document Status Lifecycle

Each generated document moves through the following statuses:

generating → completed → superseded
                      ↘ failed
StatusMeaning
generatingPDF render and upload in progress
completedPDF successfully generated and stored
supersededReplaced by a newer version of the agreement
failedGeneration or upload encountered an error

Audit Trail

Every generation stores a data snapshot alongside the generated document record. This snapshot captures the tenancy, property, landlord, and template data at the time of generation, enabling compliance audits and regeneration of historical documents.


tRPC Router Reference

All procedures are available under contractTemplate.

ProcedureTypeAuthDescription
getTemplateTypesqueryuserList available template types with labels
listqueryuserList templates (filterable by type/status)
getByIdqueryuserGet full template content by ID
createmutationadminCreate a new template
updatemutationadminUpdate template (auto-increments version)
archivemutationadminSoft-delete a template
generateAgreementmutationuserGenerate a PDF for a tenancy term
listGeneratedqueryuserList generated documents
getGeneratedqueryuserGet a single generated document
markSentForSigningmutationuserLink to a Signable envelope
supersedemutationuserMark a document as superseded