phcurado
Zoi - schema validation library inspired by Zod
Zoi is a new schema validation library for Elixir.
It’s inspired by Zod from the JavaScript ecosystem, bringing a similar functional API for defining, validating, transforming and coercing data, using elixir pipes for the schema definition.
Basic Example
iex> schema = Zoi.string() |> Zoi.min(3)
iex> Zoi.parse(schema, "hello")
{:ok, "hello"}
iex> Zoi.parse(schema, "hi")
{:error, [%Zoi.Error{message: "too small: must have at least 3 characters"}]}
Transformations
iex> schema = Zoi.string() |> Zoi.trim()
iex> Zoi.parse(schema, " world ")
{:ok, "world"}
Coercion
iex> Zoi.string() |> Zoi.parse(123)
{:error, [%Zoi.Error{message: "invalid type: must be a string"}]}
iex> Zoi.string(coerce: true) |> Zoi.parse(123)
{:ok, "123"}
Complex schemas
iex> user_schema =
...> Zoi.object(%{
...> name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
...> age: Zoi.integer() |> Zoi.min(18) |> Zoi.max(120),
...> email: Zoi.email()
...> })
iex> Zoi.parse(user_schema, %{name: "Alice", age: 30, email: "alice@email.com"})
{:ok, %{name: "Alice", age: 30, email: "alice@email.com"}}
There are many built in types, validations and transformations. Check out the documentation for all the possibilities.
Docs: Zoi — Zoi v0.17.0
Hex: zoi | Hex
Repo:
I would love to hear your feedback, thoughts and any additional features you’d like to see
Most Liked
phcurado
v0.10 released with Phoenix form support 
Hi everyone! I finally released the 0.10 version of Zoi, now you can use your Zoi schemas directly in Phoenix forms.
This release implements the Phoenix.HTML.FormData protocol, which allows you to use Zoi.Context structs as form data sources.
Quick example:
# Define schema inline
@user_schema Zoi.object(%{
name: Zoi.string() |> Zoi.min(3),
email: Zoi.email()
}) |> Zoi.Form.prepare()
# Parse and render (just like changesets!)
ctx = Zoi.Form.parse(@user_schema, params)
form = to_form(ctx, as: :user)
socket |> assign(:form, form)
# Use in your forms
~H"""
<.form for={@form} phx-submit="save">
<.input field={@form[:name]} label="Name" />
<.input field={@form[:email]} label="Email" />
<div>
<.button>Save</.button>
</div>
</.form>
"""
Guides
- Phoenix Forms Integration Guide - Detailed guide on integrating with phoenix
- Localizing Errors with Gettext Guide - For more information on how to localize error messages.
phcurado
New 0.5 version released 
Added
atom type schema
Useful for verifying atom types:
schema = Zoi.atom()
Zoi.parse(schema, :hello)
#=> {:ok, :hello}
Zoi.parse(schema, "world")
#=> {:error, [%Zoi.Error{message: "invalid type: must be an atom", path: []}]}
which can be also useful for validating maps with atom keys:
schema = Zoi.map(Zoi.atom(), Zoi.string())
Zoi.parse(schema, %{name: "John"})
#=> {:ok, %{name: "John"}}
Zoi.parse(schema, %{"name" => "John"})
#=> {:error, [%Zoi.Error{message: "invalid type: must be an atom", path: ["name"]}]}
Union and intersection custom errors
Now we can give custom errors to unions (logical OR) and intersections (logical AND)
schema = Zoi.union([Zoi.float(), Zoi.integer()], error: "something went wrong")
Zoi.parse(schema, "not a number")
#=> {:error, [%Zoi.Error{message: "something went wrong", path: []}]}
string boolean type schema
useful when parsing boolean from different sources, good for environment variables or integration with external APIs:
schema = Zoi.string_boolean()
Zoi.parse(schema, "true")
#=> {:ok, true}
Zoi.parse(schema, "1")
#=> {:ok, true}
Zoi.parse(schema, "off")
#=> {:ok, false}
Now Zoi have all types Zod have, with some additions to fit in the elixir ecosystem ![]()
phcurado
New release 
Two new types added to Zoi: struct and literal types
Struct type
Now Zoi supports struct types, to validate struct and it’s fields:
defmodule MyApp.User do
defstruct [:name, :age, :email]
end
schema = Zoi.struct(MyApp.User, %{
name: Zoi.string() |> Zoi.min(2) |> Zoi.max(100),
age: Zoi.integer() |> Zoi.min(18) |> Zoi.max(120),
email: Zoi.email()
})
Zoi.parse(schema, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"})
#=> {:ok, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"}}
Zoi.parse(schema, %{})
#=> {:error, "invalid type: must be a struct"}
by default it will try to validate the struct and it’s contents but you can also coerce the input (if it’s a map for example) to be converted to the struct:
schema = Zoi.struct(MyApp.User, %{
name: Zoi.string(),
age: Zoi.integer(),
email: Zoi.email()
}, coerce: true)
Zoi.parse(schema, %{name: "Alice", age: 30, email: "alice@email.com"})
#=> {:ok, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"}}
# Also with string keys
Zoi.parse(schema, %{"name" => "Alice", "age" => 30, "email" => "alice@email.com"})
#=> {:ok, %MyApp.User{name: "Alice", age: 30, email: "alice@email.com"}}
There are also some helpers for creating structs: struct fields, defaults and the @enforce_keys helper functions:
defmodule MyApp.User do
@schema Zoi.struct(__MODULE__, %{
name: Zoi.string()
age: Zoi.integer() |> Zoi.default(0) |> Zoi.optional(),
email: Zoi.string()
})
@enforce_keys Zoi.Struct.enforce_keys(schema) # [:name]
defstruct Zoi.Struct.struct_fields(schema) # [:name, :email, {:age, 0}]
end
Literal type
As the name suggest, this type represents a literal value. The input value should always match the defined literal:
schema = Zoi.literal(true)
Zoi.parse(schema, true)
{:ok, true}
Zoi.parse(schema, :other_value)
{:error, [%Zoi.Error{message: "invalid type: does not match literal"}]}
schema = Zoi.literal(42)
Zoi.parse(schema, 42)
{:ok, 42}
Zoi.parse(schema, 43)
{:error, [%Zoi.Error{message: "invalid type: does not match literal"}]}
phcurado
v0.7 release with OpenAPI support 
This new version introduces the new Zoi.to_json_schema/1 function, which allows you to convert your Zoi schemas into JSON Schema format. This is useful for generating API documentation and other use cases where JSON Schema is required.
schema = Zoi.object(%{
name: Zoi.string(metadata: [description: "The name of the person"]),
})
Zoi.to_json_schema(schema)
# %{
# "$schema": "https://json-schema.org/draft/2020-12/schema",
# type: :object,
# required: [:name],
# properties: %{name: %{type: :string, description: "The name of the person"}},
# additionalProperties: false
# }
This feature allow us to generate OpenAPI documentation from Zoi schemas, since the 3.1 version is 100% compatible with JSON Schema.
Checkout the Zoi OpenAPI guide for more details, it shows how to use Zoi with the Oaskit library.
I also created a quickstart guide to help you get started with Zoi.
th3mus1cman
Will have to check this out. I really like Zod so this looks pretty nice. Thanks!!







