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.