A minimal, browser-only utility to copy GitHub issue labels from one repository to another using the GitHub REST API.
No backend.
No CLI.
No installation.
Everything runs directly inside the browser.
- Copy labels between GitHub repositories
- Preview labels before syncing
- Skip or overwrite existing labels
- Live activity logging
- Progress tracking
- Browser-only execution
- Supports:
- Classic PAT
- Fine-Grained PAT
- Handles pagination automatically
- Zero dependencies
- Open source
Managing labels across multiple repositories becomes repetitive very quickly.
GitHub does not provide a native label synchronization tool.
This project solves that problem using a lightweight browser-based interface that directly communicates with the GitHub REST API.
The application follows this flow:
The user enters a GitHub Personal Access Token.
The user enters:
- source repository
- target repository
Format:
owner/repository
Example:
facebook/react
The app fetches labels from the source repository.
The app fetches existing labels from the target repository.
Labels are compared case-insensitively.
Example:
bug
BUG
Bug
are treated as the same label.
Depending on the selected mode:
Existing labels remain untouched.
Existing labels are updated.
The app creates or updates labels using the GitHub REST API.
The UI streams:
- progress
- logs
- sync statistics
- API responses
in real time.
This project intentionally keeps the architecture extremely simple.
Everything exists inside a single:
index.html
file.
| Layer | Technology |
|---|---|
| Structure | HTML |
| Styling | CSS |
| Logic | Vanilla JavaScript |
| API | GitHub REST API |
| Runtime | Browser |
Browser
│
│ fetch()
▼
GitHub REST API
GET /repos/{owner}/{repo}/labelsUsed for:
- source repository labels
- target repository labels
Pagination:
?per_page=100&page=nPOST /repos/{owner}/{repo}/labelsUsed when a label does not exist.
PATCH /repos/{owner}/{repo}/labels/{name}Used in overwrite mode.
The application supports:
- Classic PAT
- Fine-Grained PAT
Required scope:
repo
- Contents → Read
- Issues → Read and Write
If a label already exists:
- do nothing
- preserve existing values
Safest mode.
If a label already exists:
- update color
- update description
Useful for keeping repositories consistent.
| Property | Synced |
|---|---|
| Label Name | Yes |
| Label Color | Yes |
| Label Description | Yes |
| Issues Using Label | No |
| Pull Requests | No |
This section explains the internal logic for contributors.
Responsible for fetching all labels from a repository.
- Handles pagination
- Fetches labels in batches of 100
- Combines all pages
- Handles API errors
Start
↓
Fetch page 1
↓
Append labels
↓
More pages?
├── Yes → Fetch next page
└── No → Return labels
Used for rendering source label previews.
- Validate input
- Fetch source labels
- Render label chips dynamically
Labels are rendered using:
document.createElement('span')Text contrast is calculated dynamically for readability.
Main synchronization engine.
Controls the complete sync lifecycle.
- Input validation
- Source label fetching
- Target label fetching
- Conflict detection
- Label creation
- Label updating
- Progress updates
- Logging
- Statistics tracking
const srcLabels = await fetchAllLabels(src, token);const dstLabels = await fetchAllLabels(dst, token);const dstMap = new Map(
dstLabels.map(l => [l.name.toLowerCase(), l])
);Purpose:
- fast conflict lookup
- avoids repeated array scans
Complexity:
O(n)
instead of:
O(n²)
for (let i = 0; i < total; i++)For each label:
Depending on mode:
Skip label
PATCH existing labelPOST new labelWrite requests include a small delay:
await new Promise(r => setTimeout(r, 80));Purpose:
- reduce secondary rate limit triggers
- avoid abuse throttling
Logs are streamed dynamically into the activity panel.
- info
- ok
- skip
- overwrite
- error
12:10:33 info Fetching source labels
12:10:35 ok bug
12:10:35 overwrite enhancement
12:10:36 skip documentation
Progress updates continuously during sync.
Formula:
5 + Math.round(((i + 1) / total) * 95)- first 5% reserved for initialization
- remaining 95% reserved for label processing
The interface intentionally avoids unnecessary complexity.
- low visual noise
- fast interaction
- readable logs
- zero framework dependency
- minimal maintenance surface
- React
- Vue
- Node.js
- npm
- bundlers
- transpilers
- build systems
Important behavior:
- tokens never leave the browser except to GitHub
- no backend exists
- no analytics exist
- no database exists
- no cookies exist
- no persistent token storage exists
All API traffic flows directly:
Browser → GitHub
This project is designed to be:
- dependency-free
- browser-native
- zero setup
git clone git@github.com:kayspace/github-labels-sync.gitcd github-labels-syncOpen:
index.html
inside your browser.
That is enough to run the project.
Double click:
index.html
The application launches immediately.
Search:
Live Server
Author:
Ritwick Dey
- Open project folder in VS Code
- Right click:
index.html
- Click:
Open with Live Server
Required:
Nothing
This project intentionally avoids:
- Node.js
- npm
- yarn
- vite
- webpack
- frameworks
- transpilers
Tested on:
- Chrome
- Edge
- Firefox
- Brave
- Arc
Safari should also work.
github-labels-sync/
│
├── index.html
├── README.md
├── CONTRIBUTING
├── CODE_OF_CONDUCT
└── LICENSE
All logic exists inside:
index.html
Including:
- UI
- styling
- GitHub API logic
- sync engine
- logging system
- version fetching
Modify:
index.html
Workflow:
Save → Refresh Browser
No build step exists.
Open:
https://github.com/settings/tokens/new
Required scope:
repo
- Contents → Read
- Issues → Read and Write
owner/repository
Example:
facebook/react
Paste GitHub Personal Access Token.
Example:
facebook/react
Example:
kayspace/test-repo
Click:
preview source labels
Verify:
- labels load correctly
- colors render correctly
- descriptions render correctly
Click:
Run sync
The UI provides:
- live logs
- progress tracking
- sync statistics
- overwrite visibility
- error reporting
git checkout -b feature/my-featuregit add .git commit -m "add feature"git push origin feature/my-featureThis project follows:
Semantic Versioning
Format:
MAJOR.MINOR.PATCH
Example:
v1.0.0
| Type | Purpose |
|---|---|
| MAJOR | Breaking changes |
| MINOR | New features |
| PATCH | Bug fixes |
Releases should only be created for:
- stable checkpoints
- meaningful updates
- public versions
Not for every commit.
git add .
git commit -m "release v1.0.0"git pushgit tag -a v1.0.0 -m "Initial stable release"git push origin v1.0.0Open:
GitHub Repository → Releases → Create New Release
Select:
Tag: v1.0.0
Then publish the release.
If enabled, the header version automatically syncs using:
/releases/latest
Example:
v1.0.0
→
v1.1.0
→
v1.2.0
No manual version updates required.
We welcome contributions! Please see our Contributing Guidelines for detailed information on how to get started.
For inquiries or collaborations about using the content or the code, reach out to me at kayzspace@outlook.com
Licensed under the: MIT License