Every targeted policy in this pillar depends on something that is not a policy at all: knowing what each repository is.
Custom properties are that layer. Without them, policy targeting falls back to name patterns and hand-maintained lists, both of which are wrong within a month. With them, “all production repositories require signed commits” becomes a statement a machine can evaluate.
The short answer
Section titled “The short answer”GitHub defines them directly: “Custom properties are structured metadata fields that you can add to repositories and organizations in GitHub to enable better organization, governance, and automation across your environment.”
Four types: text string, single select, multi select, and true/false.
Properties can be required, with a default value, and optionally can “require explicit user-specified values” so a value must be provided at creation or transfer rather than defaulting.
Visibility follows the repository. “The visibility of custom properties matches the visibility of the repository.”
They are the targeting substrate for rulesets and repository policies, which is the reason they matter far more than a metadata field normally would. (GitHub documents repository policies as being in public preview and subject to change; the targeting model described here applies to rulesets regardless.)
The types and their limits
Section titled “The types and their limits”Text string — free text. Flexible and the hardest to keep clean, because free text becomes inconsistent immediately. Use only where the value is genuinely open-ended, such as a ticket reference or a system identifier.
Single select — one value from a defined list. This is the type to reach for by default. A constrained list is enforceable, filterable, and cannot acquire seventeen spellings of “production”.
Multi select — several values from a list. Correct where a repository can genuinely be in several categories at once, such as compliance scopes.
True/false — a boolean. Clean for binary facts, and the most likely to be misused: a boolean cannot later grow a third state without a migration.
Name and value limits: GitHub documents property names limited to 75 characters, using only a-z, A-Z, 0-9, _, -, $ and #; values likewise limited to 75 characters, accepting printable ASCII except quotation marks.
Required properties and defaults
Section titled “Required properties and defaults”The mechanism that determines whether your data is complete.
A property can be required for all repositories, with a default value applied.
Or it can require explicit user-specified values, meaning the person creating or transferring the repository must choose rather than accepting a default.
The difference matters enormously. A default means every repository has a value, and most of them have the default whether or not it is correct. An explicit value means somebody decided — which is slower and produces data you can trust.
Use defaults for properties where the default is genuinely right for most repositories. A tier property defaulting to development is reasonable, because most repositories are not production, and the ones that are will be promoted deliberately.
Use explicit values for properties where guessing is harmful. Data classification is the clearest case: a default of internal applied to a repository holding personal data is worse than no value at all, because it looks like a decision.
The unset case is the one that breaks governance. A repository with no value for a property matches no filter that references it — not the positive filter, and not the negative one. It falls through every targeted ruleset and policy, silently. Requiring the property is what closes that gap.
Designing the schema
Section titled “Designing the schema”Most enterprises need four to six properties. Adding more is easy and reducing them is not, so start small.
tier — single select: production, staging, development, experimental. The workhorse. Drives which rulesets apply, which review requirements, which retention.
owning-team — single select or text. Who is responsible. Consumed by ownership reports, offboarding checks, and every “who do I ask about this” question.
data-classification — single select, explicit value required. Public, internal, confidential, restricted. Drives visibility policy and access review scope.
lifecycle — single select: active, maintenance, deprecated, archived-pending. Drives lifecycle governance and cleanup.
compliance-scope — multi select, where applicable. Which regulatory scopes a repository falls under. Drives the stricter rulesets.
Resist the urge to add more. Every property is something somebody must set correctly on every repository forever. A schema of fifteen properties is a schema where twelve are wrong.
And resist encoding things GitHub already knows. Visibility, language, and topics are already available for targeting. A visibility custom property is a second source of truth that will disagree with the first.
Enterprise versus organisation scope
Section titled “Enterprise versus organisation scope”Properties are defined at organisation level, and enterprises can define properties across their organisations.
Define governance properties at the enterprise level where the schema must be consistent — tier, data-classification, compliance-scope. A per-organisation schema means every organisation invents its own vocabulary and no enterprise ruleset can target reliably.
Allow organisation-level properties for local concerns, where a team genuinely needs something the enterprise does not care about.
The failure mode is a schema per organisation. Twenty organisations, twenty definitions of tier, with different value lists. Every enterprise policy then has to enumerate all of them, or misses most of the fleet.
Which is the same argument as everywhere else in this cluster: the value of metadata is that it is consistent, and consistency has to be imposed at the level above the thing being described.
Targeting with properties
Section titled “Targeting with properties”The payoff, and the reason this article exists.
Rulesets and repository policies target repositories by custom property filter. A filter combines terms:
visibility:private props.team:infra -language:javaWhich reads as: private repositories, owned by the infra team, excluding Java. The props. prefix denotes a custom property; other terms reference facts GitHub already knows.
The property becomes the policy’s subject. “Production repositories require signed commits” is one ruleset with props.tier:production. A repository joins that policy by having its tier set, and leaves it by having it changed — with no ruleset edit.
This is what makes governance scale. The alternative — a list of repository names in every ruleset — requires an administrator to edit every ruleset every time a repository is created, and nobody does.
Test every filter against the actual matched set. The list a filter produces and the list you intended are different often enough that checking should be routine.
What properties are consumed by
Section titled “What properties are consumed by”Metadata with no consumer decays, because nobody notices when it is wrong. Naming the consumers is part of the design.
Rulesets and repository policies. The primary consumer, and the reason properties exist as a governance mechanism rather than a labelling one.
Reporting. Fleet inventories grouped by tier, by owner, by classification. This is what turns “we have 1,400 repositories” into something an engineering leader can act on.
Access reviews. Reviewing who can access confidential repositories requires knowing which ones are confidential. See access reviews and offboarding.
Automation. Scripts operating on the fleet — applying a workflow, opening a pull request, checking a control — select their target set by property. See API automation.
Compliance evidence. Demonstrating that a control applies to the repositories in scope requires the scope to be defined as data. See compliance evidence.
Incident response. “Which repositories does this team own” and “which repositories hold customer data” are questions asked under time pressure, and the answer needs to exist beforehand.
If you cannot name a consumer for a property, do not create it. The test is not whether the information is interesting; it is whether something will read it.
Properties versus the alternatives
Section titled “Properties versus the alternatives”Several other mechanisms are used for the same purpose, and each has a place.
Topics are free-form labels, visible, searchable, and unconstrained. Good for discovery, useless for governance — anybody can add or remove one, and there is no schema.
Naming conventions encode category in the repository name. Durable in the sense that they are visible everywhere, and brittle in that changing a category means a rename. Use for coarse, permanent distinctions only.
A file in the repository — a service.yaml or similar catalogue entry. Excellent for information the team owns and changes with the code, and reviewable through pull requests. But GitHub’s policy targeting cannot read it, so it cannot drive rulesets.
An external service catalogue. Frequently the actual source of truth in a mature organisation, holding far more than GitHub could. The right pattern is to keep it authoritative and derive the GitHub properties from it, rather than maintaining both by hand.
Teams and permissions encode ownership implicitly. Real, but not queryable as a category, and a repository can be accessible to several teams without any of them owning it.
The distinguishing feature of custom properties is that GitHub’s own policy engine can target them. That is the whole argument. For anything policy does not need to read, one of the alternatives is probably better.
Keeping the data honest
Section titled “Keeping the data honest”Metadata that is wrong is worse than metadata that is absent, because policy acts on it.
Set it at creation. A repository created through a governed path with the properties collected is correct from day one. One created without them may never be corrected. See repository templates.
Require the properties that matter. The explicit-value option is what converts a hope into a guarantee.
Audit for unset values. A scheduled report of repositories with no tier or no owning-team is the single most useful governance report available, because those repositories are invisible to every targeted policy.
Audit for stale values. An owning-team naming a team that no longer exists is wrong in a way that produces confident, incorrect answers. Cross-check against the team list.
Derive where you can. Some properties can be set by automation from a source of truth — an ownership property from a service catalogue, a compliance scope from a system-of-record. Derived values do not decay the way hand-set ones do.
Make correction easy. Properties can be set through the API, which means a team can fix their own metadata without a ticket. A correction path that requires a request is a correction that will not happen.
Organisation properties
Section titled “Organisation properties”Less discussed than repository properties, and useful for a specific problem.
Properties can be applied to organisations as well as repositories, and enterprise rulesets can target organisations by their custom properties.
Which matters when the enterprise has many organisations. Without organisation properties, an enterprise ruleset targets organisations by name pattern or by an explicit list — the same staleness problem repository name targeting has, at a level where changes are rarer but more consequential.
The useful organisation properties are few: something expressing the organisation’s purpose (production, sandbox, open-source, external), and something expressing its regulatory scope.
A concrete case: an enterprise with an open-source organisation, several product organisations, and a sandbox organisation wants different baseline rules for each. Targeting by organisation property means adding a new product organisation automatically inherits the product baseline, with no ruleset edit.
The same discipline applies. Constrained value lists, a definition per value, and a small schema. And the same failure mode: an organisation created without the property is targeted by nothing.
Set it at organisation creation, which in most enterprises is a rare enough event to be a governed process rather than self-service.
Rolling out a schema
Section titled “Rolling out a schema”-
Decide the schema on paper first. Four to six properties, with their types and value lists. Circulate it before creating anything, because changing a value list later means updating every repository that used the old value.
-
Define them at the enterprise level so the vocabulary is shared.
-
Backfill by derivation where possible. Ownership from
CODEOWNERS, tier from deployment configuration, classification from an existing register. Derived backfill is faster and more accurate than asking. -
Ask teams to fill the rest, with a deadline and a report showing who is outstanding. A visible report is more effective than a broadcast request.
-
Make properties required for new repositories once the backfill is substantially done. Doing this first blocks repository creation while the schema is still contested.
-
Then write the targeted policies. In that order. Policies written before the metadata exists target nothing.
-
Publish the unset report permanently, and give it an owner. The gap will otherwise reopen.
Evolving the schema
Section titled “Evolving the schema”The schema will change. Planning for that is what separates a design that lasts from one that gets abandoned.
Adding a property is cheap. New repositories get it if required; existing ones are unset until backfilled. The only cost is the backfill, and the gap while it is incomplete.
Adding a value to a single select is cheap. Nothing that used the old values is affected.
Removing or renaming a value is expensive. Every repository using it needs updating, and every filter referencing it needs updating, and doing those in the wrong order leaves repositories ungoverned in between.
The safe sequence for a value change:
- Add the new value alongside the old.
- Update every filter to match both values.
- Migrate repositories from the old value to the new.
- Confirm zero repositories remain on the old value.
- Update filters to match only the new value.
- Remove the old value from the list.
Removing a property entirely requires the same care in reverse: remove it from every filter first, confirm nothing references it, then delete.
Which is a strong argument for getting the value lists right initially. Spend the extra week on the schema design; the migration you avoid is worth far more.
Version the schema document. A record of what each property means, what its values mean, and when they changed. In two years somebody will need to know whether tier:production meant the same thing in 2026 as it does now, and the ruleset history alone will not tell them.
Where the schema goes wrong
Section titled “Where the schema goes wrong”Failure patterns worth recognising early, because each is easier to fix in month one than in year two.
The property that means different things to different teams. tier used by one team to mean deployment environment and by another to mean business criticality. The values look consistent and the data is meaningless. Fix: write a one-line definition for every value, and publish it where people set the property.
The property nobody sets because setting it is inconvenient. If the only path is a settings page three clicks deep, coverage will plateau around forty percent. Fix: collect it at creation and expose an API path teams can automate.
The property that is set once and never updated. Ownership is the classic case: correct at creation, wrong after the first reorganisation. Fix: derive it, or cross-check it against a source that does change.
The property added for one policy that outlives the policy. Nobody removes it, everyone keeps setting it, and it becomes folklore. Fix: the annual review, and the willingness to delete.
The catch-all text property. Somebody adds notes or metadata, and within a year it contains structured data encoded as strings that scripts parse with regular expressions. Fix: refuse to create it; if the information has structure, give it a property.
Coverage that stalls at eighty percent. The last twenty percent are unowned repositories, archived-but-not-archived repositories, and things nobody wants to claim. That residue is itself the finding — it is an inventory of the estate nobody is responsible for, and it is worth escalating rather than chasing.
Common mistakes
Section titled “Common mistakes”Writing targeted rulesets before the metadata exists. They match nothing.
Free text where a single select belongs. Four spellings of production, and filters that miss most of the fleet.
Defining properties per organisation. No enterprise policy can target reliably.
Too many properties. Most of them end up wrong.
Duplicating what GitHub already knows. A second source of truth that disagrees.
Defaults where an explicit value is needed. Every repository has a value; none of them mean anything.
Never auditing unset values. Those repositories are invisible to every targeted policy.
Stale ownership values. Confident wrong answers are worse than no answer.
Sensitive values in properties. Visibility matches the repository.
Setting properties in bulk
Section titled “Setting properties in bulk”Backfill and correction both need a programmatic path, and the shape of it is worth getting right.
Properties are set through the API, per repository, and a fleet-wide backfill is a loop over the repository list with a value derived from somewhere.
Derive rather than assume. The best backfills read an existing source — a deployment manifest, a CODEOWNERS file, a service catalogue — and set the property from it. A backfill that guesses produces data that looks complete and is not trustworthy, which is the worst of both states.
Report what you could not derive. A backfill that sets eight hundred properties and cannot determine two hundred should produce a list of the two hundred, not a default value for them. Those are the repositories that need a human.
Run it repeatedly, not once. A derivation script scheduled weekly keeps the property current as the source changes. A one-off run is correct on the day and decays from then on.
Rate limits matter at fleet scale. A loop over several thousand repositories will hit them, and the script needs to handle that gracefully rather than failing halfway with no record of where it stopped. See API automation.
Log what changed. A backfill that overwrites a value somebody set deliberately, with no record, will erode trust in the whole schema. Prefer setting only unset values on the first pass, and treat overwriting as a separate, deliberate operation.
Mental model
Section titled “Mental model”Custom properties are the schema your governance queries against. Policy expressed as “repositories like this” is durable; policy expressed as a list of names is stale on arrival. The quality of every targeted control in the enterprise is bounded by the quality of this metadata, which makes completeness and accuracy a governance concern rather than an administrative one.
What you learned
Section titled “What you learned”- Custom properties are structured metadata on repositories and organisations, used for organisation, governance and automation
- Four types: text string, single select, multi select and true/false; single select should be the default choice
- Properties can be required with a default, or can require explicit user-specified values
- Property visibility matches the repository’s visibility, so values must not be sensitive
- Names and values are limited to 75 characters with a restricted character set
- Define governance properties at enterprise level so the vocabulary is shared
- Rulesets and repository policies target repositories by property filter, using a
props.prefix - A repository with an unset property matches no filter referencing it, positive or negative
- Build the metadata before writing the policies that depend on it
Exercise
Section titled “Exercise”Use an organisation or enterprise you administer.
-
List the custom properties currently defined. Predict: how many are actually populated across the fleet?
-
For one property, count how many repositories have it set, unset, and set to the default.
-
Design a four-property schema for your estate: name, type, value list, required or not, and what policy would consume it.
-
For each property, name the consumer. Predict: can you name one for all four?
-
Pick one property you could derive from an existing source of truth. Describe the derivation.
-
Write a filter expression selecting your production-tier private repositories. Check the matched set against your expectation.
-
Produce a list of repositories with no owning team. Predict: how many, and does the number surprise you?
-
Decide who owns the unset-property report going forward.