Skip to content
Backlit
Esc
navigateopen⌘Jpreview
On this page

Fields

Describe the data type, the format, and the rendering of each piece of data in your app.

A field describes one piece of data: its name, its data type, its format, and how the client renders it. Fields are the base of most Backlit screens. A table uses fields for its columns, a datalist uses them for its labeled values, and a form uses them for its inputs.

This introduction explains what a field is, where you declare it, and how to select a field type. The page for each field type has the full options.

What a field is

The field name is the key that Backlit reads from your records. The field type says what the value is, such as text, a number, or a date. The options say what the value means and how to show it.

fields.number('total', { label: 'Total', format: 'currency', currency: 'USD' })

This declaration says that each record has a total key, that the value is a number, and that the number is an amount in US dollars. It does not say how to draw a currency amount. The client decides that, with the locale of the user. A field declares the meaning of a value, and the client owns the presentation.

A field is not a database column

A field does not know where its data comes from. Many fields have the name of a database column, because a view often returns database rows without changes. But Backlit reads the value from the record that your view supplies, and your view can build that record from any source.

Thus a field can describe a computed value, an aggregate, or a statistic. The openRate field below has no column. The query calculates it.

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

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

const newsletters = defineResource('newsletters', [
  fields.text('subject', { label: 'Subject' }),
  fields.number('recipients', { label: 'Recipients', format: 'integer' }),
  fields.number('openRate', { label: 'Open rate', format: 'percent' }),
])

newsletters.view('list', async () => {
  // Each row has `subject`, `recipients`, and `openRate`. The query calculates the
  // last two from the deliveries table.
  const rows = await db.newsletters.listWithDeliveryStats()

  return table(rows).setFields(newsletters.pick('subject', 'recipients', 'openRate'))
})

export default newsletters

Declare fields on a resource

Declare the fields of an entity one time, on its resource. Then select them where you need them. resource.pick returns several fields in the order that you give, and resource.get returns one field. The example below declares three fields and uses them as the columns of a table. The protocol output shows what the client receives.

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": []
        }
      ]
    }
  }
}

Because each screen uses the same declarations, a label or a format is written one time. When you change format on the resource, each table, datalist, and form that uses the field changes with it.

A screen can also adjust a field for its own use. configure and clone return a new builder and do not change the declaration on the resource. You can also create a field directly in a view, without a resource, for a value that only one screen shows.

Display and input

Each field has a read-only rendering and an input rendering. A date field is formatted text in a table and a date picker in a form. Backlit calls these two uses the display role and the input role, and the block selects the role. A table uses display, and a form uses input.

Two options refine the rendering inside a role. displayVariant selects the read-only presentation, such as badge for an enum. inputVariant selects the input control, such as textarea for text.

For each role, the SDK converts the value of your record to a protocol value before it sends the response. When a value cannot be converted, the SDK reports a defect and omits that value. The other values of the block stay valid. To add a role of your own, see Field roles.

Select a field type

Your data Field type
A string, an email address, a URL, a phone number, or Markdown Text
An integer, a decimal, a currency amount, a percentage, progress Number
A true or false value Boolean
A calendar date, a date and time, a time, or a relative date Date
One or more values from a fixed set of options, such as a status Enum
Several record keys that read as one value, such as an address Group
A record that the current record points to, such as the customer of an order Ref
Records that point back to the current record, such as the posts of an author Has

When no built-in type fits your data, an extension can add a field type. See Extensions.

Shared field options

Field options accept label, description, reveal, and enable where supported by the field contract. Use revealWhen and enableWhen to create configured copies from typed form conditions. See Form conditions. Simple fields also accept link: linkTo(view, args) for display navigation. Relation displays put that link on the target field.

configure and clone return new builders. They do not change the resource’s stored declaration. Most block setters change their builder and return it. Create data blocks and forms inside the resolver for each request.

Was this page helpful?