# `AshTypescript.Codegen.SchemaFormatter`
[🔗](https://github.com/ash-project/ash_typescript/blob/v0.18.1/lib/ash_typescript/codegen/schema_formatter.ex#L5)

Behaviour defining the output-format interface for schema generators.

Implement this behaviour to add a new validation library target (e.g. Zod, Valibot).
`AshTypescript.Codegen.SchemaCore` handles all resource introspection, topological
sorting, and structural generation; implementations only provide the output syntax.

## Implementing a new formatter

    defmodule MyLib.SchemaFormatter do
      @behaviour AshTypescript.Codegen.SchemaFormatter

      def null_schema, do: "ml.null()"
      def any_schema, do: "ml.any()"
      # ...
    end

# `aggregate_types`

```elixir
@callback aggregate_types() :: %{required(atom()) =&gt; String.t()}
```

Map of aggregate kind atoms to their schema strings (e.g. `%{count: "z.number().int()"}`).

# `any_schema`

```elixir
@callback any_schema() :: String.t()
```

Fallback schema for unknown/any type

# `atom_primitives`

```elixir
@callback atom_primitives() :: %{required(atom()) =&gt; String.t()}
```

Map of atom-symbol primitives (e.g. `:map`) to schema strings.

# `configured_import_path`

```elixir
@callback configured_import_path() :: String.t()
```

The import path for the validation library from application config.

# `custom_imports`

```elixir
@callback custom_imports() :: [map()]
```

Extra imports to inject into this formatter's generated schema file, so a
`mapping_overrides/0` entry can reference a schema the user authored in
TypeScript instead of an inline library expression.

Returns a list of `%{import_name: String.t(), file: String.t()}` maps — the
same shape as `import_into_generated`, but scoped per library.

# `format_array`

```elixir
@callback format_array(inner :: String.t(), constraints :: keyword()) :: String.t()
```

Build an array schema, applying outer `:min_length` and `:max_length` constraints.

# `format_enum`

```elixir
@callback format_enum(values :: String.t()) :: String.t()
```

Enum schema from a comma-joined string of quoted values.

# `format_float`

```elixir
@callback format_float(constraints :: keyword()) :: String.t()
```

Build a float schema, applying min/max/gt/lt constraints if present.

# `format_integer`

```elixir
@callback format_integer(constraints :: keyword()) :: String.t()
```

Build an integer schema, applying min/max constraints if present.

# `format_string`

```elixir
@callback format_string(constraints :: keyword(), require_non_empty :: boolean()) ::
  String.t()
```

Build a string schema, applying `constraints` if present.
`require_non_empty` is true when the string's constraints carry
`allow_empty?: false` (the Ash default) — implementations should enforce a
minimum length of 1 when no explicit `:min_length` constraint exists.

# `generate_schemas_enabled?`

```elixir
@callback generate_schemas_enabled?() :: boolean()
```

Whether schema generation is enabled in the current project config.

# `import_statement`

```elixir
@callback import_statement(import_path :: String.t()) :: String.t()
```

The TypeScript import statement for the validation library (e.g. `import { z } from "zod"`).

# `library_name`

```elixir
@callback library_name() :: String.t()
```

Human-readable library name for comments and error messages (e.g. "Zod" or "Valibot").

# `library_prefix`

```elixir
@callback library_prefix() :: String.t()
```

The library namespace prefix used when building schema declarations ("z" or "v").

# `ltree_array`

```elixir
@callback ltree_array() :: String.t()
```

Ltree type represented as an array of strings.

# `ltree_union`

```elixir
@callback ltree_union() :: String.t()
```

Ltree type represented as a string-or-array-of-strings union.

# `mapping_overrides`

```elixir
@callback mapping_overrides() :: keyword(String.t())
```

Per-library schema overrides from application config, for hand-rolled custom
types whose generated schema needs to be controlled by hand.

Returns a keyword list of `{type_module, schema_string}` tuples. Consulted by
`AshTypescript.Codegen.SchemaCore` ahead of the in-tree accept-lists, mirroring
how `type_mapping_overrides` works on the TypeScript side.

# `null_schema`

```elixir
@callback null_schema() :: String.t()
```

Schema for nil / null type

# `schema_suffix`

```elixir
@callback schema_suffix() :: String.t()
```

The schema variable name suffix (e.g. `"Schema"` or `"ValibotSchema"`).

# `section_header`

```elixir
@callback section_header() :: String.t()
```

Human-readable label for the resource schemas section comment header.

# `simple_primitives`

```elixir
@callback simple_primitives() :: %{required(module()) =&gt; String.t()}
```

Map of simple Ash type modules to schema strings — no constraint handling needed.

# `third_party_types`

```elixir
@callback third_party_types() :: %{required(module()) =&gt; String.t()}
```

Map of third-party Ash type modules (e.g. `AshMoney.Types.Money`) to schema strings.

# `wrap_nullable`

```elixir
@callback wrap_nullable(schema :: String.t()) :: String.t()
```

Wrap a schema string as nullable — i.e. the field's value may be `null`.
In zod this is `.nullable()`; in valibot, `v.nullable(...)`.

For fields that may be both omitted *and* null (the common case for nullable
Ash attributes — `JSON.stringify` drops `undefined` keys, so clearing a
nullable attribute requires sending `"field": null`), compose with
`wrap_optional/1`. Convention: apply `wrap_nullable` first (innermost), then
`wrap_optional`. The result is equivalent to zod's `.nullish()` shorthand.

# `wrap_object`

```elixir
@callback wrap_object(fields :: String.t()) :: String.t()
```

Wrap a comma-joined set of `key: schema` fields in an inline object schema.

# `wrap_optional`

```elixir
@callback wrap_optional(schema :: String.t()) :: String.t()
```

Wrap a schema string as omittable — i.e. the field may be absent from the
input object. In zod this is `.optional()`; in valibot, `v.optional(...)`.
Both libraries' optional accepts `undefined` only — not `null`. To accept
`null`, compose with `wrap_nullable/1`.

# `wrap_record`

```elixir
@callback wrap_record() :: String.t()
```

Record/map schema — string keys, any values.

# `wrap_union`

```elixir
@callback wrap_union(schemas :: String.t()) :: String.t()
```

Wrap a comma-joined set of schema strings in a union type.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
