> For the complete documentation index, see [llms.txt](/llms.txt)

# glotto.yml model shaping

## Model shaping

### `models`

```yaml
models:
  WidgetsCreateWidgetRequestAddress:
    name: Address        # rename the model, in every target and every artifact
  Coordinates:
    name: { go: Coords } # or pin the name per target
  TinyWrapper:
    inline: true         # don't emit a standalone type — inline it at each reference
  DebugInfo:
    inline: { go: true } # or un-promote it for specific targets only
```

Shape the **models** Glotto emits. Two directives, each taking a single value (applying everywhere)
or an object keyed by target:

- **`name`** renames a model. This is the escape hatch for the names Glotto *synthesizes*: an inline
  object schema — say an `address` property on a request body — is promoted to a named model so
  every language can type it, and its name is derived from where it sits in the spec
  (`WidgetsCreateWidgetRequestAddress`). Rename it to whatever reads well. Only the emitted type name
  changes; the JSON payload is untouched.
- **`inline`** does the opposite: it un-promotes a model, substituting its body wherever it's
  referenced and emitting no standalone type. Useful for a small one-off wrapper that isn't worth a
  named type of its own.

A name set as a single value applies to **every** artifact — the SDKs, the docs site, the MCP server,
the mock server and the Terraform provider all show it — so one model has one name everywhere. The
per-target object form is applied per SDK instead, leaving the shared name in place elsewhere.

An entry naming a model your spec doesn't produce is ignored, so a config can outlive a schema
change. A few directives are declined so generation stays correct: renaming onto a name another model
already holds (it would merge two types), and inlining a model that refers back to itself or that is
a member of a discriminated union. In each case the model simply stays as it was — and both
`glotto generate` tells you so, naming the directive's path and why it was
declined ([`GLOTTO_CONFIG_MODEL_SHAPING_REFUSED`](/docs/diagnostics-config#config-diagnostics)).

Migrating from Stainless? Glotto translates `x-stainless-model` and
`x-stainless-model-skip` into this block for you.

#### Renaming a model for one language

`name`'s object form is also where you settle a **name collision with the SDK's own code**. Some type
names are unusable in a given language because the generated SDK already refers to them: a model
called `Data` in a Swift SDK would land in the same module as the client's own `Data` references and
take them over, and Swift offers no way to disambiguate a same-module declaration.

Glotto handles this for you. A colliding model is emitted under a suffixed name — `Data` →
`DataModel`, and `DataModel2` if that is taken too — in **that target only**, with every reference
updated to match, and `glotto generate` prints a
[`GLOTTO_CONFIG_MODEL_RENAMED`](/docs/diagnostics-config#config-diagnostics) warning saying
which name it chose. Your other SDKs keep the original name.

To choose the name yourself instead, pin it per target — an explicit name always wins, and the
warning stops:

```yaml
models:
  Data:
    name: { swift: Payload }   # Swift emits `Payload`; every other target still emits `Data`
```

The warning opens with the exact key to set, so you can read it straight out of the output — and it
stays the key **you** wrote even if another `models` entry has already renamed that model.

There is no separate `model_names` block: this is that setting. Use `transforms` with `rename_schema`
if you want the model renamed in *every* target and artifact rather than just one.

### `enums`

```yaml
enums:
  Status:
    nominal: false            # emit a plain alias over the primitive, not a named type
  Kind:
    nominal: { go: false }    # or just for specific targets
```

Choose an enum's **typing shape**: its own named type, or a plain alias over the primitive it's
carried by.

By default Glotto gives an enum its own type in every language that can express one — a
`#[non_exhaustive]` enum in Rust, a `RawRepresentable` struct in Swift, an `extension type` in Dart,
a `StrEnum` subclass in Python (an `IntEnum`, or a float-backed enum, when the enum's values are
numbers), a defined `type Status string` in Go. Set `nominal: false` and it becomes an alias instead
(`pub type Status = String`, `typealias Status = String`, `type Status = string`, …), over whichever
primitive the enum is actually carried by — so a numeric enum aliases to `i64` / `Int` / `int` /
Python's `int` (or `float` for decimal values), not to a string type.

Reach for it when a named type gets in the way rather than helping: your callers would rather pass a
plain string than import and construct a type; you're moving from a hand-written SDK where the field
*was* a string and promoting it would break your users; or the enum's members change often, so the
named type churns your public API on every regeneration while an alias doesn't.

The trade-off is worth stating plainly: **an alias has no members**, so you give up the generated
constants (`Status.active`, `Status::Active`) and callers write the raw string. Nothing changes on
the wire — the value is the same primitive either way — so switching shape is not an API
re-versioning event.

This applies to the six targets whose language has a type-alias construct: **Go, Rust, Swift, Dart,
Kotlin and Python**. Java, PHP, C# and Ruby have no alias construct to emit, and TypeScript,
React Native and Elixir have no nominal form to choose against (TypeScript is structurally typed, so
its emitted `type Status = 'active' | 'archived'` is already an alias). `glotto generate` warns if you
address one of them per target.

`nominal: true` states the default explicitly. It's accepted and carried, but it doesn't change
emission: where a named type is available it's already what you get, and where one isn't — a
**mixed** enum (say `['a', 1, true]`, which has no single primitive to be typed over) or a
**boolean** one in Rust, Swift or Dart — Glotto declines rather than inventing a shape it can't
stand behind.

Migrating from Stainless? Glotto translates `x-stainless-nominal` into
this block. Note the defaults are opposite — Stainless aliases by default, Glotto names by default —
and only enums you actually annotated are translated, so review the ones you didn't.
### `soft_required`

```yaml
soft_required:
  listInvoices:
    parameters: [account_id]  # the SDK method demands this query/header parameter
  createInvoice:
    body: true                # …and this operation's request body
    body_fields: [currency]   # …and this field of the request-body model
```

Mark inputs your API *accepts* without, but that every real caller should send. The generated SDK
method **demands** them — a Python keyword with no default rather than `= None`, a Go `string` value
rather than a `*string`, a TypeScript member without its `?` — so the ergonomics guide the caller
toward the call you actually want.

What makes this different from editing `required` in your spec is everything it *doesn't* touch.
Your docs reference still lists the parameter as optional, the generated MCP server still marks it
optional in its tool schema, the generated mock server still accepts a request that omits it, and
Breaking-change detection reports **nothing** — because none of those describe the SDK signature,
they describe your protocol, and your protocol hasn't changed. Adding an entry here is never a
breaking change.

Entries are keyed by `operationId`, and one naming an operation, parameter, or field your spec
doesn't carry is ignored, so a config can outlive a schema change.

One case is handled on a copy so the generated SDK stays correct: a `body_fields` entry on a model
your API also **returns**. Requiredness there is the same flag the SDK's decoder reads, so demanding
the field on the shared type would make responses that omit it fail to parse — a preference turned
into a runtime error on data you don't control. Instead the SDK gains a request variant of that
model, named `<Model>Request`, and only the operation you named uses it:

```ts
export interface Invoice        { id: string; memo?: string }  // what you receive
export interface InvoiceRequest { id: string; memo: string }   // what createInvoice demands
createInvoice(params: InvoiceRequest): Promise<Invoice>
```

Every other reference — responses, nested fields, and other operations' bodies — keeps using the
original model, so nothing that parses your API's output changes. If the name is already taken, the
variant becomes `<Model>Request_2`; renaming the model itself with the [`models`](#models) block
carries through (`Invoice` → `Bill` yields `BillRequest`).

Because that adds a type you didn't name, `glotto generate` tells you it
happened, as a [`GLOTTO_CONFIG_SOFT_REQUIRED_SPLIT`](/docs/diagnostics-config#config-diagnostics)
warning naming the operation, the field, the shared model and the variant — so a new SDK type never
appears unexplained. (An entry your spec doesn't carry stays silently ignored, as above: that's the
stale-config case, not a promotion that moved.)

Migrating from Stainless? Glotto translates `x-stainless-soft-required`
into this block for you.

### `auto_populate`

```yaml
auto_populate:
  createCustomer:
    parameters: [api_version]     # the SDK sends this query/header parameter itself
    body_fields: [object]         # …and this field of the request body
```

The inverse of [`soft_required`](#soft_required). Where that block **adds** an input your protocol
doesn't demand, this one **removes** an input your protocol *does* demand — because there is only
one value it could ever hold, and the SDK can supply it.

The shape this is for is the Stripe-style discriminant: `object: { type: string, enum: ["customer"] }`
on a request body, or a pinned `api_version` query parameter. The schema permits exactly one value,
and every caller types it at every call site anyway. Name the position here and the generated method
drops it from its signature, while the generated request still sends it:

```ts
// before
createCustomer({ object: 'customer', email: 'a@b.com' })
// after
createCustomer({ email: 'a@b.com' })
```

Entries are keyed by `operationId`. `parameters` names `query` and `header` parameters by their wire
name; `body_fields` names properties of the request-body object. All 13 languages honour it, and in
each one the value is injected at the same place the per-call
`extraQuery` / `extraHeaders` / `extraBody` escape hatch merges
— so a caller who passes an explicit value for the same key **still wins**. The constant is the
base, never an override.

Like `soft_required`, this changes only the SDK signature. Your docs reference still documents the
field, the generated MCP server still exposes it, the generated mock still accepts it, and
Breaking-change detection reports **nothing** — none of those describe the SDK signature, they
describe your protocol, and your protocol hasn't changed.

A position is only eligible if its schema permits exactly one value with a sendable wire form.
Naming one that doesn't — a multi-value enum, a `null`-only schema, a field of a non-object body —
is reported by `glotto generate` as a
[`GLOTTO_CONFIG_AUTO_POPULATE_REFUSED`](/docs/diagnostics-config#config-diagnostics)
warning naming the position and the rule it failed, rather than being silently skipped. (An entry
naming an operation or field your spec doesn't carry stays silently ignored, as elsewhere: that's
the stale-config case, not a directive that was declined.)

One case is handled on a copy. If the request body resolves to a model your API also **returns**, or
that another operation sends without asking for the same removal, removing the field in place would
be lossy — the response type would lose a field the server still sends, or the other operation's
signature would lose an input with nothing to re-inject it. Instead the SDK derives a
`<Model>Request` variant carrying the removal, and only the operations you configured point at it.
Unlike the `soft_required` split there is nothing to announce: the field is *gone* from the
signature either way, so the variant never appears in a call site.

Migrating from Stainless? Glotto translates `x-stainless-const` into
this block for you.
