---
title: Introduction
description: What Backlit is, the work it removes, and the packages it ships.
---

Backlit is a server-driven UI (SDUI) framework for admin panels and internal apps.
You declare the data and the interface together on the server. A web client renders
the result.

## Do not build another app

An admin panel is usually a second application that you build next to your product.
It starts with a data-only API, because the API of the product is scoped to one
customer, and an admin panel needs elevated privileges and data across customers.
Then you build an interface, and then the client code that connects the interface to
the API. The interface is usually the only consumer that the API will ever have.

Backlit is SDUI, and SDUI collapses the three pieces into one. A resource on your
server reads the data and declares the UI primitives that display it, in the same
file. There is no separate API to build, and there is no client code to write. The
web client renders the response. The [SDUI primer](/sdui) covers the background of
this architecture and how it works in Backlit.

The three steps below build a page that lists the subscribers of a newsletter.

### Define a resource with fields

A resource is a named entity of your domain, such as a subscriber or an order. It
holds the fields of the entity and the views that display it.

A field describes one piece of data: its name, its data type, its format, and how
the client renders it. You declare a field one time. Tables, datalists, and forms
all accept the same declaration, so a label or a format is never written twice.

A field is not a mapping to a database column, although many fields have a column
with the same name. The data of a field can come from anywhere. A field can also
describe a computed value, an aggregate, or a statistic, such as the open rate of a
newsletter. The [fields reference](/reference/fields) lists the field types and
their options.

```ts title="src/resources/subscribers.ts"
import { defineResource, fields } from '@backlit/sdk'

const subscribers = defineResource('subscribers', [
  fields.text('email', { label: 'Email', format: 'email' }),
  fields.text('name', { label: 'Name' }),
  fields.enum('status', ['pending', 'subscribed', 'unsubscribed'], {
    label: 'Status',
    // The client draws the value as a badge and not as plain text.
    displayVariant: 'badge',
  }),
  fields.date('subscribedAt', { label: 'Subscribed', displayVariant: 'datetime' }),
])

export default subscribers
```

### Define a view

A view is one screen of a resource. Its address is the resource name and the view
name, thus `subscribers/list` here. The callback runs on your server for each
request. It reads the data through your own data layer and returns the blocks that
display the data.

This view returns a `section` block that contains a `table` block. The table gets
its columns from the fields of the resource, selected with `subscribers.pick`.

```ts title="src/resources/subscribers.ts"
import { defineResource, fields, section, table } from '@backlit/sdk'

import { db } from '../db.ts'

// ...the resource from the previous step

subscribers.view('list', async (ctx, view) => {
  view.setTitle('Subscribers')

  // Your query. It runs on the server with the privileges of your admin area.
  const rows = await db.subscribers.findAll()

  return section('Subscribers').setContent([
    table(rows).setFields(subscribers.pick('name', 'email', 'status', 'subscribedAt')),
  ])
})
```

### Create the app and add the view to the sidebar

The app is the root of the declaration. It groups resources into areas, and each
area has a navigation list for the sidebar. A navigation entry names a resource and
one of its views. The resources are lazy imports, so a request loads only the
resource that it needs.

```ts title="src/app.ts"
import { defineApp } from '@backlit/sdk'

export const app = defineApp({
  areas: {
    main: {
      label: 'Newsletter',
      resources: {
        subscribers: () => import('./resources/subscribers.ts'),
      },
      navigation: [
        { label: 'Subscribers', resource: 'subscribers', view: 'list', icon: 'users' },
      ],
    },
  },
})
```

A host adapter mounts the app on your HTTP server. Backlit does not own the server,
the authentication, or the database. Your middleware runs before the Backlit routes,
as it does for your other routes.

```ts title="src/server.ts"
import { Hono } from 'hono'
import { backlitRoutes } from '@backlit/hono'

import { app } from './app.ts'

const server = new Hono()
server.route('/api', backlitRoutes(app))
```

The web client requests the `subscribers/list` view, receives a tree of protocol
nodes as JSON, and renders the page.

## One protocol, many SDKs and clients

The center of Backlit is a protocol, not a library. The protocol defines the nodes
that a server can send and the requests that a client can make. An SDK is a program
that emits the protocol. A client is a program that renders it.

Backlit ships a TypeScript SDK and a web client today. Because the two sides share
only the protocol, an SDK for a different language and a client for a mobile
platform can be added without a change to the other side. Both are planned.

## Packages

| Package             | Runs on | Purpose                                                                                                                                       |
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `@backlit/sdk`      | Server  | A TypeScript library that works on any runtime. Its builders declare resources, views, actions, and blocks, and they emit protocol responses. |
| `@backlit/ui`       | Browser | The web client. It is a React library that requests views, renders protocol nodes, and submits actions.                                       |
| `@backlit/hono`     | Server  | The host adapter for Hono. It mounts the routes of an app and maps requests and responses.                                                    |
| `@backlit/contract` | Both    | The protocol as TypeScript types. The SDK and the web client both depend on it. An extension author uses it to add a node.                    |

## Next steps

- [Server-driven UI](/sdui): the background of SDUI and how it works in Backlit.
- [Key concepts](/concepts): the terms that the SDK and the reference use.
- [Getting started](/getting-started): add Backlit to an app with a coding agent.
- [Extensions](/extensions): install finished screens and new parts as packages.
- [Manifesto](/manifesto): why Backlit exists and where it goes.
