# index.html.md
# typy
Build polished PDFs from Python and Markdown
typy combines a typed Python API, reusable Typst templates, and a CLI designed for both developers and AI agents.
Get started
Open cookbook
## Quick start
```bash
typy scaffold report --output data.json
typy render --template report --data data.json --output report.pdf
```
## Explore
Getting started
Install typy and render your first document in minutes.
Read guide
Cookbook
11 runnable examples from invoices to presentations and batch generation.
View recipes
Template reference
Inspect built-in templates and field schemas for each document type.
Browse templates
CLI reference
Command-by-command guidance and options for automation-friendly usage.
Open CLI docs
API reference
Module-level API pages generated from source and type signatures.
Read API
Package format
RFC: .typy container structure, manifest v1 schema, versioning policy, and error model.
Read RFC
LLM resources
Machine-oriented entry points and generated llms.txt context files.
Open LLM page
# index.html.md
# API reference
| Module | Description |
|-----------------------------------|--------------------------------------------------------------------|
| [builder](builder.md) | `DocumentBuilder` — compose and render documents |
| [templates](templates.md) | Built-in template classes (`ReportTemplate`, `InvoiceTemplate`, …) |
| [content](content.md) | `Content` block and list helpers |
| [markup](markup.md) | Inline markup helpers (`Text`, `Bold`, `Italic`, …) |
| [functions](functions.md) | Helper functions for building document data |
| [typst_encoder](typst_encoder.md) | Internal Typst serialisation layer |
# builder.html.md
# builder
### *class* typy.builder.DocumentBuilder(verbose=False)
Bases: `object`
* **Parameters:**
**verbose** (*bool*)
#### add_template(template)
* **Parameters:**
**template** ([*Template*](templates.md#typy.templates.Template))
#### get_template_data_model()
* **Return type:**
*Type*[[*Template*](templates.md#typy.templates.Template)]
#### add_data(data)
* **Parameters:**
**data** (*dict* *|* [*Template*](templates.md#typy.templates.Template))
#### add_typ_template(typ_path, data=None)
Add a raw Typst .typ template file with optional JSON-serialisable data dict.
* **Parameters:**
* **typ_path** (*Path*) – Path to the .typ Typst template file to render.
* **data** (*dict* *|* *None*) – Optional dictionary of template variables. When provided, the
data is encoded and written to `typy_data.typ` in the build
directory so the template can import it.
* **Returns:**
self – enables method chaining.
#### copy_assets_from(source_dir)
Copy files from source_dir into the build temp dir.
This makes relative paths (e.g. `assets/image.png`) in the document
resolvable during Typst compilation, which otherwise runs in an isolated
temporary directory.
Files named `main.typ`, `typy_data.typ`, and `typy.typ` are
intentionally excluded so the generated build files are not overwritten.
* **Parameters:**
**source_dir** (*Path*) – Directory whose contents are copied to the build dir.
* **Returns:**
self – enables method chaining.
* **Return type:**
[*DocumentBuilder*](#typy.builder.DocumentBuilder)
#### add_file(filepath)
* **Parameters:**
**filepath** (*Path*)
* **Return type:**
*Path*
#### compile(typ_file, output)
* **Parameters:**
* **typ_file** (*Path*)
* **output** (*Path*)
#### save_pdf(filepath)
* **Parameters:**
**filepath** (*Path*)
#### to_buffer()
* **Return type:**
*BytesIO*
#### save_source(dirpath)
* **Parameters:**
**dirpath** (*Path*)
# cli.html.md
# CLI reference
The CLI is workflow-oriented and supports direct discovery through `--help`.
## Commands
### list
List built-in templates.
```bash
typy list
```
### info
Inspect template schema.
```bash
typy info report
typy info report --json
```
### scaffold
Generate sample JSON input for a template.
```bash
typy scaffold report --output data.json
```
### render
Render PDF from template data, Markdown, or both.
```bash
typy render --template report --data data.json --output report.pdf
typy render --markdown notes.md --output notes.pdf
typy render --template report --markdown body.md --output report.pdf
```
Constraints:
- At least one of `--template` or `--markdown` is required.
- If `--template` is provided without `--data` and without `--markdown`, typy auto-generates sample data for preview rendering.
### package
Manage `.typy` template packages. Run `typy package --help` for details.
#### package validate
Validate the structure and manifest of a `.typy` package.
```bash
typy package validate my-template.typy
```
Reports all errors in a single pass so they can be fixed together.
#### package export
Export a template as a self-contained `.typy` package archive.
```bash
typy package export template.py \
--manifest manifest.json \
--output my-template.typy
```
Optional flags:
| Flag | Description |
|-------------------|-------------------------------------------------------------------------|
| `--assets
` | Bundle a directory of static assets (images, fonts, …) under `assets/`. |
| `--readme ` | Bundle a README file as `README.md`. |
Minimal `manifest.json`:
```json
{
"manifest_version": 1,
"name": "my-report",
"version": "1.0.0",
"description": "A general-purpose report template.",
"author": "Jane Doe ",
"typy_compatibility": ">=0.1.0"
}
```
See [package-format.md](package-format.md) for the full manifest schema.
#### package install
Validate and install a `.typy` package into the local template store
(`~/.typy/packages/` by default).
```bash
typy package install my-template.typy
typy package install my-template.typy --store /path/to/store
typy package install my-template.typy --force # overwrite existing
```
After installation the package is unpacked to
`///` and can be used by name in `typy render`:
```bash
typy render --template my-report --data data.json --output report.pdf
```
## Typical package workflow
```bash
# 1. Author creates a template and writes a manifest
typy scaffold report --output data.json # optional: inspect field schema
cat manifest.json # verify manifest fields
# 2. Export as a distributable package
typy package export template.py \
--manifest manifest.json \
--output my-report.typy
# 3. Validate before distribution
typy package validate my-report.typy
# --- Option A: render directly from the .typy file (no install needed) ---
typy render --template my-report.typy --data data.json --output report.pdf
# --- Option B: install once, then use by name like a built-in template ---
typy package install my-report.typy
typy render --template my-report --data data.json --output report.pdf
```
# content.html.md
# content
### *class* typy.content.Content(content, \*\*kwargs)
Bases: `Encodable`
#### content_item_encode(item)
#### encode()
# cookbook.html.md
# Cookbook
Each entry is designed so an LLM can copy, adapt, and run it directly.
Every recipe includes a clear goal and complete code or commands.
## 1. Generate a report from a pandas DataFrame
Goal: render a report PDF containing a real table from analytics data.
**Prerequisites:** `pandas` must be installed. Run `pip install "typy[pandas]"` or `pip install pandas`.
```python
from datetime import date
import pandas as pd
from typy.builder import DocumentBuilder
from typy.content import Content
from typy.functions import Figure, Table
from typy.markup import Heading, Text
from typy.templates import ReportTemplate
df = pd.DataFrame(
{
"Region": ["EMEA", "AMER", "APAC"],
"Revenue": [1_200_000, 980_000, 1_450_000],
"GrowthPct": [8.1, 5.4, 11.3],
}
)
body = Content(
[
Heading(1, "Quarterly performance"),
Text("This report summarizes regional revenue and growth."),
Figure(Table(df.to_dict()), caption="Revenue by region"),
]
)
template = ReportTemplate(
title="Q1 revenue report",
subtitle="Regional breakdown",
author="Finance Analytics",
date=date.today().isoformat(),
body=body,
toc=True,
)
DocumentBuilder().add_template(template).save_pdf("q1-report.pdf")
```
## 2. Create an invoice from JSON data
Goal: render an invoice with line items loaded from a JSON payload.
```python
import json
from typy.builder import DocumentBuilder
from typy.templates import InvoiceTemplate
payload = {
"company_name": "Northwind Design LLC",
"company_address": "101 River St, Porto",
"client_name": "Contoso Retail",
"client_address": "25 Market Ave, Lisbon",
"invoice_number": "INV-2026-0411",
"date": "2026-04-11",
"due_date": "2026-05-11",
"tax_rate": 23.0,
"notes": "Thank you for your business.",
"items": [
{"description": "UI redesign", "quantity": 32, "unit_price": 75.0},
{"description": "Design system audit", "quantity": 12, "unit_price": 90.0},
],
}
with open("invoice.json", "w", encoding="utf-8") as f:
json.dump(payload, f, indent=2)
data = json.loads(open("invoice.json", encoding="utf-8").read())
template = InvoiceTemplate(**data)
DocumentBuilder().add_template(template).save_pdf("invoice.pdf")
```
## 3. Convert a Markdown file to a styled PDF
Goal: convert existing Markdown notes into a PDF without writing Python.
```bash
typy render --markdown README.md --output readme.pdf
```
## 4. Batch-generate certificates from a CSV
Goal: produce one PDF per person in a CSV list.
```python
import csv
from pathlib import Path
from typy.builder import DocumentBuilder
from typy.templates import BasicTemplate
Path("out").mkdir(exist_ok=True)
rows = [
{"name": "Alice Martins", "course": "Data Visualization"},
{"name": "Bruno Costa", "course": "Prompt Engineering"},
]
with open("certificates.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "course"])
writer.writeheader()
writer.writerows(rows)
with open("certificates.csv", encoding="utf-8") as f:
for row in csv.DictReader(f):
body = (
"# Certificate of Completion\n\n"
f"This certifies that **{row['name']}** completed "
f"**{row['course']}**."
)
template = BasicTemplate(
title="Certificate",
date="2026-04-11",
author="Training Team",
body=body,
)
filename = f"out/{row['name'].lower().replace(' ', '-')}.pdf"
DocumentBuilder().add_template(template).save_pdf(filename)
```
## 5. Build and render a custom template from scratch
Goal: use your own Typst template file with JSON data.
```bash
cat > custom.typ <<'TYP'
#import "typy.typ": init_typy
#import "typy_data.typ": typy_data
#let t = init_typy(typy_data)
= #t("title", "str")
#t("body", "content")
TYP
cat > custom-data.json <<'JSON'
{
"title": "Custom template demo",
"body": "## Hello\n\nRendered with a raw Typst template."
}
JSON
typy render --template custom.typ --data custom-data.json --output custom.pdf
```
## 6. Use typy only from the CLI
Goal: full CLI workflow from discovery to render.
```bash
typy list
typy info report --json
typy scaffold report --output data.json
typy render --template report --data data.json --output report.pdf
```
## 7. Use typy with an AI agent code-execution pattern
Goal: reliable command sequence for tools that can execute shell commands.
```bash
# Step 1: discover and inspect schema
typy list
typy info letter --json > schema-letter.json
# Step 2: produce data.json that matches required fields
cat > data-letter.json <<'JSON'
{
"sender_name": "Northwind Legal",
"sender_address": "101 River St, Porto",
"recipient_name": "Maria Silva",
"recipient_address": "12 Elm Road, Braga",
"date": "2026-04-11",
"subject": "Contract renewal",
"body": "Dear Maria,\n\nPlease find the updated renewal terms attached.",
"signature_name": "Pedro Azevedo"
}
JSON
# Step 3: render
typy render --template letter --data data-letter.json --output letter.pdf
```
## 8. Combine markdown with programmatic figures and tables
Goal: mix human-written markdown and generated visual content in one document.
```python
from typy.builder import DocumentBuilder
from typy.content import Content
from typy.functions import Figure, Table
from typy.markup import Markdown
from typy.templates import ReportTemplate
table_data = {
"Metric": {0: "Latency", 1: "Throughput"},
"Before": {0: "120ms", 1: "1000 req/s"},
"After": {0: "45ms", 1: "3200 req/s"},
}
body = Content(
[
Markdown("## Benchmark summary\n\nPerformance improved after optimization."),
Figure(Table(table_data), caption="Before vs after"),
]
)
template = ReportTemplate(
title="Performance benchmark",
author="Platform Team",
date="2026-04-11",
body=body,
)
DocumentBuilder().add_template(template).save_pdf("benchmark.pdf")
```
## 9. Generate a business letter from Python
Goal: create a formal letter programmatically.
```python
from typy.builder import DocumentBuilder
from typy.templates import LetterTemplate
template = LetterTemplate(
sender_name="Acme Procurement",
sender_address="44 Harbor Road, Porto",
recipient_name="Nova Supplies",
recipient_address="9 Cedar St, Faro",
date="2026-04-11",
subject="Request for quotation",
body="Dear team,\n\nPlease send a quote for 500 units of Model X by next Friday.",
closing="Best regards",
signature_name="Joana Pereira",
)
DocumentBuilder().add_template(template).save_pdf("rfq-letter.pdf")
```
## 10. Build a presentation with multiple slides
Goal: generate a slide deck PDF from structured slide content.
```python
from typy.builder import DocumentBuilder
from typy.templates import PresentationTemplate, Slide
slides = [
Slide(title="Context", body="## Current state\n\nUsage has grown 40% year over year."),
Slide(title="Plan", body="## Next steps\n\n- Stabilize API\n- Improve docs\n- Expand templates"),
]
template = PresentationTemplate(
title="Product update",
subtitle="Q2 planning",
author="Product Team",
date="2026-04-11",
slides=slides,
)
DocumentBuilder().add_template(template).save_pdf("product-update.pdf")
```
## 11. Render markdown into a report template body
Goal: keep metadata in JSON and body content in a separate Markdown file.
```bash
cat > report-data.json <<'JSON'
{
"title": "Research summary",
"author": "R&D Team",
"date": "2026-04-11",
"toc": true
}
JSON
cat > body.md <<'MD'
## Findings
The experiments show statistically significant improvements.
## Next actions
- Expand sample size
- Validate in production conditions
MD
typy render --template report --data report-data.json --markdown body.md --output research-summary.pdf
```
# functions.html.md
# functions
### *class* typy.functions.Function(name, content=None, \*\*kwargs)
Bases: `Encodable`
* **Parameters:**
**name** (*str*)
#### encode()
### *class* typy.functions.Block(content, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
**content** ([*Function*](#typy.functions.Function) *|* [*Markup*](markup.md#typy.markup.Markup) *|* [*Content*](content.md#typy.content.Content))
#### encode()
### *class* typy.functions.Figure(content, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
### *class* typy.functions.Image(src, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
**src** (*Path*)
### *class* typy.functions.Grid(content, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
**content** ([*Content*](content.md#typy.content.Content))
### *class* typy.functions.Columns(content, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
**content** ([*Content*](content.md#typy.content.Content))
### *class* typy.functions.Badge(label, fill=, stroke=, radius=, inset=, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
* **label** (*str*)
* **fill** ([*Raw*](markup.md#typy.markup.Raw))
* **stroke** ([*Raw*](markup.md#typy.markup.Raw))
* **radius** ([*Raw*](markup.md#typy.markup.Raw))
* **inset** ([*Raw*](markup.md#typy.markup.Raw))
### *class* typy.functions.Callout(content, title=None, fill=, stroke=, radius=, inset=, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
* **title** (*str* *|* *None*)
* **fill** ([*Raw*](markup.md#typy.markup.Raw))
* **stroke** ([*Raw*](markup.md#typy.markup.Raw))
* **radius** ([*Raw*](markup.md#typy.markup.Raw))
* **inset** ([*Raw*](markup.md#typy.markup.Raw))
### *class* typy.functions.Table(data, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
**data** (*dict*)
### *class* typy.functions.Datetime(date=None, \*\*kwargs)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
**date** (*datetime*)
### *class* typy.functions.Lorem(words=100)
Bases: [`Function`](#typy.functions.Function)
* **Parameters:**
**words** (*int*)
# getting-started.html.md
# Getting started
This page gets you from zero to your first PDF with both CLI and Python API.
## What typy produces
typy generates polished PDFs powered by Typst. All built-in templates share
a consistent visual identity: blue-600 (`#2563eb`) accent colour, clean
typographic hierarchy, and A4 page layouts. The presentation template uses
16:9 slides.
Run the example scripts under `examples/` to see each template in action,
or generate preview images with `python scripts/generate_previews.py`.
## 1. Install typy
```bash
pip install typy
# or
uv add typy
```
## 2. Render your first PDF with the CLI
```bash
typy scaffold report --output data.json
typy render --template report --data data.json --output first-report.pdf
```
## 3. Render Markdown directly
```bash
typy render --markdown README.md --output readme.pdf
```
## 4. Minimal Python example
```python
from typy.builder import DocumentBuilder
from typy.templates import BasicTemplate
template = BasicTemplate(
title="Hello typy",
date="2026-04-11",
author="Your Name",
body="## Welcome\n\nThis PDF was generated from Python.",
)
DocumentBuilder().add_template(template).save_pdf("hello-typy.pdf")
```
## 5. Understand what fields are required
```bash
typy info report --json
```
Use this JSON output as the source of truth before creating or validating `data.json`.
## Next
- Continue with complete examples in [cookbook](cookbook.md)
- Check built-in schemas in [template reference](templates.md)
- Explore modules in [API reference](api/index.md)
# llm.html.md
# LLM-oriented usage
Use these pages as machine-readable entry points.
## Core pages
- [Getting started](getting-started.md)
- [Cookbook](cookbook.md)
- [Template reference](templates.md)
- [CLI reference](cli.md)
- [API reference](api/index.md)
## Generated LLM artifacts
If these files are missing, rebuild docs with the docs dependency group enabled.
# markup.html.md
# markup
### *class* typy.markup.Markup(text)
Bases: `Encodable`, `ABC`
* **Parameters:**
**text** (*str*)
#### *abstract* encode()
### *class* typy.markup.Text(text)
Bases: [`Markup`](#typy.markup.Markup)
* **Parameters:**
**text** (*str*)
#### encode()
### *class* typy.markup.Raw(text)
Bases: [`Markup`](#typy.markup.Markup)
* **Parameters:**
**text** (*str*)
#### encode()
### *class* typy.markup.Heading(level, text)
Bases: [`Markup`](#typy.markup.Markup)
* **Parameters:**
* **level** (*int*)
* **text** (*str*)
#### encode()
### *class* typy.markup.Markdown(text)
Bases: [`Markup`](#typy.markup.Markup)
* **Parameters:**
**text** (*str*)
#### encode()
# package-format.html.md
# RFC: .typy template package format and manifest v1
This document defines the portable template package format used by typy.
A `.typy` file is a self-contained, distributable unit that bundles a Python `Template` class, the Typst files it references, and any associated assets.
## Overview
A `.typy` package is a **ZIP archive** with the `.typy` file extension.
The archive contains a mandatory `manifest.json` (the package descriptor) and a `template.py` file that defines the template data model as a subclass of `typy.templates.Template`.
Because it is plain ZIP, consumers can inspect and unpack it with any standard ZIP tool.
---
## Container structure
```default
my-template.typy ← ZIP archive with .typy extension
├── manifest.json ← REQUIRED: package descriptor (manifest v1)
├── template.py ← REQUIRED: Python file defining the Template subclass
├── templates/ ← REQUIRED: Typst files referenced by __template_path__ in template.py
│ └── template.typ
├── assets/ ← optional: images, fonts, or other static resources
│ ├── logo.png
│ └── font.ttf
└── README.md ← optional: human-readable notes for the package
```
### Required files
| File | Description |
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `manifest.json` | Package descriptor in JSON format conforming to manifest schema v1 (see below). |
| `template.py` | Python module containing exactly one `typy.templates.Template` subclass. The class defines the data model and sets `__template_name__` and `__template_path__`. `__template_path__` must be a path relative to the archive root pointing to the entry `.typ` file inside `templates/`. |
| `templates/` | Directory containing the Typst file(s) referenced by `__template_path__` in `template.py`. At least one `.typ` file must be present. |
### Optional files
| File/directory | Description |
|------------------|----------------------------------------------------------------------------------------------------------------------------------|
| `assets/` | Static resources (images, fonts, etc.) referenced by the template. Paths must be relative and must stay inside the archive root. |
| `README.md` | Package documentation for human readers. Shown by tools that display package info. |
### Path rules
- All paths inside the archive must be **relative** (no leading `/` or `..` traversal).
- `__template_path__` in `template.py` and any files it references **must not** escape the archive root via path traversal (`../`).
- File names are case-sensitive.
---
## Manifest schema v1
The `manifest.json` file is UTF-8-encoded JSON.
The `"manifest_version"` field determines which schema applies.
This document specifies manifest version **1**.
### Fields
| Field | Type | Required | Description |
|----------------------|------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `manifest_version` | integer | **yes** | Schema version. Must be `1` for this spec. |
| `name` | string | **yes** | Package identifier. Lowercase alphanumeric, hyphens allowed. Pattern: `^[a-z0-9][a-z0-9-]*[a-z0-9]$` (min length 2). |
| `version` | string | **yes** | [Semantic version](https://semver.org/) of this package, e.g. `"1.0.0"`. |
| `description` | string | **yes** | One-line human-readable summary of the template’s purpose. |
| `author` | string | **yes** | Author name, optionally with email in angle brackets, e.g. `"Jane Doe "`. |
| `typy_compatibility` | string | **yes** | PEP 440 version specifier for the typy version this package requires, e.g. `">=0.1.0"`. |
| `dependencies` | array of strings | no | List of other `.typy` package identifiers that must be installed before this package. Format for each entry: `"@"`. Defaults to `[]`. |
| `license` | string | no | SPDX license identifier, e.g. `"MIT"`. |
| `homepage` | string | no | URL for the package’s home page or repository. |
| `keywords` | array of strings | no | Tags for discovery. Each keyword: max 32 characters. |
### Example: minimal valid manifest
```json
{
"manifest_version": 1,
"name": "my-report",
"version": "1.0.0",
"description": "A general-purpose report template with cover page and TOC.",
"author": "Jane Doe ",
"typy_compatibility": ">=0.1.0"
}
```
### Example: full valid manifest
```json
{
"manifest_version": 1,
"name": "corporate-invoice",
"version": "2.1.0",
"description": "Professional invoice template with logo, line items, and tax calculation.",
"author": "Acme Corp ",
"typy_compatibility": ">=0.2.0,<1.0.0",
"dependencies": [
"acme-base@>=1.0.0"
],
"license": "MIT",
"homepage": "https://github.com/acme/typy-templates",
"keywords": ["invoice", "billing", "business"]
}
```
---
## Compatibility and versioning policy
### Manifest version
The `"manifest_version"` integer is the **primary compatibility signal** for readers.
| manifest_version | Status | Supported by |
|--------------------|-------------|--------------------|
| 1 | **Current** | This specification |
Future schemas increment `manifest_version`. A reader that does not recognise a `manifest_version` value **must** reject the package with a clear diagnostic (see [error model]()) rather than silently ignoring unknown fields.
### Forward compatibility within v1
- **New optional fields** may be added to manifest v1 in minor typy releases.
Readers encountering unknown fields **must** ignore them (be liberal in what they accept).
- **Removing or renaming** required fields, or changing the type of any existing field, requires a new `manifest_version`.
### Package versioning
Package authors use [Semantic Versioning](https://semver.org/) for `"version"`:
- **MAJOR** – incompatible API or field changes in the template.
- **MINOR** – new optional fields or backwards-compatible feature additions.
- **PATCH** – bug fixes or asset updates that do not alter the template interface.
### typy_compatibility
The `"typy_compatibility"` field uses [PEP 440](https://peps.python.org/pep-0440/#version-specifiers) version specifiers so that the installed typy version can be checked before importing a package.
Using a compatible-release specifier (`~=`) or an upper bound (`<`) is recommended when a package relies on behaviour introduced in a specific release.
---
## Error model for invalid packages
Errors are reported as structured diagnostics.
Each diagnostic has a **code**, a human-readable **message**, and an optional **hint** with a corrective action.
### Diagnostic structure
```json
{
"code": "PKG_E003",
"message": "manifest.json is missing required field: 'name'",
"hint": "Add a 'name' field using only lowercase letters, digits, and hyphens (e.g. 'my-template')."
}
```
### Error codes
| Code | Trigger condition | Example message |
|------------|--------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| `PKG_E001` | File is not a valid ZIP archive | `"Not a valid .typy package: file is not a ZIP archive."` |
| `PKG_E002` | `manifest.json` is absent from the archive root | `"manifest.json not found in package root."` |
| `PKG_E003` | `manifest.json` is not valid JSON | `"manifest.json contains invalid JSON: ."` |
| `PKG_E004` | `manifest_version` is missing | `"manifest.json is missing required field: 'manifest_version'."` |
| `PKG_E005` | `manifest_version` is not a recognised integer | `"Unsupported manifest_version: 99. This version of typy supports manifest_version 1."` |
| `PKG_E006` | A required field is absent | `"manifest.json is missing required field: ''."` |
| `PKG_E007` | A field has the wrong type | `"manifest.json field '' must be , got ."` |
| `PKG_E008` | `name` does not match the naming pattern | `"'name' must match ^[a-z0-9][a-z0-9-]*[a-z0-9]$, got ''."` |
| `PKG_E009` | `version` is not a valid semver string | `"'version' must be a valid semantic version (e.g. '1.0.0'), got ''."` |
| `PKG_E010` | `template.py` is absent from the archive root | `"template.py not found in package root. The Template subclass must be defined in template.py."` |
| `PKG_E011` | A path inside the archive escapes the root (`../`) | `"Unsafe path detected in archive: ''. Paths must not traverse outside the package root."` |
| `PKG_E012` | `typy_compatibility` is not a valid PEP 440 specifier | `"'typy_compatibility' is not a valid version specifier: ''."` |
| `PKG_E013` | Installed typy version does not satisfy `typy_compatibility` | `"Package requires typy but typy is installed."` |
Validators **must** collect all applicable errors before reporting, so that a user can fix all problems in one pass.
### Example: invalid manifest — multiple errors
```json
{
"manifest_version": 1,
"name": "My Template",
"version": "not-a-version",
"description": "A report template.",
"author": "Jane Doe",
"typy_compatibility": ">=0.1.0"
}
```
Expected diagnostics:
```default
PKG_E008 'name' must match ^[a-z0-9][a-z0-9-]*[a-z0-9]$, got 'My Template'.
Hint: Use only lowercase letters, digits, and hyphens (e.g. 'my-template').
PKG_E009 'version' must be a valid semantic version (e.g. '1.0.0'), got 'not-a-version'.
Hint: Use MAJOR.MINOR.PATCH format as defined at https://semver.org.
PKG_E010 template.py not found in package root. The Template subclass must be defined in template.py.
Hint: Add a template.py file to the archive root that defines a subclass of typy.templates.Template.
```
---
## Non-goals (MVP)
- Implementing `typy package export/import` commands — tracked in a separate issue.
- A remote registry or marketplace for packages.
- Package signing or tamper-detection.
- Dependency resolution or a lock-file mechanism.
---
## See also
- [Template reference](templates.md) — built-in templates and field schemas
- [CLI reference](cli.md) — available commands
- [Getting started](getting-started.md) — first-run walkthrough
# templates.html.md
# Template reference
typy ships seven built-in templates, all sharing a consistent colour palette
(blue-600 `#2563eb` accent, slate body text) and typographic family.
## Templates at a glance
| report | invoice | letter |
|----------------------------------------------|--------------------------------------------------|----------------------------------------------------------|
| []() | []() | []() |
| **cv** | **academic** | **presentation** |
| []() | []() | []() |
| Template | Best for | Key fields |
|--------------------|-----------------------------------|----------------------------------------------------------|
| [`report`]() | Multi-section reports with TOC | `title`, `author`, `body`, `abstract`, `toc` |
| [`invoice`]() | Business invoices with line items | `company_name`, `client_name`, `items`, `tax_rate` |
| [`letter`]() | Formal letters with letterhead | `sender_name`, `recipient_name`, `subject`, `body` |
| [`cv`]() | CV / résumé | `name`, `contact`, `experience`, `education`, `skills` |
| [`academic`]() | Academic papers with bibliography | `title`, `authors`, `abstract`, `body`, `two_column` |
| [`presentation`]() | 16:9 slide decks | `title`, `author`, `slides` (each with `layout_variant`) |
| [`basic`]() | Minimal single-section documents | `title`, `author`, `body` |
Use `typy info ` to inspect fields in table form.
Use `typy info --json` for machine-readable schema output.
## report
A multi-page report with optional abstract, table of contents, running header, and page numbers.
**Key fields**: `title`, `subtitle`, `author`, `date`, `body`, `abstract`, `toc`
```python
from typy.builder import DocumentBuilder
from typy.content import Content
from typy.markup import Heading
from typy.templates import ReportTemplate
body = Content([
Heading(1, "Introduction"),
"This report was generated with **typy**.",
Heading(1, "Conclusion"),
"Summary of findings goes here.",
])
template = ReportTemplate(
title="Quarterly Report",
subtitle="Q1 2026",
author="Jane Doe",
date="April 2026",
body=body,
abstract=Content(["A brief summary of the report."]),
toc=True,
)
DocumentBuilder().add_template(template).save_pdf("report.pdf")
```
See the full runnable example at [`examples/report/report.py`](../examples/report/report.py).
## invoice
A one-page business invoice with a line-item table, subtotal/tax/total block, and optional notes.
**Key fields**: `company_name`, `company_address`, `client_name`, `client_address`,
`invoice_number`, `date`, `due_date`, `items` (list of `description`/`quantity`/`unit_price`),
`tax_rate`, `notes`, `logo`
```python
from typy.builder import DocumentBuilder
from typy.templates import InvoiceItem, InvoiceTemplate
template = InvoiceTemplate(
company_name="Acme Corp",
company_address="123 Business Ave\nNew York, NY 10001",
client_name="Jane Smith",
client_address="456 Client St\nSan Francisco, CA 94102",
invoice_number="INV-2026-001",
date="April 25, 2026",
due_date="May 25, 2026",
items=[
InvoiceItem(description="Web Design", quantity=10, unit_price=120.0),
InvoiceItem(description="Backend Development", quantity=20, unit_price=150.0),
],
tax_rate=10.0,
notes="Payment due within 30 days.",
)
DocumentBuilder().add_template(template).save_pdf("invoice.pdf")
```
See the full runnable example at [`examples/invoice/invoice.py`](../examples/invoice/invoice.py).
## letter
A formal business letter with optional letterhead logo.
**Key fields**: `sender_name`, `sender_address`, `recipient_name`, `recipient_address`,
`date`, `subject`, `body`, `closing`, `signature_name`, `logo`
```python
from typy.builder import DocumentBuilder
from typy.content import Content
from typy.templates import LetterTemplate
template = LetterTemplate(
sender_name="John Doe",
sender_address="123 Main St\nAnytown, CA 12345",
recipient_name="Jane Smith",
recipient_address="Jane Smith Inc.\n456 Elm St\nOthertown, NY 67890",
date="April 25, 2026",
subject="Project Proposal",
body=Content(["I am writing to propose a new project collaboration."]),
closing="Sincerely",
signature_name="John Doe",
)
DocumentBuilder().add_template(template).save_pdf("letter.pdf")
```
See the full runnable example at [`examples/letter/letter.py`](../examples/letter/letter.py).
## cv
A single-page CV / résumé with experience, education, skills, languages, and certifications.
**Key fields**: `name`, `contact` (email/phone/location/links), `summary`, `experience`,
`education`, `skills`, `languages`, `certifications`
```python
from typy.builder import DocumentBuilder
from typy.templates import CVContact, CVEducation, CVExperience, CVTemplate
template = CVTemplate(
name="Jane Smith",
contact=CVContact(
email="jane@example.com",
phone="+1 555 000 1234",
location="San Francisco, CA",
links=["github.com/janesmith"],
),
summary="Software engineer with 8+ years of experience building scalable systems.",
experience=[
CVExperience(
title="Senior Software Engineer",
company="Acme Corp",
start_date="Jan 2021",
end_date="Present",
location="San Francisco, CA",
description="Led API platform redesign, reducing p99 latency by 40%.",
),
],
education=[
CVEducation(
degree="B.S. Computer Science",
institution="UC Berkeley",
start_date="2013",
end_date="2017",
),
],
skills=["Python", "Go", "TypeScript", "PostgreSQL", "Kubernetes"],
)
DocumentBuilder().add_template(template).save_pdf("cv.pdf")
```
See the full runnable example at [`examples/cv/cv.py`](../examples/cv/cv.py).
## academic
An academic paper template with abstract box, optional two-column body, and bibliography support.
**Key fields**: `title`, `authors` (list of `name`/`affiliation`), `abstract`, `keywords`,
`body`, `two_column`, `bibliography_path`
```python
from typy.builder import DocumentBuilder
from typy.content import Content
from typy.markup import Heading
from typy.templates import AcademicAuthor, AcademicTemplate
body = Content([
Heading(2, "Introduction"),
"We present a new approach to document generation.",
Heading(2, "Conclusion"),
"Our results demonstrate the effectiveness of the approach.",
])
template = AcademicTemplate(
title="Programmatic PDF Generation with Typst",
authors=[
AcademicAuthor(name="Jane Smith", affiliation="University of Example"),
AcademicAuthor(name="John Doe", affiliation="Institute of Technology"),
],
abstract="We present typy, a Python library for generating PDFs with Typst.",
keywords=["PDF", "Typst", "Python"],
body=body,
two_column=True,
)
DocumentBuilder().add_template(template).save_pdf("paper.pdf")
```
See the full runnable example at [`examples/academic/academic.py`](../examples/academic/academic.py).
## presentation
A 16:9 slide deck with an auto-generated title slide and per-slide layout variants.
**Key fields**: `title`, `subtitle`, `author`, `date`, `slides`, `theme`
```python
from typy.builder import DocumentBuilder
from typy.content import Content
from typy.templates import PresentationTemplate, Slide
template = PresentationTemplate(
title="Typy Presentation",
subtitle="Easy slides in Typst",
author="Jane Doe",
date="April 2026",
slides=[
Slide(
title="Introduction",
body=Content(["Welcome to **typy** — slides built from Python."]),
),
Slide(
title="Section Break",
body=Content(["Key insight goes here."]),
layout_variant="hero",
),
Slide(
title="Comparison",
body=Content(["Before #colbreak() After"]),
layout_variant="two-column",
),
Slide(
title="Conclusion",
body=Content(["Thank you! Find typy at *github.com/mgoulao/typy*"]),
layout_variant="hero",
),
],
)
DocumentBuilder().add_template(template).save_pdf("deck.pdf")
```
See the full runnable example at [`examples/presentation/presentation.py`](../examples/presentation/presentation.py).
### Slide layout variants
Each `Slide` accepts a `layout_variant` field that controls the page layout:
| `layout_variant` | Description |
|----------------------|------------------------------------------------------|
| `None` / `"default"` | Accent header bar at the top, body below |
| `"hero"` | Full accent background, large centred title and body |
| `"two-column"` | Accent header, body split into two equal columns |
| `"blank"` | No header, body fills the whole slide |
## basic
A minimal document for quick notes and one-off outputs.
**Key fields**: `title`, `author`, `date`, `body`
```python
from typy.builder import DocumentBuilder
from typy.templates import BasicTemplate
template = BasicTemplate(
title="Hello typy",
date="April 25, 2026",
author="Your Name",
body="## Welcome\n\nThis PDF was generated from Python.",
)
DocumentBuilder().add_template(template).save_pdf("hello.pdf")
```
See the full runnable example at [`examples/basic/basic.py`](../examples/basic/basic.py).
## Inspect any template
```bash
typy info report
typy info invoice --json
```
# templates.html.md
# templates
### *class* typy.templates.Template
Bases: `BaseModel`
#### verification_config *: ClassVar[VerificationConfig | None]* *= None*
Optional post-render verification constraints for this template.
Set to a `VerificationConfig` instance on a
subclass to enforce page-count, font-policy, or placeholder checks
automatically when `typy render --verify` is used.
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
#### get_data()
* **Return type:**
dict
### *class* typy.templates.LetterTemplate(\*, sender_name, sender_address, recipient_name, recipient_address, date, subject, body, closing='Sincerely', signature_name, logo='')
Bases: [`Template`](#typy.templates.Template)
* **Parameters:**
* **sender_name** (*str*)
* **sender_address** (*str*)
* **recipient_name** (*str*)
* **recipient_address** (*str*)
* **date** (*str*)
* **subject** (*str*)
* **body** ([*Content*](content.md#typy.content.Content))
* **closing** (*str*)
* **signature_name** (*str*)
* **logo** (*str*)
#### sender_name *: str*
#### sender_address *: str*
#### recipient_name *: str*
#### recipient_address *: str*
#### date *: str*
#### subject *: str*
#### body *: [Content](content.md#typy.content.Content)*
#### closing *: str*
#### signature_name *: str*
#### logo *: str*
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.InvoiceItem(\*, description, quantity, unit_price)
Bases: `BaseModel`
* **Parameters:**
* **description** (*str*)
* **quantity** (*float*)
* **unit_price** (*float*)
#### description *: str*
#### quantity *: float*
#### unit_price *: float*
#### model_config *: ClassVar[ConfigDict]* *= {}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.InvoiceTemplate(\*, company_name, company_address, client_name, client_address, invoice_number, date, due_date, items, tax_rate=None, notes=None, logo=None)
Bases: [`Template`](#typy.templates.Template)
* **Parameters:**
* **company_name** (*str*)
* **company_address** (*str*)
* **client_name** (*str*)
* **client_address** (*str*)
* **invoice_number** (*str*)
* **date** (*str*)
* **due_date** (*str*)
* **items** (*list* *[*[*InvoiceItem*](#typy.templates.InvoiceItem) *]*)
* **tax_rate** (*float* *|* *None*)
* **notes** (*str* *|* *None*)
* **logo** (*Path* *|* *None*)
#### company_name *: str*
#### company_address *: str*
#### client_name *: str*
#### client_address *: str*
#### invoice_number *: str*
#### date *: str*
#### due_date *: str*
#### items *: list[[InvoiceItem](#typy.templates.InvoiceItem)]*
#### tax_rate *: float | None*
#### notes *: str | None*
#### logo *: Path | None*
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.Slide(\*, title, subtitle=None, body, footnote=None, layout_variant=None, notes=None)
Bases: `BaseModel`
* **Parameters:**
* **title** (*str*)
* **subtitle** (*str* *|* *None*)
* **body** ([*Content*](content.md#typy.content.Content))
* **footnote** (*str* *|* *None*)
* **layout_variant** (*str* *|* *None*)
* **notes** (*str* *|* *None*)
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
#### title *: str*
#### subtitle *: str | None*
#### body *: [Content](content.md#typy.content.Content)*
#### footnote *: str | None*
#### layout_variant *: str | None*
#### notes *: str | None*
### *class* typy.templates.PresentationTemplate(\*, title, subtitle=None, author, date, slides, theme=None)
Bases: [`Template`](#typy.templates.Template)
* **Parameters:**
* **title** (*str*)
* **subtitle** (*str* *|* *None*)
* **author** (*str*)
* **date** (*str*)
* **slides** (*list* *[*[*Slide*](#typy.templates.Slide) *]*)
* **theme** (*str* *|* *None*)
#### title *: str*
#### subtitle *: str | None*
#### author *: str*
#### date *: str*
#### slides *: list[[Slide](#typy.templates.Slide)]*
#### theme *: str | None*
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.AcademicAuthor(\*, name, affiliation='')
Bases: `BaseModel`
* **Parameters:**
* **name** (*str*)
* **affiliation** (*str*)
#### name *: str*
#### affiliation *: str*
#### model_config *: ClassVar[ConfigDict]* *= {}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.AcademicTemplate(\*, title, authors, abstract, keywords, body, two_column=False, bibliography_path=None)
Bases: [`Template`](#typy.templates.Template)
* **Parameters:**
* **title** (*str*)
* **authors** (*list* *[*[*AcademicAuthor*](#typy.templates.AcademicAuthor) *]*)
* **abstract** (*str*)
* **keywords** (*list* *[**str* *]*)
* **body** ([*Content*](content.md#typy.content.Content))
* **two_column** (*bool*)
* **bibliography_path** (*Path* *|* *None*)
#### title *: str*
#### authors *: list[[AcademicAuthor](#typy.templates.AcademicAuthor)]*
#### abstract *: str*
#### keywords *: list[str]*
#### body *: [Content](content.md#typy.content.Content)*
#### two_column *: bool*
#### bibliography_path *: Path | None*
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.BasicTemplate(\*, title, date, author, body)
Bases: [`Template`](#typy.templates.Template)
* **Parameters:**
* **title** (*str*)
* **date** (*str*)
* **author** (*str*)
* **body** ([*Content*](content.md#typy.content.Content))
#### title *: str*
#### date *: str*
#### author *: str*
#### body *: [Content](content.md#typy.content.Content)*
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.CVContact(\*, email='', phone='', location='', links=[])
Bases: `BaseModel`
* **Parameters:**
* **email** (*str*)
* **phone** (*str*)
* **location** (*str*)
* **links** (*list* *[**str* *]*)
#### email *: str*
#### phone *: str*
#### location *: str*
#### links *: list[str]*
#### model_config *: ClassVar[ConfigDict]* *= {}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.CVExperience(\*, title, company, start_date, end_date, location='', description='')
Bases: `BaseModel`
* **Parameters:**
* **title** (*str*)
* **company** (*str*)
* **start_date** (*str*)
* **end_date** (*str*)
* **location** (*str*)
* **description** (*str*)
#### title *: str*
#### company *: str*
#### start_date *: str*
#### end_date *: str*
#### location *: str*
#### description *: str*
#### model_config *: ClassVar[ConfigDict]* *= {}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.CVEducation(\*, degree, institution, start_date, end_date, location='', description='')
Bases: `BaseModel`
* **Parameters:**
* **degree** (*str*)
* **institution** (*str*)
* **start_date** (*str*)
* **end_date** (*str*)
* **location** (*str*)
* **description** (*str*)
#### degree *: str*
#### institution *: str*
#### start_date *: str*
#### end_date *: str*
#### location *: str*
#### description *: str*
#### model_config *: ClassVar[ConfigDict]* *= {}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.CVLanguage(\*, name, level)
Bases: `BaseModel`
* **Parameters:**
* **name** (*str*)
* **level** (*str*)
#### name *: str*
#### level *: str*
#### model_config *: ClassVar[ConfigDict]* *= {}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.CVCertification(\*, name, issuer, date)
Bases: `BaseModel`
* **Parameters:**
* **name** (*str*)
* **issuer** (*str*)
* **date** (*str*)
#### name *: str*
#### issuer *: str*
#### date *: str*
#### model_config *: ClassVar[ConfigDict]* *= {}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.CVTemplate(\*, name, contact, summary='', experience, education, skills, languages=[], certifications=[])
Bases: [`Template`](#typy.templates.Template)
* **Parameters:**
* **name** (*str*)
* **contact** ([*CVContact*](#typy.templates.CVContact))
* **summary** (*str*)
* **experience** (*list* *[*[*CVExperience*](#typy.templates.CVExperience) *]*)
* **education** (*list* *[*[*CVEducation*](#typy.templates.CVEducation) *]*)
* **skills** (*list* *[**str* *]*)
* **languages** (*list* *[*[*CVLanguage*](#typy.templates.CVLanguage) *]*)
* **certifications** (*list* *[*[*CVCertification*](#typy.templates.CVCertification) *]*)
#### name *: str*
#### contact *: [CVContact](#typy.templates.CVContact)*
#### summary *: str*
#### experience *: list[[CVExperience](#typy.templates.CVExperience)]*
#### education *: list[[CVEducation](#typy.templates.CVEducation)]*
#### skills *: list[str]*
#### languages *: list[[CVLanguage](#typy.templates.CVLanguage)]*
#### certifications *: list[[CVCertification](#typy.templates.CVCertification)]*
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
### *class* typy.templates.ReportTemplate(\*, title, subtitle=None, author, date, body, abstract=None, toc=True)
Bases: [`Template`](#typy.templates.Template)
* **Parameters:**
* **title** (*str*)
* **subtitle** (*str* *|* *None*)
* **author** (*str*)
* **date** (*str*)
* **body** ([*Content*](content.md#typy.content.Content))
* **abstract** ([*Content*](content.md#typy.content.Content) *|* *None*)
* **toc** (*bool*)
#### title *: str*
#### subtitle *: str | None*
#### author *: str*
#### date *: str*
#### body *: [Content](content.md#typy.content.Content)*
#### abstract *: [Content](content.md#typy.content.Content) | None*
#### toc *: bool*
#### model_config *: ClassVar[ConfigDict]* *= {'arbitrary_types_allowed': True}*
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
# typst_encoder.html.md
# typst_encoder
### *class* typy.typst_encoder.TypstEncoder
Bases: `object`
#### SUPPORTED_TYPES *= (, , , , , , , , , , )*
#### *classmethod* encode(data)
#### *classmethod* encode_dict(data)
* **Parameters:**
**data** (*Dict*)
* **Return type:**
str
#### *classmethod* encode_list(data)
* **Parameters:**
**data** (*List*)
* **Return type:**
str
#### *classmethod* encode_string(data)
* **Parameters:**
**data** (*str*)
* **Return type:**
str
#### *classmethod* encode_int(data)
* **Parameters:**
**data** (*int*)
* **Return type:**
str
#### *classmethod* encode_float(data)
* **Parameters:**
**data** (*float*)
* **Return type:**
str
#### *classmethod* encode_path(data)
* **Parameters:**
**data** (*Path*)
* **Return type:**
str
#### *classmethod* encode_pydantic_model(data)
* **Parameters:**
**data** (*BaseModel*)
* **Return type:**
str
#### *classmethod* encode_dataframe(data)
* **Parameters:**
**data** (*pd.DataFrame*)
* **Return type:**
str