Changelog
View SourceAll notable changes to this project will be documented in this file.
0.17.0 - 2026-01-30
Added
unrecognized_keysoption forZoi.map/2,Zoi.object/2,Zoi.keyword/2, andZoi.struct/3to control how unrecognized keys are handled::strip(default) - removes unrecognized keys from the output:error- returns an error when unrecognized keys are present:preserve- keeps unrecognized keys in the output without validation (not available for structs){:preserve, {key_schema, value_schema}}- preserves unrecognized keys and validates them against the given schemas (not available for structs)
deprecatedoption for all schema types to emit deprecation warnings during parsing.Zoi.describe/1will also include the deprecation message in the generated documentationZoi.to_json_schema/1now emitsdeprecated: truewhen a schema is marked as deprecated- Multi-line description support in
Zoi.describe/1with proper indentation
Changed
strictoption is now deprecated in favor ofunrecognized_keys. Useunrecognized_keys: :errorinstead ofstrict: trueZoi.extend/3now uses options from schema1 instead of merging options from both schemas- Improved
Zoi.Describe.Encoderoutput format:- Enum now uses
|separator instead of "one of" - Union now uses
|separator instead of "or" - Tuple now uses
{X, Y}format instead of "tuple of X, Y values"
- Enum now uses
0.16.1 - 2026-01-20
Added
Zoi.lazy/1now supports MFA tuples{module, function, args}in addition to anonymous functions. This enables lazy schemas to be stored in module attributes and used at compile time:# MFA tuple, can be sued runtime and compiletime @schema Zoi.lazy({MyModule, :user_schema, []}) # Anonymous function, can be used runtime but not compiletime (like module attributes) Zoi.lazy(fn -> user_schema() end)
0.16.0 - 2026-01-19
Added
Zoi.discriminated_union/3type for creating discriminated unions (#138)
Changed
- Add sponsor section to documentation (#140)
- Fix Elixir 1.20 deprecation warnings (#141)
- Fix string keys parsing on maps when coercion is enabled (#139)
0.15.0 - 2026-01-05
Added
Zoi.pid/1type for validating pid valuesZoi.module/1type for validating module valuesZoi.reference/1type for validating reference valuesZoi.port/1type for validating port valuesZoi.macro/1type for validating quoted expressions (Macro.t())typespecoption for all types to override generated typespec:Zoi.integer(gte: 0, typespec: quote(do: non_neg_integer())) Zoi.any(typespec: quote(do: pos_integer()))
0.14.1 - 2026-01-02
Changed
Zoi.to_json_schema/1now preserves string refinements (pattern, format) when encoding nested schemas insideZoi.map/2,Zoi.array/1,Zoi.lazy/1,Zoi.default/2, andZoi.codec/3. Previously, types likeZoi.uuid()andZoi.email()would lose their regex pattern when nested inside an object.
0.14.0 - 2025-12-22
Added
Zoi.map/2now supports field-based mode with%{field: type}notation, following Elixir's type system where fields are required by defaultZoi.function/1type for validating function values with optional arity constraintZoi.struct/1now accepts just a module to validate struct type without field validationZoi.Describe.Encoderprotocol for generating human-readable type descriptionsZoi.json/1type for validating any JSON-compatible value (string, number, boolean, null, array, or object with string keys)Zoi.map/2now acceptscoerce: trueoption to convert structs to maps viaMap.from_struct/1, enabling validation of database structs for API output
Changed
Zoi.object/2is now an alias for field-basedZoi.map/2. Both functions work identicallyZoi.Types.Objecthas been consolidated intoZoi.Types.Map. TheZoi.object/2API remains unchangedZoi.keyword/2default behavior: defaults now apply correctly when parsing keyword list with missing keys
0.13.1 - 2025-12-19
Changed
- Add references on documentation of
0.13release
0.13.0 - 2025-12-19
Added
Zoi.codec/3for bidirectional transformations between types. Codecs enable parsing from one type to another and encoding back:date_codec = Zoi.codec( Zoi.ISO.date(), Zoi.date(), decode: fn value -> Date.from_iso8601(value) end, encode: fn value -> Date.to_iso8601(value) end ) {:ok, ~D[2025-01-15]} = Zoi.parse(date_codec, "2025-01-15") {:ok, "2025-01-15"} = Zoi.encode(date_codec, ~D[2025-01-15])Zoi.encode/3andZoi.encode!/3functions to encode values using a codec's encode function.Zoi.multiple_of/3refinement for validating that a number is a multiple of a given value. Works withinteger/0,float/0,number/0, anddecimal/0types:Zoi.TypeSpecprotocol for opt-in Elixir type specification generation. Custom types can now implement this protocol separately fromZoi.Type:defimpl Zoi.TypeSpec do def spec(_schema, _opts) do quote(do: binary()) end end
Changed
Zoi.JSONSchema.EncoderforZoi.object/2now respects thestrictoption. Whenstrict: true, the generated JSON Schema will haveadditionalProperties: false. Whenstrict: false(default), it will haveadditionalProperties: true.type_spec/2moved fromZoi.Typeprotocol to the newZoi.TypeSpecprotocol. This is not a breaking change as the public API (Zoi.type_spec/1) remains unchanged.
0.12.1 - 2025-12-09
Added
Zoi.default/2implementsZoi.JSONSchemaprotocol to include default values in generated JSON Schemas.
0.12.0 - 2025-12-05
Added
Zoi.lazy/1type for deferring schema evaluation until parse time, enabling recursive and self-referencing schemas.
0.11.1 - 2025-12-04
Added
- Improved the documentation for "Rendering forms with Phoenix" guide, adding example on how to handle errors with changesets or custom errors.
0.11.0 - 2025-11-24
Changed
- Protocol-based validation architecture: All validations now use
Zoi.Validations.*protocols instead of the centralizedZoi.Refinementsmodule. This improves:- Introspection: Constraint values stored as struct fields (e.g.,
min_length: 5) instead of opaque MFAs - Ergonomics: Pass constraints directly in constructors:
Zoi.string(min_length: 5, max_length: 100) - Integration: External libraries can easily inspect schema constraints for JSON Schema, OpenAPI, etc.
- Introspection: Constraint values stored as struct fields (e.g.,
- Validation protocols:
Gte,Lte,Gt,Lt,Length,Url,Regex,StartsWith,EndsWith,OneOf - Each type implements relevant protocols (String implements all, Integer/Float/Number implement Gte/Lte/Gt/Lt, etc.)
- Now all types
optsparams are validated at type creation time, usingZoiinternals, raising errors if invalid options are provided. Zoi.gt/2andZoi.lt/2refinements will now work withZoi.integer(),Zoi.float()andZoi.number()only.Zoi.array/2andZoi.string/2types should useZoi.min/2andZoi.max/2instead for length validations.
0.10.7 - 2025-11-16
Added
- Recipes guide with common use cases and examples of
Zoiusage.
0.10.6 - 2025-11-13
Added
Zoi.one_of/2type to accept a value that matches exactly one of the provided literal values.
0.10.5 - 2025-11-13
Changed
Zoi.enum/2typespec for binary now returnsbinary()instead of literals.
0.10.4 - 2025-11-10
Changed
- Fix
Zoi.Struct.enforce_keys/1to work whenZoi.default/2wraps aZoi.optional/2type
0.10.3 - 2025-11-10
Added
- wrap
Zoi.Type.t()intoZoi.schema()type - Group guides on hexdocs
0.10.2 - 2025-11-10
Added
Zoi.Schema.traverse/2for recursively walking and transforming schema structures. This function applies a transformation to all nested fields while leaving the root schema unchanged, making it easy to apply operations like coercion, nullish, or defaults across an entire schema tree.Zoi.coerce/1helper function to enable type coercion on schemas that support it.
Changed
Zoi.transform/2andZoi.refine/2are now chained in the order they were added, allowing more flexible validation and transformation flows.
0.10.1 - 2025-11-09
Added
Zoi.describe/1now supportsZoi.struct/2type.
0.10.0 - 2025-11-09
Added
Zoi.Formmodule withprepare/1andparse/2functions for seamless Phoenix form integration.Phoenix.HTML.FormDataprotocol implementation forZoi.Context, enabling Phoenix form rendering without losing the original params.- Partial parsing data is now preserved inside
%Zoi.Context{}(and surfaced through forms) even when validation fails, allowing Phoenix forms to keep previously valid entries. - Keyword schemas defined with another schema as the value now keep the successfully parsed entries even if a sibling entry fails validation.
Zoi.Form.prepare/1now forces coercion on every nested field so Phoenix form strings are cast into their target types automatically.Zoi.Form.parse/2automatically normalizes LiveView's map-based array format (with numeric string keys) into regular lists inctx.input, eliminating the need for manual conversion when manipulating array fields dynamically.- Architecture diagram in main module documentation (
Zoi) showing the parsing flow and validation pipeline with Mermaid visualization.
Changed
- Achieved 100% test coverage across the entire codebase (previously 99.8%).
0.9.1 - 2025-11-06
Added
Zoi.JSONSchemanow acceptsZoi.decimal/1, converting it totype: "number".
0.9.0 - 2025-11-06
Added
Zoi.array/2now accepts:coerceoption to forceMapandTupletypes into an array.
Changed
Zoi.type_spec/1for object with string keys now returns genericmap()type spec due to how Elixir handles this type internally.
0.9.0-rc.1 - 2025-11-04
Added
Zoi.object/2andZoi.keyword/2now accept:empty_valuesoption to define which values are considered empty when parsing objects and keyword lists. By default, this option is set to[], meaning no values are considered empty. You can customize this option to include values likenil, empty strings (""), or any other value you want to treat as empty and it will return a:requirederror when those values are encountered for required fields.
0.9.0-rc.0 - 2025-11-04
Changed
- All errors have been reworked to include more context on the error
codeandissue. Now errors will have the following structure (example):
%Zoi.Error{
code: :invalid_type,
issue: {"invalid type: expected string", [expected: :string]},
message: "invalid type: expected string",
path: [:user, :name]
}And it's also possible to have errors with dynamic messages:
%Zoi.Error{
code: :invalid_literal,
message: "invalid literal: expected true",
issue: {"invalid literal: expected %{expected}", [expected: true]},
path: []
}This will give more flexibility when handling errors programmatically, and better support with tools such as Gettext for localization.
- Removed
Zoi.gt/3andZoi.lt/3refinements for strings. UseZoi.min/3andZoi.max/3instead. - Allow all refinements to accept custom error messages.
Zoi.url/2now uses elixir's built-inURI.parse/1for URL validation.
0.8.4 - 2025-11-01
Changed
- Fix nested
Zoi.keyword/2error when parsing invalid values - Fix
Zoi.Describewhen dealing withDecimaloptional dependency
0.8.3 - 2025-10-31
Added
- All types now implements the
Inspectprotocol. This should improve the ergonomics when working with Zoi types in IEx or when inspecting/debugging it's types.
0.8.2 - 2025-10-30
Added
Zoi.non_negative/2refinement for numbers to accept values from 0 and aboveZoi.describe/1returns a structured documentation for keyword and object types
Changed
Zoi.keyword/2now can accept a schema in the first argument to validate the values of the keyword listZoi.keyword/2type_spec now reflects correctly the keyword list definition
0.8.1 - 2025-10-27
Changed
- Update readme with new metadata examples and reference to main api
0.8.0 - 2025-10-26
Added
Zoi.nullish/2type to acceptnilor a value of a specific type@specfor all public functions@typedocfor all public typesZoi.description/1option to add description metadata to types for documentation purposesZoi.example/1option to add example metadata to types for documentation purposes
Changed
Zoi.to_json_schema/1now readsdescription,exampleopts from types to include them in the generated JSON Schema
0.7.4 - 2025-10-25
Changed
Zoi.regex/3fix regex compilation, now theregex.optsare properly handled
0.7.3 - 2025-10-20
Added
Zoi.email/1now acceptspatternoption to customize the email regex
Changed
Zoi.enum/2now acceptscoerceoption to coerce values to the key or to the value
0.7.2 - 2025-10-13
Added
- Fixed example in
guides/using_zoi_to_generate_openapi_specs.md
0.7.1 - 2025-10-12
Added
Zoi.to_json_schema/1support for metadata (e.g., example, description)guides/quickstart_guide.mdadded to the documentation
0.7.0 - 2025-10-10
Added
Zoi.to_json_schema/1function to convertZoischemas to JSON Schema format
Changed
Zoi.array/2fixed path in errors when parsing arraysZoi.regex/2fixed regex compile errors when used in module attributes
0.6.6 - 2025-10-08
Added
Zoi.metadata/1- option to add metadata to types for documentation purposes
Changed
Zoi.example/1deprecated in favor ofZoi.metadata/1
0.6.5 - 2025-10-07
Added
Zoi.example/1option to add example values to types for documentation and testing purposes
0.6.4 - 2025-09-30
Added
Zoi.downcase/1refinement to validate if a string is in lowercaseZoi.upcase/1refinement to validate if a string is in uppercaseZoi.hex/1refinement to validate if a string is a valid hexadecimal
0.6.3 - 2025-09-27
Added
keysinZoi.object/2data structureZoi.struct/2type to parse structs and maps into structsZoi.Structmodule with helper functions to work with structs. This module offers two main functions:Zoi.Struct.enforce_keys/1: List of keys that must be present in the structZoi.Struct.struct_keys/1: List of keys and their default values to be used withdefstruct
0.6.2 - 2025-09-26
Added
Zoi.literal/2type to accept only a specific literal value
Changed
- Refactor all errors to be generated on type creation instead of parsing time
0.6.1 - 2025-09-08
Added
Zoi.null/1type to accept onlynilvaluesZoi.positive/1refinement for numbers to accept only positive valuesZoi.negative/1refinement for numbers to accept only negative values
0.6.0 - 2025-09-07
Added
Zoi.required/2type to enforce presence of a value inkeywordandobjecttypes
Changed
Zoi.object/2now usesmfato call innertransformfunctionZoi.keyword/2have all fields set as optional by default, useZoi.required/2to enforce presence of a value
0.5.7 - 2025-09-06
Changed
Zoi.parse!/3Error message
0.5.6 - 2025-09-05
Added
Zoi.parse!/3function that raises an error if parsing failsZoi.type_spec/2function that returns the Elixir type spec for a given Zoi schema, implemented for all types
0.5.5 - 2025-09-03
Added
Zoi.keyword/2type
Changed
Zoi.struct/2now works with the newZoi.keyword/2type- Improved
Zoi.transform/2documentation
0.5.4 - 2025-08-29
Added
- Guide for converting keys from maps
- Guide for generating schema from JSON structure
0.5.3 - 2025-08-29
Changed
- Fix
transformandrefinementtypes
0.5.2 - 2025-08-28
Added
Zoi.prettify_errors/2added docsZoi.extend/3type
Changed
Zoi.map/3now parses key and value types correctly- Fix encapsulated types ignoring refinements and transforms when parsing
0.5.1 - 2025-08-17
Changed
Zoi.prettify_errors/1don't return\nat the end of the string anymore
0.5.0 - 2025-08-17
Added
Zoi.atom/1typeZoi.string_boolean/1typeZoi.union/2custom error messagesZoi.intersection/2custom error messagesZoi.to_struct/2transform
Changed
Zoi.boolean/1does not coerce values besides "true" and "false" anymore. For coercion of other values, useZoi.string_boolean/1type.
0.4.0 - 2025-08-14
Added
Zoi.Contextmodule to provide context when parsing data
Changed
Zoi.object/2will not automatically parse objects with inputs that differ from the string/atom keys map format. For example:
schema = Zoi.object(%{
name: Zoi.string(),
age: Zoi.integer()
})
Zoi.object(schema, %{"name" => "John", "age" => 30})
{:error, _errors}To make this API work, you can pass coerce: true option to Zoi.object/2. This will make the object parser to check from the Map input if the keys are strings or atoms and fetch it's values automatically.
schema = Zoi.object(%{
name: Zoi.string(),
age: Zoi.integer()
})
Zoi.object(schema, %{"name" => "John", "age" => 30}, coerce: true)
{:ok, %{name: "John", age: 30}}0.3.4 - 2025-08-09
Added
Zoi.min/2,Zoi.max/2,Zoi.gt/2,Zoi.gte/2,Zoi.lt/2,Zoi.lte/2refinements forZoi.time/1typeZoi.min/2,Zoi.max/2,Zoi.gt/2,Zoi.gte/2,Zoi.lt/2,Zoi.lte/2refinements forZoi.date/1typeZoi.min/2,Zoi.max/2,Zoi.gt/2,Zoi.gte/2,Zoi.lt/2,Zoi.lte/2refinements forZoi.datetime/1typeZoi.min/2,Zoi.max/2,Zoi.gt/2,Zoi.gte/2,Zoi.lt/2,Zoi.lte/2refinements forZoi.naive_datetime/1type
0.3.3 - 2025-08-09
Added
Zoi.time/1typeZoi.date/1typeZoi.datetime/1typeZoi.naive_datetime/1type
0.3.2 - 2025-08-09
Added
Zoi.decimal/1typeZoi.min/2,Zoi.max/2,Zoi.gt/2,Zoi.gte/2,Zoi.lt/2,Zoi.lte/2refinements forZoi.decimal/1type
0.3.1 - 2025-08-08
Added
Zoi.ISO.time/1typeZoi.ISO.date/1typeZoi.ISO.datetime/1typeZoi.ISO.to_time_struct/1transformZoi.ISO.to_date_struct/1transformZoi.ISO.to_datetime_struct/1transformZoi.ISO.to_naive_datetime/1transformZoi.prettify_errors/1function to format errors in a human-readable way
0.3.0 - 2025-08-07
Added
Zoi.email/0formatZoi.url/0formatZoi.uuid/1format
Changed
- Removed
Zoi.email/1, now useZoi.email/0that will automatically use theZoi.string/1type - All refinements now accept a
:messageoption to customize the error message
0.2.3 - 2025-08-06
Added
Zoi.map/3typeZoi.intersection/2typeZoi.gt/2refinementZoi.gte/2refinementZoi.lt/2refinementZoi.lte/2refinement
0.2.2 - 2025-08-06
Added
- Guides for using
Zoiin Phoenix controllers - New
Zoi.tuple/2type - New
Zoi.any/1type - New
Zoi.nullable/2type
Changed
- Improved error messages for all validations and types
Zoi.treefy_errors/1now returns a more human-readable structureZoi.optional/2cannot acceptnilas a value anymore. UseZoi.nullable/2instead.Zoi.optional/2insideZoi.object/2now handles optional fields correctly
0.2.1 - 2025-08-06
Added
- Custom error messages for primitive types
Changed
Zoi.number/2now returns proper error message
0.2.0 - 2025-08-05
Added
mfatoZoi.refine/2andZoi.transform/2functions- accumulator errors to
Zoi.refine/2andZoi.transform/2functions Zoi.array/2typeZoi.length/2,Zoi.min/2andZoi.max/2validators for arrays
Changed
- errors are now returned as a list of
%Zoi.Error{}structs