Godoc - Documenting Go Code

Learn how to document Go source code using godoc, including comment conventions, special annotations, formatting rules, and running the local godoc web server.

Documentation in Go is generated straight from the comments you leave in your source files. There is no separate markup language, no external config files, and no build step that extracts comments into a different format. The tool that makes this possible is godoc, and the conventions that drive it are now part of the Go Doc Comments specification.

This page covers what godoc is, how it turns ordinary comments into structured documentation, the special comment annotations Go supports, and the formatting rules that produce readable HTML and plain-text output.

How Godoc Generates Documentation

godoc is a program that parses Go source files, extracts the comments attached to exported identifiers, and renders them as HTML or plain text. The link between code and documentation stays automatic: when you change a comment and rebuild, the documentation updates immediately.

Godoc and go doc:

Since Go 1.12, the godoc command is only a web server. For command-line documentation lookups, use the built-in go doc command. The same doc comment conventions apply to both.

When godoc runs as a web server, it serves the docs of the standard library and any packages it finds in your module or GOPATH. You navigate from a package overview to a specific function, and one click takes you to its source code.

Doc-Comment Conventions

A doc comment is a regular // or /* */ comment placed immediately before the declaration of an exported type, function, variable, constant, or package. There must be no blank line between the comment and the declaration it describes.

The convention has a few firm rules that make the output predictable.

Placement and Blank Lines

If you leave an empty line between the comment and the func, type, or var line, godoc treats that comment as floating text and will not associate it with the identifier below. The documentation output loses the description.

// Reverse returns the string reversed. This comment IS attached.
func Reverse(s string) string { ... }
// This comment is detached because a blank line separates
// it from the declaration below.
func AnotherFunc() {}

The AnotherFunc function will appear in the package documentation with no doc string, even though the comment looks like it belongs.

No Blank Line Between Comment and Declaration:

A single empty line disconnects the comment from the symbol. Always keep the doc comment glued directly above the declaration.

The First Sentence Rule

The first sentence of a doc comment serves as a brief summary. go doc displays it in package listings and search results. godoc uses it as the heading for that item in the full HTML view.

A sentence ends with a period followed by a space. The first sentence should start with the name of the thing being documented.

// Join concatenates the elements of its first argument to create a single string.
// The separator string sep is placed between elements in the resulting string.
func Join(elems []string, sep string) string { ... }

The summary in the output will be Join concatenates the elements of its first argument to create a single string. — clean, self-contained, and scannable.

Exported vs. Unexported Identifiers

Only exported names (those starting with an uppercase letter) appear in the generated documentation. Comments on unexported functions or types are not shown by godoc, although tools like gopls may surface them inside an IDE. If a symbol is meant for public consumption, give it a doc comment.

Missing Comments on Exported Identifiers:

go vet can flag exported identifiers that lack a doc comment when you run go vet ./.... A missing comment on a public API is a genuine maintenance problem — the next developer won't know what guarantees the function makes without reading the source.

Package Comments and the doc.go Convention

Every package deserves an introductory comment that explains its purpose, shows a typical usage, or links to deeper documentation. The package comment sits immediately before package <name>, with no blank line.

A short package comment works for simple packages:

// Package set provides a generic set type with common operations.
package set

For larger packages, the initial comment can span several paragraphs. When that comment grows long enough to feel awkward at the top of a source file, Go has the doc.go convention.

When to Use doc.go

Create a file called doc.go in the package directory that contains only the package comment and the package clause. godoc reads it just like a comment on any other file.

// Package config loads application configuration from files, environment
// variables, and command-line flags. It supports JSON, YAML, and TOML formats.
//
// # Basic usage
//
//     cfg, err := config.Load("config.yaml")
//     if err != nil {
//         log.Fatal(err)
//     }
//     port := cfg.GetInt("server.port")
//
// # Environment overrides
//
// Settings loaded from a file can be overridden with environment variables
// prefixed with APP_. The key "server.port" maps to APP_SERVER_PORT.
//
// # Concurrency
//
// A loaded Config value is safe for concurrent use once created.
// Do not modify config files while a Config is in use; behavior is undefined.
package config

This keeps the package-level narrative out of the way of implementation files, and it gives readers one predictable place to look for an overview.

BUG(who) Known-Issue Comments

Comments that start with BUG(who) at the beginning of a line, at the top level of a file, are collected into a "Bugs" section at the bottom of the package documentation. This is the canonical way to surface known problems without burying them in an issue tracker.

// BUG(rsc): The Title function mis-handles Unicode punctuation marks.

The who part is typically a username or an email that someone can contact for more context. The rest of the line is the bug description. Every top-level BUG(who) comment from any .go file in the package gets listed.

BUG Comments Appear Only at the Top Level:

If you place a BUG(who) line inside a function body or attached to a type, godoc ignores it. Keep these comments at file scope.

The "Deprecated:" Comment Convention

When an exported identifier must remain for backward compatibility but should not be used in new code, add a paragraph that begins with "Deprecated:" to its doc comment.

// OldEncoder encodes values using the legacy format.
//
// Deprecated: Use NewEncoder instead. OldEncoder does not support
// the v2 wire format and will be removed in the next major release.
type OldEncoder struct { ... }

godoc renders the deprecation notice prominently, and static analysis tools like staticcheck surface these warnings when you compile code that uses the deprecated symbol.

Go Doc Comments Formatting Rules

Since Go 1.19, doc comments follow an extended set of formatting conventions that go beyond the old "indent for preformatted text" rule. These conventions are backwards-compatible; existing comments continue to work.

The current specification handles paragraphs, headings, code blocks, lists, and links without any special markup.

Paragraphs

A blank comment line (a line with just //) starts a new paragraph.

// Package user manages user accounts and authentication.
//
// It supports creating new accounts, verifying credentials, and
// managing password resets. All operations that modify state require
// an active database transaction.
package user

Headings

A line that starts with # followed by a space and text becomes a heading. Two or more # characters produce lower-level headings.

// # Configuration
//
// ## File format
//
// The config file is a plain text file with key-value pairs.

Preformatted Text (Code Blocks)

Lines that are indented by at least one additional space relative to the surrounding comment text appear as a preformatted block.

// Load reads the config from the given path.
//
// The file must be valid JSON:
//
//   {
//     "server": {
//       "port": 8080
//     }
//   }
//
// An error is returned if the file cannot be parsed.
func Load(path string) (*Config, error) { ... }

Bare URLs are automatically turned into links. You can also create a link with a display text using [text](URL).

// For more details, see https://pkg.go.dev/encoding/json.
//
// See the [JSON specification] for the complete grammar.
//
// [JSON specification]: https://tools.ietf.org/html/rfc8259

Lists

Numbered or bulleted lists are written naturally, starting each item with a digit and a period, or with a -, *, or +. The lines must be indented to match the start of the list.

// A Validator checks a value against a set of rules.
//
// Rules are applied in this order:
//
//  1. Required fields are checked first.
//  2. Length constraints are validated.
//  3. Custom validation functions run last.

Combining Conventions

All these rules compose inside a single doc comment. The following real example from a standard-library function uses several of them:

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
//
// # Format verbs
//
//   %v  the value in a default format
//   %+v a Go-syntax representation of the value
//   %#v a Go-syntax representation of the value with type information
//
// For a complete list, see the [fmt] package documentation.
func Printf(format string, a ...any) (n int, err error) { ... }

Viewing Documentation Locally

The built-in go doc command gives you fast terminal access to documentation. The godoc web server provides a full HTML browsing experience, with search and source-code links.

Use go doc to read documentation directly in your terminal. It prints the doc comment for a package or symbol.

go doc fmt.Println

Output:

package fmt // import "fmt"
func Println(a ...any) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline
    is appended. It returns the number of bytes written and any write error
    encountered.

For offline browsing of an entire package, use go doc -all:

go doc -all fmt
1

Step 1: Install the godoc Tool

go install golang.org/x/tools/cmd/godoc@latest

This downloads and builds the godoc binary into $GOPATH/bin.

2

Step 2: Start the Server from Your Module Root

cd /path/to/your/module
godoc -http=:6060

The server listens on port 6060 and indexes your module's source files live.

3

Step 3: Open the Documentation in a Browser

Visit http://localhost:6060 and use the search bar or the directory listing under the "Packages" section. Your package appears alongside the standard library.

Real-World Example: A Complete Package

Below is a small bytesize package that demonstrates the key conventions in one place.

bytesize/bytesize.go
// Package bytesize provides types and functions for working with
// human-readable byte quantities.
//
// It defines the Size type, which wraps a uint64 and provides
// methods for formatting with appropriate units (KiB, MiB, GiB).
//
// # Usage
//
//   var s Size = 2 * MiB
//   fmt.Println(s) // prints "2.0 MiB"
//
// # Rounding
//
// The String method rounds to one decimal place by default.
// Use Format with a precision argument for other rounding behavior.
package bytesize
import "fmt"
// Size represents a quantity of bytes.
type Size uint64
const (
	KiB Size = 1 << 10
	MiB      = KiB << 10
	GiB      = MiB << 10
	TiB      = GiB << 10
)
// String returns the size formatted with the largest appropriate unit
// and one decimal place.
func (s Size) String() string {
	return s.Format(1)
}
// Format returns the size as a string with the given number of decimal places
// and the largest unit that keeps the value above 1.
//
//   s := Size(1536)
//   s.Format(2) // returns "1.50 KiB"
//
// Deprecated: Use String for typical output. Format is more general than
// needed and may move to an internal package in the future.
func (s Size) Format(precision int) string {
	// implementation omitted
	return fmt.Sprintf("%.*f ?iB", precision, float64(s))
}
// ParseSize converts a string like "1.5 MiB" into a Size.
//
// The input must contain a valid floating-point number followed by
// an optional unit (B, KiB, MiB, GiB, TiB). Without a unit, bytes
// are assumed.
//
// BUG(jack): ParseSize does not handle localized decimal separators.
func ParseSize(s string) (Size, error) {
	// implementation omitted
	return 0, nil
}

The package comment uses a heading for "Usage" and "Rounding", a code block for the usage line, and plain paragraphs. The Format method carries a "Deprecated:" note. A BUG comment flags a known limitation of ParseSize.

Common Mistakes and How to Avoid Them

These are the errors that repeatedly trip up new Go developers working with doc comments. Addressing them early prevents confusing documentation output.

Blank Line Between Comment and Declaration:

A blank line between a doc comment and the declaration it describes detaches the comment. godoc will not associate the two. Always place the comment directly above the declaration.

Starting a Comment with the Wrong Name:

The doc comment of a function must begin with the function's name. If you write "This function does X" instead of "FuncName does X," automated summaries and command-line output become vague. The go vet tool does not catch this; you must check manually.

Forgetting to Use doc.go for Long Introductions:

A 200-word package comment at the top of main.go pushes the actual code far down the file. Use a doc.go file for package-level narratives. This keeps both the overview and the implementation easy to scan.

Summary

The Go documentation system is built on a simple idea: comments that are already good enough to read in the source code are also good enough to generate documentation. No separate markup files, no build plugins. The godoc tool and the go doc command both work directly from the comments you write next to your exported identifiers.

The main practices to carry forward:

  • Keep doc comments glued directly above their declarations.
  • Start with the name of the thing being documented and finish the first sentence with a period.
  • Use doc.go when a package introduction outgrows a single source file.
  • Mark known bugs with BUG(who) and deprecated symbols with "Deprecated:".
  • Format according to the Go Doc Comments spec — headings, preformatted blocks, lists, and links all work without special syntax.