skip to content
Site header image Nerdy Momo Cat

Webtrotion 2.0: Footnotes, Citations, and JSON5 Config

Added footnotes and BibTeX citations to Webtrotion. Now you can add all your tangents and cite everyone on the internet without making readers sit through it. They can just hover if they care.

Last Updated:

tl;dr

Webtrotion (github link) now renders proper footnotes and supports inline citations with BibTeX. You get hover previews and backlinks, and you keep the optional sitewide "All Footnotes" index (as before). Everything renders at build time and you control it all from one config file.

I finally shipped two things I've wanted since day one: footnotes [a] and citations [1]. These have been on my list forever, and I'm really happy to have them working now. These are the first and probably only pseudo blocks I wish Notion had [b], so I ended up creating them myself [c]. They work across posts and pages, play nicely with dark/light themes, and slot right into Webtrotion's interlinked content system. No client-side JS btw. Just clean static output.

Why I Built This

Footnotes make the page calmer. They keep the main thread clean while preserving context and side-quests. And I do have many side quests. Citations are the other half: you get to attribute ideas properly, link sources, and still keep readers in the flow with hover previews and a clean reference section at the end. You could just use footnotes for citing stuff, but I like my bib files, so I built proper citation parsing too.

All of this renders statically during the build, which is how I like it.

Moving to JSON5 Config

One more thing: Webtrotion's configuration file now uses JSON5 format. This means you can finally have comments directly in your config file [d]. The file is now called constants-config.json5 instead of constants-config.json.

Breaking change: This is a new version and isn't backwards compatible. You will have to fill in the keys in the new constants-config.json5 file. Don't try to just copy-paste your old values because the key names have changed too.

JSON5 lets you:

  • Add inline comments to explain what each setting does
  • Use trailing commas without breaking things
  • Write cleaner, more readable config files

The configuration structure has also changed significantly. Instead of a flat structure, settings are now organized into logical sections like notion, site-info, collections-and-listings, theme, tracking, comments, block-rendering, shortcodes, and auto-extracted-sections. This makes the config much more navigable, especially as the file has grown to support more features.

How Footnotes Work in Webtrotion

Handling footnotes in digital writing can be a pain. You want the context without derailing the paragraph. Webtrotion's build‑time footnote system lets you choose the authoring style that fits your workflow.

Configuration: footnotes

Here's the relevant section from constants-config.json5:

// === Footnotes Configuration ===
// Settings for both manual (sitewide) and automatic (in-page) footnotes.
footnotes: {
	// The slug for a single, dedicated page that contains all footnotes for the entire site.
	// This is a legacy method for manual footnote management.
	"sitewide-footnotes-page-slug": "_all-footnotes",
	// Settings for the modern, automatic in-page footnote system.
	"in-page-footnotes-settings": {
		enabled: true,
		// Defines where the system should look for footnote content.
		// Important: Only ONE of these source options can be 'true' at a time.
		source: {
			// Looks for definitions at the end of a text block, like `[^ft_1]: My footnote content.`Sepearate it by two soft new lines (using shift+enter). This is the method that will work when you export Notion content to Markdown.
			"end-of-block": true,
			// Treats the first n set of child block(s) indented under a parent block as the footnote content. (n = number of footnote markers in the parent block)
			"start-of-child-blocks": false,
			// Uses Notion comments on a block as footnote content.
			// Requires granting "Read comments" permission to your Notion integration. Else falls back to 'end-of-block' method.
			"block-comments": false,
			// Not yet implemented.
			"block-inline-text-comments": false,
		},
		// The prefix for footnote markers in your Notion text. e.g., `[^ft_1]` for a prefix of `ft_`.
		"marker-prefix": "ft_",
		// If true, a collated list of all footnotes will be automatically generated at the bottom of the page.
		"generate-footnotes-section": true,
		// (Recommended) On large screens, displays footnotes in the margin. On smaller screens, it falls back to a popover.
		"show-in-margin-on-large-screens": true,
	},
},

Key Settings for Footnotes

  • sitewide-footnotes-page-slug: Legacy manual collector (e.g., _all-footnotes). The in‑page system is recommended for most occurences.
  • enabled: Toggle in‑page footnotes.
  • source: Pick exactly one of the three modes as defined Webtrotion 2.0: Footnotes, Citations, and JSON5 Config.
  • marker-prefix: Default is ft_. Change to note_ if you want markers like [^note_1].
  • generate-footnotes-section: Auto‑collate the footnote section at page bottom (also generates backlinks with popovers to where the footnotes came from).
  • show-in-margin-on-large-screens: Shows footnotes in the margin on large screens, falls back to popovers on smaller screens. Set to false for popovers everywhere.

Pick One Source Mode

Configure a single source under footnotes → in-page-footnotes-settings → source in constants-config.json5. Only one should be true at a time.
  1. The Markdown‑friendly way (end-of-block)

    Portability first. If you export to Markdown later, your footnotes will still make sense. Put the definitions at the end of the block, separated by two newlines (made using Shift+Enter).

    In Notion, this is some text with a footnote[e]. It's a really interesting topic.

    In Notion, this is some text with a footnote[^ft_desc]. It's a really interesting topic.
    
    [^ft_desc]: This is the content of the footnote. It can be long detailed and have links.
  2. The structured way (start-of-child-blocks)

    Prefer a tidier Notion outline or want footnotes with varying kinds of content? Indent child blocks as the footnote content. For n markers in a block, the first n child block(s) become the note. These child blocks can contain any block type and can have their own nested children.

    This is how you’ll write it. Some text with a first footnote [^ft_a1] and a second footnote [^ft_b1].
    [^ft_a1]: This is the content of the footnote, written in a child block.

    And this will be part of the footnote

    [^ft_b1]: This is the second footnote

    And this child is not a footnote.

    Note: The above will not be rendered because my config is set to end-of-block.
  3. The Notion‑native way (block-comments)
    Permissions: Your Notion integration needs "Read comments" permission for this mode to work.

    Use Notion comments as your footnote source to keep the notion document even more cleaner and you can have attachments in your footnotes this way. Highlight text/block [^ft_a2] with the marker definition [f], add a comment, and start it with your marker definition. Comments can include attachments, which will be rendered in the footnote.

    ℹ️
    Note: The above will not be rendered because my config is set to end-of-block.

Choose Your Own Marker Prefix

The marker-prefix setting is just a namespace for your footnote markers. By default it's ft_, so your markers look like [^ft_1], [^ft_note] [g].

Why have a prefix at all? Because if you're using standard Markdown footnote syntax elsewhere (like [^1]), you don't want collisions. The prefix keeps Webtrotion's footnotes separate. You can change it to note_ or fn_ or anything you want. Just keep it consistent across your site.

Markers inside code blocks or inline code are not processed.

Maybe Show a Collated Footnote Section at The End of Page

Set generate-footnotes-section to true and Webtrotion will automatically collect all your footnotes and dump them in a neat little section at the bottom of the page. It's like endnotes in a website or a white paper. Each one gets a popover based backlink so readers can jump back to where the footnote was mentioned.

If you set it to false, footnotes only exist as inline popovers or margin notes. No bottom section at all. I keep mine on because some people like to scroll down and read all the tangents at once.

Choose Footnote Display Mode

Set show-in-margin-on-large-screens to control how footnotes appear.

Margin notes on large screens (show-in-margin-on-large-screens: true): This is the option I use. On desktop or tablets with enough space, footnotes appear in the right margin — like marginalia. On phones, they fall back to popovers because there's no margin space.

Always popover (show-in-margin-on-large-screens: false): Every footnote marker, when hovered (on larger screens) or clicked (on smaller screens), shows a little popover overlay. Simple, consistent across all screen sizes.

The Older Sitewide Footnotes Page

This is the legacy method, from before I built the automatic in-page system. You'd create a single dedicated Notion page (like "All Footnotes") and manually add your footnote content there, then reference it from other pages. Set the slug in sitewide-footnotes-page-slug.

The in-page system is way better — it's automatic, it's per-page, and you don't have to maintain a giant footnote index manually. I'm keeping the sitewide option around for backwards compatibility, as well as for situations where you need to reference the same “footnote” at multiple places.

These footnotes won’t be collected at the bottom. They are ideal for when you just want popovers for something. Like, please buy me a coffee.

Citations with BibTeX

For students, researchers, and anyone who writes with sources, the citation system should be useful. Manage citations in a standard .bib file and have them rendered with in-text markers and an optional bibliography section. All processing happens at build time, and you get hover or click previews just like with footnotes.

For example, I can cite where the tufte style footnotes came from, they originated here [1].

Configuration: citations

Here's the relevant section from constants-config.json5:

// === Citations Configuration ===
"auto-extracted-sections": {
  citations: {
    // If true, a "Cite this page" section will be added to each page.
    "add-cite-this-post-section": false,
    // Configure how BibTeX citations are extracted and processed.
    "extract-and-process-bibtex-citations": {
      // If true, enables BibTeX citation processing.
      enabled: false,
      // Supported .bib file sources: GitHub Gist, GitHub repo file, Dropbox, and Google Drive public share links.
      "bibtex-file-url-list": [
        // Example: "https://gist.githubusercontent.com/username/gist-id/raw/file.bib"
      ],
      // The pattern for in-text citations. Only one format is supported at a time.
      "in-text-citation-format": "[@key]", // or "\cite{key}" or "#cite(key)"
      // The citation style for the bibliography. Only one format is supported at a time.
      "bibliography-format": {
        "simplified-ieee": true, // For ICML/NeurIPS style
        apa: false, // For ACL-style academic writing
      },
      // If true, a collated bibliography section will be automatically generated at the bottom of the page.
      "generate-bibliography-section": false,
    },
  },
}

Key Settings for Citations

  • add-cite-this-post-section: Auto‑add a "Cite this page" block with a pre‑formatted reference, so that people can add your post to their own bibtex file.
  • enabled: Turn on BibTeX processing.
  • bibtex-file-url-list: One or more .bib file URLs. Supports GitHub Gist, GitHub repo files, Dropbox, and Google Drive public share links. Note: Google Drive and Dropbox links don't provide public last-updated timestamps, so these files will be fetched and processed on every build.
  • in-text-citation-format: Choose your marker pattern. Supports [@key] (pandoc), \cite{key} (LaTeX), or #cite(key) (Typst).
  • bibliography-format: simplified-ieee (for ICML/NeurIPS style) or apa (for ACL-style academic writing).
  • generate-bibliography-section: Auto‑add a bibliography to the page.

Add "Cite this page" Section

Set add-cite-this-post-section to true if you want Webtrotion to automatically add a "Cite this page" section at the end of each page. This generates a pre-formatted BibTeX entry for the page itself, making it easy for readers to cite your work in their own papers or bibliographies.

The generated entry includes your page title, author (from your config), the publication date, and the URL. Handy if you're publishing research notes, technical articles, or anything citation-worthy. I keep mine off because most of my posts are informal, but if you're writing academic content, turn it on.

Enable BibTeX citation processing

This is the master switch. Set enabled to true under extract-and-process-bibtex-citations to turn on the entire citation extraction system. If this is false, none of the citation processing happens — no parsing, no formatting, no bibliography generation. Everything else in this section only matters if this is true.

Simple on/off toggle. Turn it on when you need citations, leave it off if you don't.

The bibtex-file-url-list is where you tell Webtrotion where to find your .bib files. You can have one or multiple files, and they need to be publicly accessible URLs.

Supported Hosting Options for .bib Files

"bibtex-file-url-list": [
  "https://gist.githubusercontent.com/username/gist-id/raw/file.bib",
  "https://gist.github.com/username/gist-id",
  "https://github.com/username/repo/raw/main/references.bib",
  "https://www.dropbox.com/s/xyz/references.bib?dl=0",
  "https://drive.google.com/uc?export=download&id=FILE_ID",
],

GitHub Gist (recommended): Supports smart caching via last-modified timestamps. Use the raw URL or the Gist page URL.

GitHub repo file (recommended): Same smart caching as Gist. Use the raw URL or the file URL.

Dropbox: Public share links work (dl=0 or dl=1). No last-modified timestamp, so it fetches every build.

Google Drive: Use the export URL format shown above. Fetches every build like Dropbox.

Why use Dropbox or Google Drive? Better bib can auto-sync from Zotero to these.

Performance Note

Webtrotion doesn't parse your entire 6,000-entry .bib file and convert it all to JSON. That was the first version and it crashed immediately. Instead, it only parses and caches the entries actually cited on each page.

On subsequent builds, if the .bib file hasn't changed (based on last-modified timestamp for GitHub sources), Webtrotion skips the fetch entirely. This keeps build times fast even with large bibliographies.

Dropbox and Google Drive don't provide public last-modified timestamps, so those get fetched on every build. If build speed matters and you have a large .bib file, use GitHub or Gist instead.

Example BibTeX File

Here's what a typical .bib file looks like:

@book{sweig42,
  title     = {The Impossible Book},
  author    = {Stefan Sweig},
  year      = 1942,
  month     = mar,
  publisher = {Dead Poet Society}
}

@article{steward03,
  title     = {Cooking behind bars},
  author    = {Martha Steward},
  year      = 2003,
  publisher = {Culinary Expert Series}
}

@book{impossible,
  title     = {The impossible book},
  author    = {Stefan Sweig},
  year      = 1942,
  month     = mar,
  publisher = {Dead Poet Society}
}

You can use any standard BibTeX entry types: @article, @book, @inproceedings, @misc, etc. Webtrotion parses the common fields (author, title, year, publisher, journal, booktitle, etc.) and formats them appropriately.

Choose your Citation Syntax

The in-text-citation-format setting controls which syntax you use in your Notion text to mark citations. You have three options, and they all produce the same output — pick whichever feels natural.

Standard Markdown/pandoc style ([@key]): If you're used to writing in Markdown or use pandoc elsewhere, this will feel familiar. This is also supported by Typst. Example: [@sweig42]

LaTeX style (\cite{key}): For the LaTeX users, muscle memory is real. Example: \cite{sweig42}

Typst style (#cite(key)): If you use Typst for typesetting, this is especially handy because using the @ symbol in Notion triggers the annoying date/page popup. Example: #cite(sweig42)

The key is whatever you defined in your BibTeX entry. For example, if your .bib file has:

@book{sweig42,
  title     = {The Impossible Book},
  author    = {Stefan Sweig},
  year      = 1942,
  month     = mar,
  publisher = {Dead Poet Society}
}

Then you'd cite it in Notion as [@sweig42], \cite{sweig42}, or #cite(sweig42), depending on which format you chose in your config.

Markers inside code blocks or inline code are not processed.

During the build, Webtrotion will:

  1. Find all citation markers in your Notion content
  2. Look up each key in your .bib files
  3. Format the in-text citation based on your chosen bibliography-format
  4. Collect and de-duplicate all citations for the bibliography section

Pick your bibliography style

The bibliography-format setting controls how citations are formatted both in-text and in the bibliography. You have two options:

simplified-ieee: Numbered citations in square brackets. In-text: [1], [2], etc. Good for NeurIPS, ICML style. The bibliography lists entries in citation order with [1], [2] prefixes (de-duplicated for multiple occurences).

apa: Author-year style. In-text: [Sweig, 1942], [Steward, 2003]. Better for humanities and social sciences, ACL-style papers. The bibliography lists entries alphabetically by author.

Both formats get hover previews. Readers can click or hover over [1] or [Sweig, 1942] to see the full reference in a popover, and then click through to jump to the bibliography if you have generate-bibliography-section enabled.

Generate the bibliography section

Set generate-bibliography-section to true and Webtrotion collects all citations from the page and builds a formatted bibliography at the end. Each entry gets backlink buttons that jump you to where you cited it — same UX as footnotes, but better because one source can be cited multiple times.

If you set it to false, citations still format properly and you still get hover previews, just no collected references section. Good for informal writing where you want to drop paper links without the formality of a bibliography.

The "Back to Content" button

When you click a citation to jump to the bibliography, you get a dynamic "back to content" button that takes you right back to where you were reading. I hate one-way jumps in PDFs, so I made sure this works smoothly.

How It Was Built

I built this with AI help — mostly Claude Code for the heavy lifting (core implementation, complex logic) and Gemini-CLI for cleanup, refactoring, and writing this post. Claude Code handled the architecture well. Gemini-CLI is less useful for big-picture stuff but fine for smaller tasks.

Some bugs that came up:

  • The Greedy Regex: At one point, the system was miscounting footnote markers. The regular expression was too greedy and was matching the markers in the content definitions (e.g., [^ft_a]:) as well as the markers in the text ([^ft_a]). A little tweak with a negative lookahead ((?!:)) fixed that.
  • Extracting and Removing Footnote Content: When pulling footnote definitions from end-of-block text or Notion comments, I had to carefully extract the content and then rebuild the block array without those definitions. This required precise text manipulation to avoid breaking the rendered content structure.
  • The Ghost Popover on Mobile: This was a hair-pulling bug. On mobile, the citation popovers would appear, but you couldn't click on anything inside them. Clicks were just passing right through to the content behind it. After many false leads (was it a Tailwind class? Padding? Z-index?), I discovered the solution was to reuse the exact same Astro component that was already working for other popovers (NBlocksPopover) and ensure the DOM structure was identical, right down to an empty <span> with a special data attribute. I still do not know why the original error was happening.
  • Performance Boost: Initially, I had three separate functions that traversed the entire block tree to find footnotes, citations, and interlinked content. This was inefficient. I refactored this into a single, unified extractPageContent function that collects everything in one pass, making the build process significantly faster.
  • The "Back to Citation" Button: After getting the core feature working, I realized that jumping to the bibliography was a one-way trip. And I always hate that when I read papers in pdf. So, I added the "Back to citation" button that dynamically appears on the bibliography entry you jumped from, making for a much smoother user experience.
  • The BibTeX → JSON Cache Overflow: Claude Code had it initially set up such that it tried to convert and cache the entire .bib file into JSON. When I tested it with 6,000 entries, everything crashed. I had to walk the AI through fixing that to only parse the citations actually used in each page.
  • Same id for footnote/citation popovers: Claude Code fixed this immediately (gemini-cli went on a wild unproductive chase). This is because I [h] was assigning random ids to normal popovers but not here.

It was a great learning experience, and it's incredibly rewarding to see these powerful features come to life in Webtrotion.


Footnotes

  1. [a]
    Just like these ones!
  2. [b]
    Well, maybe besides universal page blocks and universal task blocks.
  3. [c]
    I've done something similar with html injects before using shortcodes.
  4. [d]
    I should have done this from the very start rather than relying on readme or webpages for configuration help.
  5. [e]
    This is the content of the footnote. It can be long and detailed and have links.
  6. [f]
    We need this because Notion API only returns block ids, not the text span that was commented on. When that is fixed, I’ll implement block-inline-text-comments.
  7. [g]
    Or whatever your heart desires ❤️
  8. [h]
    The term I here is claude code, but I approved that code, so 🤷‍♀️