A zero-dependency Vanilla JavaScript library for parsing, filtering, extracting, and explaining bibliographic MARC (MAchine Readable Cataloging) records. Works in browsers and Node.js with no build step.
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 JavaScript 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 (via a light bridge), and a command-line tool.
- Batch processing — Parse entire
.mrcfiles or piped records, not just single entries.
JsMarc is not published on npm. It is designed to be used directly from the source.
git clone https://github.com/corbin-c/jsmarc.gitNo npm install, no build step, no bundler required.
Import modules directly from the source or a hosted URL. No installation needed.
import * as MarcParser from "https://corbin-c.github.io/jsmarc/src/parser.js";
import * as MarcHelper from "https://corbin-c.github.io/jsmarc/src/helper.js";Use the marc-node executable directly (requires Node.js ≥ 12).
# Optionally symlink it to your PATH
ln -s /path/to/jsmarc/marc-node /usr/local/bin/marc-nodeThe Node.js bridge (src/ESrequire.js) adapts ES module exports to CommonJS require() so the same modules run in both environments.
import { parseRecord } from "https://corbin-c.github.io/jsmarc/src/parser.js";
import { explainRecord } from "https://corbin-c.github.io/jsmarc/src/helper.js";
const record = parseRecord(rawMarcString);
const explained = await explainRecord(record, "marc21");
console.log(explained);const MarcParser = require("./src/ESrequire.js")("./src/parser.js");
const MarcHelper = require("./src/ESrequire.js")("./src/helper.js");
const record = MarcParser.parseRecord(rawMarcString);
const explained = await MarcHelper.explainRecord(record, "marc21");
console.log(explained);# Fetch records and display with field explanations
curl "https://web-z3950.herokuapp.com/?server=lx2.loc.gov:210/LCDB&isbn=0066620724&format=usmarc" | ./marc-node display - --format=marc21marc-node COMMAND FILE [OPTIONS]
If FILE is -, the tool reads from stdin.
| 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://web-z3950.herokuapp.com/?server=lx2.loc.gov:210/LCDB&isbn=0066620724,0596001312&format=usmarc" | ./marc-node display - --format=marc21Limit display to specific fields:
./marc-node display /path/to/records.mrc --fields=856\$uExtract fields as JSON:
./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:
./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 | ./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.js # Main MARC record parser
│ ├── helper.js # Field explanation & search via definitions
│ ├── CLI.js # Terminal display utilities
│ └── ESrequire.js # Node.js ES module → CommonJS bridge
├── marc-node # Node.js CLI executable
├── formats.json # Registry mapping format names to definition files
├── test.js # Node.js test suite
├── test.html # Browser test page
├── rec.mrc # Sample record file
└── 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:
./marc-node display records.mrc --format=myformat
A full-featured web interface is hosted at corbin-c.github.io/jsmarc/app/. It supports batch record parsing, filtering, data extraction (HTML table or JSON), and field explanation on hover. The app uses Workerify to run JsMarc in Web Workers for non-blocking processing of large batches.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes — keep it Vanilla JS, no dependencies
- Run the test suites (
node test.js, opentest.htmlin a browser) - 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.
- 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
- Workerify for powering the web app's off-thread processing
- Web-Z3950 by the same author, used in CLI examples for fetching live records