---
title: Extensions
description: Install finished screens, new fields, and new blocks as packages.
---

An extension is a package that adds new capabilities to a Backlit app. Backlit is
built to be extended from day one, and an extension can add these things:

- New field types, with their data conversion and their rendering.
- New blocks, widgets, and charts.
- Complete resources, with their views and actions.

The last item means that an extension can ship finished screens. You install a
package, you register it, and your admin panel has new pages. This page shows that
with `@backlit/bullmq`, an extension that adds an operations dashboard for
[BullMQ](https://bullmq.io) queues.

## How an extension works

A Backlit app has a server side and a client side, and an extension has a module for
each. The server module registers what runs on your server: field types, builders,
resources, views, and actions. The client module registers the components that
render the new node kinds. A package exports the server module from its main entry
and the client module from its `/client` entry.

Both modules have the same name and version. When the client starts, it reads the
list of extensions from the manifest of the server and compares the list with its
own extensions. When one side is missing, or the versions differ, the client reports
which extension has the problem. You do not get a page that fails to render a node
it does not know.

## Install an extension

Install the package. One package contains both modules.

```sh
npm install @backlit/bullmq
```

### Register the server module

Pass the extension to `defineApp`. This extension needs the connection to Redis and
the names of the queues to monitor.

```ts title="src/app.ts"
import { defineApp } from '@backlit/sdk'
// [!code ++]
import { bullmq } from '@backlit/bullmq'

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

export const app = defineApp({
  // [!code ++:3]
  extensions: [
    bullmq({ connection: redis, queues: ['emails', 'reports'] }),
  ],
  areas: {
    main: {
      label: 'Newsletter',
      resources: {
        subscribers: () => import('./resources/subscribers.ts'),
      },
      navigation: [
        { label: 'Subscribers', resource: 'subscribers', view: 'list', icon: 'users' },
      ],
    },
  },
})
```

### Register the client module

Pass the client module to `BacklitApp` in the entry file of the frontend.

```tsx title="src/client/main.tsx"
import { BacklitApp } from '@backlit/ui'
// [!code ++]
import bullmqClient from '@backlit/bullmq/client'

createRoot(rootElement).render(
  <BacklitApp
    baseUrl="/api"
    // [!code ++]
    extensions={[bullmqClient]}
  >
    <RouterProvider router={router} />
  </BacklitApp>
)
```

## The result

The app now has a "BullMQ operations" area in the area switcher of the sidebar, with
five pages. The pages monitor the queues, list and filter the jobs, show the
timeline and the logs of each job, and retry or cancel a job. You wrote no resource,
no view, and no component for them.

<Carousel
  label="Pages that the BullMQ extension adds"
  slides={[
    {
      src: '/screenshots/bullmq/overview.png',
      alt: 'The overview page with four figures, a table of queues, and a line chart of completed and failed jobs.',
    },
    {
      src: '/screenshots/bullmq/queue-performance.png',
      alt: 'The page of one queue with the success rate, the failure rate, the throughput, the workers, and two line charts.',
    },
    {
      src: '/screenshots/bullmq/jobs.png',
      alt: 'A table of jobs with a row of state buttons above it. Each button has a count.',
    },
    {
      src: '/screenshots/bullmq/job-filters.png',
      alt: 'The jobs table with an open filter menu that has a job ID input and a priority input.',
    },
    {
      src: '/screenshots/bullmq/job-failed.png',
      alt: 'The page of a failed job with a timeline, tabs for logs, error, payload, options, and result, and a list of properties.',
    },
    {
      src: '/screenshots/bullmq/job-retried.png',
      alt: 'The same job after a retry. The state is waiting, the timeline has a new event, and the button is now "Cancel job".',
    },
  ]}
/>

These pages are Backlit views. The extension builds them from the same tables,
timelines, tabs, widgets, and charts that your own views use. Thus they follow the
theme of your app, they run behind your authentication, and their actions go through
the same request pipeline as your actions.

## What an extension is for

Use an extension when more than one app needs the same thing.

- **Infrastructure dashboards.** Queues, scheduled jobs, caches, feature flags, and
  audit logs are the same in each company. An extension turns each one into a
  package.
- **Parts for your domain.** A calendar block, a map block, or a money field with
  your rounding rules. You write the part one time, and each app of your company
  installs it.
- **Your own platform.** A team that owns authentication or billing can publish an
  extension, and each internal app gets the same user management pages.

When only one app needs one screen that no block can express, a custom page is less
work than an extension. See [Write a custom page](/sdui#write-a-custom-page).

## Write an extension

An extension uses the public APIs of the SDK and the web client. The
[extensions guide](/advanced/extensions) covers the server module: factories,
macros, remappers, and builder contracts. [Field roles](/advanced/field-roles) shows
a complete extension with a server module and a client module.
