Build a GitHub Productivity Chrome Extension: Enhance Your Developer Workflow
If you are a developer who spends significant time on GitHub, you have probably wished for ways to streamline your workflow, automate repetitive tasks, or gain quick access to information that normally requires multiple clicks. Whether it is quickly checking pull request status, jumping to specific code definitions, or receiving instant notifications about repository activity, a custom Chrome extension can transform your GitHub experience. In this comprehensive guide, we will walk through the complete process of building a GitHub productivity Chrome extension from scratch, covering everything from project setup to deployment.
Why Build a GitHub Productivity Extension
GitHub is the backbone of modern software development, hosting millions of repositories and serving as the collaboration hub for developers worldwide. However, despite its powerful features, there are countless everyday tasks that could be automated or enhanced through browser extensions. The official GitHub Chrome extension provides basic functionality, but there is enormous potential for custom solutions tailored to specific workflows.
Building your own GitHub productivity extension offers several compelling advantages. First, you can customize every feature to match your exact needs, creating tools that address your unique pain points. Second, you gain complete control over the user experience, implementing interfaces that feel intuitive to you. Third, you develop valuable skills in Chrome extension development that apply to any web platform. Finally, you can share your creation with the developer community or even publish it to the Chrome Web Store.
Before diving into the technical details, it is worth considering which features would provide the most value. Common GitHub productivity enhancements include quick repository search across your organizations, inline code documentation viewers, automated PR status dashboards, keyboard shortcut overlays, repository bookmarking systems, and notification aggregators. This guide will focus on building a foundation that can support multiple such features.
Understanding Chrome Extension Architecture
Chrome extensions are essentially web applications that run within the Chrome browser, with access to special APIs that standard web pages do not have. Understanding the different components of a Chrome extension is crucial for building effective functionality.
The manifest.json file serves as the configuration file for your extension, defining permissions, content scripts, background scripts, and the user interface. This is where you specify which websites the extension can access, what capabilities it requires, and how it appears in the Chrome UI.
Content scripts are JavaScript files that run in the context of web pages, allowing you to modify page content, access the DOM, and respond to user interactions on specific pages. For a GitHub extension, content scripts will be your primary tool for interacting with the GitHub interface, injecting UI elements, and enhancing page functionality.
Background scripts operate independently of any web page, running in the background to handle events, manage state, and coordinate between different parts of your extension. These are useful for maintaining persistent data, handling notifications, and managing API requests that should continue even when no GitHub pages are open.
Popup pages are the small windows that appear when you click the extension icon in the Chrome toolbar. These provide a convenient interface for quick actions and settings, though they are not strictly required for all extensions.
Setting Up Your Development Environment
Before writing any code, you need to set up a proper development environment. This involves creating the directory structure, initializing your manifest file, and configuring your development workflow.
Create a new directory for your extension project and organize it with the following structure:
github-productivity-extension/
├── manifest.json
├── background.js
├── content.js
├── popup.html
├── popup.js
├── popup.css
├── icons/
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
└── styles.css
The manifest.json file is the heart of your extension. For a GitHub productivity extension targeting modern Chrome versions, you will use Manifest V3, which offers improved security and performance. Here is a complete manifest configuration:
{
"manifest_version": 3,
"name": "GitHub Productivity Booster",
"version": "1.0.0",
"description": "Enhance your GitHub workflow with quick actions, keyboard shortcuts, and productivity tools",
"permissions": [
"storage",
"activeTab",
"scripting",
"notifications"
],
"host_permissions": [
"https://github.com/*",
"https://api.github.com/*"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://github.com/*"],
"js": ["content.js"],
"css": ["styles.css"]
}
],
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
This manifest grants your extension access to GitHub.com and the GitHub API, while also requesting the necessary permissions for storage, notifications, and script execution.
Building the Content Script
The content script is where the magic happens for a GitHub extension. This script runs on every GitHub page, allowing you to detect the current context, modify the DOM, and add custom functionality. Let us build a comprehensive content script that provides several productivity features.
First, you need to detect which type of page the user is viewing. GitHub uses different URL patterns for repositories, pull requests, issues, and other features. Your content script should identify these contexts and respond appropriately:
// content.js
// Detect current GitHub page context
function getPageContext() {
const path = window.location.pathname;
if (path.match(/^\/[^\/]+\/[^\/]+$/)) {
return 'repository';
} else if (path.match(/^\/[^\/]+\/[^\/]+\/pull$/)) {
return 'pull-request';
} else if (path.match(/^\/[^\/]+\/[^\/]+\/issues$/)) {
return 'issues';
} else if (path.match(/^\/[^\/]+\/[^\/]+\/pulls$/)) {
return 'pull-requests';
} else if (path.match(/^\/[^\/]+\/[^\/]+\/actions$/)) {
return 'actions';
} else if (path.match(/^\/[^\/]+\/[^\/]+\/settings$/)) {
return 'settings';
}
return 'unknown';
}
// Initialize based on page context
document.addEventListener('DOMContentLoaded', () => {
const context = getPageContext();
console.log('GitHub Productivity: Detected context:', context);
initializeFeatures(context);
});
Now let us add some practical features. One of the most useful enhancements is adding quick navigation to frequently accessed repositories. We can inject a floating panel that appears when the user presses a keyboard shortcut:
// Add floating quick navigation panel
function initializeFeatures(context) {
addQuickNavigationPanel();
enhancePullRequestView(context);
addRepositoryBookmarks(context);
}
// Quick navigation panel
function addQuickNavigationPanel() {
const panel = document.createElement('div');
panel.id = 'gh-productivity-panel';
panel.className = 'gh-pro-hidden';
panel.innerHTML = `
<div class="gh-pro-panel-header">
<span>Quick Navigation</span>
<button class="gh-pro-close">×</button>
</div>
<div class="gh-pro-panel-content">
<input type="text" class="gh-pro-search" placeholder="Search repositories...">
<ul class="gh-pro-results"></ul>
</div>
`;
document.body.appendChild(panel);
// Toggle panel with keyboard shortcut (Ctrl+Shift+G)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'G') {
e.preventDefault();
panel.classList.toggle('gh-pro-hidden');
}
});
// Close button functionality
panel.querySelector('.gh-pro-close').addEventListener('click', () => {
panel.classList.add('gh-pro-hidden');
});
}
For pull request pages, we can add useful enhancements like quick status checks and easy access to review actions:
// Enhance pull request view
function enhancePullRequestView(context) {
if (context !== 'pull-request') return;
// Add quick review buttons
const reviewBox = document.querySelector('.review-box');
if (reviewBox) {
const quickActions = document.createElement('div');
quickActions.className = 'gh-pro-quick-actions';
quickActions.innerHTML = `
<button class="gh-pro-btn gh-pro-btn-approve">Quick Approve</button>
<button class="gh-pro-btn gh-pro-btn-request-changes">Request Changes</button>
<button class="gh-pro-btn gh-pro-btn-comment">Comment</button>
`;
reviewBox.appendChild(quickActions);
}
}
Creating the Background Service Worker
The background service worker handles events that occur in the background, independent of any specific web page. This is essential for maintaining persistent state, handling notifications, and managing API calls. Here is a comprehensive background script:
// background.js
// Handle extension installation
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
console.log('GitHub Productivity Extension installed');
// Initialize default settings
chrome.storage.sync.set({
bookmarks: [],
keyboardShortcuts: {
quickNav: 'Ctrl+Shift+G',
copyBranch: 'Ctrl+Shift+B',
openActions: 'Ctrl+Shift+A'
},
notifications: {
prReviewed: true,
prAssigned: true,
prMentioned: true
}
});
}
});
// Handle messages from content scripts and popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_REPOSITORY_INFO') {
fetchRepositoryInfo(message.repo).then(data => {
sendResponse(data);
});
return true;
}
if (message.type === 'BOOKMARK_REPO') {
addBookmark(message.repo, message.name);
sendResponse({ success: true });
}
if (message.type === 'GET_BOOKMARKS') {
getBookmarks().then(bookmarks => {
sendResponse(bookmarks);
});
return true;
}
});
// Fetch repository information from GitHub API
async function fetchRepositoryInfo(repoPath) {
try {
const response = await fetch(`https://api.github.com/repos/${repoPath}`);
if (!response.ok) throw new Error('Repository not found');
return await response.json();
} catch (error) {
console.error('Error fetching repository info:', error);
return null;
}
}
// Bookmark management
async function addBookmark(repoPath, name) {
const { bookmarks = [] } = await chrome.storage.sync.get('bookmarks');
const newBookmark = {
id: Date.now(),
repo: repoPath,
name: name || repoPath,
addedAt: new Date().toISOString()
};
bookmarks.push(newBookmark);
await chrome.storage.sync.set({ bookmarks });
}
async function getBookmarks() {
const { bookmarks = [] } = await chrome.storage.sync.get('bookmarks');
return bookmarks;
}
Building the Popup Interface
The popup provides a convenient interface for quick actions without navigating away from the current page. Let us create a functional popup with multiple features:
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="popup-container">
<header class="popup-header">
<h1>GitHub Productivity</h1>
</header>
<nav class="popup-tabs">
<button class="tab-btn active" data-tab="bookmarks">Bookmarks</button>
<button class="tab-btn" data-tab="search">Search</button>
<button class="tab-btn" data-tab="settings">Settings</button>
</nav>
<div class="popup-content">
<div id="bookmarks" class="tab-content active">
<div class="bookmark-input">
<input type="text" id="repo-input" placeholder="owner/repo">
<button id="add-bookmark">Add</button>
</div>
<ul id="bookmarks-list" class="item-list"></ul>
</div>
<div id="search" class="tab-content">
<input type="text" id="search-input" placeholder="Search repositories...">
<div id="search-results"></div>
</div>
<div id="settings" class="tab-content">
<div class="setting-group">
<h3>Notifications</h3>
<label>
<input type="checkbox" id="notify-pr-reviewed">
PR reviewed
</label>
<label>
<input type="checkbox" id="notify-pr-assigned">
PR assigned
</label>
</div>
<button id="save-settings" class="primary-btn">Save Settings</button>
</div>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
The corresponding JavaScript handles user interactions and communicates with the background script:
// popup.js
document.addEventListener('DOMContentLoaded', () => {
initializeTabs();
loadBookmarks();
initializeSearch();
loadSettings();
});
// Tab switching
function initializeTabs() {
const tabs = document.querySelectorAll('.tab-btn');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// Update active tab button
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Show corresponding content
const tabId = tab.dataset.tab;
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(tabId).classList.add('active');
});
});
}
// Load bookmarks from storage
async function loadBookmarks() {
const response = await chrome.runtime.sendMessage({ type: 'GET_BOOKMARKS' });
const list = document.getElementById('bookmarks-list');
list.innerHTML = '';
if (response && response.length > 0) {
response.forEach(bookmark => {
const li = document.createElement('li');
li.innerHTML = `
<a href="https://github.com/${bookmark.repo}" target="_blank">
${bookmark.name}
</a>
<button class="delete-btn" data-id="${bookmark.id}">×</button>
`;
list.appendChild(li);
});
} else {
list.innerHTML = '<li class="empty">No bookmarks yet</li>';
}
}
// Add new bookmark
document.getElementById('add-bookmark').addEventListener('click', async () => {
const input = document.getElementById('repo-input');
const repo = input.value.trim();
if (repo) {
await chrome.runtime.sendMessage({
type: 'BOOKMARK_REPO',
repo: repo,
name: repo
});
input.value = '';
loadBookmarks();
}
});
// Initialize search functionality
function initializeSearch() {
const searchInput = document.getElementById('search-input');
let debounceTimer;
searchInput.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
performSearch(e.target.value);
}, 300);
});
}
async function performSearch(query) {
const resultsContainer = document.getElementById('search-results');
if (query.length < 3) {
resultsContainer.innerHTML = '';
return;
}
try {
const response = await fetch(`https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&per_page=5`);
const data = await response.json();
resultsContainer.innerHTML = data.items.map(repo => `
<div class="search-result">
<a href="${repo.html_url}" target="_blank">
<strong>${repo.full_name}</strong>
<p>${repo.description || 'No description'}</p>
</a>
</div>
`).join('');
} catch (error) {
resultsContainer.innerHTML = '<p class="error">Search failed</p>';
}
}
// Load and save settings
async function loadSettings() {
const settings = await chrome.storage.sync.get('notifications');
if (settings.notifications) {
document.getElementById('notify-pr-reviewed').checked = settings.notifications.prReviewed;
document.getElementById('notify-pr-assigned').checked = settings.notifications.prAssigned;
}
}
document.getElementById('save-settings').addEventListener('click', async () => {
const notifications = {
prReviewed: document.getElementById('notify-pr-reviewed').checked,
prAssigned: document.getElementById('notify-pr-assigned').checked,
prMentioned: document.getElementById('notify-pr-mentioned')?.checked || false
};
await chrome.storage.sync.set({ notifications });
alert('Settings saved!');
});
Styling Your Extension
Proper styling ensures your extension looks professional and integrates well with GitHub’s design language. Here are essential styles:
/* styles.css for content script */
/* Hide panel by default */
.gh-pro-hidden {
display: none !important;
}
/* Floating panel */
#gh-productivity-panel {
position: fixed;
top: 100px;
right: 20px;
width: 300px;
background: #24292f;
border: 1px solid #30363d;
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
z-index: 9999;
color: #c9d1d9;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
}
.gh-pro-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #30363d;
font-weight: 600;
}
.gh-pro-close {
background: none;
border: none;
color: #8b949e;
font-size: 20px;
cursor: pointer;
}
.gh-pro-panel-content {
padding: 16px;
}
.gh-pro-search {
width: 100%;
padding: 8px 12px;
background: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
color: #c9d1d9;
font-size: 14px;
}
.gh-pro-search:focus {
outline: none;
border-color: #58a6ff;
}
/* Quick actions on pull requests */
.gh-pro-quick-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.gh-pro-btn {
padding: 6px 12px;
border-radius: 6px;
border: 1px solid #30363d;
background: #21262d;
color: #c9d1d9;
cursor: pointer;
font-size: 12px;
}
.gh-pro-btn:hover {
background: #30363d;
}
.gh-pro-btn-approve {
background: #1f883d;
border-color: #1f883d;
color: white;
}
/* popup.css for popup interface */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
width: 350px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
background: #0d1117;
color: #c9d1d9;
}
.popup-container {
min-height: 400px;
}
.popup-header {
padding: 16px;
background: #161b22;
border-bottom: 1px solid #30363d;
}
.popup-header h1 {
font-size: 16px;
font-weight: 600;
}
.popup-tabs {
display: flex;
border-bottom: 1px solid #30363d;
}
.tab-btn {
flex: 1;
padding: 12px;
background: none;
border: none;
color: #8b949e;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.tab-btn:hover {
color: #c9d1d9;
background: #21262d;
}
.tab-btn.active {
color: #c9d1d9;
border-bottom: 2px solid #58a6ff;
}
.popup-content {
padding: 16px;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.item-list {
list-style: none;
margin-top: 12px;
}
.item-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-radius: 6px;
}
.item-list li:hover {
background: #21262d;
}
.item-list a {
color: #58a6ff;
text-decoration: none;
}
.item-list a:hover {
text-decoration: underline;
}
.bookmark-input {
display: flex;
gap: 8px;
}
.bookmark-input input {
flex: 1;
padding: 8px;
background: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
color: #c9d1d9;
}
.bookmark-input button {
padding: 8px 16px;
background: #238636;
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
}
.primary-btn {
width: 100%;
padding: 10px;
background: #238636;
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
margin-top: 16px;
}
.setting-group {
margin-bottom: 16px;
}
.setting-group h3 {
margin-bottom: 8px;
font-size: 14px;
}
.setting-group label {
display: block;
padding: 8px 0;
cursor: pointer;
}
Testing Your Extension
Before deploying, thoroughly test your extension in Chrome. Navigate to chrome://extensions/ and enable Developer Mode in the top right corner. Click “Load unpacked” and select your extension directory. Test all features on various GitHub pages, including repository pages, pull requests, issues, and actions.
Pay special attention to the following testing scenarios: keyboard shortcuts work correctly in different contexts, the popup opens and closes properly, bookmarks persist across browser sessions, search returns accurate results, and the extension does not conflict with GitHub’s own functionality.
Deploying to the Chrome Web Store
Once testing is complete, you can publish your extension to the Chrome Web Store. First, create a developer account at the Chrome Web Store Developer Dashboard. Package your extension by clicking “Pack extension” in chrome://extensions/ with your extension directory selected. Upload the packaged .zip file to the Chrome Web Store, providing a detailed description and screenshots. After review, your extension will be available for installation by millions of users.
Conclusion
Building a GitHub productivity Chrome extension is an excellent project that combines web development skills with practical utility. The architecture we have explored provides a solid foundation that you can extend with additional features like GitHub Actions monitoring, code review enhancements, issue template generators, or integration with third-party services. The key to a successful extension is focusing on features that genuinely improve your workflow, iterating based on usage patterns, and maintaining clean, well-documented code. With the foundation provided in this guide, you are well-equipped to create powerful tools that transform your GitHub experience and potentially help thousands of other developers streamline their work.