Daniel's been thinking about programmatic PDF generation — not the LaTeX comparison, we already did that one, but the actual capability surface. What can you do with Typst if you're driving it from a script or an AI agent and you want the output to look like a real business produced it? He wants the full tour, working upward from the mechanics. Page geometry, margins, headers and footers, page numbering. Fonts and typographic control. How content flows and how you control breaks, multi-column and grid layout. Tables, figures, captions. Document structure — headings, outlines, cross-references, bibliographies. Then the scripting layer, functions, templates, taking structured data and binding it to a layout, conditional and repeated content. And he wants us to be concrete about what's genuinely easy versus where you still fight it, plus what a sensible pipeline looks like when the thing writing the document is an agent rather than a person.
That's a lot of ground. But the through-line is actually one idea — everything in Typst is a function call. The page is a function call. The font is a function call. The table of contents is a function call. And that's the thing that makes it programmable in a way word processors fundamentally aren't.
So it's not a markup language with a scripting layer bolted on. The scripting layer is the language, and the markup is just syntax for calling functions.
Right. The mental model is: you're not formatting a document, you're writing a program that produces a document. The page is an output, not a canvas. And for an agent, that's the difference between generating something that looks right and generating something that is right — deterministically, every time.
Which is not how Word templates work.
Not even close. But let's build this from the ground up. Start with the page itself.
The page function takes named arguments — paper, margin, header, footer, numbering. You set the entire page geometry in one place. It's a function call, not a series of dialog boxes. So if you want A4 with two-centimeter margins and a header, that's page(paper: "a4", margin: 2cm, header: ...). Done.
And paper sizes — you're not stuck with the presets?
Typst ships with all the standard ones — A4, US Letter, legal, and so on. But you can define custom dimensions in points, millimeters, or inches. And the page function is composable — you can have different page setups for different parts of the document. A title page on its own geometry, then the body on A4, then an appendix on landscape legal. Each is just a different page call.
So the front matter can be Roman-numeral numbered and the body starts at one, and that's not a hack — it's just parameters.
The numbering parameter takes a pattern string — "1" for Arabic, "I" for Roman, "1 / 1" if you want page-count-of-total. And because it's a function, you can restart numbering per section. You don't have to manually track anything.
Headers and footers — those are where things usually get fragile in template systems. You want the chapter name in the header, and suddenly you're doing field codes that break when someone sneezes.
This is where Typst gets interesting. Headers and footers aren't decoration — they're content. You pass a function to the header parameter, and that function receives the page context and returns content. So a header can be dynamic. It can look up the current chapter heading from the document state and render it. That's... maybe four lines of Typst.
Give me the shape of that.
You define a function that takes the page number and the page context, and inside it you query the current heading — something like locate(loc => ...) to find the nearest heading element — and return it formatted in the header style. The point is that the header isn't a separate template field you have to keep in sync. It's computed from the document structure at compile time.
So if you restructure the document, the headers update automatically. No manual reconciliation.
Right. And page numbering works the same way — it's just a function that returns the formatted number. You can do things like "Chapter 3, Page 12" by combining the chapter counter with the page counter, all in the header function.
Let's talk fonts. That's the first thing that makes a document look either professional or like a ransom note.
The text function controls everything — font family, size, weight, stylistic features. You call text(font: "Libertinus Serif", size: 11pt, weight: "regular") and everything inside that call gets those settings. And Typst embeds the fonts in the PDF output. What you see is what ships — there's no "the printer didn't have the font" problem.
Which is the kind of thing that burns you at 4 a.m. before a deadline.
And it's not just font selection. Line height, letter spacing, text alignment, hyphenation — all set through the text function or through set and show rules. The set rule changes default parameters for a scope. So set text(font: "IBM Plex Sans", size: 10pt) at the top of the document sets the default for everything below it. Then you override locally when you need to.
And show rules?
show transforms content. It's a pattern-matching system. You say "whenever you see a heading, apply this transformation." So show heading: it => text(weight: "bold", it) makes every heading bold. But you can go much further — you can match on specific heading levels, or on content that matches a pattern, and transform it arbitrarily. This is the key to consistent styling without repeating yourself. You define the rules once, at the top, and the whole document inherits them.
So the styling is a program that runs over the document structure, not a set of properties you apply manually to each element.
And that's why it works for agents. An agent doesn't have to remember to apply the right style to every paragraph — it just writes the content with the right structure, and the show rules handle the rest.
Content flow. How does Typst decide what goes where?
It uses a continuous layout engine. Content flows from page to page automatically — you don't place things on pages, you write content and the engine paginates it. Breaks are explicit when you need them: pagebreak() forces a new page, linebreak() forces a new line. But most of the time you don't need them — the engine handles widows and orphans and keeps things looking reasonable.
And when you want to switch from one column to two?
The columns function. You call columns(2) and everything inside it flows into two columns. You can switch back to one column by ending the columns call. It's a function, not a section break — so you can nest them, you can have different column counts in different parts of the document, and it all composes.
What about when flow isn't what you want? A title page, or something that needs precise positioning?
That's the grid function. You define rows and columns explicitly, and place content in cells. grid(columns: 2, rows: 3, gutter: 1em, ...) gives you a six-cell grid with a gutter between cells, and you place content in specific cells. It's not flow-based — it's explicit placement. Useful for cover pages, title layouts, anything where you need things in exact positions rather than flowing naturally.
And you can mix flow and grid in the same document?
The grid is just another piece of content — you can drop it into a flow layout, or put flow content inside a grid cell. It's all composable.
Tables. This is where a lot of document systems show their seams.
The table function takes column definitions and cell content. The basic call is table(columns: 3, [header 1], [header 2], [header 3], [data 1], [data 2], [data 3]). But the real power is that cells can contain arbitrary content. You can put another table inside a cell. You can put a figure inside a cell. You can put a grid inside a cell. And cells can contain computed values — you're not limited to static text.
So a table cell could contain a conditional badge that only appears if a data value exceeds a threshold?
And you can style rows and columns — alternating row colors, header row formatting, column alignment — all through the table function's parameters. It's not a separate styling step.
Figures and captions. The thing that makes a document look professional is consistent figure numbering and cross-references that don't break.
figure wraps content with a caption. You write figure([an image or table or whatever], caption: [This is the caption]). And Typst automatically numbers figures — Figure 1, Figure 2, and so on — and maintains a list of figures. The numbering is automatic. You never type a figure number manually.
Which means you can insert a figure in the middle of the document and every subsequent figure renumbers itself.
Right. And the same system handles cross-references. You label something with label("fig:intro") and then reference it with ref("fig:intro"). Typst generates the correct number and page reference. You never hardcode "see Figure 3 on page 12" — you write "see ref("fig:intro")" and Typst fills in the numbers at compile time.
That's the thing that breaks catastrophically in word processors when you're editing at the last minute.
And it's a game-changer for programmatic generation. An agent never has to track figure numbers or page references. It just assigns labels and writes refs, and the compiler handles the rest.
Document structure — headings, outlines, the skeleton.
Headings use hierarchical markers. One equals sign is a top-level heading, two equals is a subheading, three is a sub-subheading. It's like Markdown but with more levels. And Typst automatically builds an outline from them — the outline function generates a table of contents with page numbers, and it updates automatically when the document changes.
So the table of contents is not something you maintain. It's a function call.
outline() at the top of the document, and you get a fully populated table of contents with correct page numbers. You can customize the depth, the formatting, which headings appear — but the default is already professional-looking.
Bibliographies. This is where academic and business documents get tedious fast.
Typst supports BibTeX and BibLaTeX files natively. You pass a bibliography file — bibliography("refs.bib") — and then cite keys in the text with cite("key"). Typst formats the references section automatically with the citation style you specify. Chicago, APA, IEEE — it's a parameter.
So the agent just needs to know the citation keys. It doesn't need to format anything.
Right. And the bibliography updates automatically — if you add or remove citations, the reference list regenerates. No manual renumbering.
Alright. We've covered the high-level features. Now the part that actually makes this programmable rather than just a markup language — the scripting layer.
This is where Typst stops being a document formatter and becomes a programming language. You have variables, functions, loops, conditionals, string manipulation, data structures. It's a full language. You can define a variable — let price = 42 — and use it anywhere. You can write a for loop that iterates over a list and generates content for each item. You can write if/else that conditionally includes or excludes sections.
Give me a concrete example. What does a template actually look like?
A template is just a function that takes data and returns content. You define let my_template(data) = { ... } and inside the function you lay out the document using the data parameter. The function wraps everything with consistent styling — page setup, fonts, headers, footers — and then slots the data into the right places. When you want to use it, you call my_template(my_data). That's it.
So the template is not a separate file format with its own editor. It's just code.
Just code. And because it's code, you can version it, test it, share it, compose it. You can have a base template that defines the corporate style, and then specialized templates that extend it for specific document types — reports, letters, invoices.
Data binding. Daniel mentioned taking structured data and binding it to a layout.
Typst can read JSON, CSV, or YAML data. You load a JSON file with json("data.json") and it returns a dictionary you can iterate over. So if you have a list of product records — name, price, description — you can loop over them with a for loop and generate a table row for each one. Or generate a whole page per record. Or conditionally include a "sale" badge if the price is below a threshold.
So the agent's job shifts. It's not formatting documents — it's producing structured data and selecting the right template.
That's the pipeline. The uptrack blog post on Typst for PDF generation describes it as "data to template to Typst source to PDF." The agent produces the data and the template — or selects a pre-built template — and Typst handles the compilation. The agent never touches layout directly.
Which means the agent can iterate fast. Generate the Typst source, compile, inspect the PDF, tweak the source or the data, recompile.
And Typst compiles in milliseconds for most documents. The feedback loop is tight. An agent can generate, compile, inspect, and regenerate in a loop — which is exactly the cycle you want for automated document generation. The source is plain text, the compilation is deterministic, and the output is a PDF. No rendering differences between machines, no font substitution surprises, no "it looked different in the preview."
That determinism is the thing that makes it different from the Word template era.
Mail merge has existed for decades. Template systems have existed for decades. But they all have the same failure mode — the template engine has limitations, and when you hit them, you're doing manual fixes. Typst's scripting layer means the template itself is programmable. If you need logic that the template designer didn't anticipate, you write it. You're not stuck inside a feature set someone else defined.
Let's talk about where Typst is easy versus where you still fight it.
The easy parts: automatic numbering of figures, tables, and headings. Cross-references that never break. Bibliography formatting. The scripting layer for data binding and conditional content. All the things that are painful in word processors are trivial here — they're just function calls.
The hard parts: complex custom layouts that don't fit the flow model. If you need precise absolute positioning for a specific design — like a magazine spread where every element is placed by hand — Typst's flow engine fights you. You can do it with grids and absolute placement, but it's not the happy path.
And debugging?
The error messages can be cryptic when you're new. The show/set rule system has a learning curve — the difference between set and show isn't obvious at first, and when a rule doesn't fire the way you expect, figuring out why can take some staring. Content overflow is another pain point — when something doesn't fit on a page, the error tells you something overflowed, but tracing it back to the source can be fiddly.
So the agent needs good error handling. If compilation fails, it needs to parse the error and adjust.
Right. And that's part of the pipeline design. The agent writes the Typst source, runs the compiler, and if it fails, it reads the error output and modifies the source. The errors are machine-readable — they have line numbers and descriptions — so an agent can act on them programmatically.
What does a sensible pipeline actually look like, end to end?
The agent receives a request — "generate a quarterly report for these financial results." It has access to a library of templates, or it can generate a template on the fly. It produces a JSON data file with the structured data — revenue numbers, section text, chart references. It selects or generates a Typst template that defines the layout. It runs the Typst compiler, which produces a PDF. It inspects the PDF — either by checking the compiler output for warnings, or by using a PDF inspection tool to verify the output. If something's wrong, it modifies the source or the data and recompiles. When the output is clean, it delivers the PDF.
The human role shifts from formatting to designing templates and data schemas.
That's the open question, isn't it? As agents get better at generating Typst source, does the human become a template designer? Someone who defines the corporate style once and then lets the agents produce the documents? That's a very different job than formatting reports by hand.
It makes one-off professional documents as cheap as one-off emails. What does that do to the value of document design?
Well, the design becomes a capital investment instead of a per-document cost. You spend time crafting a really good template once, and then every document that uses it looks professional by default. The economics flip — the expensive part is the template, not the document.
If agents can generate templates too...
Then we're in a world where the agent generates the template, generates the data, compiles the PDF, and the human just reviews the output. Which is probably where this is heading.
Hilbert: Nineteen ninety-seven.
Hilbert: I worked in a print shop. Downtown Hartford. We did on-demand document generation for a law firm — merger documents, discovery filings, the kind of thing where a typo in the header means you're reprinting two hundred pages.
Hilbert: We had a system. Word templates, VBA macros, a database of client names and case numbers. You'd hit a button and it would merge the data into the template and spool it to the printer. And every single time, someone had to check the output page by page because the template would break in ways you couldn't predict. A field code would lose its formatting. A page break would land in the wrong place. The header would show the wrong client name because someone hardcoded it in the template six months ago and forgot.
Hilbert: We had one disaster — a two-hundred-page brief for a merger. Printed, bound, shipped to the client. And the header on every page had the wrong case number. The template had a hardcoded field that the macro was supposed to overwrite, and the macro didn't fire. Four thousand dollars in printing and binding, straight into the recycling bin.
Hilbert: The thing you two are describing — the agent pipeline — it's not new. Mail merge is thirty years old. Template systems are thirty years old. The difference is that Typst's scripting layer makes the template itself programmable. You're not fighting the template engine's limitations because there is no separate template engine. It's all the same language. And it compiles deterministically. Same source, same PDF, every time. No "it looks different on my machine." No hidden state in the word processor.
Hilbert: If we'd had that in ninety-seven, the case number would have been a variable. It would have been impossible to hardcode it because the template wouldn't compile without the data. The compiler would have caught it before we printed a single page.
Hilbert: I still think about that recycling bin.
The determinism is the part that's new. Word templates had mail merge, but the output depended on the printer driver, the font installation, the version of Word.
The template was a binary file with hidden state. You couldn't version-control it meaningfully. You couldn't diff two versions and see what changed. With Typst, the template is plain text — you can put it in git, you can review changes, you can roll back. The whole pipeline is reproducible.
Which is what makes it work for agents. An agent can't handle a system where the output is unpredictable. It needs to know that if it writes this source, it gets that PDF — every time, on every machine.
The compilation speed means it can iterate. A typical document compiles in tens of milliseconds. An agent can try something, compile, check the output, and adjust — fifty times in the span it would take a human to open Word.
The thing Hilbert's story makes me think about is how much of document production has been defensive. You're not designing — you're checking. You're hunting for the thing that broke because someone edited a field code wrong.
Typst eliminates whole categories of those errors. Cross-references can't break because you never type them manually. Page numbers can't be wrong because they're computed at compile time. The table of contents can't be out of date because it's regenerated every time you compile.
The agent doesn't need to be defensive. It just needs to write correct structure and let the compiler handle the rest.
That's the shift. The agent's job is to produce well-structured content and well-formed data. The compiler's job is to make it look professional. Those are separate concerns, and Typst enforces the separation.
We've covered a lot of ground. Page geometry, typography, content flow, tables and figures, document structure, the scripting layer, the agent pipeline. The takeaway isn't that Typst has a lot of features — it's that the features are all functions, and that makes them composable in a way that template systems never were.
That composability is what makes it AI-ready. An agent doesn't need to understand layout. It needs to understand structure and data. Typst handles the translation from structure to PDF deterministically and fast. That's the whole pitch.
Thanks to our producer Hilbert Flumingtop for keeping the show running, and for the recycling bin story.
This has been My Weird Prompts. Find us at my weird prompts dot com, or email the show at show at my weird prompts dot com.
We'll be back soon.