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

# Layer context

Layer context is a key/value object that lives as long as its layer. Use it to pass data to a screen you open in an overlay, like the record a picker is choosing for, without changing the screen's URL. Both your JavaScript and your server can read and change it.

<nav aria-label="Contents">

#### Contents

- [Passing context to an overlay](#initializing)
- [Varying a reused screen](#reuse-interaction-with-variation)
- [Working with context in JavaScript](#scripting)
- [Changing context from the server](#server-updates)
- [Context compared to other stores](#comparison)

</nav>

## Passing context to an overlay {#initializing}

To give a new overlay a context, set an `[up-context]` attribute with a [relaxed JSON](https://unpoly.com/api/relaxed-json.md) object on the link that opens it:

```html
<a href="/contacts" up-layer="new" up-context="{ project: 'Hosting 2021' }">
  Pick a contact
</a>
```

From JavaScript, pass a `{ context }` option to [`up.layer.open()`](https://unpoly.com/api/up.layer.open.md):

```js
up.layer.open({ url: '/contacts', context: { project: 'Hosting 2021' } })
```

Every layer has its own context, which starts as an empty object (`{}`). When an overlay closes, its context is discarded with it.

## Varying a reused screen {#reuse-interaction-with-variation}

Context is most useful when you [reuse an existing screen](https://unpoly.com/learn/subinteractions.md#reusing-existing-screens) in an overlay, but want a small variation. Say a project form lets the user pick a contact. Instead of building a picker widget, it opens the existing `/contacts` list in an overlay:

```html
<a href="/contacts"
  up-layer="new"
  up-accept-location="/contacts/$id"
  up-context="{ project: 'Hosting 2021' }">
  Pick a contact
</a>
```

When the overlay requests `/contacts`, Unpoly sends the layer's context as an [`X-Up-Context`](https://unpoly.com/api/X-Up-Context.md) request header, serialized as JSON:

```http
X-Up-Context: { "project": "Hosting 2021" }
```

Every request targeting that layer carries the header, so the server can render the contact list with an additional heading. This [ERB](https://github.com/ruby/erb) template uses the [`unpoly-rails`](https://github.com/unpoly/unpoly-rails) gem:

```erb
<% if up.context[:project] %>
  <h1>Pick a contact for <%= up.context[:project] %></h1>
<% else %>
  <h1>Contacts</h1>
<% end %>

<% @contacts.each do |contact| %>
  <li>...</li>
<% end %>
```

Other backends can read the [`X-Up-Context`](https://unpoly.com/api/X-Up-Context.md) request header the same way.

Opened on its own, `/contacts` has no context and shows the regular heading. The list needs no changes to work in both places.

> #### Note
>
> Request headers that influenced a response should be listed in a [`Vary`](https://unpoly.com/api/Vary.md) response header. This tells Unpoly to [partition its cache](https://unpoly.com/learn/optimizing-responses.md#vary) for that URL so that each request header value gets separate cache entries.

## Working with context in JavaScript {#scripting}

[`up.context`](https://unpoly.com/api/up.context.md) returns the context of the [current layer](https://unpoly.com/api/up.layer.current.md). Read and change it like any JavaScript object:

```js
up.context.project // result: "Hosting 2021"
up.context.selected = 123
```

To access the context of another layer, use the [`up.Layer#context`](https://unpoly.com/api/up.Layer.prototype.context.md) property of an [`up.Layer`](https://unpoly.com/api/up.Layer.md) object, like `up.layer.root.context` or `up.layer.get('parent').context`.

Any link or form can also add to its layer's context once it has rendered. This updates the current layer rather than opening a new one:

```html
<a href="/contacts?letter=B" up-follow up-context="{ letter: 'B' }">B</a>
```

## Changing context from the server {#server-updates}

The server can change the context of the updated layer by sending an [`X-Up-Context`](https://unpoly.com/api/X-Up-Context.md) response header with the changed keys:

```http
Content-Type: text/html
X-Up-Context: { "lives": 2 }

<html>
  ...
</html>
```

Unpoly merges the given keys into the layer's context, adding or replacing them. Keys not mentioned in the header remain unchanged. To clear a key, send it with a `null` value.

Only send the keys you changed. If the server echoed the entire context, client-side changes made while the request was in flight would be overwritten.

With the `unpoly-rails` gem, assigning a key in the controller sets this header for you:

```ruby
class GamesController < ApplicationController

  def restart
    up.context[:lives] = 3
    render 'stage1'
  end

end
```

Without `unpoly-rails`, set the [`X-Up-Context`](https://unpoly.com/api/X-Up-Context.md) response header yourself.

Context updates are applied for both successful responses and [error responses](https://unpoly.com/learn/error-responses.md).

## Context compared to other stores {#comparison}

The web platform offers other ways to persist state across requests, but none of them is scoped to a layer:

| Store | Scope | Persistence | Values | Client-manageable | Server-manageable |
| --- | --- | --- | --- | --- | --- |
| Local storage | Domain | Multiple sessions | String | Yes | - |
| Cookies | Domain | Configurable | String | Configurable | Yes |
| Session storage | Tab | Session | String | Yes | - |
| Layer context | [Layer](https://unpoly.com/api/up.layer.md) | Layer | Object | Yes | Yes |

<nav aria-label="Reading order">

- [Previous: Targeting other layers](https://unpoly.com/learn/targeting-other-layers.md)
- [Next: History in overlays](https://unpoly.com/learn/history-in-overlays.md)

</nav>

