fibgen
Generate a complete OpenAPI document (3.1 and 3.0) for Go + Fiber services through static analysis.
No annotations, no // @Summary comments, no code generation, no changes to your handlers. Point fibgen at an existing project and get a spec with paths, parameters, request bodies, per-status responses, and fully resolved type schemas ($ref, enums, nullables).
Supports Fiber v2 and v3.

Why static analysis
Every Fiber handler is just func(c fiber.Ctx) error (v3) or func(c *fiber.Ctx) error (v2). The request and response types exist only as local variables inside the body (c.Bind().Body(&dto), c.JSON(user)). The signature is opaque, so runtime reflection yields nothing useful.
The only way to get "fully automatic, on unmodified code" is to analyze the code itself with go/packages + go/types: find the route registrations, follow them into the handler body, and recover the types from the c.* calls.
Install
go install github.com/nyawave/fibgen/cmd/fibgen@latest
Or build from source:
git clone https://github.com/nyawave/fibgen
cd fibgen
./build.sh # -> ./bin/fibgen (build.bat on Windows)
Usage
# from the root of the project you want to document
fibgen ./... > openapi.yaml
# explicit options
fibgen -dir ./myservice -o openapi.json --openapi-version 3.0 ./...
Flags
| Flag |
Default |
Description |
-o |
stdout |
output file; extension (.yaml / .json) selects the format |
-format |
from -o, else yaml |
output format: yaml | json |
-dir |
. |
project directory to analyze |
--openapi-version |
3.1 |
spec version: 3.1 or 3.0 |
--title |
module name |
API title |
--doc-version |
0.1.0 |
API document version |
--quiet |
false |
suppress diagnostics on stderr |
Anything the analyzer cannot resolve statically is reported on stderr as note: ... (suppress with --quiet).
What it detects
Routes
app.Get/Post/Put/Delete/Patch/Head/Options/Connect/Trace, All, Add(method, path, ...)
Group(prefix) with arbitrary nesting; prefixes are resolved through variable assignments and through routers passed as function parameters
- Fiber paths → OpenAPI paths:
:id → {id}, :id? (optional), :id<int> (constraint maps to a type), * / + (wildcards)
Handlers (resolved to a function body)
- named functions, controller method values (
ctrl.Create), inline closures, function-typed variables, and statically resolvable handler factories (makeHandler(service))
- project helper functions that receive the active Fiber context; actual arguments are propagated so
respond(c, value) retains the concrete response type
Requests
- body:
c.BodyParser(&dto) (v2), c.Bind().Body(&dto) / .JSON(&dto) (v3)
- query:
c.QueryParser(&s) / c.Bind().Query(&s) (struct expansion via query: tags), and c.Query/QueryInt/QueryBool/QueryFloat("name")
- path: from the route template, with the type upgraded by
c.ParamsInt("id")
- header:
c.ReqHeaderParser(&s), c.Bind().Header(&s), c.Get("X-...")
Responses
c.JSON(x) / c.XML(x) → schema of x, status 200
c.Status(code).JSON(x) → status from code (literal or fiber.StatusCreated)
c.SendStatus(code), c.SendString(...), c.Status(code).Send..., c.Redirect(...)
- multiple response shapes per status — when a handler returns different types depending on a flag (e.g. a short vs. full object on
?short=true), the alternatives are collected into a oneOf
- ad-hoc JSON objects —
c.JSON(fiber.Map{...}) / map[string]any{...} are expanded into an object with concrete properties; value types are inferred from the expressions, and nested literals and $refs are supported (e.g. {"message": string, "data": SomeStruct})
Types → JSON Schema
- structs → reusable components (
$ref), honoring json: tags, omitempty (drives required), and embedded/promoted fields
- pointers → nullable (
oneOf + null in 3.1, nullable: true in 3.0)
- slices/arrays →
array, maps → additionalProperties, []byte → string/byte
- named types backed by a group of constants →
enum
- well-known types:
time.Time → date-time, time.Duration, encoding/json.RawMessage, uuid.UUID, MongoDB primitive.ObjectID/DateTime, interface{}/any → free-form, fiber.Map → object
- cross-package name collisions are disambiguated by package name (e.g.
models.User vs. dto.User)
Example
A handler like this:
func (h *UserHandler) Create(c fiber.Ctx) error {
var req CreateUserRequest
if err := c.Bind().Body(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid body"})
}
user := h.svc.Create(req)
return c.Status(fiber.StatusCreated).JSON(user)
}
…with the route app.Group("/api/v1").Group("/users").Post("/", h.Create) produces:
/api/v1/users:
post:
tags: [users]
operationId: post_api_v1_users
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
"400":
description: Bad Request
content:
application/json:
schema:
type: object
properties:
error: { type: string }
required: [error]
Two ready-to-run example projects (Fiber v2 and v3) live under examples/.
How it works
cmd/fibgen CLI entrypoint
internal/analyzer load packages, find routes, resolve group prefixes, analyze handler bodies
internal/schema go/types -> version-neutral JSON Schema (+ component registry)
internal/model framework-neutral API model (routes, params, responses)
internal/openapi render the model to OpenAPI 3.0/3.1, serialize to YAML/JSON
internal/oapi insertion-ordered map for deterministic, diff-friendly output
examples/v2, v3 example Fiber projects used by the test suite
Pipeline: analyzer.Analyze → model.API → openapi.Builder.Build → oapi.OM → openapi.Marshal.
Building
./build.sh # current platform -> ./bin/fibgen
./build.sh all # cross-compile linux/macos/windows (amd64 + arm64)
./build.sh install # go install into $GOBIN / $GOPATH/bin
Windows: use build.bat with the same subcommands.
Limitations
Static analysis does not execute code, so there are honest boundaries:
- Dynamic call targets. Helpers and handler factories are followed when their target can be resolved statically. Calls through interfaces, dependency-injection containers, or dynamically selected function values may retain only the information visible at the call site.
- Dynamic paths. A path built from a non-constant string is skipped (with a
note: on stderr).
- Handlers behind DI/registries. If a handler cannot be resolved statically to a function (passed through an interface/container), its body is not analyzed and the route appears with a default 200 response.
- Generics in response types are handled in a limited way.
- Descriptions / summaries are not generated — there is no source for them without comments. This is a deliberate trade-off for zero-annotation operation.
Testing
go test ./...
The internal/openapi test analyzes the bundled examples/v2 and examples/v3 projects and validates the generated documents with kin-openapi.
Contributing
Issues and pull requests are welcome. Good first areas: interprocedural analysis (following response helpers), more binding patterns, and richer well-known type mappings.
License
MIT