# How to generate Go doc comments from OpenAPI descriptions

> Generate a Go client with oapi-codegen, OpenAPI Generator, and sdkgen from one document, and count which summaries, descriptions, and examples reached the comments.

Source: https://voxgig.com/howto/go-doc-comments-from-openapi

- Audience: library-maintainer
- Level: intermediate
- Languages: go
- Verified: 2026-10-21
- Published: 2026-10-19

## Short answer

Run oapi-codegen or OpenAPI Generator over the document: both carry each operation's summary and description into the method's doc comment behind the method name, and oapi-codegen carries schema examples too. Neither converts CommonMark, so a list or bold text in a description renders as literal text in `go doc` and on pkg.go.dev, and only oapi-codegen writes gofmt-clean files. The sdkgen Go target writes template prose and leaves the document's text in its model and reference.

---
## You will need

Go 1.24 or later, an OpenAPI 3 document with `summary` and `description` on every operation,
and a Go module you regenerate. Verified 2026-10-21 against Go 1.24.7, oapi-codegen 2.8.0, OpenAPI
Generator 7.26.0, @voxgig/sdkgen 4.35.0, and @voxgig/create-sdkgen 0.30.8. The
[Operation Object](https://spec.openapis.org/oas/v3.0.3.html#operation-object) says a
description MAY use CommonMark, and Go doc comments have a
[syntax of their own](https://go.dev/doc/comment#syntax).[^1]

## Approaches compared

| Approach | When it fits | What it costs you | When to pick something else |
| --- | --- | --- | --- |
| [Hand-written doc comments](https://go.dev/doc/comment) | A small client, and prose that explains more than the document says | Every change to an operation is an edit nobody is reminded to make, so the comments drift from the document without a signal | The document changes more than once a quarter |
| [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen#configuration) | A Go-only client from one document, with the summary, description, and examples in the comments and output `gofmt` leaves alone | CommonMark pasted as written, a comment template you can override only by replacing the whole `client.tmpl`, and a runtime module dependency | You generate clients in other languages from the same document |
| [OpenAPI Generator Go client](https://openapi-generator.tech/docs/generators/go/) | One generator for every language your API ships, with the Go templates extractable and editable | Java-style `@param` lines in every method comment, no examples, files `gofmt` rewrites unless you hook it into post-processing, and a Java runtime | Go is your only target |
| [sdkgen Go target](https://voxgig.com/sdk) | An entity-shaped SDK across languages, with the document's field text in its model and reference | Template prose in the doc comments, no operation summary or description anywhere in the output, and files `gofmt` rewrites | The doc comments must say what the document says |

Voxgig maintains sdkgen. This page compares it with oapi-codegen and OpenAPI Generator.

oapi-codegen carries the most and costs you a runtime module and a template you can replace
only whole. OpenAPI Generator carries the same prose plus a block of `@param` tags that Go renders as
code, and costs you a Java runtime and a `gofmt` step of your own. sdkgen carries the field
descriptions into its model and its reference file, but no operation prose reaches its Go, so
its comments say what its templates say.

## Generate three clients from one document

The document has four operations whose descriptions are written the way most are: a second
paragraph, backticks, a bullet list, bold text, a link, and a Markdown heading. The schemas
carry `description` and `example` on most properties.

```json title="openapi.json"
      "post": {
        "operationId": "createMeter",
        "summary": "Registers a meter.",
        "description": "Registers a meter at a site. The meter starts in the `pending` state until its first reading arrives.\n\n- `serial` must be unique for the account\n- `site` must name an existing site\n\n**Note:** a meter cannot be moved between sites. Register a new one instead.",
```

`generate.sh` ran the three generators once, with the network, and its output is committed
under `generated/`. Each output is a Go module of its own, so the report at the root parses
all three while `go test ./...` builds only the report.

```bash title="generate.sh"
mkdir -p generated/oapi-codegen
GOTOOLCHAIN=auto go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.8.0 \
  -config oapi-codegen.yaml openapi.json
(cd generated/oapi-codegen && go mod init meterco.example/oapi-codegen && go mod tidy)

curl -sSfO https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.26.0/openapi-generator-cli-7.26.0.jar
java -jar openapi-generator-cli-7.26.0.jar generate -g go -i openapi.json -o oag-out \
  --additional-properties=packageName=meterco --git-user-id=meterco --git-repo-id=meterco-go
mkdir -p generated/openapi-generator
cp oag-out/*.go oag-out/go.mod oag-out/go.sum generated/openapi-generator/
rm -rf oag-out openapi-generator-cli-7.26.0.jar

npx --yes @voxgig/create-sdkgen@0.30.8 meterco --def openapi.json --target go
(cd meterco-sdk/.sdk && npm run generate)
```

## Count what each generator carried over

`report.go` parses every generated file with `go/parser`, never importing it, and reads each
exported symbol's doc comment. It then looks for the document's text in those comments: the
four summaries, the first sentence of the four descriptions, the thirteen distinct property
descriptions, and the seven example values.

```go title="report.go"
// Symbol is one exported func, method or type with the doc comment it carries.
type Symbol struct {
	Name string
	Kind string // func, method or type
	Doc  string
	File string
}

// Markers of CommonMark that a Go doc comment renders as literal text.
var markdown = regexp.MustCompile("`[^`]+`|\\*\\*[^*]+\\*\\*|\\[[^\\]]+\\]\\([^)]+\\)|(?m)^- ")
```

```bash
go run .
```

```text output
document: 4 operations with a summary and a description, 13 field descriptions, 7 example values

generator                 files exported documented start name summaries descriptions fields examples markdown longest  gofmt
oapi-codegen 2.8.0            1       73         69         66       4/4          4/4  13/13      7/7       18     167    0/1
OpenAPI Generator 7.26.0     11      261        119        112       4/4          4/4  13/13      0/7        4     167  10/11
sdkgen 4.35.0                55      215         86         45       0/4          0/4   0/13      0/7        3      93  12/55
```

The two OpenAPI generators carry every summary, every description, and every field
description, and oapi-codegen alone carries the examples, as `Example: mtr_01HZX4` lines under
the field. Both keep the longest description as one 167-character line. sdkgen's doc comments
carry none of it. The field descriptions reach its model files, `REFERENCE.md`, and the
configuration map its Go code builds, and the operation prose reaches nothing in the output.
The typed `Meter` struct it writes has no fields, while its model lists six.

```go title="generated/oapi-codegen/meterco.gen.go"
// Meter A physical meter and where it is installed.
type Meter struct {
	// DecommissionedAt When the meter was taken out of service, or null while it is still in service.
	DecommissionedAt *time.Time `json:"decommissionedAt"`

	// Id The id.
	//
	// Example: mtr_01HZX4
	Id string `json:"id"`
```

The `start name` column counts comments that open with the identifier, which is the Go
convention and the synopsis pkg.go.dev shows. The same run names the exceptions.

```bash
go run .
```

```text output
oapi-codegen 2.8.0: 1 documented funcs or methods do not open with their name, such as NewClient
  NewClient: "Creates a new Client, with reasonable defaults"

OpenAPI Generator 7.26.0: 7 documented funcs or methods do not open with their name, such as CreateMeterExecute, GetMeterExecute, Site
  CreateMeterExecute: "Execute executes the request"

sdkgen 4.35.0: 35 documented funcs or methods do not open with their name, such as Record, Record, String
  Record: "The record a context serialises as; the options and the client stay out."

sdkgen 4.35.0: the document's prose appears outside Go comments, in REFERENCE.md, core/config.go, model/meter.aontu, model/reading.aontu
```

Both OpenAPI generators put the method name in front of the summary, so `Registers a meter.`
becomes `CreateMeter Registers a meter.` rather than a sentence that opens on a verb. The
exceptions are template text: OpenAPI Generator documents `CreateMeterExecute` as `Execute`.
[ST1020 in `staticcheck`](https://staticcheck.dev/docs/checks/#ST1020) flags that shape once
you enable it, because it is off by default, and `go vet` does not check it.

## Read the comments the way pkg.go.dev renders them

`go doc` prints a comment through the same `go/doc/comment` syntax the site uses, so it shows
what a reader will see without a publish.

```bash
cd generated/openapi-generator && go doc . DefaultAPIService.CreateMeter
```

```text output
package meterco // import "github.com/meterco/meterco-go"

func (a *DefaultAPIService) CreateMeter(ctx context.Context) ApiCreateMeterRequest
    CreateMeter Registers a meter.

    Registers a meter at a site. The meter starts in the `pending` state until
    its first reading arrives.

    - `serial` must be unique for the account - `site` must name an existing
    site

    **Note:** a meter cannot be moved between sites. Register a new one instead.

        @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
        @return ApiCreateMeterRequest
```

Four things happened to the paragraph. The bullet list became one sentence, because a
[Go list](https://go.dev/doc/comment#lists) is a span of indented lines and these were not
indented. The asterisks and backticks stayed, because the syntax has no emphasis and no code
spans. The `@param` lines were indented in the template, and an indented span is a
[code block](https://go.dev/doc/comment#code), so they print as code.[^2] The
167-character sentence wrapped at 80 columns, the
[printer's default](https://pkg.go.dev/go/doc/comment#Printer). One CommonMark feature
survived: the `# Units` heading in `recordReading` renders as a heading, because Go uses the
same character.[^3]

sdkgen's output is the other shape of the problem. Its comments are well-formed Go sentences
that name the symbol and say nothing the document said.

```bash
cd generated/sdkgen && go doc ./entity Meter
```

```text output
package entity // import "github.com/voxgig-sdk/meterco-sdk/go/entity"

type Meter struct {
}
    Meter is the typed data model for the meter entity.
```

## Keep the output formatted and fix the words upstream

[`gofmt -l`](https://pkg.go.dev/cmd/gofmt) names the files it would rewrite. Twenty-two of
the sixty-seven generated files are on the list: ten from OpenAPI Generator and twelve from
sdkgen, mostly for import order, spacing, and alignment. In `api_default.go` it also puts a
blank line and a tab before the `@return` line of four comments. oapi-codegen's one file is
already in the form `gofmt` writes.

```bash
gofmt -l generated
```

```text output
generated/openapi-generator/api_default.go
generated/openapi-generator/client.go
generated/openapi-generator/configuration.go
generated/openapi-generator/model_error.go
```

OpenAPI Generator runs a formatter for you when you pass `--enable-post-process-file` and
set `GO_POST_PROCESS_FILE` to `gofmt -w`, as its
[post-processing page](https://openapi-generator.tech/docs/file-post-processing/) describes.
For sdkgen, run `gofmt -w` over the output after `npm run generate`. The formatter fixes the
alignment and leaves the asterisks, which are text.

The words are fixed in one of two places. The document is the first: a description written as
plain sentences, with no list and no emphasis, arrives intact in every target. The template is
the second, and each of the three tools lets you change it without a fork. oapi-codegen takes
[`output-options.user-templates`](https://github.com/oapi-codegen/oapi-codegen#custom-code-generation),
a map from a built-in template's filename to a local path, a URL, or inline text, so the
method comment changes by replacing `client.tmpl` whole. OpenAPI Generator's
[`author template`](https://openapi-generator.tech/docs/templating/#retrieving-templates)
command extracts `api.mustache` for the Go generator, and `-t` points at your copy. sdkgen
[vendors its templates](https://voxgig.com/sdk/custom) into the project, so the sentence that
documents `CreateTyped` lives in `.sdk/src/cmp/go/fragment/EntityCreateOp.fragment.go` in your own
repository. Its customization page warns that a hand edit to a vendored file is the one change
an upgrade can revert.

## Check it worked

```bash
go test -v . | grep -E '^(=== RUN|PASS$|FAIL$)'
```

```text output
=== RUN   TestTheDocumentHasTheProseTheComparisonNeeds
=== RUN   TestTheTwoOpenAPIGeneratorsCarryEverySummaryAndDescription
=== RUN   TestOnlyOapiCodegenCarriesTheExamples
=== RUN   TestSdkgenLeavesTheProseInItsModel
=== RUN   TestTheTypedMeterStructHasNoFields
=== RUN   TestGofmtPassesOapiCodegenAndFailsTheOtherTwo
=== RUN   TestMarkdownSurvivesIntoTheCommentsOfBothOpenAPIGenerators
=== RUN   TestTheCommentsThatDoNotOpenWithTheirName
=== RUN   TestProseInAGoStringIsOutsideTheComments
PASS
```

The run ends in `PASS`, and the `grep` drops the per-test timings. The `gofmt` test pins the
counts at `0`, `10`, and `12`, and the struct test pins the empty `Meter`. A generator release
that fills the struct or formats its own output turns a test red.

## When it goes wrong

A method's synopsis names the wrong thing. OpenAPI Generator writes `Execute executes the
request` as the comment on `CreateMeterExecute`, so the package index on pkg.go.dev lists a method whose
first word is not its name. The template is `api.mustache`; extract it, change the comment,
and pass the directory with `-t`.

The list in a description comes out as one line. Go recognizes a list only when its items
are indented, and CommonMark reads an indented block as code, so the two formats cannot hold
the same list. Write the items as sentences in the document, or move the list into the
template where the indentation is yours.

The summary reads as a label. `ListMeters List meters` is what both generators print when the
summary is `List meters`, because the name goes in front of a title. A summary written as the
method's predicate, `lists the meters installed for the account`, reads as a sentence after
the prefix. Every other renderer of the document then shows a strange heading, so decide which
reader comes first.

## When not to do this

Do not edit a generated file to fix its comments. oapi-codegen and OpenAPI Generator rewrite
their output on every run, and sdkgen's scaffold resyncs vendored files on upgrade. The edit
lasts until the next regeneration, which happens without a word to the editor.

Do not generate comments from a document whose descriptions were never written for a reader.
The report counts `The id.` as a carried field description, because it is one.
The fix is the sentence in the document, which both OpenAPI generators then copy faithfully.

Do not pick the sdkgen Go target for the comments. At this version its Go output carries no
operation summary or description, and its typed `Meter` struct has no fields to hang a
description on. The document's field text sits in the model, the Go configuration map, and
`REFERENCE.md` instead. Pick sdkgen
for the entity shape and the shared behavior across languages, and expect to write the Go
comments into its vendored templates yourself.

A CommonMark list, emphasis, or link in a description earns nothing in a Go target that plain
text would not, so leave them out when Go is one of your targets.

## Related how-tos

- [Generate Python docstrings and type hints from an API spec](/howto/python-docstrings-and-type-hints-from-a-spec)

- [Generate your first SDK with sdkgen](/howto/first-sdk-with-sdkgen)

- [Decide between generating and hand-writing a client](/howto/generate-or-hand-write-a-small-api-client)

## Last verified

Verified 2026-10-21 against Go 1.24.7, oapi-codegen 2.8.0, OpenAPI Generator 7.26.0,
@voxgig/sdkgen 4.35.0, and @voxgig/create-sdkgen 0.30.8. Every output block is what the
command preceding it printed, run in the page's code directory. The generation ran once,
through `generate.sh`, with the network, Java 21, and Node 22, and its output is committed
under `generated/`; the checks parse that output and never regenerate it. A second run of the
sdkgen step reproduced its committed tree byte for byte. pkg.go.dev was not
fetched: the rendering claims rest on `go doc`, which reads the syntax the Go doc comment page
defines for both tools.

[^1]: The Go doc comment page allows that [Markdown aficionados](https://go.dev/doc/comment#syntax)
can view its syntax as a simplified subset of Markdown, and in the same breath rules out font
changes and raw HTML. The OpenAPI specification, for its part, says a description MAY use
CommonMark. The two formats share the heading character and the list marker, and part with
each other on the list's indentation, the emphasis, and the link.

[^2]: The `@param` and `@return` lines come from OpenAPI Generator's
[`api.mustache`](https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/go/api.mustache),
which writes a Java documentation comment into a Go file, one leading space and all. Java's
documentation tool reads an `@` tag as a tag. Go reads a leading space as indentation, and indentation as a
[code block](https://go.dev/doc/comment#code), so the tags arrive on pkg.go.dev as code,
which is the one rendering neither language intended.

[^3]: The `#` [heading syntax](https://go.dev/doc/comment#headings) arrived in Go 1.19. Before
it, a heading was any single-line paragraph with no terminating punctuation, identified by
inference, and `gofmt` rewrites those implicit headings into the explicit form. The
Markdown heading in this page's document therefore renders correctly by coincidence of
character.