Skip to content
Engineering

The open-swaggo docs described an API that didn't exist

A top-to-bottom accuracy pass on the open-swaggo docs — deleting a fictional CLI and fake config fields, fixing every example — plus the same LLM-friendly endpoints.

Andrian Prasetya Andrian Prasetya
open-swaggo docs update: 100% accurate documentation aligned with the real Go source, plus AI/LLM-friendly docs.

Last week I went through the go-migration docs line by line and made every example compile. This week I did the same for open-swaggo — and it was worse.

go-migration’s docs had drifted: right shape, wrong details. open-swaggo’s docs had drifted into fiction. Whole pages documented an aspirational API that was never built — a CLI that doesn’t exist, config fields that were never defined, a code generator for a language the package doesn’t support. A reader following the Getting Started guide couldn’t get a single endpoint to compile, because the functions in the example weren’t real.

So I rewrote the whole site against the actual Go source: Getting Started, Examples, Adapters, API Reference, Features, Guides, FAQ, Troubleshooting, and the landing page.

The examples used an API that was never built

The core failure was a docs site written against a design, not against the code. Here are the corrections that mattered most.

Creating and mounting docs

The old flow passed endpoints into the constructor and mounted through a package-level function. Neither signature exists.

go main.go Before
// None of this compiles
docs := openswag.New(cfg, endpoints...)
openswag.Mount(mux, "/docs", docs)
go main.go After
// Endpoints are added; mounting is a method.
docs := openswag.New(cfg)
docs.AddAll(endpoints...)
docs.Mount(mux, "/docs")

Responses are a map, not a slice

The status code is the map key — there was never a StatusCode field.

go endpoints.go Before
Responses: []openswag.Response{
  {StatusCode: 200, Description: "OK", ContentType: "application/json"},
}
go endpoints.go After
// Status code is the map key — no StatusCode field.
Responses: map[int]openswag.Response{
  200: {Description: "OK", Schema: UserResponse{}},
}

Title and Version live on Info

go config.go Before
openswag.Config{
  Title: "My API",
  Version: "1.0.0",
  UIConfig: ...,
}
go config.go After
openswag.Config{
  Info: openswag.Info{Title: "My API", Version: "1.0.0"},
  UI: ...,
}

Adapter argument order

The framework adapters take (router, docs, basePath), and MountGroup doesn’t take a base path at all.

go router.go Before
swaggin.Mount(r, "/docs", docs)
swaggin.MountGroup(api, "/docs", docs)
go router.go After
// (router, docs, basePath); MountGroup takes no basePath.
swaggin.Mount(r, docs, "/docs")
swaggin.MountGroup(api, docs)

Authentication by scheme name

Security is a list of registered scheme names, not inline config structs.

go auth.go Before
Security: []auth.Scheme{
  auth.BearerAuth(auth.BearerAuthConfig{...}),
}
go auth.go After
// Scheme is registered once; reference by name.
Security: []string{openswag.SecurityBearerAuth}
// docs.BuildSpec().AddSecurityScheme(...)

Deleting the fiction

Some of the old content didn’t describe a real thing at all, so it had to go rather than be corrected:

  • A fake CLI. open-swaggo init, open-swaggo gen, and go install ...@latest all implied a command-line tool. open-swaggo is a library — installation is go get, full stop.
  • PHP code snippets. The real snippet generator emits curl, JavaScript, Go, and Python. There is no PHP. Those examples were removed.
  • A multi-tenant example built entirely on functions that don’t exist — rewritten against the real API.
  • Fictional types and fields, including Config.{Security, Console, Playground, Endpoints, BasePath}, schema.Generate, ParseTags/TagInfo, examples.NewGenerator, snippets.SnippetConfig/LangPHP, auth.*Config{}, auth.OAuth2(), WithCredentialStore, and docs.AddEndpoint/GenerateSpec.

Calling things by their real names

A pile of functions were documented under names that were close, but wrong:

Documented (wrong) Actual API
schema.Generate(v) schema.FromType(v)
docs.AddEndpoint(...) docs.Add(...)
docs.GenerateSpec() / docs.Spec() docs.SpecJSON() / docs.BuildSpec()
versioning.Differ{...}.Diff(...) versioning.NewDiffer().Compare(...)
examples.NewFaker(seed) examples.NewFaker()

What the docs now actually cover

Cutting fiction left room to document the real capabilities that had never made the page:

  • Endpoint.QueryParams and PathParams — structs with form/param tags, the actual way you declare parameters.
  • The security constantsopenswag.SecurityBearerAuth, SecurityApiKey, and friends.
  • The real schema tagsjson, example, format, swagger, description.
  • The methods that existSpecJSON(), BuildSpec(), GetUIConfig().

Docs for humans and machines

The other half of this update mirrors what go-migration got: the docs now speak to LLMs directly, instead of forcing a model to scrape rendered HTML and hallucinate the API (which, given how wrong the old text was, would have been catastrophic anyway).

  • /llms.txt — a compact index of every docs page, the convention agents look for first.
  • /llms-full.txt — the entire documentation as one clean Markdown file.
  • Raw Markdown per page — every page is reachable as its source, e.g. /docs/getting-started/installation.mdx.
  • A “Copy as Markdown” button — copies the page as clean Markdown, with JSX components flattened.
  • An “Open in LLM” dropdown — open the page straight in ChatGPT or Claude, plus a “View raw Markdown” option.

The flattening is the interesting bit. The pipeline runs remark with a custom plugin that collapses MDX components — Tabs, Cards, and the rest — into plain Markdown, so the output reads cleanly to a model instead of arriving as a soup of component tags.

The principle is the same one from the go-migration write-up: the machine reads the same honest source you do. If the human-facing example compiles, so does the one an assistant copies — because they’re the same bytes.

Why I keep writing these

Two packages in, the pattern is clear enough to name: documentation is a promise, and an unverified promise quietly becomes a lie. A spec generator whose own docs can’t generate a spec is the most embarrassing version of that — and it was the version I was shipping until this week.

If you’re using open-swaggo and an example doesn’t compile, that’s now a bug. I’d like to hear about it on the repo.