# Lists and filters

> Describe admin lists in config_list.yaml and columns.yaml, with search, sorting, pagination options and switch, date range and scope filters.

The List behaviour's `config_list.yaml`, the model's `columns.yaml` and the Filter widget's `config_filter.yaml` keep their WinterCMS shape. [cabana](/docs/api/cabana.md) compiles them at boot and runs every list query itself, so search, sort and filter parameters from the request apply only to what the YAML declares.

## config_list.yaml

```yaml
list: ~/plugins/acme/blog/models/post/columns.yaml
modelClass: Post
title: acme.blog::lang.posts.title
recordUrl: acme/blog/posts/update/:id
recordsPerPage: 20
perPageOptions: [20, 50, 100]
showCheckboxes: true
defaultSort:
    column: published_at
    direction: desc
filter: config_filter.yaml
toolbar:
    buttons: [create, delete]
    search:
        prompt: backend::lang.list.search_prompt
```

| Key | Sets |
|-----|------|
| `list` | The columns file, with a WinterCMS `~/plugins/...` path into the plugin. |
| `modelClass` | Must equal the controller's model name. |
| `title`, `noRecordsMessage` | Translation keys shown by the SPA. |
| `recordUrl` | Where a click on a row goes; `:id` is replaced. |
| `recordsPerPage`, `perPageOptions` | The page size and the sizes a user may pick. |
| `showCheckboxes`, `showSetup`, `showSorting`, `showSearch` | Which list controls appear. |
| `defaultSort` | `column` and `direction` of the initial order. |
| `toolbar` | `buttons` (the built-in `create` and `delete` and registered actions) and `search.prompt`. |
| `filter` | The filter file, relative to the controller's directory. |
| `headerPartial` | A server-rendered strip above the list; see [Partials and widgets](/docs/backend/partials-and-widgets.md). |

## columns.yaml

```yaml
columns:
    title:
        label: acme.blog::lang.posts.title_column
        searchable: true
    published:
        label: acme.blog::lang.posts.published
        type: switch
    published_at:
        label: acme.blog::lang.posts.published_at
        type: datetime
```

A column takes `label`, `type` (`text`, `datetime` or `switch`), `searchable` (default false) and `sortable` (default true, as in WinterCMS), and, for a column read through a relation, `relation` and `select`. Other WinterCMS column types and options are refused at boot. The compiled list keeps the columns in file order:

```go
// The plugin embeds its controllers/ and models/ trees; the example reads
// the same files from testdata.
fsys := os.DirFS("testdata/docs")
list, err := cabana.CompileList("acme.blog", PostsController{}, fsys)
if err != nil {
	fmt.Println(err)
	return
}
fmt.Println(list.Title, list.RecordsPerPage, list.PerPageOptions, list.ToolbarButtons)
for _, c := range list.Columns {
	fmt.Printf("column %s type=%q searchable=%v sortable=%v\n", c.Key, c.Type, c.Searchable, c.Sortable)
}
for _, f := range list.Filters {
	fmt.Printf("filter %s type=%s column=%s\n", f.Name, f.Type, f.Column)
}
// Output:
// acme.blog::lang.posts.title 20 [20 50 100] [create delete]
// column title type="" searchable=true sortable=true
// column published type="switch" searchable=false sortable=true
// column published_at type="datetime" searchable=false sortable=true
// filter published type=switch column=published
// filter published_at type=daterange column=published_at
```

Search runs over the searchable columns only; sort accepts only sortable columns. A request that names any other column is refused, never passed to SQL.

## Filters

`config_filter.yaml` lists scopes. Three filter types are supported:

```yaml
scopes:
    published:
        label: acme.blog::lang.posts.published
        type: switch
        column: published
    published_at:
        label: acme.blog::lang.posts.published_at
        type: daterange
        column: published_at
```

| Type | Filters |
|------|---------|
| `switch` | A boolean `column`. With `options`, the two values are the options' keys, kept as typed scalars. |
| `daterange` | A date or timestamp `column` between two dates. |
| scope (a `scope` key with `modelClass` and `nameFrom`) | Records by a model-backed choice. The model implements `pact.FilterScope`, whose `FilterScopes` lists the scope names it answers, and `pact.FilterOptions` for the choices the SPA loads. |

WinterCMS `conditions` SQL fragments are not supported; use a `column` or a scope the model implements. A scope filter whose name the model does not list in `FilterScopes` stops the start-up, so request text can never select another method.

## Scoping every list

To restrict which records an administrator sees at all, implement `pact.ListExtendQuery` on the controller. It receives the list query before search, filters and pagination are applied, so the restriction holds for every request. See [Admin controllers](/docs/backend/admin-controllers.md) for the other hooks.
