The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Resolve against a baseconst url = new URL('../guide', document.baseURI)View examples
Parse without throwingconst url = URL.parse(input, location.href)View examples
Validate a destinationif (url.origin !== location.origin || url.protocol !== 'https:') throw new Error('Blocked')View examples
Set one query valueurl.searchParams.set('page', '2')View examples
Append a repeated valueurl.searchParams.append('tag', 'html')View examples
Read repeated valuesconst tags = url.searchParams.getAll('tag')View examples
Navigate to a fragmentlocation.hash = 'installation'View examples
Observe fragment traversaladdEventListener('hashchange', event => render(location.hash))View examples
Add application historyhistory.pushState({ page: 2 }, '', '?page=2')View examples
Replace the current entryhistory.replaceState({ page: 1 }, '', '?page=1')View examples
Restore traversed stateaddEventListener('popstate', event => render(event.state))View examples
Intercept supported navigationevent.intercept({ handler: () => render(new URL(event.destination.url)) })View examples
Navigate with the new APIawait navigation.navigate('/account').finishedView examples

URLs are application state that users bookmark, share, reload, and traverse with browser controls. Parse them with the platform URL model, mutate query tuples rather than concatenating strings, and reserve fragments for addressable document state. When an application updates session history, it must also restore the matching interface during traversal. The newer Navigation API can centralize same-document routing where supported, but ordinary links and server-rendered destinations remain the dependable foundation.

Step by step

Detailed examples

01

Parse, resolve, and validate URLs as structured data

new URL(input, base) resolves relative references and throws TypeError for invalid input; URL.parse(input, base) is the newer non-throwing alternative and returns null, so feature-detect it when older engines matter. Inspect protocol, origin, hostname, port, pathname, search, and hash instead of testing raw strings. Parsing is not authorization: validate the exact schemes and origins your operation permits, reject embedded credentials, and never turn an untrusted next or redirect parameter into navigation without an allowlist. Visually confusable internationalized hosts are another reason to compare parsed origins rather than displayed substrings.

Resolve input and enforce a same-origin HTTPS policy
function safeDestination(input) {
  let url;
  try {
    url = new URL(input, document.baseURI);
  } catch {
    return null;
  }

  if (url.protocol !== 'https:' || url.origin !== location.origin) return null;
  if (url.username || url.password) return null;
  return url;
}

const destination = safeDestination('/account?tab=security');
if (destination) console.log(destination.pathname);
Back to quick reference ↑
02

Treat a query as an ordered list of name-value tuples

URLSearchParams handles encoding and supports repeated names. set replaces all tuples with that name, append adds one, get returns the first, getAll returns every value, and delete removes matches. A URL object's searchParams is live: mutations update its serialized URL. The serializer follows application/x-www-form-urlencoded rules, encoding spaces as plus signs, and can change equivalent spelling such as percent escapes or tilde encoding; do not use the resulting href as a cryptographic signature unless you control canonicalization. Constructing URLSearchParams from a full URL string does not parse the URL—pass url.search or use new URL first.

Update pagination while preserving repeated filters
const url = new URL(location.href);
url.searchParams.set('page', '2');
url.searchParams.delete('debug');
url.searchParams.append('tag', 'html');
url.searchParams.append('tag', 'accessibility');

console.log(url.searchParams.getAll('tag'));
console.log(url.href);
history.replaceState(history.state, '', url);
Back to quick reference ↑
03

Use fragments for addressable document state

A URL fragment identifies a location or state within the current resource and is not sent in an HTTP request. Normal anchor links such as href=#details provide shareable, keyboard-friendly navigation without script. Assigning location.hash normally creates a session-history entry; location.replace('#details') replaces instead. hashchange fires when the active entry's fragment changes through fragment navigation or traversal, but not when pushState or replaceState changes a URL. Avoid putting secrets in fragments: they remain visible, copyable, available to page scripts, and may reach analytics or downstream applications.

Synchronize a tab-like view with real fragment links
<nav aria-label="Account sections">
  <a href="#profile">Profile</a>
  <a href="#security">Security</a>
</nav>
<section id="profile"><h2>Profile</h2></section>
<section id="security"><h2>Security</h2></section>
<script>
  addEventListener('hashchange', () => {
    console.log('Active fragment:', location.hash.slice(1));
  });
</script>
Back to quick reference ↑
04

Keep session history and rendered state synchronized

pushState adds an entry and replaceState updates the active entry; neither loads the URL nor dispatches popstate at call time. Their state is structured-cloned, must be serializable, and should remain small because user agents may impose limits. The URL must be one the current document is allowed to rewrite, which in ordinary HTTP(S) pages means staying on the same origin. popstate is for traversal such as Back, Forward, or history.go(), and event.state is the newly active entry's state. Store durable identifiers in history and re-fetch large or sensitive data; always be able to rebuild from the URL because state may be null after initial load or external navigation.

Push filter state and restore it on traversal
function showCatalog(url, state = {}) {
  const category = url.searchParams.get('category') ?? 'all';
  document.querySelector('#view').textContent = `Category: ${category}`;
}

function chooseCategory(category) {
  const url = new URL(location.href);
  url.searchParams.set('category', category);
  history.pushState({ category }, '', url);
  showCatalog(url, history.state);
}

addEventListener('popstate', event => {
  showCatalog(new URL(location.href), event.state ?? {});
});
showCatalog(new URL(location.href), history.state ?? {});
Back to quick reference ↑
05

Progressively enhance routing with the Navigation API

The Navigation API exposes the current entry, entries, programmatic navigation, and a single navigate event that covers link activation, form submission, traversal, reload, and script-driven navigation. It is not yet a universal baseline, so keep real anchors and server routes functional. Intercept only when event.canIntercept is true and the destination matches your routing policy; let downloads, cross-origin destinations, and fragment-only navigation retain browser behavior. An intercept handler can render asynchronously, report failure through the navigation result, and use focusReset or scroll options deliberately. Never evaluate HTML or code obtained from a destination URL, and sanitize any fetched content before insertion.

Enhance same-origin page links without breaking fallback navigation
if ('navigation' in window) {
  navigation.addEventListener('navigate', event => {
    const url = new URL(event.destination.url);
    if (!event.canIntercept || event.hashChange || event.downloadRequest !== null) return;
    if (url.origin !== location.origin || !url.pathname.startsWith('/docs/')) return;

    event.intercept({
      async handler() {
        const response = await fetch(url, { headers: { Accept: 'text/html' } });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const html = await response.text();
        document.querySelector('#view').textContent = html;
      }
    });
  });
}

Note: textContent makes this minimal example safe but displays markup literally. A production router should fetch structured data or parse and sanitize a trusted HTML fragment before rendering it.

Back to quick reference ↑

Local code tester

Try URL state and history traversal

Change filters, inspect the generated query, and use Back and Forward to verify that the rendered view follows session history.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. WHATWGURL Standard — API and URLSearchParamsurl.spec.whatwg.org
  2. WHATWGHTML Standard — Session history and navigation APIshtml.spec.whatwg.org
  3. WHATWGHTML Standard — The Navigation APIhtml.spec.whatwg.org
  4. MDN Web DocsURL APIdeveloper.mozilla.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback