Build a User Agent Switcher Chrome Extension: Test Mobile Views on Desktop

26 min read

Build a User Agent Switcher Chrome Extension: Test Mobile Views on Desktop

Build a User Agent Switcher Chrome Extension: Test Mobile Views on Desktop

Web developers and testers frequently need to test how their websites appear on different devices without physically owning every smartphone or tablet on the market. This is where a user agent switcher Chrome extension becomes an indispensable tool in your development arsenal. By building your own user agent switcher extension, you gain complete control over how your website appears across different browsers and devices, all from the convenience of your desktop computer.

In this comprehensive guide, we will walk through the complete process of building a user agent switcher Chrome extension from scratch. You will learn how to create a functional extension that allows you to quickly switch between different user agents, test mobile views on desktop, and emulate various devices. This project is perfect for web developers, QA testers, and anyone who needs to verify responsive design implementations.


What Is a User Agent and Why Does It Matter?

Before we dive into building the extension, let us understand what a user agent actually is and why it plays such a crucial role in web development. A user agent is a string of text that web browsers send to web servers with every request. This string identifies the browser, its version, the operating system, and other important details that help websites deliver appropriate content.

When you visit a website, your browser sends a user agent string that looks something like this:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36

This string tells the server that you are using Chrome version 123 on a 64-bit Windows 10 computer. Based on this information, websites may serve different content, apply different styling, or redirect you to mobile versions of their site.

Why Web Developers Need User Agent Switching

The ability to switch user agent strings is essential for several important tasks:

  1. Responsive Design Testing: Verify that your website looks great on mobile devices, tablets, and desktops without physically testing on each device.

  2. Cross-Browser Compatibility: Test how your site appears in different browsers even if you primarily use Chrome for development.

  3. API Development: Test how your backend handles requests from different device types and browser versions.

  4. Debugging Device-Specific Issues: Reproduce and fix bugs that only occur on specific devices or browser versions.

  5. Competitive Analysis: See how your competitors’ websites appear on different devices.


Project Setup and Prerequisites

Before we start coding, let us set up our project structure and ensure we have everything we need to build this user agent switcher Chrome extension.

Required Tools

To follow this tutorial, you will need:

  • A code editor (VS Code, Sublime Text, or any editor you prefer)
  • Google Chrome browser installed
  • Basic knowledge of HTML, CSS, and JavaScript
  • A Google account (for publishing to the Chrome Web Store)

Project Directory Structure

Create a new folder for your project and set up the following directory structure:

user-agent-switcher/
├── manifest.json
├── popup.html
├── popup.css
├── popup.js
├── content.js
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

This structure separates our extension into logical components: the manifest file defines extension metadata, the popup provides the user interface, and content scripts handle the actual user agent manipulation.


Creating the Manifest File

The manifest.json file is the heart of every Chrome extension. It tells Chrome everything about your extension, including its name, version, permissions, and which files to load. For our user agent switcher, we need to specify the appropriate permissions to access web requests and modify headers.

Create a file named manifest.json in your project folder and add the following content:

{
  "manifest_version": 3,
  "name": "User Agent Switcher",
  "version": "1.0.0",
  "description": "Switch user agent strings to test mobile views and emulate different devices directly from Chrome.",
  "permissions": [
    "storage",
    "declarativeNetRequest",
    "scripting"
  ],
  "host_permissions": [
    "<all_urls>"
  ],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "background": {
    "service_worker": "popup.js"
  }
}

The key permissions we need are:

  • storage: To save the selected user agent and remember it between sessions
  • declarativeNetRequest: To modify network requests and change the user agent header
  • scripting: To inject scripts that can read and modify page content

The popup is what users see when they click on your extension icon in the Chrome toolbar. For our user agent switcher, we need a clean interface that displays a list of predefined user agents and allows users to select their desired option.

Create popup.html with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>User Agent Switcher</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>User Agent Switcher</h1>
      <p class="subtitle">Test mobile views on desktop</p>
    </header>

    <div class="current-status">
      <span class="label">Current User Agent:</span>
      <span id="current-ua" class="value">Default</span>
    </div>

    <div class="device-categories">
      <h2>Select Device</h2>
      
      <div class="category">
        <h3>Mobile Devices</h3>
        <select id="mobile-devices" class="device-select">
          <option value="">-- Select Mobile Device --</option>
          <option value="iphone-15-pro">iPhone 15 Pro</option>
          <option value="iphone-15">iPhone 15</option>
          <option value="iphone-14-pro">iPhone 14 Pro</option>
          <option value="iphone-se">iPhone SE</option>
          <option value="pixel-8">Google Pixel 8</option>
          <option value="pixel-7">Google Pixel 7</option>
          <option value="samsung-s24">Samsung Galaxy S24</option>
          <option value="samsung-s23">Samsung Galaxy S23</option>
        </select>
      </div>

      <div class="category">
        <h3>Tablets</h3>
        <select id="tablet-devices" class="device-select">
          <option value="">-- Select Tablet --</option>
          <option value="ipad-pro-12">iPad Pro 12.9"</option>
          <option value="ipad-pro-11">iPad Pro 11"</option>
          <option value="ipad-air">iPad Air</option>
          <option value="ipad-mini">iPad mini</option>
          <option value="nexus-9">Nexus 9</option>
        </select>
      </div>

      <div class="category">
        <h3>Desktop Browsers</h3>
        <select id="desktop-browsers" class="device-select">
          <option value="">-- Select Browser --</option>
          <option value="chrome-windows">Chrome (Windows)</option>
          <option value="chrome-mac">Chrome (macOS)</option>
          <option value="firefox-windows">Firefox (Windows)</option>
          <option value="firefox-mac">Firefox (macOS)</option>
          <option value="safari-mac">Safari (macOS)</option>
          <option value="edge">Microsoft Edge</option>
        </select>
      </div>

      <div class="category">
        <h3>Custom User Agent</h3>
        <input type="text" id="custom-ua" class="custom-input" placeholder="Enter custom user agent string">
        <button id="apply-custom" class="btn-apply">Apply Custom UA</button>
      </div>
    </div>

    <div class="actions">
      <button id="reset-ua" class="btn-reset">Reset to Default</button>
    </div>

    <footer>
      <p class="note">Note: Some sites may cache their responses. Refresh the page after switching.</p>
    </footer>
  </div>
  <script src="popup.js"></script>
</body>
</html>

This HTML provides a comprehensive interface with dropdowns for mobile devices, tablets, and desktop browsers, plus an option to enter custom user agent strings.


Now let us add some CSS to make our popup look professional and user-friendly. Create popup.css:

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

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  width: 320px;
  background-color: #f5f5f5;
  color: #333;
}

.container {
  padding: 16px;
}

header {
  text-align: center;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid #e0e0e0;
}

header h1 {
  font-size: 18px;
  font-weight: 600;
  color: #1a73e8;
  margin-bottom: 4px;
}

.subtitle {
  font-size: 12px;
  color: #666;
}

.current-status {
  background: #e8f0fe;
  padding: 10px 12px;
  border-radius: 6px;
  margin-bottom: 16px;
}

.current-status .label {
  font-size: 11px;
  color: #555;
  display: block;
  margin-bottom: 4px;
}

.current-status .value {
  font-size: 12px;
  font-weight: 500;
  color: #1a73e8;
  word-break: break-all;
}

.device-categories h2 {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 12px;
  color: #333;
}

.category {
  margin-bottom: 16px;
}

.category h3 {
  font-size: 12px;
  font-weight: 500;
  color: #555;
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.device-select,
.custom-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 13px;
  background: white;
  margin-bottom: 8px;
}

.device-select:focus,
.custom-input:focus {
  outline: none;
  border-color: #1a73e8;
  box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.1);
}

.btn-apply,
.btn-reset {
  width: 100%;
  padding: 10px;
  border: none;
  border-radius: 4px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s;
}

.btn-apply {
  background-color: #1a73e8;
  color: white;
}

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

.btn-reset {
  background-color: #f5f5f5;
  color: #333;
  border: 1px solid #ddd;
}

.btn-reset:hover {
  background-color: #e8e8e8;
}

.actions {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid #e0e0e0;
}

footer {
  margin-top: 12px;
}

.note {
  font-size: 11px;
  color: #777;
  text-align: center;
  font-style: italic;
}

Implementing the Extension Logic

Now comes the most important part: implementing the JavaScript logic that handles user agent switching. This is where the magic happens. Create popup.js:

// User agent strings for different devices
const userAgents = {
  // Mobile Devices
  'iphone-15-pro': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
  'iphone-15': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
  'iphone-14-pro': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
  'iphone-se': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
  'pixel-8': 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'pixel-7': 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'samsung-s24': 'Mozilla/5.0 (Linux; Android 14; SM-S921B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'samsung-s23': 'Mozilla/5.0 (Linux; Android 13; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  
  // Tablets
  'ipad-pro-12': 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
  'ipad-pro-11': 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
  'ipad-air': 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
  'ipad-mini': 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
  'nexus-9': 'Mozilla/5.0 (Linux; Android 7.0; Nexus 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  
  // Desktop Browsers
  'chrome-windows': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'chrome-mac': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'firefox-windows': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
  'firefox-mac': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0',
  'safari-mac': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
  'edge': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0'
};

// DOM Elements
const mobileSelect = document.getElementById('mobile-devices');
const tabletSelect = document.getElementById('tablet-devices');
const desktopSelect = document.getElementById('desktop-browsers');
const customInput = document.getElementById('custom-ua');
const applyCustomBtn = document.getElementById('apply-custom');
const resetBtn = document.getElementById('reset-ua');
const currentUaDisplay = document.getElementById('current-ua');

// Initialize the extension
document.addEventListener('DOMContentLoaded', async () => {
  // Load saved user agent from storage
  const savedData = await chrome.storage.local.get(['currentUserAgent', 'currentDevice']);
  if (savedData.currentUserAgent) {
    currentUaDisplay.textContent = truncateUA(savedData.currentUserAgent);
  }
  
  // Set initial dropdown values if a device was previously selected
  if (savedData.currentDevice) {
    const [category, device] = savedData.currentDevice.split('-');
    if (category === 'mobile') {
      mobileSelect.value = savedData.currentDevice;
    } else if (category === 'tablet') {
      tabletSelect.value = savedData.currentDevice;
    } else if (category === 'desktop') {
      desktopSelect.value = savedData.currentDevice;
    }
  }
});

// Handle mobile device selection
mobileSelect.addEventListener('change', async (e) => {
  const device = e.target.value;
  if (device && userAgents[device]) {
    await applyUserAgent(device, userAgents[device]);
    // Reset other dropdowns
    tabletSelect.value = '';
    desktopSelect.value = '';
  }
});

// Handle tablet selection
tabletSelect.addEventListener('change', async (e) => {
  const device = e.target.value;
  if (device && userAgents[device]) {
    await applyUserAgent(device, userAgents[device]);
    // Reset other dropdowns
    mobileSelect.value = '';
    desktopSelect.value = '';
  }
});

// Handle desktop browser selection
desktopSelect.addEventListener('change', async (e) => {
  const device = e.target.value;
  if (device && userAgents[device]) {
    await applyUserAgent(device, userAgents[device]);
    // Reset other dropdowns
    mobileSelect.value = '';
    tabletSelect.value = '';
  }
});

// Handle custom user agent input
applyCustomBtn.addEventListener('click', async () => {
  const customUA = customInput.value.trim();
  if (customUA) {
    await applyUserAgent('custom', customUA);
    // Reset all dropdowns
    mobileSelect.value = '';
    tabletSelect.value = '';
    desktopSelect.value = '';
  }
});

// Handle reset to default
resetBtn.addEventListener('click', async () => {
  await chrome.storage.local.remove(['currentUserAgent', 'currentDevice']);
  currentUaDisplay.textContent = 'Default';
  
  // Reset all dropdowns
  mobileSelect.value = '';
  tabletSelect.value = '';
  desktopSelect.value = '';
  customInput.value = '';
  
  // Show notification
  showNotification('User agent reset to default');
});

// Apply user agent function
async function applyUserAgent(device, userAgentString) {
  // Save to storage
  await chrome.storage.local.set({
    currentUserAgent: userAgentString,
    currentDevice: device
  });
  
  // Update display
  currentUaDisplay.textContent = truncateUA(userAgentString);
  
  // Apply using declarativeNetRequest
  try {
    await applyUserAgentHeader(userAgentString);
    showNotification(`Applied: ${formatDeviceName(device)}`);
  } catch (error) {
    console.error('Error applying user agent:', error);
    showNotification('Error applying user agent');
  }
}

// Apply user agent using declarativeNetRequest
async function applyUserAgentHeader(userAgent) {
  // First, remove any existing rules
  const rules = await chrome.declarativeNetRequest.getDynamicRules();
  if (rules.length > 0) {
    await chrome.declarativeNetRequest.updateDynamicRules({
      removeRuleIds: rules.map(r => r.id)
    });
  }
  
  // Add new rule to modify user agent
  await chrome.declarativeNetRequest.updateDynamicRules({
    addRules: [{
      id: 1,
      priority: 1,
      action: {
        type: 'modifyHeaders',
        requestHeaders: [
          { header: 'User-Agent', operation: 'set', value: userAgent }
        ]
      },
      condition: {
        urlFilter: '*',
        resourceTypes: ['main_frame', 'sub_frame', 'xmlhttprequest', 'script', 'stylesheet', 'image', 'object', 'ping', 'csp_report', 'media', 'websocket', 'other']
      }
    }]
  });
}

// Helper function to truncate user agent for display
function truncateUA(ua) {
  if (ua.length > 50) {
    return ua.substring(0, 50) + '...';
  }
  return ua;
}

// Helper function to format device name
function formatDeviceName(device) {
  return device
    .split('-')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join(' ');
}

// Simple notification function
function showNotification(message) {
  // Create a temporary notification element
  const notification = document.createElement('div');
  notification.className = 'notification';
  notification.textContent = message;
  notification.style.cssText = `
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: #333;
    color: white;
    padding: 10px 20px;
    border-radius: 4px;
    font-size: 13px;
    z-index: 1000;
    animation: fadeIn 0.3s ease;
  `;
  
  document.body.appendChild(notification);
  
  // Remove after 2 seconds
  setTimeout(() => {
    notification.remove();
  }, 2000);
}

Creating the Content Script

While the declarativeNetRequest API handles most user agent switching at the network level, adding a content script provides additional functionality for detecting and displaying the current user agent within web pages. Create content.js:

// Content script to display user agent info on page

// Listen for messages from the popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'getUserAgent') {
    sendResponse({
      userAgent: navigator.userAgent,
      platform: navigator.platform
    });
  }
  return true;
});

// Optional: Inject a floating indicator showing current user agent
async function createUAIndicator() {
  const stored = await chrome.storage.local.get('currentUserAgent');
  
  if (stored.currentUserAgent) {
    const indicator = document.createElement('div');
    indicator.id = 'ua-switcher-indicator';
    indicator.innerHTML = `
      <span>User Agent: ${stored.currentUserAgent.substring(0, 30)}...</span>
    `;
    indicator.style.cssText = `
      position: fixed;
      bottom: 10px;
      right: 10px;
      background: rgba(0, 0, 0, 0.8);
      color: white;
      padding: 8px 12px;
      border-radius: 4px;
      font-size: 11px;
      z-index: 999999;
      max-width: 300px;
      word-break: break-all;
    `;
    document.body.appendChild(indicator);
  }
}

// Only run on specific pages (optional feature)
if (window.location.href.includes('debug=')) {
  createUAIndicator();
}

Testing Your Extension

Now that we have created all the necessary files, it is time to test your user agent switcher Chrome extension. Follow these steps to load your extension in Chrome:

  1. Open Google Chrome and navigate to chrome://extensions/
  2. Enable “Developer mode” using the toggle in the top-right corner
  3. Click the “Load unpacked” button that appears on the top-left
  4. Select the folder containing your extension files
  5. Your extension should now appear in the extensions list

How to Test the Extension

Once loaded, test your user agent switcher by following these steps:

  1. Click the extension icon in your Chrome toolbar to open the popup
  2. Select a device from any of the dropdown menus (mobile, tablet, or desktop)
  3. Navigate to any website (such as whatismybrowser.com or browserleaks.com) to verify the user agent has changed
  4. Try selecting different devices to see the user agent change dynamically
  5. Use the custom input to enter your own user agent string
  6. Click “Reset to Default” to restore the original user agent

Common Issues and Troubleshooting

If your extension is not working as expected, check the following:

  • User agent not changing: Some websites use JavaScript to detect the browser instead of relying on headers. Our extension handles the header-based detection, which works for most server-side rendering scenarios.

  • Chrome Web Store restrictions: The declarativeNetRequest permission requires special approval for publishing. Consider using the more limited but approved webRequest or webRequestBlocking permissions for initial publication.

  • Caching issues: Some websites cache their responses. Hard refresh (Ctrl+Shift+R or Cmd+Shift+R) may be necessary after changing user agents.


Publishing to the Chrome Web Store

Once you have thoroughly tested your extension and are satisfied with its functionality, you can publish it to the Chrome Web Store. Here is what you need to do:

Prepare for Publishing

  1. Create a developer account at the Chrome Web Store Developer Dashboard
  2. Prepare your extension icons (16x16, 48x48, and 128x128 pixels)
  3. Create screenshots and a promotional image for your listing
  4. Write a compelling description using your target keywords naturally

Submission Process

When submitting your extension, ensure you:

  • Use clear, descriptive text that naturally incorporates keywords like “user agent switcher,” “mobile view testing,” and “device emulation”
  • Provide detailed release notes explaining what is new in each version
  • Respond promptly to any review feedback from Google

Manifest V3 Considerations

Chrome has moved to Manifest V3, which has some restrictions on certain APIs. The declarativeNetRequest API we used requires a special review process for full functionality. For initial publication, you might consider:

  • Using the webRequest API instead (though it also requires approval)
  • Limiting the scope of user agent modification
  • Clearly documenting any limitations in your extension description

Advanced Features and Enhancements

Once you have the basic user agent switcher working, consider adding these advanced features:

Preset Device Profiles

Add predefined profiles that not only change the user agent but also simulate viewport sizes, touch events, and device pixel ratios for more accurate mobile emulation.

Quick Toggle

Add keyboard shortcuts (Ctrl+Shift+U or Cmd+Shift+U) to quickly toggle between your most-used user agents without opening the popup.

URL-Specific Rules

Allow users to set different user agents for specific websites, so you can automatically switch to mobile when visiting your development server and stay on desktop for other sites.

Sync Across Devices

Use the chrome.storage.sync API to sync user agent preferences across all your Chrome installations where you are signed in.


Conclusion

Building a user agent switcher Chrome extension is an excellent project that teaches you many fundamental concepts of Chrome extension development. You have learned how to create a Manifest V3 extension, build a popup interface, use the declarativeNetRequest API to modify network requests, and store user preferences with the Chrome storage API.

This user agent switcher extension is a practical tool that you can use daily in your web development workflow. It allows you to quickly test mobile views on desktop, debug device-specific issues, and ensure your websites provide excellent experiences across all devices.

Remember that the user agent switching functionality we implemented works at the network request level, which handles the majority of use cases. Some sophisticated websites may use JavaScript to detect browser features rather than relying on user agent strings, but the approach covered here works for the vast majority of real-world scenarios.

Start building your extension today, test it thoroughly, and consider publishing it to the Chrome Web Store to help other developers with their responsive design testing needs!

No previous article
No next article