Chrome Side Panel API Tutorial for Extension Developers

22 min read

Chrome Side Panel API Tutorial for Extension Developers

Chrome Side Panel API Tutorial for Extension Developers

The Chrome Side Panel API represents one of the most powerful additions to Chrome’s extension ecosystem in recent years. Introduced to enhance user experience and provide developers with a dedicated space for extension functionality, the Side Panel API allows you to build immersive, always-accessible tools that work seamlessly alongside web content. Whether you are creating a note-taking extension, a reading assistant, a developer tool, or a content management interface, the Side Panel API provides the perfect foundation for delivering a polished user experience.

This comprehensive tutorial will guide you through building a complete Chrome Side Panel extension from scratch using Manifest V3. We will cover everything from initial setup and manifest configuration to advanced patterns like communicating with content scripts, managing panel state, and implementing user preferences. By the end of this guide, you will have the knowledge and practical skills needed to create professional-grade side panel extensions that enhance Chrome’s functionality while providing genuine value to users.


Understanding the Chrome Side Panel API

The Chrome Side Panel API, introduced in Chrome 114, provides extensions with a dedicated panel that appears on the right side of the browser window. Unlike popups that appear temporarily when clicked, the side panel remains visible as users navigate between tabs, providing a persistent workspace for extension functionality. This persistent nature makes the side panel ideal for applications that require ongoing access or reference to information while users browse the web.

The side panel operates independently from the main content area, meaning it maintains its own DOM and JavaScript context. This separation provides several important benefits: the panel does not interfere with page scripts, users can interact with both the page and the panel simultaneously, and developers have full control over the panel’s appearance and behavior without worrying about page styles leaking in. The API also supports automatic opening when users visit specific sites, programmatic control from background scripts, and integration with other extension components.

One of the most significant advantages of the Side Panel API over alternative approaches is its seamless integration with Chrome’s native interface. The panel appears as a built-in feature rather than an overlay or injection, providing a more professional and trustworthy appearance. Users can resize the panel, collapse it when not needed, and it persists across browsing sessions according to their preferences. This native feel significantly improves user adoption and satisfaction compared to less integrated solutions.

Key Features and Capabilities

The Chrome Side Panel API offers a rich set of features that enable developers to create sophisticated extensions. The API supports both static panel definitions in the manifest and dynamic panel content that changes based on context. You can specify different panels for different websites, allowing users to have specialized tools for specific workflows. The panel also supports keyboard shortcuts, allowing power users to toggle it quickly without using the mouse.

Communication between the side panel and web pages is handled through Chrome’s standard message passing system, with some specialized APIs for accessing page content. The panel can read from and modify the active tab’s DOM when granted appropriate permissions, enabling powerful integrations like annotation tools, page analyzers, and content extractors. You can also coordinate panel behavior with background scripts to implement complex features like cross-tab synchronization, data persistence, and external API integrations.


Project Setup and Manifest Configuration

Every Chrome extension begins with the manifest file, and side panel extensions require specific configurations to enable this functionality. Let us set up a complete project structure for our side panel extension.

Creating the Manifest

The manifest.json file serves as the foundation of your extension. For side panel support, you need to declare the "side_panel" permission and specify the default panel in the manifest. Here is a complete Manifest V3 configuration for a side panel extension:

{
  "manifest_version": 3,
  "name": "Page Notes Side Panel",
  "version": "1.0.0",
  "description": "A powerful side panel for taking notes on any webpage",
  "permissions": [
    "sidePanel",
    "storage",
    "activeTab"
  ],
  "side_panel": {
    "default_path": "sidepanel.html"
  },
  "action": {
    "default_title": "Open Page Notes"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

This manifest declares the necessary permissions for side panel functionality, storage access for saving notes, and active tab access for communicating with web pages. The "side_panel" key specifies the default HTML file that will load when the panel opens. You can also use the "default_path" property to point to different panel implementations based on your needs.

Project Directory Structure

Creating a well-organized project structure makes development and maintenance significantly easier. For our side panel extension, we recommend the following directory organization:

chrome-side-panel-extension/
├── manifest.json
├── background.js
├── content.js
├── sidepanel/
│   ├── sidepanel.html
│   ├── sidepanel.css
│   └── sidepanel.js
├── icons/
│   ├── icon16.png
│   ├── icon48.png
│   └── icon128.png
└── popup/
    └── popup.html

This structure separates concerns clearly: the sidepanel directory contains all panel-specific files, icons are stored separately for easy reference, and background scripts remain at the root level. This organization scales well as your extension grows in complexity and makes it easier to collaborate with other developers.


Building the Side Panel Interface

With the manifest configured, we can now build the actual side panel interface. The side panel functions as a mini web application within Chrome, complete with its own HTML structure, CSS styling, and JavaScript functionality. Let us create a complete, production-ready panel implementation.

HTML Structure

The side panel HTML file defines the structure of your interface. For our page notes extension, we will create a clean, functional layout:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Page Notes</title>
  <link rel="stylesheet" href="sidepanel.css">
</head>
<body>
  <div class="panel-container">
    <header class="panel-header">
      <h1>Page Notes</h1>
      <button id="settings-btn" class="icon-btn" aria-label="Settings">
        <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
          <path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/>
          <path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/>
        </svg>
      </button>
    </header>
    
    <div class="notes-container" id="notes-container">
      <!-- Notes will be dynamically inserted here -->
    </div>
    
    <div class="note-input-area">
      <textarea 
        id="note-input" 
        placeholder="Type your note here..."
        rows="4"
      ></textarea>
      <button id="save-note-btn" class="primary-btn">Save Note</button>
    </div>
    
    <footer class="panel-footer">
      <span id="page-info">No page selected</span>
      <button id="clear-notes-btn" class="text-btn">Clear All</button>
    </footer>
  </div>
  
  <script src="sidepanel.js"></script>
</body>
</html>

This HTML provides a complete user interface with a header, scrollable notes container, input area, and footer. The structure uses semantic HTML elements and includes accessibility attributes for screen reader compatibility.

Styling the Side Panel

CSS styling ensures your side panel looks professional and integrates well with Chrome’s interface. The side panel has a fixed width that users can adjust, so responsive design is essential:

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

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: #202124;
  background: #ffffff;
  height: 100vh;
  overflow: hidden;
}

.panel-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 12px;
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid #e0e0e0;
  margin-bottom: 12px;
}

.panel-header h1 {
  font-size: 18px;
  font-weight: 600;
  color: #1f1f1f;
}

.notes-container {
  flex: 1;
  overflow-y: auto;
  margin-bottom: 12px;
}

.note-card {
  background: #f8f9fa;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 8px;
  transition: box-shadow 0.2s ease;
}

.note-card:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.note-card .note-text {
  margin-bottom: 8px;
  white-space: pre-wrap;
  word-break: break-word;
}

.note-card .note-meta {
  font-size: 12px;
  color: #5f6368;
}

.note-input-area {
  margin-bottom: 12px;
}

.note-input-area textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  font-family: inherit;
  font-size: 14px;
  resize: none;
  margin-bottom: 8px;
}

.note-input-area textarea:focus {
  outline: none;
  border-color: #1a73e8;
  box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
}

.primary-btn {
  width: 100%;
  padding: 10px 16px;
  background: #1a73e8;
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s ease;
}

.primary-btn:hover {
  background: #1557b0;
}

.panel-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 12px;
  border-top: 1px solid #e0e0e0;
  font-size: 12px;
  color: #5f6368;
}

.text-btn {
  background: none;
  border: none;
  color: #1a73e8;
  cursor: pointer;
  font-size: 12px;
}

.text-btn:hover {
  text-decoration: underline;
}

.icon-btn {
  background: none;
  border: none;
  padding: 4px;
  cursor: pointer;
  color: #5f6368;
  border-radius: 4px;
}

.icon-btn:hover {
  background: #f1f3f4;
}

This CSS provides a clean, modern appearance that matches Chrome’s design language. The layout uses flexbox for responsive sizing, and all interactive elements have appropriate hover and focus states.


Implementing Side Panel Functionality

With the interface in place, we now need to implement the JavaScript functionality that makes the side panel work. This includes handling user interactions, communicating with the background script, and managing data persistence.

Side Panel JavaScript

The side panel script runs in its own context and handles all user interface interactions:

// sidepanel.js

// State management
let currentTabUrl = '';
let currentTabTitle = '';
let notes = [];

// DOM Elements
const notesContainer = document.getElementById('notes-container');
const noteInput = document.getElementById('note-input');
const saveNoteBtn = document.getElementById('save-note-btn');
const clearNotesBtn = document.getElementById('clear-notes-btn');
const pageInfo = document.getElementById('page-info');
const settingsBtn = document.getElementById('settings-btn');

// Initialize panel
async function init() {
  // Get current tab information
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  if (tab) {
    currentTabUrl = tab.url;
    currentTabTitle = tab.title;
    pageInfo.textContent = truncateText(tab.title, 30);
  }
  
  // Load saved notes for this page
  await loadNotes();
  
  // Set up event listeners
  setupEventListeners();
}

// Load notes from storage
async function loadNotes() {
  if (!currentTabUrl) return;
  
  try {
    const result = await chrome.storage.local.get(currentTabUrl);
    notes = result[currentTabUrl] || [];
    renderNotes();
  } catch (error) {
    console.error('Error loading notes:', error);
  }
}

// Save notes to storage
async function saveNotes() {
  try {
    await chrome.storage.local.set({ [currentTabUrl]: notes });
  } catch (error) {
    console.error('Error saving notes:', error);
  }
}

// Render notes in the container
function renderNotes() {
  notesContainer.innerHTML = '';
  
  if (notes.length === 0) {
    notesContainer.innerHTML = `
      <div class="empty-state">
        <p>No notes yet for this page.</p>
        <p>Start typing below to add your first note.</p>
      </div>
    `;
    return;
  }
  
  notes.forEach((note, index) => {
    const noteElement = document.createElement('div');
    noteElement.className = 'note-card';
    noteElement.innerHTML = `
      <div class="note-text">${escapeHtml(note.text)}</div>
      <div class="note-meta">
        ${formatDate(note.timestamp)}
        <button class="delete-note-btn" data-index="${index}">Delete</button>
      </div>
    `;
    notesContainer.appendChild(noteElement);
  });
  
  // Add delete event listeners
  document.querySelectorAll('.delete-note-btn').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const index = parseInt(e.target.dataset.index);
      deleteNote(index);
    });
  });
}

// Add a new note
function addNote() {
  const text = noteInput.value.trim();
  if (!text) return;
  
  const note = {
    text: text,
    timestamp: new Date().toISOString()
  };
  
  notes.unshift(note);
  saveNotes();
  renderNotes();
  noteInput.value = '';
}

// Delete a note
function deleteNote(index) {
  notes.splice(index, 1);
  saveNotes();
  renderNotes();
}

// Clear all notes for current page
function clearAllNotes() {
  if (confirm('Are you sure you want to delete all notes for this page?')) {
    notes = [];
    saveNotes();
    renderNotes();
  }
}

// Event listeners
function setupEventListeners() {
  saveNoteBtn.addEventListener('click', addNote);
  
  noteInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && e.ctrlKey) {
      addNote();
    }
  });
  
  clearNotesBtn.addEventListener('click', clearAllNotes);
  
  settingsBtn.addEventListener('click', () => {
    // Open settings or show options
    console.log('Settings clicked');
  });
}

// Utility functions
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function truncateText(text, maxLength) {
  if (text.length <= maxLength) return text;
  return text.substring(0, maxLength) + '...';
}

function formatDate(isoString) {
  const date = new Date(isoString);
  return date.toLocaleString();
}

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', init);

This JavaScript provides complete functionality for managing notes within the side panel. It handles loading and saving data using Chrome’s storage API, renders notes dynamically, and provides user feedback through the interface.

Background Script Integration

The background script enables advanced features and coordinates between different extension components:

// background.js

// Handle side panel opening from toolbar icon
chrome.action.onClicked.addListener(async (tab) => {
  // Open the side panel for the current tab
  await chrome.sidePanel.setOptions({
    tabId: tab.id,
    path: 'sidepanel/sidepanel.html',
    enabled: true
  });
  
  await chrome.sidePanel.open({ tabId: tab.id });
});

// Allow side panel to access the current tab's URL
chrome.sidePanel
  .setPanelBehavior({ openPanelOnActionClick: true })
  .catch(() => {
    // Feature not supported in older Chrome versions
  });

// Handle messages from side panel or content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'GET_TAB_INFO') {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      if (tabs[0]) {
        sendResponse({
          url: tabs[0].url,
          title: tabs[0].title,
          id: tabs[0].id
        });
      }
    });
    return true; // Keep message channel open for async response
  }
  
  if (message.type === 'ANALYZE_PAGE') {
    // Perform page analysis in background
    analyzePageContent(message.data)
      .then(result => sendResponse(result))
      .catch(error => sendResponse({ error: error.message }));
    return true;
  }
});

// Function to analyze page content
async function analyzePageContent(tabId) {
  try {
    const results = await chrome.scripting.executeScript({
      target: { tabId: tabId },
      func: () => {
        // Extract page metadata
        return {
          title: document.title,
          headings: Array.from(document.querySelectorAll('h1, h2, h3')).map(h => h.textContent),
          links: document.querySelectorAll('a').length,
          images: document.querySelectorAll('img').length
        };
      }
    });
    return results[0].result;
  } catch (error) {
    throw new Error('Failed to analyze page: ' + error.message);
  }
}

The background script provides a bridge between the side panel and other extension components, enabling features like page analysis, cross-component communication, and enhanced functionality that cannot be implemented within the panel itself.


Advanced Patterns and Best Practices

Building production-ready side panel extensions requires attention to performance, user experience, and maintainability. Let us explore some advanced patterns that will help you create professional-quality extensions.

Optimizing Panel Performance

Performance is critical for side panel extensions because users expect instant responsiveness. The panel loads every time a user opens it, so minimizing initialization time is essential. Implement lazy loading for heavy components, defer non-critical operations, and use efficient data structures for managing large datasets. Chrome’s storage API is asynchronous, so always handle loading states gracefully to prevent UI flickering.

Handling Cross-Origin Content

When your side panel needs to interact with page content, you must handle cross-origin restrictions carefully. Content scripts run with elevated privileges but cannot directly access side panel JavaScript. Use Chrome’s message passing system to communicate between the content script and side panel. Always validate and sanitize any data received from web pages to prevent security vulnerabilities.

Implementing User Preferences

Allow users to customize their experience through a settings system. Store preferences using chrome.storage.sync so they persist across devices for signed-in users. Common options include panel width defaults, auto-open behavior for specific sites, theme preferences, and keyboard shortcut configurations. Providing sensible defaults while allowing customization significantly improves user satisfaction.


Testing and Deployment

Before publishing your extension to the Chrome Web Store, thorough testing ensures a smooth user experience. Use Chrome’s developer mode to load your extension unpacked and test all functionality across different scenarios.

Testing Checklist

Verify that the side panel opens correctly from the toolbar icon, content loads properly for various page types, data persists across browser restarts, communication between components works reliably, and the extension handles edge cases gracefully. Test with different screen sizes and monitor configurations to ensure the panel displays correctly.

Publishing to Chrome Web Store

Once testing is complete, package your extension using Chrome’s developer dashboard. Prepare clear, accurate descriptions and screenshots that showcase your extension’s functionality. Include relevant keywords in your listing to improve discoverability. After submission, the review process typically takes a few days before your extension becomes publicly available.


Conclusion

The Chrome Side Panel API provides an excellent foundation for building powerful, user-friendly extensions that integrate seamlessly with Chrome’s interface. Throughout this tutorial, we have covered the complete development workflow: from understanding the API’s capabilities and configuring the manifest, through building responsive interfaces and implementing sophisticated functionality, to testing and deployment considerations.

By following the patterns and practices outlined in this guide, you can create side panel extensions that provide genuine value to users while maintaining the performance and reliability expected of professional Chrome extensions. The side panel’s persistent nature makes it ideal for a wide range of applications, from note-taking and productivity tools to developer utilities and content management systems.

As you continue developing your extension, remember to stay current with Chrome’s evolving APIs and best practices. The extension ecosystem is continuously improving, and new features regularly become available that can enhance your extension’s capabilities. Happy building!


Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article