191 words, 1 min read

Ecto changesets provide built-in validations like validate_required/2 and validate_length/3, but sometimes you need custom validation logic. In this post, we'll explore how to implement custom validations in Ecto.

Let's say we have a User schema, and we need to ensure that the age field is at least 18.

We can define the schema like this and add a the custom validator validate_age:

defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :name, :string
field :age, :integer
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :age])
|> validate_required([:name, :age])
|> validate_age()
end
defp validate_age(changeset) do
validate_change(changeset, :age, fn :age, age ->
if age < 18 do
[{:age, "must be at least 18"}]
else
[]
end
end)
end
end

Another approach is to check the value directly and add an error manually:

defp validate_age(changeset) do
age = get_change(changeset, :age, get_field(changeset, :age))
if age && age < 18 do
add_error(changeset, :age, "must be at least 18")
else
changeset
end
end

When to use each approach:

  • validate_change/3 is useful for concise validations that return lists of errors.
  • add_error/3 is better when checking multiple fields or requiring more control over the validation logic.