Build a Custom Chrome DevTools Panel Extension: Complete Tutorial
Chrome DevTools is the most powerful browser-based development environment available today. It provides web developers with a comprehensive suite of debugging, profiling, and inspection tools. But did you know you can extend DevTools itself? By building a custom DevTools panel extension, you can add entirely new functionality to Chrome’s developer toolkit, creating specialized tools tailored to your workflow or your users’ needs.
In this complete tutorial, we will walk through building a custom Chrome DevTools panel extension from scratch. You will learn how to create a DevTools panel, communicate with the inspected page, leverage the Chrome Extensions API, and package your extension for distribution. By the end of this guide, you will have a fully functional DevTools panel extension that you can extend and customize further.
Understanding Chrome DevTools Panel Extensions
Before we dive into code, it is essential to understand what DevTools panel extensions are and how they differ from regular Chrome extensions.
What Are DevTools Panel Extensions?
A DevTools panel extension is a special type of Chrome extension that adds a custom tab to the Chrome DevTools window. When users open DevTools (F12 or right-click and Inspect), they will see your custom panel alongside built-in tabs like Elements, Console, Network, and Performance.
DevTools panels can:
- Display custom UI built with HTML, CSS, and JavaScript
- Inspect and interact with the currently inspected page
- Access the DOM and JavaScript context of the inspected page
- Use Chrome Extension APIs to extend functionality beyond DevTools
- Communicate bidirectionally with content scripts and background scripts
Use Cases for DevTools Panels
DevTools panel extensions are perfect for:
- Custom Debugging Tools: Build specialized debugging interfaces for your framework or library
- Visual Inspectors: Create visual property editors or component explorers
- Performance Profilers: Add custom performance monitoring and analysis tools
- API Clients: Build REST or GraphQL clients directly within DevTools
- State Viewers: Display application state in a human-readable format
- Design Tools: Add color pickers, measurement tools, or accessibility checkers
Popular examples include React DevTools, Vue DevTools, Angular Augury, and various API testing tools that integrate directly into the browser’s development environment.
Setting Up the Project Structure
Let us start by setting up the project structure for our DevTools panel extension. We will create a well-organized project that follows Chrome extension best practices.
Creating the Directory Structure
Create a new folder for your extension and set up the following structure:
devtools-panel-extension/
├── manifest.json
├── background.js
├── devtools/
│ ├── devtools.js
│ └── panel.html
├── content/
│ └── content.js
├── icons/
│ ├── icon16.png
│ ├── icon32.png
│ ├── icon48.png
│ └── icon128.png
└── styles/
└── panel.css
The Manifest File
Every Chrome extension starts with a manifest.json file. For DevTools panel extensions, we need to declare the DevTools page and ensure proper permissions.
{
"manifest_version": 3,
"name": "Custom DevTools Panel",
"version": "1.0.0",
"description": "A custom DevTools panel extension for web developers",
"permissions": [
"activeTab",
"scripting"
],
"devtools_page": "devtools/devtools.html",
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
The key difference from regular extensions is the "devtools_page" field, which tells Chrome where to find your DevTools extension entry point.
Creating the DevTools Page
The DevTools page (devtools.html) is a special page that Chrome loads when DevTools opens. It does not have a visible UI but is responsible for registering your custom panel.
The DevTools HTML File
Create devtools/devtools.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="devtools.js"></script>
</body>
</html>
Registering the Panel
Create devtools/devtools.js. This script runs in the context of the DevTools page and registers your custom panel:
// Create the custom panel
chrome.devtools.panels.create(
"My Custom Panel", // Panel title
"icons/icon16.png", // Panel icon
"devtools/panel.html", // Panel HTML file
function(panel) {
// Panel callback - called when panel is created
console.log("Custom panel created:", panel);
// You can add event listeners here
panel.onShown.addListener(function(panelWindow) {
console.log("Panel shown:", panelWindow);
});
panel.onHidden.addListener(function() {
console.log("Panel hidden");
});
}
);
The chrome.devtools.panels.create() method takes four parameters:
- title: The name displayed in the DevTools tab bar
- icon: A 16x16 icon shown next to the title
- page: The HTML file that contains your panel’s UI
- callback: A function called with the panel object after creation
Building the Panel UI
Now let us create the actual panel interface that users will see. Our panel will demonstrate several key capabilities: inspecting the DOM, executing scripts in the context of the inspected page, and displaying information in real-time.
The Panel HTML
Create devtools/devtools/panel.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../../styles/panel.css">
</head>
<body>
<div class="panel-container">
<header class="panel-header">
<h1>DevTools Panel</h1>
<button id="refresh-btn" class="btn">Refresh Data</button>
</header>
<div class="panel-content">
<section class="info-section">
<h2>Page Information</h2>
<div id="page-info" class="info-box">
<p>Loading page information...</p>
</div>
</section>
<section class="dom-section">
<h2>DOM Inspector</h2>
<div class="controls">
<input type="text" id="selector-input" placeholder="Enter CSS selector">
<button id="query-btn" class="btn">Query Element</button>
</div>
<div id="dom-result" class="result-box">
<p>Results will appear here...</p>
</div>
</section>
<section class="console-section">
<h2>Execute Code</h2>
<textarea id="code-input" placeholder="Enter JavaScript code to execute in page context"></textarea>
<button id="execute-btn" class="btn">Execute</button>
<div id="exec-result" class="result-box">
<p>Execution results will appear here...</p>
</div>
</section>
</div>
</div>
<script src="panel.js"></script>
</body>
</html>
Styling the Panel
Create styles/panel.css to make our panel look professional:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
font-size: 13px;
color: #333;
background: #fff;
}
.panel-container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
}
.panel-header h1 {
font-size: 16px;
font-weight: 600;
}
.panel-content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
section {
margin-bottom: 24px;
}
section h2 {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
color: #555;
}
.info-box, .result-box {
background: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 12px;
min-height: 60px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
white-space: pre-wrap;
word-break: break-all;
}
.controls {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.controls input {
flex: 1;
padding: 6px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 12px;
}
.controls input:focus {
outline: none;
border-color: #4285f4;
}
textarea {
width: 100%;
height: 80px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 12px;
resize: vertical;
margin-bottom: 8px;
}
textarea:focus {
outline: none;
border-color: #4285f4;
}
.btn {
padding: 6px 14px;
background: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
}
.btn:hover {
background: #3367d6;
}
.btn:active {
background: #2a5bb8;
}
.error {
color: #d32f2f;
}
.success {
color: #388e3c;
}
Implementing Panel Logic
The panel JavaScript handles user interactions and communicates with the inspected page. This is where the real functionality lives.
The Panel JavaScript
Create devtools/devtools/panel.js:
// Reference to the inspected page's window object
let inspectedWindow = null;
// Set up the panel when it is shown
chrome.devtools.panels.elements.createSidebarPane(
"Element Properties",
function(sidebar) {
function updateSidebar() {
chrome.devtools.inspectedWindow.eval(
"window.getSelection().toString()",
function(result, isException) {
if (!isException && result) {
sidebar.setExpression("(" + getElementProperties.toString() + ")('" + result + "')");
}
}
);
}
chrome.devtools.panels.elements.onSelectionChanged.addListener(updateSidebar);
updateSidebar();
}
);
function getElementProperties(selection) {
return "Selected: " + selection;
}
// Button event listeners
document.addEventListener('DOMContentLoaded', function() {
// Refresh button
document.getElementById('refresh-btn').addEventListener('click', function() {
refreshPageInfo();
});
// Query element button
document.getElementById('query-btn').addEventListener('click', function() {
var selector = document.getElementById('selector-input').value;
queryElement(selector);
});
// Execute code button
document.getElementById('execute-btn').addEventListener('click', function() {
var code = document.getElementById('code-input').value;
executeCode(code);
});
// Initial load
refreshPageInfo();
});
// Refresh page information from the inspected window
function refreshPageInfo() {
var infoBox = document.getElementById('page-info');
infoBox.innerHTML = '<p class="success">Loading...</p>';
chrome.devtools.inspectedWindow.eval(
'({' +
' title: document.title,' +
' url: window.location.href,' +
' readyState: document.readyState,' +
' elementsCount: document.getElementsByTagName("*").length,' +
' scriptsCount: document.scripts.length,' +
' imagesCount: document.images.length' +
'})',
function(result, isException) {
if (isException) {
infoBox.innerHTML = '<p class="error">Error: ' + isException.value + '</p>';
return;
}
var info = result;
infoBox.innerHTML =
'<p><strong>Title:</strong> ' + escapeHtml(info.title) + '</p>' +
'<p><strong>URL:</strong> ' + escapeHtml(info.url) + '</p>' +
'<p><strong>Ready State:</strong> ' + info.readyState + '</p>' +
'<p><strong>Elements:</strong> ' + info.elementsCount + '</p>' +
'<p><strong>Scripts:</strong> ' + info.scriptsCount + '</p>' +
'<p><strong>Images:</strong> ' + info.imagesCount + '</p>';
}
);
}
// Query an element using CSS selector
function queryElement(selector) {
var resultBox = document.getElementById('dom-result');
if (!selector || selector.trim() === '') {
resultBox.innerHTML = '<p class="error">Please enter a CSS selector</p>';
return;
}
var expression = '(function() {' +
' var elements = document.querySelectorAll("' + selector.replace(/"/g, '\\"') + '");' +
' if (elements.length === 0) return { error: "No elements found" };' +
' var info = [];' +
' for (var i = 0; i < Math.min(elements.length, 10); i++) {' +
' var el = elements[i];' +
' info.push({' +
' tag: el.tagName.toLowerCase(),' +
' id: el.id || null,' +
' classes: el.className || null,' +
' text: el.textContent ? el.textContent.substring(0, 50) + "..." : null' +
' });' +
' }' +
' return { count: elements.length, elements: info };' +
'})()';
chrome.devtools.inspectedWindow.eval(expression, function(result, isException) {
if (isException) {
resultBox.innerHTML = '<p class="error">Error: ' + isException.value + '</p>';
return;
}
if (result.error) {
resultBox.innerHTML = '<p class="error">' + result.error + '</p>';
return;
}
var html = '<p class="success">Found ' + result.count + ' element(s)</p>';
if (result.elements && result.elements.length > 0) {
html += '<ul style="margin-top: 8px; padding-left: 20px;">';
result.elements.forEach(function(el) {
var classes = el.classes ? '.' + el.classes.split(' ').join('.') : '';
html += '<li><' + el.tag + (el.id ? '#' + el.id : '') + classes + '>';
if (el.text) {
html += '<br><span style="color: #666;">' + escapeHtml(el.text) + '</span>';
}
html += '</li>';
});
html += '</ul>';
}
resultBox.innerHTML = html;
});
}
// Execute code in the context of the inspected page
function executeCode(code) {
var resultBox = document.getElementById('exec-result');
if (!code || code.trim() === '') {
resultBox.innerHTML = '<p class="error">Please enter some code to execute</p>';
return;
}
chrome.devtools.inspectedWindow.eval(code, function(result, isException) {
if (isException) {
resultBox.innerHTML = '<p class="error">Error: ' + isException.value + '</p>';
return;
}
var resultStr = '';
try {
resultStr = JSON.stringify(result, null, 2);
} catch (e) {
resultStr = String(result);
}
resultBox.innerHTML = '<p class="success">Result:</p><pre>' + escapeHtml(resultStr) + '</pre>';
});
}
// Utility function to escape HTML
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
Working with the Inspected Window
One of the most powerful features of DevTools panel extensions is the ability to interact with the page being inspected. The chrome.devtools.inspectedWindow API provides this capability.
Key Inspected Window Methods
The chrome.devtools.inspectedWindow API offers several important methods:
eval(expression, callback): Executes JavaScript in the context of the inspected pagegetResources(callback): Retrieves a list of resources loaded by the pagereload(): Reloads the inspected pagecaptureScreenshot(callback): Captures a screenshot of the inspected page
Communication Patterns
There are several ways to communicate between your panel and the inspected page:
Direct Evaluation: Use chrome.devtools.inspectedWindow.eval() to run code directly in the page context:
chrome.devtools.inspectedWindow.eval(
"document.title",
function(result, isException) {
console.log("Page title:", result);
}
);
PostMessage Communication: Set up message passing between the panel and content scripts:
// In panel.js
chrome.devtools.inspectedWindow.postMessage(
"myExtensionMessage",
{ data: "hello from panel" }
);
// In content.js
window.addEventListener("message", function(event) {
if (event.data.source === "chrome-devtools-page") {
console.log("Received from panel:", event.data);
}
});
Advanced Features
Now that we have covered the basics, let us explore some advanced features that can make your DevTools panel even more powerful.
Creating a Sidebar Pane
Sidebar panes appear alongside the Elements panel and are perfect for displaying contextual information:
chrome.devtools.panels.elements.createSidebarPane(
"My Sidebar",
function(sidebar) {
// Set the sidebar content
sidebar.setExpression("document.body.innerHTML.length");
// Or set HTML content directly
sidebar.setPage({ path: "sidebar.html" });
}
);
Accessing the Selection
You can track the user’s selection in the Elements panel:
chrome.devtools.panels.elements.onSelectionChanged.addListener(function() {
chrome.devtools.inspectedWindow.eval(
"$0", // $0 is the currently selected element
function(selectedElement) {
// Do something with the selected element
console.log("Selected:", selectedElement);
}
);
});
Adding a Theme
DevTools supports both light and dark themes. Your panel should adapt accordingly:
// Detect the current theme
chrome.devtools.panels.getThemeName(function(themeName) {
console.log("Current theme:", themeName); // "dark" or "light"
if (themeName === "dark") {
document.body.classList.add("dark-theme");
}
});
Testing Your Extension
Testing is crucial to ensure your DevTools panel works correctly. Here is how to load and test your extension.
Loading the Extension
- Open Chrome and navigate to
chrome://extensions/ - Enable “Developer mode” in the top right corner
- Click “Load unpacked” and select your extension directory
- Open a new tab and press F12 to open DevTools
- Look for your custom panel in the DevTools tab bar
Debugging Tips
Use the DevTools console to debug your panel:
// In your panel.js
console.log("Panel loaded");
// Access the inspected window console
chrome.devtools.inspectedWindow.eval(
"console.log('From panel to page')"
);
Check the background service worker console at chrome://extensions/ by clicking “service worker” under your extension.
Publishing Your Extension
Once your DevTools panel extension is working, you can publish it to the Chrome Web Store.
Preparation
- Create icon files in the
icons/folder (16, 32, 48, and 128 pixels) - Test thoroughly across different pages and scenarios
- Create a detailed description for the store listing
- Prepare screenshots and a promotional image
Publishing Steps
- Zip your extension directory (excluding development files)
- Go to the Chrome Web Store Developer Dashboard
- Create a new item and upload your zip file
- Fill in the store listing details
- Submit for review
Conclusion
Congratulations! You have successfully built a custom Chrome DevTools panel extension from scratch. Throughout this tutorial, we covered:
- The fundamentals of DevTools panel extensions and their architecture
- Setting up the project structure with proper manifest configuration
- Creating the DevTools page and registering your panel
- Building an interactive panel UI with HTML and CSS
- Implementing functionality to inspect and interact with the inspected page
- Advanced features like sidebar panes and theme detection
- Testing and debugging techniques
- Publishing your extension to the Chrome Web Store
DevTools panel extensions open up incredible possibilities for enhancing the development workflow. Whether you are building tools for your own use, your team, or distributing to millions of developers worldwide, the techniques you have learned here provide a solid foundation.
Continue experimenting with the Chrome Extensions APIs, explore the DevTools protocol, and consider what unique tools you can build to make web development even more productive and enjoyable.