---
area: "Learn"
parent: "History chapter"
url: "https://unpoly.com/learn/updating-history"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [History chapter](https://unpoly.com/learn/history.md)</nav>

# Updating history

When Unpoly updates a page's main fragment, it also updates the browser's address bar, the window title and the meta tags in the `<head>`. The result looks and behaves like a full page load: the user can bookmark the URL, share it, or return to it with the Back button. Updates to minor fragments leave history untouched.

<nav aria-label="Contents">

#### Contents

- [When history is updated](#when-history-is-changed)
- [What is updated](#history-state)
- [Turning off history aspects](#disabling)
- [Changing history from JavaScript](#history-api)
- [Observing changes](#observing)

</nav>

## When history is updated {#when-history-is-changed}

Following a link or submitting a form counts as [navigation](https://unpoly.com/learn/navigation-defaults.md). When navigation updates the layer's [main fragment](https://unpoly.com/api/main.md), Unpoly adds a history entry for the new URL:

```html
<a href="/posts/5" up-follow>Read post</a>
```

Clicking the link fetches `/posts/5` and swaps the main fragment. The address bar now shows `/posts/5`, and the window title is taken from the response's `<title>`.

A link that updates a smaller fragment does not change history:

```html
<a href="/comments?page=2" up-target="#comments">Next page</a>
```

Clicking this link replaces only the `#comments` element. The address bar keeps its URL, so the user does not collect a history entry for every minor update.

This is the `auto` setting of the [`[up-history]`](https://unpoly.com/api/up-follow.md#up-history) attribute, which is the default for links and forms. History is updated when the targeted fragment contains an element matching [`up.fragment.config.autoHistoryTargets`](https://unpoly.com/api/up.fragment.config.md#config.autoHistoryTargets). By default that is the layer's main fragment. When the new URL is the one already in the address bar, no entry is added.

> #### Note
>
> An overlay only shows its URL in the address bar when it has [visible history](https://unpoly.com/learn/history-in-overlays.md). This page describes the root layer, which always has visible history.

### Forcing or preventing a change {#forcing}

To update history even when a minor fragment is updated, set `[up-history=true]`:

```html
<a href="/comments?page=2" up-target="#comments" up-history="true">Next page</a>
```

To never update history, set `[up-history=false]`:

```html
<a href="/posts/5" up-follow up-history="false">Peek at post</a>
```

Forms accept the same [`[up-history]`](https://unpoly.com/api/up-submit.md#up-history) attribute. When rendering from JavaScript, pass a [`{ history }`](https://unpoly.com/api/up.render.md#options.history) option instead.

To change the default for all links and forms, configure `up.fragment.config.navigateOptions.history`. To decide per link, listen to [`up:link:follow`](https://unpoly.com/api/up:link:follow.md) or [`up:form:submit`](https://unpoly.com/api/up:form:submit.md) and set `event.renderOptions.history`:

```js
up.on('up:link:follow', function(event, link) {
  if (link.matches('.pagination a')) {
    event.renderOptions.history = false
  }
})
```

To stop Unpoly from ever changing history, or only some aspects of it, see [Turning off history aspects](#disabling).

### Treating other fragments as major {#auto-history-targets}

If your app has a significant element that is not the main fragment, you can add its selector to [`up.fragment.config.autoHistoryTargets`](https://unpoly.com/api/up.fragment.config.md#config.autoHistoryTargets):

```js
up.fragment.config.autoHistoryTargets.push('.content')
```

Now a link targeting `.content`, or any fragment that contains a `.content` element, updates history without an explicit `[up-history=true]`.

### Only `GET` requests update history {#get-requests}

Only a `GET` request can be reloaded or restored by the browser, so only a `GET` response updates history. A response to a `POST`, `PUT` or `PATCH` request never changes history, even with `[up-history=true]`.

When a successful submission redirects to a `GET` URL, the redirect's response updates history with the new URL. Unpoly detects the redirect from the response URL, or from an [`X-Up-Method`](https://unpoly.com/api/X-Up-Method.md) header that your server can set.

The exception is an explicit location: when you set [`[up-location]`](https://unpoly.com/api/up-follow.md#up-location) or pass a [`{ location }`](https://unpoly.com/api/up.render.md#options.location) option, Unpoly pushes that URL even after a non-`GET` request.

The same rules apply to error responses. When an error response to a `GET` request renders an error page into the main fragment, the address bar shows its URL, like after a full page load. A form submission that fails with a validation error does not change history. See [Handling error responses](https://unpoly.com/learn/error-responses.md).

### Rendering from JavaScript {#scripting}

The low-level [`up.render()`](https://unpoly.com/api/up.render.md) function never updates history by default:

```js
up.render({ target: '.content', url: '/path' }) // History is unchanged
```

Pass a `{ history }` option to opt in:

```js
up.render({ target: '.content', url: '/path', history: true })   // Always update
up.render({ target: '.content', url: '/path', history: 'auto' }) // Update if the main fragment is rendered
```

To render with all [navigation defaults](https://unpoly.com/learn/navigation-defaults.md#navigation-defaults), including `{ history: 'auto' }`, use [`up.navigate()`](https://unpoly.com/api/up.navigate.md) instead of [`up.render()`](https://unpoly.com/api/up.render.md).

## What is updated {#history-state}

A history update comprises the following:

- The URL shown in the browser's address bar.
- The document title shown as the browser's window title.
- Meta tags like `meta[name=description]` or `link[rel=canonical]`.
- The `[lang]` attribute of the root `<html>` element.
- [JSON-LD](https://json-ld.org/) annotations in the `<head>`.

In the document below, the highlighted nodes are updated when history changes, in addition to the location URL:

```html
<html lang="en">
  <head>
    <title>Our team · AcmeCorp</title>
    <link rel="canonical" href="https://example.com/team">
    <meta name="description" content="About the AcmeCorp team">
    <meta property="og:image" content="https://example.com/team.jpg">
    <script src="/assets/app.js"></script>
    <link rel="stylesheet" href="/assets/app.css">
    <script type="application/ld+json">...</script>
  </head>
  <body>
    ...
  </body>
</html>
```

The linked JavaScript and stylesheet are _not_ part of history state and will not be updated. See [Reacting to new deployments](https://unpoly.com/learn/handling-asset-changes.md) for strategies to detect new app deployments.

### Including and excluding meta tags {#meta-tags}

To update an additional `<head>` element during history changes, mark it with an [`[up-meta]`](https://unpoly.com/api/up-meta.md) attribute:

```html
<link rel="license" href="https://opensource.org/license/mit/" up-meta>
```

To keep an element that would be updated by default, set `[up-meta=false]`:

```html
<meta name="theme-color" content="#ffffff" up-meta="false">
```

To change the defaults for all pages, configure [`up.history.config.metaTagSelectors`](https://unpoly.com/api/up.history.config.md#config.metaTagSelectors) and [`up.history.config.noMetaTagSelectors`](https://unpoly.com/api/up.history.config.md#config.noMetaTagSelectors). Only elements in the `<head>` are ever considered.

### Updating only some of the state {#partial-updates}

You can keep aspects of the history state unchanged while the rest is updated. Each aspect has an attribute for links and forms, and a render option for JavaScript:

| Keep unchanged | Attribute | Render option |
| --- | --- | --- |
| URL | [`[up-location=false]`](https://unpoly.com/api/up-follow.md#up-location) | [`{ location: false }`](https://unpoly.com/api/up.render.md#options.location) |
| Title | [`[up-title=false]`](https://unpoly.com/api/up-follow.md#up-title) | [`{ title: false }`](https://unpoly.com/api/up.render.md#options.title) |
| Meta tags | [`[up-meta-tags=false]`](https://unpoly.com/api/up-follow.md#up-meta-tags) | [`{ metaTags: false }`](https://unpoly.com/api/up.render.md#options.metaTags) |
| `html[lang]` | [`[up-lang=false]`](https://unpoly.com/api/up-follow.md#up-lang) | [`{ lang: false }`](https://unpoly.com/api/up.render.md#options.lang) |

For example, this link updates the URL and title, but leaves the meta tags alone:

```html
<a href="/posts/5" up-follow up-meta-tags="false">Read post</a>
```

These options only take effect when a render pass [updates history](#when-history-is-changed) in the first place.

To keep an aspect unchanged for all render passes, see [Turning off history aspects](#disabling).

### Setting a different URL or title {#explicit-values}

Instead of the response's URL and `<title>`, you can set your own values. Give `[up-location]` or `[up-title]` a string instead of `false`:

```html
<a href="/posts/5?utm=newsletter" up-follow up-location="/posts/5" up-title="My post">Read post</a>
```

The server can do the same by responding with an [`X-Up-Location`](https://unpoly.com/api/X-Up-Location.md) or [`X-Up-Title`](https://unpoly.com/api/X-Up-Title.md) header. This is useful when you [optimize a response](https://unpoly.com/learn/optimizing-responses.md) to omit the layout, and the response no longer includes a `<title>`.

An explicit value cannot update an aspect that is [turned off](#disabling) in the configuration.

## Turning off history aspects {#disabling}

To stop Unpoly from ever changing history, set `up.history.config.enabled = false`. Fragments are still updated, but the address bar, window title, meta tags and `html[lang]` stay as they are.

To turn off only some aspects, set an array of the aspects that Unpoly may update. The aspects are `'location'`, `'title'`, `'metaTags'` and `'lang'`. For example, an app that keeps URLs out of the address bar and browser history for privacy can still let `html[lang]` follow the content:

```js
up.history.config.enabled = ['lang']
```

[Attributes and render options](#partial-updates) can keep more aspects unchanged for a single render pass, but they can never update an aspect that the configuration excludes.

Turning off the `'location'` aspect has some consequences:

- Unpoly adds no history entries, so there is nothing to [restore](https://unpoly.com/learn/restoring-history.md) when the user goes back.
- [`up.history.push()`](https://unpoly.com/api/up.history.push.md) and [`up.history.replace()`](https://unpoly.com/api/up.history.replace.md) do nothing.
- Links to a `#hash` still scroll to the matching fragment, but don't change the URL.

For every aspect that is turned off, a [layer](https://unpoly.com/api/up.layer.md) reports what it rendered, not what the browser shows. For example, [`up.layer.location`](https://unpoly.com/api/up.layer.location.md) is the URL of the rendered content, so an [`[up-nav]`](https://unpoly.com/api/up-nav.md) bar highlights links to the content instead of the unchanged address bar. Closing an [overlay](https://unpoly.com/learn/history-in-overlays.md) leaves aspects that are turned off alone, so a title that your own code set while the overlay was open is kept.

## Changing history from JavaScript {#history-api}

To add a history entry without rendering, call [`up.history.push()`](https://unpoly.com/api/up.history.push.md):

```js
up.history.push('/posts/5')
```

To change the URL of the current entry, call [`up.history.replace()`](https://unpoly.com/api/up.history.replace.md). Neither function updates the title or meta tags.

Entries placed this way are owned by Unpoly. When the user goes back to such an entry, Unpoly [restores the content](https://unpoly.com/learn/restoring-history.md) at that URL. To push an entry that your own script will restore, use the browser's `history.pushState()` instead.

## Observing changes {#observing}

After the address bar changed for any reason, Unpoly emits an [`up:location:changed`](https://unpoly.com/api/up:location:changed.md) event:

```js
up.on('up:location:changed', function(event) {
  console.log('New location is', event.location)
})
```

The event has a `{ reason }` property that tells whether an entry was pushed or replaced, whether the user went back, or whether only the `#hash` changed. See [Tracking page views](https://unpoly.com/learn/tracking-page-views.md) for using this event to track page views.

<nav aria-label="Reading order">

- [Previous: History](https://unpoly.com/learn/history.md)
- [Next: Restoring history](https://unpoly.com/learn/restoring-history.md)

</nav>

