Appearance
Examples
A cookbook of settings.yml files. Every settings example on this page runs through the real document validation in CI, so the shapes stay current. What each section manages, which token permission it needs, and whether its undeclared entries are deleted or kept is specified in the Sections table; the cross-section rules live under Semantics. This page shows shapes, not behavior.
One rule frames everything below: only declared keys are applied or compared. A section, or a field inside one, that the file does not mention is never touched. The rule has edges worth knowing; the last two bend it where the API forces their hand, as the Sections table notes:
- Full-payload entries: a ruleset is applied with a full-payload PUT, so a key the entry omits but the live ruleset holds is drift in check and a refused write in apply; Semantics has the edges.
- Labels and milestones work the other way: only the fields you declare are sent, so an omitted description or state is left alone.
- Classic
protection: inside a declaredprotectionobject the classic API requires all four core keys, so apply fills the ones you omit withnull(see Classic branch protection below). actions: declaring any base permissions key (orselected_actions, which infersallowed_actions: selected) makes the base PUT carryenabled: trueunless the file says otherwise. Retention-, cache-, workflow-token-, or access-only declarations leave the base policy alone.
A minimal file
Enough to be useful on day one: a few repository fields and the labels you actually triage with.
yaml
repository:
description: Payments service
topics: payments, service
has_wiki: false
delete_branch_on_merge: true
labels:
- name: bug
color: "d73a4a"
description: Something isn't working
- name: needs-triage
color: "ededed"A full-featured file
A single-repo file exercising most sections. topics accepts a comma-separated string or a YAML list, and enable_* keys are feature toggles the section routes to their own endpoints; everything else under repository goes to the API verbatim.
yaml
repository:
description: Payments service
homepage: https://payments.example.com
topics: payments, service, production
has_wiki: false
has_projects: false
allow_merge_commit: false
allow_squash_merge: true
squash_merge_commit_title: PR_TITLE
delete_branch_on_merge: true
enable_vulnerability_alerts: true
enable_automated_security_fixes: true
labels:
- name: bug
color: "d73a4a"
description: Something isn't working
- name: prio
new_name: priority-high
color: "b60205"
rulesets:
- name: protect main
target: branch
enforcement: active
conditions:
ref_name:
include: ["~DEFAULT_BRANCH"]
exclude: []
rules:
- type: deletion
- type: non_fast_forward
- type: pull_request
parameters:
required_approving_review_count: 1
dismiss_stale_reviews_on_push: true
require_code_owner_review: false
require_last_push_approval: false
required_review_thread_resolution: true
- name: release tags
target: tag
enforcement: active
conditions:
ref_name:
include: ["refs/tags/v*"]
exclude: []
rules:
- type: deletion
- type: update
environments:
- name: production
pinned: true
wait_timer: 30
prevent_self_review: true
reviewers:
- type: Team
id: 4501
deployment_branch_policy:
protected_branches: false
custom_branch_policies: true
deployment_branch_policies:
- name: release/*
- name: v*
type: tag
deployment_protection_rules:
- app: my-gate-app
variables:
- name: DEPLOY_REGION
value: eu-west-1
secrets:
- name: PROD_DEPLOY_KEY
value: $PROD_DEPLOY_KEY
autolinks:
- key_prefix: "TICKET-"
url_template: "https://example.atlassian.net/browse/TICKET-<num>"
actions:
enabled: true
allowed_actions: selected
selected_actions:
github_owned_allowed: true
verified_allowed: false
patterns_allowed:
- Vivswan/*
default_workflow_permissions: read
can_approve_pull_request_reviews: false
artifact_and_log_retention:
days: 30
cache:
max_cache_retention_days: 7
actions_variables:
- name: DEPLOY_REGION
value: us-east-1
actions_secrets:
- name: DEPLOY_TOKEN
value: $DEPLOY_TOKEN
dependabot_secrets:
- name: REGISTRY_TOKEN
value: $REGISTRY_TOKEN
codespaces_secrets:
- name: DEVCONTAINER_PAT
value: $DEVCONTAINER_PAT
agents_secrets:
- name: AGENT_TOKEN
value: $AGENT_TOKEN
agents_variables:
- name: AGENT_MODEL
value: default
webhooks:
- config:
url: https://ci.example.com/hook
content_type: json
secret: $HOOK_SECRET
events: [push, pull_request]
active: true
workflows:
- path: nightly-sync.yml
state: disabled
check_suite_preferences:
auto_trigger_checks:
- app_id: 12345
setting: false
pages:
build_type: workflow
code_scanning_default_setup:
state: configured
query_suite: default
code_quality_setup:
state: configured
languages: [go, python]
collaborators:
- username: octocat
permission: push
deploy_keys:
- title: deploy-bot
key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB5n2eXAMPLEeXAMPLEeXAMPLEeXAMPLEeXAMPLEeXAM deploy@example
read_only: true
teams:
- name: platform
permission: maintain
milestones:
- title: v2.0
description: The big rewrite
state: open
interaction_limits:
limit: collaborators_only
expiry: one_week
pull_request_creation_cap:
enabled: true
max_open_pull_requests: 5
pull_request_creation_bypass: [octocat, hubot]
custom_properties:
- property_name: team
value: payments
- property_name: compliance
value: [soc2, pci]
- property_name: pilot
value: true
secret_scanning_custom_patterns:
- name: Internal API token
pattern: "int_[a-z0-9]{32}"
start_delimiter: '\b'In rulesets, short ref names are auto-prefixed (staging becomes refs/heads/staging) and ~DEFAULT_BRANCH passes through; rule parameters go to the API verbatim, so rule types GitHub ships tomorrow work unchanged (see Forward compatibility). custom_properties sets values for properties the organization has already defined (a value: null unsets one), so it applies to org-owned repositories only - on a personal account the section skips with a note.
Classic branch protection
branches is the classic per-branch protection API, kept for Probot compatibility; rulesets are the modern replacement. The declared protection object is the PUT payload, with one adjustment: the classic API rejects a payload missing any of its four core keys (required_status_checks, enforce_admins, required_pull_request_reviews, restrictions), so apply fills omitted core keys with null.
A null there means "off", so an omitted enforce_admins is turned off, not left alone. Declare every core key you want to keep; check mode reports an omitted-but-live core key as drift before an apply would null it away.
yaml
branches:
- name: main
protection:
required_pull_request_reviews:
required_approving_review_count: 1
enforce_admins: true
required_status_checks:
strict: true
contexts: [all-green]
restrictions: nullTwo protection surfaces the REST API cannot express ride GraphQL under the hood, declared as ordinary keys:
force_push_bypasserslists who may force push: a bare login is a user,org/team-sluga team,app/sluga GitHub App.required_deploymentsrequires deployments to the named environments before merging;nullturns the requirement off. Declare the environments in the same file - theenvironmentssection applies first.
An entry whose name is a wildcard pattern (release/*) is a classic RULE rather than a branch: it reconciles entirely through GraphQL and its protection accepts only the keys with exact GraphQL equivalents, so prefer rulesets for new pattern-based configuration.
yaml
environments:
- name: production
branches:
- name: main
protection:
enforce_admins: true
force_push_bypassers: [octocat, my-org/release-team, app/deploy-bot]
required_deployments:
environments: [production]
- name: release/*
protection:
required_linear_history: true
required_status_checks:
strict: true
contexts: [all-green]What null means
For most sections, leaving a key out means "do not touch it". Three resource-level declarations give an explicit null a meaning of its own.
pages: null declares the Pages site off. Apply deletes an existing site; an absent pages key leaves the site alone.
yaml
pages: nullinteraction_limits: null clears an active repository-level interaction limit - the base limit only; the pull request creation cap and its bypass list are separate resources a null never touches. An absent key leaves whatever limit is live untouched.
yaml
interaction_limits: nullprotection: null on a branch declares it unprotected, and apply removes existing classic protection.
yaml
branches:
- name: legacy
protection: nullA mode: render fold keeps all three meanings: a higher pages: null or interaction_limits: null is written as the section's value even over a lower layer's declaration, and a higher protection: null is the branch entry's value where the two branches lists union by name. A null never removes a key; to drop a lower layer's keyed entry, write _remove: true on it, and the layering guide has the rules.
A multi-repo defaults-file never merges into a target's file, so a null there keeps the meanings above.
A few individual fields accept null as a value of their own too, such as pages.cname to remove a custom domain; the published schema marks those.
Notes in the file
Unknown top-level sections are hard errors, so a typo cannot silently do nothing. The one exception: under a sections allowlist, unknown keys outside the allowlist warn instead of failing, which eases version skew; the troubleshooting guide covers it.
An underscore key is not an escape hatch: the underscore marks this action's three directives, _layering, _undeclared, and _remove (the layering guide owns them), and any other underscore key at the top level or on a list section's wrapper fails validation the same way. Inside a keyed entry, an underscore key is a field and follows the entry's open or closed shape. A note belongs in a YAML comment:
yaml
# owner: platform-team, see runbook RB-112
labels:
- name: bug
color: "d73a4a"Where to go next
Check mode is the safe way to try any of these files against a real repository before applying. Multi-repo mode reuses the same documents across a fleet, layering folds several files into one, and the undeclared policy explains what happens to live resources these files do not declare, and the _undeclared knob that changes it.