title: "Form Actions" page: forms section: Forms parent: Forms parent_href: /docs/forms

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="beforeend">
  <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)
outerHTMLReplace the target element entirely
beforebeginInsert before the target element
afterendInsert after the target element
beforeendAppend inside the target (at the end)
afterbeginPrepend inside the target (at the start)
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>