Build a Code Autocomplete Chrome Extension: Complete Developer Guide

31 min read

Build a Code Autocomplete Chrome Extension: Complete Developer Guide

Build a Code Autocomplete Chrome Extension: Complete Developer Guide

Code autocomplete extensions have revolutionized how developers write code. Tools like GitHub Copilot have demonstrated the power of AI-powered code suggestions directly within the browser and code editors. In this comprehensive guide, we will walk you through building your own code autocomplete Chrome extension from scratch. By the end of this tutorial, you will have a fully functional extension that provides intelligent code suggestions as users type in text areas and code editors across the web.


Why Build a Code Autocomplete Chrome Extension?

The demand for code autocomplete extension tools has never been higher. Developers are constantly seeking ways to improve their productivity, reduce repetitive coding tasks, and minimize syntax errors. Building an AI code helper Chrome extension allows you to tap into this growing market while learning valuable skills in Chrome extension development, artificial intelligence integration, and modern web APIs.

There are several compelling reasons to undertake this project:

First, code suggestion features are highly sought after by developers. Whether you are building a tool for personal use or planning to publish on the Chrome Web Store, having a code autocomplete feature adds significant value to any development workflow. Many developers rely on these tools to speed up their coding process and avoid common mistakes.

Second, this project provides an excellent opportunity to learn about the Chrome Extension platform’s advanced features. You will work with content scripts, background service workers, message passing, and various Chrome APIs. These skills are transferable to many other extension projects you might want to build in the future.

Third, the extensibility of Chrome means your extension can work across numerous websites and code editors. Unlike standalone IDE plugins, a Chrome extension can provide code suggestions in GitHub, Stack Overflow, documentation sites, and web-based code playgrounds.


Understanding the Architecture

Before we dive into coding, it is essential to understand the architecture of a code autocomplete Chrome extension. Modern Chrome extensions follow the Manifest V3 specification, which requires a specific structure and utilizes service workers instead of background pages.

Core Components

Our code autocomplete extension will consist of four main components:

  1. Manifest File (manifest.json): The configuration file that defines the extension’s permissions, capabilities, and entry points.

  2. Content Script: Injected into web pages to detect user input in text areas and code editors, then display the autocomplete dropdown.

  3. Background Service Worker: Handles communication with external APIs (such as AI services) and manages extension state.

  4. Popup Interface: A simple user interface for configuring extension settings, API keys, and preferences.

How It Works

The flow of our code autocomplete extension works as follows:

First, the content script detects when a user focuses on a text area or code editor on any webpage. It listens for keyboard events and captures the context around the cursor position.

Next, when the user pauses typing or presses a trigger key (such as Tab or Ctrl+Space), the content script sends a message to the background service worker with the current code context.

The background service worker then processes this context and sends it to an AI code completion API (which could be OpenAI, Claude, or a custom model). The AI analyzes the code and generates relevant suggestions.

Finally, the suggestions are returned to the content script, which displays them in a floating dropdown menu near the cursor. When the user selects a suggestion, it is inserted into the code editor at the current position.


Setting Up the Project Structure

Let us start by creating the project structure for our code autocomplete Chrome extension. Create a new folder called code-autocomplete-extension and set up the following file structure:

code-autocomplete-extension/
├── manifest.json
├── background.js
├── content.js
├── popup.html
├── popup.js
├── popup.css
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

You can use any basic images for the icons for now, or create simple placeholder icons. The extension will function without them, but they are required for publishing to the Chrome Web Store.


Creating the Manifest File

The manifest.json file is the heart of every Chrome extension. For our code autocomplete extension, we need to declare the appropriate permissions and define the extension’s components:

{
  "manifest_version": 3,
  "name": "Code Autocomplete AI",
  "version": "1.0.0",
  "description": "AI-powered code autocomplete extension for Chrome. Get intelligent code suggestions as you type.",
  "permissions": [
    "storage",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

This manifest file grants the extension the necessary permissions to run on all websites, access storage for saving API keys, and communicate between content scripts and the background service worker.


Implementing the Content Script

The content script is responsible for detecting code editors, capturing user input, and displaying the autocomplete dropdown. Create a file called content.js with the following implementation:

// Code Autocomplete Extension - Content Script

class CodeAutocomplete {
  constructor() {
    this.suggestions = [];
    this.currentPosition = 0;
    this.isVisible = false;
    this.dropdown = null;
    this.currentTextarea = null;
    this.apiKey = null;
    
    this.init();
  }

  async init() {
    // Load API key from storage
    const result = await chrome.storage.local.get(['apiKey']);
    this.apiKey = result.apiKey;
    
    // Create dropdown element
    this.createDropdown();
    
    // Set up event listeners
    this.setupEventListeners();
    
    // Listen for messages from background script
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
      if (message.type === 'suggestions') {
        this.displaySuggestions(message.suggestions);
      }
    });
  }

  createDropdown() {
    this.dropdown = document.createElement('div');
    this.dropdown.className = 'code-autocomplete-dropdown';
    this.dropdown.style.cssText = `
      position: absolute;
      display: none;
      background: #1e1e1e;
      border: 1px solid #3c3c3c;
      border-radius: 6px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
      max-height: 300px;
      overflow-y: auto;
      z-index: 999999;
      font-family: 'Consolas', 'Monaco', monospace;
      font-size: 13px;
    `;
    document.body.appendChild(this.dropdown);
  }

  setupEventListeners() {
    // Listen for focus on textareas and inputs
    document.addEventListener('focusin', (e) => {
      if (this.isCodeElement(e.target)) {
        this.currentTextarea = e.target;
        this.updatePosition(e.target);
      }
    });

    // Listen for input events
    document.addEventListener('input', (e) => {
      if (this.isCodeElement(e.target)) {
        this.handleInput(e.target);
      }
    });

    // Listen for keyboard events
    document.addEventListener('keydown', (e) => {
      if (this.isVisible) {
        this.handleKeyboard(e);
      }
    });

    // Close dropdown when clicking outside
    document.addEventListener('click', (e) => {
      if (!this.dropdown.contains(e.target) && e.target !== this.currentTextarea) {
        this.hideDropdown();
      }
    });
  }

  isCodeElement(element) {
    const codeSelectors = [
      'textarea',
      'input[type="text"]',
      'input[type="search"]',
      '[contenteditable="true"]',
      '.code-editor',
      '.monaco-editor',
      '.cm-editor',
      '[data-language]'
    ];
    
    return codeSelectors.some(selector => {
      try {
        return element.matches(selector) || element.closest(selector);
      } catch (e) {
        return false;
      }
    });
  }

  updatePosition(element) {
    const rect = element.getBoundingClientRect();
    const style = window.getComputedStyle(element);
    const lineHeight = parseInt(style.lineHeight) || 20;
    
    // Calculate cursor position (simplified)
    this.dropdown.style.top = `${rect.top + window.scrollY + lineHeight}px`;
    this.dropdown.style.left = `${rect.left + window.scrollX}px`;
  }

  async handleInput(element) {
    const text = element.value;
    const cursorPos = element.selectionStart;
    
    // Get context around cursor
    const context = this.getContext(text, cursorPos);
    
    // Debounce API calls
    clearTimeout(this.debounceTimer);
    this.debounceTimer = setTimeout(() => {
      this.fetchSuggestions(context);
    }, 500);
  }

  getContext(text, cursorPos) {
    // Get text before cursor
    const beforeCursor = text.substring(0, cursorPos);
    
    // Get last few lines for context
    const lines = beforeCursor.split('\n');
    const lastLines = lines.slice(-10).join('\n');
    
    return lastLines;
  }

  async fetchSuggestions(context) {
    if (!this.apiKey) {
      console.log('Code Autocomplete: Please set your API key in the extension popup');
      return;
    }

    try {
      const response = await chrome.runtime.sendMessage({
        type: 'getSuggestions',
        context: context,
        language: this.detectLanguage()
      });
      
      if (response && response.suggestions) {
        this.displaySuggestions(response.suggestions);
      }
    } catch (error) {
      console.error('Code Autocomplete: Error fetching suggestions', error);
    }
  }

  detectLanguage() {
    // Simple language detection based on URL or page content
    const url = window.location.href;
    
    if (url.includes('github') || url.includes('stackoverflow')) {
      return 'javascript';
    }
    
    return 'javascript';
  }

  displaySuggestions(suggestions) {
    if (!suggestions || suggestions.length === 0) {
      this.hideDropdown();
      return;
    }

    this.suggestions = suggestions;
    this.dropdown.innerHTML = '';
    
    suggestions.forEach((suggestion, index) => {
      const item = document.createElement('div');
      item.className = 'suggestion-item';
      item.style.cssText = `
        padding: 8px 12px;
        cursor: pointer;
        color: #d4d4d4;
        border-bottom: 1px solid #2d2d2d;
        white-space: pre-wrap;
        word-break: break-all;
      `;
      
      item.textContent = suggestion;
      
      item.addEventListener('mouseenter', () => {
        item.style.background = '#264f78';
      });
      
      item.addEventListener('mouseleave', () => {
        item.style.background = 'transparent';
      });
      
      item.addEventListener('click', () => {
        this.insertSuggestion(index);
      });
      
      this.dropdown.appendChild(item);
    });

    this.updatePosition(this.currentTextarea);
    this.showDropdown();
  }

  insertSuggestion(index) {
    const suggestion = this.suggestions[index];
    if (!this.currentTextarea || !suggestion) return;

    const cursorPos = this.currentTextarea.selectionStart;
    const text = this.currentTextarea.value;
    
    // Insert suggestion at cursor position
    const newText = text.substring(0, cursorPos) + suggestion + text.substring(cursorPos);
    this.currentTextarea.value = newText;
    
    // Move cursor to end of inserted text
    const newCursorPos = cursorPos + suggestion.length;
    this.currentTextarea.setSelectionRange(newCursorPos, newCursorPos);
    
    this.hideDropdown();
    this.currentTextarea.focus();
  }

  handleKeyboard(e) {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        this.currentPosition = Math.min(this.currentPosition + 1, this.suggestions.length - 1);
        this.highlightSuggestion();
        break;
      case 'ArrowUp':
        e.preventDefault();
        this.currentPosition = Math.max(this.currentPosition - 1, 0);
        this.highlightSuggestion();
        break;
      case 'Enter':
        if (this.isVisible) {
          e.preventDefault();
          this.insertSuggestion(this.currentPosition);
        }
        break;
      case 'Tab':
        if (this.isVisible) {
          e.preventDefault();
          this.insertSuggestion(this.currentPosition);
        }
        break;
      case 'Escape':
        this.hideDropdown();
        break;
    }
  }

  highlightSuggestion() {
    const items = this.dropdown.querySelectorAll('.suggestion-item');
    items.forEach((item, index) => {
      if (index === this.currentPosition) {
        item.style.background = '#264f78';
        item.scrollIntoView({ block: 'nearest' });
      } else {
        item.style.background = 'transparent';
      }
    });
  }

  showDropdown() {
    this.dropdown.style.display = 'block';
    this.isVisible = true;
    this.currentPosition = 0;
  }

  hideDropdown() {
    this.dropdown.style.display = 'none';
    this.isVisible = false;
    this.currentPosition = 0;
  }
}

// Initialize the autocomplete system
new CodeAutocomplete();

This content script provides the foundation for detecting code elements and displaying suggestions. It handles keyboard navigation, suggestion selection, and positioning of the dropdown menu.


Creating the Background Service Worker

The background service worker handles communication with AI APIs and manages extension state. Create background.js:

// Code Autocomplete Extension - Background Service Worker

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'getSuggestions') {
    getCodeSuggestions(message.context, message.language)
      .then(suggestions => {
        sendResponse({ suggestions: suggestions });
      })
      .catch(error => {
        console.error('Error getting suggestions:', error);
        sendResponse({ suggestions: [] });
      });
    
    return true; // Keep the message channel open for async response
  }
});

async function getCodeSuggestions(context, language) {
  // Get API key from storage
  const result = await chrome.storage.local.get(['apiKey']);
  const apiKey = result.apiKey;
  
  if (!apiKey) {
    console.log('No API key configured');
    return generateMockSuggestions(context);
  }

  try {
    // Using OpenAI API as an example
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`
      },
      body: JSON.stringify({
        model: 'gpt-3.5-turbo',
        messages: [
          {
            role: 'system',
            content: `You are a code completion assistant. Given the code context, provide up to 5 possible code completions. Each completion should be a single line or block that logically continues the code. Return only the completion text, one per line.`
          },
          {
            role: 'user',
            content: `Complete this ${language} code:\n\n${context}`
          }
        ],
        max_tokens: 150,
        temperature: 0.7,
        n: 5
      })
    });

    if (!response.ok) {
      throw new Error(`API request failed: ${response.status}`);
    }

    const data = await response.json();
    const content = data.choices[0].message.content;
    
    // Parse suggestions from response
    const suggestions = content.split('\n')
      .map(line => line.trim())
      .filter(line => line.length > 0)
      .slice(0, 5);
    
    return suggestions;
    
  } catch (error) {
    console.error('API Error:', error);
    // Fall back to mock suggestions on error
    return generateMockSuggestions(context);
  }
}

// Generate mock suggestions for testing
function generateMockSuggestions(context) {
  const suggestions = [];
  const lastLine = context.split('\n').pop().trim();
  
  // Simple pattern-based suggestions
  if (lastLine.includes('function') || lastLine.includes('=>')) {
    suggestions.push('{');
    suggestions.push('{ return value; }');
    suggestions.push('{ console.log(); }');
  }
  
  if (lastLine.includes('const') || lastLine.includes('let') || lastLine.includes('var')) {
    suggestions.push('= [];');
    suggestions.push('= {};');
    suggestions.push('= "");');
  }
  
  if (lastLine.includes('if')) {
    suggestions.push('{}');
    suggestions.push('{ } else { }');
  }
  
  if (lastLine.includes('for')) {
    suggestions.push('{}');
    suggestions.push('{ for(let i = 0; i < length; i++) { } }');
  }
  
  if (lastLine.includes('class')) {
    suggestions.push('{');
    suggestions.push('{ constructor() { } }');
  }
  
  return suggestions.length > 0 ? suggestions : ['// Type to get suggestions'];
}

// Handle extension installation
chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'install') {
    console.log('Code Autocomplete Extension installed');
    // Set default settings
    chrome.storage.local.set({
      apiKey: '',
      enabled: true
    });
  }
});

This background service worker connects to the OpenAI API for code suggestions. It also includes a fallback mechanism that provides basic pattern-based suggestions when no API key is configured.


The popup interface allows users to configure their API key and extension settings. Create the following files:

popup.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Code Autocomplete Settings</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <h1>Code Autocomplete AI</h1>
    <p class="subtitle">Intelligent code suggestions for Chrome</p>
    
    <div class="settings">
      <div class="form-group">
        <label for="apiKey">OpenAI API Key</label>
        <input type="password" id="apiKey" placeholder="sk-..." />
        <p class="help-text">Get your API key from <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI</a></p>
      </div>
      
      <div class="form-group">
        <label class="toggle">
          <input type="checkbox" id="enabled" checked />
          <span class="slider"></span>
          <span class="label-text">Enable Autocomplete</span>
        </label>
      </div>
      
      <button id="saveBtn" class="save-button">Save Settings</button>
      <p id="status" class="status"></p>
    </div>
    
    <div class="info">
      <h3>How to Use</h3>
      <ul>
        <li>Click on any code editor or text area</li>
        <li>Start typing your code</li>
        <li>Press <strong>Tab</strong> or <strong>Ctrl+Space</strong> to see suggestions</li>
        <li>Use arrow keys to navigate and Tab to select</li>
      </ul>
    </div>
  </div>
  <script src="popup.js"></script>
</body>
</html>

popup.css

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  width: 320px;
  background: #1e1e1e;
  color: #d4d4d4;
}

.container {
  padding: 20px;
}

h1 {
  font-size: 18px;
  color: #569cd6;
  margin-bottom: 4px;
}

.subtitle {
  font-size: 12px;
  color: #808080;
  margin-bottom: 20px;
}

.form-group {
  margin-bottom: 16px;
}

label {
  display: block;
  font-size: 13px;
  margin-bottom: 6px;
  color: #9cdcfe;
}

input[type="password"],
input[type="text"] {
  width: 100%;
  padding: 8px 12px;
  background: #252526;
  border: 1px solid #3c3c3c;
  border-radius: 4px;
  color: #d4d4d4;
  font-size: 13px;
}

input[type="password"]:focus,
input[type="text"]:focus {
  outline: none;
  border-color: #569cd6;
}

.help-text {
  font-size: 11px;
  color: #6a9955;
  margin-top: 4px;
}

.help-text a {
  color: #4fc1ff;
  text-decoration: none;
}

.toggle {
  display: flex;
  align-items: center;
  cursor: pointer;
}

.toggle input {
  display: none;
}

.slider {
  width: 40px;
  height: 20px;
  background: #3c3c3c;
  border-radius: 20px;
  position: relative;
  margin-right: 10px;
  transition: 0.3s;
}

.slider:before {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: white;
  top: 2px;
  left: 2px;
  transition: 0.3s;
}

input:checked + .slider {
  background: #569cd6;
}

input:checked + .slider:before {
  transform: translateX(20px);
}

.label-text {
  color: #d4d4d4;
}

.save-button {
  width: 100%;
  padding: 10px;
  background: #0e639c;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 13px;
  cursor: pointer;
  transition: 0.2s;
}

.save-button:hover {
  background: #1177bb;
}

.status {
  margin-top: 10px;
  font-size: 12px;
  text-align: center;
}

.status.success {
  color: #4ec9b0;
}

.status.error {
  color: #f48771;
}

.info {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid #3c3c3c;
}

.info h3 {
  font-size: 13px;
  color: #569cd6;
  margin-bottom: 8px;
}

.info ul {
  list-style: none;
  font-size: 12px;
}

.info li {
  margin-bottom: 6px;
  padding-left: 12px;
  position: relative;
}

.info li:before {
  content: '›';
  position: absolute;
  left: 0;
  color: #569cd6;
}

.info strong {
  color: #ce9178;
}

popup.js

document.addEventListener('DOMContentLoaded', () => {
  // Load saved settings
  loadSettings();
  
  // Set up save button
  document.getElementById('saveBtn').addEventListener('click', saveSettings);
});

async function loadSettings() {
  const result = await chrome.storage.local.get(['apiKey', 'enabled']);
  
  if (result.apiKey) {
    document.getElementById('apiKey').value = result.apiKey;
  }
  
  document.getElementById('enabled').checked = result.enabled !== false;
}

async function saveSettings() {
  const apiKey = document.getElementById('apiKey').value.trim();
  const enabled = document.getElementById('enabled').checked;
  
  const statusEl = document.getElementById('status');
  
  try {
    await chrome.storage.local.set({
      apiKey: apiKey,
      enabled: enabled
    });
    
    statusEl.textContent = 'Settings saved successfully!';
    statusEl.className = 'status success';
    
    setTimeout(() => {
      statusEl.textContent = '';
      statusEl.className = 'status';
    }, 2000);
    
  } catch (error) {
    statusEl.textContent = 'Error saving settings';
    statusEl.className = 'status error';
  }
}

Testing Your Extension

Now that we have built all the components, let us test our code autocomplete Chrome extension:

  1. Open Chrome and navigate to chrome://extensions/
  2. Enable Developer Mode using the toggle in the top-right corner
  3. Click “Load unpacked” and select your code-autocomplete-extension folder
  4. The extension should now appear in your toolbar

To test the extension:

  1. Navigate to any website with a text area (such as GitHub, Stack Overflow, or a code editor)
  2. Click on a text area and start typing
  3. If you have configured an API key, suggestions will appear automatically
  4. If not, you can still test the UI by typing keywords like function, const, or if

Enhancing Your Extension

There are many ways to improve your code autocomplete Chrome extension:

Add Support for Multiple AI Providers

You can extend the background service worker to support other AI providers such as Anthropic’s Claude, Google’s Gemini, or even local models. This gives users more options and can reduce costs.

Implement Smart Language Detection

Improve the language detection to automatically identify the programming language based on the website URL, file extension, or code patterns. This allows for more accurate suggestions.

Add Context Awareness

Enhance the suggestion algorithm to consider the entire file or page context, not just the current line. This can provide more accurate and contextually relevant suggestions.

Implement Caching

Add caching to reduce API calls and improve response times. Store frequently used code patterns and their suggestions locally.

Add Custom Snippets

Allow users to define their own code snippets that can be triggered with shortcuts. This is particularly useful for boilerplate code and common patterns.


Publishing to the Chrome Web Store

When you are ready to publish your extension:

  1. Create a ZIP file of your extension folder (excluding source files if needed)
  2. Go to the Chrome Web Store Developer Dashboard
  3. Create a new item and upload your ZIP file
  4. Fill in the required information including description, screenshots, and category
  5. Submit for review

Ensure you comply with Chrome Web Store policies, particularly regarding user data collection and API usage.


Conclusion

Building a code autocomplete Chrome extension is an excellent project that combines web development skills with artificial intelligence integration. In this guide, we covered the essential components needed to create a functional code autocomplete extension with AI-powered code suggestion capabilities.

The extension we built includes content scripts for detecting code editors and displaying suggestions, a background service worker for communicating with AI APIs, and a popup interface for configuration. These patterns can be extended and customized to create more sophisticated ai code helper chrome tools.

As you continue to develop your extension, consider adding more advanced features such as multi-language support, custom snippet insertion, and integration with additional AI providers. The Chrome extension platform provides powerful APIs that enable you to create truly innovative tools for developers.

Remember to test thoroughly across different websites and code editors, and gather user feedback to continuously improve your extension. With dedication and creativity, you can build a valuable tool that helps developers write better code faster.


Frequently Asked Questions

Q: Do I need an API key to use this extension?

A: Yes, to get AI-powered suggestions, you need an OpenAI API key. You can get one from the OpenAI platform. Without an API key, the extension provides basic pattern-based suggestions.

Q: Can this extension work with any code editor?

A: The extension works with most web-based code editors and text areas. It detects standard HTML elements like textareas and inputs, as well as popular code editor frameworks.

Q: Is my code sent to external servers?

A: Yes, when you have an API key configured, the code context is sent to the AI provider for processing. No code is stored on external servers beyond what is necessary for generating suggestions.

Q: Can I use a different AI provider?

A: Yes, you can modify the background service worker to work with other AI providers such as Claude, Gemini, or custom models.

Q: How do I debug the extension?

A: You can use Chrome’s developer tools. Right-click on the extension icon and select “Inspect popup” for the popup, or navigate to chrome://extensions/ and click “Service worker” under your extension to debug the background script.

No previous article
No next article