Skip to content

METS identifiers

Every element in a METS file the platform writes carries an ID attribute, and the structural elements cross-reference each other by those IDs. This page explains, in plain terms, how those IDs are made, what you can and cannot rely on, and what is changing.

It complements The METS files we write (the full document shape) and What the METS parser can read (how third-party METS is read).

An ID is a fixed prefix telling you what kind of thing it identifies, followed by the resource’s path within the deposit:

Prefix Identifies Example
PHYS_ a div in the physical structMap PHYS_objects/photo.tif
FILE_ a file in the fileSec FILE_objects/photo.tif
ADM_ an amdSec (administrative metadata) ADM_objects/photo.tif
TECH_ the techMD inside that amdSec TECH_objects/photo.tif
DMD_ a dmdSec (descriptive metadata) DMD_objects/photo.tif
digiprovMD_ClamAV_ a virus-scan event record digiprovMD_ClamAV_ADM_objects/photo.tif

The point of this scheme is human readability: open the XML and every element tells you what it is and what it belongs to. There is one authority for spelling them in code: MetsIds.

The METS schema types ID attributes as xs:ID, which must be an NCName — and an NCName may not contain /, spaces, and various other characters that file paths contain freely. So PHYS_objects/my file.pdf is doubly invalid (a slash and a space), and a validating parser will reject the document. This is issue #188.

A space in an ID causes a second, subtler problem. Reference attributes like ADMID and DMDID are typed IDREFS — a whitespace-separated list of IDs. An ID that contains a space is therefore indistinguishable, to standard tooling, from two references. Typed deserialisers split it in half.

The fix is sequenced so that every METS file already written stays readable and editable, unchanged, indefinitely. That promise shapes everything:

  1. Step 1: IDs stopped being load-bearing. The platform no longer navigates by ID text. It builds a path cache from the metadata that genuinely records where things are — premis:originalName for directories, FLocat/@xlink:href for files — and treats the ID as an opaque label. Reference attributes are resolved tolerantly on both the writing and parsing stacks: the whole attribute value is tried first (which is exactly how a legacy space-containing ID matches), then each whitespace-separated token (which is how a genuine multi-reference list resolves).

  2. Step 2: new IDs are schema-valid. Newly written entries encode the path part with XmlConvert.EncodeLocalName/ becomes _x002F_, a space becomes _x0020_ — so PHYS_objects/my file.pdf is now minted as PHYS_objects_x002F_my_x0020_file.pdf. The encoding is reversible, but nothing decodes it: the path is read from the metadata, never from the ID. Existing entries keep the IDs they were born with, so a document edited after step 2 can legitimately contain both forms side by side. Because of step 1, nothing cares.

    Two consequences worth knowing. A new ID is a single IDREFS token, so the split-in-half problem above simply does not arise for anything written from now on. And when the platform writes a reference into an older document — an fptr, an smLink — it looks up the target element’s real ID and copies it, rather than minting what the ID “should” be; otherwise it would write references to IDs that document does not contain.

  3. Step 3: deliberate migration. A normalisation rewrites every illegal ID — and every reference to it, including whitespace-split IDREFS tokens and the xlink attributes that carry IDs — into the encoded form, producing character-for-character what the platform would mint today. IDs that are already legal are never touched, whoever minted them, which is what makes the operation safe to run repeatedly and keeps it away from IDs that are not ours to renumber. A document that already carries the same ID on two elements is refused rather than normalised, so that a visible corruption is not turned into an invisible one. It runs three ways: a UI action on a deposit, the POST /deposits/{id}/mets/normalise endpoint, and a bulk migration tool (src/mets-id-migration/) that works through the preserved corpus creating a METS-only new OCFL version per Archival Group, with the Activity Stream event suppressed — the version changes how the object is recorded, not what it holds, so downstream IIIF rebuilds are deliberately not triggered. While the FeatureFlags:NormaliseMetsIdsOnWrite flag is enabled, writing a managed METS also normalises any illegal IDs still in the document. Once the migration campaigns complete, the legacy forms are gone.

An ID like PHYS_objects_x002F_annual_x0020_report.pdf still carries the file’s path, and that is deliberate. Two audiences want different things from an identifier, and this scheme serves both:

  • For a person reading the METS — now, or in fifty years, with no access to this platform — an ID that says which file it is beats an opaque PHYS_7 or a GUID. Preservation metadata is meant to be readable without the software that wrote it.
  • For the platform it also comes free of charge: a deposit-relative path is already unique within a document, so deriving the ID from the path makes it unique without any allocation, counter or registry. A GUID scheme would need the minting sites to coordinate; this one does not.

But no code may infer anything from the string value of an ID. Not the path, not the file it belongs to, not its position. Paths are read from FLocat/@xlink:href and premis:originalName, which are the ground truth in every ID era and in third-party METS alike; references are followed by matching the raw string against ID attributes.

Note what that means: because the ID genuinely does contain the path, this is a discipline, not a property the format enforces. Nothing stops a reader parsing it. The rule binds the platform’s own code exactly as much as it binds anyone else’s — and it is the reason the platform navigates by a path cache built from originalName/FLocat, rather than by reconstructing the ID it thinks a path ought to have.

Two places in the platform still break the rule, both knowingly, and both scoped to legacy content: navigation falls back to the pre-step-2 PHYS_ + raw-path convention when a document’s own path metadata will not resolve, and the virus-scan reader falls back to matching digiprovMD IDs by convention when a file’s ADMID resolves to no usable technical metadata. Both exist only to serve documents written before step 2, and both become removable once the step 3 migration campaigns have run everywhere.

The rules, if you consume or produce this METS

Section titled “The rules, if you consume or produce this METS”
  • Treat IDs as opaque. Never parse a path out of an ID, however clearly it appears to contain one — see Opaque to code, legible to people. The path lives in FLocat/@xlink:href (files) and premis:originalName (directories) — those are the ground truth, in both ID eras.
  • Resolve references by the raw string. To follow an ADMID/DMDID/FILEID/smLink reference, match the attribute value against ID attributes exactly. If the whole value matches nothing and contains whitespace, try each token. (This is what the platform’s own reader does.)
  • Copy IDs verbatim when writing references. A reference must be the target element’s actual ID, character for character — never reconstructed from a path or a naming convention. This is the rule the platform follows internally too — it is the single most likely way to corrupt a mixed-era document.
  • Don’t assume uniqueness of shape. Legacy IDs contain slashes and spaces; new ones contain _x002F_/_x0020_ escapes; third-party METS (Goobi, Archivematica, EPrints) follows entirely different conventions (AMD_0001, file-{uuid}, …). All are just strings.
  • Don’t assume one ID per thing, either. Some records accumulate: a file scanned twice has two digiprovMD events, the second identified digiprovMD_ClamAV_2_ADM_… — the occurrence number sits between the prefix and the identifier, precisely so a numbered ID can never be confused with the plain ID of a differently-named file. Read the last one, and see what the platform writes.

All three steps are in the platform: step 1, the IDREFS resolution work and step 2 landed in digital-preservation PRs #211, #213 and #214 (with follow-up fixes in #218 and #220), and step 3 — the normalisation machinery and the bulk migration tool — in #232. The migration campaigns themselves (development, then production) are operational runs of the tool rather than code changes; until they have completed in every environment, documents in both ID forms remain in circulation and all the mixed-form rules on this page stay load-bearing.