Skip to content

Commands

A command is how something asks your system to change — open an account, check out a book, change an address. In Arc a command is a small, intent-revealing record that carries the data for that change and knows how to handle itself. There’s no separate handler class, no controller boilerplate to write: you declare the intent and what it does, and Arc wires up the HTTP endpoint, validation, and a typed TypeScript proxy for the frontend.

POST

binds + validates

returns

typed proxy

React UI

Arc endpoint

Command record + Handle()

CommandResult

Here’s the whole thing — the command, its data, and its behavior, in one record:

public interface IAccounts
{
Task Open(AccountId id, AccountHolder owner);
}
[Command]
public record OpenAccount(AccountId Id, AccountHolder Owner)
{
public Task Handle(IAccounts accounts) =>
accounts.Open(Id, Owner);
}

Handle() is defined directly on the record — that’s the convention. Arc discovers it, exposes the command as an HTTP POST, binds the incoming JSON to the record, runs any validation, then calls Handle(). Whatever you inject into Handle() is resolved from the container. In this example IAccounts is an application-owned service; it might write MongoDB, EF Core, another application service, or anything else your slice owns. With the Chronicle integration installed, a command can return events instead and let Arc append them for you.

Handle() can return what suits the operation:

  • nothing (void / Task) — fire-and-forget changes
  • a value — becomes a typed CommandResult<T> the frontend can read
  • a tuple — return an event and a value (e.g. a generated id)
  • a Result<TSuccess, TError> — model success and failure explicitly

Whatever you return, the caller gets a CommandResult carrying success, validation, and authorization state — so the frontend always knows what happened.

StyleWhat it looks likeReach for it when
Model-boundA [Command] record with Handle(), as aboveThe default. Least boilerplate; the intent and behavior live together.
Controller-basedA command type posted to a controller actionYou need full control over the HTTP surface, or you’re integrating with existing controllers.

Once the command exists, layer on the cross-cutting concerns Arc handles for you:

ConcernPage
Validate input before it runsValidation
Check validity without executing (pre-flight)Command Validation
Run a command in code, not over HTTPCommand Pipeline
Carry ambient values through the pipelineCommand Context
Apply cross-cutting logic to every commandCommand Filters
Authorize by role or policyAuthorization
Shape the response the frontend receivesResponse Value Handlers · Response Examples

The payoff: it’s already on the frontend

Section titled “The payoff: it’s already on the frontend”

You didn’t write a DTO or an API client. When you build the backend, Arc’s proxy generator emits a typed TypeScript proxy for OpenAccount, ready to call from React with full type checking — see Commands in React. Rename a property in the C# record, rebuild, and the frontend won’t compile until it’s fixed. That’s the whole point of building on Arc: one definition, typed end to end.

Next, read about the queries that read the data your commands change.