Reference

The functions and types Twyla adds to Typst’s standard library.

document

document(
  output: strauto,
  title: contentnone,
  date: datetimenone,
  description: contentnone,
  kind: strauto,
  extra: any,
  draft: bool,
  body: content,
) -> content

Defines a page of the website.

Conventionally, document metadata is provided near the top of each page. with a set rule. Every field is then available on this page via #context
document.<field>
, and the whole site’s metadata is available through documents().

#set document(
  title: "Rewriting My Blog",
  date: datetime(year: 2026, month: 4, day: 12),
  description: "Why I moved off Markdown.",
  kind: "post",
)

It is also possible to create new pages inline, by constructing documents:

#context link(
    document(
        title: "Page within a page",
        output: "subpage/index.html",
    )[Hello from a page within a page!].url(),
)[This is a link to a page within a page]
outputstrauto= autonamedsettable

Where to write the document, as a bundle output path — e.g. "foo/index.html" for a page called “foo”, or "foo.html". Can be used to create a page literally called “main”.

A relative path (no leading /) resolves against a base: for a full-file page, the source’s folder below content/ (content/blog/post.typblog/, so output: "extra.html"blog/extra.html); for an inline document(..), the enclosing page’s output directory. A leading / is bundle-root-absolute (output: "/feed.xml"), ignoring that base. When auto, a full-file page derives its output from the source filename; an inline document must set it explicitly.

titlecontentnone= nonenamedsettable
The page’s title.
datedatetimenone= nonenamedsettable
The page’s publication date.
descriptioncontentnone= nonenamedsettable
A short description or summary of the page.
kindstrauto= autonamedsettable

What kind of page this is, e.g. "post" or "page" — used to group pages in listings and feeds, and to pick the {kind}-template a convert draft shows. If auto, defaults from the source filename:

Kind Default for
"root" content/main.typ — the site index
"dir" content/<dir>/main.typ — a section index
"page" any other file
extraany= (:)namedsettable
Arbitrary extra data for your own use. Available as document.extra and as the extra field of this page’s [documents] entry. Defaults to an empty dictionary, so templates can document.extra.at(.., default: ..) without first checking for none.
draftbool= falsenamedsettable
Whether this page is a draft. Drafts are still built, but are conventionally excluded from listings and feeds.
bodycontentrequiredpositional
The body of an inline document. When document is called as a constructor — #document(output: "x")[stuff] — this carries stuff, which twyla hoists into its own bundle output (see [crate::compile]). #set document(..) never touches this (required positional fields aren’t settable), so the set-rule-only metadata-carrier use is unaffected.
Returns content

.url()

document.url() -> str

The page’s public URL. Two call forms:

Contextual — call it inside #context. context precedes the optional this self-positional: the #[func] macro classifies special params by name and forwards them ahead of ordinary positionals (the instance is prepended as that positional on a method call).

Returns str

documents

documents() -> array

The list of every page in the site.

Returns an array with one dictionary per page, each carrying that page’s url plus the metadata it set via #set document(..): url, title, date, description, kind, draft, and extra. Access fields with ordinary dot syntax, e.g. doc.title.

This is contextual — call it inside a #context block:

#context for doc in documents() {
  if doc.kind == "post" and not doc.draft [
    == #link(doc.url, doc.title)
    #doc.date.display()

    #doc.description
  ]
}
Returns array

asset

.url()

asset.*.url() -> str

The resolved, fingerprinted URL of this asset (e.g. /assets/logo-<hash>.svg). Contextual — call it inside #context.

context must precede the this self-positional: the #[func] macro classifies special params by name and forwards them ahead of ordinary positionals, and the method call prepends the element as that positional.

Returns str

.read()

asset.*.read(
  encoding: "utf8"none,
) -> strbytes

The file’s contents — for inlining instead of linking. Mirrors the native read function: UTF-8 str by default, raw bytes with encoding: none. Contextual.

encoding"utf8"none= "utf8"named
The encoding to read the asset with. If {none}, returns raw bytes; otherwise the bytes are decoded as UTF-8 into a string.
Returns strbytes

asset.file

asset.file(
  path: pathstr,
) -> content

Reference a project file as an asset, copied verbatim and fingerprinted.

#context html.elem("img", attrs: (src: asset.file("logo.svg").url()))
pathpathstrrequiredpositional
Path to the file, relative to the calling file.
Returns content

asset.sass

asset.sass(
  path: pathstr,
  minify: bool,
) -> content

Compile a Sass/SCSS file to a fingerprinted CSS asset.

minify is a settable field, so #set asset.sass(minify: false) switches a whole page (or site) to expanded output.

#context html.elem("link", attrs: (
  rel: "stylesheet",
  href: asset.sass("main.scss").url(),
))
pathpathstrrequiredpositional
Path to the .sass/.scss file, relative to the calling file.
minifybool= truenamedsettable
Minify the output with grass’s compressed style (strips whitespace, comments, and other redundant characters). Part of the asset key, so the minified and expanded builds of one file resolve to distinct assets.
Returns content

asset.image

asset.image(
  path: pathstr,
  width: intnone,
  height: intnone,
  fit: "contain""cover""stretch",
  filter: "nearest""triangle""catmull-rom""gaussian""lanczos",
  format: "png""jpeg""gif""webp""avif"none,
  quality: int,
) -> content

Decode, resize, and re-encode a raster image asset.

#context html.elem("img", attrs: (
  src: asset.image("photo.jpg", width: 600, format: "webp").url(),
))

With no width/height the image is only transcoded (to format); with no format it keeps its source format and is only resized. All parameters are settable, so #set asset.image(format: "webp") applies to a whole scope.

pathpathstrrequiredpositional
Path to the source image, relative to the calling file.
widthintnone= nonenamedsettable
Target width in pixels. With only one of width/height set the other is computed to preserve the aspect ratio; with neither, the image is not resized.
heightintnone= nonenamedsettable
Target height in pixels. See [width](Self::width).
fit"contain""cover""stretch"= "contain"namedsettable
How the image is fit into width×height when both are given: "contain" scales to fit inside the box (aspect preserved), "cover" scales and crops to fill it (aspect preserved), "stretch" forces the exact dimensions (aspect distorted). cover/stretch require both dimensions.
filter"nearest""triangle""catmull-rom""gaussian""lanczos"= "lanczos"namedsettable
The resampling filter used when scaling. "lanczos" (the default) gives the best downscaling quality; cheaper options trade quality for speed.
format"png""jpeg""gif""webp""avif"none= nonenamedsettable
Output format. {none} (the default) keeps the source format; otherwise the image is transcoded to the named format.
qualityint= 75namedsettable
Encoder quality, 1–100, for lossy formats (JPEG, WebP, AVIF). Ignored by PNG and GIF.
Returns content

asset.typst

asset.typst(
  source: pathstrcontent,
  format: "svg""png""pdf""html",
  ppi: int,
) -> content

Compile a typst document (content or a project .typ file) to a fingerprinted asset.

format (default svg) and ppi are settable, so #set asset.typst(format: "pdf") configures a whole scope.

#context html.elem("a", attrs: (
  href: asset.typst("/resume/cv.typ", format: "pdf").url(),
))[Download my CV]
sourcepathstrcontentrequiredpositional
The document to compile: an inline content value, or a path string to a project .typ file (resolved relative to the calling file).
format"svg""png""pdf""html"= "svg"namedsettable
Output format: "svg" (the default), "png", "pdf", or "html". svg/png/pdf lay the document out as pages; html compiles it the same way the site’s pages are compiled.
ppiint= 144namedsettable
Pixels per inch for the png format. Ignored by the other formats (and excluded from the cache key for them, so it never fragments their output).
Returns content

raw-html

raw-html(
  html: strbytes,
) -> content

Splice a string of raw HTML into the output verbatim.

Typst has no first-class raw-HTML node, so for now this wraps the markup in <script type="…">…</script> — a raw-text element typst won’t escape — and twyla’s post-render pass ([crate::render::resolve_raw_html_placeholders]) strips the wrapper back out. Exposing it as a builtin rather than a typst #let keeps call sites stable if we later swap the implementation (e.g. parse the HTML with html5ever and emit real typst nodes).

htmlstrbytesrequiredpositional
The raw HTML markup to emit unescaped — a string, or bytes (e.g. from asset.file("icon.svg").read(encoding: none)) decoded as UTF-8.
Returns content

plain-text

plain-text(
  content: content,
) -> str

The plain text of some content — the same flattening typst uses to derive the document <title> from document(title: …). Useful for slugs, alt text, and other places that need a string from rich content.

contentcontentrequiredpositional
The content to flatten to plain text.
Returns str