glotto.yml reference
glotto.yml is the single config for a project: it describes your organization, the API spec to
read, the SDK targets to emit, your API surface, and how the generated clients behave. This
reference documents every key across this overview and the linked optional-reference pages. Where
a key has its own page, it links there rather than repeating detail.
Editor validation
Generate the JSON Schema for glotto.yml and reference it from the file for inline completion and
validation in VS Code, Cursor, Neovim, and other editors with the YAML language server:
glotto schema --out glotto.schema.json
# yaml-language-server: $schema=./glotto.schema.json
organization:
name: Acme
# …
glotto schema (no flags) prints the schema to stdout instead. It’s the same byte-stable schema
core-config exports as glottoConfigJsonSchema().
Required keys
organization
organization:
name: Acme
contact: support@acme.com
homepage: https://acme.com # optional
# optional: security metadata → a SECURITY.md in every generated repo
security_contact: security@acme.com
security_policy_url: https://acme.com/security
security_policy_terms: Reports are acknowledged within two business days.
name and contact are required; homepage is optional.
Security metadata → SECURITY.md
Setting any of security_contact, security_policy_url, or security_policy_terms makes
glotto generate write a SECURITY.md into every generated repo — the file GitHub’s “Report a
vulnerability” affordance keys off, so a researcher who finds an issue in your SDK has a documented
route to you. Set none of them and nothing is emitted; your output is byte-for-byte what it was.
Each key contributes one section, and an unset key contributes none:
| Key | Section it adds |
|---|---|
security_contact |
Reporting a Vulnerability — the address, plus a note directing researchers away from public issue trackers. Must be an email address. |
security_policy_url |
Disclosure Policy — a link to your published policy, named with organization.name. Must be an absolute http/https URL. |
security_policy_terms |
Additional Terms — your prose, verbatim. Free text, no grammar. |
The document is deterministic: it embeds no date or other ambient value, so regenerating an unchanged config reproduces it byte-for-byte.
Input source — exactly one of openapi / asyncapi / graphql
openapi:
source: ./spec/openapi.yaml # a local path or an http(s) URL
code_samples:
formats: [x-codeSamples, readme] # optional: code-sample formats to emit
source accepts a string — a local path (resolved against the directory holding your
glotto.yml, so it means the same thing wherever you run Glotto from) or an http(s):// URL —
or one of three structured objects: command (run your own exporter), introspect
(read a framework’s source directly), or git (below). asyncapi takes a source; graphql
takes a source plus optional operations and autogenerate. Exactly one input source must be
present.
The same source forms apply to all three inputs, and to graphql’s second document: a
git-hosted AsyncAPI channel document, or a GraphQL schema produced by your own exporter, is
written exactly the way the OpenAPI equivalent is.
asyncapi:
source:
git: { repo: https://github.com/acme/api-definitions.git, ref: main, path: events/chat.yaml }
# or
graphql:
source: { command: npm, args: [run, print-schema], output: { kind: stdout } }
operations: ./operations.graphql
The one exception is introspect, which is accepted on openapi.source only: introspection
reads a framework’s source and synthesizes an OpenAPI document, so it cannot be what an
asyncapi or graphql key means. Writing it anywhere else is a glotto generate error
(GLOTTO_CONFIG_INTROSPECT_SOURCE_OPENAPI_ONLY).
Specs in another repository
When your OpenAPI document lives in a different git repository — the usual shape when a
separate API-definition repo produces the spec — point source at it directly instead of
vendoring a copy you then have to keep in step by hand:
openapi:
source:
git:
repo: https://github.com/acme/api-definitions.git
ref: main # a branch, tag, or commit sha
path: openapi/petstore.yaml # relative to the repository root
All three keys are required. path is resolved inside the cloned repository, never against your
glotto.yml — a path escaping the repository is rejected. Glotto shallow-clones repo at ref
on each generation, reads that one file, and removes the clone; authentication uses your ambient
git configuration. The same { git: … } object is accepted wherever a spec or overlay is
referenced — asyncapi.source, graphql.source, graphql.operations, every
docs.versions[] snapshot, docs.changelog.previous_spec.source, and an
apply_overlay source.
Because ref can be a moving branch, the verification report records
the exact bytes it read, under the machine-independent origin git:<repo>#<ref>:<path>.
Multi-file specs
Glotto ingests a single OpenAPI document. If your spec is split across multiple files
($refs into sibling files — common for large APIs and a frequent migration case), bundle it into
one document first. Glotto does not merge multiple sources itself, so the bundling stays in your
control — there are two ways to wire it:
Pre-bundle — run a bundler as a build step and point source at the result:
redocly bundle ./openapi/main.yaml -o ./openapi/bundled.yaml
openapi:
source: ./openapi/bundled.yaml
Bundle inline — let Glotto run the bundler at generate time via the command form of source;
its stdout (or a file it writes) is ingested directly, with no committed intermediate:
openapi:
source:
command: redocly
args: [bundle, ./openapi/main.yaml]
output: { kind: stdout } # or { kind: file, path: ./.glotto/bundled.yaml }
environments
environments:
production: https://api.acme.com
staging: https://staging-api.acme.com
A map of environment name → base URL, with at least one entry.
default_environment
environments:
dev: https://dev.acme.com
production: https://api.acme.com
default_environment: production
Which environment everything Glotto emits points at when it needs one base URL — your SDK
clients and their README quickstarts, the code samples on your docs site, your docs playground, and
your MCP server’s upstream. Optional; it must name one of the keys under
environments.
Leave it out and Glotto resolves the default for you, in this order:
- an environment named
production, if you declared one — the nameglotto initwrites; - otherwise the first environment by sorted name.
That second rule exists so your generated code depends only on what your config says, never on
the order you happened to type it in. It is a determinism rule, not a guess at what you meant — so
when it is what decides the answer (two or more environments, none of them named production,
none declared), glotto generate tells you, with
GLOTTO_CONFIG_DEFAULT_ENVIRONMENT_INFERRED. Naming an environment that
isn’t declared is an error, GLOTTO_CONFIG_DEFAULT_ENVIRONMENT.
targets
targets:
typescript:
package_name: "@acme/sdk"
react_native:
package_name: "@acme/sdk-react-native"
secure_storage: keychain # or 'mmkv', 'expo'
expo_plugin: true # optional explicit opt-in for the public ./plugin subpath
csharp:
package_name: Acme.Commerce # NuGet PackageId (publish identity)
namespace: Acme.Commerce # C# namespace (code identity)
go:
namespace: github.com/acme/commerce-go # go.mod module path
java:
namespace: com.acme.commerce # Java package + Gradle group
dart:
secureStorage: true # emit the flutter_secure_storage token-store adapter
python: {}
A map of language slug → per-target config. Every target accepts:
-
package_name— the published/registry identity stamped on the manifest (npm name, PyPI name, NuGet<PackageId>, …). -
namespace— the SDK’s code identity, interpreted idiomatically per engine: the C#namespace/<RootNamespace>, Java/Kotlinpackage(+ source directory + Gradlegroup), Gogo.modmodule path, PHP PSR-4 namespace, Ruby/Elixir top-level module, Python’s import package (dots nest into directories, soacme.commercegivesfrom acme.commerce import Client), and Rust’s Cargo[lib] name(a lib name is a flat identifier, so dots collapse —acme.commercegivesuse acme_commerce). Optional — absent means each engine’s default (Glotto/com.glotto/glotto_sdk). Engines whose code identity is the published package —typescript/react_native(npm name),dart,swift— have no separate code identity to set:package_nameis the only knob, and settingnamespaceon one of them warns,GLOTTO_CONFIG_NAMESPACE_NOT_APPLIED, rather than being silently ignored.Both fields are held to one grammar, because both are written into a manifest your own build tooling then executes (
.csproj,build.gradle,mix.exs, a gemspec,composer.json,go.mod,pyproject.toml,pubspec.yaml,Cargo.toml): letters, digits and._-/:@, not starting with-,/or:, plus a single interior space or\(PHP’s namespace separator) — neither leading, trailing nor doubled. Quotes, angle brackets,$,#, braces, tabs, newlines and control characters are refused. Every shape the examples above use — an npm scope, a Composervendor/name, a Mavengroup:artifact, a Go module path, a dotted namespace — is accepted, as is a name with a space, which each engine sanitizes to its own convention.One per-target exception:
javaandkotlinrefuse a backslash. Their manifests are Gradle scripts, and a Groovy or Kotlin string literal reads\as an escape introducer rather than as a character — so a value carrying one either breaks the emitted build script or silently changes the value it lands in (art\namebecomes an artifact name containing a real newline). A JVM group is dot-separated and needs no separator, so nothing is lost.phpkeeps the backslash, because there it is the namespace separator:namespace: Acme\Commerceis accepted forphpand refused forjava/kotlin.namespaceis a per-target key, so give each target the spelling its own language uses —targets.php.namespace: Acme\Commercebesidetargets.java.namespace: com.acme. Do not reach for a dotted namespace to satisfy both: PHP namespace separators are backslashes, andAcme.Commerceemitsnamespace Acme.Commerce;, which is a parse error. -
repo— the target’s release repo (owner/name; Azure Reposorg/project/repo), with an optionalrepo_provider(github,gitlab,bitbucket, orazure-repos) naming the VCS host. Used by the hosted platform’s targets sync, which derives each project’s SDK-target records from this file — where release PRs and tags for that language’s SDK go. Optional — omit both for registry-only targets;repo_providerrequiresrepo. -
repo_path— for a monorepo release repo: the repo-relative directory this language’s SDK lives under (e.g.sdks/typescript). Several targets may share onerepowhen each names a distinctrepo_path— each gets its own subtree, release branch, and release PR. Optional (requiresrepo) — absent means the SDK owns the repo root, the one-repo-per-SDK layout. -
release— this target’s branch names, overriding the project-levelreleaseblock one name at a time. Three optional keys, all defaulting to Glotto’sgenerated→next→mainvocabulary:targets: typescript: repo: acme/acme-typescript release: branch: sdk-next # the release PR's head — the merged result baseline_branch: sdk-generated # the pristine generator output (the merge base) base_branch: trunk # the release PR's base — released codebranchandbaseline_branchmust differ — one branch cannot be both the merged result and the base it merges against. Two targets releasing to the samerepomay not name the samebranch. Usebase_branchwhen a repository’s released branch is not calledmain: Glotto refuses to open the PR rather than guessing at a branch you never named. -
variants—spec_repoonly: which documents the published spec repo carries. A non-empty list ofbase(your spec as supplied,exclude-pruned),with_transforms(after everytransformscorrection — the document your SDKs were generated from), andwith_code_samples(with per-operation SDK samples embedded asx-codeSamples). Optional — absent means all three. An unknown member, and a member listed twice, are bothglotto generateerrors rather than being ignored or deduplicated: a variant you asked for and did not get is never silently dropped. Asking for one your input cannot produce fails loudly — see which variants each input can publish. -
formats—spec_repoonly: the serializations each variant is published in. A non-empty list ofyamlandjson, same duplicate/unknown-member rules. Optional — absent meansyamlalone. Emitted file names are a function of the selection’s content, never of the order you listed it in.
React Native additionally accepts secure_storage and expo_plugin. The Expo plugin is emitted by
default only when the API has an intrinsic plugin capability. An API without OAuth or
bearer-style/secure-storage auth capability—for example, one using only custom or API-key auth—has
no ./plugin export by default. Set expo_plugin: true to opt that target in explicitly, then
import <your-sdk>/plugin from the generated package in your Expo config. Dart
accepts secureStorage: true (emit the flutter_secure_storage-shaped token-store adapter — see
Dart). Each target is an open
object, so language-specific keys are allowed. See React Native for the
RN-specific options.
model_presence
targets:
go:
model_presence: explicit
Opt-in. It makes every declared model field keep three states — missing, explicit null
and a value — apart through decoding, copying, updating and re-encoding with ordinary client
calls. That is the difference a read-modify-write PATCH depends on: “leave this field alone”
and “clear it” are different requests.
Most default models collapse the first two. In Go, Java, Kotlin, C#, PHP, Ruby, Swift, Rust, Dart
and Elixir, an optional nullable field that decodes from null holds the same empty value as an
omitted key, and re-encoding the model omits it. Python dataclasses omit an optional constructor
argument passed as None; their create(...) factory and response decoding already keep it.
TypeScript and React Native objects, and Python’s Pydantic models, already keep the states apart,
so they need no source migration.
explicitis the only value. Leave the key out and every target keeps its existing models, byte for byte.- Every SDK target accepts it, including Python’s dataclass and Pydantic modes and Kotlin’s JVM, Android and Multiplatform builds. On a non-SDK target, or with any other value, configuration validation fails rather than ignoring it.
- Where the default collapses presence, turning it on changes the model API: a
Field<T>orField[T]carrier, field-state accessors with copy builders, or a dedicated missing value, depending on the language. Treat it as a source migration; changed models carry no binary-compatibility promise. Each generated SDK’s README documents its states and its copy, update and remove idioms.
service_helpers
targets:
typescript:
service_helpers:
openai:
responses_operation: createResponse
chat_completions_operation: createChatCompletion
Opt-in, for an API that implements OpenAI’s Responses and Chat Completions protocols. It adds a
separate facade — openAIHelpers(client) from the SDK’s helpers/openai entry, or
openai_helpers(client) in Python — whose parse and stream methods handle structured-output
parsing and stream accumulation for those two operations. Your resource methods are unchanged.
openaiis the only profile. It takes exactly two keys,responses_operationandchat_completions_operation, each a required non-empty string naming an original OpenAPIoperationId, and they must name different operations. An unknown key, a missing or empty binding, or another profile is a configuration error.- Only
typescript,react_nativeandpythonaccept it; on any other target configuration validation fails. - Before emitting,
glotto generateresolves both bindings and checks their request, response and event schemas — including each operation’sstreaming.dual_modeentry — and stops on a missing or incompatible shape rather than emitting a helper that cannot work. Operation names, URLs and payload resemblance never turn it on. - Leave the block out and nothing is added: no helper exports and no new dependencies.
resources
resources:
invoices:
models:
invoice: '#/components/schemas/Invoice'
methods:
createInvoice: post /v1/invoices
getInvoice: get /v1/invoices/{id}
listInvoices: { endpoint: get /v1/invoices, paginated: cursor }
subresources:
line_items:
methods:
addInvoiceLine: post /v1/invoices/{invoice_id}/lines
Your API surface, grouped into resources. A method is either a "VERB /path" string or an object
{ endpoint, paginated?, streaming?, polling?, default_request_options? }. resources recurse through subresources
arbitrarily deep.
Set default_request_options on a
resource or method to configure routing headers, attempt timeouts and retry limits for its calls.
The method key declares the emitted method name. Writing
list: get /v1/invoicesemitsclient.invoices.list()in every language, whatever the operation’soperationIdsays. Declare nothing and the emitted name is theoperationIdexactly as before — naming is opt-in per operation, so a config with nomethodskeys produces a byte-identical SDK.The resource tree itself is still derived from your spec’s paths: the key names the method, not the resource, and never moves a method between resources.
Two things are refused rather than guessed, and both stop
glotto generate(GLOTTO_CONFIG_RESOURCE_METHOD_NAME) — a name you asked for and did not get is never silently dropped:
- an endpoint matching no operation in your spec (the verb and path must match exactly, placeholder names included);
- two operations on one resource ending up with the same method name. Glotto names both and stops, rather than picking a winner or appending a suffix — you wrote both names, so you hold the fix.
Renaming a method your customers already call is an API-compatible change if you say so: keep the old name working with
aliasesand mark it withdeprecated, and breaking-change detection reports the rename as non-breaking.
Optional-key reference pages
Optional settings are grouped by the part of the generated surface they shape:
- Client behavior — client runtime defaults, transforms, naming, custom casings, positional parameters, and enum naming.
- Model shaping — model names and inlining, enum types, soft-required inputs, and auto-populated values.
- API surface — target filtering, global exclusion, client methods, aliases and deprecations, streaming, and query serialization.
- Project settings — diagnostics, project-wide settings, code owners, MCP, generated docs, licensing, releases, README examples, and hosted binding.
See the CLI reference for the commands that read this config, the pipeline concept for how it’s consumed, or Getting started for a walkthrough.