How-to › Document and support developers

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.

Audience
Library maintainer
Level
intermediate
Topic
Ship docs with the code
Languages
Go
Verified

You generate a Go client from an OpenAPI document whose operations each carry a paragraph of description. On pkg.go.dev the client shows template sentences, or the paragraph pasted as written: a bullet list run together into one line, two asterisks around Note, and a block of @param tags. gofmt -l lists every generated file.

What you get

You will end up with three Go clients generated from one document, a report counting what reached each one’s doc comments, and go doc and gofmt -l runs on the result. This is for you if you publish a Go client you regenerate.

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 says a description MAY use CommonMark, and Go doc comments have a syntax of their own.1

Approaches compared

ApproachWhen it fitsWhat it costs youWhen to pick something else
Hand-written doc commentsA small client, and prose that explains more than the document saysEvery change to an operation is an edit nobody is reminded to make, so the comments drift from the document without a signalThe document changes more than once a quarter
oapi-codegenA Go-only client from one document, with the summary, description, and examples in the comments and output gofmt leaves aloneCommonMark pasted as written, a comment template you can override only by replacing the whole client.tmpl, and a runtime module dependencyYou generate clients in other languages from the same document
OpenAPI Generator Go clientOne generator for every language your API ships, with the Go templates extractable and editableJava-style @param lines in every method comment, no examples, files gofmt rewrites unless you hook it into post-processing, and a Java runtimeGo is your only target
sdkgen Go targetAn entity-shaped SDK across languages, with the document’s field text in its model and referenceTemplate prose in the doc comments, no operation summary or description anywhere in the output, and files gofmt rewritesThe 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.

      "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.

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.

// 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)^- ")
go run .
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.

// 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.

go run .
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 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.

cd generated/openapi-generator && go doc . DefaultAPIService.CreateMeter
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 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, so they print as code.2 The 167-character sentence wrapped at 80 columns, the printer’s default. 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.

cd generated/sdkgen && go doc ./entity Meter
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 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.

gofmt -l generated
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 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, 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 command extracts api.mustache for the Go generator, and -t points at your copy. sdkgen vendors its templates 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

go test -v . | grep -E '^(=== RUN|PASS$|FAIL$)'
=== 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.

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.

Footnotes

  1. The Go doc comment page allows that Markdown aficionados 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. ↩︎ Back to text

  2. The @param and @return lines come from OpenAPI Generator’s 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, so the tags arrive on pkg.go.dev as code, which is the one rendering neither language intended. ↩︎ Back to text

  3. The # heading syntax 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. ↩︎ Back to text

Read this page as markdown · All how-to guides

Generate the client instead of writing it#

Retries, timeouts, pagination and auth are the same problems in every client. Voxgig generates them from your OpenAPI description, in 23 languages, from one model.

Listen to Fireside, the Voxgig podcast

Since 2018, Richard Rodger has talked with developer advocates, event organizers, and the people who build developer-facing companies about the specifics of the work.