npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@behackl/citation-js-extras

v0.1.0

Published

Preserve custom BibTeX fields through citation-js and render academic bibliographies with linked titles, badges, and more.

Readme

@behackl/citation-js-extras

Preserve custom BibTeX fields through citation-js and render academic bibliographies with linked titles, configurable badges, and more.

The problem

citation-js converts BibTeX to CSL-JSON, but silently drops all non-standard fields during the conversion. There is no plugin hook or configuration option to preserve them. Fields like arxiv, mrnumber, publication-status, or project identifiers are lost.

This package solves the problem with a two-pass parsing strategy: one pass extracts the raw BibTeX fields, the other produces CSL-JSON for formatting. The results are merged so you get the best of both worlds.

Install

npm install @behackl/citation-js-extras citation-js
# or
pnpm add @behackl/citation-js-extras citation-js

citation-js is a peer dependency — you bring your own version.

Quick start

import { Bibliography } from "@behackl/citation-js-extras";

const bib = new Bibliography({
  data: "./references.bib", // file path or raw BibTeX string
  cslStyle: "./my-style.csl", // optional: file path, raw XML, or registered template name
  customFields: ["publication-status", "arxiv", "mrnumber"],
});

// Filter and sort
const published = bib.filter({ "publication-status": "published" });
const sorted = bib.sort(published, { by: "year", order: "desc" });

// Render HTML
const html = bib.formatHtml(sorted, {
  titleLink: ["url", "doi", "arxiv"],
  badges: [
    { field: "doi", label: "doi", url: "https://doi.org/$1", className: "badge-doi" },
    {
      field: "arxiv",
      label: "arXiv",
      url: "https://arxiv.org/abs/$1",
      match: /^(.+?)(?:v\d+)?$/,
      className: "badge-arxiv",
    },
  ],
});

API

new Bibliography(options)

| Option | Type | Description | |---|---|---| | data | string | BibTeX input — a raw string or a file path. | | cslStyle | string? | CSL style — a registered template name, raw XML, or a file path. Defaults to 'apa'. | | customFields | string[]? | BibTeX field names to preserve. These appear on each entry under .custom. |

bib.entries

All parsed entries as BibEntry[]:

interface BibEntry {
  csl: Record<string, any>; // CSL-JSON data (for citation-js)
  key: string; // BibTeX citation key
  year: number | null; // extracted from CSL `issued`
  custom: Record<string, string>; // declared custom fields
  raw: Record<string, any>; // all raw BibTeX properties
}

bib.filter(criteria)

Filter entries by custom field values. All criteria must match (AND logic).

bib.filter({ "publication-status": "published" });
bib.filter({ "publication-status": "published", project: "ABC-123" });

bib.sort(entries, options?)

Return a sorted copy of the entries (the input is not mutated).

bib.sort(entries); // by year, descending (default)
bib.sort(entries, { by: "year", order: "asc" });

bib.formatHtml(entries, options?)

Render entries as a complete HTML bibliography list.

bib.formatHtml(entries, {
  titleLink: ["url", "doi", "arxiv"],
  badges: [ /* ... */ ],
  list: "ol", // 'ol', 'ul', or 'div' (div uses <div class="csl-entry"> children)
  listAttributes: { reversed: true },
  linkifyUrls: true,
});

Entries are formatted in one citeproc pass, so style-dependent state (for example numeric labels in Vancouver) remains correct.

bib.formatEntry(entry, options?)

Render a single entry as an HTML string (no list wrapper). The title link targets the actual CSL title text, regardless of italics.

For citation styles that depend on multi-entry context (numbered labels, ibid behavior, etc.), prefer formatHtml(...).

Title links are only created for safe URL schemes (http, https, mailto) or normalized DOI/arXiv links.

Badges

Badges are small inline links appended to each entry. They are configured declaratively:

interface BadgeConfig {
  field: string; // BibTeX field name to read
  label: string; // display text (e.g. "doi", "arXiv")
  url: string; // URL template — $1 is replaced by the field value
  match?: RegExp; // optional: validate/transform the field value
  className?: string; // CSS class(es) for the <a> element
}

The url template uses $1 as a placeholder for the field value:

{ field: "doi", label: "doi", url: "https://doi.org/$1" }
// doi: "10.1234/example" → href="https://doi.org/10.1234/example"

When match is provided, the field value is tested against the regex. If it doesn't match, the badge is skipped. If it matches, $1 in the URL is replaced by the first capture group (or the full match if there are no capture groups):

// Strip version suffix from arXiv IDs:
{ field: "arxiv", label: "arXiv",
  url: "https://arxiv.org/abs/$1",
  match: /^(.+?)(?:v\d+)?$/ }
// "2301.00001v3" → capture group "2301.00001" → href=".../2301.00001"

// Only link if the field looks like a valid identifier:
{ field: "zbl", label: "zbMATH",
  url: "https://zbmath.org/?q=an:$1",
  match: /^(\d+\.\d+)$/ }
// "7654.12345" → match → linked
// "not-a-number" → no match → badge skipped

Badge labels are HTML-escaped before rendering. Generated badge links are emitted only for http(s) and mailto: URLs; unsafe schemes are skipped.

linkifyBareUrls(html)

Standalone utility: auto-linkify bare http(s):// URLs in HTML text nodes that aren't already inside <a>, <script>, or <style> tags. Trailing punctuation is kept outside the link.

import { linkifyBareUrls } from "@behackl/citation-js-extras";

linkifyBareUrls("See https://example.com.");
// → 'See <a href="https://example.com">https://example.com</a>.'

Custom CSL styles

Pass a file path or raw XML to cslStyle. You can also pass the name of any template already registered with citation-js:

const bib = new Bibliography({
  data: bibtex,
  cslStyle: "./styles/my-department.csl",
  customFields: ["publication-status"],
});

The style is registered with citation-js and used for all formatting calls.

Raw CSL XML styles are internally registered under deterministic content-hash names to avoid collisions between multiple Bibliography instances.

How it works

citation-js has a hardcoded list of ~106 BibTeX → CSL field mappings. Any field not in that list is silently dropped. There is no plugin API to extend this mapping.

This package works around the limitation with a two-pass parse:

  1. Cite.plugins.input.chainLink(bibData) — returns raw BibTeX entries with all fields preserved (but no CSL conversion).
  2. new Cite(bibData) — returns CSL-JSON entries (needed for formatted output via citeproc) but with custom fields stripped.

The results are merged by citation key, giving you CSL-formatted output and access to every custom BibTeX field.

License

MIT