Skip to main content
Safe Settings uses a three-level configuration hierarchy that allows you to set organization-wide defaults while giving teams flexibility to customize settings for their specific needs.

Configuration Hierarchy

Precedence Order: Repository > Sub-Organization > Organization Settings at lower levels override settings at higher levels, allowing centralized defaults with local customization.

Basic Multi-Level Example

# Organization-level defaults
repository:
  private: true
  has_issues: true
  has_wiki: false
  allow_merge_commit: false
  allow_squash_merge: true
  delete_branch_on_merge: true

labels:
  include:
    - name: bug
      color: "d73a4a"
    - name: enhancement
      color: "a2eeef"

teams:
  - name: all-engineers
    permission: pull

branches:
  - name: default
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 1
        dismiss_stale_reviews: true
      required_status_checks:
        strict: true
        contexts: []
      enforce_admins: false
      restrictions:
        apps: []
        users: []
        teams: []
Result for api-gateway repository:
  • Private repository (from org)
  • Has issues, no wiki (from org)
  • Squash merge only (from org)
  • Backend + performance + database labels (from suborg)
  • Backend team has admin access (from suborg)
  • 3 approvals required (from repo)
  • 4 CI checks required (2 from suborg + 2 from repo)
  • Only platform leads can push (from repo)
  • Enforced for admins (from repo)

Real-World Scenarios

Scenario 1: Tiered Security by Service Criticality

.github/settings.yml
# Baseline security for all repos
repository:
  private: true
  security:
    enableVulnerabilityAlerts: true
    enableAutomatedSecurityFixes: true

branches:
  - name: default
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 1
        dismiss_stale_reviews: true
      required_status_checks:
        strict: true
        contexts:
          - "ci/tests"
      enforce_admins: false
      restrictions:
        apps: []
        users: []
        teams: []

Scenario 2: Team-Based Configuration

# Minimal org defaults
repository:
  private: true
  has_issues: true
  default_branch: main

teams:
  - name: everyone
    permission: pull

labels:
  include:
    - name: bug
      color: "d73a4a"
    - name: enhancement
      color: "a2eeef"
Result: Each team automatically gets their configuration applied to any repository they’re added to, with team-specific labels, access, and protection rules.

Scenario 3: Environment-Based Configuration

# Base settings
repository:
  private: true
  has_issues: true
  allow_squash_merge: true
  delete_branch_on_merge: true

teams:
  - name: engineering
    permission: push
Result: Repositories are automatically configured based on their environment (staging vs production) through naming conventions.

Understanding Setting Merges

How Arrays Merge

Labels are merged - items from all levels are combined:
# Org: 2 labels
labels:
  include:
    - name: bug
    - name: enhancement

# Suborg: adds 2 more
labels:
  include:
    - name: bug        # same as org
    - name: enhancement  # same as org
    - name: frontend   # new
    - name: ui         # new

# Result: 4 labels (bug, enhancement, frontend, ui)

Complete Override vs. Partial Override

# Org level
repository:
  private: true
  has_issues: true
  has_wiki: false

# Repo level - partial override
repository:
  has_wiki: true  # Only override this field

# Result:
# private: true      (from org)
# has_issues: true   (from org)
# has_wiki: true     (from repo)
Repository settings merge field-by-field.
# Org level
branches:
  - name: default
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 1
      required_status_checks:
        strict: true
        contexts: []

# Repo level - must specify all fields
branches:
  - name: default
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 2
      required_status_checks:
        strict: true
        contexts:
          - "ci/tests"
      enforce_admins: true
      restrictions:
        apps: []
        users: []
        teams: []

# Result: Exactly what's in repo level
Branch protection replaces entirely - must specify all required fields.

Advanced Patterns

Pattern 1: Shared Configuration Modules

Create suborg configs for common patterns:
# .github/suborgs/public-repos.yml
# Use for all public/open-source repos
suborgrepos:
  - "docs-*"
  - "blog-*"
  - "examples-*"

repository:
  private: false
  visibility: public
  has_issues: true
  has_wiki: true
  license_template: mit

labels:
  include:
    - name: good first issue
    - name: help wanted

Pattern 2: Property-Based Suborgs

# .github/suborgs/high-security.yml
# Apply to repos with custom property
suborgproperties:
  - security-level: high

branches:
  - name: default
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 3
      enforce_admins: true
Then set custom properties on repositories:
# .github/repos/auth-service.yml
custom_properties:
  - name: security-level
    value: high
# Org level - shared milestones
milestones:
  - title: Q1 2024
    description: First quarter deliverables
    state: open
  - title: Q2 2024
    description: Second quarter deliverables
    state: open

autolinks:
  - key_prefix: "JIRA-"
    url_template: "https://jira.company.com/browse/JIRA-<num>"

# Suborg level - additional milestones
milestones:
  - title: Q1 2024
    description: First quarter deliverables
    state: open
  - title: Q2 2024
    description: Second quarter deliverables
    state: open
  - title: Backend Migration
    description: Database migration project
    state: open

autolinks:
  - key_prefix: "JIRA-"
    url_template: "https://jira.company.com/browse/JIRA-<num>"
  - key_prefix: "DB-"
    url_template: "https://dbissues.company.com/ticket/<num>"

Best Practices

Start Simple

Begin with org-level settings only. Add suborg/repo configs when you have a clear need.

Document Overrides

Use comments to explain why settings are overridden:
# Override: Payment service requires PCI compliance
branches:
  - name: default
    protection:
      required_approving_review_count: 3

Use CODEOWNERS

Let different teams manage their suborg configs:
.github/suborgs/frontend-*.yml @frontend-team
.github/suborgs/backend-*.yml  @backend-team

Validate in PRs

Always test multi-level config changes in a PR first. Safe Settings will show you the merged result.

Common Pitfalls

Teams Don’t Merge: If you specify teams at the suborg level, you must re-include org-level teams if you want to keep them.
Branch Protection Requires All Fields: When overriding branch protection, you must specify all required fields (required_pull_request_reviews, required_status_checks, enforce_admins, restrictions).
Include/Exclude Patterns: Be careful with overlapping patterns. Test your glob patterns to ensure repos match the intended suborg.

Troubleshooting

Debug steps:
  1. Check which suborg config applies (review suborgrepos, suborgteams, suborgproperties)
  2. Check precedence: repo > suborg > org
  3. Review Safe Settings check run for what was actually applied
  4. Look for validation errors in PR comments
Solution: Only one suborg config applies per repository. If a repo matches multiple patterns:
  • First match wins (alphabetical order of filename)
  • Avoid overlapping suborg patterns
  • Use repo-level config for specific overrides
Check:
  • Repository is not in restrictedRepos
  • Safe Settings has been triggered (push to default branch or webhook event)
  • No validation errors in check runs

Next Steps

Custom Validation

Add validators to prevent invalid overrides

Basic Setup

Review the basic setup guide

Build docs developers (and LLMs) love