Validating list of emails in the changeset

I am trying to validate list of emails through ecto changeset. The field is defined in the schema as:

    field(:cc_email, {:array, :string})

It was :string before and I was validating it like this:

defp validate_email_format(changeset, field) do
    |> validate_format(field, MyApp.Accounts.User.email_regex(), message: "is invalid")
    |> validate_length(field, max: 160)

How can I modify this function to validate email list now?

One way is to use the Ecto.Changeset.validate_change/3 function instead of validate_format. You will have to figure out how to write the callback function yourself, but there is an example in the documentation :slight_smile:

To add to this: changeset validations (and their errors) are on a per field basis. So you either need to have a custom validation for checking the individual items of an array of them as suggested, or if you want more fine grained errors/validations consider embeds_many over arrays of something.

1 Like

Thank you, this is what I needed.

You’d definitely have to create a custom validator using Ecto.Changeset.validate_change/3.
If you want to use the existing API for a single element, then you can use schemaless changeset with a single element like this:

defmodule MyHelper do
  def validate_array(changeset, field, map_single_element_changeset_callback) do
    changeset = Ecto.Changeset.change(changeset)
    {:array, single_element_type} = changeset.types[field]

    |> Ecto.Changeset.validate_change(field, fn _, values ->
      error =
        Enum.find_value(values, fn value ->
          {%{}, %{value: single_element_type}}
          |> Ecto.Changeset.cast(%{value: value}, [:value])
          |> map_single_element_changeset_callback.()
          |> Ecto.Changeset.apply_action(:insert)
          |> case do
            {:ok, _} ->

            {:error, changeset} ->
              {message, _meta} = changeset.errors[:value]
              {message, value}

      case error do
        nil ->

        {message, value} ->
          [{field, "contains invalid element #{value}: #{message}"}]


> {%{}, %{emails: {:array, :string}}}
|> Ecto.Changeset.cast(%{emails: ["", "invalid[at]"]}, [:emails])
|> MyHelper.validate_array(:emails, fn changeset -> changeset |> Ecto.Changeset.validate_format(:value, ~r/@/) end)

  action: nil,
  changes: %{emails: ["", "invalid[at]"]},
  errors: [
    emails: {"contains invalid element invalid[at] has invalid format",
  data: %{},
  valid?: false
1 Like