Typst embeds a powerful scripting language. You can automate your documents and create more sophisticated styles with code. Below is an overview over the scripting concepts.


In Typst, markup and code are fused into one. All but the most common elements are created with functions. To make this as convenient as possible, Typst provides compact syntax to embed a code expression into markup: An expression is introduced with a hashtag (#) and normal markup parsing resumes after the expression is finished. If a character would continue the expression but should be interpreted as text, the expression can forcibly be ended with a semicolon (;).

#rect[Hello] \
#emoji.face \

The example above shows a few of the available expressions, including function calls, field accesses, and method calls. More kinds of expressions are discussed in the remainder of this chapter. A few kinds of expressions are not compatible with the hashtag syntax (e.g. binary operator expressions). To embed these into markup, you can use parentheses, as in #(1 + 2).


To structure your code and embed markup into it, Typst provides two kinds of blocks:

Content and code blocks can be nested arbitrarily. In the example below, [hello] is joined with the output of a + [ the ] + b yielding [hello from the *world*].

  let a = [from]
  let b = [*world*]
  [hello ]
  a + [ the ] + b

Let bindings

As already demonstrated above, variables can be defined with let bindings. The variable is assigned the value of the expression that follows the = sign. The assignment of a value is optional, if no value is assigned, the variable will be initialized as none. The let keyword can also be used to create a custom named function. Let bindings can be accessed for the rest of the containing block or document.

#let name = "Typst"
This is #name's documentation.
It explains #name.

#let add(x, y) = x + y
Sum is #add(2, 3).


With a conditional, you can display or compute different things depending on whether some condition is fulfilled. Typst supports if, else if and else expression. When the condition evaluates to true, the conditional yields the value resulting from the if's body, otherwise yields the value resulting from the else's body.

#if 1 < 2 [
  This is shown
] else [
  This is not.

Each branch can have a code or content block as its body.


With loops, you can repeat content or compute something iteratively. Typst supports two types of loops: for and while loops. The former iterate over a specified collection whereas the latter iterate as long as a condition stays fulfilled. Just like blocks, loops join the results from each iteration into one value.

In the example below, the three sentences created by the for loop join together into a single content value and the length-1 arrays in the while loop join together into one larger array.

#for c in "ABC" [
  #c is a letter.

#let n = 2
#while n < 10 {
  n = (n * 2) - 1

For loops can iterate over a variety of collections:

To control the execution of the loop, Typst provides the break and continue statements. The former performs an early exit from the loop while the latter skips ahead to the next iteration of the loop.

#for letter in "abc nope" {
  if letter == " " {


The body of a loop can be a code or content block:


You can use dot notation to access fields on a value. The value in question can be either:

#let dict = (greet: "Hello")
#dict.greet \


A method is a kind of a function that is tightly coupled with a specific type. It is called on a value of its type using the same dot notation that is also used for fields: value.method(..). The type documentation lists the available methods for each of the built-in types. You cannot define your own methods.

#let array = (1, 2, 3, 4)
#array.pop() \
#array.len() \

#("a, b, c"
    .split(", ")
    .join[ --- ])

Methods are the only functions in Typst that can modify the value they are called on.


You can split up your Typst projects into multiple files called modules. A module can refer to the content and definitions of another module in multiple ways:

Instead of a path, you can also use a module value, as shown in the following example:

#import emoji: face


The following table lists all available unary and binary operators with effect, arity (unary, binary) and precedence level (higher binds stronger).

+No effect (exists for symmetry)Unary7
==Check equalityBinary4
!=Check inequalityBinary4
<Check less-thanBinary4
<=Check less-than or equalBinary4
>Check greater-thanBinary4
>=Check greater-than or equalBinary4
inCheck if in collectionBinary4
not inCheck if not in collectionBinary4
notLogical "not"Unary3
andShort-circuiting logical "and"Binary3
orShort-circuiting logical "orBinary2