history. pushState()
Adds a new entry to the browser history and changes the address bar without reloading the page.
- Returns
undefined
Example
#function showLesson(number) {
document.querySelector('#title').textContent = `Lesson ${number}`
}
function goToLesson(number) {
showLesson(number)
history.pushState({ lesson: number }, '', `/courses/javascript/lesson-${number}`)
}
goToLesson(6)
console.log(location.pathname)
console.log(history.state)/courses/javascript/lesson-6
{ lesson: 6 }In the isolated preview frame, changing the address is blocked (SecurityError), so this example doesn’t run here. On a regular page, the address bar changes to /courses/javascript/lesson-6, and the page doesn’t reload.
Definition and usage
#The history.pushState() method changes the address shown in the browser’s address bar and adds a new entry to the tab’s history, but doesn’t reload the page or fetch anything from the server. Single-page applications (SPAs) rely on this: you change the content with JavaScript, and the address keeps up with what the user sees, so it can be copied, bookmarked, or refreshed.
The first argument is the data (state) tied to the entry, e.g. { lesson: 5 }. It must be cloneable, the same way structuredClone() works, so functions and DOM elements are out. Browsers ignore the second argument, but you still need to pass it, usually as an empty string. The third is the new address: it can be relative, but must belong to the same origin, or the method throws.
When the user clicks “Back” or “Forward”, the browser fires a popstate event on window and passes the data saved with the entry in event.state. Your job then is to show the matching content. pushState() itself does not trigger a popstate event.
The sibling method history.replaceState() takes the same arguments but replaces the current entry instead of adding a new one. It’s handy for things like filter changes that shouldn’t fill up the history. Keep in mind that after a page refresh the browser asks the server for the new address, so the server has to be able to handle it.
Syntax
#history.pushState(state, unused)
history.pushState(state, unused, url)
history.replaceState(state, unused, url)
history.state
window.addEventListener("popstate", listener)Parameters
#state
any cloneable value
The data saved with the history entry. It comes back inevent.stateof thepopstateevent, and inhistory.state.unused
string
A parameter kept for historical reasons. Pass an empty string"".url
optional, string or URL
The new address, relative or absolute, from the same origin as the page. Without it, the address doesn’t change.
More examples
#const title = document.querySelector('#title')
function render(lesson) {
title.textContent = `Lesson ${lesson}`
console.log(`Showing lesson ${lesson}`)
}
window.addEventListener('popstate', (event) => {
render(event.state?.lesson ?? 1)
})
history.pushState({ lesson: 6 }, '', '/courses/javascript/lesson-6')
render(6)
history.pushState({ lesson: 7 }, '', '/courses/javascript/lesson-7')
render(7)
history.back()Showing lesson 6 Showing lesson 7 Showing lesson 6
history.back() acts like clicking “Back”: the browser goes back to the previous entry and fires a popstate event with the saved state { lesson: 6 }. The page doesn’t reload, and the render() function redraws the content.
function applyFilter(level) {
const url = new URL(location.href)
url.searchParams.set('level', level)
history.replaceState(null, '', url)
console.log(location.search)
}
applyFilter('beginner')
applyFilter('intermediate')?level=beginner ?level=intermediate
Each call replaces the current history entry, so clicking “Back” doesn’t step back through the filters one by one; it goes straight to the previous page. The result assumes a page that started out with no parameters in its address.
Browser support
#Widely available
Works in all modern browsers, phones included. You can use it without worry.
- Chrome
- Edge
- Firefox
- Safari
Good practices
#- After
pushState(), always handle thepopstateevent. Without it, the “Back” button changes the address, but the page content stays the old one. - Save only simple data in the state, such as a lesson id. Functions and DOM elements can’t be cloned and will cause a
DataCloneError. - Make sure the server handles the addresses created by
pushState(). After a page refresh or opening a bookmark, the browser will ask the server for them.
Related entries
#- locationAn object with information about the current page’s address. Lets you read its parts and navigate to a different address.
- URLAn object for creating, reading, and changing URLs. It breaks an address into parts: protocol, host, path, and parameters.
- URLSearchParamsAn object for reading and building query parameters in an address, the part after the ? character, e.g. ?course=js&page=2.
- EventTarget.addEventListener()Registers a function that’s called every time a given event occurs on an element, for example a click.
- structuredClone()Creates a deep copy of a value, including nested objects, dates, Maps, and Sets.
Spotted a mistake or missing an example? Write to us.