Generates Dart code so beautiful, it must be from outer space!
A simple, hackable OpenAPI 3.0 and 3.1 generator.
Implements most of the OpenAPI spec. Patches welcome.
space_gen is typically installed via pub, so you have two options for install:
Short command line (just space_gen) for use across all projects:
dart pub global activate space_genOr install a specific version using:
dart pub global activate space_gen <version>If you haven't already, you might need to set up your path.
Useful if you need per-project versions or want everything self-contained.
dart pub add --dev space_gendart run space_gen <args>- Generates highest quality, modern Dart code.
- Zero analyzer, formatter or linter errors. Aims to be as good as handwritten.
- Gives readable errors on failure.
- Aspires to handle all of OpenAPI 3.x.
- Generates testable code.
- Modern null-safe Dart (currently 3.8+ only)
- Generates properly recursive toJson/fromJson which round-trip fully.
- Able to generate immutable models.
- Uses real enum classes.
- Handles oneOf, allOf, anyOf.
- fromJson is non-nullable.
- Generates maybeFromJson with explicit nullability.
- Generates independent classes, which can be imported independently.
- Correctly follows required vs. nullable semantics.
This started as a hobby project in August 2023, there were two separate (soon to be combined?) OpenAPI generators for Dart. One for package:http and one for package:dio. I only ever used the http one and it had lots of bugs. I looked at fixing them in OpenAPI, but failed to figure out how to hack on the OpenAPI generator (Java) or successfully interact with the community (several separate slacks) so ended up starting my own. Still in 2025 there doesn't seem to be a consensus towards a better Dart generator, so releasing this one.
- Loader - Fetches spec urls and imports.
- Parser - Turns Json/Yaml parsed tree into Spec, some validation.
- Resolver - Resolves all references into Resolved* tree, more validation.
- Renderer - Translates Resolved tree into Render tree. Render tree is passed off to FileRenderer which uses SchemaRenderer to render api (operation) and model (schema) files and imports.
space_gen supports consumers who don't use the analysis_options.yaml it
emits. A consumer can subclass FileRenderer and override the scaffold hooks
(renderAnalysisOptions, the barrel, the HTTP client) to no-op, then
hand-maintain those files themselves — shorebird_code_push_protocol does
exactly this, down to its own workspace analysis_options.yaml formatter
settings, which space_gen reads and respects at write time.
Because of that, lint noise from spec-derived prose (angle brackets in
descriptions, unresolvable [bracket] references, long lines, etc.) is
suppressed in-file, via emit-time // ignore_for_file: directives added by
the maybeAdd*Ignore helpers in render/formatting.dart. A per-file directive
travels with the .dart file and works regardless of whose analysis_options
wins; a blanket disable in the emitted analysis_options.yaml only reaches
consumers who keep it, so it silently fails for the hand-maintained case.
New per-spec lint suppression should follow this pattern rather than adding a
blanket disable. (The blanket disables still present in the
analysis_options.mustache template predate this decision.)
The overriding goal is Dart output that reads as if it were hand-written (see Project Values). That always wins. Where — and only where — it leaves a genuine choice between shapes that are equally good Dart, space_gen prefers the one that resembles the OpenAPI Generator's Dart client. Resembling the OpenAPI Generator is a fallback we reach for when it's free; it is never a reason to generate worse Dart.
The reason for the fallback is migration: when this project started, the
OpenAPI Generator was the most popular Dart client generator, so the main way
people would adopt space_gen was by porting an existing generated client, which
is smoothest when the generated surface (field names, accessors, types) lines
up. This is why, for example, an object with additionalProperties exposes its
overflow through an entries map rather than a field name we might pick in
isolation.
Even this fallback is a default we may revisit — the opt-in quirks already diverge on purpose, and as LLMs take over the mechanical parts of migration, byte-level familiarity with one reference generator matters less than it used to. But being OpenAPI-Generator-shaped is always subordinate to being good Dart.
space_gen is iterated against a rotation of real-world OpenAPI specs.
For each, the regenerated client is expected to dart analyze clean
and its synthesized round-trip tests are expected to pass.
- GitHub REST API (~1000 operations, OpenAPI 3.0) — Complete.
- Discord API (~511 schemas, ~141 paths, OpenAPI 3.1) — In progress.
- SpaceTraders — Complete.
- Train Travel API (YAML, OpenAPI 3.1) — Complete.
- Backstage catalog API
(YAML, OpenAPI 3.1) — Complete. Exercises
JsonObject/ open-objectallOfmembers andproperties: {}+additionalPropertiesmaps. - Petstore (the OpenAPI canonical example) — Complete.
If you try space_gen against a public spec and the output isn't clean, please file an issue.
space_gen implements a few OpenAPI quirks to optionally make the generated output maximally openapi_generator compatible in case you're transitioning from openapi_generator to space_gen.
OpenAPI makes all List values default to [], and stores all lists as non-nullable, even if they're nullable (not required) in the spec. This breaks round-tripping of values, since if your 'list' property is null (or missing) openapi_generator will parse it as [] and send back []. The openapi_generator can never send null for a list value. space_gen has this quirk on by default for now, but it can be disabled.
OpenAPI uses SCREAMING_CAPS enums, this can be enabled in space_gen for easier transition from openapi_generator to space_gen. By default space_gen will use lowerCamelCase enums matching Dart style.
OpenAPI generates mutable model types and your existing code may depend on this behavior. space_gen can generate either mutable or immutable model types and by default makes models immutable.