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#
#errors.title#

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