MARC Primer¶
An introduction to MARC record structure for developers.
What is MARC?¶
MARC (MAchine-Readable Cataloging) is a data format for bibliographic records. Created by the Library of Congress in the 1960s, it remains the standard for library systems worldwide.
A MARC record describes a single item—a book, journal, map, recording, or other material—with structured metadata about its title, author, publisher, subjects, and more.
Why MARC matters:
- Over 200 million records in WorldCat alone
- Standard interchange format for library systems (ILS, OCLC, Z39.50)
- Most library metadata exists in MARC format
Record Structure¶
A MARC record has three parts:
┌─────────────────────────────────────────┐
│ Leader (24 bytes) │
├─────────────────────────────────────────┤
│ Directory (variable length) │
├─────────────────────────────────────────┤
│ Fields (variable length) │
└─────────────────────────────────────────┘
Leader¶
The leader is exactly 24 characters containing record metadata:
| Position | Length | Description |
|---|---|---|
| 00-04 | 5 | Record length |
| 05 | 1 | Record status (n=new, c=corrected, d=deleted) |
| 06 | 1 | Type of record (a=language material, e=map, etc.) |
| 07 | 1 | Bibliographic level (m=monograph, s=serial) |
| 08-09 | 2 | Type of control |
| 10 | 1 | Indicator count (always "2") |
| 11 | 1 | Subfield code count (always "2") |
| 12-16 | 5 | Base address of data |
| 17-19 | 3 | Encoding level, descriptive form, linked record |
| 20-23 | 4 | Entry map (always "4500") |
Python:
leader = record.leader
print(leader.record_status) # 'n', 'c', or 'd'
print(leader.type_of_record) # 'a' for language material
Rust:
let leader = record.leader();
println!("{}", leader.record_status());
println!("{}", leader.type_of_record());
Directory¶
The directory is an index listing each field's tag, length, and starting position. MRRC handles directory parsing automatically—you work directly with fields.
Field Types¶
MARC has two types of fields:
Control Fields (001-009)¶
Control fields contain unstructured data—no indicators or subfields.
| Tag | Name | Example |
|---|---|---|
| 001 | Control Number | ocm12345678 |
| 003 | Control Number Identifier | OCoLC |
| 005 | Date/Time Last Modified | 20240115120000.0 |
| 006 | Fixed-Length Data Elements | (additional material) |
| 007 | Physical Description | (format-specific codes) |
| 008 | Fixed-Length Data Elements | (40 character coded data) |
Python:
Rust:
if let Some(field) = record.field("001") {
if let Some(data) = field.data() {
println!("{}", data);
}
}
Data Fields (010-999)¶
Data fields have a 3-digit tag, two indicators, and one or more subfields.
Indicators¶
Each data field has two single-character indicators that modify the field's meaning:
| Field | Ind1 | Ind2 | Meaning |
|---|---|---|---|
| 245 (Title) | 0-1 | 0-9 | Title added entry; Nonfiling characters |
| 100 (Author) | 0-3 | (blank) | Type of name |
| 650 (Subject) | (blank) | 0-7 | Level; Thesaurus |
Example: 245 14 means:
- Ind1=1: Title added entry
- Ind2=4: Skip first 4 characters when filing ("The " in "The Book")
Subfields¶
Subfields are identified by a delimiter ($ or ‡) and a single-character code:
245 10 $a Programming in Rust :$b a comprehensive guide /$c by Jane Smith.
↑ ↑ ↑
$a = title proper $b = subtitle $c = statement of responsibility
Python:
title_field = record["245"]
if title_field:
print(title_field["a"]) # "Programming in Rust :"
print(title_field["b"]) # "a comprehensive guide /"
print(title_field["c"]) # "by Jane Smith."
print(title_field.ind1) # "1"
print(title_field.ind2) # "0"
Rust:
if let Some(field) = record.field("245") {
println!("{:?}", field.subfield("a"));
println!("{:?}", field.subfield("b"));
println!("Indicators: {} {}", field.indicator1(), field.indicator2());
}
Common Fields¶
Identification¶
| Tag | Name | Subfields |
|---|---|---|
| 001 | Control Number | (control field, no subfields) |
| 010 | LC Control Number | $a number |
| 020 | ISBN | $a ISBN, $q qualifier |
| 022 | ISSN | $a ISSN |
| 035 | System Control Number | $a number |
Main Entry (Author)¶
| Tag | Name | Subfields |
|---|---|---|
| 100 | Personal Name | $a name, $d dates, $e relator |
| 110 | Corporate Name | $a name, $b subordinate unit |
| 111 | Meeting Name | $a name, $d date, $c location |
Title¶
| Tag | Name | Subfields |
|---|---|---|
| 245 | Title Statement | $a title, $b subtitle, $c responsibility |
| 246 | Varying Form of Title | $a title, $b remainder |
| 130/240 | Uniform Title | $a title, $l language |
Publication¶
| Tag | Name | Subfields |
|---|---|---|
| 260 | Publication (older) | $a place, $b publisher, $c date |
| 264 | Production/Publication | $a place, $b name, $c date |
Physical Description¶
| Tag | Name | Subfields |
|---|---|---|
| 300 | Physical Description | $a extent, $b details, $c dimensions |
Subject Access¶
| Tag | Name | Subfields |
|---|---|---|
| 600 | Subject - Personal | $a name, $d dates, $t title |
| 610 | Subject - Corporate | $a name, $b subordinate |
| 650 | Subject - Topical | $a topic, $x general subdivision |
| 651 | Subject - Geographic | $a place, $x subdivision |
Added Entries¶
| Tag | Name | Subfields |
|---|---|---|
| 700 | Added Entry - Personal | $a name, $e relator |
| 710 | Added Entry - Corporate | $a name, $b subordinate |
| 856 | Electronic Location | $u URI, $z note |
Using Convenience Methods¶
MRRC provides shortcuts for common fields:
Python:
# Instead of parsing 245$a yourself:
print(record.title())
# Instead of checking 100, 110, 111:
print(record.author())
# Gets all ISBNs from 020$a:
for isbn in record.isbns():
print(isbn)
Rust:
println!("{:?}", record.title());
println!("{:?}", record.author());
for isbn in record.isbns() {
println!("{}", isbn);
}
Character Encoding¶
MARC records use one of two encodings:
| Encoding | Leader position 09 | Description |
|---|---|---|
| MARC-8 | (blank) | Legacy encoding with escape sequences |
| UTF-8 | a |
Unicode, modern standard |
MRRC handles encoding automatically, converting MARC-8 to UTF-8 when reading.
ISO 2709 Binary Format¶
MARC records are commonly stored in ISO 2709 format (.mrc files):
- Records are concatenated with no separator
- Each record ends with a record terminator (ASCII 29)
- Fields within a record end with a field terminator (ASCII 30)
- Subfields are delimited by ASCII 31
MRRC handles all parsing automatically—you work with structured Record objects.
See Also¶
- Character Encoding Reference - MARC-8 and UTF-8 details
- Python Quickstart - Start working with records
- Rust Quickstart - Rust API introduction
- LOC MARC Documentation - Official MARC21 specifications