---
title: Views, zones, actions, and lookups
description: The endpoints of a resource, which read data, change data, and supply options.
---

Views, actions, and lookups are the endpoints of a resource. Each one has an address,
runs your code on the server for each request, and returns a protocol response. A
zone is a part of a view that the client can request separately. Together they
replace the routes and the controllers of an admin API.

This introduction explains what each endpoint does and how they work together. The
page for each endpoint has the full API.

| Endpoint                         | Purpose                                          | Request         |
| -------------------------------- | ------------------------------------------------ | --------------- |
| [View](/reference/views/view)     | Reads data and returns the blocks of one screen  | `GET`           |
| [Zone](/reference/views/zone)     | Returns one part of a view again                 | `GET`, partial  |
| [Action](/reference/views/action) | Validates a payload and changes data             | `POST`          |
| [Lookup](/reference/views/lookup) | Supplies records to a search or an options input | `GET`           |

## Addresses, params, and args

You register each endpoint on a resource with a name, so the resource name and the
endpoint name identify it. An endpoint can also declare params, which are ordered
names such as `['id']`. The values that a request supplies for the params are the
args.

```ts
type ViewAddress = { resource: string; view: string; args: string[] }
```

An address is not a URL. The host converts an address to a URL, so your URL
structure stays under your control. In your code, you do not write addresses as
strings. You pass the definition that `resource.view` or `resource.action` returns,
and TypeScript checks the args.

## Views

A view is one screen. Its callback reads the data and returns one block or an array
of blocks. It can also return a redirect, or an error for a failure that you expect,
such as a record that does not exist.

```ts
const orderDetail = orders.view('detail', ['id'], async (ctx, view) => {
  const order = await db.orders.find(ctx.paramValues.id)
  if (!order) {
    return ctx.fail('This order does not exist.')
  }

  view.setTitle(`Order ${order.reference}`)
  return datalist(order).setFields(orders.pick('reference', 'customer', 'total'))
})
```

A view can also register search state, which is the filters, the sorter, and the
paginator of the screen. See [Search state](/reference/state).

## Actions

An action is one operation that changes data. It has a schema and a handler. The SDK
validates the payload against the schema before the handler runs, so the handler
reads typed data from `ctx.data`. The schema can be from any validator that
implements Standard Schema, such as Zod.

The handler returns the next state of the interface and not only a status. It can
redirect to a view, refresh the current view or some of its zones, show a
notification, or fail with a message.

```ts
const updateOrder = orders.action('update', ['id'], updateOrderSchema, async (ctx) => {
  const saved = await db.orders.update(ctx.paramValues.id, ctx.data)
  if (!saved) {
    return ctx.fail('This order no longer exists.')
  }

  return ctx.redirectTo(orderList).notify({ text: 'Order saved.', tone: 'success' })
})
```

An action does not belong to one form. A form, a row action of a table, and an agent
tool can all call the same action, and each call gets the same validation.

## Zones

A view callback runs again for each request for the view. For most screens this is
correct. A zone is for the part of a screen that must load again separately, such as
a table that changes after a row action when the rest of the page stays the same.

A zone has a name and its own callback, and you put it in the content of a view like
a block. An action refreshes it by name with `ctx.refreshZones(['items'])`. For a
zone request, the server runs the view callback to find the zone, then runs the
callback of that zone only, and skips the other zones. Thus put the expensive queries
inside the zone callbacks and not in the view callback.

## Lookups

A lookup supplies records to an input that selects a related record, such as the
customer of an order. The input sends the text that the user types, and the lookup
callback runs your search and returns the fields and the rows. Backlit does not
search your data. You connect an input to a lookup with a
[lookup target](/reference/targets/lookup).

## How the endpoints connect

Blocks reach endpoints through [targets](/reference/targets). A link on a table row
opens a view, a form submits to an action, and a select input reads from a lookup.
The response of an action then decides the next screen. This cycle is a complete
create and edit flow:

1. The `list` view returns a table. Each row has a link target to the `edit` view.
2. The `edit` view returns a form that submits to the `update` action.
3. The `update` action saves the record and returns a redirect to the `list` view
   with a notification.
