π Sync GitHub repositories from source to target organizations using mirror cloning. Creates target repositories if they don't exist, with support for visibility control, Actions disabling, and archiving.
- π Mirror cloning - Complete repository sync including all branches and tags
- ποΈ Automatic repository creation - Creates target repos if they don't exist
- ποΈ Visibility control - Set repository visibility per repo (private/public/internal)
- π« GitHub Actions management - Disable Actions on target repositories
- π¦ Repository archiving - Archive repositories after sync (with smart unarchive/re-archive)
- π Multi-server support - Sync between github.com and GitHub Enterprise Server
- π Post-run analysis - Detailed sync summary with statistics
Tip
This example uses personal access tokens for simplicity. See the GitHub Apps section below for the recommended approach using GitHub Apps.
- uses: actions/checkout@v5
- name: Bulk GitHub Repository Sync
  uses: joshjohanning/bulk-github-repo-sync-action@v1
  with:
    repo-list-file: repos.yml
    source-github-token: ${{ secrets.SOURCE_GITHUB_TOKEN }}
    target-github-token: ${{ secrets.TARGET_GITHUB_TOKEN }}
    overwrite-repo-visibility: true # overwrite repo visibility with what is in yml file; defaults to false
    force-push: false # force push to target repos (overwrites history); defaults to false
    ### only needed if either your source or target is NOT github.com
    # target-github-api-url: https://ghes.domain.com/api/v3 # API URL for GHES
    # source-github-api-url: https://api.github.com # only needed if source is not github.202132.xyzThe repository list uses YML format with per-repository configuration:
repos:
  - source: source-org/source-repo-1
    target: target-org/target-repo-1
    visibility: private # Optional: private, public, or internal (defaults to private)
    disable-github-actions: true # Optional: disable Actions on target repo (defaults to true)
    archive-after-sync: false # Optional: archive repo after sync (defaults to false)See sample file.
You can use a personal access token, but it is recommended to use GitHub Apps instead:
Note
Required GitHub App Permissions:
- Source App: Repository Read access to contentsandmetadata
- Target App: Repository Read and Write access to actions,administration,contents, andmetadata
- uses: actions/checkout@v5
# source
- uses: actions/create-github-app-token@v2
  id: source-app-token
  with:
    app-id: ${{ vars.SOURCE_APP_ID }}
    private-key: ${{ secrets.SOURCE_APP_PRIVATE_KEY }}
    owner: ${{ github.repository_owner }}
# target
- uses: actions/create-github-app-token@v2
  id: target-app-token
  with:
    app-id: ${{ vars.TARGET_APP_ID }}
    private-key: ${{ secrets.TARGET_APP_PRIVATE_KEY }}
    owner: joshjohanning-emu
- name: Bulk GitHub Repository Sync
  uses: joshjohanning/bulk-github-repo-sync-action@v1
  with:
    repo-list-file: repos.yml
    source-github-token: ${{ steps.source-app-token.outputs.token }}
    target-github-token: ${{ steps.target-app-token.outputs.token }}
    overwrite-repo-visibility: true # overwrite repo visibility with what is in yml file; defaults to false
    # force push to target repos (overwrites history); defaults to false
    # target-github-api-url: https://ghes.domain.com/api/v3 # only needed if target is GHES| Setting | Description | Default | 
|---|---|---|
| source | Source repository in owner/repoformat | - | 
| target | Target repository in owner/repoformat | - | 
| visibility | Repository visibility (private/public/internal) | private | 
| disable-github-actions | Disable GitHub Actions on target repository | true | 
| archive-after-sync | Archive repository after successful sync | false | 
| Input | Description | Required | Default | 
|---|---|---|---|
| repo-list-file | YML file with repository configurations | Yes | - | 
| source-github-token | GitHub PAT for source repositories | Yes | - | 
| target-github-token | GitHub PAT for target repositories | No | (uses source token if not specified) | 
| source-github-api-url | Source GitHub API URL (e.g., https://api.github.202132.xyzorhttps://ghes.domain.com/api/v3). Instance URL is auto-derived. | No | ${{ github.api_url }} | 
| target-github-api-url | Target GitHub API URL (e.g., https://api.github.202132.xyzorhttps://ghes.domain.com/api/v3). Instance URL is auto-derived. | No | ${{ github.api_url }} | 
| overwrite-repo-visibility | Force update visibility of existing repos | No | false | 
| force-push | Force push to target repositories (overwrites history) | No | false | 
You can also run the script directly:
export SOURCE_GITHUB_TOKEN=ghp_abc
export TARGET_GITHUB_TOKEN=ghp_xyz
node src/index.js --file=repos.yml=== SYNC SUMMARY ===
Total repositories: 5
β
 Successful: 5
β Failed: 0
π Created: 2
π Updated: 3
ποΈ  Visibility updated: 1
π¦ Archived: 2