Documentation and Doc Comments
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Go 1.27 (go1.27.2) and was checked on 2026-10-18. In Go, documentation is not a separate
file format or a markup language bolted onto the source: it is the comment written directly above a declaration. The
same text feeds go doc in the terminal, the editor hover from gopls and the web pages at pkg.go.dev, so there is one
place to write it and one syntax to learn. This page covers that syntax, the commands that read it and the executable
examples that complete it. It is the last page of the tooling group, after
Formatting, vet, fix and Static Analysis, which
explains the gofmt that also reformats doc comments, and it builds on
Testing, which explains how example functions are run. Every example
documents the shelf library-catalogue module used throughout this section.
What a doc comment is
A doc comment is a comment that appears immediately before a top-level package, const, func, type or var
declaration, with no blank line between the comment and the declaration. Comments on exported struct fields,
interface methods and the specs inside a grouped const/var block are also treated as documentation of that item.
A comment separated from the declaration by a blank line is an ordinary comment and is invisible to the tools. Only
exported (capitalized) names appear in the documentation by default; whether a name is exported is decided by the
rules on Program Structure and Lexical Elements
and declarations are scoped as in the spec’s
Declarations and scope.
The convention, from Effective Go: Commentary, is that the comment is a
complete sentence that starts with the name of the thing it describes (ParseISBN validates s and ...). The first
sentence is the summary: go doc -short, package listings on pkg.go.dev and editor completion show it alone, so
it has to stand on its own.
A package comment starts Package name ... and lives in one file (conventionally doc.go); a command’s starts
Command name ... (or the program name) and holds the usage; a type’s comment says what an instance represents and
whether it is safe for concurrent use; a function’s says what it returns, which errors it can return and how arguments
are interpreted; and constants and variables take a comment on the declaration or on the whole group.
The shelf package documented
The package comment of catalog lives in its own file, doc.go. It uses a heading, a list, a code block, doc links
and a plain URL, all described in the sections below. If several files of a package carry a package comment, the tools
concatenate them, which is why a single doc.go is the usual choice.
// Package catalog models a library catalogue: books, their authors and the
// loans made against them.
//
// # Overview
//
// A [Store] persists [Book] values and is safe for concurrent use. Create
// one with [NewMemStore]; parse identifiers with [ParseISBN].
//
// # Lending
//
// A book moves through the states listed in [LoanState]:
//
// - [Available]: on the shelf
// - [OnLoan]: lent to a member
// - [Overdue]: past its due date
//
// Errors are reported with the sentinel values [ErrNotFound] and
// [ErrInvalidISBN]; test for them with [errors.Is].
//
// # Example
//
// Looking a book up by ISBN:
//
// s := catalog.NewMemStore()
// b, err := s.Get(isbn)
// if errors.Is(err, catalog.ErrNotFound) {
// // not catalogued yet
// }
//
// See https://example.com/shelf for the project home page.
package catalog
Source: Go Doc Comments: Packages
Doc comment syntax
The syntax is deliberately tiny and is described in Go Doc Comments. There are no tags, no
@param and no HTML. Introduced in Go 1.19, links, lists and headings replaced the older convention of recognizing
headings and code by heuristics (Go 1.19 release notes).
| Element | How to write it |
|---|---|
Paragraph |
Consecutive lines; a blank comment line ( |
Heading |
A line starting with `# ` and followed by a blank line, on its own paragraph. Only one level exists. |
Code block |
A span of lines indented (tab recommended) relative to the text. Rendered preformatted, not highlighted. |
List |
Indented lines starting with |
Doc link |
|
Link |
|
Directive |
|
Note |
|
gofmt rewrites doc comments
Because the rendering rules are precise, gofmt reformats doc comments into their canonical form: it adds the # to
a heading, normalizes list markers and indentation, indents code blocks with a tab and puts blank comment lines around
them. Run on a deliberately untidy package comment (lines starting with - are what it removes, + what it writes; real output, and gofmt -w applies them):
$ gofmt -d messy.go
diff messy.go.orig messy.go
--- messy.go.orig
+++ messy.go
@@ -1,12 +1,13 @@
// Package messy shows how gofmt rewrites doc comments.
//
-// Overview
+// # Overview
//
// Lists with assorted markers:
//
-// * first item
-// + second item
+// - first item
+// - second item
//
// Code is a block indented by spaces:
-// x := 1
+//
+// x := 1
package messy
Source: Go Doc Comments: Syntax; cmd/gofmt
Two details matter. The one-line heading written the old way (a short capitalized line without punctuation) was
promoted to # Overview automatically. And a list item or code block written flush with the margin is not
recognized: * text at column zero stays an ordinary paragraph, so lists and code have to be indented first.
Seeing what the parser sees
The standard library exposes the exact parser and printers that go doc and pkgsite use as go/doc/comment. This
program parses a comment, resolves doc links with a fake LookupPackage and LookupSym and prints the HTML
rendering; it is the quickest way to check how a tricky comment will render.
package main
import (
"fmt"
"go/doc/comment"
"os"
)
const src = `Lists and links.
# Overview
Lists:
- bullet one
- bullet two
Then:
1. numbered one
Not a list:
* flush-left marker
See [Book], [catalog.Store], [*bytes.Buffer] and [Go site].
[Go site]: https://go.dev
`
func main() {
var p comment.Parser
p.LookupPackage = func(name string) (string, bool) {
return "example.com/shelf/" + name, name == "catalog"
}
p.LookupSym = func(recv, name string) bool { return recv == "" && name == "Book" }
doc := p.Parse(src)
var pr comment.Printer
pr.DocLinkURL = func(l *comment.DocLink) string {
return "https://pkg.go.dev/" + l.ImportPath + "#" + l.Name
}
fmt.Println("--- HTML")
os.Stdout.Write(pr.HTML(doc))
}
--- HTML
<p>Lists and links.
<h3 id="hdr-Overview">Overview</h3>
<p>Lists:
<ul>
<li>bullet one
<li>bullet two
</ul>
<p>Then:
<ol>
<li>numbered one
</ol>
<p>Not a list:
<p>* flush-left marker
<p>See <a href="https://pkg.go.dev/#Book">Book</a>, <a href="https://pkg.go.dev/example.com/shelf/catalog#Store">catalog.Store</a>, <a href="https://pkg.go.dev/bytes#Buffer">*bytes.Buffer</a> and <a href="https://go.dev">Go site</a>.
Source: go/doc/comment
Reading the output: [Book] became a link because LookupSym said the package has a Book; [catalog.Store] and
[*bytes.Buffer] were resolved to import paths; the heading became an <h3> with an anchor; and [Go site] used the link definition. The flush-left star stayed text, as above.
Doc links
A doc link [Name] names a declaration in the current package; [pkg.Name] names one in an imported package,
written with the name the file imports it under. A leading star ([*bytes.Buffer]) is allowed and is ignored for
linking. Links to methods and fields use [Type.Method] and [Type.Field] (the shelf package uses [Store.Get]).
A bracketed word that does not resolve is simply printed in brackets, which is also how a slice index in prose
survives. A link text needs a definition line; the definitions may be anywhere in the comment but are conventionally
the last paragraph, as in the earlier example.
Lists and code blocks
Lists are rendered from indented blocks. A list ends at the first line that is not indented, so a numbered list placed directly after a bullet list becomes part of the last bullet instead of starting a new list. Separate the two with a paragraph, as the program above does. Code blocks are for display: they are not highlighted, not compiled and not checked, which is one reason executable examples (below) are preferred for anything that must stay correct.
Notes and directives
A marker such as BUG(alberto): text is collected by go doc -all (and by pkgsite) under a separate heading. A
//go: directive at the end of a doc comment is not part of the rendered text. Both are real behaviours of go1.27.2:
// Package notes shows markers and directives in comments.
package notes
// BUG(alberto): Renew ignores the member's loan limit.
// Renew extends a loan by one week.
//
// This line is documentation.
//
//go:noinline
func Renew() {}
package notes // import "example.com/notes"
Package notes shows markers and directives in comments.
FUNCTIONS
func Renew()
Renew extends a loan by one week.
This line is documentation.
BUG: Renew ignores the member's loan limit.
Source: Go Doc Comments: Directives; go/doc: Note
Deprecated and common mistakes
A paragraph that begins with Deprecated: marks the declaration (or the whole package, when it is in the package
comment) as deprecated. The paragraph should say what to use instead. Tools notice it: go doc prints it, pkg.go.dev
marks deprecated identifiers, gopls and staticcheck report uses of deprecated names, and go vet does not.
The paragraph must start the paragraph and be separated by a blank comment line from the text above it.
// Package detached shows comments that are not doc comments.
package detached
// Renew extends a loan.
func Renew() {}
//Reserve holds a book.
//
//Deprecated: use Return.
func Reserve() {}
// Close ends the session.
//
// Deprecated: Use Return instead.
func Close() {}
$ go doc -all .
package detached // import "example.com/detached"
Package detached shows comments that are not doc comments.
FUNCTIONS
func Close()
Close ends the session.
Deprecated: Use Return instead.
func Renew()
func Reserve()
Reserve holds a book.
Deprecated: use Return.
This listing demonstrates several rules. Close is correctly deprecated. Renew is detached by a blank line, so
go doc prints no text for it, and no tool complains. Reserve was written without a space after //, which
gofmt repairs on save; go doc still accepts it and, in this build, a lower-case use Return after Deprecated:
too. A Deprecated: in the middle of a sentence is plain text and marks nothing. A go.mod can deprecate a whole module the same way, with a // Deprecated:
paragraph above the module line (Go modules reference: module directive);
that is covered on Workspaces, Versioning and Publishing.
Other mistakes to avoid: starting the comment with something other than the name (the web page still works, but the
sentence reads badly); documenting every private helper at length while leaving the exported API bare; putting usage
examples in comments instead of Example functions; and using HTML or Markdown, which are shown literally except for
the constructs in the table. Packages are expected to document each exported name, but nothing enforces it.
Commands
For package main the comment describes the program, not an API. Conventionally it starts Command name ... and
contains the usage as an indented code block. Plain go doc ./cmd/shelf prints only that comment; the exported
identifiers of a command are hidden unless -cmd is given, because they are not importable. With -cmd the
package clause and func Version() string appear as well:
// Command shelf searches, lends and returns books.
//
// Usage:
//
// shelf [-store file] command [arguments]
//
// The commands are:
//
// search find books by title
// lend lend a book to a member
// return return a lent book
//
// Use "shelf help command" for more information about a command.
package main
import "fmt"
// Exported symbols of a command are hidden by go doc unless -cmd is given.
func Version() string { return "1.0" }
func main() { fmt.Println("shelf", Version()) }
$ go doc -cmd ./cmd/shelf | tail -n 3
package main // import "example.com/shelf/cmd/shelf"
Command shelf searches, lends and returns books.
Usage:
shelf [-store file] command [arguments]
The commands are:
search find books by title
lend lend a book to a member
return return a lent book
Use "shelf help command" for more information about a command.
func Version() string
Source: Go Doc Comments: Commands
The go doc command
go doc prints the documentation of a package, a symbol, a method or a struct field, then a one-line summary of the
items beneath it. With no argument it documents the package in the current directory. Arguments are matched loosely:
lower-case letters match either case, a package may be given by a suffix of its path (json for encoding/json),
and the first match in a breadth-first search wins (standard library first). The output of go help doc for go1.27.2
lists these flags:
| Flag | Effect |
|---|---|
|
Show all the documentation for the package (every exported item with its full comment). |
|
Respect case when matching symbols. |
|
Treat a command like a regular package and show its exported symbols. |
|
Include executable examples (new in Go 1.27). |
|
Serve HTML docs over HTTP (new in Go 1.25). |
|
One-line representation per symbol; cannot be combined with |
|
Show the full source of the declaration, including the function body. |
|
Show unexported symbols, methods and fields too. |
Go 1.27 also lets the first argument carry a module version (go doc example.com/pkg@v1.2.3), so a package can be read
without adding it to go.mod. Source: cmd/go: Show documentation for package or symbol
and Go 1.27 release notes: go doc
Package, symbol, method and field
Here are the everyday forms against the shelf module: a function (package and symbol as two arguments) and a
deprecated function. go doc ./catalog alone prints the package comment and a list of every exported item, go doc
./catalog.LoanState the type, its constants and methods, and go doc ./catalog.Book.Title one struct field, with
the other fields elided.
$ go doc ./catalog ParseISBN
package catalog // import "example.com/shelf/catalog"
func ParseISBN(s string) (ISBN, error)
ParseISBN validates s and returns it as an ISBN. Hyphens are ignored.
It returns an error wrapping ErrInvalidISBN if s does not contain 13 digits
or the check digit is wrong.
$ go doc ./catalog.Find
package catalog // import "example.com/shelf/catalog"
func Find(m *MemStore, title string) (Book, bool)
Find looks a book up by title.
Deprecated: Use Store.Get with an ISBN; titles are not unique.
Source: cmd/doc
The doc links in the comment ([ISBN], [ErrInvalidISBN]) are printed as plain names in the terminal. A symbol
listing shows grouped constants as const Available LoanState = iota ..., without their comments; -all shows them.
-short, -all, -src and -u
$ go doc -short ./catalog
var ErrInvalidISBN = errors.New("catalog: invalid ISBN")
var ErrNotFound = errors.New("catalog: book not found")
type Author struct{ ... }
type Book struct{ ... }
func Find(m *MemStore, title string) (Book, bool)
type ISBN string
func ParseISBN(s string) (ISBN, error)
type LoanState int
const Available LoanState = iota ...
type MemStore struct{ ... }
func NewMemStore() *MemStore
type Store interface{ ... }
$ go doc -src ./catalog.NewMemStore
package catalog // import "example.com/shelf/catalog"
// NewMemStore returns an empty [MemStore].
func NewMemStore() *MemStore { return &MemStore{books: map[ISBN]Book{}} }
-src prints the comment and the real source, so it doubles as a quick way to read a standard-library implementation
(go doc -src strings.Cut). -all adds every exported item’s full comment (with constants and their comments);
-u reveals the unexported fields of MemStore, here the mutex and the map, which are otherwise filtered out.
-ex, examples and pkg@version
-ex (Go 1.27) lists the executable examples of a package or symbol. Passing an example’s own name
(go doc ./catalog.ExampleMemStore_Get) prints its source as a runnable main program followed by its Output:
line; in go1.27.2 it printed that program twice in this environment, which is best treated as a quirk of this build and not
as documented behaviour. The listing form appends the example names under the symbol:
$ go doc -ex ./catalog.ParseISBN
package catalog // import "example.com/shelf/catalog"
func ParseISBN(s string) (ISBN, error)
ParseISBN validates s and returns it as an ISBN. Hyphens are ignored.
It returns an error wrapping ErrInvalidISBN if s does not contain 13 digits
or the check digit is wrong.
func ExampleParseISBN()
func ExampleParseISBN_invalid()
Source: Go 1.27 release notes: go doc
The two other newer features need the network, because they fetch code from outside the local module. With
GOPROXY=off (as used for every example on this page) they fail with these real messages; they were not run
successfully, so what follows is what the release notes state, not something verified here.
$ go doc golang.org/x/tools/go/analysis@v0.52.0
doc: golang.org/x/tools/go/analysis@v0.52.0: module lookup disabled by GOPROXY=off
$ go doc strings@go1.27.2 Cut
doc: strings@go1.27.2: argument must not be a package in the standard library
$ go doc -http ./catalog
doc: go: golang.org/x/pkgsite/cmd/internal/doc@v0.0.0-20260722200430-7c7eca789259: module lookup disabled by GOPROXY=off
-
pkg@versionlooks the module up (here the lookup was refused byGOPROXY=off) and documents that version. It is rejected for standard-library packages, as the second message shows. -
-http(Go 1.25) starts a local documentation server showing the page for the argument, using pkgsite code that the command downloads the first time, which is why it needs the proxy. See Go 1.25 release notes for the announcement.
pkg.go.dev, pkgsite and the old godoc
https://pkg.go.dev/ renders the same comments for every public module, with versions, licenses, imports and examples.
Publishing a tagged version (see
Workspaces, Versioning and Publishing) is
enough; the site fetches it from the module proxy, and a request to the page of a version triggers the fetch
(pkg.go.dev: About). The software behind the site is
pkgsite, which can also be run locally against a private module
tree; go doc -http uses it. The older godoc web server (golang.org/x/tools/cmd/godoc) was deprecated, with the reason that it cannot select the version of a package it displays, and v0.36.0 is the last golang.org/x/tools release that contains it; use go doc in the terminal, gopls hover in the editor and pkgsite for HTML.
pkgsite adds, on top of the comment text: anchors from headings, an index of the declarations, deprecation notices, constructors grouped under their types, and each example shown next to what it documents. All of it is derived from the comment and example files, so no extra configuration is needed.
Examples as documentation
An example function is a function in a _test.go file whose name starts with Example. It is compiled with the
tests, and if it ends with an // Output: comment, go test runs it and compares what it prints with that comment.
The tools also show it in the documentation, attached to the item named in the function name: ExampleParseISBN to the
function, ExampleMemStore_Get to the method Get of MemStore, and a lower-case suffix (ExampleParseISBN_invalid)
for an additional example of the same item. Example alone documents the package, and Example_suffix a variation of
it. The examples live in the external test package (catalog_test) so that they read like code that imports the
package, which is what a user would write.
package catalog_test
import (
"errors"
"fmt"
"example.com/shelf/catalog"
)
func ExampleParseISBN() {
isbn, err := catalog.ParseISBN("978-0-13-468599-1")
fmt.Println(isbn, err)
// Output: 9780134685991 <nil>
}
func ExampleParseISBN_invalid() {
_, err := catalog.ParseISBN("123")
fmt.Println(errors.Is(err, catalog.ErrInvalidISBN))
// Output: true
}
func ExampleMemStore_Get() {
s := catalog.NewMemStore()
_, err := s.Get("9780134685991")
fmt.Println(err)
// Output: catalog: book not found
}
$ go test -v -run Example ./catalog
=== RUN ExampleParseISBN
--- PASS: ExampleParseISBN (0.00s)
=== RUN ExampleParseISBN_invalid
--- PASS: ExampleParseISBN_invalid (0.00s)
=== RUN ExampleMemStore_Get
--- PASS: ExampleMemStore_Get (0.00s)
PASS
ok example.com/shelf/catalog (cached)
An example cannot drift out of date silently: if ParseISBN changes what it returns, go test fails. An example
without an // Output: comment is only compiled, never run, which suits examples that need the network or a clock.
// Unordered output: compares lines without regard to order, which suits map iteration. The testing mechanics, -run
Example and the naming rules checked by go vet (its tests analyzer reports an example that refers to a name that
does not exist) are explained on Testing.
Writing guidance that tools cannot check
-
Say what the code promises, not how it works: concurrency safety, whether a returned slice may be modified, which sentinel errors can be returned (
ErrNotFound) and the meaning of the zero value. -
Keep lines about 80 columns wide, as the standard library does; gofmt does not rewrap prose.
Where the books are outdated
-
Bodner, chapter 9 (modules and packages), describes
go docand package comments as they were in 2021. Since then Go 1.19 added links, lists and headings (and gofmt reformatting) to the syntax, and Go 1.25 and 1.27 extendedgo docwith-http,pkg@versionand-ex. The chapter’s publishing advice still sends readers to pkg.go.dev, which remains right. -
Vivien, chapter 1, shows the
godocbinary (godoc -http=:6060) for local documentation. That program is obsolete: documentation is read withgo docor pkgsite, as above. -
Both books recommend
golintto check that comments start with the name.golintis archived and no replacement in the standard tools enforces it; community linters (staticcheck, golangci-lint, revive) can, and gopls shows deprecations. See Formatting, vet, fix and Static Analysis. -
Neither book covers
Deprecated:paragraphs, doc links,go/doc/commentor testable examples as documentation.