utoipa: compile-time OpenAPI documentation for Rust APIs
Simple, Fast, Code first and Compile time generated OpenAPI documentation for Rust
At a glance
- What is it?
- utoipa generates OpenAPI 3.1 documents from Rust types and handler attributes at compile time, with optional bindings for axum, actix-web and Rocket. It is framework-agnostic, but the framework extras are what decide how much of the document you have to write by hand.
- Who is it for?
- Adopt utoipa if your API is already described by Rust types and you want the OpenAPI document to come out of the compiler rather than a hand-edited YAML file. Do not adopt it if your routes are defined dynamically or you need to document an API that is not written in Rust, because the macros annotate Rust items and nothing else.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem utoipa solves for Rust API authors
The README frames the motivation as a choice between writing OpenAPI YAML or JSON by hand and having the document fall out of the code. utoipa takes the second position and calls it a code-first approach. The target reader is a Rust developer who already has types for request and response bodies and does not want to maintain a second, parallel description of the same shapes.
The crate is framework-agnostic. According to the README it "could be used together with any web framework, or even without one." That matters because it means the core crate does not assume axum, actix-web or Rocket. You can annotate plain Rust structs and enums with ToSchema and build an OpenApi value without ever mounting a web server.
The second audience is smaller but real: people who want to write the OpenAPI spec itself in Rust. The README says the crate "contains Rust types of the OpenAPI spec, allowing you to write the OpenAPI spec only using Rust if auto generation is not your flavor." So the generation macros are one entry point, and the type model is another.
What utoipa does not do is discover your routes by itself. Nothing in the README suggests the crate inspects a router at runtime. The document is assembled from what you annotate, which is the central trade-off of the whole design.
How the macros, features and framework extras fit together
The workspace layout tells most of the story. The repository has a utoipa/ directory for the core crate, utoipa-gen/ for the proc macros, and separate directories for utoipa-actix-web, utoipa-axum, utoipa-config, utoipa-rapidoc, utoipa-redoc, utoipa-scalar, utoipa-swagger-ui and utoipa-swagger-ui-vendored. The macros live in utoipa-gen and are re-exported through the macros feature, which the README says is enabled by default.
Framework support is opt-in through features. actix_extras parses path, path parameters and query parameters from actix-web path attribute macros. axum_extras lets you use IntoParams without defining the parameter_in attribute. rocket_extras does the equivalent parsing for Rocket. Without these features you still get the schema generation, but the path and parameter details are yours to write.
The README is explicit that the extras are not equal. For actix-web it lists parsing of path, path parameters and query parameters plus recognition of request and response bodies, and points at the utoipa-actix-web bindings. For axum it lists path and query parameter parsing and body recognition, with utoipa-axum bindings. For Rocket it lists path, path parameters, query parameters and body recognition. The axum row does not mention path parameters, so if that distinction matters to your API you should read the attribute documentation rather than assume parity.
Schema collection is recursive. The README says schemas are collected automatically from usages, with request bodies taken either from handler function arguments where the framework supports it or from the request_body attribute, and response bodies from the body or content attribute. There is a documented boundary: tuples, arrays and slices cannot be used as generic arguments on types, and types implementing ToSchema manually should not have generic arguments because they are not composeable and will produce a compile error.
One workspace detail is worth noting because it constrains builds. The root Cargo.toml pins time to ">=0.3, <0.3.52" with a comment explaining that cookie 0.18.1, pulled in through rocket_http, does not compile against time 0.3.52 and later. The pin is there so a fresh resolve works without committing a lockfile. If you use Rocket alongside utoipa, that ceiling applies to your dependency graph too.
Installing utoipa and generating a first document
The README does not include a copy-paste installation block, so the only exact dependency line available from the repository is the workspace pin in the root Cargo.toml. That file carries the time constraint quoted below, which is what a fresh resolve has to satisfy while the cookie issue described in its comment is open.
[workspace.dependencies]
time = { version = ">=0.3, <0.3.52" }The comment above that entry in the root Cargo.toml explains the reason: cookie 0.18.1, reached through rocket_http, does not compile against time 0.3.52 and later because Parsable::parse gained a defaults argument. The pin keeps a fresh resolve working without committing a Cargo.lock. If your project pulls in Rocket, expect to carry the same ceiling.
For the crate itself, the README names the features you enable rather than a dependency snippet. The ones relevant to a first setup are macros, which is on by default, and the framework extra for your server: actix_extras, axum_extras or rocket_extras. The yaml feature enables yaml_serde serialization of OpenAPI objects if you want YAML output instead of JSON.
The repository's own test recipe shows how the project exercises those features. The justfile runs cargo test per crate, and for utoipa-gen it passes a long feature list that includes actix_extras, chrono, decimal, uuid, ulid, url, time, repr and smallvec. That is not an install command, but it is the exact feature spelling to copy when you enable them in your own manifest.
cargo test -p utoipa --features openapi_extensions,preserve_order,preserve_path_order,debug,macrosTo serve a UI, add one of the viewer crates as a separate dependency. The workspace publishes utoipa-swagger-ui, utoipa-redoc, utoipa-rapidoc and utoipa-scalar as distinct crates, and the examples directory contains todo-warp-rapidoc, todo-warp-redoc-with-file-config and axum-utoipa-bindings as worked references. The examples/README.md is the place the repository points to for runnable setups. For the attribute-level syntax of ToSchema and the path attribute, the README defers to docs.rs rather than showing a full example, so confirm the arguments against the version you resolve.
Where utoipa stops helping
The most important limitation is that the document is only as complete as your annotations. If a handler is not listed in the openapi attribute and its types are not registered as components, it will not appear. There is no runtime route walk that catches the omission for you. On a large service this means the OpenAPI document can silently fall behind the router, and nothing in the build will fail.
The generic type restrictions are a second concrete edge. Tuples, arrays and slices cannot be used as generic arguments on types, and a manual ToSchema implementation on a generic type will not compile because the README states such arguments are not composeable. If your API surface leans on generic wrappers, expect to write concrete wrapper types or to implement the schema by hand.
Framework coverage is uneven by design. The README's own table gives actix-web and Rocket more parsing behaviour than axum, and everything under "Others" gets "little less automation" with no path or parameter parsing at all. If you use warp, tide or a framework not in the table, you are writing the path metadata yourself.
There is also a workspace-level constraint that has nothing to do with documentation quality. The clippy lint large_stack_arrays is set to deny across the workspace, with a comment tying it to a regression of large stack allocations in generated code. That is a deliberate guard, but it means generated code is expected to stay within stack-size limits, and any future expansion of what the macros emit has to respect that.
The MSRV is 1.88, stated in both the README badge and the workspace package metadata. That is a hard floor for anyone on an older toolchain.
Finally, utoipa is a documentation generator, not a validator. Nothing in the README claims it checks that your running server matches the document it produces.
utoipa compared with aide and hand-written specs
The most common comparison in this space is aide, which also targets Rust APIs and OpenAPI generation. The difference in approach is where the document is assembled. utoipa's core is macros: you annotate types with ToSchema and handlers with the path attribute, and utoipa-gen expands that at compile time. aide's model is built around generating the document from the router itself, so the route registration is the source of truth. That distinction decides which failure mode you get. With a router-driven generator, a route that is registered but unannotated still tends to appear in the output. With utoipa, a route that is not listed in the OpenApi derive does not appear, and the compiler will not tell you.
Neither approach is strictly better. If your team already treats Rust types as the contract and wants the schema to follow the types, utoipa's compile-time expansion keeps the document close to the code and produces no runtime cost for generation. If your routes change often and you would rather not remember to update an attribute list, a router-driven tool removes that step.
The other alternative is writing the OpenAPI document by hand, or generating it from a design-first spec. utoipa supports that direction too, since it ships Rust types for the OpenAPI spec and lets you build the document in Rust. But if your spec is authored elsewhere and Rust is a consumer, utoipa is the wrong layer: it is built to produce a document from Rust code, not to consume one.
For the UI side there is no real competition inside the project. utoipa delegates that to separate crates, and the choice between Swagger UI, ReDoc, RapiDoc and Scalar is a choice between those projects, not between utoipa configurations. Note that utoipa-swagger-ui-vendored exists as its own crate, which matters when you cannot fetch UI assets at build time.
Maintenance, releases and licence
The repository is not archived, and the last push was on 2026-09-22. The three most recent releases in the list are utoipa-swagger-ui-vendored 0.2.0, utoipa-swagger-ui 10.0.0 and utoipa-scalar 0.4.0, all published on 2026-09-22. The version numbers are not synchronized across crates, which is normal for a workspace where each crate is published independently, but it means you should track versions per crate rather than assuming one number covers the family.
The root Cargo.toml declares a publish order under workspace metadata: utoipa-config, utoipa-gen, utoipa, utoipa-swagger-ui-vendored, utoipa-swagger-ui, utoipa-redoc, utoipa-rapidoc, utoipa-scalar, utoipa-axum, utoipa-actix-web. That order is the dependency order, and it tells you which crates a release has to wait on. If you depend on utoipa-axum, your upgrade path runs through utoipa-gen and utoipa first.
Upgrade cost is concentrated in the macros. Because the document is generated at compile time, a change in utoipa-gen that alters emitted code shows up as a compile error or as a changed document, not as a runtime surprise. The workspace lint on large_stack_arrays exists precisely to catch regressions in generated code, so macro-level changes are guarded by CI. The justfile provides a test recipe that runs cargo test per crate, with utoipa-gen exercising a long list of features including actix_extras, chrono, decimal, uuid, ulid, url, time, repr and smallvec. That is a wide feature matrix, which is a good sign for coverage and a hint that some feature combinations are less travelled than others.
The licence is Apache-2.0 according to the repository metadata. The repository also contains a LICENSE-MIT file, which suggests dual licensing, but the stated licence for the project is Apache-2.0 and the LICENSE-APACHE file is present at the top level. If your organisation has rules about which of the two you accept, read both files rather than relying on the metadata field. That is a policy question for your legal team, not something this article can settle.
Editorial conclusion
Adopt utoipa if your API is already described by Rust types and you want the OpenAPI document to come out of the compiler rather than a hand-edited YAML file. Do not adopt it if your routes are defined dynamically or you need to document an API that is not written in Rust, because the macros annotate Rust items and nothing else. Before committing, check that your MSRV is at least 1.88, confirm whether you need the actix_extras, axum_extras or rocket_extras feature for your framework, and decide which UI crate you will mount, since utoipa-swagger-ui, utoipa-redoc, utoipa-rapidoc and utoipa-scalar are separate dependencies with their own release cycles.
Frequently asked questions
What is the utoipa alternative for generating OpenAPI docs in Rust?
The closest comparison is aide, which also targets Rust APIs but assembles the document from the router rather than from macros. utoipa's approach is code-first and compile-time: you derive ToSchema on types and annotate handlers, and utoipa-gen expands the document during the build. The practical difference is that an unlisted route will not appear in a utoipa document, while a router-driven generator tends to pick it up.
Is there a Rust utoipa alternative that works without macros?
utoipa itself covers that case. The README states the crate contains Rust types of the OpenAPI spec, so you can build the document in Rust without relying on auto generation. What utoipa does not offer is a runtime route scanner, so a macro-free setup still means describing the paths explicitly.
Which web frameworks does utoipa support out of the box?
The README lists actix-web, axum and Rocket in its framework table, each with a corresponding feature: actix_extras, axum_extras and rocket_extras. Anything else falls under "Others", which the README describes as giving the basic benefits with little less automation and no path or parameter parsing.
What Rust version does utoipa require?
The MSRV is 1.88. It is stated in the README badge and in the workspace package metadata in the root Cargo.toml, so it is a hard floor for the whole workspace.
Can utoipa document generic Rust types?
Partly. The README lists support for generic types as a feature, but notes that tuples, arrays and slices cannot be used as generic arguments, and that types implementing ToSchema manually should not have generic arguments because they are not composeable and will produce a compile error.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/juhaku-utoipa)