---
title: "Keep screen state in the address · Agent Wranglers"
url: https://agentwranglers.se/playbook/app-ui/client-and-server-state/state-in-url
updated: 2026-10-20
licence: "All rights reserved"
---

# Keep screen state in the address

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Client and server state](https://agentwranglers.se/playbook/app-ui/client-and-server-state)
- Rule: Put every choice that changes what a screen shows in the address, such as filters, search, sort, page, tab and the open record. Reloading or sharing the link shows the same screen.
- How: Read the state from the query string when the screen loads, and write it back when it changes. Use replace for typing and push for steps people expect Back to undo, such as opening a record or moving to another page of results. Validate each parameter and fall back to a default for bad values. Leave defaults out, so links stay short. Never put personal data or tokens in the address.
- Check: A browser test sets a filter, a sort and page 3, copies the address and opens it in a new context. The same rows show. Back returns to the previous page of results.

Example:

```ts
const SORTS = ['newest', 'oldest', 'amount'] as const;
type Sort = (typeof SORTS)[number];

export function readOrderFilters(url = new URL(location.href)) {
  const sort = url.searchParams.get('sort');
  return {
    status: url.searchParams.get('status') ?? 'any',
    sort: (SORTS as readonly string[]).includes(sort ?? '') ? (sort as Sort) : 'newest',
    cursor: url.searchParams.get('cursor'),
  };
}

export function writeOrderFilters(filters: Record<string, string | null>, mode: 'push' | 'replace') {
  const url = new URL(location.href);
  for (const [name, value] of Object.entries(filters)) value ? url.searchParams.set(name, value) : url.searchParams.delete(name);
  history[mode === 'push' ? 'pushState' : 'replaceState'](null, '', url);
}
```

```python
SORTS = {"newest", "oldest", "amount"}

@app.get("/orders")
def order_list():
    sort = request.args.get("sort", "newest")
    filters = OrderFilters(
        status=request.args.get("status", "any"),
        sort=sort if sort in SORTS else "newest",
        cursor=request.args.get("cursor"),
    )
    return render_template("orders/list.html", page=orders.list(current_user().account_id, filters), filters=filters)
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/client-and-server-state/state-in-url`.

- Previous: [Undo optimistic updates that fail](https://agentwranglers.se/playbook/app-ui/client-and-server-state/optimistic-with-rollback)
- Next: [Compute values instead of storing them](https://agentwranglers.se/playbook/app-ui/client-and-server-state/derived-values)
- [Client and server state](https://agentwranglers.se/playbook/app-ui/client-and-server-state)
