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:
- Contract Templates — Reusable, versioned agreement templates managed by admins
- PDF Generator — Server-side PDFKit renderer that merges template content with live tenancy data
- 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:
| Type | Description |
|---|---|
ast | Assured Shorthold Tenancy |
non_ast | Non-AST agreements |
company_let | Tenancies where the tenant is a company |
hmo | House 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:
| Placeholder | Value |
|---|---|
{{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
| Status | Meaning |
|---|---|
generating | PDF render and upload in progress |
completed | PDF successfully generated and stored |
superseded | Replaced by a newer version of the agreement |
failed | Generation 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.
| Procedure | Type | Auth | Description |
|---|---|---|---|
getTemplateTypes | query | user | List available template types with labels |
list | query | user | List templates (filterable by type/status) |
getById | query | user | Get full template content by ID |
create | mutation | admin | Create a new template |
update | mutation | admin | Update template (auto-increments version) |
archive | mutation | admin | Soft-delete a template |
generateAgreement | mutation | user | Generate a PDF for a tenancy term |
listGenerated | query | user | List generated documents |
getGenerated | query | user | Get a single generated document |
markSentForSigning | mutation | user | Link to a Signable envelope |
supersede | mutation | user | Mark a document as superseded |