How to Build Reliable Timers in Chrome Extensions with Manifest V3

This guide explains the shift from persistent background scripts to Service Workers in Manifest V3, compares three timer implementation strategies—including chrome.alarms, content‑script intervals, and event‑driven storage checks—and provides best‑practice recommendations for idempotency, logging, deduplication, and state persistence.

FunTester
FunTester
FunTester
How to Build Reliable Timers in Chrome Extensions with Manifest V3

From Persistent Background to On‑Demand Wakeup

Early Chrome extensions could keep a background script resident and use setInterval for simple timers. With Manifest V3 the background script becomes a Service Worker that is started on demand and sleeps when idle, so timers must be designed for wake‑up events.

Implementation Options

Option 1: Use chrome.alarms API

Chrome provides the official chrome.alarms API for periodic tasks such as data sync or polling.

// Create a timer named 'FunTesterAlarm' that fires every 15 minutes
chrome.alarms.create('FunTesterAlarm', { periodInMinutes: 15 });

// Listen for the alarm event
chrome.alarms.onAlarm.addListener(alarm => {
  if (alarm.name === 'FunTesterAlarm') {
    console.log('FunTesterAlarm triggered');
    // Execute task here (e.g., fetch config, send notification)
  }
});

Task execution depends on the Service Worker being woken; the browser may delay or skip runs when asleep.

The minimum interval is 1 minute, so second‑level timers are not possible.

Each wake‑up starts with a fresh context; global variables cannot be relied on.

Option 2: Content‑script‑based timer

When a task only needs to run while a user is on a page, a setInterval inside a content script can be used.

setInterval(() => {
  // Check DOM state or send heartbeat
}, 10000);

The interval stops when the page is closed.

It depends on user activity and cannot run as a true background task.

Option 3: Event‑driven simulation with storage

This approach records the last execution time in chrome.storage and checks the elapsed time on startup or when a message arrives, executing the task only if a defined interval has passed.

// Trigger on extension startup
chrome.runtime.onStartup.addListener(() => {
  checkAndRunTask();
});

function checkAndRunTask() {
  const now = Date.now();
  chrome.storage.local.get('lastRun', res => {
    const lastRun = res.lastRun || 0;
    // Run if more than 30 minutes have passed
    if (now - lastRun > 1000 * 60 * 30) {
      chrome.storage.local.set({ lastRun: now });
      // Perform the actual work here
    }
  });
}

This method is less precise but offers better stability for low‑frequency, non‑critical background jobs.

Best Practices: Building Reliable Timers

Ensure task idempotency – repeated executions must produce the same result. Use data validation or deduplication when syncing resources.

Record detailed execution logs – log start, end, and any errors (e.g., via console.log or a remote logging service) to aid debugging.

Prevent duplicate runs – implement a lock or flag so a new trigger returns early if a previous run is still in progress.

Persist important state – store timestamps, flags, or other state in chrome.storage (or chrome.storage.local) because the Service Worker may be destroyed and recreated.

Below is a complete example that combines these practices into an idempotent, logged, and deduplicated timer using chrome.alarms:

// Create a timer that fires every 30 minutes
chrome.alarms.create('FunTesterTask', { periodInMinutes: 30 });

chrome.alarms.onAlarm.addListener(alarm => {
  if (alarm.name !== 'FunTesterTask') return;
  const now = Date.now();
  chrome.storage.local.get('lastRun', res => {
    const lastRun = res.lastRun || 0;
    if (now - lastRun > 1000 * 60 * 30) {
      console.log('Executing FunTesterTask at', new Date().toISOString());
      executeTask().then(() => {
        console.log('FunTesterTask completed successfully');
        chrome.storage.local.set({ lastRun: now });
      }).catch(err => console.error('FunTesterTask failed:', err));
    } else {
      console.log('FunTesterTask skipped, last run was too recent');
    }
  });
});

function executeTask() {
  return new Promise((resolve) => {
    console.log('Performing FunTesterTask...');
    setTimeout(resolve, 2000); // Simulate async work
  });
}

Additional advanced triggers can be added, such as server push (Firebase Cloud Messaging), WebSocket messages, or external scheduling services (AWS Lambda, Google Cloud Functions) that call the extension via chrome.runtime.onMessageExternal. These methods increase flexibility but still require the same idempotent and logging safeguards.

In summary, building timers for Chrome extensions under Manifest V3 demands awareness of the Service Worker's lifecycle, careful choice of the triggering mechanism, and adherence to best practices around idempotency, logging, deduplication, and persistent storage to achieve stable and reliable background functionality.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Chrome Extensionbest practicesService WorkerTimersBackground Scriptschrome.alarms
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.