Watch the 📼 Remix Singles: Data Mutations with Form + action and Multiple Forms and Single Button Mutations

A route action is a server only function to handle data mutations and other actions. If a non-GET request is made to your route (DELETE, PATCH, POST, or PUT) then the action is called before the loaders.

actions have the same API as loaders, the only difference is when they are called. This enables you to co-locate everything about a data set in a single route module: the data read, the component that renders the data, and the data writes:

import type { ActionFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import { Form } from "@remix-run/react";

import { TodoList } from "~/components/TodoList";
import { fakeCreateTodo, fakeGetTodos } from "~/utils/db";

export async function action({
}: ActionFunctionArgs) {
  const body = await request.formData();
  const todo = await fakeCreateTodo({
    title: body.get("title"),
  return redirect(`/todos/${}`);

export async function loader() {
  return json(await fakeGetTodos());

export default function Todos() {
  const data = useLoaderData<typeof loader>();
  return (
      <TodoList todos={data} />
      <Form method="post">
        <input type="text" name="title" />
        <button type="submit">Create Todo</button>

When a POST is made to a URL, multiple routes in your route hierarchy will match the URL. Unlike a GET to loaders, where all of them are called to build the UI, only one action is called.

The route called will be the deepest matching route, unless the deepest matching route is an "index route". In this case, it will post to the parent route of the index (because they share the same URL, the parent wins).

If you want to post to an index route use ?index in the action: <Form action="/accounts?index" method="post" />

action url route action
/accounts?index app/routes/accounts._index.tsx
/accounts app/routes/accounts.tsx

Also note that forms without an action prop (<Form method="post">) will automatically post to the same route within which they are rendered, so using the ?index param to disambiguate between parent and index routes is only useful if you're posting to an index route from somewhere besides the index route itself. If you're posting from the index route to itself, or from the parent route to itself, you don't need to define a <Form action> at all, just omit it: <Form method="post">.

See also:

Docs and examples licensed under MIT