Build a Code Snippet Manager Chrome Extension: Save and Reuse Code Blocks

21 min read

Build a Code Snippet Manager Chrome Extension: Save and Reuse Code Blocks

Build a Code Snippet Manager Chrome Extension: Save and Reuse Code Blocks

Every developer knows the frustration of writing the same code pattern for the hundredth time. Whether it is a React useState hook, a Python logging configuration, a SQL query template, or a CSS flexbox centering snippet, reusable code blocks save countless hours of redundant typing. A well-designed code snippet manager Chrome extension transforms your browser into a powerful, always-accessible code library that travels with you across every project and every device.

In this comprehensive guide, we will walk through building a complete code snippet manager Chrome extension from scratch. You will learn how to create a popup interface for quick snippet capture, implement persistent storage using Chrome’s storage API, add powerful search and organization features, and enable one-click copying back to your clipboard. By the end of this tutorial, you will have a fully functional snippet manager extension ready to streamline your development workflow.


Why Build a Code Snippet Manager Extension?

Before diving into the code, let us explore why a custom snippet manager is worth building. The Chrome Web Store offers several existing snippet managers, but they often come with limitations. Some require paid subscriptions for basic features, others sync to cloud services you may not trust with your code, and many include more complexity than you actually need.

Building your own snippet manager gives you complete control over your data, ensures your code never leaves your local browser unless you explicitly choose to sync it, and allows you to customize every feature to match your exact workflow. Additionally, the process of building the extension teaches valuable skills in Chrome extension development that apply to countless other projects.

A quality code snippet manager should support multiple programming languages, provide syntax highlighting for readability, allow flexible organization through tags and categories, offer fast search across your entire library, and integrate seamlessly with your coding workflow through keyboard shortcuts and context menus.


Project Structure and Manifest Configuration

Every Chrome extension begins with a manifest file that tells Chrome about the extension is capabilities and permissions. Create a new folder for your project and start with the manifest.json file:

{
  "manifest_version": 3,
  "name": "Code Snippet Manager",
  "version": "1.0",
  "description": "Save, organize, and reuse code snippets directly from your browser",
  "permissions": [
    "storage",
    "clipboardRead",
    "clipboardWrite"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "commands": {
    "open-snippet-manager": {
      "suggested_key": {
        "default": "Ctrl+Shift+S",
        "mac": "Command+Shift+S"
      },
      "description": "Open Code Snippet Manager"
    }
  }
}

This manifest requests the storage permission for persisting snippets, clipboard read and write permissions for copying and pasting code, and defines a keyboard shortcut that opens the extension. The manifest_version 3 is the current standard for Chrome extensions and offers improved security and performance over version 2.

Your project folder should contain the following files: manifest.json, popup.html for the user interface, popup.js for the extension logic, styles.css for styling, and an icons folder containing your extension icons in various sizes.


The popup interface is what users see when they click the extension icon. It needs to be clean, fast, and functional. Let us create an intuitive interface that handles snippet creation, browsing, searching, and copying:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Code Snippet Manager</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>đź“‹ Snippet Manager</h1>
      <button id="add-btn" class="primary-btn">+ New Snippet</button>
    </header>
    
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search snippets...">
    </div>
    
    <div id="snippet-form" class="snippet-form hidden">
      <input type="text" id="title-input" placeholder="Snippet title">
      <select id="language-select">
        <option value="javascript">JavaScript</option>
        <option value="python">Python</option>
        <option value="html">HTML</option>
        <option value="css">CSS</option>
        <option value="sql">SQL</option>
        <option value="bash">Bash</option>
        <option value="other">Other</option>
      </select>
      <textarea id="code-input" placeholder="Paste your code here..."></textarea>
      <div class="tags-input">
        <input type="text" id="tags-input" placeholder="Add tags (comma separated)">
      </div>
      <div class="form-buttons">
        <button id="save-btn" class="primary-btn">Save Snippet</button>
        <button id="cancel-btn" class="secondary-btn">Cancel</button>
      </div>
    </div>
    
    <div id="snippets-list" class="snippets-list"></div>
  </div>
  
  <script src="popup.js"></script>
</body>
</html>

This HTML structure provides a clean layout with a header containing the title and add button, a search input for filtering snippets, a hidden form for creating new snippets, and a list container for displaying saved snippets. The interface uses a card-based design that shows each snippet in an organized, scannable format.


Styling Your Extension

The CSS should make your extension feel professional and comfortable to use. Focus on readability, clear visual hierarchy, and efficient use of space:

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

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  width: 400px;
  min-height: 500px;
  background: #f8f9fa;
  color: #333;
}

.container {
  padding: 16px;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

h1 {
  font-size: 18px;
  font-weight: 600;
}

.primary-btn {
  background: #4a90d9;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.2s;
}

.primary-btn:hover {
  background: #3a7bc8;
}

.secondary-btn {
  background: #e1e4e8;
  color: #333;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
}

.search-container {
  margin-bottom: 16px;
}

#search-input {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 14px;
}

.snippet-form {
  background: white;
  padding: 16px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  margin-bottom: 16px;
}

.snippet-form.hidden {
  display: none;
}

.snippet-form input,
.snippet-form select,
.snippet-form textarea {
  width: 100%;
  margin-bottom: 12px;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 14px;
}

.snippet-form textarea {
  min-height: 120px;
  font-family: 'Monaco', 'Menlo', monospace;
  resize: vertical;
}

.form-buttons {
  display: flex;
  gap: 8px;
}

.snippet-card {
  background: white;
  border-radius: 8px;
  padding: 16px;
  margin-bottom: 12px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
  transition: box-shadow 0.2s;
}

.snippet-card:hover {
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
}

.snippet-header {
  display: flex;
  justify-content: space-between;
  align-items: start;
  margin-bottom: 8px;
}

.snippet-title {
  font-weight: 600;
  font-size: 15px;
}

.language-badge {
  background: #e8f4fd;
  color: #4a90d9;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
}

.snippet-code {
  background: #282c34;
  color: #abb2bf;
  padding: 12px;
  border-radius: 6px;
  font-family: 'Monaco', 'Menlo', monospace;
  font-size: 12px;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 100px;
  margin-bottom: 12px;
}

.snippet-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}

.tag {
  background: #f1f3f4;
  color: #5f6368;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 11px;
}

.snippet-actions {
  display: flex;
  gap: 8px;
}

.copy-btn {
  background: #34a853;
  color: white;
  border: none;
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
}

.delete-btn {
  background: #ea4335;
  color: white;
  border: none;
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
}

.copy-btn:hover {
  background: #2d8f47;
}

.delete-btn:hover {
  background: #d33426;
}

The styling creates a modern, clean interface with a light color scheme that is easy on the eyes. Code blocks use a dark theme inspired by popular code editors, making code snippets visually distinct and readable. The hover effects and transitions add polish without adding complexity.


Implementing Core Functionality

The JavaScript file handles all the logic for saving, loading, searching, and managing snippets. This is where the extension comes to life:

// Initialize storage key
const STORAGE_KEY = 'code_snippets';

// DOM Elements
const addBtn = document.getElementById('add-btn');
const saveBtn = document.getElementById('save-btn');
const cancelBtn = document.getElementById('cancel-btn');
const snippetForm = document.getElementById('snippet-form');
const snippetsList = document.getElementById('snippets-list');
const searchInput = document.getElementById('search-input');

const titleInput = document.getElementById('title-input');
const languageSelect = document.getElementById('language-select');
const codeInput = document.getElementById('code-input');
const tagsInput = document.getElementById('tags-input');

// Load snippets from storage
async function loadSnippets() {
  const result = await chrome.storage.local.get(STORAGE_KEY);
  return result[STORAGE_KEY] || [];
}

// Save snippets to storage
async function saveSnippets(snippets) {
  await chrome.storage.local.set({ [STORAGE_KEY]: snippets });
}

// Render snippets list
function renderSnippets(snippets) {
  snippetsList.innerHTML = '';
  
  if (snippets.length === 0) {
    snippetsList.innerHTML = '<p class="empty-state">No snippets yet. Click "+ New Snippet" to add one!</p>';
    return;
  }
  
  snippets.forEach((snippet, index) => {
    const card = document.createElement('div');
    card.className = 'snippet-card';
    
    const tags = snippet.tags ? snippet.tags.map(tag => 
      `<span class="tag">${tag.trim()}</span>`
    ).join('') : '';
    
    card.innerHTML = `
      <div class="snippet-header">
        <span class="snippet-title">${escapeHtml(snippet.title)}</span>
        <span class="language-badge">${snippet.language}</span>
      </div>
      <pre class="snippet-code"><code>${escapeHtml(snippet.code)}</code></pre>
      <div class="snippet-tags">${tags}</div>
      <div class="snippet-actions">
        <button class="copy-btn" data-index="${index}">Copy</button>
        <button class="delete-btn" data-index="${index}">Delete</button>
      </div>
    `;
    
    snippetsList.appendChild(card);
  });
  
  // Add event listeners to buttons
  document.querySelectorAll('.copy-btn').forEach(btn => {
    btn.addEventListener('click', handleCopy);
  });
  
  document.querySelectorAll('.delete-btn').forEach(btn => {
    btn.addEventListener('click', handleDelete);
  });
}

// Escape HTML to prevent XSS
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

// Handle copy button click
async function handleCopy(e) {
  const index = parseInt(e.target.dataset.index);
  const snippets = await loadSnippets();
  const snippet = snippets[index];
  
  await navigator.clipboard.writeText(snippet.code);
  
  const btn = e.target;
  btn.textContent = 'Copied!';
  setTimeout(() => {
    btn.textContent = 'Copy';
  }, 1500);
}

// Handle delete button click
async function handleDelete(e) {
  const index = parseInt(e.target.dataset.index);
  const snippets = await loadSnippets();
  
  snippets.splice(index, 1);
  await saveSnippets(snippets);
  renderSnippets(snippets);
}

// Handle save new snippet
async function handleSave() {
  const title = titleInput.value.trim();
  const language = languageSelect.value;
  const code = codeInput.value;
  const tags = tagsInput.value.split(',').map(t => t.trim()).filter(t => t);
  
  if (!title || !code) {
    alert('Please enter a title and code');
    return;
  }
  
  const snippets = await loadSnippets();
  snippets.unshift({
    title,
    language,
    code,
    tags,
    createdAt: new Date().toISOString()
  });
  
  await saveSnippets(snippets);
  
  // Clear form
  titleInput.value = '';
  codeInput.value = '';
  tagsInput.value = '';
  snippetForm.classList.add('hidden');
  
  // Reload list
  renderSnippets(snippets);
}

// Search functionality
searchInput.addEventListener('input', async (e) => {
  const searchTerm = e.target.value.toLowerCase();
  const snippets = await loadSnippets();
  
  const filtered = snippets.filter(snippet => 
    snippet.title.toLowerCase().includes(searchTerm) ||
    snippet.code.toLowerCase().includes(searchTerm) ||
    snippet.tags.some(tag => tag.toLowerCase().includes(searchTerm))
  );
  
  renderSnippets(filtered);
});

// Event listeners
addBtn.addEventListener('click', () => {
  snippetForm.classList.remove('hidden');
});

cancelBtn.addEventListener('click', () => {
  snippetForm.classList.add('hidden');
});

saveBtn.addEventListener('click', handleSave);

// Initialize
(async () => {
  const snippets = await loadSnippets();
  renderSnippets(snippets);
})();

This JavaScript code provides all the essential functionality for a snippet manager. The loadSnippets and saveSnippets functions interact with Chrome storage API to persist data locally. The renderSnippets function creates the visual card layout for each snippet, complete with syntax display, tags, and action buttons. The search functionality filters snippets by title, code content, and tags in real-time.

Security is a priority in this implementation. The escapeHtml function prevents cross-site scripting attacks by properly encoding user input before rendering it in the DOM. This is essential when handling code that might contain HTML-like characters.

The copy functionality uses the modern Clipboard API for seamless one-click copying, with visual feedback confirming the copy action. The delete functionality removes snippets from the array and saves the updated list back to storage.


Adding Context Menu Integration

To make your snippet manager even more useful, add context menu integration that allows users to save selected text from any webpage. Add this to your manifest and JavaScript:

In manifest.json, add the contextMenus permission and a background script:

{
  "permissions": ["storage", "clipboardRead", "clipboardWrite", "contextMenus"],
  "background": {
    "service_worker": "background.js"
  }
}

Create background.js:

// Create context menu on install
chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: 'saveToSnippets',
    title: 'Save to Code Snippets',
    contexts: ['selection']
  });
});

// Handle context menu click
chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === 'saveToSnippets') {
    const selectedText = info.selectionText;
    chrome.storage.local.get('code_snippets', (result) => {
      const snippets = result.code_snippets || [];
      snippets.unshift({
        title: `Snippet from ${tab.title}`,
        language: 'other',
        code: selectedText,
        tags: ['quick-save'],
        createdAt: new Date().toISOString()
      });
      chrome.storage.local.set({ code_snippets: snippets });
    });
  }
});

This background script adds a “Save to Code Snippets” option that appears when you select text on any webpage. Clicking it instantly saves the selected code to your snippet library, making it incredibly easy to capture code snippets while browsing documentation, tutorials, or Stack Overflow answers.


Keyboard Shortcuts for Power Users

The manifest already defines a keyboard shortcut, but you need to implement it in the background script. Add this to background.js:

chrome.commands.onCommand.addListener(async (command) => {
  if (command === 'open-snippet-manager') {
    chrome.action.openPopup();
  }
});

With this shortcut (Ctrl+Shift+S or Command+Shift+S on Mac), you can instantly open your snippet manager from anywhere in Chrome without clicking the extension icon.


Testing Your Extension

Now that all the code is in place, it is time to test your extension. Open Chrome and navigate to chrome://extensions/. Enable Developer mode in the top right corner if it is not already enabled. Click “Load unpacked” and select your project folder.

You should see your extension icon appear in the Chrome toolbar. Click it to open the popup and try adding a new snippet. Test the search functionality by adding multiple snippets and filtering them. Verify that copying works and that your snippets persist after closing and reopening the browser.

Check the context menu by selecting text on any webpage and right-clicking. You should see “Save to Code Snippets” in the context menu. Try the keyboard shortcut to open the popup quickly.


Enhancing Your Extension

This basic implementation provides a solid foundation, but there are many features you can add to make your snippet manager even more powerful. Consider adding syntax highlighting using a library like Prism.js or Highlight.js to make code blocks visually appealing and color-coded. Implement folder or category organization to group snippets by project or topic.

Adding export and import functionality would let you backup your snippets or share them with team members. You could implement cloud sync using Chrome storage sync API to access your snippets across multiple devices. A tagging system with tag management would improve organization for users with large snippet collections.

Another powerful feature would be template variables that allow snippets to include placeholders like {{date}} or {{username}} that get replaced when copying. This is particularly useful for code that needs slight modifications each time.


Conclusion

You have successfully built a fully functional code snippet manager Chrome extension from scratch. The extension allows you to save code snippets with titles, programming language tags, and custom labels, search through your entire collection instantly, copy any snippet to your clipboard with a single click, save selected text from any webpage using context menus, and access your snippets quickly with keyboard shortcuts.

This extension solves a real problem for developers by providing a fast, private, and customizable way to store and retrieve code snippets. Unlike cloud-based solutions, your code stays local in your browser unless you explicitly choose to export it. The skills you have learned in this tutorial—working with Chrome storage API, creating popup interfaces, implementing context menus, and handling clipboard operations—apply directly to many other Chrome extension projects you might build.

The foundation is solid, but the real power comes from customizing the extension to match your specific workflow. Add the features you need, remove what you do not, and continue iterating as your requirements evolve. Happy coding!

No previous article
No next article