npx claudepluginhub naymspace/backpexThis skill uses the workspace's default tool permissions.
When the user wants to add a filter:
Guides creating custom Backpex field types implementing Backpex.Field behaviour, adding fields to LiveResource's fields/0 callback, and using built-in fields with options.
Builds filtering query builders for REST, GraphQL, and OpenAPI endpoints. Provides step-by-step guidance, production-ready code, and best practices for API filtering functionality.
Provides Elixir Ecto patterns for schemas, changesets, queries, associations, and transactions. Useful for building database-driven Elixir applications.
Share bugs, ideas, or general feedback.
When the user wants to add a filter:
filters/0 callback| Type | Use for | use module | Key callbacks |
|---|---|---|---|
| Boolean | Checkbox predicates (published yes/no) | Backpex.Filters.Boolean | options/1 returning %{label, key, predicate} maps |
| Select | Single-value dropdown | Backpex.Filters.Select | prompt/0, options/1 returning {label, value} tuples |
| MultiSelect | Multi-value dropdown with checkboxes | Backpex.Filters.MultiSelect | prompt/0, options/1 returning {label, value} tuples |
| Range | Date, datetime, or number ranges | Backpex.Filters.Range | type/0 returning :date, :datetime, or :number |
| Custom | Anything else | Backpex.Filter | query/4, render/1, render_form/1 |
All built-in filters auto-implement query/4, render/1, and render_form/1. Do NOT re-implement those unless you need custom behavior.
All filters have these overridable callbacks with defaults:
label/0: filter label (optional callback, can also be set in filters/0 map)can?/1: visibility control, receives assigns, default truetype/1: Ecto type for validation (default :string), receives assignschangeset/3: custom changeset validation (default: no-op)validate/2: public validation API, builds changeset from type/1 and changeset/3Multiple options selectable via checkboxes, combined with OR. Predicates use Ecto.Query.dynamic/2.
defmodule MyAppWeb.Filters.PostPublished do
use Backpex.Filters.Boolean
import Ecto.Query
@impl Backpex.Filter
def label, do: "Published?"
@impl Backpex.Filters.Boolean
def options(_assigns) do
[
%{label: "Published", key: "published", predicate: dynamic([x], x.published)},
%{label: "Not published", key: "not_published", predicate: dynamic([x], not x.published)}
]
end
end
Single-value dropdown. Default query/4 does WHERE field = value.
defmodule MyAppWeb.Filters.PostCategorySelect do
use Backpex.Filters.Select
import Ecto.Query
alias MyApp.Repo
@impl Backpex.Filter
def label, do: "Category"
@impl Backpex.Filters.Select
def prompt, do: "Select category ..."
@impl Backpex.Filters.Select
def options(_assigns) do
from(c in MyApp.Category, select: {c.name, c.id}, order_by: c.name) |> Repo.all()
end
end
Same as Select but allows multiple values. Default query/4 does WHERE field IN values. Uses prompt/0 (implement with @impl Backpex.Filters.Select) and options/1 (implement with @impl Backpex.Filters.MultiSelect). Note: MultiSelect internally sets @behaviour Backpex.Filters.Select for the prompt callback.
Renders "From" and "To" inputs. Note: type/0 is arity 0, not arity 1.
defmodule MyAppWeb.Filters.PostLikeRange do
use Backpex.Filters.Range
@impl Backpex.Filters.Range
def type, do: :number
@impl Backpex.Filter
def label, do: "Likes"
end
For dates use def type, do: :date. For datetimes use def type, do: :datetime.
Use Backpex.Filter directly when no built-in type fits. You must implement query/4, render/1, and render_form/1. Note: use Backpex.Filter does not import HEEx sigils. You need use Phoenix.Component for ~H support.
defmodule MyAppWeb.Filters.PostCustom do
use Phoenix.Component
use Backpex.Filter
import Ecto.Query
@impl Backpex.Filter
def label, do: "Custom"
@impl Backpex.Filter
def query(query, attribute, value, _assigns) do
where(query, [x], field(x, ^attribute) == ^value)
end
@impl Backpex.Filter
def render(assigns) do
~H"{@value}"
end
@impl Backpex.Filter
def render_form(assigns) do
~H"""
<input type="text" name={@form[@field].name} value={@value} class="input input-sm" />
"""
end
end
filters/0 returns a keyword list. Each key is the schema field atom being filtered.
@impl Backpex.LiveResource
def filters do
[
published: %{
module: MyAppWeb.Filters.PostPublished
},
category_id: %{
module: MyAppWeb.Filters.PostCategorySelect,
label: "Category"
},
likes: %{
module: MyAppWeb.Filters.PostLikeRange,
label: "Likes",
presets: [
%{label: "Over 100", values: fn -> %{"start" => 100, "end" => nil} end},
%{label: "1-99", values: fn -> %{"start" => 1, "end" => 99} end}
]
}
]
end
| Key | Required | Description |
|---|---|---|
:module | yes | The filter module |
:label | no | Overrides the module's label/0 |
:default | no | Pre-selected value on initial page load |
:presets | no | Quick-select shortcuts: [%{label: String.t(), values: (-> value)}] |
MyAppWeb.Filters.<Resource><FilterName> (e.g. MyAppWeb.Filters.PostPublished)lib/my_app_web/filters/<snake_case_name>.exfilters/0 must match the database column or foreign keyimport Ecto.Query when using dynamic/2 or writing custom queriesoptions/1 are fine since it runs at render time