Get URL Query String Parameters in JavaScript – URLSearchParams Example

Pulling one value from a URL looks easy, until the URL contains encoded characters, empty values or the same parameter more than once.

This small tool accepts a complete URL or just a query string, then displays every parameter and its decoded value. You can copy individual values or export the whole result as JSON.

It uses the native URL and URLSearchParams APIs. No library and no regular expression needed.

This is part of Copy, Run & Customize, a series of small projects you can open, change and use in your own work.

Copy the three blocks below, then open the TeknoFenomen Code Playground to run and edit it.

HTML

<main class="query-page">
  <section class="query-card">
    <header class="tool-header">
      <span class="eyebrow">URL TOOL</span>
      <h1>Query String Parser</h1>
      <p>
        Paste a URL or query string to see its parameters,
        decoded values and JSON output.
      </p>
    </header>

    <div class="url-field">
      <label for="urlInput">URL or query string</label>

      <div class="input-row">
        <input
          id="urlInput"
          type="text"
          placeholder="https://example.com/search?q=javascript&page=2"
          spellcheck="false"
          autocomplete="off"
        >

        <button id="parseButton" class="primary-button" type="button">
          Parse URL
        </button>
      </div>
    </div>

    <div class="secondary-actions">
      <button id="exampleButton" type="button">
        Load example
      </button>

      <button id="clearButton" type="button">
        Clear
      </button>
    </div>

    <div
      id="statusBox"
      class="status-box neutral"
      role="status"
      aria-live="polite"
    >
      <span class="status-dot"></span>

      <div>
        <strong id="statusTitle">Ready</strong>
        <p id="statusMessage">
          Enter a URL to view its query parameters.
        </p>
      </div>
    </div>

    <section id="resultsSection" class="results-section" hidden>
      <div class="results-heading">
        <h2>
          Parameters
          <span id="parameterCount">0</span>
        </h2>

        <button id="copyJsonButton" type="button">
          Copy as JSON
        </button>
      </div>

      <div id="parameterList" class="parameter-list"></div>

      <div class="json-output">
        <div class="json-heading">
          <strong>JSON output</strong>
          <span>Repeated keys become arrays</span>
        </div>

        <pre><code id="jsonOutput"></code></pre>
      </div>
    </section>
  </section>
</main>

CSS

:root {
  color-scheme: dark;
  font-family:
    Inter, ui-sans-serif, system-ui, -apple-system,
    BlinkMacSystemFont, "Segoe UI", sans-serif;

  --background: #080b12;
  --panel: #111722;
  --panel-light: #182131;
  --input: #090e17;
  --border: #2b3547;
  --text: #edf1f7;
  --muted: #95a1b5;
  --accent: #6d62ff;
  --accent-hover: #857cff;
  --success: #42d890;
  --danger: #ff6c7b;
}

* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  margin: 0;
  background:
    radial-gradient(circle at top, #1a2440 0, transparent 40%),
    var(--background);
  color: var(--text);
}

button,
input {
  font: inherit;
}

button {
  padding: 10px 15px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel-light);
  color: var(--text);
  cursor: pointer;
  transition:
    border-color 0.2s,
    background 0.2s,
    transform 0.15s;
}

button:hover {
  border-color: #59677e;
  background: #222d40;
}

button:active {
  transform: translateY(1px);
}

.query-page {
  width: min(860px, calc(100% - 30px));
  margin: 0 auto;
  padding: 50px 0;
}

.query-card {
  padding: 28px;
  border: 1px solid var(--border);
  border-radius: 20px;
  background: rgba(17, 23, 34, 0.96);
  box-shadow: 0 25px 70px rgba(0, 0, 0, 0.35);
}

.tool-header {
  max-width: 650px;
  margin-bottom: 25px;
}

.eyebrow {
  color: #aaa2ff;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.16em;
}

h1 {
  margin: 8px 0 10px;
  font-size: clamp(1.8rem, 6vw, 2.7rem);
  line-height: 1.1;
}

.tool-header p {
  margin: 0;
  color: var(--muted);
  line-height: 1.65;
}

.url-field label {
  display: block;
  margin-bottom: 8px;
  font-size: 0.88rem;
  font-weight: 700;
}

.input-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
}

input {
  width: 100%;
  min-width: 0;
  padding: 13px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  outline: none;
  background: var(--input);
  color: var(--text);
  font-family:
    "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 0.87rem;
}

input:focus {
  border-color: var(--accent);
}

input::placeholder {
  color: #5e6a7d;
}

.primary-button {
  min-width: 115px;
  border-color: var(--accent);
  background: var(--accent);
  color: white;
  font-weight: 750;
}

.primary-button:hover {
  border-color: var(--accent-hover);
  background: var(--accent-hover);
}

.secondary-actions {
  display: flex;
  gap: 9px;
  margin-top: 11px;
}

.secondary-actions button {
  padding: 8px 13px;
  font-size: 0.84rem;
}

.status-box {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-top: 18px;
  padding: 14px 15px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: #0c121d;
}

.status-dot {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  margin-top: 5px;
  border-radius: 50%;
  background: var(--muted);
}

.status-box strong {
  display: block;
  margin-bottom: 3px;
}

.status-box p {
  margin: 0;
  color: var(--muted);
  font-size: 0.88rem;
  line-height: 1.5;
}

.status-box.success {
  border-color: rgba(66, 216, 144, 0.45);
}

.status-box.success .status-dot {
  background: var(--success);
  box-shadow: 0 0 12px rgba(66, 216, 144, 0.6);
}

.status-box.error {
  border-color: rgba(255, 108, 123, 0.5);
}

.status-box.error .status-dot {
  background: var(--danger);
  box-shadow: 0 0 12px rgba(255, 108, 123, 0.55);
}

.results-section {
  margin-top: 27px;
}

.results-heading,
.json-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 15px;
}

.results-heading {
  margin-bottom: 13px;
}

.results-heading h2 {
  margin: 0;
  font-size: 1rem;
}

.results-heading h2 span {
  display: inline-grid;
  min-width: 25px;
  height: 25px;
  place-items: center;
  margin-left: 6px;
  border-radius: 20px;
  background: var(--panel-light);
  color: var(--muted);
  font-size: 0.78rem;
}

.results-heading button {
  padding: 8px 13px;
  font-size: 0.84rem;
}

.parameter-list {
  display: grid;
  gap: 10px;
}

.parameter-row {
  display: grid;
  grid-template-columns: minmax(120px, 0.7fr) minmax(0, 1.5fr) auto;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--input);
}

.parameter-name,
.parameter-value {
  overflow-wrap: anywhere;
  font-family:
    "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 0.85rem;
}

.parameter-name {
  color: #aaa2ff;
  font-weight: 700;
}

.parameter-value {
  color: #dbe6f7;
}

.parameter-row button {
  padding: 7px 11px;
  font-size: 0.78rem;
}

.json-output {
  overflow: hidden;
  margin-top: 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--input);
}

.json-heading {
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--panel-light);
}

.json-heading span {
  color: var(--muted);
  font-size: 0.76rem;
}

pre {
  max-height: 330px;
  overflow: auto;
  margin: 0;
  padding: 16px;
}

code {
  color: #dbe6f7;
  font-family:
    "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 0.87rem;
  line-height: 1.65;
}

@media (max-width: 620px) {
  .query-page {
    width: min(100% - 20px, 860px);
    padding: 20px 0;
  }

  .query-card {
    padding: 18px;
  }

  .input-row {
    grid-template-columns: 1fr;
  }

  .primary-button {
    width: 100%;
  }

  .parameter-row {
    grid-template-columns: 1fr auto;
  }

  .parameter-value {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .parameter-row button {
    grid-column: 2;
    grid-row: 1;
  }

  .json-heading {
    align-items: flex-start;
    flex-direction: column;
    gap: 4px;
  }
}

JavaScript

const urlInput = document.querySelector("#urlInput");
const parseButton = document.querySelector("#parseButton");
const exampleButton = document.querySelector("#exampleButton");
const clearButton = document.querySelector("#clearButton");

const statusBox = document.querySelector("#statusBox");
const statusTitle = document.querySelector("#statusTitle");
const statusMessage = document.querySelector("#statusMessage");

const resultsSection = document.querySelector("#resultsSection");
const parameterList = document.querySelector("#parameterList");
const parameterCount = document.querySelector("#parameterCount");
const jsonOutput = document.querySelector("#jsonOutput");
const copyJsonButton = document.querySelector("#copyJsonButton");

const exampleUrl =
  "https://example.com/products?category=phones&sort=price%20low&page=2&tag=new&tag=sale";

let currentJson = "";

function setStatus(type, title, message) {
  statusBox.className = `status-box ${type}`;
  statusTitle.textContent = title;
  statusMessage.textContent = message;
}

function buildUrl(input) {
  const value = input.trim();

  if (!value) {
    throw new Error("Enter a URL or query string first.");
  }

  if (value.startsWith("?")) {
    return new URL(`https://example.com/${value}`);
  }

  if (!value.includes("?") && !value.includes("://")) {
    return new URL(`https://example.com/?${value}`);
  }

  return new URL(value, "https://example.com");
}

function parametersToObject(searchParams) {
  const output = Object.create(null);

  for (const [key, value] of searchParams.entries()) {
    const alreadyExists =
      Object.prototype.hasOwnProperty.call(output, key);

    if (!alreadyExists) {
      output[key] = value;
      continue;
    }

    if (Array.isArray(output[key])) {
      output[key].push(value);
    } else {
      output[key] = [output[key], value];
    }
  }

  return output;
}

function fallbackCopy(text) {
  const temporaryInput = document.createElement("textarea");

  temporaryInput.value = text;
  temporaryInput.setAttribute("readonly", "");
  temporaryInput.style.position = "fixed";
  temporaryInput.style.left = "-9999px";
  temporaryInput.style.opacity = "0";

  document.body.appendChild(temporaryInput);
  temporaryInput.select();

  const copied = document.execCommand("copy");

  temporaryInput.remove();

  return copied;
}

async function copyText(text) {
  if (navigator.clipboard && window.isSecureContext) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch {
      return fallbackCopy(text);
    }
  }

  return fallbackCopy(text);
}

function createParameterRow(name, value) {
  const row = document.createElement("div");
  row.className = "parameter-row";

  const nameElement = document.createElement("span");
  nameElement.className = "parameter-name";
  nameElement.textContent = name || "(empty key)";

  const valueElement = document.createElement("span");
  valueElement.className = "parameter-value";
  valueElement.textContent = value || "(empty value)";

  const copyButton = document.createElement("button");
  copyButton.type = "button";
  copyButton.textContent = "Copy";

  copyButton.addEventListener("click", async () => {
    const copied = await copyText(value);

    if (copied) {
      copyButton.textContent = "Copied!";

      setTimeout(() => {
        copyButton.textContent = "Copy";
      }, 1400);
    } else {
      setStatus(
        "error",
        "Copy failed",
        "Your browser blocked clipboard access."
      );
    }
  });

  row.append(nameElement, valueElement, copyButton);

  return row;
}

function parseUrl() {
  try {
    const url = buildUrl(urlInput.value);
    const entries = Array.from(url.searchParams.entries());

    if (entries.length === 0) {
      resultsSection.hidden = true;

      setStatus(
        "error",
        "No parameters found",
        "The URL does not contain a query string."
      );

      return;
    }

    parameterList.innerHTML = "";

    entries.forEach(([name, value]) => {
      parameterList.append(
        createParameterRow(name, value)
      );
    });

    const parameterObject =
      parametersToObject(url.searchParams);

    currentJson = JSON.stringify(parameterObject, null, 2);
    jsonOutput.textContent = currentJson;

    parameterCount.textContent = entries.length;
    resultsSection.hidden = false;

    const uniqueCount =
      new Set(entries.map(([name]) => name)).size;

    setStatus(
      "success",
      "Parameters found",
      `${entries.length} values across ${uniqueCount} unique keys.`
    );
  } catch (error) {
    resultsSection.hidden = true;

    setStatus(
      "error",
      "Could not parse that",
      error.message
    );
  }
}

parseButton.addEventListener("click", parseUrl);

urlInput.addEventListener("keydown", (event) => {
  if (event.key === "Enter") {
    parseUrl();
  }
});

exampleButton.addEventListener("click", () => {
  urlInput.value = exampleUrl;
  parseUrl();
});

clearButton.addEventListener("click", () => {
  urlInput.value = "";
  currentJson = "";
  parameterList.innerHTML = "";
  jsonOutput.textContent = "";
  resultsSection.hidden = true;

  setStatus(
    "neutral",
    "Ready",
    "Enter a URL to view its query parameters."
  );

  urlInput.focus();
});

copyJsonButton.addEventListener("click", async () => {
  const copied = await copyText(currentJson);

  if (copied) {
    copyJsonButton.textContent = "Copied!";

    setStatus(
      "success",
      "JSON copied",
      "The parameter object is now in your clipboard."
    );

    setTimeout(() => {
      copyJsonButton.textContent = "Copy as JSON";
    }, 1500);
  } else {
    setStatus(
      "error",
      "Copy failed",
      "Your browser blocked clipboard access."
    );
  }
});

A couple of useful details

URLSearchParams automatically handles encoded characters:

const params = new URLSearchParams("?search=red%20shoes");

console.log(params.get("search"));
// red shoes

To get a single value:

const url = new URL(window.location.href);
const page = url.searchParams.get("page");

To check whether a parameter exists:

const hasSearch = url.searchParams.has("search");

A URL can also contain the same key more than once:

?tag=javascript&tag=html&tag=css

In that case, use getAll():

const tags = url.searchParams.getAll("tag");

The tool keeps repeated values instead of silently throwing the earlier ones away. When copied as JSON, those values are placed inside an array.