Claude Skills Guide

Chrome extensions that handle multiple accounts within a single browser profile solve a real problem for developers, marketers, and power users who manage numerous identities. Rather than maintaining separate browser profiles or logging out and back in repeatedly, a well-designed multi-account container system lets users switch between contexts instantly.

This guide covers the architecture patterns, implementation strategies, and practical code examples for building Chrome extensions with robust multi-account container support.

Understanding Multi-Account Container Architecture

At its core, a multi-account container system isolates data associated with different identities. Each container maintains its own state, cookies, local storage, and sometimes even separate DOM contexts. The isolation prevents identity leakage—where actions in one account accidentally affect another.

Chrome extensions can implement container isolation at several levels:

  1. Storage-level isolation: Using Chrome’s storage.session or storage.local APIs with namespaced keys
  2. Cookie isolation: Using the cookies API with separate container names or paths
  3. Iframe isolation: Creating sandboxed iframes for each account context
  4. Profile-level isolation: Spawning Chrome profiles programmatically (requires native messaging)

The right approach depends on your extension’s requirements. Most extensions benefit from a combination of storage and cookie isolation.

Implementing Storage-Based Container Isolation

The simplest approach uses Chrome’s storage API with a container identifier. Each account gets a namespaced key prefix:

// background.js - Container management
class AccountContainer {
  constructor(containerId) {
    this.containerId = containerId;
    this.storageKey = `container_${containerId}`;
  }

  async setData(key, value) {
    const storage = await chrome.storage.local.get(this.storageKey);
    const containerData = storage[this.storageKey] || {};
    containerData[key] = value;
    await chrome.storage.local.set({
      [this.storageKey]: containerData
    });
  }

  async getData(key) {
    const storage = await chrome.storage.local.get(this.storageKey);
    const containerData = storage[this.storageKey] || {};
    return containerData[key];
  }

  async clearContainer() {
    await chrome.storage.local.remove(this.storageKey);
  }
}

// Usage: Create separate containers for each account
const workAccount = new AccountContainer('work');
const personalAccount = new AccountContainer('personal');

// Store account-specific data
await workAccount.setData('user', { name: 'John', role: 'developer' });
await personalAccount.setData('user', { name: 'John', role: 'hobbyist' });

This pattern works well for extensions that manage data rather than authenticated sessions directly. The storage persists across browser restarts, making it suitable for maintaining user preferences and cached data per account.

For extensions that need to manage authenticated sessions across multiple accounts on the same domain, cookie isolation provides a more robust solution:

// background.js - Cookie container management
class CookieContainer {
  constructor(name) {
    this.name = name;
  }

  async setCookie(domain, cookieData) {
    const cookie = {
      url: `https://${domain}`,
      name: cookieData.name,
      value: cookieData.value,
      domain: cookieData.domain,
      path: '/',
      secure: true,
      httpOnly: false,
      sameSite: 'no_restriction'
    };
    
    // Add container-specific suffix to prevent collisions
    cookie.name = `[${this.name}]_${cookie.name}`;
    
    await chrome.cookies.set(cookie);
  }

  async getCookies(domain) {
    const url = `https://${domain}`;
    const allCookies = await chrome.cookies.getAll({ domain });
    
    return allCookies
      .filter(c => c.name.startsWith(`[${this.name}]`))
      .map(c => ({
        name: c.name.replace(`[${this.name}]_`, ''),
        value: c.value,
        domain: c.domain
      }));
  }

  async clearCookies(domain) {
    const cookies = await this.getCookies(domain);
    for (const cookie of cookies) {
      await chrome.cookies.remove({
        url: `https://${domain}`,
        name: `[${this.name}]_${cookie.name}`
      });
    }
  }
}

This approach prefixes cookie names with the container identifier, allowing multiple authentication states for the same domain. Users can switch accounts by loading cookies from the appropriate container.

Building the UI for Account Switching

A practical multi-account extension needs an intuitive interface for switching between containers. Here’s a popup implementation:

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 300px; padding: 16px; font-family: system-ui; }
    .container-list { list-style: none; padding: 0; margin: 0; }
    .container-item {
      display: flex;
      align-items: center;
      padding: 12px;
      border: 1px solid #e0e0e0;
      border-radius: 8px;
      margin-bottom: 8px;
      cursor: pointer;
      transition: background 0.2s;
    }
    .container-item:hover { background: #f5f5f5; }
    .container-item.active { border-color: #4285f4; background: #e8f0fe; }
    .container-avatar {
      width: 32px; height: 32px; border-radius: 50%;
      margin-right: 12px; display: flex;
      align-items: center; justify-content: center;
      background: #ddd; font-weight: bold;
    }
    .container-info { flex: 1; }
    .container-name { font-weight: 600; font-size: 14px; }
    .container-status { font-size: 12px; color: #666; }
  </style>
</head>
<body>
  <h3>Accounts</h3>
  <ul class="container-list" id="containerList"></ul>
  <button id="addContainer" style="width:100%; padding:10px; margin-top:8px;">
    + Add Account
  </button>
  <script src="popup.js"></script>
</body>
</html>
// popup.js
document.addEventListener('DOMContentLoaded', async () => {
  const containers = await loadContainers();
  const activeContainer = await getActiveContainer();
  const listEl = document.getElementById('containerList');

  containers.forEach(container => {
    const li = document.createElement('li');
    li.className = `container-item ${container.id === activeContainer ? 'active' : ''}`;
    li.innerHTML = `
      <div class="container-avatar" style="background: ${container.color}">
        ${container.name[0].toUpperCase()}
      </div>
      <div class="container-info">
        <div class="container-name">${container.name}</div>
        <div class="container-status">${container.email || 'Not connected'}</div>
      </div>
    `;
    li.addEventListener('click', () => switchContainer(container.id));
    listEl.appendChild(li);
  });

  document.getElementById('addContainer').addEventListener('click', addNewContainer);
});

async function switchContainer(containerId) {
  await chrome.storage.local.set({ activeContainer: containerId });
  // Notify background script to update context
  chrome.runtime.sendMessage({ 
    type: 'SWITCH_CONTAINER', 
    containerId 
  });
  window.close();
}

Handling Active Context in Content Scripts

Content scripts need to know which container is active to provide the right experience. Use message passing between the popup and content scripts:

// background.js - Message handling
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'SWITCH_CONTAINER') {
    // Store the active container in session storage
    chrome.storage.session.set({ 
      activeContainer: message.containerId 
    });
    
    // Notify all tabs about the switch
    chrome.tabs.query({}, tabs => {
      tabs.forEach(tab => {
        chrome.tabs.sendMessage(tab.id, {
          type: 'CONTAINER_SWITCHED',
          containerId: message.containerId
        });
      });
    });
    
    sendResponse({ success: true });
  }
  return true;
});
// content.js - Responding to container changes
let currentContainer = null;

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'CONTAINER_SWITCHED') {
    currentContainer = message.containerId;
    loadContainerContext(message.containerId);
  }
});

async function loadContainerContext(containerId) {
  const storage = await chrome.storage.local.get(`container_${containerId}`);
  const containerData = storage[`container_${containerId}`];
  
  if (containerData) {
    // Apply container-specific configurations
    updateUIBasedOnContainer(containerData);
    injectAccountSpecificFeatures(containerData);
  }
}

Best Practices for Production Extensions

When building multi-account containers for public use, consider these practical concerns:

Data security: Always encrypt sensitive data stored in chrome.storage.local. Use chrome.storage.session for temporary data that doesn’t need persistence.

Container limits: Chrome’s storage API has quotas. Monitor usage and implement cleanup policies for old containers.

User experience: Provide clear visual indicators of the active container. Users should never wonder which identity they’re currently using.

Error handling: Network failures and storage quota exceeded errors should be caught and presented gracefully. Container operations can fail in ways that surprise users.

Migration support: When updating your extension, handle schema changes in stored container data. Provide migration functions that transform old data formats.

Conclusion

Multi-account container systems transform Chrome extensions from single-purpose tools into flexible identity management platforms. Whether you’re building a social media management tool, an email client, or a developer utility, the container pattern provides clean separation between user contexts.

Start with storage-based isolation for simplicity, add cookie management for authenticated sessions, and layer in proper UI controls for account switching. This architecture scales well and provides the isolation users need when managing multiple identities.

Built by theluckystrike — More at zovo.one