Skip to content
Backlit
Esc
navigateopen⌘Jpreview
On this page

Resources

Declare a named entity, its fields, and its Backlit endpoints.

A resource is a named entity such as an order, customer, or product. Its name is the first part of each view, action, and lookup address.

A resource declares reusable fields and holds its registered endpoints. It does not fetch data and does not create a protocol node. Views, zones, actions, and lookups fetch data when they resolve.

API

defineResource(name: string, fields: FieldBuilderContract[]): Resource
Member Input Result
name Returns the resource name.
pick ...fieldNames Returns fields in the requested order.
get fieldName Returns one field builder.
view name, params or state, resolver Registers and returns a view definition.
getView name Returns a registered view or undefined.
action name, params, schema, handler Registers and returns an action definition.
getAction name Returns a registered action or undefined.
lookup name, params or state, resolver Registers and returns a lookup definition.
getLookup name Returns a registered lookup or undefined.

Field, view, action, and lookup names must be unique in their own group on one resource.

Basic resource

Pass the address name and the field declarations to defineResource.

import { callout, defineResource } from '@backlit/sdk'

const orders = defineResource('orders', [])

orders.view('overview', () =>
  callout('The orders resource is available.')
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "The orders resource is available."
        }
      ]
    }
  }
}

Resource without fields

A resource can have no fields. This is useful when its screens show computed blocks instead of record data.

import { callout, defineResource } from '@backlit/sdk'

const orders = defineResource('orders', [])

orders.view('overview', () =>
  callout('A resource can have no fields.')
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "A resource can have no fields."
        }
      ]
    }
  }
}

Name

The name property keeps its literal TypeScript type. Backlit also uses this value in endpoint addresses.

import { callout, defineResource } from '@backlit/sdk'

const orders = defineResource('orders', [])

orders.view('overview', () =>
  callout(`Resource: ${orders.name}`)
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "Resource: orders"
        }
      ]
    }
  }
}

Fields

Declare fields once on the resource. Tables, datalists, forms, and lookups can reuse the same builders.

import { defineResource, fields, table } from '@backlit/sdk'

const orders = defineResource('orders', [
  fields.number('id', { label: 'Order' }),
  fields.text('customer', { label: 'Customer' }),
  fields.date('placedAt', { label: 'Placed' }),
])

orders.view('list', () =>
  table([]).setFields(
    orders.pick('id', 'customer', 'placedAt')
  )
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "list",
    "slots": {
      "content": [
        {
          "kind": "table",
          "fields": [
            {
              "kind": "number",
              "name": "id",
              "label": "Order"
            },
            {
              "kind": "text",
              "name": "customer",
              "label": "Customer"
            },
            {
              "kind": "date",
              "name": "placedAt",
              "label": "Placed"
            }
          ],
          "data": []
        }
      ]
    }
  }
}

See Fields for every field type and option.

Pick fields

Use pick when a block needs several resource fields. The returned tuple keeps the requested order. TypeScript rejects a name that the resource does not have.

import { defineResource, fields, table } from '@backlit/sdk'

const orders = defineResource('orders', [
  fields.number('id', { label: 'Order' }),
  fields.text('customer', { label: 'Customer' }),
  fields.text('status', { label: 'Status' }),
])

orders.view('list', () =>
  table([{ id: 42, customer: 'Acme', status: 'Open' }])
    .setFields(orders.pick('status', 'id'))
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "list",
    "slots": {
      "content": [
        {
          "kind": "table",
          "fields": [
            {
              "kind": "text",
              "name": "status",
              "label": "Status"
            },
            {
              "kind": "number",
              "name": "id",
              "label": "Order"
            }
          ],
          "data": [
            {
              "status": "Open",
              "id": 42
            }
          ]
        }
      ]
    }
  }
}

Get one field

Use get when code needs one field builder and its field-specific methods. The returned builder keeps its exact type.

import {
  callout,
  defineResource,
  fields,
} from '@backlit/sdk'

const orders = defineResource('orders', [
  fields.enum('status', [
    { value: 'open', label: 'Open' },
    { value: 'shipped', label: 'Shipped' },
  ]),
])

orders.view('overview', () => {
  const labels = orders.get('status').getOptionLabels()
  return callout(`Statuses: ${labels.join(', ')}`)
})

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "Statuses: Open, Shipped"
        }
      ]
    }
  }
}

Registered views

Calling view registers the definition. Use getView when a host must find a definition by name. An unknown name returns undefined.

import { callout, defineResource } from '@backlit/sdk'

const orders = defineResource('orders', [])

orders.view('list', () => callout('Order list'))

orders.view('overview', () => {
  const listView = orders.getView('list')
  return callout(`Registered view: ${listView?.name}`)
})

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "Registered view: list"
        }
      ]
    }
  }
}

See Views for view registration and resolver options.

Registered actions

Calling action registers the definition. Use getAction to find it by name. The action schema can use any Standard Schema validator.

import { callout, defineResource } from '@backlit/sdk'
import { z } from 'zod'

const orders = defineResource('orders', [])

orders.action('create', z.object({}), () => {})

orders.view('overview', () => {
  const createAction = orders.getAction('create')
  return callout(`Registered action: ${createAction?.name}`)
})

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "Registered action: create"
        }
      ]
    }
  }
}

Registered lookups

Calling lookup registers the definition. Use getLookup to find it by name.

import {
  callout,
  defineResource,
  fields,
} from '@backlit/sdk'

const orders = defineResource('orders', [
  fields.number('id'),
  fields.text('customer'),
])

orders.lookup('picker', (_ctx, lookup) =>
  lookup
    .setFields(orders.pick('id', 'customer'))
    .withData([])
)

orders.view('overview', () => {
  const picker = orders.getLookup('picker')
  return callout(`Registered lookup: ${picker?.name}`)
})

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "Registered lookup: picker"
        }
      ]
    }
  }
}

A lookup can declare params, the same as a view. The resolver reads their values from ctx.paramValues. The search query stays on the context and can be empty.

const statePicker = states.lookup(
  'byCountry',
  { params: ['country'] },
  (ctx, lookup) =>
    lookup
      .setFields(states.pick('code', 'name'))
      .withData(statesOf(ctx.paramValues.country))
)

See Lookup target for how an input supplies the args.

All members

One resource can declare fields and register views, actions, and lookups. Each endpoint group has its own name registry.

import {
  callout,
  defineResource,
  fields,
} from '@backlit/sdk'
import { z } from 'zod'

const orders = defineResource('orders', [
  fields.number('id', { label: 'Order' }),
  fields.text('customer', { label: 'Customer' }),
])

orders.action('create', z.object({}), () => {})

orders.lookup('picker', (_ctx, lookup) =>
  lookup
    .setFields(orders.pick('id', 'customer'))
    .withData([])
)

orders.view('list', () => callout('Order list'))

orders.view('overview', () =>
  callout(
    `${orders.name}: ${orders.getView('list')?.name}, ${orders.getAction('create')?.name}, ${orders.getLookup('picker')?.name}`
  )
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "overview",
    "slots": {
      "content": [
        {
          "kind": "callout",
          "text": "orders: list, create, picker"
        }
      ]
    }
  }
}

Groups and endpoint discovery

resource.group(fields) creates an anonymous group for a relation display. It checks field callbacks against the target resource record type. views(), actions(), and lookups() return registered definitions. toAgentTools() collects opted-in definitions. See Agent tools.

Was this page helpful?