6. Extension mechanism
6.1 Naming
Section titled “6.1 Naming”An extension namespace is represented as a nested-mapping path from the document root. The path:
- MUST contain at least 2 segments.
- MUST have a first segment matching
^[a-z][a-z0-9-]*$and that is not a reserved key. - All segments MUST match
^[a-z0-9][a-z0-9-]*$. The first segment additionally MUST start with a letter so that purely numeric values cannot be mistaken for extension namespaces. - The full path, joined by
., MAY be the reverse of a domain the producer controls, optionally followed by additional segments scoping the extension within that domain; but any unique string following the segment rules is permitted. Reverse-DNS is a RECOMMENDED convention for collision-avoidance, not a requirement — ownership of the corresponding domain is neither verified nor required.
Examples (written as dotted paths; each maps to a YAML nested mapping, and likewise to JSON or TOML):
org.codeberg.alice.notes— extension namespaced under a Codeberg useralice, using theorg.codeberg.<user>convention.com.example.build— a hypothetical build-configuration extension under the neutral placeholderexample.com.com.weird-company.jenkins— Jenkins configuration fromweird-company.com.
Labels com, org, net, io, dev, and other top-level domains are permitted at the first position. For users who do not own a domain, namespacing under a forge where they have an account is a conventional, collision-free choice — org.codeberg.<user> (Codeberg), com.github.<user> (GitHub), com.gitlab.<user> (GitLab). These remain claims and conventions, not assertions of ownership: nothing verifies the account, and no account is required to use the form.
6.2 Cross-format representation (normative)
Section titled “6.2 Cross-format representation (normative)”The same extension namespace expressed in each encoding:
com: example: build: type: standard image: "ubuntu:24.04"[com.example.build]type = "standard"image = "ubuntu:24.04"{ "com": { "example": { "build": { "type": "standard", "image": "ubuntu:24.04" } } }}All three forms parse to the same in-memory tree. Conversion between encodings is mechanical and lossless at the document level. Producers MUST NOT use a single dotted-string key (e.g., a literal YAML key "com.example.build":) to represent an extension namespace; such forms are rejected (§3.6).
6.3 Consumer obligations
Section titled “6.3 Consumer obligations”A consumer reading a document:
- MUST preserve unknown extension subtrees on any round-trip write (formatting may change, see Section 3.7).
- MUST NOT fail validation on an unknown extension namespace.
- MAY ignore the content of unknown extension namespaces when interpreting the document.
6.4 Producer obligations
Section titled “6.4 Producer obligations”A producer writing a document:
- SHOULD write every reserved field for which it has authoritative information.
- MUST NOT modify, rename, or remove subtrees in extension namespaces it does not own.
- MUST NOT alter values of reserved fields it does not control beyond what its task requires.
- SHOULD document its extension schema at a stable URL and reference it from the non-normative registry (§6.5).
- MAY version its extension independently of this specification (e.g., by including a
versionsubkey under its namespace).
A conformant producer is one that obeys the rules above. The shorthand: write everything you know, change nothing that isn’t yours.
6.5 Registry
Section titled “6.5 Registry”The specification repository maintains a non-normative registry at spec/registry.yaml listing known extension namespaces, their owners, and pointers to documentation. The registry is advisory — there is no gate-keeping. Contributing an entry is a pull request with a one-line purpose; the registry exists for discoverability, not permission.
Entries with a shape key point to an illustrative YAML fragment under spec/shapes/{namespace}.yaml. Shape files are not projectfile documents — they are syntax-valid YAML fragments showing the expected structure of the extension. They have no normative standing; the authoritative shape description is the documentation URL in the entry’s docs field.
A registry entry MAY also carry an optional links sequence — each entry a url, title, and role of consumer (a tool or project that reads the namespace), producer (one that writes or generates it), or reference (related documentation) — so readers can discover which tools work with a given extension namespace. These links are contributed by pull request and are purely advisory: they neither gate nor endorse, and an unrecognized role is rendered without error. This registry-level links surfaces which tools consume or produce an extension namespace and is distinct from the document-level reserved top-level links key (§4.10), which describes a single project’s own navigational URLs (and MAY be tool-generated, e.g., by the projectfile CLI or other generators).
6.6 Overlapping extensions
Section titled “6.6 Overlapping extensions”When two extensions describe the same operational concern (for example, two namespaces describing health-check endpoints), independent producers and consumers risk producing inconsistent or duplicated state. The recommended pattern is:
- Cooperating actors SHOULD agree on a shared extension namespace for the concern (e.g., a community-maintained
org.projectfile.health) and treat vendor-specific namespaces as overlays that reference or extend it. - A consumer encountering both a shared and a vendor-specific namespace SHOULD prefer the shared form for portable interpretation, and consult the vendor namespace only for vendor-specific behavior.
- The registry (§6.5) is the appropriate place to publish such shared namespaces.
This specification reserves no fields for these concerns; coordination is left to the community.
6.7 Extension authors’ guide (non-normative)
Section titled “6.7 Extension authors’ guide (non-normative)”The extension mechanism is the part of projectfile that grows without anyone’s permission, and it costs nothing to start. Invent any namespace you like — no domain ownership, no registration, no committee — picking a reverse-DNS-style string (or org.codeberg.<your-user> if you have nothing else) only so it is unlikely to collide with anyone else’s. Publish your sub-schema at a stable URL and version it independently of this specification, so your tooling can evolve on its own clock. Ship tools that read and write your namespace, and rely on the peaceful cohabitation rule (§3.7): every other conformant tool carries your subtree through untouched, even tools written years before yours existed. Register the namespace in the non-normative registry (§6.5) — a one-line pull request — so others can discover it. That is the whole bargain: claim a name, document it, and your data rides alongside everyone else’s in the same file, forever, without coordination.