Build an API Tester Chrome Extension: Postman Alternative in Your Browser

19 min read

Build an API Tester Chrome Extension: Postman Alternative in Your Browser

Build an API Tester Chrome Extension: Postman Alternative in Your Browser

If you are a web developer or API tester, you have probably used tools like Postman, Insomnia, or similar HTTP clients to test REST APIs. While these desktop applications are powerful, they require installation and cannot integrate directly into your browser workflow. What if you could have a lightweight, always-available API tester right in Chrome? In this comprehensive guide, we will walk you through building your own API Tester Chrome extension—a Postman alternative that runs directly in your browser.

This tutorial assumes you have basic knowledge of HTML, CSS, and JavaScript. By the end of this guide, you will have a fully functional Chrome extension capable of sending HTTP requests, handling responses, and managing authentication—without leaving your browser.


Why Build Your Own API Tester Chrome Extension?

Before we dive into the code, let us explore why building a custom API tester Chrome extension is worth your time. The most obvious reason is convenience. Instead of opening a separate application every time you need to test an endpoint, your extension is just a click away. This seamless integration into your browser workflow can significantly boost productivity.

Another advantage is customization. When you build your own tool, you can tailor it to your specific needs. Maybe you only work with JSON APIs and want a minimalist interface. Perhaps you need to quickly switch between different authentication tokens. With your own extension, you have complete control over features and design.

From a learning perspective, building an API tester is an excellent project to understand Chrome extension architecture. You will work with the extension popup, background scripts, and potentially content scripts. You will also gain deeper insight into how HTTP requests work, handling different methods, headers, and response formats.

Finally, there is the satisfaction of using a tool you built yourself. Many developers find that using custom tools improves their workflow and makes debugging more enjoyable. Your API tester can evolve with your needs, adding new features as you require them.


Project Architecture and File Structure

Every Chrome extension requires a manifest file and a user interface. For our API tester extension, we will need the following files:

  1. manifest.json - The extension configuration file
  2. popup.html - The user interface for the extension popup
  3. popup.js - The JavaScript logic handling user interactions
  4. popup.css - Styling for the popup interface

This simple structure is perfect for a lightweight API tester. As your extension grows, you might add background scripts for persistent functionality, but for our purposes, keeping everything in the popup is efficient and straightforward.

Let us start by creating the project directory and setting up our manifest file.


Setting Up the Manifest File

The manifest.json is the heart of every Chrome extension. It tells Chrome about your extension’s permissions, UI components, and version. Create a new file called manifest.json in your project directory with the following content:

{
  "manifest_version": 3,
  "name": "API Tester - Postman Alternative",
  "version": "1.0",
  "description": "A lightweight HTTP client for testing REST APIs directly in your browser",
  "permissions": [
    "activeTab",
    "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"
  }
}

A few important notes about this manifest. We are using Manifest V3, which is the current standard for Chrome extensions. The host_permissions with <all_urls> is crucial—we need this permission to make HTTP requests to any website. Without it, our extension would only be able to communicate with a limited set of domains.

The action section defines our popup. When users click the extension icon, Chrome will display our popup.html file. We have also specified icons in various sizes, which you would need to create for a production extension.


Building the User Interface

Now let us create the popup interface where users will enter their API requests. Open a new file called popup.html and add the following structure:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>API Tester</title>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>API Tester</h1>
    </header>
    
    <div class="request-section">
      <div class="url-bar">
        <select id="method" class="method-select">
          <option value="GET">GET</option>
          <option value="POST">POST</option>
          <option value="PUT">PUT</option>
          <option value="DELETE">DELETE</option>
          <option value="PATCH">PATCH</option>
        </select>
        <input type="text" id="url" placeholder="Enter request URL" class="url-input">
        <button id="send-btn" class="send-button">Send</button>
      </div>
      
      <div class="request-options">
        <div class="tabs">
          <button class="tab-btn active" data-tab="headers">Headers</button>
          <button class="tab-btn" data-tab="body">Body</button>
          <button class="tab-btn" data-tab="auth">Auth</button>
        </div>
        
        <div class="tab-content active" id="headers">
          <textarea id="headers-input" placeholder='{"Content-Type": "application/json"}'></textarea>
        </div>
        
        <div class="tab-content" id="body">
          <textarea id="body-input" placeholder='{"key": "value"}'></textarea>
        </div>
        
        <div class="tab-content" id="auth">
          <select id="auth-type" class="auth-select">
            <option value="none">No Auth</option>
            <option value="bearer">Bearer Token</option>
            <option value="basic">Basic Auth</option>
            <option value="apikey">API Key</option>
          </select>
          <input type="text" id="auth-value" placeholder="Enter token or credentials" class="auth-input">
        </div>
      </div>
    </div>
    
    <div class="response-section">
      <div class="response-header">
        <span class="status-label">Status:</span>
        <span id="status-code" class="status-code">-</span>
        <span class="time-label">Time:</span>
        <span id="response-time" class="response-time">-</span>
      </div>
      <div class="response-body">
        <pre id="response-content">Response will appear here</pre>
      </div>
    </div>
  </div>
  
  <script src="popup.js"></script>
</body>
</html>

This interface provides all the essential features of an API tester. Users can select HTTP methods, enter URLs, add headers and body content, configure authentication, and view responses. We have included tabs for different request sections to keep the interface clean and organized.


Stylizing the Extension

A well-designed interface makes the extension pleasant to use. Create popup.css with these styles:

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

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  width: 400px;
  min-height: 500px;
  background: #f5f5f5;
}

.container {
  padding: 16px;
}

header h1 {
  font-size: 18px;
  color: #333;
  margin-bottom: 16px;
}

.url-bar {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}

.method-select {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background: #fff;
  font-weight: bold;
  cursor: pointer;
}

.url-input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
}

.send-button {
  padding: 8px 16px;
  background: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: bold;
  transition: background 0.2s;
}

.send-button:hover {
  background: #45a049;
}

.send-button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.request-options {
  background: white;
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 16px;
}

.tabs {
  display: flex;
  background: #eee;
}

.tab-btn {
  flex: 1;
  padding: 10px;
  border: none;
  background: #eee;
  cursor: pointer;
  font-size: 13px;
}

.tab-btn.active {
  background: white;
  border-bottom: 2px solid #4CAF50;
}

.tab-content {
  display: none;
  padding: 12px;
}

.tab-content.active {
  display: block;
}

.tab-content textarea {
  width: 100%;
  height: 80px;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-family: monospace;
  font-size: 12px;
  resize: vertical;
}

.auth-select, .auth-input {
  width: 100%;
  padding: 8px;
  margin-bottom: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.response-section {
  background: white;
  border-radius: 8px;
  overflow: hidden;
}

.response-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px;
  background: #f9f9f9;
  border-bottom: 1px solid #eee;
  font-size: 13px;
}

.status-code {
  font-weight: bold;
  color: #4CAF50;
}

.status-code.error {
  color: #f44336;
}

.response-body {
  padding: 12px;
  max-height: 200px;
  overflow: auto;
}

.response-body pre {
  font-family: monospace;
  font-size: 12px;
  white-space: pre-wrap;
  word-break: break-word;
  color: #333;
}

The styling keeps the interface clean and professional. We have used a neutral color palette with green accents for the primary actions. The response section includes scrollable content for handling large responses.


Implementing the JavaScript Logic

Now comes the core functionality—handling the HTTP requests. Create popup.js:

document.addEventListener('DOMContentLoaded', () => {
  // DOM Elements
  const methodSelect = document.getElementById('method');
  const urlInput = document.getElementById('url');
  const sendBtn = document.getElementById('send-btn');
  const headersInput = document.getElementById('headers-input');
  const bodyInput = document.getElementById('body-input');
  const authType = document.getElementById('auth-type');
  const authValue = document.getElementById('auth-value');
  const statusCode = document.getElementById('status-code');
  const responseTime = document.getElementById('response-time');
  const responseContent = document.getElementById('response-content');
  const tabBtns = document.querySelectorAll('.tab-btn');
  const tabContents = document.querySelectorAll('.tab-content');

  // Tab switching
  tabBtns.forEach(btn => {
    btn.addEventListener('click', () => {
      const tabName = btn.dataset.tab;
      
      tabBtns.forEach(b => b.classList.remove('active'));
      tabContents.forEach(c => c.classList.remove('active'));
      
      btn.classList.add('active');
      document.getElementById(tabName).classList.add('active');
    });
  });

  // Send request handler
  sendBtn.addEventListener('click', async () => {
    const method = methodSelect.value;
    const url = urlInput.value.trim();
    
    if (!url) {
      alert('Please enter a URL');
      return;
    }

    // Reset response display
    statusCode.textContent = '...';
    statusCode.className = 'status-code';
    responseTime.textContent = '...';
    responseContent.textContent = 'Loading...';
    sendBtn.disabled = true;

    const startTime = performance.now();

    try {
      // Prepare headers
      const headers = {};
      
      // Parse custom headers
      try {
        const customHeaders = JSON.parse(headersInput.value || '{}');
        Object.assign(headers, customHeaders);
      } catch (e) {
        console.warn('Invalid headers JSON, ignoring custom headers');
      }

      // Add authentication headers
      const authTypeValue = authType.value;
      const authValueData = authValue.value.trim();
      
      if (authTypeValue === 'bearer' && authValueData) {
        headers['Authorization'] = `Bearer ${authValueData}`;
      } else if (authTypeValue === 'basic' && authValueData) {
        headers['Authorization'] = `Basic ${btoa(authValueData)}`;
      } else if (authTypeValue === 'apikey' && authValueData) {
        headers['X-API-Key'] = authValueData;
      }

      // Prepare request options
      const options = {
        method: method,
        headers: headers
      };

      // Add body for methods that support it
      if (['POST', 'PUT', 'PATCH'].includes(method) && bodyInput.value.trim()) {
        options.body = bodyInput.value.trim();
        
        // Set content type if not already set
        if (!headers['Content-Type'] && !headers['content-type']) {
          // Try to detect JSON
          try {
            JSON.parse(bodyInput.value);
            headers['Content-Type'] = 'application/json';
          } catch (e) {
            headers['Content-Type'] = 'text/plain';
          }
        }
      }

      // Make the request using fetch API
      const response = await fetch(url, options);
      
      const endTime = performance.now();
      const duration = Math.round(endTime - startTime);

      // Display status
      statusCode.textContent = `${response.status} ${response.statusText}`;
      statusCode.className = response.ok ? 'status-code' : 'status-code error';
      responseTime.textContent = `${duration}ms`;

      // Get response text
      const text = await response.text();
      
      // Try to format as JSON if possible
      try {
        const json = JSON.parse(text);
        responseContent.textContent = JSON.stringify(json, null, 2);
      } catch (e) {
        responseContent.textContent = text;
      }

    } catch (error) {
      statusCode.textContent = 'Error';
      statusCode.className = 'status-code error';
      responseTime.textContent = '-';
      responseContent.textContent = `Error: ${error.message}`;
    } finally {
      sendBtn.disabled = false;
    }
  });
});

This JavaScript handles everything from tab switching to making HTTP requests. We use the modern Fetch API, which is built into Chrome and supports all modern HTTP features. The code parses user input for headers and body, handles different authentication types, and attempts to format responses as pretty-printed JSON.

A key feature is the timing measurement. We use performance.now() to measure how long the request takes, giving users valuable feedback about API response times. We also handle errors gracefully, displaying meaningful error messages when requests fail.


Loading and Testing Your Extension

Now that we have all the files, it is time to load our extension into Chrome and test it. Follow these steps:

  1. Open Chrome and navigate to chrome://extensions/
  2. Enable “Developer mode” using the toggle in the top right corner
  3. Click “Load unpacked” button
  4. Select your project directory

Your extension should now appear in the Chrome toolbar. Click the extension icon to open the popup. Try sending a test request to a public API like https://jsonplaceholder.typicode.com/posts/1 using the GET method.

You should see the JSON response displayed in the response section, along with the status code and response time. Congratulations—you have built your own API tester Chrome extension!


Enhancements and Future Improvements

While our API tester is functional, there are many ways you could enhance it. Here are some ideas for features you might want to add:

Request History: Store previous requests in local storage so users can quickly revisit them. This is invaluable when debugging APIs.

Environment Variables: Allow users to define variables like {{base_url}} that get replaced before sending requests. This makes it easy to switch between development and production environments.

Response Collections: Save successful responses for comparison or documentation purposes.

Import/Export: Support importing Postman collections or cURL commands to make migration easier.

Syntax Highlighting: Add a proper code editor with syntax highlighting for JSON body editing.

These enhancements would transform your simple API tester into a production-ready tool comparable to Postman or Insomnia.


Publishing Your Extension

Once you are satisfied with your extension, you can publish it to the Chrome Web Store. First, create a ZIP file of your extension directory (excluding any development files). Then, create a developer account on the Chrome Web Store Developer Dashboard, pay the one-time registration fee, and upload your extension.

Before publishing, ensure you have created proper icons and consider adding screenshots and a detailed description. Your extension will undergo review before being published, so make sure it complies with Chrome’s policies.


Conclusion

Building an API tester Chrome extension is an excellent project that combines practical utility with learning opportunity. Throughout this guide, you have created a fully functional HTTP client that can send requests, handle various authentication methods, and display responses—all without leaving your browser.

The extension we built demonstrates the core concepts of Chrome extension development: manifest configuration, popup interfaces, and JavaScript integration with browser APIs. These skills transfer directly to other extension projects you might want to tackle.

As you continue developing your API tester, remember that the best tools evolve based on real usage. Pay attention to what features you find yourself wishing for during your daily work, and implement those improvements. Your custom-built API tester will serve you well as a lightweight, always-available alternative to heavier desktop applications.

Start building today, and enjoy the satisfaction of using a tool you created yourself!

No previous article
No next article