Filtr
Query params validation in Phoenix
Install / Use
/learn @Blatts12/FiltrREADME

Parameter validation library for Elixir with Phoenix integration.
Filtr provides a flexible, plugin-based system for validating and casting parameters in Phoenix applications. It offers seamless integration with Phoenix Controllers and LiveViews using an attr-style syntax similar to Phoenix Components.
Features
- Phoenix Integration - Seamless Controller and LiveView support
- Plugin System - Extend with custom types and validators
- Multiple Error Modes - Fallback, strict, and raise modes with per-field overrides
- Zero Dependencies - Lightweight core library
- attr-like
parammacro - Familiar, declarative syntax just like for Phoenix Components - Nested Schemas - Deep nesting with
param ... do...endmacro syntax
Requirements
- Elixir ~> 1.16
- Phoenix >= 1.6.0 (optional, for Controller/LiveView integration)
- Phoenix LiveView >= 0.20.0 (optional, for LiveView integration)
Installation
Add filtr to your list of dependencies in mix.exs:
def deps do
[
{:filtr, "~> 0.4.0"}
]
end
Formatter
To ensure proper formatting of the param macro, add :filtr to your .formatter.exs configuration:
[import_deps: [:filtr]]
Quick Start
Phoenix Controller
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
use Filtr.Controller, error_mode: :raise
param :name, :string, required: true
param :age, :integer, min: 18, max: 120
param :email, :string, required: true, pattern: ~r/@/
def create(conn, params) do
# params.name is guaranteed to be a string
# params.age is guaranteed to be an integer between 18 and 120
# params.email is guaranteed to be a string containing "@"
json(conn, %{message: "User #{params.name} created"})
end
# Nested parameters
param :filters do
param :q, :string, default: ""
param :page, :integer, default: 1, min: 1
param :category, :string, in: ["books", "movies", "music"], default: "books"
end
def search(conn, params) do
json(conn, %{query: params.filters.q, page: params.filters.page})
end
# List of nested schema
param :items, :list do
param :name, :string, required: true
param :quantity, :integer, min: 1, default: 1
end
def order(conn, params) do
# params.items is a list
json(conn, %{items: params.items})
end
end
Phoenix LiveView
defmodule MyAppWeb.SearchLive do
use MyAppWeb, :live_view
use Filtr.LiveView, error_mode: :raise
param :query, :string, required: true, min: 1
param :limit, :integer, default: 10, min: 1, max: 100
# Nested parameters for better organization
param :filters do
param :category, :string, default: "all"
param :sort, :string, in: ["name", "date"], default: "name"
end
# List of nested schemas
param :tags, :list do
param :label, :string, required: true
param :color, :string, in: ["red", "blue", "green"], default: "blue"
end
def mount(_params, _session, socket) do
# socket.assigns.filtr contains validated params
# socket.assigns.filtr.query - validated query string
# socket.assigns.filtr.limit - validated limit (defaults to 10)
# socket.assigns.filtr.filters.category - validated category
# socket.assigns.filtr.filters.sort - validated sort
# socket.assigns.filtr.tags - validated list of tag objects
{:ok, socket}
end
def handle_params(_params, _uri, socket) do
# Params are automatically revalidated on navigation
{:noreply, socket}
end
end
Standalone Usage
schema = %{
name: [type: :string, validators: [required: true, min: 2]],
age: [type: :integer, validators: [min: 18, max: 120]],
tags: [type: {:list, :string}, validators: [max: 5]]
}
params = %{
"name" => "John Doe",
"age" => "25",
"tags" => ["elixir", "phoenix"]
}
result = Filtr.run(schema, params, error_mode: :raise)
# %{name: "John Doe", age: 25, tags: ["elixir", "phoenix"]}
Error Modes
Filtr supports three error handling modes:
Fallback Mode (Default)
Returns default values or nil on validation errors:
schema = %{age: [type: :integer, validators: [default: 18]]}
params = %{"age" => "invalid"}
Filtr.run(schema, params, error_mode: :fallback)
# %{age: 18}
Strict Mode
Returns error tuples for invalid values:
schema = %{age: [type: :integer, validators: [min: 18]]}
params = %{"age" => "10"}
Filtr.run(schema, params, error_mode: :strict)
# %{age: {:error, ["must be at least 18"]}}
Raise Mode
Raises exceptions on validation errors:
schema = %{name: [type: :string, validators: [required: true]]}
params = %{}
Filtr.run(schema, params, error_mode: :raise)
# ** (RuntimeError) Invalid value for name: required
Global Configuration
Set a default error mode in your config:
# config/config.exs
config :filtr, error_mode: :strict
Or specify per-module:
use Filtr.Controller, error_mode: :strict
use Filtr.LiveView, error_mode: :raise
Validation Status
Filtr automatically includes a _valid? field in the result map to indicate whether all parameters passed validation. This field is always present regardless of error mode.
schema = %{
name: [type: :string, validators: [required: true]],
age: [type: :integer, validators: [min: 18]]
}
# Valid params
params = %{"name" => "John", "age" => "25"}
result = Filtr.run(schema, params, error_mode: :strict)
# %{name: "John", age: 25, _valid?: true}
# Invalid params
params = %{"age" => "10"}
result = Filtr.run(schema, params, error_mode: :strict)
# %{name: {:error, ["required"]}, age: {:error, [...]}, _valid?: false}
This makes it easy to check validation status without inspecting individual fields:
defmodule MyAppWeb.UserController do
use Filtr.Controller, error_mode: :strict
param :name, :string, required: true
param :email, :string, required: true, pattern: ~r/@/
def create(conn, params) do
if params._valid? do
# All params are valid, proceed with creation
json(conn, %{message: "User #{params.name} created"})
else
# Some params have errors, collect and return them
conn
|> put_status(:bad_request)
|> json(%{errors: collect_errors(params)})
end
end
end
Advanced Features
Nested Schemas
Filtr supports nested schemas with two syntaxes:
Macro Syntax (Controllers & LiveViews)
Use the param do...end syntax for clean, declarative nested schemas:
defmodule MyAppWeb.UserController do
use Filtr.Controller
param :user do
param :name, :string, required: true
param :email, :string, required: true, pattern: ~r/@/
param :age, :integer, min: 18
end
param :settings do
param :theme, :string, in: ["light", "dark"], default: "light"
param :notifications, :boolean, default: true
end
def create(conn, params) do
# params.user.name
# params.user.email
# params.settings.theme
json(conn, %{message: "User #{params.user.name} created"})
end
end
Deep nesting is fully supported:
param :company do
param :name, :string, required: true
param :headquarters do
param :country, :string, default: "US"
param :contact do
param :email, :string, required: true
param :phone, :string, default: ""
end
end
end
# Access: params.company.headquarters.contact.email
Map Syntax (Standalone)
For standalone usage, use the map syntax:
schema = %{
user: %{
name: [type: :string, validators: [required: true]],
email: [type: :string, validators: [required: true]]
},
settings: %{
theme: [type: :string, validators: [in: ["light", "dark"]]],
notifications: [type: :boolean]
}
}
params = %{
"user" => %{
"name" => "John",
"email" => "john@example.com"
},
"settings" => %{
"theme" => "dark",
"notifications" => "true"
}
}
result = Filtr.run(schema, params)
# %{
# user: %{name: "John", email: "john@example.com"},
# settings: %{theme: "dark", notifications: true}
# }
List of Nested Schemas
Filtr supports lists containing nested schema, allowing you to validate arrays of complex objects.
Macro Syntax (Controllers & LiveViews)
Use the param :field, :list do...end syntax for clean, declarative list schemas:
defmodule MyAppWeb.OrderController do
use Filtr.Controller
# List of items with nested validation
param :items, :list do
param :name, :string, required: true
param :quantity, :integer, min: 1, default: 1
param :price, :float, min: 0
end
def create(conn, params) do
# params.items is a list of validated item objects
# Each item has validated name, quantity, and price fields
total = Enum.reduce(params.items, 0, fn item, acc ->
acc + (item.quantity * item.price)
end)
json(conn, %{total: total})
end
end
LiveView with URL Parameters:
defmodule MyAppWeb.UserLive do
use Filtr.LiveView
param :users, :list do
param :name, :string, required: true
param :age, :integer, min: 18
end
def mount(_params, _session, socket) do
# socket.assigns.filtr.users is a validated list
{:ok, socket}
end
end
# URL format: /users?users[0][name]=John&users[0][age]=25&users[1][name]=Jane&users[1][age]=30
# Converts to: [%{name: "John", age: 25}, %{name: "Jane", age: 30}]
Map Syntax (Standalone)
For standalone usage, use the map syntax with {:list, schema}:
schema = %{
items: [
type: {
:list,
%{
name: [type: :string, validators: [required: true]],
quantity: [type: :integer, validators: [min: 1]]
}
}
]
}
params = %{
"items" => [
%{"name" => "Product A", "quantity" => "5"},
%{"name" => "Product B", "quantity" => "3"}
]
}
result = Filtr.run(schema, param
Related Skills
node-connect
353.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
353.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
353.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
