Skip to content

Filter Options ​

Scope options set the values scopes start with, replace their default queries with SQL or model scopes, shape the options of a group and link scopes together. The scope properties and scope types list every option in one place.

The filters on this page ​

Most examples are built like those on Scope Types: a filter of posts with the scopes from its own YAML file, bound to a list of posts. The last two show their own methods.

php
/**
 * makeOptionFilters builds a filter for each example whose options are all in its YAML file, bound to a list of posts.
 *
 * @return array<string, array{filter: Filter, list: Lists}>
 */
protected function makeOptionFilters(): array
{
    $examples = [];

    foreach (['defaults', 'conditions', 'group-options', 'date-options', 'names'] as $name) {
        $list = $this->makeFilteredPosts(Str::camel($name).'List');

        $filter = Filter::make(
            alias: Str::camel($name).'Filter',
            model: new Post,
            scopes: "~/resources/ui/reference/filters/options/{$name}.yaml",
        );

        $filter->bindToListWidget($list);

        $examples[$name] = ['filter' => $filter, 'list' => $list];
    }

    return $examples;
}

/**
 * makeFilteredPosts builds a list of posts for a filter to filter.
 */
protected function makeFilteredPosts(string $alias): Lists
{
    return Lists::make(
        alias: $alias,
        model: new Post,
        columns: '~/resources/ui/reference/filters/posts.yaml',
        recordsPerPage: 5,
        noRecordsMessage: 'No posts match the filter.',
        customPageName: false,
    );
}

Default values ​

default sets the value a scope starts with, so these posts start without the archived ones and with only the approved status. A change to a scope lasts for the session, and Clear Filters goes back to the defaults.

A checkbox takes 1 to start on, a switch 1 or 2 for its first or second state, a dropdown the value of an option and a group a list of values. The popover types take an array of their values, such as a condition and a value.

yaml
scopes:
    hide_archived:
        label: Hide archived
        type: checkbox
        default: 1
        conditions: status <> 'archived'

    status:
        label: Status
        type: dropdown
        emptyOption: Any status
        default: approved
        options:
            draft: Draft
            review: In Review
            approved: Approved
            archived: Archived

Conditions ​

conditions replaces a scope's default query with SQL.

  • A checkbox takes one condition, applied while it is on, and a switch takes a list of two, one for each state.
  • A dropdown takes one condition, where :value is the picked option, and a group takes one where :value is the picked values, quoted and separated by commas.
  • Number, date and text scopes take one condition for each choice they offer, keyed by its name, where true keeps the default query. They offer only the named choices, and with a single choice the popover shows it as a label in place of a select, like the Address scope here.
ChoiceScope typesSQL placeholders
equalsnumber, date, text:value, and :valueDate for a date
notEqualsdate:value, :valueDate
betweennumber, datenumber: :min, :max. Date: :after, :afterDate, :before, :beforeDate
greaternumber:value
lessernumber:value
beforedate:value, :valueDate
afterdate:value, :valueDate
containstext:value

In a date condition, :value, :after and :before hold a date and time in the app timezone, while :valueDate, :afterDate and :beforeDate hold the date alone.

yaml
scopes:
    popular:
        label: Popular
        type: dropdown
        emptyOption: Any views
        conditions: views >= :value
        options:
            1000: 1,000 views or more
            5000: 5,000 views or more

    comment_count:
        label: Comments
        type: number
        conditions:
            greater: (select count(*) from comments where comments.post_id = posts.id) >= :value
            lesser: (select count(*) from comments where comments.post_id = posts.id) <= :value

    review_on:
        label: Review Date
        type: date
        conditions:
            before: review_on <= :valueDate
            after: review_on >= :valueDate

    slug:
        label: Address
        type: text
        conditions:
            contains: true

Empty option ​

emptyOption on a group adds an option for the records with no value, here the posts without tags. Picked with other options, it keeps the records with any of them or with none. It widens only its own scope, so the published switch and the search box still apply. On a dropdown, emptyOption is the option that applies no filter instead.

php
/**
 * makeEmptyOptionFilter filters by tag, offering an option for the untagged posts.
 */
protected function makeEmptyOptionFilter(Lists $list): Filter
{
    $filter = Filter::make(
        alias: 'emptyOptionFilter',
        model: new Post,
        scopes: '~/resources/ui/reference/filters/options/empty-option.yaml',
    );

    $filter->bindToListWidget($list);

    return $filter;
}

/**
 * makeEmptyOptionToolbar searches the same list as the filter.
 */
protected function makeEmptyOptionToolbar(Lists $list): Toolbar
{
    $toolbar = Toolbar::make(
        alias: 'emptyOptionToolbar',
        search: ['prompt' => 'Search posts...'],
    );

    $toolbar->bindToListWidget($list);

    return $toolbar;
}
yaml
scopes:
    published:
        label: Published
        type: switch
        conditions:
            - published = false
            - published = true

    tags:
        label: Tags
        type: group
        nameFrom: name
        emptyOption: Untagged

Group options ​

modelClass names the model whose records are a group's options, in place of the model of the relation the scope is named after. optionsScope applies one of that model's scopes to the options, here offering only the categories that hold posts. matchMode: toggle adds a choice between including and excluding the picked options, while matchMode: exclude always excludes them.

yaml
scopes:
    category:
        label: Category
        type: group
        modelClass: App\Models\Category
        nameFrom: name
        optionsScope: hasPosts
        matchMode: toggle
php
/**
 * scopeHasPosts limits the query to categories holding at least one post, used by the posts category filter.
 */
public function scopeHasPosts($query)
{
    return $query->has('posts');
}

Date picker options ​

minDate and maxDate limit the dates that can be picked, here to 2026, and yearRange sets how many years either side the calendar's year view offers. firstDay: 1 starts the week on Monday in place of the locale's first day, and showWeekNumber numbers the weeks. useTimezone: false compares the picked days with the stored dates as they are, in place of reading them in the viewer's timezone.

yaml
scopes:
    published_at:
        label: Published
        type: date
        minDate: '2026-01-01'
        maxDate: '2026-12-31'
        firstDay: 1
        showWeekNumber: true
        yearRange: 1
        useTimezone: false

Names, labels and order ​

valueFrom filters a column with a name other than the scope's, here published for the live scope, and shortLabel replaces the label in the bar. Scopes show in the order of the file, which before and after change, so the status scope here comes before the views, and order sets a position as a number. cssClass adds a class to the scope.

yaml
scopes:
    live:
        label: Published posts only
        shortLabel: Live
        type: checkbox
        valueFrom: published

    views:
        label: Views
        type: number
        cssClass: fst-italic

    status:
        label: Status
        type: group
        before: views
        options:
            draft: Draft
            review: In Review
            approved: Approved
            archived: Archived

Model scopes and dependent scopes ​

modelScope runs a model scope in place of the default query. The scope method receives the query and the filter scope, whose value holds the picked value, here filtering the users by their email domain. modelScope also takes a static method as Class::method, called with the query and the scope.

dependsOn links scopes together: when the named scope changes, this one is cleared and rendered again, so a picked name resets when the domain does. On a group with a modelClass, an optionsMethod given as a method name is called on that model with every scope and its current value, so it can narrow its options by another scope, as in $scopes->domain->value.

php
/**
 * makeFilteredUsers builds a list of users for the dependent scopes to filter.
 */
protected function makeFilteredUsers(): Lists
{
    return Lists::make(
        alias: 'dependsList',
        model: new User,
        columns: '~/resources/ui/reference/filters/users.yaml',
        recordsPerPage: 5,
        noRecordsMessage: 'No users match the filter.',
        customPageName: false,
    );
}

/**
 * makeDependsFilter filters the users by a model scope and a scope that resets when it changes.
 */
protected function makeDependsFilter(Lists $list): Filter
{
    $filter = Filter::make(
        alias: 'dependsFilter',
        model: new User,
        scopes: '~/resources/ui/reference/filters/options/depends.yaml',
    );

    $filter->bindToListWidget($list);

    return $filter;
}
yaml
scopes:
    domain:
        label: Email Domain
        type: dropdown
        emptyOption: All domains
        modelScope: applyDomain
        options:
            example.com: example.com
            example.org: example.org
            example.net: example.net

    name:
        label: Name
        type: group
        optionsMethod: App\Models\User::getNameGroupOptions
        dependsOn: domain
yaml
columns:
    name:
        label: Name

    email:
        label: Email
php
/**
 * Filter users by email domain, used by the "Email Domain" scope on the Filter Options reference page.
 */
public function scopeApplyDomain($query, $scope)
{
    return $query->where('email', 'like', '%@'.$scope->value);
}
php
/**
 * Options for the "Name" group filter scope.
 *
 * @return array<string, string>
 */
public static function getNameGroupOptions(): array
{
    return static::query()->orderBy('name')->pluck('name', 'name')->all();
}

Scope values ​

A model scope reads the scope's value from these properties.

Scope typeProperties
checkboxvalue, set while the checkbox is on.
switchvalue: 1 or 2 for the first or second state.
dropdownvalue: the picked option.
groupvalue: the picked values. mode: include or exclude.
datecondition, with value, or after and before for between.
numbercondition, with value, or min and max for between.
textcondition and value.