A copied URL can contain several pieces of information packed into one string: its scheme, hostname, path, query, and fragment. A small Chrome extension can make those parts easier to understand, but it should handle pasted links carefully and request only the browser access it needs.
This tutorial builds a Manifest V3 extension that lets a user paste an absolute HTTP or HTTPS URL and inspect its components. It uses the browser's built-in URL API, does not fetch or navigate to the pasted address, and needs no special permissions.
Key takeaways
- Use
new URL(value)to parse an absolute URL and catch errors for inputs the browser cannot parse. - Read components such as
protocol,hostname,pathname,search, andhashfrom the resulting object. - A parser explains a URL's structure; it does not determine whether a destination is trustworthy.
- A popup that only processes text entered by the user does not need clipboard or host permissions.
- Render pasted values with
textContent, and treat query strings as potentially sensitive.
Understand the URL components
Consider this example:
https://docs.example.test:8443/guide/setup?mode=quick#install
| Component | Value | Meaning |
|---|---|---|
| Scheme | https: | The URL's protocol identifier. |
| Hostname | docs.example.test | The host name, without the port. |
| Port | 8443 | An explicitly specified port, if present. |
| Path | /guide/setup | The location within the host. |
| Query | ?mode=quick | Parameters following ?, if present. |
| Fragment | #install | A reference following #, if present. |
The URL API exposes these as properties. For example, url.protocol includes the colon, while url.search and url.hash include their leading ? and #. A missing query or fragment is represented by an empty string. The API parses and normalizes URLs; it does not establish that a URL is safe to visit.
If you want to inspect a URL without building the extension first, you can also inspect URL components.
Set up a minimal Manifest V3 extension
Create a folder with these files:
url-component-explainer/
├── manifest.json
├── popup.html
├── popup.css
└── popup.js
The manifest declares a popup and no permissions:
{
"manifest_version": 3,
"name": "URL Component Explainer",
"version": "1.0.0",
"description": "Inspect the components of a pasted web address.",
"action": {
"default_popup": "popup.html"
}
}
This extension needs no permissions or host_permissions entries. It does not read the clipboard automatically, inject scripts into pages, or make network requests. Avoid adding permissions unless you later implement a feature that genuinely requires them.
Build the popup interface
Add a labeled text area, a submit button, an error message, and a place to show the parsed components. The extension uses separate JavaScript and CSS files rather than inline code.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>URL Component Explainer</title>
<link rel="stylesheet" href="popup.css">
<script src="popup.js" defer></script>
</head>
<body>
<main>
<h1>Explain a URL</h1>
<form id="url-form">
<label for="url-input">Paste an HTTP or HTTPS URL</label>
<textarea id="url-input" rows="3" required spellcheck="false"></textarea>
<button type="submit">Parse URL</button>
</form>
<p id="error" role="alert"></p>
<section id="results" aria-labelledby="results-heading" hidden>
<h2 id="results-heading">Components</h2>
<dl id="components"></dl>
</section>
</main>
</body>
</html>
A simple stylesheet keeps the popup readable. Chrome controls the popup window's size, so keep the layout compact.
body {
box-sizing: border-box;
width: 360px;
margin: 0;
padding: 16px;
color: #202124;
font: 14px/1.5 system-ui, sans-serif;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
textarea {
display: block;
width: 100%;
margin: 6px 0 10px;
resize: vertical;
}
button {
padding: 7px 12px;
}
#error {
color: #b3261e;
}
dt {
margin-top: 8px;
font-weight: 700;
}
dd {
margin-left: 0;
overflow-wrap: anywhere;
}
Parse and display the URL
The form handler below requires an absolute HTTP or HTTPS URL. It rejects leading or trailing whitespace rather than silently changing the pasted value. It also declines URLs containing username or password fields, which can otherwise expose credentials in a copied link. The results are created as text nodes, not interpreted as HTML.
const form = document.querySelector("#url-form");
const input = document.querySelector("#url-input");
const error = document.querySelector("#error");
const results = document.querySelector("#results");
const components = document.querySelector("#components");
form.addEventListener("submit", (event) => {
event.preventDefault();
error.textContent = "";
results.hidden = true;
components.replaceChildren();
const value = input.value;
if (!value.trim()) {
error.textContent = "Paste a URL to get started.";
return;
}
if (value !== value.trim()) {
error.textContent = "Remove leading or trailing whitespace, then try again.";
return;
}
let url;
try {
// No base is supplied, so relative URLs are rejected.
url = new URL(value);
} catch {
error.textContent = "That is not a valid absolute URL.";
return;
}
if (url.protocol !== "http:" && url.protocol !== "https:") {
error.textContent = "This example accepts only HTTP and HTTPS URLs.";
return;
}
if (url.username || url.password) {
error.textContent = "For safety, this example does not display URLs containing credentials.";
return;
}
const fields = [
["Scheme", url.protocol],
["Hostname", url.hostname],
["Port", url.port || "(not specified)"],
["Path", url.pathname],
["Query", url.search || "(none)"],
["Fragment", url.hash || "(none)"],
["Origin", url.origin]
];
for (const [label, value] of fields) {
const term = document.createElement("dt");
const description = document.createElement("dd");
term.textContent = label;
description.textContent = value;
components.append(term, description);
}
results.hidden = false;
});
The hostname property excludes the port. The port is an empty string when it is not explicitly present, so the example displays a friendly label instead. The path for a URL with a host and no explicit path is generally /. The API may normalize parts of a URL, so the displayed components need not reproduce the original spelling character for character.
Load and test the extension
- Save the four files in the same folder.
- Open Chrome's Extensions page and enable Developer mode.
- Choose the option to load an unpacked extension and select the folder.
- Open the extension popup, paste a URL such as
https://docs.example.test:8443/guide/setup?mode=quick#install, and select Parse URL. - Try a malformed value, a relative path, a non-HTTP scheme, and a URL with leading whitespace. Confirm that the extension shows a helpful error rather than stale results.
A URL parser can accept some forms that your own product may not want to support. This demo intentionally applies an HTTP/HTTPS scheme policy after parsing. That check is a product decision, not a general safety guarantee.
Safe handling of copied URLs
The user pastes text explicitly into the popup; the extension does not request clipboard access. This keeps permissions minimal and avoids reading clipboard contents without a direct user action. The extension also does not send the URL anywhere or open it.
Even so, pasted links can contain sensitive information. Query strings sometimes carry tokens, identifiers, or other private data, and fragments can also hold application state. Keep processing local, do not log or persist full URLs, and make clear that the displayed values remain visible in the popup. This example avoids displaying URL username and password fields, but it still displays the query and fragment because explaining those components is its purpose.
Use textContent when showing user-supplied values. Do not insert a pasted URL into the page with innerHTML. If a future version adds navigation, URL fetching, analytics, or automatic clipboard access, reassess its permissions and security boundaries separately. Parsing alone does not make a destination trustworthy, and a client-side parser is not a substitute for server-side controls when a server fetches user-provided URLs.
Common mistakes
- Passing a relative path to
new URL()without a base. This tutorial expects an absolute URL. If your application needs relative references, provide a deliberate base URL and explain how it affects parsing. - Treating a successful parse as proof of safety. Parsing answers structural questions; it does not establish ownership, reputation, or safe behavior.
- Confusing hostname and origin. A hostname does not include the scheme or port. An origin combines scheme, hostname, and an applicable port.
- Assuming every query is a simple key-value map. Query strings can contain repeated keys and encoding details. If you need to interpret parameters, use
URLSearchParamsand account for duplicate values. - Using broad permissions for a text-only popup. Clipboard, tab, host, and scripting permissions are unnecessary for this implementation.
- Rendering pasted input as HTML. Treat all pasted content as text, even when it appears to be a URL.
Conclusion
A small Chrome extension can make URL structure clear with the built-in URL API and a Manifest V3 popup. Validate the input your feature intends to support, display components with safe DOM APIs, and request no permissions that the implementation does not need. Most importantly, explain that parsing describes a URL—it does not certify that the URL is safe.
