Chrome Extension Tab Suspender — Developer Guide

6 min read

Build a Tab Suspender Extension — Full Tutorial

What We’re Building

Prerequisites

Step 1: manifest.json

{
  "manifest_version": 3,
  "name": "Tab Suspender",
  "version": "1.0.0",
  "permissions": ["tabs", "idle", "storage", "alarms"],
  "action": { "default_popup": "popup.html" },
  "background": { "service_worker": "background.js" },
  "options_page": "options.html"
}

Step 2: Storage Schema

Define the configuration schema for whitelist and timeout settings:

import { createStorage, defineSchema } from '@theluckystrike/webext-storage';

const configSchema = defineSchema({
  timeoutMinutes: 5,        // Default 5 minutes (range: 5-60)
  whitelistDomains: [],     // Domains never to suspend
  tabCountThreshold: 20,    // Auto-suspend when tabs exceed this
  enabled: true
});

export const config = createStorage(configSchema, 'sync');
export const suspendedTabs = createStorage(defineSchema({
  tabId: 'number',
  url: 'string',
  title: 'string',
  suspendedAt: 'number'
}), 'local');

Step 3: Idle Detection

Monitor user idle state and trigger tab suspension:

import { config, suspendedTabs } from './storage.js';

// Start idle detection with configurable interval
chrome.idle.setDetectionInterval(60); // Check every minute

chrome.idle.onStateChanged.addListener(async (state) => {
  const settings = await config.get('enabled');
  if (!settings.enabled) return;

  if (state === 'idle') {
    await suspendInactiveTabs();
  }
});

async function suspendInactiveTabs() {
  const settings = await config.getAll();
  const tabs = await chrome.tabs.query({ pinned: false, active: false });
  
  const whitelist = settings.whitelistDomains || [];
  const eligibleTabs = tabs.filter(tab => {
    const url = new URL(tab.url);
    return !whitelist.some(domain => url.hostname.includes(domain));
  });

  for (const tab of eligibleTabs) {
    try {
      await chrome.tabs.discard(tab.id);
      await saveSuspendedTab(tab);
    } catch (e) {
      // Tab may already be discarded or not discardable
    }
  }
}

Step 4: Tab Count Threshold

Auto-suspend when too many tabs are open:

chrome.tabs.onCreated.addListener(async () => {
  const settings = await config.get('tabCountThreshold');
  const allTabs = await chrome.tabs.query({});
  
  if (allTabs.length > settings.tabCountThreshold) {
    await suspendInactiveTabs();
  }
});

Step 5: Restore on Activation

Restore suspended tabs when users switch back:

chrome.tabs.onActivated.addListener(async (activeInfo) => {
  const tab = await chrome.tabs.get(activeInfo.tabId);
  
  // If tab is discarded (has no title/URL yet), reload to restore
  if (tab.id && !tab.title) {
    await chrome.tabs.reload(tab.id);
  }
});

Step 6: Badge Display

Show suspended tab count in the extension badge:

async function updateBadge() {
  const suspended = await suspendedTabs.getAll();
  const count = suspended.length;
  
  chrome.action.setBadgeText({ text: count > 0 ? String(count) : '' });
  chrome.action.setBadgeBackgroundColor({ color: '#4CAF50' });
}

Step 7: Options Page

Create options.html for user configuration:

<input type="number" id="timeout" min="5" max="60" value="5">
<input type="text" id="whitelist" placeholder="domain.com, example.org">
<input type="number" id="threshold" min="5" value="20">
document.getElementById('timeout').value = await config.get('timeoutMinutes');
document.getElementById('whitelist').value = (await config.get('whitelistDomains')).join(', ');
document.getElementById('threshold').value = await config.get('tabCountThreshold');

saveBtn.addEventListener('click', async () => {
  await config.set('timeoutMinutes', Number(timeoutInput.value));
  await config.set('whitelistDomains', whitelistInput.value.split(',').map(s => s.trim()));
  await config.set('tabCountThreshold', Number(thresholdInput.value));
});

Step 8: Time-Based Suspension

Use alarms for periodic suspension checks:

chrome.alarms.create('checkIdle', { periodInMinutes: 1 });

chrome.alarms.onAlarm.addListener(async (alarm) => {
  if (alarm.name === 'checkIdle') {
    const state = await chrome.idle.queryState(60);
    if (state === 'idle') {
      await suspendInactiveTabs();
    }
  }
});

Testing

What You Learned

See Also

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

No previous article
No next article