Please note that this specification is suitable for pre-production pilot implementations.
Extension Vocabularies
UNTP is a common core meant to be usable in any sector and any jurisdiction, which means it will always be missing terms that some particular sector needs. A battery passport needs cell chemistry, a livestock passport needs a breed, and neither belongs in a dictionary shared across all of trade. Extensions are how those terms get added.
The constraint that makes extensions worth governing is that a credential conforming to an extension must still be a conforming UNTP credential. A buyer in another sector, who has never heard of the extension, has to be able to read everything in the credential that is core, and must not be tripped up by the parts that are not. Extensions are therefore additive: they MAY add properties, they MUST NOT change or remove core ones.
This page covers adding terms. For the governance rules an extension must follow and how to register one, see the Community Activation Register and its registration guide.
Two layers have to agree
A UNTP credential is checked at two layers, and an extension property has to get past both.
JSON Schema decides which properties are allowed on an object. A class with additionalProperties: true accepts properties the schema does not name; a class with additionalProperties: false rejects them.
JSON-LD decides what each property name means, by resolving it to an IRI when the credential is expanded into a graph. A name the context cannot resolve is dropped from the graph, silently in the default mode and as an error in safe mode.
Passing the first and failing the second is the trap. The schema will accept your property and the credential will look valid, while the property vanishes from the graph that consumers actually query.
Where properties may be added
Twenty-one classes are open at the schema layer:
AuthorityEndorsement, Characteristics, Claim, ConformityAssessment, ConformityAttestation, ConformityProfile, Criterion, Dimension, Facility, FacilityVerification, IssuerParty, Location, Material, Package, Party, ProcessDeclaration, Product, ProductVerification, RegisteredIdentity, SchemeDeclaration, ScoringFramework.
The rest are closed. They are value shapes whose meaning is complete, such as Measure, Address, Period, Score, Classification and Link, and adding a property to one would make it ambiguous wherever it is reused. If you need to qualify a closed value, add a property to the open class that contains it rather than to the value itself: a sibling property on Product rather than a new field inside an embedded Measure.
Render methods follow a different specification. SuiteRenderMethod and its template object are defined by the W3C render method specification, which decides what properties they carry.
Three ways a property can end up, and only two of them work
Declare your own vocabulary, and use a prefix
This is the pattern an extension with a published vocabulary should use, and the one extension working groups should document for their implementers. Declare a scoped @context inside the object and prefix the terms:
{
"type": ["Facility"],
"id": "https://facility-register.example.com/fac-002",
"name": "Sample Copper Refinery",
"@context": { "ag": "https://example-industry.org/ag/v1/" },
"ag:shiftPattern": "24/7"
}
ag:shiftPattern expands to https://example-industry.org/ag/v1/shiftPattern, an IRI the extension body owns and can publish a definition at. The property survives expansion in both default and safe mode, and it works on any open class. The prefix is local to the object, and several can coexist in one document.
Product characteristics, for implementers with no vocabulary of their own
Product.characteristics is a documented extension point with a @vocab fallback, so unprefixed keys inside it still resolve:
{
"type": ["Product"],
"id": "https://manufacturer.example/p/42",
"name": "Battery module",
"characteristics": {
"type": ["Characteristics"],
"batteryChemistry": "NMC 811"
}
}
batteryChemistry expands to https://test.uncefact.org/vocabulary/Characteristics/batteryChemistry. The key is preserved in the graph and safe mode passes, so SPARQL and SHACL can target it. What it does not get is a definition: the IRI is minted from the key, and nothing at the other end says what the term means. This is the escape hatch for an implementer with one or two fields and no vocabulary to put them in, not the pattern for an extension that expects others to consume its data.
A bare key anywhere else, which does not work
Adding an unprefixed property to any other open class passes schema validation and then disappears:
{
"type": ["Facility"],
"id": "https://facility-register.example.com/fac-002",
"name": "Sample Copper Refinery",
"shiftPattern": "24/7"
}
The schema accepts it, because Facility is open. Expansion drops it, because the context has no definition for shiftPattern and, outside product characteristics, no fallback to mint one. In safe mode, which is what validate-artefacts runs, expansion fails outright rather than quietly losing the data.
Earlier versions of UNTP gave every open class a @vocab fallback, so a bare key always landed somewhere. That is no longer so: a catch-all turns a typo into a plausible IRI instead of an error, so the fallbacks were removed from the core and kept only at the one place that exists for extension. If you want a property to survive, give it a namespace.
Rules for an extension vocabulary
- Each credential type an extension defines MUST reference a JSON-LD
@contextthat defines every term the extension adds. - An extension
@contextMUST NOT redefine a term from the UNTP context. - Vocabularies an extension references, including its own, SHOULD follow the UN/CEFACT rules for a well governed vocabulary, which cover term lifecycle, versioning in place rather than in the path, and the integrity of published context files. They SHOULD be stable and SHOULD NOT delete terms.
- An extension SHOULD publish a definition at each IRI it mints, for the same reason UNTP does: an identifier that resolves to nothing is only half an identifier.
The second of those is enforced rather than merely asked for. UNTP marks its terms @protected, so an attempt to redefine one is rejected when the context is processed:
Invalid JSON-LD syntax; tried to redefine a protected term.
An implementer cannot accidentally turn name into something else, and a curated term always wins over a fallback.
Licensing
Anyone may build an extension by following this page and use it without restriction. UNTP is UN intellectual property and is free for anyone to use or modify, so nothing described here needs permission, registration, or a conversation with anybody.
Publishing an extension through the Community Activation Register is a different act, and it carries a condition: the extension must be contributed under the licence the Community Activation Programme enforces, usually CC BY 4.0. An extension that others are expected to implement cannot be one they have to negotiate access to, which is the same reasoning that makes UNTP core free to use. The Community Activation Programme describes the terms, and the register describes what registration involves.
Promoting a term into the core vocabulary
An extension term that turns out to be widely useful can be proposed for the shared dictionary. Terms are added to the UN/CEFACT vocabulary through vocab-bsp, published, and only then referenced from a UNTP context, so the order is always vocabulary first.
Promotion changes the term's IRI, from the extension's own namespace or the Characteristics fallback to a published unece: term. That is a breaking change for anyone already consuming the old IRI. Graphs built from credentials issued before the promotion keep the old one, and both will be in circulation for as long as those credentials are. Treat an extension IRI as stable within a context version and subject to change at a major version boundary, which is a reason to prefer a namespace you control over the fallback.
Checking that an extension survives
Run the credential through JSON-LD expansion in safe mode, which is the check that catches a property the schema accepted and the graph lost:
node .claude/scripts/validate-artefacts.mjs
Extensions SHOULD add their sample credentials to the validator's VALIDATIONS list so a regression in the extension is caught the same way as one in the core. A sample that expands cleanly in safe mode, and validates against both the extension schema and the core schema, is the minimum evidence that an extension is interoperable.