4. Reserved fields
4.1 $schema and spec_version
Section titled “4.1 $schema and spec_version”-
$schema- Type: string (URI)
- Cardinality: REQUIRED for YAML and JSON encodings, exactly one. Permitted but redundant in TOML, where it MUST be written as the quoted key
"$schema":$is not a TOML bare-key character, so$schema = …is a syntax error. - Constraint: when present, MUST equal
https://projectfile.org/schema/v1.jsonfor documents conforming to version 1 of this specification.
-
spec_version- Type: string
- Cardinality: REQUIRED for TOML encoding, exactly one. OPTIONAL in YAML and JSON, where
$schemaalready carries the version. - Constraint: MUST equal
"1"for documents conforming to version 1 of this specification.
A document MUST satisfy the discriminator rule of §3.4 in its encoding. Validators MUST reject a YAML or JSON document missing $schema, and MUST reject a TOML document missing spec_version, even when it carries "$schema".
4.2 kind
Section titled “4.2 kind”- Type: string
- Cardinality: OPTIONAL, at most one.
- Default:
SoftwareSourceCode. - Values: a Schema.org type name (drawn from the Schema.org type registry). For software projects, RECOMMENDED values are
SoftwareSourceCode(the default — source code repositories, libraries),SoftwareApplication(end user applications), andWebApplication. Other Schema.org types MAY be used if justified by the project. The vocabulary is intentionally open: consumers MUST NOT reject a document for using an unrecognized type, and the JSON Schema does not constrainkindto a closed enum.
4.3 identity
Section titled “4.3 identity”- Type: mapping
- Cardinality: REQUIRED, exactly one.
- Subkeys:
| Key | Type | Cardinality | Description |
|---|---|---|---|
namespace |
string | REQUIRED | Reverse-DNS identifier of the owning entity. MUST match ^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$. Example: com.acme. |
name |
string | REQUIRED | Project name. MUST match ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ (DNS-label shape). |
version |
string | OPTIONAL | Project version, in the project’s own scheme. A SemVer 2.0.0 string per SemVer is RECOMMENDED; CalVer (24.04), two-part (16.2) and other native schemes are valid. Producers SHOULD treat this field as advisory; the authoritative version of an artifact remains its source-control tag or package-registry record. |
title |
localized-string | OPTIONAL | Human-readable title. See Section 7 for the localized-string type. |
summary |
localized-string | OPTIONAL | Single-sentence headline. SHOULD be ≤ 120 characters and SHOULD NOT contain line breaks. Distinct from description: the summary is the one-liner shown by package indices, app stores, and search results; the description is the longer prose. |
description |
localized-string | OPTIONAL | Longer human-readable description. May be multi-paragraph. |
created |
date | OPTIONAL | ISO-8601 calendar date of project inception. The interpretation of “inception” is producer-defined; conventional choices include the date of first commit, first publication, or directory creation. |
released |
date | OPTIONAL | ISO-8601 calendar date on which the version named in version was released. Producers SHOULD update this field together with version. Consumers generating CITATION.cff or AppStream release entries SHOULD use this field for date-released. |
modified |
date | OPTIONAL | ISO-8601 calendar date of the last meaningful change to the project. Conventional choices include the date of the most recent non-trivial commit or the most recent release, whichever the producer considers more representative. |
visibility |
string | OPTIONAL | Whether the project is meant for public distribution. public when the producer intends it, private when it does not. Default private; see “Absence means private” below. A localized-string is NOT a valid value. |
Absence means private
Section titled “Absence means private”visibility is OPTIONAL and its absence is private: a consumer MUST resolve a document that declares neither value to private. The two classes of consumer are bound differently, because deriving a local value and altering remote state are not the same risk.
- A consumer writing a “do not publish” marker into a local manifest — npm
private, aPrivate :: Do Not Uploadclassifier, Cargopublish = false— MUST write it forprivate, which includes every document that omits the field. Such a marker is scoped to the artifact the document declares: a consumer projectsvisibilityinto a manifest for an artifact only when the document declares that artifact, becauseprivatein a package manifest guards THAT PACKAGE from publication and is not by itself a statement about the project. A manifest the document declares no artifact for is left untouched. - A consumer publishing through a pipeline the document already declares — a registry artifact under
org.projectfile.artifacts, a release tag — MUST skip it forprivate, which includes every undeclared document. - A consumer changing a remote resource’s visibility — creating a repository, or making an existing one public or private — MUST act only on a declared value, in either direction. The default is a derivation rule, not a licence to alter state the document does not describe.
public and private are closed. A consumer MUST reject any other value rather than map it to a default, and MUST NOT treat the field as a localized-string (Section 7).
A consumer emitting version into a format that requires SemVer (npm package.json, Cargo Cargo.toml) MUST NOT write a non-SemVer value there; it SHOULD skip the field and say so.
Date fields — created, released, modified here, and from/to in §4.5.1 and §4.5a — are YYYY-MM-DD strings in every encoding, so producers MUST quote them. An unquoted 2026-04-22 parses as a native date in TOML and as a timestamp in YAML, not as a string, and a validator evaluating the parsed tree rejects it.
The project’s coordinates — the (namespace, name) pair — uniquely identify a project within the ecosystem. Namespaces are claims, not proofs of ownership: nothing in this specification verifies that a producer controls the domain whose reverse form it uses. Consumers MAY use the coordinates as a deduplication key under that understanding (see Section 8).
Project URLs are not part of identity — they live in two dedicated top-level keys: repositories (§4.3a) for source-control endpoints, which carry structural attributes (branch, path, role) the rest of the URL vocabulary does not need; and links (§4.10) for every navigational URL with a type discriminator and an open vocabulary.
4.3a repositories
Section titled “4.3a repositories”- Type: sequence of mapping
- Cardinality: OPTIONAL.
- Item shape:
| Key | Type | Cardinality | Description |
|---|---|---|---|
url |
URI | REQUIRED | Clone endpoint. An includes fragment MAY omit it on its origin entry, which merges into the including document’s (§4.9). Any scheme Git accepts: https://, git@host:path (SSH), git+ssh://, git://, etc. The spec does NOT require an http-style scheme here — repositories[] carries the truthful clone endpoint, not the human-facing forge page. |
type |
string | OPTIONAL | VCS kind: git (default), hg, svn, fossil, bzr. Open vocabulary. |
path |
string | OPTIONAL | Sub-directory inside the repository tree where this project lives. Used by monorepos. |
branch |
string | OPTIONAL | Default branch on the remote. Producers SHOULD record this when known. Consumers MAY read it to tell a default-branch push from any other — a CI lowering distinguishing release builds from branch previews does exactly that — and, when it is absent, SHOULD fall back to a conventional default rather than fail. |
role |
string | OPTIONAL | Open vocabulary; recommended values: origin, mirror, archive. The value origin is reserved: it marks the canonical entry. Exactly one entry MUST carry role = origin when the sequence has more than one entry, even when no entry carries a role at all; a sole entry is implicitly origin. |
issues |
boolean | OPTIONAL | Marks the repository whose forge issue tracker is the project’s canonical bug tracker. At most one entry MAY carry issues = true. When present, consumers inferring issue-tracker URLs (the links[type=bugs] derivation described in §4.10) MUST emit a link only for this entry, even when other entries also point at forge hosts that provide issue trackers. When absent on all entries, the entry with role = origin (or the sole entry) is used. |
packages |
boolean | OPTIONAL | Marks the repository that package registries name as the project’s source. At most one entry MAY carry packages = true. When present, consumers writing a package manifest’s repository field (npm repository, a registry’s source link) MUST use this entry. When absent on all entries, the entry with role = origin (or the sole entry) is used. |
Consumers needing “the” clone URL (badges, the one shown in CITATION.cff repository-code) MUST pick the entry with role = origin, or the sole entry; package indices pick the entry with packages = true first. When the picked entry’s URL is not http(s) and the consumer needs an http-style URL (e.g., to emit a CITATION.cff repository-code field or a <url> element that browsers can follow), the consumer SHOULD either (a) look up links[type=source-code] (§4.10) for the matching forge page, or (b) derive an http URL from the clone URL using the consumer’s own forge-host knowledge.
The forge landing page (the human-facing project page rendered by GitHub, GitLab, Forgejo, Gitea, sourcehut, etc.) is NOT a value in repositories[]. It is a links[] entry with type = "source-code" (§4.10) — distinct from the clone endpoint even when both are http URLs.
4.4 license
Section titled “4.4 license”- Type: mapping
- Cardinality: OPTIONAL, at most one.
- Subkeys:
| Key | Type | Cardinality | Description |
|---|---|---|---|
spdx |
string | REQUIRED if license is present |
SPDX license expression per SPDX-EXPR. Single identifiers like MIT are valid expressions. Compound expressions: MIT OR Apache-2.0, (GPL-2.0-only WITH Classpath-exception-2.0). |
covers |
string | OPTIONAL | What corpus the SPDX expression applies to. Values: project (default) — applies to the entire source tree; additions — applies only to original work and packaging contributed in this repository, while bundled or wrapped upstream software retains its own licensing. See “Scope” below. |
file |
string (path) or sequence of string (path) | OPTIONAL | Relative path(s) to license file(s) that ship alongside the document. A single string identifies one file; a sequence enumerates several (e.g., dual-licensed projects with both LICENSE-MIT and LICENSE-APACHE, or REUSE-style LICENSES/*.txt directories). Each entry MUST resolve per the path rules in Section 8. |
Copyright holders are NOT carried under license. They are assembled by consumers from the top-level copyright block and the per-person from / to fields under people (entries whose roles contains copyright). This separation lets multiple consumers — LICENSE generators, SBOM tools, REUSE-style per-file headers — derive the same copyright lines from one source of truth.
Scope: covers
Section titled “Scope: covers”The covers subkey resolves an ambiguity in earlier drafts: an SPDX expression alone does not say which corpus it applies to. For an original-authorship project the answer is “everything in the source tree,” but for a distribution, container packaging repository, mirror, or any project that bundles or wraps upstream software, the SPDX expression typically describes only what the project’s own contributors authored.
covers: project(default) — the SPDX expression applies to the entire source tree. Equivalent to omittingcovers. Appropriate for projects where contributors are the original authors of substantially all source.covers: additions— the SPDX expression applies only to original work and packaging contributed in this repository (e.g., Dockerfiles, build scripts, entrypoints, configuration glue, and any framework code authored alongside it). Bundled or wrapped upstream software retains its own licensing and is not relicensed by this declaration. Producers usingcovers: additionsSHOULD enumerate per-artifact upstream licenses out-of-band (REUSE-styleLICENSES/directory, populatedlicense.filesequence, vendoredNOTICE/THIRD-PARTYfiles, or similar) and SHOULD make the boundary explicit inidentity.description.
Consumers generating SBOMs, attribution reports, or compliance metadata MUST treat covers: additions as a signal that license.spdx is not authoritative for the full image or distribution and MUST seek upstream license metadata from the bundled artifacts themselves.
Omitting the license field entirely signals that the licensing of the project is not declared in the document; it does not imply “no license” (which is a distinct legal concept) or public domain. Projects intending “no license granted” SHOULD state so using SPDX’s LicenseRef-* mechanism or the NOASSERTION identifier where appropriate.
4.4a copyright
Section titled “4.4a copyright”- Type: mapping
- Cardinality: OPTIONAL, at most one.
- Subkeys:
| Key | Type | Cardinality | Description |
|---|---|---|---|
year |
integer | OPTIONAL | Default copyright year, applied when a person or entity with the copyright role lacks a from date. A single year — date ranges per holder are derived from the per-person from / to fields under §4.5. |
Copyright is conceptually separate from license: a project’s SPDX expression governs how downstream users may use the work, while the copyright lines record who holds rights and for which years. Earlier drafts collapsed both under [license] (the holders field), forcing every consumer to re-implement the parse of "Copyright YYYY[-YYYY] Name <email>" strings. The split puts the rights-holder data in one place and lets multiple tools — LICENSE generators, SBOM emitters, REUSE-style per-file headers — derive identical copyright statements from one source of truth.
Consumers assembling copyright lines:
- Filter both
peopleandorganizationsto entries whoseroles(§4.5.3) containscopyright. - For each such entry, format
"Copyright {from}[-{to}] {flat-name}[ <{email}>]".fromandtoare taken from the per-entry fields (§4.5.1 for people, §4.5a for organizations); whenfromis absent, fall back to[copyright].year; whentois absent, the line ends with no upper bound (representing “present”). The flat name is synthesized per §4.5.2 for people; organizations usenamedirectly. - Emit the resulting lines in document order across both lists.
A document MAY carry [copyright] without any copyright-role people (e.g., when only the year is recorded and the holder is implied by the project namespace), and MAY carry copyright-role people without a top-level [copyright] block (every such person supplies its own from). Producers SHOULD set at least one of the two so downstream consumers can render a meaningful copyright line.
4.4b upstream
Section titled “4.4b upstream”- Type: sequence of mapping
- Cardinality: OPTIONAL.
- Item shape:
| Key | Type | Cardinality | Description |
|---|---|---|---|
name |
localized-string | REQUIRED | The upstream software’s own name, as its authors spell it. Example: PostgreSQL. |
url |
URI | OPTIONAL | The upstream project’s landing page. |
version |
string | OPTIONAL | The upstream version or series this project ships. MAY be a §3.8 reference to the field that pins it. |
license |
string | OPTIONAL | SPDX license expression per SPDX-EXPR under which the upstream is distributed. |
authors |
localized-string | OPTIONAL | Who wrote the upstream: one author or a collective name, as the upstream credits itself. Generators render it as a “Thanks to” line. |
relation |
string | OPTIONAL | Open vocabulary; recommended values: packages (default) — the project builds or ships the upstream unmodified; fork — the project carries a modified copy of its source. |
upstream names the third-party software a distribution, container packaging repository or fork exists to deliver. It pairs with license.covers (§4.4): under covers: additions the project’s own SPDX expression applies to its packaging only, and each upstream entry’s license states what applies to that upstream. Consumers presenting licensing to a reader MUST keep the two apart — the project’s license never covers an upstream entry, and an entry with no license is reported as “its own license”, not as the project’s. A project whose upstream entries are all relation: packages SHOULD declare covers: additions.
upstream is not a links[] entry: a link is navigation, while an upstream entry is a statement about what the project ships and under which terms.
4.5 people
Section titled “4.5 people”- Type: sequence of mapping
- Cardinality: OPTIONAL.
- Item shape: each entry is a person. Entities (organizations, projects, teams) go in the separate
organizationskey (§4.5a).
4.5.1 Person
Section titled “4.5.1 Person”| Key | Type | Cardinality | Description |
|---|---|---|---|
family-names |
string | REQUIRED | Family name(s) / surname(s). Multi-part surnames (Iberian García Márquez) go here as a single string. |
given-names |
string | OPTIONAL | Given name(s) / first name(s). |
name-particle |
string | OPTIONAL | Particle between given and family names. Examples: van, van der, de, von. |
name-suffix |
string | OPTIONAL | Generational or honorific suffix. Examples: Jr., III, PhD. |
alias |
string | OPTIONAL | Short handle, nickname, or username (e.g. a GitHub login). |
display-name |
string | OPTIONAL | Producer-supplied flat name. When emitting to single-name formats (npm author, gemspec authors[], AppStream <developer_name>), consumers MUST prefer this value over any synthesized form. See §4.5.2 for the synthesis rule consumers SHOULD apply when display-name is absent. |
email |
string | OPTIONAL | Contact email, per RFC 5322. |
url |
URI | OPTIONAL | Homepage. Sync programs emitting CITATION.cff map this to website. |
handles |
string → handle map | OPTIONAL | Platform handles/usernames. Open-vocabulary map from platform name to the handle. Each value: string, array of strings, or map of strings. Examples: signal: 'damianbuho.57', mastodon: 'mastodon.social/@user', gpg-key: { messaging: 'B64C122E…', code: '87B15E…' }, github: ['user', 'user-bot']. |
affiliation |
string | OPTIONAL | Institutional affiliation (employer, university, research group). Carried into CITATION.cff and codemeta Person.affiliation. |
orcid |
string | OPTIONAL | ORCID ID, per https://orcid.org/. |
from |
ISO date string | OPTIONAL | First date this person was associated with the project, in YYYY-MM-DD form. General-purpose lifecycle metadata; consumers assembling copyright lines (§4.4a) use the year portion when the person’s roles contains copyright. |
to |
ISO date string | OPTIONAL | Last date this person was associated with the project, in YYYY-MM-DD form. Absent means “present” — the person is still active on the project. For copyright-role entries, an absent to produces an open-ended copyright line. |
pronouns |
string | OPTIONAL | Personal pronouns (e.g. she/her, he/him, they/them). Carried into human-facing outputs where pronoun display is appropriate. |
roles |
sequence of string | OPTIONAL | See §4.5.3. |
4.5.2 Flattening a person to a single-name string
Section titled “4.5.2 Flattening a person to a single-name string”Consumers emitting to formats that carry only a single name string (npm author, gemspec authors[], AppStream <developer_name>, Maven POM <developer><name>) MUST use the person’s display-name when present. When display-name is absent, consumers SHOULD synthesize a flat name by concatenating, in order: given-names, name-particle, family-names, then a comma-space and name-suffix. Empty parts are skipped; remaining parts are joined with single spaces.
This default produces correct output for Western names (Alice Example, Ludwig van Beethoven, Martin Luther King, Jr.) and Iberian names with multi-part family-names (Gabriel García Márquez). It is not correct for cultures that order family name first (Hungarian, East Asian written in native order, Vietnamese), nor for mononyms with culturally significant ordering. Producers SHOULD set display-name explicitly whenever the default rule would misorder, mistransliterate, or otherwise misrepresent the name. The spec makes no attempt to encode locale-driven name-ordering rules; that responsibility lies with the producer who knows the person’s preferred presentation.
4.5.3 roles
Section titled “4.5.3 roles”roles is a sequence of strings recording the roles this person or organization holds on the project. Recommended values: author, maintainer, founder, contributor, reviewer, funder, packager, security, community, copyright. The list is free-form; consumers MAY filter by role. Absent means unspecified. The vocabulary applies to both people and organizations (§4.5a); consumers filtering by role SHOULD iterate both lists. The packager role identifies a person or organization who assembles, distributes, or containerizes upstream software; it is distinct from author and the two MAY co-occur on a single entry. The security role identifies a member of the project’s security response team. A person entry with roles: ["security"] is a named individual on that team; an organization entry with roles: ["security"] represents the team itself (its email is the team inbox, its url is the team contact page). Consumers generating a SECURITY.md SHOULD filter by this role to populate the contact section. The community role identifies a person or organization responsible for community management and Code of Conduct enforcement. Consumers generating a CODE_OF_CONDUCT.md SHOULD filter by this role to populate the enforcement-contact section. The copyright role marks the person or organization as a rights-holder whose name appears on the assembled copyright line (§4.4a); the per-entry from / to fields supply the date range, falling back to [copyright].year when from is absent. For non-copyright-role entries, from and to remain valid historical metadata but do not contribute to copyright statements. roles is a projectfile extension over CITATION.cff, which encodes the equivalent distinction by splitting authors, maintainers, and contacts into separate top-level sequences. When a consumer needs to pick a single contact for a role-targeted artefact (e.g. SECURITY.md, CODE_OF_CONDUCT.md) and no entry carries the artefact-specific role, it SHOULD fall back to any person or organization with roles: ["maintainer"], then to any person or organization with an email address.
4.5.4 Notes
Section titled “4.5.4 Notes”The people field carries persons only. Organizations and other non-person actors go in organizations (§4.5a). A consumer needing only current maintainers SHOULD filter both lists by "maintainer" in roles. A consumer building a citation record SHOULD filter by "author" in roles. A person or organization SHOULD appear once even when holding multiple roles.
When migrating legacy inputs that carry only a single flat name (npm author, git config user.name, gemspec authors[]), producers SHOULD split the string with the heuristic: the last whitespace-separated token becomes family-names; the rest becomes given-names. A single-token name maps to family-names only (legal for mononymous people). A name in the comma form Family, Given MUST be split on the comma, with the part before becoming family-names and the part after becoming given-names. When the producer cannot confidently split, it SHOULD set display-name to the original string and leave family-names as the best-effort guess so §4.5.2 round-trips losslessly. When the source clearly identifies an organization (npm author containing an Inc./LLC/Foundation/Group marker, or a CFF - name: entity), the producer SHOULD emit the entry under organizations instead.
The shape borrows from CITATION.cff so citation metadata can be derived mechanically. CFF authors merges both people and organizations filtered by role. projectfile does not mirror CFF’s full contact-field set (address, city, country, region, post-code, tel, fax, location, date-start, date-end); producers needing those fields SHOULD carry them under an extension namespace (e.g., org.citation-cff).
4.5a organizations
Section titled “4.5a organizations”- Type: sequence of mapping
- Cardinality: OPTIONAL.
- Item shape: each entry is an organization or other non-person entity (company, project, team, research group).
| Key | Type | Cardinality | Description |
|---|---|---|---|
name |
string | REQUIRED | Organization or entity name. |
alias |
string | OPTIONAL | Short handle. |
email |
string | OPTIONAL | Contact email, per RFC 5322. |
url |
URI | OPTIONAL | Homepage. Sync programs emitting CITATION.cff map this to website. |
handles |
string → handle map | OPTIONAL | Platform handles and usernames. Same open-vocabulary map value shapes (string / array / map) as the person field of the same name. |
orcid |
string | OPTIONAL | ORCID ID; permitted on entities per CITATION.cff. |
from |
ISO date string | OPTIONAL | First date this organization was associated with the project, in YYYY-MM-DD form. Same semantics as the person field of the same name. |
to |
ISO date string | OPTIONAL | Last date this organization was associated with the project, in YYYY-MM-DD form. Absent means “present”. |
roles |
sequence of string | OPTIONAL | See §4.5.3. |
4.6 keywords
Section titled “4.6 keywords”- Type: sequence of string
- Cardinality: OPTIONAL.
- Values: lowercase tags for discovery. Each tag MUST match
^[a-z0-9]([a-z0-9-]| [a-z0-9])*(?::[a-z0-9]([a-z0-9-]| [a-z0-9])*)?$. Spaces are permitted between words (e.g.open source); consecutive or trailing spaces are not.
A keyword tag has one of two shapes:
- Bare —
logging,graph-database,static-site-generator,open source. Equivalent topackage.jsonkeywordsor GitHub topics: free-form thematic tags. Consumers MAY use them for indexing, search ranking, or display. - Namespaced —
prefix:value, whereprefixandvalueeach match the bare-tag pattern (spaces allowed in the value segment). Used to attach lightweight, classifier-style metadata without growing a structuredcategoriesfield. Recommended (non-controlled) prefixes:topic:— subject-matter classification (topic:logging,topic:cryptography,topic:web-framework).audience:— intended consumer (audience:developer,audience:scientist,audience:end-user).status:— maturity (status:alpha,status:beta,status:stable,status:deprecated).framework:— primary framework (framework:django,framework:rails).
Producers MAY use other prefixes; consumers MUST NOT reject documents for using them, and SHOULD treat unknown prefixes as opaque tags. Tools mapping from PyPI trove classifiers, Cargo categories, or AppStream <categories> SHOULD emit namespaced tags rather than collapsing into bare keywords.
The technologies field (Section 4.7) carries the technological taxonomy; keywords carries the thematic and classifier one.
4.7 technologies
Section titled “4.7 technologies”- Type: sequence of string
- Cardinality: OPTIONAL.
- Values: lowercase tags identifying technologies relevant to building, running, or consuming the project.
The technologies field is a flat set of tags. Tags typically identify languages (python, rust), runtimes (node, jvm), frameworks (laravel, nextjs), data systems (postgres, redis), and platforms (docker, wasm). Consumers MAY use technologies to generate .gitignore, select linters, scaffold CI pipelines, or present the project in search results.
The recommended vocabulary is published in spec/technologies.yaml in the specification repository. The vocabulary is non-normative: producers SHOULD prefer recommended tags so cooperating consumers behave consistently, but consumers MUST NOT reject a document for using a tag outside the recommended vocabulary. A consumer encountering an unknown tag MAY ignore it.
Producers needing a tag broadly applicable to other projects SHOULD contribute it to the recommended vocabulary by pull request (no gate-keeping; one-line rationale in the commit message). Producers with project-private taxonomy needs SHOULD use an extension namespace (e.g., com.example.technologies) instead of inflating the recommended vocabulary.
Each entry in the recommended vocabulary MAY carry an optional use-cases list of advisory tokens (e.g. gitignore, dockerignore, editorconfig) hinting at generation features a tag enables. These are non-normative and purely advisory: consumers MAY use them to offer such features, MAY ignore them, and MUST NOT reject a document on their basis. They live on the vocabulary entry, not in the projectfile document.
4.8 requirements
Section titled “4.8 requirements”- Type: mapping
- Cardinality: OPTIONAL, at most one.
- Subkeys:
| Key | Type | Cardinality | Description |
|---|---|---|---|
browsers |
sequence of string OR mapping of string → sequence of string | OPTIONAL | Browser targets as Browserslist query strings. A flat sequence applies to all environments and generates a flat .browserslistrc. A mapping of lowercase environment names to query-string sequences generates per-environment [section] headers; environment names MUST match ^[a-z][a-z0-9-]*$. Absent = unconstrained. |
runtime |
mapping of string → string | OPTIONAL | Required runtime versions. Keys are lowercase identifiers (typically matching a technology tag: node, python, php, ruby, go, rust, java, dotnet). Values are version ranges in SemVer 2.0.0 syntax per [SemVer]: ">=8.5", "^3.12", "~1.0". |
requirements carries runtime-targeted constraints: which browser targets and runtime versions the project consumes. Platform targeting (operating system, CPU architecture) is a build/distribution concern rather than a runtime requirement, so it lives in the org.projectfile.operating-system and org.projectfile.architecture extension fields (§4.8a), not here.
browsers uses Browserslist query syntax. Producers MAY use any query string accepted by the Browserslist library; consumers MUST treat unknown or future query syntax as opaque and MUST NOT reject documents for using it. The production environment name carries special meaning in several bundlers (Vite, webpack, Parcel) and corresponds to the queries used when no NODE_ENV override is set. Producers SHOULD use production as the primary environment name where multi-environment entries are needed.
runtime ranges use SemVer syntax for portability across ecosystems. Producers whose ecosystem uses a non-SemVer scheme (PEP 440 epochs, Ruby ~> operator, .NET floating versions) MAY emit native syntax in addition under an extension namespace (e.g., org.python.pep440). Consumers SHOULD treat the SemVer form as advisory and consult the language-native manifest for authoritative resolution.
4.8a Platform targeting (org.projectfile.operating-system, org.projectfile.architecture)
Section titled “4.8a Platform targeting (org.projectfile.operating-system, org.projectfile.architecture)”Platform targeting — which operating systems and CPU architectures a project builds or ships for — is declared under the spec’s own extension namespace, not under requirements (it is a build/distribution concern, not a runtime requirement). These two fields are the recommended, portable home for platform sets; they are extension-namespace data, so they are opaque to the core JSON Schema (§3.7).
| Field | Type | Cardinality | Description |
|---|---|---|---|
org.projectfile.operating-system |
sequence of string | OPTIONAL | Target operating systems. Recommended values follow the OCI image-spec platform vocabulary [OCI-IMAGE-SPEC]: linux, darwin, windows, freebsd, openbsd, netbsd, illumos, solaris, aix, dragonfly, plan9, js, wasip1. Absent = unconstrained (a consumer typically assumes linux for container builds). |
org.projectfile.architecture |
sequence of string | OPTIONAL | Target CPU architectures. Recommended values follow the OCI image-spec architecture vocabulary [OCI-IMAGE-SPEC]: amd64, arm64, arm, 386, riscv64, ppc64le, s390x, mips64le, loong64, wasm. Absent = unconstrained (a consumer typically assumes the build host’s architecture). |
The lists are recommendations, not closed enums; consumers MUST NOT reject documents for values outside them. They mirror the vocabulary used by container tooling (Docker, Podman, Kubernetes) and Go’s GOOS/GOARCH.
When a project declares architecture without operating-system, a consumer building container images SHOULD assume linux as the operating system (the OCI default-platform convention). The cartesian product of the two lists is the project’s supported platform set; consumers lower it to their native form (e.g. linux/amd64 linux/arm64).
A consumer reading these fields SHOULD treat them as the project-wide platform set. Components of a build that target a different arch (e.g. a cross-compilation standard library bundled into every image arch, or a binary fetched for a wider arch set than the image builds) SHOULD be declared at that component, not by inflating the project-wide list — otherwise every platform-targeted component is expected to cover every declared arch.
An org.projectfile.artifacts entry MAY carry its own operating-system and architecture, which replace the project-level lists for that artifact, and an exclude list of cells ({os: darwin, arch: riscv64}) removed from their product. Every os and arch an exclude cell names MUST appear in the list it filters; a consumer MUST reject a cell that does not, because a misspelt cell would otherwise exclude nothing.
4.9 includes
Section titled “4.9 includes”- Type: sequence of string or mapping
- Cardinality: OPTIONAL.
- Values: local paths or
https/httpURLs referencing projectfile documents or fragments to merge before reading, or a pinned remote include (below).
Each entry is resolved in order and deep-merged into the base document: later entries win over earlier ones, and the base document always wins over all includes. The mechanism is encoding-agnostic — a YAML base may include a JSON remote fragment and vice-versa. Extension namespaces are merged recursively like any other mapping.
An entry is either:
- A relative path — resolved against the directory containing the projectfile. Path traversal rules from Section 8 apply.
- An HTTP(S) URL — fetched at read time. The URL’s file extension determines the encoding (
.yaml,.toml,.json). Listing it is the author’s consent to that fetch (Section 8). - A pinned remote include — a mapping
{url, sha256}, wheresha256is the lowercase hex SHA-256 of the fetched bytes. A consumer MUST reject the include when the bytes differ, a cached copy included. A plain URL follows whatever its server returns, and a branch URL changes meaning on every push; a pin, or a URL naming a tag or commit, does not.
Merge. Mappings merge key by key, recursively. Where both sides hold a scalar, or values of different types, the winner’s value replaces the other. Lists are unioned: the loser’s entries first, then the winner’s, skipping any entry deep-equal to one already present, so a value reached through two includes appears once. people and organizations merge by identity instead: entries naming the same person or organization — the same orcid, else the same email, else the same flattened name (§4.5.2) — combine into one entry, the winner’s fields taking precedence. repositories merges its origin the same way: when both sides name an origin entry (role = origin, or a sole entry with no role), the two combine into one, so a shared include can set branch for every including document without restating its url; every other entry is unioned. A merge therefore never removes a list entry; to drop an inherited one, stop including its source.
Resolution. An included document MAY declare its own includes, resolved the same way before it is merged. Its relative paths resolve against its own directory. A document fetched over HTTP(S) MAY include HTTP(S) URLs only: a consumer MUST NOT resolve a local path it names, and SHOULD warn that it skipped one. A document that includes itself, directly or transitively, is a cycle, and the consumer MUST reject it.
Failure. A failed fetch — a network error, a non-2xx status, a body that does not parse — is an error. A consumer MAY instead serve a copy it fetched earlier (on a server error, or in an offline mode), or continue without an include it cannot reach offline, and MUST report each include it served stale or left out. A consumer SHOULD bound each fetch in time and size, and SHOULD reject an HTML response, which is a login page far more often than a document. A consumer MAY cache fetched includes.
Fragments. A document reached only through includes is a fragment. It carries the same field shapes as a document but needs no discriminator and no required field, so a fragment can supply a single value such as identity.namespace. It is validated against the fragment schema, https://projectfile.org/schema/v1-fragment.json, which a fragment MAY name in $schema for editor support. Where a fragment and the base document both set a value, the base wins, identity included.
Validation. The structural contract (§3.4) applies to the merged document: a validator MUST resolve includes first and validate the result. A document that declares includes MAY therefore omit fields its includes supply, identity and its required subkeys among them, and the schema does not require them there; a validator that cannot resolve the includes checks field shapes only, and SHOULD say so.
Includes are resolved before any field is interpreted. A consumer that does not support includes MUST treat the document as valid and MUST NOT fail (peaceful cohabitation, §3.7), though its view of the document will lack the merged content.
Rationale: document composition is a core structural concern, not a tool-specific behaviour. Elevating it from an extension namespace (org.projectfile.cli.includes) to a reserved field makes it discoverable by every consumer and ensures the same merge semantics across all tooling.
4.10 links
Section titled “4.10 links”- Type: sequence of mapping
- Cardinality: OPTIONAL.
- Item shape:
| Key | Type | Cardinality | Description |
|---|---|---|---|
type |
string | REQUIRED | URL category. Open vocabulary; consumers MUST NOT reject documents for unknown types. See recommended values below. |
url |
URI | REQUIRED | The URL. |
label |
localized-string | OPTIONAL | Human-readable display label. |
preferred |
boolean | OPTIONAL | Marks the canonical entry of its type when more than one shares the same type. At most one entry per type MAY carry preferred = true. |
derived |
boolean | OPTIONAL | True when this link was auto-generated by a producer (e.g. the projectfile CLI’s derive pass). Consumers MUST treat derived and hand-written links identically; the flag exists so producers can track ownership and avoid overwriting user-edited entries. |
Additional keys beyond those listed above are accepted and MUST be preserved on round-trip (peaceful cohabitation, §3.7).
links is the spec’s single typed navigational-URL surface: every URL associated with the project goes here under an open type vocabulary, with two exceptions. Source-control clone endpoints live in repositories[] (§4.3a), because they carry structural attributes — branch, path, role — that the rest of the URL vocabulary does not need. Funding configuration lives in the org.projectfile.funding extension namespace.
Cardinality and selection. A document MAY contain multiple entries with the same type (two bug trackers, two chat channels, multiple package-registry landings). When a consumer needs the URL of a given type (e.g., to emit package.json’s single bugs.url), it MUST pick: the entry with preferred = true if one exists for that type, else the sole entry of that type, else the first entry of that type in document order. Producers SHOULD set preferred = true only when more than one entry of the same type is present and the choice would otherwise be ambiguous.
Consumer fallback chains. When a consumer needs a URL of a given semantic role but the document does not carry an entry of the corresponding type, the consumer MAY fall back along a role-specific chain. The chains are consumer policy, not spec policy — emitters for package.json homepage, composer’s support, gemspec metadata, AppStream <url>, and CITATION.cff url each encode their own preferences. A reasonable default chain for a “project landing” slot is links[type=homepage] → links[type=source-code] → links[type=documentation]. Consumers SHOULD document their chains in their own changelogs; the spec does not enumerate them here.
Recommended type values (non-normative):
type |
Typical use |
|---|---|
homepage |
Project home page (marketing site, landing page). |
source-code |
Forge landing page (GitHub / GitLab / Forgejo / Gitea / sourcehut project page). Distinct from repositories[] (§4.3a), which carries clone endpoints. |
bugs |
Issue tracker. |
documentation |
Rendered project documentation site (not source). |
changelog |
Changelog or release-notes page. |
chat |
Real-time discussion channel (IRC, Matrix, Discord, Slack). |
forum |
Long-form discussion forum (Discourse, mailing-list archive). |
faq |
Frequently asked questions page. |
wiki |
Project wiki or knowledge base. |
contact |
General-purpose contact URL — mailto:, contact form, mailing-list signup. |
translate |
Localization platform (Weblate, Crowdin, Transifex, Pontoon). |
donation |
Donation or sponsorship landing page. Distinct from org.projectfile.funding, which enumerates structured platform entries; donation is a single human-facing URL. |
security-policy |
Full security policy document. |
security-report |
Vulnerability submission endpoint. |
security-severity |
Project severity definitions. |
security-acknowledgments |
Researcher hall of fame. |
bug-bounty |
Bug bounty program landing page. |
pgp-key |
PGP public key for encrypted disclosure. |
openssf-best-practices |
OpenSSF Best Practices badge. |
cla |
Contributor License Agreement signing page. |
first-contribution |
New-contributor guide. |
enforcement |
Code of Conduct enforcement contact page. |
conduct-full-text |
Full Code of Conduct text. |
paid-support |
Commercial or enterprise support offering. |
status-page |
Service status / uptime page. |
zenodo-archive |
Zenodo or Figshare archive landing page. |
doi-landing |
DOI resolver or journal article. |
governance |
Project governance document. |
roadmap |
Public roadmap. |
renovate-dashboard |
Renovate self-hosted dashboard. |
ci |
CI/CD pipeline dashboard. |
package-registry |
Package-index landing page for this project (npmjs.com, pypi.org, packagist.org, crates.io). One entry per registry where the project is published. |
ai-policy |
AI/LLM contribution and usage policy document (governance page, AI agreement, or the generated policy file — AI_POLICY.md by default). See org.projectfile.ai for the policy declaration. |
ai-blocklist |
Public list of contributors barred under the AI policy. Named by the publish value of org.projectfile.ai.enforcement. |
Extension namespaces SHOULD use links entries for any URL with a navigational role (something a human would open) rather than declaring their own URL fields. Machine-functional endpoint URLs (registry base URLs, API endpoints, package-index URLs consumed by clients) MAY remain as structured fields in the relevant extension namespace, since they are configuration values rather than navigational links.