Browser APIs Chrome Modern Features Development

Leveraging New Browser APIs for Powerful Extensions

E
Extendable Team
· 14 min read

Browser vendors continuously add new APIs that expand what extensions can do. Staying current with these APIs lets you build more capable extensions with better user experiences. This guide covers the most impactful new browser APIs and how to use them effectively.

Side Panel API

The Side Panel API (Chrome 114+) lets extensions display content in a persistent panel beside web pages—perfect for AI assistants, note-taking, and productivity tools.

// manifest.json
{
  "manifest_version": 3,
  "side_panel": {
    "default_path": "sidepanel.html"
  },
  "permissions": ["sidePanel"]
}
// background.js
// Open side panel when extension icon is clicked
chrome.action.onClicked.addListener(async (tab) => {
  await chrome.sidePanel.open({ windowId: tab.windowId });
});

// Set panel behavior per tab
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  if (changeInfo.url) {
    // Show different panels for different sites
    if (tab.url.includes('github.com')) {
      await chrome.sidePanel.setOptions({
        tabId,
        path: 'panels/github-assistant.html',
        enabled: true
      });
    }
  }
});
<!-- sidepanel.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      width: 100%;
      height: 100vh;
      margin: 0;
      padding: 16px;
      box-sizing: border-box;
      font-family: system-ui;
    }
    .chat-container {
      display: flex;
      flex-direction: column;
      height: 100%;
    }
    .messages {
      flex: 1;
      overflow-y: auto;
    }
    .input-area {
      padding-top: 16px;
    }
  </style>
</head>
<body>
  <div class="chat-container">
    <div class="messages" id="messages"></div>
    <div class="input-area">
      <textarea id="input" placeholder="Ask anything..."></textarea>
      <button id="send">Send</button>
    </div>
  </div>
  <script src="sidepanel.js"></script>
</body>
</html>
Side Panel Best Practices:
  • Keep the panel width-responsive (users can resize)
  • Implement keyboard shortcuts for quick access
  • Remember scroll position when panel reopens
  • Use postMessage to communicate with content scripts

Declarative Net Request API

Replace the deprecated webRequestBlocking with declarativeNetRequest for content blocking and request modification:

// manifest.json
{
  "manifest_version": 3,
  "permissions": ["declarativeNetRequest"],
  "declarative_net_request": {
    "rule_resources": [{
      "id": "ruleset_1",
      "enabled": true,
      "path": "rules.json"
    }]
  }
}
// rules.json
[
  {
    "id": 1,
    "priority": 1,
    "action": { "type": "block" },
    "condition": {
      "urlFilter": "||ads.example.com",
      "resourceTypes": ["script", "image"]
    }
  },
  {
    "id": 2,
    "priority": 2,
    "action": {
      "type": "redirect",
      "redirect": { "extensionPath": "/blocked.html" }
    },
    "condition": {
      "urlFilter": "||malware-site.com",
      "resourceTypes": ["main_frame"]
    }
  },
  {
    "id": 3,
    "priority": 1,
    "action": {
      "type": "modifyHeaders",
      "responseHeaders": [
        { "header": "X-Frame-Options", "operation": "remove" }
      ]
    },
    "condition": {
      "urlFilter": "*",
      "resourceTypes": ["sub_frame"]
    }
  }
]

Dynamic Rules

Add rules at runtime for user-configured blocking:

// Add a user-defined blocking rule
async function blockDomain(domain) {
  const rules = await chrome.declarativeNetRequest.getDynamicRules();
  const newRuleId = Math.max(0, ...rules.map(r => r.id)) + 1;

  await chrome.declarativeNetRequest.updateDynamicRules({
    addRules: [{
      id: newRuleId,
      priority: 1,
      action: { type: 'block' },
      condition: {
        urlFilter: `||${domain}`,
        resourceTypes: ['main_frame', 'sub_frame', 'script', 'image', 'xmlhttprequest']
      }
    }]
  });

  return newRuleId;
}

// Remove a rule
async function unblockDomain(ruleId) {
  await chrome.declarativeNetRequest.updateDynamicRules({
    removeRuleIds: [ruleId]
  });
}

User Scripts API

The User Scripts API (Chrome 120+) lets users inject custom JavaScript into web pages:

// manifest.json
{
  "manifest_version": 3,
  "permissions": ["userScripts"],
  "optional_host_permissions": ["*://*/*"]
}
// Register a user script
async function registerUserScript(script) {
  await chrome.userScripts.register([{
    id: script.id,
    matches: script.matches,
    js: [{ code: script.code }],
    runAt: 'document_idle',
    world: 'MAIN' // Run in page context, not isolated
  }]);
}

// Example: Add dark mode to any site
await registerUserScript({
  id: 'dark-mode',
  matches: ['*://*.example.com/*'],
  code: `
    document.documentElement.style.filter = 'invert(1) hue-rotate(180deg)';
    document.querySelectorAll('img, video').forEach(el => {
      el.style.filter = 'invert(1) hue-rotate(180deg)';
    });
  `
});

Reading List API

Access the browser’s built-in reading list (Chrome 91+):

// Add to reading list
async function saveForLater(url, title) {
  try {
    await chrome.readingList.addEntry({
      url,
      title,
      hasBeenRead: false
    });
    return { success: true };
  } catch (error) {
    // Entry might already exist
    return { success: false, error: error.message };
  }
}

// Get all reading list items
async function getReadingList() {
  const entries = await chrome.readingList.query({});
  return entries;
}

// Mark as read
async function markAsRead(url) {
  await chrome.readingList.updateEntry({
    url,
    hasBeenRead: true
  });
}

// Remove from reading list
async function removeFromReadingList(url) {
  await chrome.readingList.removeEntry({ url });
}

Offscreen Documents API

Create off-screen documents for tasks that need DOM APIs but shouldn’t be visible:

// manifest.json
{
  "manifest_version": 3,
  "permissions": ["offscreen"]
}
// background.js
async function createOffscreenDocument() {
  const existingContexts = await chrome.runtime.getContexts({
    contextTypes: ['OFFSCREEN_DOCUMENT']
  });

  if (existingContexts.length > 0) return;

  await chrome.offscreen.createDocument({
    url: 'offscreen.html',
    reasons: ['DOM_PARSER'],
    justification: 'Parse HTML content from API responses'
  });
}

// offscreen.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'PARSE_HTML') {
    const parser = new DOMParser();
    const doc = parser.parseFromString(message.html, 'text/html');

    // Extract data using DOM APIs
    const result = {
      title: doc.querySelector('title')?.textContent,
      headings: Array.from(doc.querySelectorAll('h1, h2')).map(h => h.textContent),
      links: Array.from(doc.querySelectorAll('a[href]')).map(a => ({
        text: a.textContent,
        href: a.getAttribute('href')
      }))
    };

    sendResponse(result);
  }
  return true;
});
Offscreen Use Cases:
  • Audio playback (Web Audio API)
  • HTML/DOM parsing
  • Clipboard access
  • Canvas rendering
  • WebRTC connections

Storage API Improvements

Session Storage

Temporary storage that clears when the browser closes:

// Store session-only data
await chrome.storage.session.set({
  tempAuthToken: 'abc123',
  recentSearches: ['query1', 'query2']
});

// Access from any extension context
const { tempAuthToken } = await chrome.storage.session.get('tempAuthToken');

Storage Access for Service Workers

Service workers can now access storage synchronously during specific events:

// background.js (service worker)
chrome.storage.session.setAccessLevel({
  accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS'
});

// Now content scripts can access session storage too

Scripting API Enhancements

Execute Scripts with Arguments

// Pass arguments to injected functions
async function highlightText(tabId, searchText, color) {
  await chrome.scripting.executeScript({
    target: { tabId },
    func: (text, highlightColor) => {
      const walker = document.createTreeWalker(
        document.body,
        NodeFilter.SHOW_TEXT
      );

      while (walker.nextNode()) {
        const node = walker.currentNode;
        if (node.textContent.includes(text)) {
          const span = document.createElement('span');
          span.style.backgroundColor = highlightColor;
          const range = document.createRange();
          range.selectNodeContents(node);
          range.surroundContents(span);
        }
      }
    },
    args: [searchText, color]
  });
}

Register Content Scripts Dynamically

// Add content scripts at runtime
async function enableFeatureForSite(pattern) {
  await chrome.scripting.registerContentScripts([{
    id: `feature-${pattern}`,
    matches: [pattern],
    js: ['feature.js'],
    css: ['feature.css'],
    runAt: 'document_idle'
  }]);
}

// Update existing registered scripts
await chrome.scripting.updateContentScripts([{
  id: 'feature-*://example.com/*',
  css: ['feature-v2.css'] // Update CSS only
}]);

// Remove registered scripts
await chrome.scripting.unregisterContentScripts({
  ids: ['feature-*://example.com/*']
});

Tabs API Improvements

Tab Groups

Organize tabs into collapsible groups:

// Create a tab group
async function groupRelatedTabs(tabIds, title, color) {
  const groupId = await chrome.tabs.group({ tabIds });

  await chrome.tabGroups.update(groupId, {
    title,
    color, // 'grey', 'blue', 'red', 'yellow', 'green', 'pink', 'purple', 'cyan'
    collapsed: false
  });

  return groupId;
}

// Move tab to existing group
async function addToGroup(tabId, groupId) {
  await chrome.tabs.group({
    tabIds: [tabId],
    groupId
  });
}

// Query grouped tabs
const groups = await chrome.tabGroups.query({});
for (const group of groups) {
  const tabs = await chrome.tabs.query({ groupId: group.id });
  console.log(`Group "${group.title}": ${tabs.length} tabs`);
}

Action API

The unified Action API replaces browser_action and page_action:

// Dynamic badge
chrome.action.setBadgeText({ text: '5' });
chrome.action.setBadgeBackgroundColor({ color: '#FF0000' });

// Per-tab state
chrome.action.setIcon({
  tabId: tab.id,
  path: {
    16: 'icons/active-16.png',
    32: 'icons/active-32.png'
  }
});

// Disable for specific tabs
chrome.action.disable(tab.id);

// Open popup programmatically (requires user gesture)
chrome.action.openPopup();

Feature Detection

Always check for API availability before using new features:

// Check if API exists
function hasFeature(apiPath) {
  const parts = apiPath.split('.');
  let obj = chrome;

  for (const part of parts) {
    if (obj[part] === undefined) return false;
    obj = obj[part];
  }

  return true;
}

// Usage
if (hasFeature('sidePanel')) {
  await chrome.sidePanel.open({ windowId });
} else {
  // Fallback to popup or new tab
  await chrome.windows.create({
    url: 'sidepanel.html',
    type: 'popup',
    width: 400,
    height: 600
  });
}

Summary

Staying current with browser APIs lets you build more powerful, user-friendly extensions. Key APIs to consider:

  • Side Panel: Persistent UI alongside web content
  • Declarative Net Request: Efficient content blocking
  • User Scripts: Custom code injection
  • Offscreen Documents: Background DOM operations
  • Tab Groups: Organize user tabs

Always implement feature detection and graceful fallbacks for broader browser compatibility.