title: "Step 5 — Sessions & State" active_step: 5

# Step 5 — Sessions & State

Server-side state that persists, mutated from HTML attributes.

## How it works

Sessions are stored server-side in SQLite. Every browser gets a unique session. The `w-set` attribute on any HTML element mutates session data when clicked or changed. No JavaScript. No API routes.

    <button w-set="session.counter += 1">+1</button>
    <span>#session.counter#</span>

## Interactive counter

This counter is stored in your session. Reload the page — the value persists.

#session.tut_counter|default:"0"#

Reload the page. The number will still be there.

    <div>#session.tut_counter|default:"0"#</div>
    <button w-set="session.tut_counter += 1">+1</button>
    <button w-set="session.tut_counter -= 1">-1</button>
    <button w-set="session.tut_counter = 0">Reset</button>

## Toggle demo

Booleans toggle with the `toggle` operator.

Status: active inactive

    <button w-set="session.tut_toggle = toggle">
      <if session.tut_toggle>ON</if>
      <unless session.tut_toggle>OFF</unless>
    </button>

## Mutation patterns

Pattern | Effect
--- | ---
`session.n += 1` | Increment by 1
`session.n -= 5` | Decrement by 5
`session.x = "hello"` | Set a string value
`session.flag = toggle` | Toggle boolean
`session.x = $value` | Set from input field value

## Application-wide state

Use `app.*` instead of `session.*` for state shared across all users. Use `wired.*` for real-time state pushed to all connected browsers via WebSocket.

    session.*   <!-- Per-user, persists across reloads -->
    app.*       <!-- Shared across all users -->
    wired.*     <!-- Shared + real-time WebSocket push -->

What you learned

- `w-set` on any element mutates server state when clicked or changed
- `session.*` is per-user and persists across page reloads
- Mutations: `+= 1`, `-= N`, `= "value"`, `= $value`, `= toggle`
- Multiple mutations: `w-set="session.a += 1; session.b = 'ok'"`
- `app.*` is shared across all users; `wired.*` pushes real-time via WebSocket