Getting Started
Use typed_schema as a drop-in replacement for Ecto.Schema.schema:
defmodule MyApp.Blog.Post do
use EctoTypedSchema
typed_schema "posts" do
field :title, :string, typed: [null: false]
field :status, Ecto.Enum, values: [:draft, :published]
belongs_to :author, MyApp.Accounts.User
has_many :comments, MyApp.Blog.Comment
timestamps()
end
endThis generates:
@type t() :: %MyApp.Blog.Post{
__meta__: Ecto.Schema.Metadata.t(MyApp.Blog.Post),
id: integer(),
title: String.t(),
status: :draft | :published | nil,
author_id: integer() | nil,
author: Ecto.Schema.belongs_to(MyApp.Accounts.User.t()) | nil,
comments: Ecto.Schema.has_many(MyApp.Blog.Comment.t()),
inserted_at: NaiveDateTime.t() | nil,
updated_at: NaiveDateTime.t() | nil
}You can verify the generated type in IEx:
iex> t MyApp.Blog.Post
@type t() :: %MyApp.Blog.Post{...}Options
Type Parameters
Create parameterized types with parameter/2:
typed_embedded_schema type_kind: :opaque, type_name: :result, null: false do
parameter :ok
parameter :error
field :ok, :string, typed: [type: ok]
field :error, :string, typed: [type: error]
end
# Generates: @opaque result(ok, error) :: %__MODULE__{...}Plugins
Register TypedStructor plugins to extend the generated type definition:
typed_schema "users" do
plugin MyPlugin, some_option: true
field :name, :string
endPlugins are forwarded into the generated typed_structor block and receive all
three callbacks (init, before_definition, after_definition).
Embedded Schemas
typed_embedded_schema do
field :display_name, :string
field :bio, :string
endEmbedded schema types omit __meta__.
Edge Cases
Through associations
through: associations are included in the generated type. If the chain can't be resolved at compile time, the type falls back to term() / list(term()) with a warning. Provide an explicit type to suppress:
has_many :post_tags, through: [:posts, :tags], typed: [type: list(Tag.t())]belongs_to with define_field: false
No typed metadata is generated for the FK field. Define it manually with field/3 if you need custom type settings.
default: nil
Does not make a field non-nullable; the type stays ... | nil.
Typed Options
Pass typed: [...] on any field or association to customize its generated type:
| Option | Effect |
|---|---|
type: | Override the inferred type entirely |
null: | false removes | nil from the type |
default: | Type metadata default; non-nil defaults imply non-nullable (runtime defaults come from Ecto default:) |
field :email, :string, typed: [null: false]
field :role, :string, typed: [type: :admin | :user]For belongs_to, the :foreign_key sub-option controls the FK field's type:
belongs_to :org, Organization,
foreign_key: :org_id,
typed: [foreign_key: [type: Ecto.UUID.t()]]Schema-level Options
typed_schema/3 and typed_embedded_schema/2 accept options that apply as
defaults to every field (per-field typed: options override):
typed_schema "users", null: false do
field :name, :string
field :bio, :string, typed: [null: true] # override: nullable
end| Option | Effect |
|---|---|
null: | Default nullability for all fields |
type_kind: | :opaque, :typep, etc. (default :type) |
type_name: | Custom type name (default :t) |
Summary
Schema
Defines a typed embedded Ecto schema that delegates to Ecto.Schema.embedded_schema/1.
Defines a typed embedded Ecto schema with schema-level options.
Defines a typed Ecto schema that delegates directly to Ecto.Schema.schema/2.
Defines a typed Ecto schema with schema-level options.
Fields and Associations
Defines a typed Ecto.Schema.belongs_to/3 association.
Equivalent to embeds_many/4 without a block. See embeds_many/4 for
details and examples.
Defines a typed Ecto.Schema.embeds_many/3 embed. Always non-nullable
(defaults to []); the :null option has no effect.
Equivalent to embeds_one/4 without a block. See embeds_one/4 for
details and examples.
Defines a typed Ecto.Schema.embeds_one/3 embed. Nullable by default.
Defines a typed schema field that wraps Ecto.Schema.field/3.
Defines a typed Ecto.Schema.has_many/3 association. Always non-nullable
(defaults to []); the :null option has no effect.
Defines a typed Ecto.Schema.has_one/3 association. Nullable by default.
Defines a typed Ecto.Schema.many_to_many/3 association. Always non-nullable
(defaults to []); the :null option has no effect.
Defines typed timestamp fields that wrap Ecto.Schema.timestamps/1.
Type Customization
Declares a type parameter for the schema's generated type.
Registers a TypedStructor plugin for the schema's generated type.
Schema
Defines a typed embedded Ecto schema that delegates to Ecto.Schema.embedded_schema/1.
Equivalent to typed_embedded_schema([], do: block).
See typed_embedded_schema/2 for options and examples.
Defines a typed embedded Ecto schema with schema-level options.
Wraps Ecto.Schema.embedded_schema/1 and captures type metadata for
@type t() generation. Embedded schemas do not include a __meta__
field in their generated type.
Schema-level Options
Options that apply as defaults to every field (individual fields
can override via their :typed option):
:null- iffalse, makes all field types non-nullable:type_kind- the kind of type to generate (e.g.,:opaque):type_name- custom name for the generated type (default::t)
See the "Schema-level Options" section in the module documentation for more details.
Examples
typed_embedded_schema null: false do
field :theme, :string
field :bio, :string, typed: [null: true] # override: nullable
end
Defines a typed Ecto schema that delegates directly to Ecto.Schema.schema/2.
Equivalent to typed_schema(source, [], do: block).
See typed_schema/3 for options and examples.
Defines a typed Ecto schema with schema-level options.
Wraps Ecto.Schema.schema/2 and captures type metadata for
@type t() generation via TypedStructor.
Schema-level Options
Options that apply as defaults to every field (individual fields
can override via their :typed option):
:null- iffalse, makes all field types non-nullable:type_kind- the kind of type to generate (e.g.,:opaque):type_name- custom name for the generated type (default::t)
See the "Schema-level Options" section in the module documentation for more details.
Examples
typed_schema "users", null: false do
field :name, :string
field :bio, :string, typed: [null: true] # override: nullable
end
Fields and Associations
Defines a typed Ecto.Schema.belongs_to/3 association.
Creates both the association field and its foreign key field. The association is nullable by default.
Typed Options
:foreign_key- a keyword list to customize the foreign key field's type independently
See the "Typed options" section in the module documentation for more options.
Examples
belongs_to :user, User, typed: [null: false]
belongs_to :organization, Organization,
foreign_key: :org_id,
typed: [foreign_key: [type: Ecto.UUID.t()]]
Equivalent to embeds_many/4 without a block. See embeds_many/4 for
details and examples.
Defines a typed Ecto.Schema.embeds_many/3 embed. Always non-nullable
(defaults to []); the :null option has no effect.
The optional do block allows defining the embedded schema inline.
See the "Typed options" section in the module documentation for more options.
Examples
embeds_many :addresses, Address
embeds_many :line_items, LineItem, primary_key: false do
field :product_name, :string
field :quantity, :integer
field :price, :decimal
end
Equivalent to embeds_one/4 without a block. See embeds_one/4 for
details and examples.
Defines a typed Ecto.Schema.embeds_one/3 embed. Nullable by default.
The optional do block allows defining the embedded schema inline.
See the "Typed options" section in the module documentation for more options.
Examples
embeds_one :address, Address
embeds_one :profile, Profile, typed: [null: false]
embeds_one :address, Address, primary_key: false do
field :street, :string
field :city, :string
end
Defines a typed schema field that wraps Ecto.Schema.field/3.
Type Mapping
The generated type is inferred from the Ecto type. Common mappings include:
| Ecto Type | Elixir Typespec |
|---|---|
:string | String.t() |
:integer | integer() |
:float | float() |
:boolean | boolean() |
:binary | binary() |
:decimal | Decimal.t() |
:date | Date.t() |
:time / :time_usec | Time.t() |
:naive_datetime / :naive_datetime_usec | NaiveDateTime.t() |
:utc_datetime / :utc_datetime_usec | DateTime.t() |
:binary_id | Ecto.UUID.t() |
:map | map() |
{:array, inner} | list(inner_type) |
| Ecto.Enum | Union of atom values (e.g., :active | :inactive) |
| Custom module | Module.t() |
Ecto.Enum values are automatically captured and used to generate a union type.
See the "Typed options" section in the module documentation for more options.
Examples
field :name, :string
field :email, :string, typed: [null: false]
field :role, Ecto.Enum, values: [:admin, :user, :guest]
Defines a typed Ecto.Schema.has_many/3 association. Always non-nullable
(defaults to []); the :null option has no effect.
Also supports through-associations via has_many :name, through: [:assoc, :chain].
See the "Typed options" section in the module documentation for more options.
Examples
has_many :posts, Post, foreign_key: :user_id
has_many :post_tags, through: [:posts, :tags], typed: [type: list(Tag.t())]
Defines a typed Ecto.Schema.has_one/3 association. Nullable by default.
See the "Typed options" section in the module documentation for more options.
Examples
has_one :profile, Profile
has_one :settings, Settings, typed: [null: false]
Defines a typed Ecto.Schema.many_to_many/3 association. Always non-nullable
(defaults to []); the :null option has no effect.
See the "Typed options" section in the module documentation for more options.
Examples
many_to_many :tags, Tag, join_through: "posts_tags", typed: [type: list(Tag.t())]
Defines typed timestamp fields that wrap Ecto.Schema.timestamps/1.
The single :typed option applies to both generated timestamp fields.
Timestamp Type Mapping
| Ecto Type | Elixir Typespec |
|---|---|
:naive_datetime (default) | NaiveDateTime.t() |
:naive_datetime_usec | NaiveDateTime.t() |
:utc_datetime | DateTime.t() |
:utc_datetime_usec | DateTime.t() |
See the "Typed options" section in the module documentation for more options.
Examples
timestamps()
timestamps(type: :utc_datetime)
timestamps(typed: [null: false])
Type Customization
Declares a type parameter for the schema's generated type.
Parameters make the generated type parameterized, e.g., @type t(age) :: %__MODULE__{...}.
They are passed through to TypedStructor.parameter/2.
Examples
typed_schema "users" do
parameter :age
field :name, :string
field :age, :integer, typed: [type: age]
endThis generates @type t(age) :: %__MODULE__{...} where the :age field
uses the type parameter instead of the inferred integer() type.
Registers a TypedStructor plugin for the schema's generated type.
Plugins are forwarded to the typed_structor block generated at compile time.
They receive the TypedStructor.Definition and can modify types, add fields,
or inject code before/after the struct definition.
See TypedStructor.Plugin for the plugin behaviour and callbacks.
Examples
typed_schema "users" do
plugin MyPlugin, some_option: true
field :name, :string
end