Chrome Extension GitHub Integration: Complete Tutorial for 2025

17 min read

Chrome Extension GitHub Integration: Complete Tutorial for 2025

Chrome Extension GitHub Integration: Complete Tutorial for 2025

Integrating your Chrome extension with GitHub opens up a world of possibilities for developers and teams. Whether you want to create a repository manager, issue tracker, or pull request notifier, the GitHub API provides the foundation you need. In this comprehensive guide, we’ll walk you through building a Chrome extension that seamlessly interacts with GitHub, covering everything from OAuth authentication to advanced API calls.

This tutorial assumes you have basic knowledge of JavaScript and Chrome extension development. We’ll use Manifest V3, the latest version of the Chrome extension platform, ensuring your extension follows current best practices and passes Chrome Web Store review.


Why Integrate Chrome Extensions with GitHub?

The combination of Chrome extensions and GitHub creates powerful workflows that can significantly enhance developer productivity. By bringing GitHub functionality directly into the browser, you eliminate the need to switch between contexts and reduce friction in your daily development tasks.

Common Use Cases

There are numerous practical applications for Chrome extension GitHub integration. Repository management extensions allow you to view star counts, check repository health, and manage settings without leaving your browser. Issue tracking extensions enable you to create, view, and update GitHub issues directly from the Chrome interface, making it easier to capture bugs and feature requests as you browse the web.

Pull request management is another popular use case. Extensions can notify you of new PRs, display diff previews, and even allow you to leave comments without opening the full GitHub interface. For teams using GitHub Actions, integration extensions can show workflow status, trigger builds, and display deployment information right in the browser.

The convenience factor cannot be overstated. Instead of maintaining a separate tab for GitHub or constantly switching contexts, you can access the functionality you need exactly where you already are—in your browser.


Prerequisites and Setup

Before we dive into the implementation, let’s ensure you have everything needed to follow this tutorial. You’ll need Node.js installed on your system, along with a modern code editor like Visual Studio Code. You’ll also need a GitHub account to register your OAuth application and test the integration.

Creating Your Project Structure

Let’s start by setting up the extension project. Create a new directory for your extension and initialize the necessary files. The project structure will follow Manifest V3 conventions, with clear separation between the popup, background service worker, and content scripts.

mkdir github-integration-extension
cd github-integration-extension
mkdir -p popup background content scripts icons

The popup directory will contain the HTML, CSS, and JavaScript for the extension’s popup UI. The background directory houses the service worker that handles API calls and manages authentication state. Content scripts can be added if you want the extension to interact with the github.com domain directly.


Manifest V3 Configuration

The manifest.json file is the heart of any Chrome extension. For GitHub integration, we need to declare specific permissions and configure the extension properly.

{
  "manifest_version": 3,
  "name": "GitHub Integration",
  "version": "1.0.0",
  "description": "Integrate with GitHub repositories, issues, and pull requests",
  "permissions": [
    "storage",
    "identity",
    "https://api.github.com/*"
  ],
  "host_permissions": [
    "https://github.com/*"
  ],
  "oauth2": {
    "client_id": "YOUR_CLIENT_ID",
    "scopes": ["repo", "read:user"]
  },
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background/background.js"
  }
}

Notice the OAuth2 configuration. This tells Chrome to handle the OAuth flow using the chrome.identity API. The scopes we request—repo and read:user—allow access to repositories and basic user information. Adjust these scopes based on your extension’s actual needs.


Implementing OAuth Authentication

Authentication is the foundation of any GitHub integration. The chrome.identity API provides the launchWebAuthFlow method, which handles the OAuth redirect flow in a popup window.

Setting Up GitHub OAuth App

Before writing code, you need to register an OAuth application in GitHub. Navigate to your GitHub account settings, go to Developer settings, and select OAuth Apps. Click “New OAuth App” and fill in the required information.

For the Authorization callback URL, use: https://<extension-id>.chromiumapp.org/

The extension ID is assigned when you first load your extension in Chrome. You can find it in chrome://extensions after enabling developer mode. Note that this ID changes if you repack your extension, so you may need to update the callback URL during development.

Authentication Code Implementation

Create the background service worker to handle authentication:

// background/background.js

const CLIENT_ID = 'YOUR_GITHUB_CLIENT_ID';
const SCOPES = ['repo', 'read:user'];

let accessToken = null;

// Check for stored token on startup
chrome.runtime.onStartup.addListener(async () => {
  const result = await chrome.storage.local.get(['githubToken']);
  if (result.githubToken) {
    accessToken = result.githubToken;
  }
});

export async function authenticate() {
  return new Promise((resolve, reject) => {
    const redirectUri = chrome.identity.getRedirectURL();
    const authUrl = `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${SCOPES.join(',')}`;
    
    chrome.identity.launchWebAuthFlow(
      { url: authUrl, interactive: true },
      async (responseUrl) => {
        if (chrome.runtime.lastError) {
          reject(chrome.runtime.lastError);
          return;
        }
        
        // Extract code from redirect URL
        const url = new URL(responseUrl);
        const code = url.searchParams.get('code');
        
        // In production, exchange code for token via your backend
        // For this tutorial, we'll store the code for demonstration
        resolve(code);
      }
    );
  });
}

export async function getAccessToken() {
  if (accessToken) {
    return accessToken;
  }
  
  const result = await chrome.storage.local.get(['githubToken']);
  if (result.githubToken) {
    accessToken = result.githubToken;
    return accessToken;
  }
  
  return null;
}

The authentication flow works as follows: when the user clicks the authenticate button in your popup, the extension launches the GitHub OAuth page in a popup window. After the user authorizes the application, GitHub redirects back to the extension with an authorization code. This code should be exchanged for an access token.

For security reasons, you should exchange the code for a token on your own server rather than in the extension. This prevents exposing your client secret and allows you to implement additional security measures.


Making GitHub API Calls

With authentication in place, we can now make API calls to GitHub. The REST API provides endpoints for repositories, issues, pull requests, and more. For more complex queries, especially around relationships between objects, consider using GraphQL.

Basic API Client

Create a utility module for making API requests:

// background/github-api.js

import { getAccessToken } from './background.js';

const API_BASE = 'https://api.github.com';

async function makeRequest(endpoint, options = {}) {
  const token = await getAccessToken();
  
  if (!token) {
    throw new Error('Not authenticated');
  }
  
  const headers = {
    'Authorization': `Bearer ${token}`,
    'Accept': 'application/vnd.github.v3+json',
    'X-GitHub-Api-Version': '2022-11-28'
  };
  
  if (options.body && typeof options.body === 'object') {
    headers['Content-Type'] = 'application/json';
    options.body = JSON.stringify(options.body);
  }
  
  const response = await fetch(`${API_BASE}${endpoint}`, {
    ...options,
    headers
  });
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message || `API error: ${response.status}`);
  }
  
  return response.json();
}

// Repository operations
export async function getRepository(owner, repo) {
  return makeRequest(`/repos/${owner}/${repo}`);
}

export async function getUserRepositories() {
  return makeRequest('/user/repos?sort=updated&per_page=30');
}

export async function getRepositoryIssues(owner, repo, options = {}) {
  const params = new URLSearchParams(options);
  return makeRequest(`/repos/${owner}/${repo}/issues?${params}`);
}

export async function createIssue(owner, repo, title, body, labels = []) {
  return makeRequest(`/repos/${owner}/${repo}/issues`, {
    method: 'POST',
    body: { title, body, labels }
  });
}

export async function getPullRequests(owner, repo, state = 'open') {
  return makeRequest(`/repos/${owner}/${repo}/pulls?state=${state}`);
}

export async function getAuthenticatedUser() {
  return makeRequest('/user');
}

This API client provides methods for common GitHub operations. The getAccessToken function retrieves the stored authentication token, and each API method adds the appropriate authorization headers.


The popup is what users interact with most frequently. Let’s create a clean, functional interface that displays repository information and provides quick actions.

<!-- popup/popup.html -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>GitHub Integration</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>GitHub Integration</h1>
      <button id="auth-btn" class="btn-primary">Connect GitHub</button>
    </header>
    
    <div id="user-info" class="hidden">
      <div class="user-profile">
        <img id="user-avatar" src="" alt="User avatar">
        <div>
          <h2 id="user-name"></h2>
          <p id="user-login"></p>
        </div>
      </div>
      
      <div class="repositories">
        <h3>Recent Repositories</h3>
        <ul id="repo-list"></ul>
      </div>
      
      <div class="quick-actions">
        <button id="refresh-btn" class="btn-secondary">Refresh</button>
        <button id="logout-btn" class="btn-secondary">Disconnect</button>
      </div>
    </div>
  </div>
  
  <script type="module" src="popup.js"></script>
</body>
</html>
// popup/popup.js

import { authenticate, getAccessToken } from '../background/background.js';
import { getUserRepositories, getAuthenticatedUser } from '../background/github-api.js';

document.addEventListener('DOMContentLoaded', async () => {
  const authBtn = document.getElementById('auth-btn');
  const userInfo = document.getElementById('user-info');
  
  // Check if user is already authenticated
  const token = await getAccessToken();
  if (token) {
    authBtn.classList.add('hidden');
    userInfo.classList.remove('hidden');
    await loadUserData();
  }
  
  authBtn.addEventListener('click', async () => {
    try {
      await authenticate();
      await loadUserData();
      authBtn.classList.add('hidden');
      userInfo.classList.remove('hidden');
    } catch (error) {
      console.error('Authentication failed:', error);
      alert('Authentication failed. Please try again.');
    }
  });
  
  document.getElementById('logout-btn').addEventListener('click', async () => {
    await chrome.storage.local.remove(['githubToken']);
    window.location.reload();
  });
  
  document.getElementById('refresh-btn').addEventListener('click', loadUserData);
});

async function loadUserData() {
  try {
    const user = await getAuthenticatedUser();
    document.getElementById('user-avatar').src = user.avatar_url;
    document.getElementById('user-name').textContent = user.name || user.login;
    document.getElementById('user-login').textContent = '@' + user.login;
    
    const repos = await getUserRepositories();
    const repoList = document.getElementById('repo-list');
    repoList.innerHTML = '';
    
    repos.slice(0, 5).forEach(repo => {
      const li = document.createElement('li');
      li.innerHTML = `
        <a href="${repo.html_url}" target="_blank">
          <span class="repo-name">${repo.name}</span>
          <span class="repo-stars">★ ${repo.stargazers_count}</span>
        </a>
      `;
      repoList.appendChild(li);
    });
  } catch (error) {
    console.error('Failed to load user data:', error);
  }
}

Handling the OAuth Token Exchange

As mentioned earlier, you should exchange the OAuth code for a token on your server rather than in the extension. Here’s a simple example using Express:

// server.js (example)
const express = require('express');
const axios = require('axios');
const app = express();

app.get('/auth/github/callback', async (req, res) => {
  const { code } = req.query;
  
  try {
    const response = await axios.post('https://github.com/login/oauth/access_token', {
      client_id: process.env.GITHUB_CLIENT_ID,
      client_secret: process.env.GITHUB_CLIENT_SECRET,
      code
    }, {
      headers: { Accept: 'application/json' }
    });
    
    const { access_token } = response.data;
    
    // Store token securely and redirect to extension
    // In production, you might want to associate this with a user session
    res.redirect(`https://extension-id.chromiumapp.org/?token=${access_token}`);
  } catch (error) {
    res.status(500).send('Authentication failed');
  }
});

app.listen(3000);

The extension would then retrieve the token from the redirect URL and store it using chrome.storage.local. This keeps your client secret secure and allows for additional validation if needed.


Best Practices and Security Considerations

When building GitHub-integrated Chrome extensions, security should be your top priority. Here are essential best practices to follow.

Token Security

Never store tokens in localStorage or chrome.storage.sync without encryption. Use chrome.storage.local for sensitive data, and consider implementing token refresh logic to handle expired tokens gracefully. Implement proper logout functionality that clears tokens completely.

Scope Minimization

Only request the OAuth scopes your extension actually needs. If you’re building a simple repository viewer, you might only need read:user and repo:status. Be honest with users about what data your extension accesses and why.

Rate Limiting Handling

GitHub’s API has rate limits—60 requests per hour for unauthenticated requests and 5,000 for authenticated requests. Implement proper error handling for rate limit responses and consider caching frequently accessed data to reduce API calls.

Content Security Policy

Manifest V3 enforces strict Content Security Policy. Ensure all your API calls go to allowed domains and that you’re not executing dynamic code. Use JSON.parse for parsing API responses rather than eval.


Advanced Features

Once you have the basic integration working, consider adding these advanced features to make your extension truly stand out.

Real-time Notifications

Use the GitHub Notifications API to fetch and display real-time alerts for issues, PRs, and mentions. Combine this with Chrome’s notification API to show desktop notifications when important events occur.

Implement a search interface that leverages GitHub’s search API to find repositories, code, issues, and pull requests. Add keyboard shortcuts for quick access to search functionality.

GitHub Actions Integration

Display workflow runs, build status, and deployment information. Allow users to trigger workflows or rerun failed builds directly from the extension.


Testing and Debugging

Chrome provides excellent developer tools for testing extensions. Use chrome://extensions to load your unpacked extension and access the service worker console. The popup has its own DevTools—you can right-click the popup and select “Inspect” to debug it.

For testing GitHub integration, create a test organization with dummy repositories. This allows you to test all functionality without affecting real projects.


Conclusion

Building a Chrome extension with GitHub integration opens up powerful possibilities for developer productivity. In this tutorial, we covered the essential components: OAuth authentication using chrome.identity, API client implementation, popup UI creation, and security best practices.

Remember to follow GitHub’s terms of service and API guidelines when building your extension. With proper implementation, your extension can provide a seamless workflow that saves time and enhances the development experience.

Start with the basics covered here, then iterate and add features based on your users’ needs. The GitHub API is extensive, and there’s no limit to what you can build when you master the integration.


Happy coding! If you have questions about this tutorial, feel free to leave a comment below.

No previous article
No next article