title: "Step 7 — Database & CRUD" active_step: 7 fetch.notes = "local:notes?sort=created_at:desc"

# Step 7 — Database & CRUD

Full create, read, update, delete — from HTML forms, no backend code.

## How data fetching works

Declare a `fetch.*` directive in the `<what>` block. The `local:` prefix queries your configured database. The result is available as a template variable.

    <what>
    fetch.notes = "local:notes?sort=created_at:desc&limit=20"
    </what>

    <loop data="#notes#" as="note">
      <div>#note.title#</div>
    </loop>

## Create a note

Use `action="/w-action/notes"` with `method="post"` to insert a new record. The form fields become the record's columns.

#flash.success#
#flash.error#
Title *
#errors.title#
Content #old.body#

## Your notes

Records are fetched via `fetch.notes = "local:notes"` in the `<what>` block at the top of this page.

No notes yet. Create one above.
#note.title#
#note.body#

## CRUD reference

Action | Form action | Method
--- | --- | ---
Create record | `/w-action/notes` | `POST`
Update record | `/w-action/notes/#id#` | `POST`
Delete record | `/w-action/notes/#id#?w-action=delete` | `POST`

## Where does the data go?

Forms can send data to different destinations depending on the action URL:

Destination | How
--- | ---
Database | `action="/w-action/collection"` — persists to SQLite/D1
Session | `w-set="session.key = $value"` — per-user, no form submit needed
App state | `w-set="app.key = $value"` — shared across all users
Wired (real-time) | `w-set="wired.key = $value"` — broadcasts to all browsers via WebSocket

## CSRF protection

Every `<form method="post">` automatically gets a hidden CSRF token injected by the engine. You don't need to add it manually — it's built in.

    <!-- You write this: -->
    <form method="post" action="/w-action/notes">
      <input name="title">
      <button type="submit">Save</button>
    </form>

    <!-- The engine renders this: -->
    <form method="post" action="/w-action/notes">
      <input type="hidden" name="_csrf" value="auto-generated-token">
      <input name="title">
      <button type="submit">Save</button>
    </form>

Security: POST requests without a valid CSRF token are rejected with 403 Forbidden. This prevents cross-site request forgery attacks.

## Query options

    <!-- Sort, filter, search, paginate -->
    fetch.notes = "local:notes?sort=created_at:desc&limit=10&offset=0"
    fetch.active = "local:items?filter=status:active"
    fetch.results = "local:posts?search=hello"

What you learned

- `fetch.name = "local:collection"` queries your SQLite database
- Fetched data is available as `#name#` and iterated with `<loop>`
- Create: `POST /w-action/collection` — form fields become columns
- Update: `POST /w-action/collection/#id#`
- Delete: `POST /w-action/collection/#id#?w-action=delete`
- Query params: `sort`, `filter`, `search`, `limit`, `offset`
- Data can go to: database (`/w-action/`), session, app state, or wired (real-time)
- CSRF tokens are auto-injected into all POST forms — no manual setup needed