WASM node package for compiling Typst documents inside FlowLike workflows. The package compiles Typst source text into PDF/SVG artifacts, resolves relative imports/assets from a FlowLike source folder, and returns structured compile metadata.
Implemented in this package:
| Node | Purpose |
|---|---|
Typst Source to PDF |
Compile Typst source text into PDF and write the PDF to an output FlowPath. |
Typst File to PDF |
Read a .typ file from FlowLike storage and compile it into PDF. |
Typst Source to SVG |
Compile Typst source text into SVG and write the SVG to an output FlowPath. |
Typst File to SVG |
Read a .typ file from FlowLike storage and compile it into SVG. |
Typst Source to PDF + SVG |
Compile Typst source text once and write both output formats. |
Typst File to PDF + SVG |
Read a .typ file, compile once, and write both output formats. |
Markdown to Typst |
Convert Flow-Like Markdown into a safe, styleable Typst body fragment, including static Nivo and Plotly charts. |
Typst Source with Template to PDF |
Apply an optional imported .typ template function to source text and write PDF. |
Typst File with Template to PDF |
Read a .typ document file, apply an optional imported .typ template function, and write PDF. |
Typst Source with Template to SVG |
Apply an optional imported .typ template function to source text and write SVG. |
Typst File with Template to SVG |
Read a .typ document file, apply an optional imported .typ template function, and write SVG. |
Typst Source with Template to PDF + SVG |
Apply an optional imported .typ template function to source text and write both output formats. |
Typst File with Template to PDF + SVG |
Read a .typ document file, apply an optional imported .typ template function, and write both output formats. |
Validate Typst Source |
Compile Typst source text without exporting, returning success, page count, and warnings. |
Validate Typst File |
Read a .typ file and validate it without exporting. |
Primary pins:
| Pin | Type | Use |
|---|---|---|
markdown |
string |
Flow-Like Markdown input for Markdown to Typst. |
typst_source |
string |
Generated Typst body fragment for a format/template node. |
source |
string |
Main Typst document text used by source nodes. |
source_file |
FlowPath |
Main .typ document source file used by file nodes. |
template_file |
FlowPath |
Optional .typ module exporting the template function to import and apply. |
template_function |
string |
Exported function name to import from template_file. Defaults to template. |
template_data |
string |
JSON object exposed to template functions as named arguments through Typst sys.inputs. |
source_folder |
FlowPath |
Folder used as the root for relative imports and assets referenced by source. |
output_file |
FlowPath |
PDF/SVG file to write. Compile nodes always write to this path. |
pdf_output_file |
FlowPath |
PDF output path for the PDF + SVG nodes. |
svg_output_file |
FlowPath |
SVG output path for the PDF + SVG nodes. |
allow_universe_packages |
bool |
Enables @preview/... Typst Universe package downloads through FlowLike host HTTP. Defaults to true; displayed as Packages. |
Markdown to Typst is a pure conversion node intended for model output. A
typical workflow is:
Model Markdown -> Markdown to Typst -> Format String -> Typst Source to PDF
The user-owned Typst document keeps the global page, text, heading, table, list, quote, and other show/set rules. Leave a distinctive Format String placeholder where the generated body belongs, for example:
#set page(paper: "a4", margin: 2cm)
#set text(font: "Libertinus Serif", size: 11pt)
#show heading.where(level: 1): set text(fill: rgb("#c2410c"))
{markdown_body}Connect Markdown to Typst.typst_source to the Format String input named
markdown_body, then connect formatted_string to Typst Source to PDF.source.
Markdown-authored text is emitted through Typst string literals, so text such as
#pagebreak() remains text rather than becoming executable Typst.
The converter covers Flow-Like's documented Markdown formatting: headings,
emphasis, strikethrough, inline and fenced code, nested ordered/unordered/task
lists, links, relative images, aligned GFM tables, blockquotes, alerts,
horizontal rules, footnotes, and visual line breaks. Remote Markdown images are
rendered as links because the Typst project resolver does not fetch arbitrary
HTTP image URLs; relative images continue to work when source_folder contains
the asset.
Fenced nivo and plotly blocks accept the same two forms as Flow-Like's
Markdown renderer:
```nivo
type: bar
title: Quarterly revenue
showLegend: true
---
quarter,revenue,profit
Q1,120,30
Q2,150,42
Q3,170,55
```or native JSON (chartType plus data for Nivo; data, layout, and optional
config for Plotly). Supported charts become package-free static SVG embedded
in the Typst fragment, so they require neither a browser nor a Typst Universe
package. Interactive-only options such as animation and hover behavior have no
static PDF equivalent. Chart shapes without a faithful static renderer fall
back to a deterministic data card; malformed chart blocks fall back to escaped
source instead of breaking the whole document.
See the example gallery for standalone Markdown, Flow-Like Nivo and Plotly configurations, user-owned Format String layouts, an imported Typst template, and their checked-in Typst, PDF, and SVG outputs.
When source_folder is omitted for Typst File to PDF, relative files resolve
from the parent folder of source_file.
When source_folder points to:
reports
then source text can reference files below that folder with relative Typst paths:
#import "templates/styles.typ": *
#image("images/logo.png")
#let csv = read("data/table.csv")
#bibliography("refs.bib")Typst Universe imports work when allow_universe_packages is enabled and the
workflow grants outbound HTTP:
#import "@preview/conchord:0.4.0": smart-chordPackage files are fetched from https://packages.typst.org, decompressed in
memory for the current node run, and resolved separately from relative project
files.
Use a template node when the layout stays the same but the document body or workflow data changes. In Typst, a template is a function: named arguments carry data such as a title or customer, and one final content argument receives the document body.
The repository includes three files you can copy:
report.typis the reusable layout.document.typis the document body.data.jsonis the workflow data.
Put them in FlowLike storage under one project folder. data.json is shown in
the tree for convenience; this example pastes its contents into a string pin
rather than reading the file at runtime.
reports/quarterly/
├── document.typ
├── data.json
├── templates/
│ └── report.typ
└── output/
└── briefing.pdf
Create reports/quarterly/templates/report.typ:
#let report(
title: "Untitled report",
client: "",
items: (),
draft: false,
doc,
) = [
#set page(paper: "a4", margin: 2cm)
#set text(font: "Libertinus Serif", size: 11pt)
#set document(title: title)
#if draft [
#align(center)[*DRAFT*]
]
= #title
#if client != "" [
*Client:* #client
]
#if items.len() > 0 [
== Highlights
#for item in items [
- #item
]
]
#doc
]Create reports/quarterly/document.typ:
= Executive Summary
This body is wrapped by the imported report template.Add the Typst File with Template to PDF node and connect these values. FlowPath pins are file/folder objects selected or produced by FlowLike, not plain strings; the paths below show which objects to select.
| Pin | Value |
|---|---|
source_file |
FlowPath for reports/quarterly/document.typ |
template_file |
FlowPath for reports/quarterly/templates/report.typ |
template_function |
report |
template_data |
Contents of data.json, passed as a string |
source_folder |
FlowPath folder reports/quarterly |
output_file |
Writable FlowPath reports/quarterly/output/briefing.pdf |
allow_universe_packages |
true, unless the project must forbid package downloads |
Paste this string into template_data:
{
"title": "Quarterly Briefing",
"client": "Rheosoph",
"items": ["Data", "Automation", "AI"],
"draft": true
}Run the node. It writes the PDF to output_file, returns that FlowPath, and
returns a compile report with page and warning counts.
For an inline body, use the Typst Source with Template to PDF node and put
the contents of document.typ in the source pin. Keep the other pin values
the same.
The node effectively adds the import and show rule for you:
#import "templates/report.typ": report as __flowlike_template
#show: __flowlike_template.with(..sys.inputs)
= Executive Summary
This body is wrapped by the imported report template.Do not add that import/show pair to the body as well, or the template will be applied twice.
For inline bodies, FlowLike creates a collision-safe internal entry file at the
project root. You do not choose or reserve its name, and a stored template can
safely be named main.typ.
All six template nodes apply data in the same way. Choose the input style first, then PDF, SVG, or both:
| Input style | Body comes from | Best for |
|---|---|---|
| Source with Template | The source string pin |
Typst text generated earlier in a workflow. |
| File with Template | The .typ file selected by source_file |
Documents and multi-file projects kept in FlowLike storage. |
The combined PDF + SVG variant uses pdf_output_file and svg_output_file;
the single-format variants use output_file.
The function exported by template_file must:
- Be defined with
#let; its name must matchtemplate_function. - Accept each top-level
template_datakey as a named parameter. Defaults are recommended for optional data. - Accept the document body as a positional content parameter such as
doc. - Insert
docwhere the caller's body should appear.
This follows Typst's standard #show: function.with(...) template pattern. The
show rule supplies the final document body automatically.
template_data must be one JSON object. Each top-level key becomes a named
template argument. FlowLike converts values to native Typst values:
| JSON | Typst |
|---|---|
| string | str |
| integer or decimal | int or float |
| boolean | bool |
| array | array |
| object | dictionary |
null |
none |
This is intentionally richer than the Typst CLI's --input, which exposes
command-line values as strings. The template above can therefore loop over
items directly and test draft as a boolean; it does not need to parse JSON.
For compatibility with the original template nodes, template_file is
optional. When it is unconnected, the node compiles the body unchanged and
still makes template_data available as sys.inputs. template_function is
then unused.
For example, set source to:
#let title = sys.inputs.at("title", default: "Untitled report")
#let items = sys.inputs.at("items", default: ())
#let draft = sys.inputs.at("draft", default: false)
#if draft [*DRAFT*]
= #title
#for item in items [
- #item
]Use this mode for existing documents that already read sys.inputs, or for a
small one-off document. Connect template_file for a reusable layout that stays
separate from its content.
source_folder is the Typst project root and file-access boundary. For
predictable multi-file projects, connect it explicitly:
source_fileandtemplate_filemust be insidesource_folderand use the same FlowLike storage backend as it.- FlowPath pin paths must identify stored files directly; do not put
.or..segments in them or start them with/. - A relative path in an inline
sourcebody starts atsource_folder. In a stored body it starts at the parent folder ofsource_file. - Inside Typst code, a relative path is resolved from the
.typfile containing that path. For example,image("../assets/logo.svg")insidetemplates/report.typresolves from thetemplatesfolder. - A leading
/in Typst code means the project root, not the operating-system root. With the root above,image("/assets/logo.svg")starts atreports/quarterly. output_fileis not part of Typst's read root and may be any writable FlowPath.
If source_folder is omitted, a file node normally uses the parent of
source_file; an inline template node can use the parent of template_file.
That default is convenient only when all referenced files are below that
folder. When body, template, or assets are in sibling folders, select their
common parent explicitly.
An empty body is valid: use it when the template itself produces all visible content.
| Error or symptom | Fix |
|---|---|
template_data must be valid JSON |
Use double quotes, remove trailing commas/comments, and validate the JSON string. |
template_data must be a JSON object |
Wrap the data in { ... }; a top-level array or scalar is not accepted. |
Typst reports unexpected argument: ... |
Remove that JSON key or add a matching named parameter to the template function. The example's draft key is accepted by draft: false. |
| The imported function is missing | Make template_function exactly match an exported #let name in template_file; it defaults to template. |
source_file and template_file must refer to different files |
Keep the reusable template module and document body in separate .typ files. |
| A template or source path is outside the root, or uses a different store | Select a common source_folder and recreate all project FlowPaths from the same storage backend. |
| An image, bibliography, CSV, or import cannot be found | Resolve a relative path from the file that contains it, or use a /project-root-relative Typst path. |
| The template appears twice | Remove any manual template #show rule from the body; the template node inserts one. |
An @preview/... package cannot be fetched |
Enable allow_universe_packages and grant the workflow outbound HTTP, or remove the package import. |
- Making a Template
explains template functions, everything show rules, named arguments, imports,
and
.with(...). - Scripting: Modules
documents
importandinclude. - Paths and project roots
explains relative paths,
/paths, and the project file-access boundary. sys.inputsdocuments Typst's external input dictionary.- The Typst team maintains an official template repository; its charged IEEE template is a complete multi-file example.
- The Typst package repository contains the template packages published in Typst Universe.
The implementation uses current Typst Rust crates instead of shelling out to the Typst CLI:
| Crate | Role |
|---|---|
typst |
Core compiler and document model. |
typst-as-lib |
In-memory source/file resolver around Typst. |
typst-assets |
Embedded default fonts for sandboxed compilation. |
typst-pdf |
PDF export. |
typst-svg |
SVG export. |
Typst Universe @preview/... packages are resolved through FlowLike host HTTP,
not the native Typst package cache. This keeps the WASM node aligned with
FlowLike permissions and avoids depending on a local filesystem cache.
High-value follow-up nodes:
| Node | Why |
|---|---|
Typst Source to PNG |
Render page images with typst-render for previews and thumbnails. |
Typst Package Cache |
Persist approved Typst Universe packages through FlowLike storage so repeated runs do not need another network download. |
Typst Project Bundle to PDF |
Compile a whole project from a directory FlowPath, preserving relative imports and assets. |
Typst Diagnostics |
Return structured source spans and hints instead of debug-formatted warnings. |
Typst HTML Export |
Add HTML output when Typst's HTML export is stable enough for this runtime. |
Useful Typst Universe package categories for FlowLike automations:
| Category | Typical Packages |
|---|---|
| Charts and plots | cetz, cetz-plot, chart/table packages for reports. |
| Tables and data | Table styling, CSV/data helpers, report layout helpers. |
| Invoices/forms | Invoice, letter, resume, and business document templates. |
| Academic documents | Bibliography, theorem, code, and publication templates. |
| Diagrams | Diagram and drawing packages for architecture/report visuals. |
rustup target add wasm32-wasip2
cargo build --releaseWith mise:
mise run buildThe release component is copied to node.wasm; the original artifact is at:
target/wasm32-wasip2/release/flow_like_typst_nodes.wasm
cargo test --target $(rustc -vV | grep host | awk '{print $2}')node.wasm is a generated release artifact and is intentionally ignored by
git. Build it immediately before publishing.
Release checklist:
- Confirm
Cargo.toml,flow-like.toml, andREADME.mduse the same version. - Run
cargo test --target $(rustc -vV | grep host | awk '{print $2}'). - Run
mise run build. - Confirm
node.wasmexists and is newer than the last source edit. - In FlowLike Desktop, open Library -> Packages -> Publish.
- Select
node.wasmandflow-like.toml. - Submit the package for review.