A zero-dependency TypeScript library for parsing, filtering, extracting, and explaining bibliographic MARC (MAchine Readable Cataloging) records. Works in browsers and Node.js with no bundler.
JsMarc was created by Clément Corbin to make MARC data accessible from the web and the terminal.
- Parse MARC records — Convert raw ISO 2709 MARC data into structured objects, handling leader, directory, fields, and subfields.
- Filter records — Extract matching records from a batch by field/subfield values (e.g. filter by ISBN).
- Extract data — Pull specific fields from large record sets, with JSON output.
- Explain fields — Annotate parsed records with human-readable labels using standard MARC definition files (MARC21, UNIMARC).
- Search fields — Reverse-lookup field codes by keyword (e.g. search "author" to find all related fields).
- Works everywhere — Browser (ES modules), Node.js (native ESM), and a command-line tool.
- Batch processing — Parse entire
.mrcfiles or piped records, not just single entries.
npm install jsmarcOr for development, clone the repository:
git clone https://github.com/corbin-c/jsmarc.git
cd jsmarc
npm installNo bundler required. The source ships as TypeScript — run it directly with a TypeScript-aware runtime (tsx, ts-node, etc.) or compile it yourself.
Use native ESM imports with a TypeScript runtime such as tsx:
import { parseRecord } from 'jsmarc'
import { explainRecord } from 'jsmarc/helper'import { parseRecord } from 'jsmarc'
import { explainRecord } from 'jsmarc/helper'
const record = parseRecord(rawMarcString)
const explained = await explainRecord(record, 'marc21')
console.log(explained)# Fetch records and display with field explanations
curl "https://z3950.nibroc.fr/?server=lx2.loc.gov:210/LCDB&isbn=0066620724&format=usmarc" | npx marc-node display - --format=marc21marc-node COMMAND FILE [OPTIONS]
If FILE is -, the tool reads from stdin. Run directly with tsx if not using npx:
tsx marc-node.ts COMMAND FILE [OPTIONS]| Command | Description |
|---|---|
display |
Parse and display records (with optional field explanation via --format) |
filter |
Filter records by field/subfield values |
extract |
Extract specific fields as JSON |
help |
Show usage information |
| Option | Syntax | Default | Description |
|---|---|---|---|
--encoding |
string | utf8 |
File encoding when reading from disk |
--record-separator |
string | \u001d |
Character that separates records in the batch |
--field-separator |
string | \u001e |
Field separator within a record |
--subfield-separator |
string | \u001f |
Subfield separator within a field |
--format |
marc21 or unimarc |
— | Enrich display with field/subfield labels from definitions |
--fields |
notation string | * |
Field notation (e.g. 020$a,856$u) — use \ to escape $ in shells |
--values |
comma-separated | — | Values for filtering (quote values containing spaces) |
Display all records with explained fields:
curl "https://z3950.nibroc.fr/?server=lx2.loc.gov:210/LCDB&isbn=0066620724,0596001312&format=usmarc" | npx marc-node display - --format=marc21Limit display to specific fields:
npx marc-node display /path/to/records.mrc --fields=856\$uExtract fields as JSON:
npx marc-node extract /path/to/records.mrc --fields=100\$a,020\$aOutput:
[
{
"leader": "01208cam a22003014a 4500",
"fields": [
{ "code": "020", "indicator": " ", "subfields": [{ "code": "a", "value": "0066620724 (hc)" }] },
{ "code": "100", "indicator": "1 ", "subfields": [{ "code": "a", "value": "Torvalds, Linus," }] }
]
}
]Filter records by value:
npx marc-node filter ./records.mrc --fields=020\$a --values=0596001312,"0066620724 (hc)"Only records whose 020$a matches one of the given comma-separated values are kept. Output is raw MARC.
Pipe from stdin:
cat /path/to/records.mrc | npx marc-node display - --fields=245\$aAll exports from the parser and helper modules.
Parse a raw MARC record string into a structured MarcParser object.
| Parameter | Type | Default | Description |
|---|---|---|---|
recordString |
string |
required | Raw ISO 2709 MARC record |
options.toParse |
string | string[] |
"*" |
Field notation(s) to parse (e.g. "020\$a,856\$u" or ["020\$a"]). "*" parses all fields. |
options.fields |
string |
"\u001e" |
Custom field separator |
options.subfields |
string |
"\u001f" |
Custom subfield separator |
Returns: MarcParser — an object with the following shape:
{
rawRecord: string, // the original record string
leader: string, // first 24 characters
header: string, // leader + directory + field separator
fieldSeparator: string, // field separator used
subfieldSeparator: string,// subfield separator used
parseCode: string, // field notation filter applied
directory: [{ // parsed directory entries
code: string, // field tag (e.g. "001")
length: string, // field length (zero-padded)
position: string // byte offset from start of body (zero-padded)
}],
fields: [{ // parsed fields
code: string, // field tag
value?: string, // raw value (control fields: 001-009)
indicator?: string, // two indicator characters (data fields: 010+)
subfields?: [{ // parsed subfields (data fields only)
code: string, // subfield code (single character)
value: string // subfield data
}]
}]
}Check if a parsed record contains matching values for a given field.
| Parameter | Type | Description |
|---|---|---|
parsedRecord |
MarcParser |
A record returned by parseRecord() |
fieldNotation |
string |
Field notation (e.g. "020\$a") |
values |
string[] |
Values to match against |
Returns: boolean — true if the record matches at least one value.
Parse a field notation string into a predicate function used internally by the parser.
| Parameter | Type | Description |
|---|---|---|
notationString |
string |
Comma-separated field notations (e.g. "020\$a,856\$u") or "*" |
Returns: Function — a filter function (recordPart) => boolean that tests if a record part matches.
Binary-safe string utility for byte-level length and slicing (handles multi-byte characters correctly in both browser and Node.js contexts).
bin.length(str) // → number (byte length)
bin.slice(str, start, end) // → string (byte-level slice)Default configuration template with ISO 2709 separators:
MARC.recordSeparator // "\u001d"
MARC.fieldSeparator // "\u001e"
MARC.subfieldSeparator // "\u001f"The parser class. Always prefer the parseRecord() wrapper unless you need the class directly.
Enrich a parsed record with human-readable labels from MARC definition files.
| Parameter | Type | Description |
|---|---|---|
parsedRecord |
MarcParser |
A record returned by parseRecord() |
format |
string |
Format key from formats.json (e.g. "marc21", "unimarc") |
Returns: Promise<object> — the record with added label properties on fields, subfields, and indicators.
Same as explainRecord but operates on a single field object.
Returns: Promise<object> — the field object with added labels.
Search for field/subfield codes by keyword in the definition files.
| Parameter | Type | Description |
|---|---|---|
searchString |
string |
Keyword to search (e.g. "author", "auteur") |
format |
string |
Format key (e.g. "marc21", "unimarc") |
Returns: Promise<Array<{code: string, value: string}>> — matching code/label pairs.
await searchField('auteur', 'unimarc')
// [
// { code: "200\$c", value: "Titre propre d'un auteur différent" },
// { code: "701\$4", value: "Auteur d'oeuvre adaptée ou continuée" },
// ...
// ]A Promise that resolves to the loaded format definitions registry (from formats.json). Used internally — you typically access definitions through explainRecord or searchField.
jsmarc/
├── app/ # Web application
│ ├── index.html
│ ├── front.js
│ └── style.css
├── definitions/ # MARC format definitions
│ ├── marc21.json
│ └── unimarc.json
├── samples/ # Sample MARC record files
├── src/ # Core source modules
│ ├── parser.ts # Main MARC record parser (TypeScript)
│ ├── parser.test.ts # Vitest test suites
│ ├── helper.ts # Field explanation & search via definitions
│ ├── helper.test.ts
│ ├── CLI.ts # Terminal display utilities (TypeScript)
├── marc-node.ts # Node.js CLI executable (TypeScript, published as bin)
├── formats.json # Registry mapping format names to definition files
├── rec.mrc # Sample record file
├── package.json # npm package definition
├── tsconfig.json # TypeScript configuration
├── vitest.config.ts # Vitest test runner configuration
└── LICENSE # GNU GPL v3
JsMarc ships with definition files for two MARC variants:
| Format | Source | License | Language |
|---|---|---|---|
| MARC21 | Library of Congress | Public domain | English |
| UNIMARC | ABES (Agence Bibliographique de l'Enseignement Supérieur) | CC BY-SA | French |
Definitions provide human-readable labels for every field, subfield, and indicator value. These labels power the explainRecord and searchField APIs as well as the CLI's --format flag.
The formats.json file maps format names to their definition files:
{
"marc21": "definitions/marc21.json",
"unimarc": "definitions/unimarc.json"
}-
Create a JSON file in
definitions/following the existing schema (seedefinitions/marc21.jsonfor reference):{ "010": { "value": "Library of Congress Control Number", "ind1": { "#": "Undefined" }, "ind2": { "#": "Undefined" }, "subfields": { "a": { "*": "LC control number" } } } } -
Add an entry to
formats.json:{ "myformat": "definitions/myformat.json" } -
Use it immediately:
npx marc-node display records.mrc --format=myformat
A full-featured web interface is hosted at corbin-c.github.io/jsmarc/. It supports batch record parsing, filtering, data extraction (HTML table or JSON), and field explanation on hover.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes — keep it dependency-free at runtime
- Run the test suite:
npm test(Vitest) - Run the type checker:
npm run typecheck(TypeScript —tsc --noEmit) - Submit a pull request
For major changes, consider opening an issue first to discuss your approach.
JsMarc is released under the GNU General Public License v3.0. You are free to use, modify, and distribute it under those terms.
The MARC records in samples/ and rec.mrc are provided for demonstration and
educational purposes only. They were obtained from publicly accessible library
catalogs and belong to their respective institutions (Library of Congress,
SUDOC/ABES, OpenEdition, BnF). These files are not part of the JsMarc project
and are not covered by the project's license. If you believe your rights are
infringed, please open an issue.
- Library of Congress for the MARC21 standard and public-domain field definitions
- ABES for the UNIMARC field definitions (CC BY-SA)
- The MARC standards community for decades of cataloging infrastructure
- Web-Z3950 by the same author, used in CLI examples for fetching live records