Here’s a snippet to debug bibliography entries by showing what CSL actually receives.
Example usage
#import "debug-cite.typ": debug-cite
#debug-cite(<key>)
#bibliography("path/to/your.bib") // or *.yaml
Full code
#set page(height: auto, width: 300pt, margin: 15pt)
#import "debug-cite.typ": debug-cite
#debug-cite(<key>)
#bibliography(bytes(
```yaml
key:
type: video
title: Title of Episode
author:
- Creator
- Another contributor
director: Director
date: 2161
note: Season 1, Episode 1.
archive: Streaming Platform
url: https://example.com
parent:
title: Title of TV Series
author: Production company
```.text,
))
Why?
Biliography sources will be converted multiple times. Occasionally it is really difficult to predict which type and what variables the CSL style will receive.
Therefore, it would be helpful if we can inspect the bibliography entry.
The citegeist package tells you what biblatex thinks, and this debug-cite snippet tells you what citationberg thinks.
Let’s take the entry type for an example.
mermaid.js code
flowchart TB
subgraph source
bib
yaml
end
subgraph typst
biblatex
hayagriva
citationberg
end
bib["“@type” in *.bib file"]
-->|🔍 citegeist| biblatex["biblatex::EntryType<br>(33 types + 1 Unknown)"]
--> hayagriva["hayagriva::types::EntryType<br>(30 types)"]
-->|🔍 debug-cite| citationberg["citationberg::taxonomy::Kind<br>(45 CSL types)"]
--> output["Output<br>(PDF/SVG/PNG/HTML)"]
yaml["“type: …” in *.yaml file"] <-->|identical| hayagriva
csl[CSL style] --> output
-
We write a certain type in a BibLaTeX
*.bibfile.Say,
@video. -
When typst loads the
*.bibfile using thebiblatexcrate, the type is classified into one of the 34biblatex::EntryTypes.In this case,
@videobecomesbiblatex::EntryType::Unknown("video"). -
Now typst uses the
hayagrivacrate to manage bibliography, and further converts it to one of the 30hayagriva::types::EntryTypes.Here, all biblatex’s unknown type turn into hayagriva’s
EntryType::Misc. Although hayagriva does have aEntryType::Videovariant,Unknown("video")is no exception. -
Finally typst passes data to the
citationbergcrate, and typesets the biliography with a CSL style. The CSL file might leverage<if type="…">for conditional rendering. The CSL specification defines 45 types, socitationberg::taxonomy::Kindhas 45 variants.Guess which citationberg’s type will match hayagriva’s
EntryType::Misc?Kind::Document, and alsoKind::Webpageif it hasurl.Note that the above only applies to
*.bib. If we use*.yamlinstead, then there’s always a single match, which isKind::MotionPictureif the video does not have aparent(not an episode in a series), orKind::Broadcastif it has.
(@_@)
Test code for the above behaviour
#import "debug-cite.typ": debug-cite
= BibTeX
#debug-cite(<youtube-bib>)
= YAML
#debug-cite(<youtube-yaml>)
= YAML + parent
#debug-cite(<youtube-yaml-parent>)
#bibliography((
bytes(
```yaml
youtube-yaml:
type: video
author: John Doe
title: How to Use BibLaTeX for Video Citations
date: 2025-03-15
url:
value: https://www.youtube.com/watch?v=abc123def456
date: 2025-10-23
youtube-yaml-parent:
type: video
author: John Doe
title: How to Use BibLaTeX for Video Citations
date: 2025-03-15
url:
value: https://www.youtube.com/watch?v=abc123def456
date: 2025-10-23
parent:
title: A list
```.text,
),
bytes(
```bib
@video{youtube-bib,
author = {John Doe},
title = {How to Use BibLaTeX for Video Citations},
date = {2025-03-15},
url = {https://www.youtube.com/watch?v=abc123def456},
urldate = {2025-10-23},
}
```.text,
),
))
Definition of debug-cite
// The lists of types and varieables are copied from the CSL spec.
// https://docs.citationstyles.org/en/v1.0.2/specification.html
#let types = (
"article",
"article-journal",
"article-magazine",
"article-newspaper",
"bill",
"book",
"broadcast",
"chapter",
"classic",
"collection",
"dataset",
"document",
"entry",
"entry-dictionary",
"entry-encyclopedia",
"event",
"figure",
"graphic",
"hearing",
"interview",
"legal_case",
"legislation",
"manuscript",
"map",
"motion_picture",
"musical_score",
"pamphlet",
"paper-conference",
"patent",
"performance",
"periodical",
"personal_communication",
"post",
"post-weblog",
"regulation",
"report",
"review",
"review-book",
"software",
"song",
"speech",
"standard",
"thesis",
"treaty",
"webpage",
)
#let non-number-standard-variables = (
"abstract",
"annote",
"archive",
"archive_collection",
"archive_location",
"archive-place",
"authority",
"call-number",
"citation-key",
"citation-label",
"collection-title",
"container-title",
"container-title-short",
"dimensions",
"division",
"DOI",
"event",
"event-title",
"event-place",
"genre",
"ISBN",
"ISSN",
"jurisdiction",
"keyword",
"language",
"license",
"medium",
"note",
"original-publisher",
"original-publisher-place",
"original-title",
"part-title",
"PMCID",
"PMID",
"publisher",
"publisher-place",
"references",
"reviewed-genre",
"reviewed-title",
"scale",
"source",
"status",
"title",
"title-short",
"URL",
"volume-title",
"year-suffix",
)
#let number-standard-variables = (
"chapter-number",
"citation-number",
"collection-number",
"edition",
"first-reference-note-number",
"issue",
"locator",
"number",
"number-of-pages",
"number-of-volumes",
"page",
"page-first",
"part-number",
"printing-number",
"section",
"supplement-number",
"version",
"volume",
)
#let date-variables = (
"accessed",
"available-date",
"event-date",
"issued",
"original-date",
"submitted",
)
#let name-variables = (
"author",
"chair",
"collection-editor",
"compiler",
"composer",
"container-author",
"contributor",
"curator",
"director",
"editor",
"editorial-director",
"editor-translator",
"executive-producer",
"guest",
"host",
"illustrator",
"interviewer",
"narrator",
"organizer",
"original-author",
"performer",
"producer",
"recipient",
"reviewed-author",
"script-writer",
"series-creator",
"translator",
)
#let match-type = (
```xml
<text value='"type": ['/>
<group delimiter=", ">
[[inner]]
</group>
<text value="], "/>
```
.text
.replace(
"[[inner]]",
{
let template = ```xml
<choose><if type="[[type]]">
<text value="[[type]]" prefix='"' suffix='"' />
</if></choose>
```.text
types.map(t => template.replace("[[type]]", t)).join()
},
)
)
#let match-standard-variable(group, variables) = {
```xml
<text value='"[[group]]": { '/>
<group delimiter=", ">
[[inner]]
</group>
<text value=" }, "/>
```
.text
.replace("[[group]]", group)
.replace("[[inner]]", {
let template = ```xml
<text variable="[[variable]]" prefix='"[[variable]]": "' suffix='"' />
```.text
variables.map(v => template.replace("[[variable]]", v)).join()
})
}
#let match-date-variable = {
```xml
<text value='"date-variable": { '/>
<group delimiter=", ">
[[inner]]
</group>
<text value=' }, '/>
```
.text
.replace(
"[[inner]]",
date-variables
.map(
v => `<date variable="[[v]]" form="numeric" prefix='"[[v]]": "' suffix='"' />`
.text
.replace("[[v]]", v),
)
.join(),
)
}
#let match-name-variable = {
```xml
<text value='"name-variable": { '/>
<group delimiter=", ">
[[inner]]
</group>
<text value='} '/>
```
// There might be a typst bug for the trailing suffix.
.text
.replace(
"[[inner]]",
name-variables
.map(
v => `<names variable="[[v]]" prefix='"[[v]]": "' suffix='"' />`
.text
.replace("[[v]]", v),
)
.join(),
)
}
#let csl = (
```xml
<?xml version='1.0' encoding='utf-8'?>
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0">
<info>
<title>Which CSL</title>
<id>https://typst.app/universe/package/which-csl</id>
</info>
<macro name="debug">
<group prefix="{ " suffix=" }">
[[match-type]]
[[match-standard-variables]]
[[match-date-variable]]
[[match-name-variable]]
</group>
</macro>
<citation>
<layout>
<text macro="debug" />
</layout>
</citation>
<bibliography>
<layout>
<text value="This CSL style is only intended for citations, not bibliography." />
</layout>
</bibliography>
</style>
```
.text
.replace("[[match-type]]", match-type)
.replace(
"[[match-standard-variables]]",
(
match-standard-variable(
"non-number-standard-variables",
non-number-standard-variables,
),
match-standard-variable(
"number-standard-variables",
number-standard-variables,
),
).join(),
)
.replace("[[match-date-variable]]", match-date-variable)
.replace("[[match-name-variable]]", match-name-variable)
)
#let debug-cite(key, pretty-print: true, ..args) = {
show: body => if pretty-print {
// Convert the URL to plain text
show link: it => it.body
// Parse as JSON
show regex(".+"): it => json(bytes(it.text))
body
} else {
body
}
cite(key, style: bytes(csl), ..args)
}
Links and future plan
This snippet is inspired by How to parse BibTex "@incollection" or Hayagriva "type: anthos, parent-type: anthology" entry in CSL? - #3 by DiegoCasas.
It was developed while I was debugging How do I determine which type a legislation bibentry will have in CSL? and Is there a way to cite TV Shows and Episodes with the MLA style?.
I may publish it as a typst package later.
However, I seem to have found a bug of typst for nested <group suffix="}"> in CSL. I won’t publish it until it’s further tested.
Update: Confirmed. Nested group's suffixes may get trimmed · Issue #411 · typst/hayagriva · GitHub

