Schemecto

Schemecto provides schemaless Ecto changesets with support for JSON Schema generation and nested schemaless changesets.

Installation

Add schemecto to your dependencies in mix.exs:

def deps do
[
{:schemecto, "~> 0.1.0"}
]
end

Usage

Define fields and validation functions for your data structures:

defmodule Example do
def changeset(params) do
fields = [
%{name: :name, type: :string, title: "Full Name"},
%{name: :email, type: :string, description: "User email address"},
%{name: :address, type: Schemecto.one(
[
%{name: :street, type: :string},
%{name: :city, type: :string},
%{name: :zip, type: :string}
],
with: &Example.validate_address/1
)}
]
Schemecto.new(fields, params)
|> Ecto.Changeset.validate_required([:name, :email])
|> Ecto.Changeset.validate_format(:email, ~r/@/)
end
def validate_address(changeset) do
changeset
|> Ecto.Changeset.validate_required([:street, :city])
|> Ecto.Changeset.validate_length(:zip, is: 5)
end
end

Since Schemecto is built on Ecto changesets, you can use all standard Ecto validation functions. The field list can be constructed dynamically at runtime, making it ideal for scenarios where the schema must be composed at runtime. Each field definition supports metadata like title, description, deprecated, and default values.

JSON Schema Generation

Convert your changesets to JSON Schema to generate API documentation, validate client-side forms, or integrate with other tools:

Schemecto.to_json_schema(changeset)

The generated schema includes field metadata, required fields, format patterns, length constraints, numeric bounds, and enum values from your changeset. For instance, the changeset above will emit:

{
"type": "object",
"properties": {
"address": {
"type": "object",
"properties": {
"city": {
"type": "string"
},
"street": {
"type": "string"
},
"zip": {
"type": "string",
"maxLength": 5,
"minLength": 5
}
},
"required": ["street", "city"]
},
"email": {
"type": "string",
"description": "User email address",
"pattern": "@"
},
"name": {
"type": "string",
"title": "Full name"
}
},
"required": ["name", "email"]
}

Supported types

Ecto typeJSON type
:integerinteger
:floatnumber
:decimalnumber
:booleanboolean
:stringstring
:mapobject
{:array, type}array of type
{:array, :any}array of object
Ecto.Enum of `typeenum of type

Custom Ecto types and parameterized types are also supported as long as they emit one of the types above. More types can be added in the future too.

Schemecto.one/2 and Schemecto.many/2 should be preferred instead of :map when the fields are known upfront.

License

Copyright 2026 José Valim

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.