Skip to content
Backlit
Esc
navigateopen⌘Jpreview
On this page

Filters

Declare the controls that narrow the data of a view or a zone.

A filter describes one query value and the control that changes it. The user changes the control, the value goes into the URL, and your view reads the typed value and applies it to its query.

A filter is not a field. A field describes a piece of data in a record. A filter describes a question about the records, and one filter can search many columns or none. Thus filters have their own builders, and they do not accept field declarations.

This introduction explains how a filter goes from a declaration to a query. The page for each filter type has the full options.

The URL is the state

The current value of each filter is in the query string and nowhere else. The protocol response describes the controls and does not contain their values. This has three results. A filtered list has a URL that a user can share or bookmark. The back button of the browser restores the previous filters. And a reload shows the same records.

The four steps of a filter

Declare. defineFilters creates a filter bar from an array of filters. The order of the array is the order of the controls.

const listFilters = defineFilters([
  filters.text('search').setLabel('Search'),
  filters.select('status', statusOptions).setLabel('Status'),
])

Bind. A filter bar has no place in the URL until you register it on a view or a zone. The owner gives the filters their scope. A view filter uses f[name] in the query string, and a zone filter uses f[zone][name], so two zones on one page can each have a status filter.

orders.view('list', { filters: listFilters }, async (ctx, view) => {
  // ...
})

Read. ctx.parse(view).filters returns one typed property for each filter. A value that is missing or not valid is undefined. Backlit parses the values and does not apply them. Your callback applies them to your query, so you decide what “search” means for your data.

const { filters: applied } = ctx.parse(view)

const rows = await db.orders.list({
  search: applied.search,
  status: applied.status,
})

Place. A filter bar is not a content block. A section and a panel each have a separate slot for it, which you set with setFilters. The controls then render in the header of that block.

return section('Orders')
  .setFilters(view.state.filters)
  .setContent([panel([table(rows).setFields(columns)])])

When the owner of the filter bar is a zone, a change to a control requests only that zone. See Binding and placement.

Select a filter type

Your need Filter
Search with free text Text
Select yes, no, or no selection Toggle
Select one or more values from a fixed set Select
Select the records between two calendar dates Date range
Select the records between two numbers Number range
Select the grouping unit of a chart, such as day or month Period

A select filter with one value can also render as a row of buttons above a list. See the segments block.

Filters are one of three types of search state. A paginator and a sorter use the same steps: declare, bind, read, and place. See Search state.

API

defineFilters(filters: FilterBuilderContract[]): FilterBar

Filter names can contain letters, numbers, underscores, and hyphens. Each name must be unique in one filter bar.

Declaration

Declare related filters together. Their array order is their control order.

import {
  defineFilters,
  defineResource,
  filters,
  section,
} from '@backlit/sdk'

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

const orderFilters = defineFilters([
  filters.text('search').setLabel('Search orders'),
  filters.toggle('paid').setLabel('Paid'),
])

orders.view(
  'list',
  { filters: orderFilters },
  (_ctx, view) =>
    section('Orders').setFilters(view.state.filters)
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "list",
    "slots": {
      "content": [
        {
          "kind": "section",
          "title": "Orders",
          "slots": {
            "content": [],
            "filters": {
              "kind": "filters",
              "resource": "orders",
              "view": "list",
              "keyword": "f",
              "definesResult": true,
              "dependsOnResult": false,
              "filters": [
                {
                  "kind": "text",
                  "name": "search",
                  "label": "Search orders"
                },
                {
                  "kind": "toggle",
                  "name": "paid",
                  "label": "Paid"
                }
              ]
            }
          }
        }
      ]
    }
  }
}

Label

Every filter supports setLabel. If you do not set a label, the SDK sends an empty label.

import {
  defineFilters,
  defineResource,
  filters,
  section,
} from '@backlit/sdk'

const orders = defineResource('orders', [])
const orderFilters = defineFilters([
  filters.text('search').setLabel('Search orders'),
])

orders.view(
  'list',
  { filters: orderFilters },
  (_ctx, view) =>
    section('Orders').setFilters(view.state.filters)
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "list",
    "slots": {
      "content": [
        {
          "kind": "section",
          "title": "Orders",
          "slots": {
            "content": [],
            "filters": {
              "kind": "filters",
              "resource": "orders",
              "view": "list",
              "keyword": "f",
              "definesResult": true,
              "dependsOnResult": false,
              "filters": [
                {
                  "kind": "text",
                  "name": "search",
                  "label": "Search orders"
                }
              ]
            }
          }
        }
      ]
    }
  }
}

All filter types

One filter bar can contain each built-in filter type. A select filter can also accept several selected values.

import {
  defineFilters,
  defineResource,
  filters,
  section,
} from '@backlit/sdk'

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

const orderFilters = defineFilters([
  filters.text('search').setLabel('Search orders'),
  filters.toggle('paid').setLabel('Paid'),
  filters
    .select('status', [
      { value: 'open', label: 'Open' },
      { value: 'shipped', label: 'Shipped' },
      { value: 'cancelled', label: 'Cancelled' },
    ])
    .setLabel('Status')
    .setCardinality('many'),
  filters.dateRange('placed').setLabel('Order date'),
  filters
    .period('period', ['week', 'month', 'quarter', 'year'])
    .setLabel('Group by'),
])

orders.view(
  'list',
  { filters: orderFilters },
  (_ctx, view) =>
    section('Orders').setFilters(view.state.filters)
)

export default orders
{
  "kind": "success",
  "status": 200,
  "node": {
    "kind": "view",
    "resource": "orders",
    "name": "list",
    "slots": {
      "content": [
        {
          "kind": "section",
          "title": "Orders",
          "slots": {
            "content": [],
            "filters": {
              "kind": "filters",
              "resource": "orders",
              "view": "list",
              "keyword": "f",
              "definesResult": true,
              "dependsOnResult": false,
              "filters": [
                {
                  "kind": "text",
                  "name": "search",
                  "label": "Search orders"
                },
                {
                  "kind": "toggle",
                  "name": "paid",
                  "label": "Paid"
                },
                {
                  "kind": "select",
                  "name": "status",
                  "label": "Status",
                  "options": [
                    {
                      "value": "open",
                      "label": "Open"
                    },
                    {
                      "value": "shipped",
                      "label": "Shipped"
                    },
                    {
                      "value": "cancelled",
                      "label": "Cancelled"
                    }
                  ],
                  "cardinality": "many"
                },
                {
                  "kind": "date-range",
                  "name": "placed",
                  "label": "Order date"
                },
                {
                  "kind": "period",
                  "name": "period",
                  "label": "Group by",
                  "units": [
                    "week",
                    "month",
                    "quarter",
                    "year"
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  }
}

Was this page helpful?