LaTeX & Typst (papers, theses, resumes and slides) Module 6 – Citations and bibliographies
Typst bibliographies, Hayagriva and CSL styles
Cite with @key from a .bib or Hayagriva file in Typst, pick a built-in or CSL style, use prose and other forms, and print several bibliographies.
What you will learn
- Load a .bib or Hayagriva file and cite its entries with @key in Typst
- Choose a built-in citation style or load a CSL file, from the source or the command line
- Use prose, author, year and full citations, and citations with a page
- Split citations among several bibliographies and control how they are numbered
- Compare Typst's output with biblatex's for the same entries, and decide how to store titles for both
Before you start
What you’ll learn · Before you start
What you will learn
- Load a .bib or Hayagriva file and cite its entries with @key in Typst
- Choose a built-in citation style or load a CSL file, from the source or the command line
- Use prose, author, year and full citations, and citations with a page
- Split citations among several bibliographies and control how they are numbered
- Compare Typst's output with biblatex's for the same entries, and decide how to store titles for both
Before you start
On this page
Typst needs one compile and no second program to print citations and a reference list, but the same .bib file came
out of its IEEE style with its titles in title case and an access date written Oct. 04, where biblatex’s IEEE style
lowered the titles and wrote Oct. 4. Both are faithful to their styles; a reader of your paper sees only one of them.
Below, you will cite with @key from a .bib file and from Hayagriva: Typst's own bibliography format, a YAML file in which each entry has a type, a person is one string and a journal or book is a nested parent entry., Typst’s own
format, switch among built-in styles and a CSL file written for this lesson, use the forms a citation can take, and
print several bibliographies with one numbering or several. Last, the same entries go through Typst and biblatex side
by side, and you will see how to store titles so that both print them right. Every run is recorded with Typst 0.15.1,
and the biblatex side with Biber 2.22 from TeX Live 2026.
One compile prints the citations and the list
In Typst a citation is written like a reference, @ and the key, and #bibliography("refs.bib") both loads the data
and prints the list where it stands. The default style is IEEE:
basic/main.typ
#set page(width: 15cm, height: auto, margin: 1cm)
Rainfall rose in the gardens @iyer2024monsoon, and a model forecast it
@sarma2023lstm.
#bibliography("refs.bib") refs.bib
% Five invented works. Every name, title, journal, address and DOI is made up.
@article{iyer2024monsoon,
author = {Iyer, Kavya and Østergaard, Mikkel},
title = {Monsoon Rainfall Records from {Assamese} Tea Gardens},
journal = {Journal of Invented Climate Studies},
year = {2024},
volume = {12},
number = {3},
pages = {41--58},
doi = {10.5555/jics.2024.0312},
}
@book{rao2021typesetting,
author = {Rao, Sunita},
title = {Typesetting for Scientists},
publisher = {Example Academic Press},
address = {Pune},
year = {2021},
}
@inproceedings{sarma2023lstm,
author = {Śarmā, Anand and Okafor, Chidi},
title = {Forecasting Rainfall with {LSTM} Networks},
booktitle = {Proceedings of the Fifth Workshop on Invented Data},
year = {2023},
pages = {101--110},
doi = {10.5555/wid.2023.101},
}
@online{rainportal2026,
author = {{Example Rainfall Portal}},
title = {Daily Rainfall Data for {Kerala}},
url = {https://example.com/rain/kerala},
urldate = {2026-10-04},
year = {2026},
}
@dataset{rainset2025,
author = {Menon, Deepa and Iyer, Kavya and Okafor, Chidi and Rao, Sunita},
title = {Hourly Rain Gauge Readings, 2015--2024},
year = {2025},
doi = {10.5555/rain.hourly.2025},
keywords = {data},
} from session import files, run
# One compile of main.typ prints the citations and the list, and leaves no file but the PDF; then a misspelt key, and a citation in a document that
# has no bibliography at all.
files("refs.bib", "basic/main.typ", "basic/typo.typ", "basic/no-list.typ")
run(r"""
typst compile main.typ
ls
pdftotext main.pdf - | grep .
!typst compile typo.typ
!typst compile no-list.typ
""") Output
$ typst compile main.typ $ ls main.pdf main.typ no-list.typ refs.bib typo.typ $ pdftotext main.pdf - | grep . Rainfall rose in the gardens [1], and a model forecast it [2]. Bibliography [1] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea Gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024, doi: 10.5555/jics.2024.0312. [2] A. Śarmā and C. Okafor, “Forecasting Rainfall with LSTM Networks,” in Proceedings of the Fifth Workshop on Invented Data, 2023, pp. 101–110. doi: 10.5555/wid.2023.101. $ typst compile typo.typ error: label `<iyer2024monson>` does not exist in the document ┌─ typo.typ:2:29 │ 2 │ Rainfall rose in the gardens @iyer2024monson. │ ^^^^^^^^^^^^^^^ [exit status 1] $ typst compile no-list.typ error: label `<iyer2024monsoon>` does not exist in the document ┌─ no-list.typ:2:29 │ 2 │ Rainfall rose in the gardens @iyer2024monsoon. │ ^^^^^^^^^^^^^^^^ [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 basic_session.py
typst compile main.typ printed [1], [2] and the list in a single run, and ls found no file beside the PDF: no
.aux, .bcf or .bbl file, and no Biber. That speed comes with strictness. A key that is not in the file,
@iyer2024monson, stopped the compile with label `<iyer2024monson>` does not exist in the document, and so did a
correct key in a document with no bibliography, because Typst’s cite reference says that a citation needs a
bibliography somewhere in the document. A LaTeX build in the same situation goes on and prints the key or [?]. The heading Bibliography comes from the
title parameter, whose default, the reference page says, suits the text’s language; title: [References] changes
it and title: none removes it.
Expecting a list of everything in the file
Only cited works are listed. For an entry you want in the list without citing it, write
#cite(<key>, form: none); for every entry of the file, #bibliography("refs.bib", full: true).
Checkpoint
Check: Citing and printing in one compile
Hayagriva is Typst’s own format
The bibliography reference describes Hayagriva as a YAML format designed for use with Typst, read by the same
#bibliography call as a .bib file. Here two works of refs.bib are written in it, cited from each file in turn:
hayagriva/refs.yml
# Two of refs.bib's entries in Hayagriva, Typst's own bibliography format. Invented works, as in refs.bib.
iyer2024monsoon:
type: article
title: Monsoon Rainfall Records from Assamese Tea Gardens
author: ["Iyer, Kavya", "Østergaard, Mikkel"]
date: 2024
page-range: 41-58
serial-number:
doi: 10.5555/jics.2024.0312
parent:
type: periodical
title: Journal of Invented Climate Studies
volume: 12
issue: 3
rainportal2026:
type: web
title: Daily Rainfall Data for Kerala
author: Example Rainfall Portal
date: 2026
url:
value: https://example.com/rain/kerala
date: 2026-10-04 hayagriva/from-yml.typ
#set page(width: 15cm, height: auto, margin: 1cm)
Records @iyer2024monsoon and the portal @rainportal2026.
#bibliography("refs.yml") from session import files, run
# The same two works cited from refs.yml (Hayagriva) and from refs.bib; diff compares the text of the two PDFs.
files("refs.bib", "hayagriva/refs.yml", "hayagriva/from-yml.typ", "hayagriva/from-bib.typ")
run(r"""
typst compile from-yml.typ
pdftotext from-yml.pdf - | grep .
typst compile from-bib.typ
pdftotext from-yml.pdf yml.txt
pdftotext from-bib.pdf bib.txt
diff yml.txt bib.txt && echo "the two PDFs hold the same text"
""") Output
$ typst compile from-yml.typ $ pdftotext from-yml.pdf - | grep . Records [1] and the portal [2]. Bibliography [1] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea Gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024, doi: 10.5555/jics.2024.0312. [2] Example Rainfall Portal, “Daily Rainfall Data for Kerala.” Accessed: Oct. 04, 2026. [Online]. Available: https://example.com/rain/kerala $ typst compile from-bib.typ $ pdftotext from-yml.pdf yml.txt $ pdftotext from-bib.pdf bib.txt $ diff yml.txt bib.txt && echo "the two PDFs hold the same text" the two PDFs hold the same text
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 hayagriva_session.py
diff found no difference between the two PDFs’ text, so Hayagriva is not a way to a different output; it is a
different way to write the data. Set beside the .bib entry, the fields its file-format documentation describes line
up like this:
| In a .bib file | In Hayagriva |
|---|---|
@article{iyer2024monsoon, …} |
iyer2024monsoon: and type: article |
journal = {…}, volume, number |
a parent: of type: periodical with its title, volume and issue |
author = {Iyer, Kavya and Østergaard, Mikkel} |
author: ["Iyer, Kavya", "Østergaard, Mikkel"], one string a person |
pages = {41--58} |
page-range: 41-58 |
doi = {…} |
serial-number: with doi: under it |
year = {2024}, urldate |
date: 2024, and a url: with value and date |
The nesting is the point of the format: a journal, a book or a website is a parent entry with fields of its own, and a
person is one string, so no and can split an organisation. Choose Hayagriva for a project that only Typst reads;
keep a .bib file when the same data must also feed BibTeX, Biber or a reference manager.
Checkpoint
Check: Hayagriva files
A style is a name or a CSL file
The style parameter takes a built-in style’s name or a path to a CSL: Citation Style Language: an XML format for citation styles. A CSL file describes how citations and the reference list look, and Typst, Zotero and other programs read it. file, and the reference
page lists the built-in names, some twice, under a full name and a short alias such as apa and ieee. To switch
styles without editing the document, styles.typ reads its style from the command line through sys.inputs, which
holds what --input key=value passes:
styles/styles.typ
#set page(width: 15cm, height: auto, margin: 1cm)
// The style comes from the command line: typst compile --input style=apa styles.typ
#let style = sys.inputs.at("style", default: "ieee")
Rainfall rose @iyer2024monsoon, as #cite(<sarma2023lstm>, form: "prose") predicted from
hourly readings @rainset2025.
#bibliography("refs.bib", style: style) styles/rain-short.csl
<?xml version="1.0" encoding="utf-8"?>
<!-- A small author-date style written for this lesson: short names and the year in citations,
then surname, initials, year and the title in italics in the list. -->
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0">
<info>
<title>Rain report, short author-date</title>
<id>https://example.com/csl/rain-report-short</id>
<updated>2026-01-01T00:00:00+00:00</updated>
</info>
<citation>
<layout prefix="(" suffix=")" delimiter="; ">
<group delimiter=" ">
<names variable="author">
<name form="short" and="symbol" et-al-min="3" et-al-use-first="1"/>
</names>
<date variable="issued">
<date-part name="year"/>
</date>
</group>
</layout>
</citation>
<bibliography>
<sort>
<key variable="author"/>
</sort>
<layout suffix=".">
<group delimiter=". ">
<names variable="author">
<name name-as-sort-order="all" initialize-with="." delimiter=", " and="symbol"/>
</names>
<date variable="issued">
<date-part name="year"/>
</date>
<text variable="title" font-style="italic"/>
</group>
</layout>
</bibliography>
</style> from session import files, run
# styles.typ reads its style name from --input, so one source prints in IEEE, APA, Chicago author-date and a CSL
# file of this lesson; then a style name Typst 0.15 has renamed, and one it does not know.
files("refs.bib", "styles/styles.typ", "styles/rain-short.csl")
run(r"""
typst compile --input style=ieee styles.typ ieee.pdf
pdftotext ieee.pdf - | grep .
typst compile --input style=apa styles.typ apa.pdf
pdftotext apa.pdf - | grep .
typst compile --input style=chicago-author-date styles.typ chicago.pdf
pdftotext chicago.pdf - | grep .
typst compile --input style=rain-short.csl styles.typ own.pdf
pdftotext own.pdf - | grep .
typst compile --input style=vancouver styles.typ vancouver.pdf
!typst compile --input style=ieee-transactions styles.typ unknown.pdf
""") Output
$ typst compile --input style=ieee styles.typ ieee.pdf
$ pdftotext ieee.pdf - | grep .
Rainfall rose [1], as A. Śarmā and C. Okafor [2] predicted from hourly readings
[3].
Bibliography
[1] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea
Gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024,
doi: 10.5555/jics.2024.0312.
[2] A. Śarmā and C. Okafor, “Forecasting Rainfall with LSTM Networks,” in
Proceedings of the Fifth Workshop on Invented Data, 2023, pp. 101–110. doi:
10.5555/wid.2023.101.
[3] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly Rain Gauge Readings, 2015–
2024. (2025). doi: 10.5555/rain.hourly.2025.
$ typst compile --input style=apa styles.typ apa.pdf
$ pdftotext apa.pdf - | grep .
Rainfall rose (Iyer & Østergaard, 2024), as Śarmā & Okafor (2023) predicted from
hourly readings (Menon et al., 2025).
Bibliography
Iyer, K., & Østergaard, M. (2024). Monsoon Rainfall Records from Assamese Tea
Gardens. Journal of Invented Climate Studies, 12(3), 41–58. https://doi.org/10.
5555/jics.2024.0312
Menon, D., Iyer, K., Okafor, C., & Rao, S. (2025). Hourly Rain Gauge Readings,
2015–2024 [Computer software]. https://doi.org/10.5555/rain.hourly.2025
Śarmā, A., & Okafor, C. (2023). Forecasting Rainfall with LSTM Networks.
Proceedings of the Fifth Workshop on Invented Data, 101–110. https://doi.org/
10.5555/wid.2023.101
$ typst compile --input style=chicago-author-date styles.typ chicago.pdf
$ pdftotext chicago.pdf - | grep .
Rainfall rose (Iyer and Østergaard 2024), as Śarmā and Okafor (2023) predicted
from hourly readings (Menon et al. 2025).
Bibliography
Iyer, Kavya, and Mikkel Østergaard. 2024. “Monsoon Rainfall Records from
Assamese Tea Gardens.” Journal of Invented Climate Studies 12 (3): 41–58.
https://doi.org/10.5555/jics.2024.0312.
Menon, Deepa, Kavya Iyer, Chidi Okafor, and Sunita Rao. 2025. Hourly Rain
Gauge Readings, 2015–2024. Released. https://doi.org/10.5555/rain.hourly.2025.
Śarmā, Anand, and Chidi Okafor. 2023. “Forecasting Rainfall with LSTM
Networks.” In “Proceedings of the Fifth Workshop on Invented Data.” Special
issue, Proceedings of the Fifth Workshop on Invented Data, 101–10. https://doi.
org/10.5555/wid.2023.101.
$ typst compile --input style=rain-short.csl styles.typ own.pdf
$ pdftotext own.pdf - | grep .
Rainfall rose (Iyer & Østergaard 2024), as Śarmā & Okafor (2023) predicted from
hourly readings (Menon et al. 2025).
Bibliography
Iyer, K. & Østergaard, M. 2024. Monsoon Rainfall Records from Assamese Tea
Gardens.
Menon, D., Iyer, K., Okafor, C., & Rao, S. 2025. Hourly Rain Gauge Readings, 2015–
2024.
Śarmā, A. & Okafor, C. 2023. Forecasting Rainfall with LSTM Networks.
$ typst compile --input style=vancouver styles.typ vancouver.pdf
warning: style `"vancouver"` has been deprecated in favor of `"nlm-citation-sequence"`
┌─ styles.typ:6:33
│
6 │ #bibliography("refs.bib", style: style)
│ ^^^^^
$ typst compile --input style=ieee-transactions styles.typ unknown.pdf
error: unknown style: ieee-transactions
┌─ styles.typ:6:33
│
6 │ #bibliography("refs.bib", style: style)
│ ^^^^^
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 styles_session.py
The four builds printed the same three works four ways. IEEE gave [1] to [3] in order of citation with initials;
APA and Chicago author-date gave the names and years, (Iyer & Østergaard, 2024) and (Iyer and Østergaard 2024),
sorted the list by name, and wrote DOIs as https://doi.org/ addresses. rain-short.csl is a CSL file written for this
lesson: the specification asks for a <style> root with its class and version, an <info> block with a title, an id
and an update time, and a <citation> with a <layout>, and lets a <bibliography> follow. Its citation layout put
names and year in parentheses and its et-al-min="3" shortened the four people of the dataset to
Menon et al. 2025, so a journal’s own CSL file works the same way: save it next to the document and give its path.
Two last commands show what Typst 0.15 does with style names. vancouver still worked but warned that it
has been deprecated in favor of "nlm-citation-sequence", one of the renames of the 0.15.0 changelog, made to follow
the CSL project’s names; an unknown name, ieee-transactions, stopped with unknown style.
Proof-read a style you have not used before
The APA build labelled the dataset [Computer software], and Chicago printed Released. for it and named the paper’s
proceedings twice, the second time after Special issue. A style is a program that reads your data its own way: read
every list once in each style a document uses.
Checkpoint
Check: Built-in styles and CSL files
Forms change what a citation prints
@key prints the style’s normal citation. #cite takes a form for the other uses a sentence has, and @key[…] adds
a supplement, such as a page. The same seven citations in APA and in IEEE:
#set page(width: 15cm, height: auto, margin: 1cm)
#let style = sys.inputs.at("style", default: "apa")
+ normal: rainfall rose @iyer2024monsoon.
+ with a page: rainfall rose @iyer2024monsoon[p. 45].
+ two at once: rainfall rose @iyer2024monsoon @rao2021typesetting.
+ prose: #cite(<sarma2023lstm>, form: "prose") trained a model.
+ author and year: #cite(<rao2021typesetting>, form: "author") wrote it in #cite(<rao2021typesetting>, form: "year").
+ full: #cite(<rao2021typesetting>, form: "full")
+ listed, not printed: the portal#cite(<rainportal2026>, form: none) is open.
#bibliography("refs.bib", style: style) from session import files, run
# Seven citations of forms.typ in APA, then in IEEE: the style decides how each form looks.
files("refs.bib", "forms/forms.typ")
run(r"""
typst compile --input style=apa forms.typ apa.pdf
pdftotext apa.pdf - | grep .
typst compile --input style=ieee forms.typ ieee.pdf
pdftotext ieee.pdf - | grep .
""") Output
$ typst compile --input style=apa forms.typ apa.pdf $ pdftotext apa.pdf - | grep . 1. normal: rainfall rose (Iyer & Østergaard, 2024). 2. with a page: rainfall rose (Iyer & Østergaard, 2024, p. 45). 3. two at once: rainfall rose (Iyer & Østergaard, 2024; Rao, 2021). 4. prose: Śarmā & Okafor (2023) trained a model. 5. author and year: Rao wrote it in 2021. 6. full: Rao, S. (2021). Typesetting for Scientists. Example Academic Press. 7. listed, not printed: the portal is open. Bibliography Example Rainfall Portal. (2026,). Daily Rainfall Data for Kerala. https://example. com/rain/kerala Iyer, K., & Østergaard, M. (2024). Monsoon Rainfall Records from Assamese Tea Gardens. Journal of Invented Climate Studies, 12(3), 41–58. https://doi.org/10. 5555/jics.2024.0312 Rao, S. (2021). Typesetting for Scientists. Example Academic Press. Śarmā, A., & Okafor, C. (2023). Forecasting Rainfall with LSTM Networks. Proceedings of the Fifth Workshop on Invented Data, 101–110. https://doi.org/ 10.5555/wid.2023.101 $ typst compile --input style=ieee forms.typ ieee.pdf $ pdftotext ieee.pdf - | grep . 1. normal: rainfall rose [1]. 2. with a page: rainfall rose [1, p. 45]. 3. two at once: rainfall rose [1], [2]. 4. prose: A. Śarmā and C. Okafor [3] trained a model. 5. author and year: S. Rao wrote it in 2021. 6. full: [2] S. Rao, Typesetting for Scientists. Pune: Example Academic Press, 2021. 7. listed, not printed: the portal is open. Bibliography [1] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea Gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024, doi: 10.5555/jics.2024.0312. [2] S. Rao, Typesetting for Scientists. Pune: Example Academic Press, 2021. [3] A. Śarmā and C. Okafor, “Forecasting Rainfall with LSTM Networks,” in Proceedings of the Fifth Workshop on Invented Data, 2023, pp. 101–110. doi: 10.5555/wid.2023.101. [4] Example Rainfall Portal, “Daily Rainfall Data for Kerala.” Accessed: Oct. 04, 2026. [Online]. Available: https://example.com/rain/kerala
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 forms_session.py
| Written | APA printed | IEEE printed |
|---|---|---|
@iyer2024monsoon |
(Iyer & Østergaard, 2024) |
[1] |
@iyer2024monsoon[p. 45] |
(Iyer & Østergaard, 2024, p. 45) |
[1, p. 45] |
@iyer2024monsoon @rao2021typesetting |
(Iyer & Østergaard, 2024; Rao, 2021) |
[1], [2] |
#cite(<sarma2023lstm>, form: "prose") |
Śarmā & Okafor (2023) |
A. Śarmā and C. Okafor [3] |
form: "author" and form: "year" |
Rao and 2021 |
S. Rao and 2021 |
form: "full" |
the whole entry | the whole entry, with its number |
form: none |
nothing; the portal is still listed | nothing; still listed as [4] |
The cite reference describes prose as the form for weaving a citation into the text, the counterpart of biblatex’s
\textcite and natbib’s \citet; #set cite(form: "prose") would make it the default for what follows. Two adjacent
citations, separated by a space, became one parenthesis in APA and two brackets in IEEE. Note the & in APA’s prose
form: biblatex-apa’s \textcite printed Śarmā and Okafor (2023) in this module’s biblatex lesson, so check which
your style guide wants in running text.
A key with a slash in angle brackets
#cite(<my/key-2020>) is not a valid label. The cite reference says to write a key with such characters as
#cite(label("my/key-2020")).
Checkpoint
Check: Citation forms and supplements
Several bibliographies, one numbering or several
Typst 0.15 lets a document hold several bibliographies, its changelog says, and gives each citation to exactly one.
The bibliography reference gives the order of the rules: the first list whose target selects the citation;
otherwise the nearest following list without a target of its own that holds its key; otherwise the nearest preceding
such list. A second parameter, group, decides which lists share their numbers. Three files try the rules:
several/by-chapter.typ
#set page(width: 15cm, height: auto, margin: 1cm)
= Rain
Records @iyer2024monsoon and a model @sarma2023lstm.
#bibliography("refs.bib", title: [Sources for Rain])
= Data
The portal @rainportal2026 and the readings @rainset2025.
#bibliography("refs.bib", title: [Sources for Data]) several/restart.typ
#set page(width: 15cm, height: auto, margin: 1cm)
#set bibliography(group: none)
= Rain
Records @iyer2024monsoon and a model @sarma2023lstm.
#bibliography("refs.bib", title: [Sources for Rain])
= Data
The portal @rainportal2026 and the readings @rainset2025.
#bibliography("refs.bib", title: [Sources for Data]) several/datasets.typ
#set page(width: 15cm, height: auto, margin: 1cm)
// A box with a list of its own: the context block is the box, so here() is the box and the list takes only the
// citations inside it.
#let data-box(body) = context block(stroke: 0.5pt, inset: 6pt)[
#body
#bibliography("refs.bib", title: [Datasets and web pages], target: selector(cite).within(here()))
]
= Report
Records @iyer2024monsoon and a model @sarma2023lstm.
#data-box[The readings @rainset2025 and the portal @rainportal2026.]
#bibliography("refs.bib", title: [Works cited]) from session import files, run
# Two lists that each take the citations before them (numbers continue), the same with group: none (numbers restart),
# and a list that takes only the citations inside its box through target.
files("refs.bib", "several/by-chapter.typ", "several/restart.typ", "several/datasets.typ")
run(r"""
typst compile by-chapter.typ
pdftotext by-chapter.pdf - | grep -E "^\[|^Records|^The portal|^Sources"
typst compile restart.typ
pdftotext restart.pdf - | grep -E "^\[|^Records|^The portal|^Sources"
typst compile datasets.typ
pdftotext datasets.pdf - | grep .
""") Output
$ typst compile by-chapter.typ $ pdftotext by-chapter.pdf - | grep -E "^\[|^Records|^The portal|^Sources" Records [1] and a model [2]. Sources for Rain [1] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea [2] A. Śarmā and C. Okafor, “Forecasting Rainfall with LSTM Networks,” in The portal [3] and the readings [4]. Sources for Data [3] Example Rainfall Portal, “Daily Rainfall Data for Kerala.” Accessed: Oct. 04, [4] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly Rain Gauge Readings, 2015– $ typst compile restart.typ $ pdftotext restart.pdf - | grep -E "^\[|^Records|^The portal|^Sources" Records [1] and a model [2]. Sources for Rain [1] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea [2] A. Śarmā and C. Okafor, “Forecasting Rainfall with LSTM Networks,” in The portal [1] and the readings [2]. Sources for Data [1] Example Rainfall Portal, “Daily Rainfall Data for Kerala.” Accessed: Oct. 04, [2] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly Rain Gauge Readings, 2015– $ typst compile datasets.typ $ pdftotext datasets.pdf - | grep . Report Records [3] and a model [4]. The readings [1] and the portal [2]. Datasets and web pages [1] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly Rain Gauge Readings, 2015–2024. (2025). doi: 10.5555/rain.hourly.2025. [2] Example Rainfall Portal, “Daily Rainfall Data for Kerala.” Accessed: Oct. 04, 2026. [Online]. Available: https://example.com/rain/kerala Works cited [3] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea Gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024, doi: 10.5555/jics.2024.0312. [4] A. Śarmā and C. Okafor, “Forecasting Rainfall with LSTM Networks,” in Proceedings of the Fifth Workshop on Invented Data, 2023, pp. 101–110. doi: 10.5555/wid.2023.101.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 several_session.py
Follow the citations through the rules:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- No target anywhere. In by-chapter.typ no list has a target, so @iyer2024monsoon, in the Rain chapter, goes to the first list after it that holds its key, Sources for Rain.
- Numbers continue. @rainportal2026 in the Data chapter goes to Sources for Data. Both lists are in the default group, so they share one numbering and it became [3].
- A group of its own. With #set bibliography(group: none), in restart.typ, each list numbers on its own, and the same citation became [1] in Sources for Data.
- A list with a target. In datasets.typ, the box's list has target: selector(cite).within(here()), so it takes the two citations inside the box, @rainset2025 and @rainportal2026, as [1] and [2].
- The rest go on. The two citations before the box are not selected by its target, so they go to the next list without one, Works cited, as [3] and [4]. The numbers follow the order of the lists, not of the text.
- Looking back. A citation with no list after it that holds its key goes to the nearest list before it that does; the reference page gives this as the last rule.
- No list at all. In no-list.typ, which has no bibliography, @iyer2024monsoon stopped the compile with the error that the label <iyer2024monsoon> does not exist in the document.
With lists in the default group, numbers continued from chapter to chapter, [3] and [4] in the second;
#set bibliography(group: none) made each list start at [1]. The box of datasets.typ took its own citations through
target: selector(cite).within(here()), inside a context block, because here() is a contextual function, which its
reference says can only be used when the context is known. Its list printed first, so its works took [1] and [2],
and the citations before the box became [3] and [4]: the numbers follow the lists, not the text. That is correct
by the rules and odd to read, so give such a box group: none, or put lists in the order a reader meets them.
Key idea
Each citation goes to one list, by target first and then the nearest list that holds its key, and the lists of a group share one numbering in the order they are printed.
Checkpoint
Check: Several bibliographies and their numbering
The same .bib file, printed by Typst and by biblatex
Typst reads .bib files with its own parser and formats them with CSL styles, so the same entries do not print the
same as with Biber and a biblatex style. The four citations of refs.bib in biblatex’s IEEE style, the biblatex-ieee
package, and in Typst’s, then the article stored in sentence case and printed by four Typst styles:
compare/compare.tex
\documentclass{article}
\usepackage[style=ieee,backend=biber]{biblatex}
\addbibresource{refs.bib}
\begin{document}
Records \autocite{iyer2024monsoon}, a model \autocite{sarma2023lstm}, the portal \autocite{rainportal2026}
and the readings \autocite{rainset2025}.
\printbibliography
\end{document} compare/compare.typ
#set page(width: 15cm, height: auto, margin: 1cm)
Records @iyer2024monsoon, a model @sarma2023lstm, the portal @rainportal2026
and the readings @rainset2025.
#bibliography("refs.bib", style: "ieee") compare/sentence.bib
% The article of refs.bib with its title in sentence case: capitals only where the title needs them, braced.
@article{iyer2024monsoon,
author = {Iyer, Kavya and Østergaard, Mikkel},
title = {Monsoon rainfall records from {Assamese} tea gardens},
journal = {Journal of Invented Climate Studies},
year = {2024},
volume = {12},
number = {3},
pages = {41--58},
} from session import files, run
# The same four citations of refs.bib in biblatex's IEEE style and in Typst's, then the article stored in sentence
# case, printed by four of Typst's styles.
files("refs.bib", "compare/compare.tex", "compare/compare.typ", "compare/sentence.bib", "compare/sentence.typ")
run(r"""
pdflatex -interaction=nonstopmode compare.tex | grep -E "Output"
biber compare | grep -E "Found [0-9]+ citekeys"
pdflatex -interaction=nonstopmode compare.tex | grep -E "Output"
pdftotext compare.pdf - | grep .
typst compile compare.typ
pdftotext compare.pdf - | grep .
typst compile --input style=ieee sentence.typ s-ieee.pdf
pdftotext s-ieee.pdf - | grep .
typst compile --input style=apa sentence.typ s-apa.pdf
pdftotext s-apa.pdf - | grep .
typst compile --input style=chicago-author-date sentence.typ s-chicago.pdf
pdftotext s-chicago.pdf - | grep .
typst compile --input style=mla sentence.typ s-mla.pdf
pdftotext s-mla.pdf - | grep .
""") Output
$ pdflatex -interaction=nonstopmode compare.tex | grep -E "Output" Output written on compare.pdf (1 page, 28302 bytes). $ biber compare | grep -E "Found [0-9]+ citekeys" INFO - Found 4 citekeys in bib section 0 $ pdflatex -interaction=nonstopmode compare.tex | grep -E "Output" Output written on compare.pdf (1 page, 70444 bytes). $ pdftotext compare.pdf - | grep . Records [1], a model [2], the portal [3] and the readings [4]. References [1] K. Iyer and M. Østergaard, “Monsoon rainfall records from Assamese tea gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024. doi: 10.5555/jics.2024.0312 [2] A. Śarmā and C. Okafor, “Forecasting rainfall with LSTM networks,” in Proceedings of the Fifth Workshop on Invented Data, 2023, pp. 101–110. doi: 10.5555/wid.2023.101 [3] Example Rainfall Portal. “Daily rainfall data for Kerala,” Accessed: Oct. 4, 2026. [Online]. Available: https://example.com/rain/kerala [4] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly rain gauge readings, 2015–2024, 2025. doi: 10.5555/rain.hourly.2025 1 $ typst compile compare.typ $ pdftotext compare.pdf - | grep . Records [1], a model [2], the portal [3] and the readings [4]. Bibliography [1] K. Iyer and M. Østergaard, “Monsoon Rainfall Records from Assamese Tea Gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024, doi: 10.5555/jics.2024.0312. [2] A. Śarmā and C. Okafor, “Forecasting Rainfall with LSTM Networks,” in Proceedings of the Fifth Workshop on Invented Data, 2023, pp. 101–110. doi: 10.5555/wid.2023.101. [3] Example Rainfall Portal, “Daily Rainfall Data for Kerala.” Accessed: Oct. 04, 2026. [Online]. Available: https://example.com/rain/kerala [4] D. Menon, K. Iyer, C. Okafor, and S. Rao, Hourly Rain Gauge Readings, 2015– 2024. (2025). doi: 10.5555/rain.hourly.2025. $ typst compile --input style=ieee sentence.typ s-ieee.pdf $ pdftotext s-ieee.pdf - | grep . [1] K. Iyer and M. Østergaard, “Monsoon rainfall records from Assamese tea gardens,” Journal of Invented Climate Studies, vol. 12, no. 3, pp. 41–58, 2024. $ typst compile --input style=apa sentence.typ s-apa.pdf $ pdftotext s-apa.pdf - | grep . Iyer, K., & Østergaard, M. (2024). Monsoon rainfall records from Assamese tea gardens. Journal of Invented Climate Studies, 12(3), 41–58. $ typst compile --input style=chicago-author-date sentence.typ s-chicago.pdf $ pdftotext s-chicago.pdf - | grep . Iyer, Kavya, and Mikkel Østergaard. 2024. “Monsoon Rainfall Records from Assamese Tea Gardens.” Journal of Invented Climate Studies 12 (3): 41–58. $ typst compile --input style=mla sentence.typ s-mla.pdf $ pdftotext s-mla.pdf - | grep . Iyer, Kavya, and Mikkel Østergaard. “Monsoon Rainfall Records from Assamese Tea Gardens.” Journal of Invented Climate Studies, vol. 12, no. 3, 2024, pp. 41– 58.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compare_session.py
| In the list | biblatex ieee |
Typst ieee |
|---|---|---|
| Heading | References |
Bibliography |
| The article’s title | Monsoon rainfall records from Assamese tea gardens |
Monsoon Rainfall Records from Assamese Tea Gardens |
| The web page’s access date | Accessed: Oct. 4, 2026 |
Accessed: Oct. 04, 2026 |
| The web page | Example Rainfall Portal. “Daily rainfall data for Kerala,” |
Example Rainfall Portal, “Daily Rainfall Data for Kerala.” |
| The dataset | Hourly rain gauge readings, 2015–2024, 2025. |
Hourly Rain Gauge Readings, 2015–2024. (2025). |
| A DOI at the end | doi: 10.5555/jics.2024.0312 |
doi: 10.5555/jics.2024.0312. |
The biggest difference is the case of titles, and the CSL specification explains it. It marks sentence-case
conversion as deprecated and advises storing titles in sentence case, because a program can turn sentence case into
title case reliably but cannot find the proper nouns to go the other way. So Typst’s styles print a title as stored,
and only turn it into title case where the style asks for title case. The sentence-case file proved it: IEEE and APA
printed Monsoon rainfall records from Assamese tea gardens, Chicago and MLA
Monsoon Rainfall Records from Assamese Tea Gardens, all from one stored form with the proper noun braced.
One .bib file for biblatex and Typst
Store titles in sentence case and brace proper nouns and acronyms, Monsoon rainfall records from {Assamese} tea gardens. biblatex’s sentence-case styles and Typst’s print it right, and Typst’s title-case styles convert it. Then
proof-read each list once, as the dataset and the access date show.
Checkpoint
Check: Typst against biblatex, and titles for both
Practise: check a Typst document’s citations
Two small functions cover what this lesson did by hand: finding the keys a Typst document cites that no bibliography
file holds, before the compile stops on them, and turning a .bib article into a Hayagriva entry. Their sample tests
use this lesson’s files, typo.typ among them, and the first hint of each step is open:
Code lab · 2 steps
Check a Typst document's citations
List the keys a Typst document cites that its bibliography file lacks, then convert a .bib article into a Hayagriva entry.
- Find the citations a Typst compile will stop on To do
- Turn a .bib article into a Hayagriva entry To do
Exercise · Medium · Python
Find the citations a Typst compile will stop on
In the lesson, @iyer2024monson, one letter short, stopped the compile with `label <iyer2024monson> does not exist`. A check before the compile can list every such key at once.
Write missing_keys(source, keys) in missing.py. Given the text of a Typst file and the set of keys its bibliography file holds, it returns, in order of first appearance and each once, the cited keys that are neither in keys nor labels the document defines itself. Count as citations:
@name, where a name is letters, digits,_,-,:and., without a.or:at its end, which belongs to the sentence:@iyer2024monsoon.citesiyer2024monsoon; a supplement after it,@iyer2024monsoon[p. 45], is not part of the name;#cite(<name>, …).
A label the document defines is a <name> written in the text, such as = Data <data>; then @data is a reference to that heading, not a citation. The <name> inside #cite(…) defines nothing.
Starter code · missing.py
import re
def missing_keys(source, keys):
"""Return the cited keys of a Typst source that are neither in `keys` nor labels it defines, in order, once each."""
# Replace this line: find @name and #cite(<name>) citations, and the <name> labels the text defines.
return [] The sample tests · test_missing.py
from missing import missing_keys
KEYS = {"iyer2024monsoon", "sarma2023lstm", "rao2021typesetting", "rainportal2026", "rainset2025"}
def test_the_lessons_typo():
"""finds the misspelt key of typo.typ"""
source = "Rainfall rose in the gardens @iyer2024monson.\n\n#bibliography(\"refs.bib\")\n"
assert missing_keys(source, KEYS) == ["iyer2024monson"]
def test_all_present():
"""finds nothing when every key is in the file"""
source = "Rainfall rose @iyer2024monsoon[p. 45], as #cite(<sarma2023lstm>, form: \"prose\") predicted.\n"
assert missing_keys(source, KEYS) == []
def test_cite_call():
"""reads the key of a cite call"""
assert missing_keys("as #cite(<okafor2022rain>, form: \"author\") wrote", KEYS) == ["okafor2022rain"]
def test_heading_label_is_not_a_citation():
"""leaves out a reference to a label the document defines"""
source = "= Data <data>\nSee @data, and the readings @rainset2025.\n"
assert missing_keys(source, KEYS) == []
def test_sentence_end():
"""does not take a full stop or a colon into the key"""
assert missing_keys("as shown by @menon2025gauges: rain rose.", KEYS) == ["menon2025gauges"]
def test_each_once_in_order():
"""reports each missing key once, in order of first appearance"""
source = "@zeta2020 and @alpha2021, then @zeta2020 again, and #cite(<alpha2021>)."
assert missing_keys(source, KEYS) == ["zeta2020", "alpha2021"] A hint
Work in three passes. Collect the keys of #cite(<…> and blank those calls out of the text with re.sub; collect the labels the rest defines with <([\w:.-]+)>; then go through the @ references and the cite keys in order of position, with rstrip(".:") on each @ name.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Exercise · Medium · Python
Turn a .bib article into a Hayagriva entry
The lesson's refs.yml holds the article of refs.bib in Hayagriva's shape: the journal is a parent of type periodical, each person is a string, and the DOI is a serial number. Write article(fields) in hayagriva.py. Given the fields of a .bib article as a dictionary of strings, it returns the Hayagriva entry as a dictionary:
typeis"article", andtitleanddate(fromyear) are copied;authoris the list of names, split atandbetween spaces outside braces, each without its outer braces, so that{Example Rainfall Portal}becomesExample Rainfall Portal, while a name with braces inside it, such as{\O}stergaard, Mikkel, keeps them;page-rangeispageswith--written as-;serial-numberis{"doi": …}when there is a DOI;parentis{"type": "periodical", "title": …}fromjournal(orjournaltitle), withvolume, andissuefromnumber, when they are there.
A field the entry lacks is left out, not given as an empty string.
Starter code · hayagriva.py
import re
def article(fields):
"""Return the Hayagriva entry of a .bib article given as a dictionary of its fields."""
# Replace this line: build type, title, author, date, page-range, serial-number and parent from the fields.
return {"type": "article"} The sample tests · test_hayagriva.py
from hayagriva import article
IYER = {
"author": "Iyer, Kavya and Østergaard, Mikkel",
"title": "Monsoon Rainfall Records from {Assamese} Tea Gardens",
"journal": "Journal of Invented Climate Studies",
"year": "2024",
"volume": "12",
"number": "3",
"pages": "41--58",
"doi": "10.5555/jics.2024.0312",
}
def test_the_lessons_article():
"""converts the article of refs.bib"""
assert article(IYER) == {
"type": "article",
"title": "Monsoon Rainfall Records from {Assamese} Tea Gardens",
"author": ["Iyer, Kavya", "Østergaard, Mikkel"],
"date": "2024",
"page-range": "41-58",
"serial-number": {"doi": "10.5555/jics.2024.0312"},
"parent": {"type": "periodical", "title": "Journal of Invented Climate Studies", "volume": "12", "issue": "3"},
}
def test_missing_fields_are_left_out():
"""leaves out the fields an entry lacks"""
fields = {"author": "Thomas, Ruth", "title": "Long Spells", "journal": "Invented Weather Letters", "year": "2018"}
assert article(fields) == {
"type": "article",
"title": "Long Spells",
"author": ["Thomas, Ruth"],
"date": "2018",
"parent": {"type": "periodical", "title": "Invented Weather Letters"},
}
def test_journaltitle_alias():
"""reads the journal from journaltitle too"""
fields = {"author": "Shah, Meera", "title": "Wet Spells", "journaltitle": "Invented Weather Letters", "year": "2020"}
assert article(fields)["parent"] == {"type": "periodical", "title": "Invented Weather Letters"}
def test_braced_organisation():
"""removes the outer braces of an organisation"""
fields = {"author": "{Example Rainfall Portal} and Menon, Deepa", "title": "Gauges", "journal": "J", "year": "2026"}
assert article(fields)["author"] == ["Example Rainfall Portal", "Menon, Deepa"]
def test_braces_inside_a_name():
"""keeps braces that belong to a name, such as a LaTeX accent"""
fields = {"author": "{\\O}stergaard, Mikkel and Iyer, Kavya", "title": "Short Spells", "journal": "J", "year": "2017"}
assert article(fields)["author"] == ["{\\O}stergaard, Mikkel", "Iyer, Kavya"]
def test_single_page():
"""keeps a single page as it is"""
fields = {"author": "Rao, Sunita", "title": "Notes", "journal": "J", "year": "2021", "pages": "7"}
assert article(fields)["page-range"] == "7" A hint
Build the dictionary step by step and add a key only if its field is present. For the names, split with re.split(r"\s+and\s+", author) (the tests put no and inside braces), then take the braces off each name that both starts with { and ends with }.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Last, the questions on Typst’s bibliographies, Hayagriva, styles, forms, several lists and the comparison with biblatex: every one explains its answer, and the session starts with your weakest idea:
Practice that adapts to you
Practise this lesson
18 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): Typst reads both .bib and Hayagriva files. When would you write Hayagriva?
Both give the same output: in this lesson, the text of two PDFs built from refs.yml and from refs.bib was
identical. The choice is about the data. Hayagriva is YAML designed for Typst: a journal or a book is a nested parent
entry with its own fields, each person is one string in a list, so no and can split an organisation’s name, and dates
are ISO dates. That makes it easy to write and read by hand or from a script. A .bib file is what BibTeX, Biber and
reference managers such as Zotero read and write, so it is the right choice whenever the same data also feeds a LaTeX
document or comes from a manager. Write Hayagriva for a project only Typst will ever read, and keep .bib otherwise.
Two harder questions follow: a journal’s CSL file whose output does not match its guide, and moving a paper’s bibliography from biblatex to Typst. Plan your first step before you open each one:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A journal sends its CSL file for your Typst paper. The rendered list follows the journal's guide except in three ways: article titles appear in title case where the guide wants sentence case, a dataset is labelled as software, and one name is printed wrongly. Find each cause and fix it without editing the journal's style.
-
Mid to senior
A lab moves its reports from LaTeX with biblatex's IEEE style to Typst, keeping the same .bib file of 400 entries. The first Typst build differs from the old PDFs in titles, dates and headings. Plan the move so that the reports still meet the IEEE guide, and say how you prove it.
Key takeaways
@keycites and#bibliographyloads and prints in one compile; a key that no list holds, or a citation in a document without a list, stops the compile.- Hayagriva is Typst’s YAML format, with nested parents and one string per person; it prints what the same
.bibdata prints, and a.bibfile stays the choice when LaTeX or a manager shares the data. styletakes a built-in name or a CSL file’s path, so a journal’s CSL style works by path, andsys.inputslets the command line choose it; renamed styles warn and unknown ones stop.formgives prose, author, year, full or no citation, and@key[…]a page, with the style deciding how each looks.- Several bibliographies take citations by target, then the nearest list with the key, and a group shares one
numbering in print order;
group: nonerestarts it. - Typst’s CSL styles print titles as stored and only turn them into title case, so store titles in sentence case with braced proper nouns for a file that biblatex and Typst share, and proof-read each style’s list.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
How does Typst cite and print a bibliography?
@key cites, and #bibliography("refs.bib") loads the data and prints the list where it stands, in one compile with no other program. The default style is IEEE.
-
What does Typst do with a key that no bibliography holds?
It stops the compile: label does not exist in the document. A citation in a document with no bibliography at all stops it too.
-
How do you list an entry in Typst without citing it in the text?
#cite(<key>, form: none) for one entry, or #bibliography("refs.bib", full: true) for every entry of the file.
-
What is Hayagriva, and when do you choose it?
Typst's YAML bibliography format, with journals and books as nested parents and one string per person. Choose it for data only Typst reads; keep a .bib file when LaTeX or a reference manager shares it.
-
How do you use a journal's own CSL style in Typst?
Save the .csl file next to the document and give its path as the style parameter of #bibliography; built-in styles are named, such as "apa" or "ieee".
-
What does #cite(<key>, form: "prose") print?
A citation woven into the sentence, such as Śarmā & Okafor (2023) in APA, like biblatex's \textcite; @key[p. 45] adds a page to an ordinary citation.
-
How do several Typst bibliographies share or restart their numbers?
Lists of the default group share one numbering, in the order they are printed; #set bibliography(group: none) makes each list start at [1].
-
Why store .bib titles in sentence case for Typst?
CSL styles print a title as stored and only ever convert it to title case, because a program cannot find the proper nouns to lower the rest. Brace the proper nouns as well.
Terms in this lesson
- CSL
- Citation Style Language: an XML format for citation styles. A CSL file describes how citations and the reference list look, and Typst, Zotero and other programs read it.
- Hayagriva
- Typst's own bibliography format, a YAML file in which each entry has a type, a person is one string and a journal or book is a nested parent entry.
References
- Typst reference: bibliography (Typst)
- Typst reference: cite (Typst)
- Typst 0.15.0 changelog (Typst)
- Typst reference: sys (Typst)
- Typst reference: here (Typst)
- The Hayagriva YAML file format (Typst)
- Citation Style Language 1.0.2 specification (Citation Style Language project)
- biblatex-ieee: IEEE style files for BibLaTeX (CTAN)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress