what

Form Actions

Create, update, and delete records with declarative HTML attributes. No JavaScript, no API routes — just forms.

Create

Add w-action="create" and w-store="collection" to a form. Each <input> name becomes a field in the new record.

<form w-action="create" w-store="posts" w-redirect="/blog">
  <input name="title" type="text" placeholder="Post title">
  <textarea name="content" placeholder="Write something..."></textarea>
  <button type="submit">Publish</button>
  <button type="reset">Cancel</button>
</form>

On submit, the framework creates a new record in the "posts" collection with an auto-generated id and created_at timestamp, then redirects to /blog.

Update

Use w-action="update" with w-id to specify which record to modify. Only the submitted fields are updated.

<form w-action="update" w-store="posts" w-id="#post.id#" w-redirect="/blog">
  <input name="title" type="text" value="#post.title#">
  <textarea name="content">#post.content#</textarea>
  <button type="submit">Save Changes</button>
  <button type="reset">Cancel</button>
</form>

Delete

Use w-action="delete" with w-id. No input fields are needed — just a submit button.

<form w-action="delete" w-store="posts" w-id="#post.id#" w-redirect="/blog">
  <button type="submit">Delete Post</button>
</form>

Redirects

The w-redirect attribute sets where the user goes after a successful action.

<!-- Redirect to a specific page -->
<form w-action="create" w-store="posts" w-redirect="/blog">

<!-- Redirect to a dynamic URL -->
<form w-action="update" w-store="posts" w-id="#post.id#" w-redirect="/blog/#post.id#">

If w-redirect is not specified, the form redirects back to the referring page.

CSRF Protection

Every form is automatically protected against cross-site request forgery. The engine injects a hidden _csrf field and a <meta name="csrf-token"> tag. No manual work is needed.

<!-- You write this: -->
<form w-action="create" w-store="posts">
  <input name="title">
  <button type="submit">Create</button>
</form>

<!-- The engine renders this: -->
<form action="/w-action/posts?w-action=create" method="post">
  <input type="hidden" name="_csrf" value="...">
  <input name="title">
  <button type="submit">Create</button>
</form>
Tip: CSRF tokens are tied to the user's session. The framework validates the token on every form submission automatically.

Flash Messages

After a form action, the framework sets flash messages in the session. Display them with #flash.success# and #flash.error#.

<if flash.success>
  <div class="alert alert-success">#flash.success#</div>
</if>

<if flash.error>
  <div class="alert alert-danger">#flash.error#</div>
</if>

Flash messages are consumed on read — they appear once and are cleared from the session.

Partial Updates

Instead of a full page redirect, update a specific part of the page using w-target and w-swap.

<form w-action="create" w-store="comments" w-target="#comments-list" w-swap="append">
  <input name="body" placeholder="Add a comment...">
  <button type="submit">Post</button>
</form>

<div id="comments-list">
  <loop data="#comments#" as="comment">
    <p>#comment.body#</p>
  </loop>
</div>

Available swap modes:

ValueBehavior
innerHTMLReplace the target's inner content (default)
appendInsert at the end of the target (alias: beforeend)
prependInsert at the start of the target (alias: afterbegin)
beforeInsert before the target element (alias: beforebegin)
afterInsert after the target element (alias: afterend)
outerHTMLReplace the target element entirely
noneDon't swap, just trigger the action

Confirmation Dialogs

Add w-confirm to show a browser confirmation dialog before submitting. If the user cancels, the form is not submitted.

<form w-action="delete" w-store="posts" w-id="#post.id#" w-redirect="/blog">
  <button type="submit" w-confirm="Are you sure you want to delete this post?">
    Delete
  </button>
</form>

Complete CRUD Example

A full page with a create form, a list of items, and edit/delete actions:

site/tasks/index.html
<what>
fetch.tasks = "local:tasks"
fetch.tasks.sort = "created_at:desc"
</what>

<h1>Tasks</h1>

<if flash.success>
  <div class="alert alert-success mb-4">#flash.success#</div>
</if>

<!-- Create form -->
<form w-action="create" w-store="tasks" w-redirect="/tasks" class="flex gap-2 mb-6">
  <input name="title" type="text" placeholder="New task..." class="input flex-1">
  <input name="status" type="hidden" value="pending">
  <button type="submit" class="btn btn-primary">Add Task</button>
</form>

<!-- Task list -->
<if tasks>
  <loop data="#tasks#" as="task">
    <div class="flex items-center justify-between p-3 border-b">
      <div>
        <strong>#task.title#</strong>
        <span class="badge ml-2">#task.status#</span>
      </div>
      <div class="flex gap-2">
        <form w-action="update" w-store="tasks" w-id="#task.id#" w-redirect="/tasks">
          <input name="status" type="hidden" value="done">
          <button type="submit" class="btn btn-sm">Complete</button>
        </form>
        <form w-action="delete" w-store="tasks" w-id="#task.id#" w-redirect="/tasks">
          <button type="submit" class="btn btn-sm btn-danger"
                  w-confirm="Delete this task?">Delete</button>
        </form>
      </div>
    </div>
  </loop>
</if>
<else/>
  <p class="text-muted p-4">No tasks yet. Add one above.</p>
</else>