# Swift OpenAPI Generator builds your API layer at compile time, not at review time

> Apple's Swift OpenAPI Generator is a Swift package plugin that turns an OpenAPI 3.0 or 3.1 document into Swift client and server code during the build, keeping generated files out of your repository. It suits teams already shipping Swift on macOS, Linux or Windows who want the API layer to match the document exactly, and it does not suit projects that need generated code to be checked in, diffed and signed off like ordinary source.

**apple/swift-openapi-generator** — Generate Swift client and server code from an OpenAPI document.

- Repository: https://github.com/apple/swift-openapi-generator
- Website: https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
- Stars: 1,990 · Forks: 192
- Language: Swift
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/apple-swift-openapi-generator

## What it replaces: hand-written request plumbing

An OpenAPI document is a specification for documenting HTTP services, written in YAML or JSON, that tools read to automate workflows such as generating the code needed to send and receive HTTP requests. Most Swift teams end up with two copies of the truth: the document that other languages consume, and a hand-written Swift layer of URL construction, request bodies, status-code switches and response decoding. Those two drift.

Swift OpenAPI Generator removes the second copy by generating it as a Swift package plugin runs inside your build. The README is explicit about the consequence: the code is generated at build time, so it is always in sync with the OpenAPI document and does not need to be committed to your source repository. That single design choice is the reason to consider this package over a CLI that writes a Sources directory.

The project is also an incubating Swift project, which is a status marker worth knowing before you build a roadmap on it. Releases move fast: 1.14.0 on 2026-10-06, 1.14.1 on 2026-10-14, and 1.13.1 on 2026-09-01, with the last push on 2026-10-16.

## Three abstractions split the generated code from URLSession and Vapor

The architectural commitment is that generated code is not bound to a specific HTTP library or web framework. The README lists client, server and middleware abstractions as a feature precisely for this reason, decoupling generated code from the HTTP client library and web framework.

On the client side, the generated `Client` type provides a method for each HTTP operation in the document and works with any HTTP library that implements `ClientTransport`. On the server side, you define a type conforming to the generated `APIProtocol`, with a method per operation, and it works with any web framework implementing `ServerTransport`. The generated server exposes `registerHandlers`, so wiring is a library call rather than a hand-maintained routing table.

That split is why the ecosystem spans nine repositories. The runtime library lives in `apple/swift-openapi-runtime` and is what the generated code actually calls. Transports are separate: `apple/swift-openapi-urlsession` for `URLSession`, `swift-server/swift-openapi-async-http-client` for AsyncHTTPClient, `frameo-net/swift-okhttp` for OkHttp, and `compnerd/openapi-winhttp-transport` for WinHTTP. Server transports are `vapor/swift-openapi-vapor`, `hummingbird-project/swift-openapi-hummingbird`, and `awslabs/swift-openapi-lambda` for AWS Lambda.

The README gives the rationale directly: the split exists to enable extensibility and minimize dependencies in your project. If your HTTP library is not on that list, you are writing a `ClientTransport` yourself, which is a real but bounded amount of work.

## Calling a generated client with URLSession

This is the shortest useful example in the README. You import the URLSession transport package and Foundation, construct the generated `Client` with a base server URL and a transport, call the generated method for the operation, and read the typed response body:

```swift
import OpenAPIURLSession
import Foundation

let client = Client(
    serverURL: URL(string: "http://localhost:8080/api")!,
    transport: URLSessionTransport()
)
let response = try await client.getGreeting()
print(try response.ok.body.json.message)
```

Three things are worth reading out of those lines. The constructor takes the base URL and the transport, so changing HTTP stack is a one-line change. The method `getGreeting()` is named after the operation in the document rather than being a generic `send`. And the response is an enum, so `.ok` is a case you switch on exhaustively, and `body.json` is a typed body rather than an `Any`.

The compile-time exhaustiveness is the real payoff. If someone changes an operation to add a status code, every `switch` over the output stops compiling, which is exactly the failure a hand-written client defers to runtime. There is also an example project for this path in the repository, `hello-world-urlsession-client-example`, alongside `command-line-client-example` and `curated-client-library-example`.

## Implementing generated server stubs on Vapor

The server side mirrors the client. You conform a type to `APIProtocol`, implement one method per operation, then register the handlers on a transport and run:

```swift
import OpenAPIRuntime
import OpenAPIVapor
import Vapor

struct Handler: APIProtocol {
    func getGreeting(_ input: Operations.GetGreeting.Input) async throws -> Operations.GetGreeting.Output {
        let name = input.query.name ?? "Stranger"
        return .ok(.init(body: .json(.init(message: "Hello, \(name)!"))))
    }
}

@main struct HelloWorldVaporServer {
    static func main() async throws {
        let app = try await Application.make()
        let transport = VaporTransport(routesBuilder: app)
        let handler = Handler()
        try handler.registerHandlers(on: transport, serverURL: URL(string: "/api")!)
        try await app.execute()
    }
}
```

Note what the generated types give you: `Operations.GetGreeting.Input` and `.Output`, a typed query with a defaulted value, and a body built from a typed initializer. The routing, JSON encoding and status code come from the transport.

Swapping Vapor for Hummingbird or AWS Lambda means changing the transport and the framework import, not the handler body. The repository carries `hello-world-vapor-server-example`, `hello-world-hummingbird-server-example`, and bidirectional and server event stream examples. If your deployment target is a function, the Lambda transport is the relevant one and the handler signature does not change.

## OpenAPI 3.2 support is preliminary, and 3.0 and 3.1 are the safe ground

The requirements table states that generator versions from `1.0.0` up to `main` support OpenAPI 3.0, 3.1 and 3.2, with 3.2 marked preliminary. That parenthetical is the whole caveat, and it is the first thing to check against your document.

Preliminary means the coverage of the newest specification version is incomplete. If your document uses 3.2-only constructs, expect either build failures or a construct that parses but produces something you did not intend. The README points to a Supported OpenAPI features page in the documentation for the authoritative list rather than attempting to summarise it in the overview.

Content types are a second boundary. The documented support covers JSON, multipart, URL-encoded form, base64, plain text and raw bytes, all represented as value types with type-safe properties. Anything outside that set, such as XML request bodies, is not described as supported. Form encoding and multipart being first class is notable because they are the two that hand-written clients usually get wrong.

The streaming story is the third. The README calls out streaming request and response bodies, enabling use cases such as JSON event streams and large payloads without buffering. The repository backs that with `bidirectional-event-streams-client-example`, `bidirectional-event-streams-server-example`, `event-streams-client-example`, `event-streams-server-example` and a `streaming-chatgpt-proxy`. Streaming without buffering is what makes server-sent events and multi-gigabyte uploads viable rather than theoretical.

## Platform support is asymmetric between the generator and its output

The README splits the platform question in two, because the generator and what it generates run in different places.

The generator plugin and CLI are used during development and are supported on macOS 10.15 or later, on Linux, and on Windows. They are not supported on iOS, tvOS, watchOS, visionOS or Android. That is a build-machine constraint, not a runtime one: you can generate on a Mac and ship to a phone.

The generated code and the runtime library are supported on macOS 10.15+, Linux, Windows, iOS 13+, tvOS 13+, watchOS 6+, visionOS 1+ and Android. Transports follow the same broad set. Practically, this means a project targeting iOS 13 and above can consume generated code without worrying about the generator's own platform limits.

Middleware is where the platform table does not tell the whole story, because middleware is yours to choose. The repository includes `auth-client-middleware-example`, `auth-server-middleware-example`, `retrying-middleware-example`, `metrics-middleware-example`, `logging-middleware-oslog-example` and `logging-middleware-swift-log-example`. So does middleware need to be OpenAPI-aware, how does it interact with streaming, and what happens when a retry middleware meets a non-idempotent operation? The examples answer the first question, and the others you will have to work out yourself.

## Where this approach stops working

The build-time design has costs that teams may discover over time.

First, a Swift package plugin executes during the build, which means your build environment needs the package graph resolved and the plugin allowed to run. That is a different CI requirement from checking in generated code and building offline, and it is the single most common reason adoption stalls in restricted or air-gapped pipelines.

Second, generated code that is never committed is code nobody reviews. That is a win when the document is the source of truth, and a loss when you want a diff showing that a schema change altered a Swift type. There is a middle path, since the repository ships `manual-generation-generator-cli-example` and `manual-generation-package-plugin-example`, but the README does not document a command for producing files into a source directory.

Third, the whole design assumes the API is HTTP and is described by OpenAPI. GraphQL, gRPC, JSON-RPC over WebSocket, and hand-rolled protocols fall outside it entirely.

Fourth, the middleware question above is real. Retrying a generated operation requires knowing whether it is idempotent, and the generated `Input` and `Output` types do not obviously carry that judgement for you.

## Where to start, and how to choose between transports

The README's own path is step-by-step tutorials in the documentation, then a list of example projects for experimenting with ecosystem packages. The Examples directory in the repository is the practical index: twenty-plus directories covering the client, the server, streaming, middleware, and combinations.

For transport selection, decide by runtime target first. Apple platforms and macOS command line tools point at `OpenAPIURLSession`, since `URLSession` is already there. Linux server processes point at AsyncHTTPClient, which is what `swift-server/swift-openapi-async-http-client` provides. Android points at OkHttp, and Windows-specific work has a WinHTTP transport. Server side, Vapor and Hummingbird cover long-running services, and AWS Lambda covers functions.

Then pick one middleware example to copy before writing your own. Start with `retrying-middleware-example` or `auth-client-middleware-example`, because both change observable behaviour and both are easier to get right by adapting than by writing from scratch.

Licence is Apache-2.0, with `LICENSE.txt` and `NOTICE.txt` in the repository root. Generated code inherits that licence in your project, which matters for organisations that run licence checks over everything in the dependency tree.

## Conclusion

Adopt Swift OpenAPI Generator when the OpenAPI document is the source of truth, your build runs on macOS, Linux or Windows, and you would rather not review machine-written Swift in a pull request. Do not adopt it if your CI only builds on Linux ARM without network access to resolve package plugins, if you need generated sources as committed artifacts, or if your service uses a protocol the tool's supported-features list does not cover. Before writing handlers, open the supported OpenAPI features page in the documentation and confirm that every construct in your document, especially 3.2 material, is listed there.

## FAQ

### Do I need to commit the code that Swift OpenAPI Generator produces?

No. The README states the code is generated at build-time, so it is always in sync with the OpenAPI document and does not need to be committed to your source repository.

### Which OpenAPI versions does Swift OpenAPI Generator support?

Generator versions from 1.0.0 to main support OpenAPI 3.0 and 3.1, with preliminary support for 3.2.

### Which platforms can run the generator itself?

The generator plugin and CLI are supported on macOS 10.15 and later, Linux and Windows, and not on iOS, tvOS, watchOS, visionOS or Android. The generated code and runtime library are supported on all of those, with iOS 13+, tvOS 13+, watchOS 6+ and visionOS 1+ as the minimums.

### Can Swift OpenAPI Generator stream request and response bodies?

Yes. The README lists streaming request and response bodies as a feature, enabling use cases such as JSON event streams and large payloads without buffering.

### How do I use Swift OpenAPI Generator with Vapor or Hummingbird?

Conform a type to the generated `APIProtocol`, implement a method per operation, then call `registerHandlers` on a `ServerTransport` such as `VaporTransport`. Separate repositories provide the Vapor, Hummingbird and AWS Lambda server transports.

### Can I run the generator as a CLI instead of a build plugin?

The repository ships a generator CLI alongside the package plugin, and the Examples directory includes `manual-generation-generator-cli-example` and `manual-generation-package-plugin-example` for the two approaches. The README does not document a command for writing output into a committed source directory.

## Sources

- [apple/swift-openapi-generator on GitHub](https://github.com/apple/swift-openapi-generator)
- [License: Apache-2.0](https://github.com/apple/swift-openapi-generator/blob/main/LICENSE)
- [Project website](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation)
- [README](https://github.com/apple/swift-openapi-generator/blob/main/README.md)
- [Releases](https://github.com/apple/swift-openapi-generator/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/apple-swift-openapi-generator
