AlgoMaster Logo

Documentation

Low Priority26 min readUpdated June 6, 2026

In Go, documentation isn't a separate artifact you maintain on a wiki. It lives next to the code as plain comments, and the toolchain renders it into the API reference at pkg.go.dev automatically. Write the comments well and your package gets a complete documentation site. Skip them and your package looks abandoned to anyone browsing pkg.go.dev. This chapter covers the conventions, the rendering rules, the link syntax, runnable example functions, and the tools you'll use to read and publish docs.

Why Doc Comments Are the Public API Contract

A Go package's documentation is the comment block immediately above each exported identifier. There's no separate "docs file" or template. When someone visits pkg.go.dev/example.com/shop/cart, they're reading the comments you wrote in cart.go. The same comments show up in go doc cart.Cart on the terminal, in IDE hover tooltips, and in any third-party tool that consumes Go's documentation format.

That single source has consequences. Treat doc comments like part of your API. Changing a function's behavior means updating the comment. Renaming a parameter means updating the comment. Adding a new error return means updating the comment. The community expects this because the comment is what they read before deciding whether to import your package.

Here's the bare minimum for a cart package:

First, the package itself gets a comment block. Second, every exported identifier (Cart, New, AddItem) gets a comment. Third, each comment starts with the identifier's name. That last rule is what makes the docs read like prose: "Cart represents..." flows naturally into the doc page, while "Represents a customer's cart..." reads like a stub.

Skipping doc comments has no runtime cost, but it has a real adoption cost. Packages without docs rank poorly on pkg.go.dev's search and look unmaintained to anyone evaluating dependencies.

The Naming Convention That Doc Comments Reinforce

The rule "start the comment with the identifier name" exists because of how Go's tooling renders summaries. The first sentence (up to the first period followed by a space) becomes the synopsis line in pkg.go.dev's index, in go doc's short output, and in IDE quick-info popups. If your comment doesn't start with the name, the synopsis reads awkwardly out of context.

Effective Go (the official style guide at go.dev/doc/effective_go) reinforces this rule and it's checked by golint and revive. Once you've written a few packages this way, the form becomes automatic.

The convention extends to every exported identifier, not just functions. Types, methods, variables, constants, and even error sentinels follow it:

Each comment starts with the identifier. Each first sentence is something pkg.go.dev can show on its own, without surrounding context. This is the whole rule.

Package Docs and the doc.go File

Package-level documentation is the comment block immediately above the package clause. For a small package, you put it above the package line in one of your source files (often the most central one). For a larger package, the convention is to put it in a dedicated doc.go file that contains nothing else.

That whole block becomes the landing page on pkg.go.dev for example.com/shop/payment. The first sentence ("Package payment processes credit card charges...") shows in search results and in any index that links to the package. Subsequent paragraphs render as the full overview.

doc.go is a convention, not a requirement. The compiler doesn't care which file holds the package comment, as long as exactly one file has one. Putting it in doc.go keeps it out of your way when you're editing real code and makes it easy to find when you're updating the docs. Many standard library packages do this: see net/http, encoding/json, database/sql.

A typical doc.go:

The # Configuration and # Example lines aren't decorative. They're headings, and Go's renderer treats them specially. The next section covers how that works.

How Go Renders Doc Comments

Before Go 1.19, doc comments were rendered as plain text with a few special rules (blank lines became paragraph breaks, indented blocks became code blocks). Go 1.19 added a small markdown-like syntax for headings, links, and lists. The change was deliberate: the team wanted richer docs without inventing a new markup language or pulling in a full markdown parser.

The rules are intentionally narrow. Anything not on this list renders as plain text.

ConstructSyntaxRenders as
ParagraphBlank line between blocksParagraph break
HeadingLine starting with # then space<h3> heading
Bullet listLines starting with - or * <ul> list
Numbered listLines starting with 1. etc.<ol> list
Code blockIndented by tab or spaces<pre> block
Inline codeBackticks aren't specialPlain text
Doc link[Name] or [pkg.Name]Hyperlink

Here's a comment that uses several at once:

pkg.go.dev renders that with a real heading for "Failure Modes", a bulleted list of error types, a numbered list of failure categories, and clickable links to ErrNetworkTimeout, ErrProviderUnavailable, ErrRateLimited, and Processor.Refund. None of that requires a separate documentation generator. It's all in the comment.

The indentation rules for code blocks deserve a mention. A line indented by a tab or by spaces beyond the comment's normal column becomes a code block. That's how you embed examples inside doc comments:

The indented lines render as a monospaced code block. Don't wrap them in backticks or markdown fences. Go's comment format doesn't recognize either.

Doc Links: [pkg.Type], [Func], [Type.Method]

The square-bracket link syntax is how you reference other identifiers from inside a doc comment. The renderer turns each link into a hyperlink on pkg.go.dev and into a clickable reference in IDE tooltips.

TargetSyntaxExample
Same-package function or type[Name][Cart], [New]
Method on a same-package type[Type.Method][Cart.AddItem]
Field on a same-package type[Type.Field][Cart.Total]
Other-package identifier[pkg.Name][fmt.Stringer], [errors.Is]
Other-package method[pkg.Type.Method][sql.DB.Query]
Full URL[label] then // [label]: urlSee below

Here's a real example weaving several of these together:

Each [Name] becomes a link on pkg.go.dev that jumps to the relevant identifier's documentation. [errors.Is] links to pkg.go.dev/errors#Is. [order.Order] links to your own order package. [Cart.Validate] links to the Validate method on the same Cart type.

For external URLs that aren't Go identifiers, use the labeled-link form:

The [label]: url line at the bottom of the comment defines where the link points. The label can be any text; you can have several of them in one comment. pkg.go.dev renders the label as the clickable text and uses the URL as the destination.

Doc links don't slow anything down at runtime or build time. The only "cost" is that broken links (a method that was renamed, a package that was deleted) show up as plain text instead of hyperlinks. Run go vet and check pkg.go.dev after refactors.

Example Functions

Example functions are testable, runnable documentation. They look like tests, live in _test.go files, and go test executes them with the same machinery, but their primary purpose is to appear on pkg.go.dev as runnable code snippets users can click to expand.

The naming convention encodes what the example documents:

Example function nameDocuments
ExampleThe whole package
ExampleCartThe Cart type or Cart function
ExampleCart_AddItemThe AddItem method on Cart
ExampleCart_AddItem_negativeAn extra variant of Cart.AddItem (the suffix is the variant name)
ExampleNewThe New function

The underscore between type and method is required. The optional suffix after a second underscore lets you have multiple examples for the same identifier, each highlighting a different scenario.

A package-level example:

That's a complete program inside a function. go test compiles it, runs it, captures its standard output, and compares the output against the // Output: comment at the bottom. If the output doesn't match, the example fails like any other test.

The // Output: comment is what makes the example a test. Without it, go test still compiles the function (catching syntax errors and broken imports) but doesn't run it. With it, the comparison is exact, including whitespace and newlines.

A method-specific example:

On pkg.go.dev, this snippet appears collapsed under the AddItem method, with a "Run" button that executes it in the playground. Readers see exactly how to call the method, what the output looks like, and they can edit and re-run it without leaving the docs page.

When the output order isn't deterministic (ranging over a map, for example), use // Unordered output: instead. The runner sorts both the actual output and the expected output before comparing, so order-independent outputs still pass.

Without // Unordered output:, this example would fail intermittently because map iteration order is randomized. With it, the runner compares the sorted lines and accepts either order.

How godoc Pulls Everything Together

The full pipeline from source code to rendered documentation has a few moving parts. Knowing the pieces helps you predict what pkg.go.dev will show.

The go/doc package in the standard library is what reads your source files, extracts comments above exported identifiers, and produces a structured representation. Every documentation tool, go doc on the terminal, pkgsite for self-hosting, pkg.go.dev for the public site, builds on that same parser. There's no separate doc compiler.

When you publish a module, pkg.go.dev pulls the source from the proxy (proxy.golang.org), runs the doc parser, runs the examples to verify their output, renders the result, and indexes it. The whole pipeline is automatic. You don't push to pkg.go.dev. You don't register your package. You publish a version tag to your module's repository, and within a few minutes the docs appear.

The go doc Command

go doc is the terminal interface to the same data pkg.go.dev shows. Use it when you're offline, when you want a quick reminder of a function signature, or when you're working in a codebase without a browser handy.

The output shows the signature and the doc comment, nothing else. For more detail:

That dumps the full documentation for the entire fmt package, every exported identifier with its comment, plus all examples. It's overwhelming for fmt. For a small package like the cart package above, it's the exact view of what pkg.go.dev would show.

A few patterns that come up daily:

CommandWhat it shows
go docThe current package's docs
go doc cartThe cart package's docs
go doc cart.CartJust the Cart type and its methods
go doc cart.Cart.AddItemJust the AddItem method
go doc -all cartEverything, including unexported names if -u is added
go doc -src cart.CartThe source code for the Cart declaration

go doc works offline and works on packages you haven't published. If you're inside a module, go doc./internal/payment shows your local payment package's docs even though nothing has been pushed anywhere.

go doc -all for a large package like net/http prints thousands of lines. Pipe to a pager (go doc -all net/http | less) or scope to a specific identifier (go doc net/http.Client) when you only need part of it.

pkgsite for Self-Hosted Docs

pkgsite is the program that powers pkg.go.dev itself. The Go team open-sourced it, so you can run the same site against your private modules:

That starts a local web server on port 8080 that serves docs for every module in your local Go cache. Browse to http://localhost:8080/example.com/shop/cart and you get the same rendering pkg.go.dev would show, including examples, links, and search.

This is what most companies use for internal documentation. Private modules never reach the public proxy, so they never appear on pkg.go.dev. A self-hosted pkgsite solves that without requiring any code changes: write the same doc comments, get the same rendering.

There's also an older tool called godoc (no site suffix) that was the original self-hosted documentation server. It still works, but pkgsite is the modern replacement and matches what the public site does. New projects should use pkgsite.

Versioning and the pkg.go.dev Badge

pkg.go.dev shows docs for every published version of your module. The version selector at the top of the page lets readers switch between v1.0.0, v1.1.0, v2.0.0, and so on. Each version pulls its docs from that version's source, which means docs for older releases stay correct even as you change the comments in newer code.

Publishing a version is one command:

Within a few minutes (sometimes seconds), pkg.go.dev/example.com/shop/cart@v1.2.0 appears with the docs at that tag. The version without @version always points to the latest non-prerelease, which is what most readers see by default.

The pkg.go.dev badge is the small image you embed in your README:

That renders as a clickable badge linking to your package's documentation page. Most Go libraries display it near the top of their README so visitors can jump to the API reference without scrolling through prose.

Search ranking on pkg.go.dev is influenced by a handful of signals: import count (how many other modules import yours), license clarity (an explicit LICENSE file helps), documentation completeness (more comments and examples rank higher), and recency (active modules outrank dormant ones). You can't game the ranking, but writing good docs and shipping releases regularly is what the algorithm rewards.

BUG and Deprecated Markers

Two special comment patterns get rendered specially on pkg.go.dev.

A // BUG(author): comment at package scope lists known bugs in a dedicated "Bugs" section at the bottom of the package's docs page:

This appears under "Bugs" with the author label, the message, and a link to the file. The pattern comes from the standard library, where is it in packages like time (// BUG(rsc):...).

The Deprecated: marker is a paragraph inside a regular doc comment that signals "don't use this, use the alternative":

pkg.go.dev renders deprecated identifiers with a strikethrough and a warning banner. IDEs like GoLand and VS Code (with the Go extension) show a strikethrough in code that calls deprecated functions. The Deprecated: keyword must appear at the start of a paragraph, with a colon after it. That exact form is what the tooling recognizes.

The Refund page on pkg.go.dev now shows a clear "Deprecated" banner with the explanation and a link to the replacement. Callers know to migrate, and IDE warnings nudge them toward the new API.

README.md Alongside godoc

A README.md at the module root and your doc comments serve different purposes. They overlap a little, but each has a job that the other can't do.

Lives inPurposeAudience
README.mdWhy the project exists, install instructions, examples for tooling and CI, badges, contributing guideSomeone evaluating the project
Doc commentsThe exact behavior of every exported identifierSomeone using the package

Both render on pkg.go.dev. The README appears at the top of the package page. The doc comments appear below it, organized by identifier. A good split puts the elevator pitch and quick-start in the README and the API reference in the comments. Don't repeat the comments inside the README; readers will see them on the same page.

A minimal README structure that complements good doc comments:

The README is a few hundred words at most. The full API lives in the comments. Readers who land on pkg.go.dev see the README first, get oriented, and then examine the API reference below it.

A Complete E-Commerce Example

Here's the full picture: a payment package with package docs, type docs, method docs, doc links, and a runnable example. This is what a well-documented Go package looks like end to end.

payment/doc.go:

payment/processor.go:

payment/example_test.go:

Run go test./payment and the example executes; its output must match exactly. Run go doc payment.Processor.Charge and the comment appears on the terminal. Push a tag and pkg.go.dev/example.com/shop/payment shows the full rendered docs with clickable links between Processor, Charge, Refund, ErrDeclined, and New.

Nothing in those three files is documentation tooling. It's all just Go code and comments. That's the point of how Go handles docs: there's no separate system to maintain.

Common Doc Comment Mistakes

A handful of mistakes show up over and over.

Comment doesn't start with the identifier name.

The first form makes the synopsis on pkg.go.dev read "Adds an item to the cart." with no context. The second form reads "AddItem adds qty units...", which is self-contained.

Comment is separated from the declaration by a blank line.

The doc parser walks backward from each declaration looking for a comment block. A blank line ends the block. With the blank line, the comment is orphaned and doesn't appear on the type's doc page.

Comment is on the wrong side of the declaration.

Trailing comments are valid Go but they don't become doc comments. Only the comment block directly above the declaration counts.

Example function has no `// Output:` line.

Without // Output:, go test compiles the example (catching broken imports) but doesn't run it. The example still appears on pkg.go.dev, but readers don't get the "Run" button, and you don't get a regression test if the function's behavior changes.

Quiz

Documentation Quiz

9 quizzes