go-openapi codescan

github.com/go-openapi/codescan is a Go source code scanner that produces Swagger 2.0 (OpenAPI 2.0) specifications.

It reads specially formatted comments (annotations) in Go source files and extracts API metadata β€” routes, parameters, responses, schemas and more β€” to build a complete spec.Swagger document. It supports Go modules (since go1.11).

The scanner works entirely at the AST / go/types level: it never compiles or executes the code it scans. It only reads the source and its annotation comments.

Status

Fork me Stable API. Actively maintained.

The only exposed API is Run() and Options.

Getting started

go get github.com/go-openapi/codescan

Point the scanner at one or more packages and get back a *spec.Swagger:

import "github.com/go-openapi/codescan"

swaggerSpec, err := codescan.Run(&codescan.Options{
    Packages: []string{"./..."},
})

Where to go next

  • About

    What codescan is, why scan source to a spec, and how it relates to go-swagger.

    β†’ about

  • Getting started

    Install the scanner, annotate a package, and produce your first spec.

    β†’ getting-started

  • Tutorials

    Learn by spec concept β€” model definitions, routes, validations β€” annotated Go next to the spec it produces.

    β†’ tutorials

  • Shaping the output

    How-to guides for the rendering knobs: $ref vs inline, aliases, nullable pointers, extensions.

    β†’ shaping-the-output

  • Annotation index

    Every annotation at a glance, linked to its worked example and its full reference.

    β†’ annotation-index

  • Reference (maintainers)

    The complete compendium β€” annotations, keywords, sub-languages and the formal grammar.

    β†’ maintainers

  • Project

    Repo overview, license and links to the shared go-openapi guides.

    β†’ project

Licensing

SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers

This library ships under the Apache-2.0 license.

Contributing

Issues and pull requests welcome.

See the shared go-openapi contributing guidelines and the per-repo notes in project/.


  • Repo-level information for github.com/go-openapi/codescan. Cross-org contributing and maintainer guides live in the shared go-openapi doc-site.
  • What codescan is, why you would scan source to produce a spec, and how it relates to the go-swagger toolkit.
  • Install codescan and choose how to drive it. Today the scanner is consumed as a Go library; more usage modes will be added here as siblings.
  • Learn codescan by spec concept β€” model definitions, routes and operations, validations, examples, and document metadata β€” each shown as annotated Go next to the Swagger it produces.
  • How-to guides for the knobs that change how the same Go source renders into the spec β€” $ref vs inline, alias handling, descriptions beside a $ref, nullable pointers, vendor extensions, and spec overlays.
  • Every swagger:* annotation at a glance β€” what it produces and where it attaches β€” linked to both its worked example and its full reference.
  • The complete, normative reference for the codescan annotation language β€” every annotation, every keyword, the embedded sub-languages, and the formal grammar the parser implements.
Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of go-openapi codescan

Project

This section holds material specific to this repository:

  • README β€” repo overview and announcements
  • License β€” Apache-2.0

Cross-org documentation that applies to every go-openapi repo lives in the shared doc-site:

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Project

README

codescan

A Go source code scanner that produces Swagger 2.0 (OpenAPI 2.0) specifications from annotated Go source files.

Supports Go modules (since go1.11).

Announcements

  • 2025-04-19: large package layout reshuffle
    • the entire project is being refactored to restore a reasonable level of maintainability
    • the only exposed API is Run() and Options.

Status

API is stable.

Import this library in your project

go get github.com/go-openapi/codescan

Basic usage

import (
  "github.com/go-openapi/codescan"
)

swaggerSpec, err := codescan.Run(&codescan.Options{
  Packages: []string{"./..."},
})

See getting started for a worked example.

Change log

See https://github.com/go-openapi/codescan/releases.

Licensing

This library ships under the Apache-2.0 license.

See the license NOTICE, which recalls the licensing terms of all the pieces of software on top of which it has been built.

Other documentation

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

LICENSE

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

About

codescan is a code-first OpenAPI engine: it reads specially formatted comments (annotations) in your Go source and produces a Swagger 2.0 specification. It works entirely at the AST / go/types level β€” it never compiles or runs the code it scans.

Two ways to build an API

APIs and their documentation tend to evolve along one of two paths. The go-openapi / go-swagger toolkit supports both.

  • Design-first (contract-first) β€” you write the OpenAPI document first and treat it as the contract, then generate servers and clients from it. If this is your workflow, reach for go-swagger (swagger generate server / swagger generate client).
  • Code-first β€” you write annotated Go and scan it to produce the spec. This keeps the document in sync with the code as it changes, and lets you produce a valid specification for a service that already exists.

codescan is the engine for the code-first path.

Relationship to go-swagger

codescan began life as a single package inside go-swagger and was spun out into its own go-openapi repository. It is the scanner behind the go-swagger command:

swagger generate spec ./...

go-swagger remains the main command-line consumer of this library. This site documents the scanner library itself β€” the layer beneath swagger generate spec β€” so it sits upstream of go-swagger’s “generate spec” documentation. If you arrived here from go-swagger: the annotations are exactly the same, and you can either keep using the swagger CLI or call codescan.Run directly from your own program (see Getting started).

Why scan from source

  • One source of truth. The spec is derived from the code, so it cannot silently drift from what the service actually exposes.
  • Fast iteration. Add a field, add its annotation, regenerate β€” no separate document to keep in step by hand.
  • Document what exists. Produce a standards-compliant spec for an API server that is already deployed, so it becomes interoperable with new clients and tooling.

When document-level metadata (info, security, servers) is more naturally hand-authored, you do not have to push it into the code: scan the code for the operations and models, and overlay the result onto a hand-written base document (see Shaping the output β†’ Overlaying a spec).

A community toolkit

go-openapi and go-swagger are community-driven, open-source building blocks meant to be assembled and customized β€” there are too many ways to approach APIs to cover them all. Fork, reuse, and adapt what you find useful. See the go-swagger project’s “About” page for the wider toolkit story.

Where to go next

  • Getting started

    Install codescan and produce your first spec.

    β†’ getting-started

  • Tutorials

    Learn by spec concept, with annotated Go beside the spec it produces.

    β†’ tutorials

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Getting started

Install

go get github.com/go-openapi/codescan

codescan exposes a deliberately small surface: a single Run function and an Options struct.

func Run(opts *Options) (*spec.Swagger, error)

Ways to use codescan

  • Import codescan, annotate a package, and produce a Swagger 2.0 specification from your Go program.

Today, codescan is used as a Go library (below). Additional usage modes will appear here as siblings as the toolkit grows.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Getting started

Usage as a library

The most direct way to use codescan is to import it and call Run from your own Go program β€” a generator, a go:generate step, or a test that keeps your spec in sync with the source.

Annotate your source

Annotations are special comments following the go-swagger convention (swagger:meta, swagger:route, swagger:model, swagger:parameters, swagger:response, …).

A package-level swagger:meta block carries the top-level metadata of the spec:

// Package petstore Petstore API
//
// A tiny pet store, used to demonstrate codescan annotations: the package
// comment is a `swagger:meta` block carrying the top-level metadata of the
// generated specification (title, description, version, base path, …).
//
//	Schemes: https
//	Version: 1.0.0
//	BasePath: /v1
//
//	Consumes:
//	- application/json
//
//	Produces:
//	- application/json
//
// swagger:meta

Full source: docs/examples/petstore/doc.go

A swagger:model annotation turns a Go struct into a definition; field-level comments become validations and descriptions:

// Pet is a single pet in the store.
//
// swagger:model Pet
type Pet struct {
	// The id of the pet.
	//
	// required: true
	// minimum: 1
	ID int64 `json:"id"`

	// The name of the pet.
	//
	// required: true
	// min length: 1
	Name string `json:"name"`

	// The tags associated with this pet.
	Tags []string `json:"tags,omitempty"`
}

Full source: docs/examples/petstore/pet.go

Run the scanner

Point codescan at the package(s) to scan. Patterns are relative go list-style patterns, resolved against WorkDir:

opts := &codescan.Options{
	WorkDir:    workDir,                // module root to resolve patterns from
	Packages:   []string{"./petstore"}, // relative package pattern
	ScanModels: true,                   // also emit definitions for swagger:model types
}

doc, err := codescan.Run(opts)
if err != nil {
	return nil, err
}

Full source: docs/examples/basic/scan.go

The returned *spec.Swagger is the standard github.com/go-openapi/spec document β€” marshal it to JSON or YAML, feed it to a validator, or merge it onto an existing spec via Options.InputSpec.

Options worth knowing

FieldEffect
PackagesRelative go list patterns to scan (e.g. ./...).
WorkDirDirectory the patterns resolve against.
ScanModelsAlso emit definitions for swagger:model types.
InputSpecOverlay: merge discoveries on top of an existing spec.
BuildTags, Include/ExcludeScope control over what gets scanned.
RefAliases, TransparentAliases, DescWithRefAlias-handling knobs.
SkipExtensionsSuppress x-go-* vendor extensions.

See the godoc for the full list.

Next

  • Tutorials β€” the worked, by-concept version of the above, each with the spec it produces.
  • Annotation index β€” every annotation at a glance, linked to its example and its full reference.
  • Maintainers reference β€” the complete annotation vocabulary, keywords, and grammar.
Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Tutorials

These tutorials teach codescan by spec concept, not annotation by annotation. Each page takes one thing you want in your OpenAPI document β€” a model definition, a route, a validated field β€” and shows the Go annotation that produces it next to the resulting JSON, side by side.

Every Go snippet on these pages comes from the test-covered docs/examples module, and every JSON pane is a golden file a test regenerates β€” so the examples cannot drift from what the scanner actually emits.

Reading the panes

The example panes put the annotation in on the left and the spec concept out on the right:

Annotated Go
// Pet is a single pet in the store.
//
// swagger:model Pet
type Pet struct {
	// The id of the pet.
	//
	// required: true
	// minimum: 1
	ID int64 `json:"id"`

	// The name of the pet.
	//
	// required: true
	// min length: 1
	Name string `json:"name"`

	// The tags associated with this pet.
	Tags []string `json:"tags,omitempty"`
}

Full source: docs/examples/petstore/pet.go

Generated spec
{
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "schemes": [
    "https"
  ],
  "swagger": "2.0",
  "info": {
    "description": "A tiny pet store, used to demonstrate codescan annotations: the package\ncomment is a `swagger:meta` block carrying the top-level metadata of the\ngenerated specification (title, description, version, base path, …).",
    "title": "Petstore API",
    "version": "1.0.0"
  },
  "basePath": "/v1",
  "paths": {
    "/pets": {
      "get": {
        "tags": [
          "pets"
        ],
        "summary": "Lists all the pets in the store.",
        "operationId": "listPets",
        "responses": {
          "200": {
            "$ref": "#/responses/petsResponse"
          }
        }
      }
    }
  },
  "definitions": {
    "Pet": {
      "type": "object",
      "title": "Pet is a single pet in the store.",
      "required": [
        "id",
        "name"
      ],
      "properties": {
        "id": {
          "description": "The id of the pet.",
          "type": "integer",
          "format": "int64",
          "minimum": 1,
          "x-go-name": "ID"
        },
        "name": {
          "description": "The name of the pet.",
          "type": "string",
          "minLength": 1,
          "x-go-name": "Name"
        },
        "tags": {
          "description": "The tags associated with this pet.",
          "type": "array",
          "items": {
            "type": "string"
          },
          "x-go-name": "Tags"
        }
      },
      "x-go-package": "github.com/go-openapi/codescan/docs/examples/petstore"
    }
  },
  "responses": {
    "petsResponse": {
      "description": "petsResponse is the list of pets returned by listPets.",
      "schema": {
        "type": "array",
        "items": {
          "$ref": "#/definitions/Pet"
        }
      }
    }
  }
}

Full source: docs/examples/basic/testdata/swagger.json

The concepts

  • Turn Go types into spec definitions β€” structs, string formats, enums, allOf composition, and the per-type overrides.
  • Publish paths and operations β€” swagger:route and swagger:operation β€” with their parameters and responses.
  • Drive JSON-Schema validations from field doc comments β€” numeric ranges, length and array bounds, patterns, formats, and enums β€” and understand the reduced surface on parameters and headers.
  • Attach example values and defaults to properties β€” and understand the narrow swagger:default hint.
  • Set the top-level spec fields β€” title, version, host, basePath, schemes, consumes/produces, license and contact β€” from the package doc comment.
  • The smallest end-to-end use of codescan: annotate a package, scan it, and get back a Swagger 2.0 document.

When you want the exhaustive rule rather than an example, every page links into the Maintainers reference; the Annotation index maps every annotation to both.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Tutorials

Model definitions

A definitions entry is the most common thing you ask codescan to produce. This page walks the annotations that create and shape one, from the plain swagger:model struct to the per-type overrides. Each pane pairs the annotated Go (left) with the exact fragment the scanner emits (right) β€” both come from the test-covered docs/examples/concepts/models package.

For the exhaustive rule on any annotation below, follow its link to the Maintainers reference.

swagger:model

swagger:model publishes a Go struct as a definition. Field doc comments become property descriptions; json tags drive the property names; the Go type drives the JSON-Schema type / format.

Annotated Go
// Pet is a single pet in the store.
//
// swagger:model
type Pet struct {
	// ID is the unique identifier.
	ID int64 `json:"id"`

	// Name is the pet's display name.
	Name string `json:"name"`

	// Tags categorise the pet.
	Tags []string `json:"tags,omitempty"`
}

Full source: docs/examples/concepts/models/models.go

#/definitions/Pet
{
  "type": "object",
  "title": "Pet is a single pet in the store.",
  "properties": {
    "id": {
      "description": "ID is the unique identifier.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "ID"
    },
    "name": {
      "description": "Name is the pet's display name.",
      "type": "string",
      "x-go-name": "Name"
    },
    "tags": {
      "description": "Tags categorise the pet.",
      "type": "array",
      "items": {
        "type": "string"
      },
      "x-go-name": "Tags"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

Full source: docs/examples/concepts/models/testdata/model.json

swagger:strfmt

swagger:strfmt <name> marks a named string type as a custom format. The type does not become its own definition β€” instead, every field typed by it renders as {type: string, format: <name>}.

Annotated Go
// MAC is a hardware address rendered as a colon-separated hex string.
//
// swagger:strfmt mac
type MAC string

func (m MAC) MarshalText() ([]byte, error)  { return []byte(m), nil }
func (m *MAC) UnmarshalText(b []byte) error { *m = MAC(b); return nil }

// Device exposes a strfmt-typed field: wherever MAC appears it renders inline
// as {type: string, format: mac}.
//
// swagger:model
type Device struct {
	// Addr is the hardware address.
	Addr MAC `json:"addr"`
}

Full source: docs/examples/concepts/models/models.go

#/definitions/Device
{
  "description": "Device exposes a strfmt-typed field: wherever MAC appears it renders inline\nas {type: string, format: mac}.",
  "type": "object",
  "properties": {
    "addr": {
      "description": "Addr is the hardware address.",
      "type": "string",
      "format": "mac",
      "x-go-name": "Addr"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

Full source: docs/examples/concepts/models/testdata/strfmt.json

swagger:enum

swagger:enum <name> collects the type’s const values. When a model field references the type, the property carries the enum array and an x-go-enum-desc extension built from the per-value doc comments. (The enum type is reachable, and so emitted, only because a model field points at it.)

Annotated Go
// Priority is the urgency level on a task.
//
// swagger:enum Priority
type Priority string

const (
	// PriorityLow is for tasks that can wait.
	PriorityLow Priority = "low"
	// PriorityMedium is the default.
	PriorityMedium Priority = "medium"
	// PriorityHigh is for tasks that must run soon.
	PriorityHigh Priority = "high"
)

// Task is a unit of work carrying an enum-typed field. Referencing Priority
// from a model is what makes the enum reachable, and so emitted.
//
// swagger:model
type Task struct {
	// Priority is the task's urgency.
	Priority Priority `json:"priority"`
}

Full source: docs/examples/concepts/models/models.go

#/definitions/Task
{
  "description": "Task is a unit of work carrying an enum-typed field. Referencing Priority\nfrom a model is what makes the enum reachable, and so emitted.",
  "type": "object",
  "properties": {
    "priority": {
      "description": "Priority is the task's urgency.\nlow PriorityLow is for tasks that can wait.\nmedium PriorityMedium is the default.\nhigh PriorityHigh is for tasks that must run soon.",
      "type": "string",
      "enum": [
        "low",
        "medium",
        "high"
      ],
      "x-go-enum-desc": "low PriorityLow is for tasks that can wait.\nmedium PriorityMedium is the default.\nhigh PriorityHigh is for tasks that must run soon.",
      "x-go-name": "Priority"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

Full source: docs/examples/concepts/models/testdata/enum.json

swagger:allOf

Embedding base types under swagger:allOf composes a schema. Each embedded base becomes a $ref arm of the allOf; the struct’s own (non-embedded) fields form a final inline arm. That last arm is inline rather than a $ref because those fields are new to this type β€” they belong to no existing definition to point at. Here Dog embeds two bases (Animal, Tagged) and adds breed, producing three arms: two $refs and one inline object.

Annotated Go
// Animal is one abstract base.
//
// swagger:model
type Animal struct {
	// Kind discriminates the animal.
	Kind string `json:"kind"`
}

// Tagged is a second reusable base.
//
// swagger:model
type Tagged struct {
	// Tags label the resource.
	Tags []string `json:"tags"`
}

// Dog composes two base models plus its own fields: each embedded base becomes
// a $ref arm of the allOf, and the struct's own (non-embedded) fields β€” which
// are new and cannot be a $ref β€” form the final inline arm.
//
// swagger:model
type Dog struct {
	// swagger:allOf
	Animal

	// swagger:allOf
	Tagged

	// Breed is the dog's breed.
	Breed string `json:"breed"`
}

Full source: docs/examples/concepts/models/models.go

#/definitions/Dog
{
  "description": "Dog composes two base models plus its own fields: each embedded base becomes\na $ref arm of the allOf, and the struct's own (non-embedded) fields β€” which\nare new and cannot be a $ref β€” form the final inline arm.",
  "allOf": [
    {
      "$ref": "#/definitions/Animal"
    },
    {
      "$ref": "#/definitions/Tagged"
    },
    {
      "type": "object",
      "properties": {
        "breed": {
          "description": "Breed is the dog's breed.",
          "type": "string",
          "x-go-name": "Breed"
        }
      }
    }
  ],
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

Full source: docs/examples/concepts/models/testdata/allof.json

swagger:type

swagger:type <type> overrides the type codescan would infer. Here a [16]byte field is published as a string.

Annotated Go
// ULID is a 128-bit identifier stored as bytes but rendered as a string.
//
// swagger:type string
type ULID [16]byte

// Token carries a field whose inferred type is overridden, inline.
//
// swagger:model
type Token struct {
	// ID renders as a string despite its [16]byte Go type.
	ID ULID `json:"id"`
}

Full source: docs/examples/concepts/models/models.go

#/definitions/Token
{
  "type": "object",
  "title": "Token carries a field whose inferred type is overridden, inline.",
  "properties": {
    "id": {
      "description": "ID renders as a string despite its [16]byte Go type.",
      "type": "string",
      "x-go-name": "ID"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

Full source: docs/examples/concepts/models/testdata/type.json

The accepted values are the scalar Swagger types β€” string, integer, number, boolean, object (plus the Go builtin names codescan resolves). array and file are not accepted here; an unrecognized value leaves the field on its underlying Go type.

swagger:name

A model defined as an interface publishes one property per nullary method. By default the property name is the camelCased method name β€” so Maker() already becomes maker with no annotation. swagger:name <name> is the override for when that default is not what you want (interface methods cannot carry a json tag). Here StructType() would default to structType; the annotation publishes it as jsonClass instead.

Annotated Go
// Car is exposed as a schema via its method set. Interface methods cannot carry
// a json tag, so by default each property takes the camelCased method name;
// swagger:name overrides that where the default is not what you want.
//
// swagger:model
type Car interface {
	// Maker is the manufacturer. With no override the property is the
	// camelCased method name, "maker".
	Maker() string

	// StructType is the polymorphic class. Without the override the property
	// would be "structType"; swagger:name publishes it as "jsonClass".
	//
	// swagger:name jsonClass
	StructType() string
}

Full source: docs/examples/concepts/models/models.go

#/definitions/Car
{
  "description": "Car is exposed as a schema via its method set. Interface methods cannot carry\na json tag, so by default each property takes the camelCased method name;",
  "type": "object",
  "properties": {
    "jsonClass": {
      "description": "StructType is the polymorphic class. Without the override the property\nwould be \"structType\"; swagger:name publishes it as \"jsonClass\".",
      "type": "string",
      "x-go-name": "StructType"
    },
    "maker": {
      "description": "Maker is the manufacturer. With no override the property is the\ncamelCased method name, \"maker\".",
      "type": "string",
      "x-go-name": "Maker"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

Full source: docs/examples/concepts/models/testdata/name.json

swagger:ignore

swagger:ignore drops a declaration from the output. The scanner sees Secret, classifies it, then excludes it β€” so it never reaches the definitions (a fact the example’s TestIgnoreOmitsType asserts).

// Secret never reaches the spec.
//
// swagger:ignore
type Secret struct {
	// Token is internal.
	Token string `json:"token"`
}

Full source: docs/examples/concepts/models/models.go

What’s next

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Routes & operations

Routes and operations turn an annotation into an entry in the spec’s paths map, wired to the parameters it accepts and the responses it returns. This page covers the two operation annotations and the companion structs they reference. Each pane pairs the annotated Go (left) with the exact fragment the scanner emits (right), from the test-covered docs/examples/concepts/routes package.

For the exhaustive rule on any annotation below, follow its link to the Maintainers reference; the Parameters: / Responses: body grammars are covered in Sub-languages.

swagger:route

swagger:route <METHOD> <path> [tags] <operationID> declares a path and its operation in one annotation. The body’s responses: block ties status codes to named responses ($ref into the spec’s responses). It lives in a plain comment block β€” no Go declaration required.

Annotated Go
// swagger:route GET /pets pets listPets
//
// Lists pets in the store, optionally filtered by tag.
//
// responses:
//
//	200: petsResponse
//	default: errorResponse

Full source: docs/examples/concepts/routes/routes.go

paths[/pets]
{
  "get": {
    "tags": [
      "pets"
    ],
    "summary": "Lists pets in the store, optionally filtered by tag.",
    "operationId": "listPets",
    "parameters": [
      {
        "type": "string",
        "x-go-name": "Tag",
        "description": "Tag filters pets by tag.",
        "name": "tag",
        "in": "query"
      },
      {
        "maximum": 100,
        "minimum": 1,
        "type": "integer",
        "format": "int32",
        "x-go-name": "Limit",
        "description": "Limit caps the number of results.",
        "name": "limit",
        "in": "query"
      }
    ],
    "responses": {
      "200": {
        "$ref": "#/responses/petsResponse"
      },
      "default": {
        "$ref": "#/responses/errorResponse"
      }
    }
  }
}

Full source: docs/examples/concepts/routes/testdata/route.json

swagger:operation

swagger:operation carries the same header but spells the operation out as a YAML document after a --- fence β€” useful when you want to author the operation object directly (here a path parameter and an inline $ref response schema).

Annotated Go
// swagger:operation GET /pets/{id} pets getPet
//
// ---
// summary: Get a pet by ID.
// parameters:
//   - name: id
//     in: path
//     required: true
//     type: integer
//     format: int64
// responses:
//   '200':
//     description: the requested pet
//     schema:
//       $ref: '#/definitions/Pet'
//   default:
//     $ref: '#/responses/errorResponse'

Full source: docs/examples/concepts/routes/routes.go

paths[/pets/{id}]
{
  "get": {
    "tags": [
      "pets"
    ],
    "summary": "Get a pet by ID.",
    "operationId": "getPet",
    "parameters": [
      {
        "type": "integer",
        "format": "int64",
        "name": "id",
        "in": "path",
        "required": true
      }
    ],
    "responses": {
      "200": {
        "description": "the requested pet",
        "schema": {
          "$ref": "#/definitions/Pet"
        }
      },
      "default": {
        "$ref": "#/responses/errorResponse"
      }
    }
  }
}

Full source: docs/examples/concepts/routes/testdata/operation.json

swagger:parameters

swagger:parameters <operationID>… declares a struct whose fields become the parameters of the named operation(s). Field doc comments carry in:, the validations, and the description; the parameters attach to every operation ID listed.

Annotated Go
// ListPetsParams is the parameter set for the listPets operation. Each field
// becomes one parameter; the operation IDs after swagger:parameters name the
// operations the set applies to.
//
// swagger:parameters listPets
type ListPetsParams struct {
	// Tag filters pets by tag.
	//
	// in: query
	Tag string `json:"tag"`

	// Limit caps the number of results.
	//
	// in: query
	// minimum: 1
	// maximum: 100
	Limit int32 `json:"limit"`
}

Full source: docs/examples/concepts/routes/routes.go

parameters on listPets
[
  {
    "type": "string",
    "x-go-name": "Tag",
    "description": "Tag filters pets by tag.",
    "name": "tag",
    "in": "query"
  },
  {
    "maximum": 100,
    "minimum": 1,
    "type": "integer",
    "format": "int32",
    "x-go-name": "Limit",
    "description": "Limit caps the number of results.",
    "name": "limit",
    "in": "query"
  }
]

Full source: docs/examples/concepts/routes/testdata/parameters.json

swagger:response

swagger:response <name> declares a struct as a named entry in the spec’s top-level responses. A Body field (or in: body) becomes the response schema; routes reference it by name. Here the body is a []Pet, so the schema is an array of $refs.

Annotated Go
// PetsResponse is the list returned by listPets.
//
// swagger:response petsResponse
type PetsResponse struct {
	// in: body
	Body []Pet
}

// ErrorResponse is the default error payload.
//
// swagger:response errorResponse
type ErrorResponse struct {
	// in: body
	Body struct {
		// Message is a human-readable error message.
		Message string `json:"message"`
	}
}

Full source: docs/examples/concepts/routes/routes.go

responses[petsResponse]
{
  "description": "PetsResponse is the list returned by listPets.",
  "schema": {
    "type": "array",
    "items": {
      "$ref": "#/definitions/Pet"
    }
  }
}

Full source: docs/examples/concepts/routes/testdata/response.json

swagger:file

swagger:file on a parameter field marks it as a binary upload β€” the parameter emits as {type: file}. It belongs on a formData field of a swagger:parameters struct.

Annotated Go
// swagger:route POST /pets/{id}/photo pets uploadPetPhoto
//
// responses:
//
//	200: petsResponse

// UploadParams is the multipart upload for the uploadPetPhoto operation.
//
// swagger:parameters uploadPetPhoto
type UploadParams struct {
	// Photo is the image to upload.
	//
	// in: formData
	// swagger:file
	Photo io.ReadCloser `json:"photo"`
}

Full source: docs/examples/concepts/routes/routes.go

parameters on uploadPetPhoto
[
  {
    "type": "file",
    "x-go-name": "Photo",
    "description": "Photo is the image to upload.",
    "name": "photo",
    "in": "formData"
  }
]

Full source: docs/examples/concepts/routes/testdata/file.json

What’s next

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Validations

Validations are keyword-driven: you write keyword: value lines in a field’s doc comment and they become minimum, maxLength, pattern, enum, and the rest of the validation surface on that property. Each pane below pairs the annotated Go (left) with the fragment the scanner emits (right), from the test-covered docs/examples/concepts/validations package.

For the per-keyword reference card β€” value shapes, aliases, and legal contexts β€” see Keywords.

On a model field β€” the full surface

A swagger:model field accepts the full JSON-schema validation vocabulary:

  • Numeric β€” minimum, maximum, multipleOf (on Price).
  • Length β€” min length / max length (on Name).
  • Arrays β€” min items / max items / unique (on Tags).
  • Pattern β€” a regular expression (on SKU).
  • Enum β€” a fixed value set (on Grade).
  • Required β€” required: true lifts the property into the schema’s object-level required array (sku).
Annotated Go
// Product is a model whose fields carry the full JSON-schema validation surface.
//
// swagger:model
type Product struct {
	// SKU is the stock code.
	//
	// required: true
	// pattern: ^[A-Z]{3}-[0-9]{4}$
	SKU string `json:"sku"`

	// Price is the price in cents.
	//
	// minimum: 1
	// maximum: 1000000
	// multipleOf: 1
	Price int64 `json:"price"`

	// Name is the display name.
	//
	// min length: 1
	// max length: 120
	Name string `json:"name"`

	// Grade is a quality band.
	//
	// enum: A,B,C
	Grade string `json:"grade"`

	// Tags label the product.
	//
	// min items: 1
	// max items: 10
	// unique: true
	Tags []string `json:"tags"`
}

Full source: docs/examples/concepts/validations/validations.go

#/definitions/Product
{
  "type": "object",
  "title": "Product is a model whose fields carry the full JSON-schema validation surface.",
  "required": [
    "sku"
  ],
  "properties": {
    "grade": {
      "description": "Grade is a quality band.",
      "type": "string",
      "enum": [
        "A",
        "B",
        "C"
      ],
      "x-go-name": "Grade"
    },
    "name": {
      "description": "Name is the display name.",
      "type": "string",
      "maxLength": 120,
      "minLength": 1,
      "x-go-name": "Name"
    },
    "price": {
      "description": "Price is the price in cents.",
      "type": "integer",
      "format": "int64",
      "maximum": 1000000,
      "minimum": 1,
      "multipleOf": 1,
      "x-go-name": "Price"
    },
    "sku": {
      "description": "SKU is the stock code.",
      "type": "string",
      "pattern": "^[A-Z]{3}-[0-9]{4}$",
      "x-go-name": "SKU"
    },
    "tags": {
      "description": "Tags label the product.",
      "type": "array",
      "maxItems": 10,
      "minItems": 1,
      "uniqueItems": true,
      "items": {
        "type": "string"
      },
      "x-go-name": "Tags"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/validations"
}

Full source: docs/examples/concepts/validations/testdata/field.json

On parameters β€” the simple-schema surface

Info

Simple schemas have a reduced surface. Parameters other than in: body, and response headers, are simple schemas in OpenAPI 2.0 β€” not full JSON schemas. They accept the validation subset (maximum/minimum/multipleOf, maxLength/minLength/pattern, maxItems/minItems/uniqueItems, enum, plus the simple-schema-only collectionFormat) but not schema-only constructs. A schema-only keyword such as readOnly placed on a query parameter is simply not emitted β€” spec.Parameter has nowhere to carry it.

The same numeric and length keywords work on a query parameter; arrays add collectionFormat:

Annotated Go
// SearchParams is the simple-schema parameter set for searchProducts. Query
// parameters accept the reduced OAS 2.0 validation surface.
//
// swagger:parameters searchProducts
type SearchParams struct {
	// Q is the search text.
	//
	// in: query
	// min length: 3
	// max length: 50
	Q string `json:"q"`

	// Limit caps the number of results.
	//
	// in: query
	// minimum: 1
	// maximum: 100
	Limit int32 `json:"limit"`

	// Sort lists the sort fields.
	//
	// in: query
	// collection format: csv
	// unique: true
	Sort []string `json:"sort"`
}

Full source: docs/examples/concepts/validations/validations.go

parameters on searchProducts
[
  {
    "maxLength": 50,
    "minLength": 3,
    "type": "string",
    "x-go-name": "Q",
    "description": "Q is the search text.",
    "name": "q",
    "in": "query"
  },
  {
    "maximum": 100,
    "minimum": 1,
    "type": "integer",
    "format": "int32",
    "x-go-name": "Limit",
    "description": "Limit caps the number of results.",
    "name": "limit",
    "in": "query"
  },
  {
    "uniqueItems": true,
    "type": "array",
    "items": {
      "type": "string"
    },
    "collectionFormat": "csv",
    "x-go-name": "Sort",
    "description": "Sort lists the sort fields.",
    "name": "sort",
    "in": "query"
  }
]

Full source: docs/examples/concepts/validations/testdata/param.json

On response headers

A response header is also a simple schema, so it takes the same reduced validation set (here minimum on an integer header). Note headers carry no required flag.

Annotated Go
// RateLimited is a response carrying a validated header (a simple schema).
//
// swagger:response rateLimited
type RateLimited struct {
	// XRateRemaining is the remaining request budget.
	//
	// minimum: 0
	XRateRemaining int32 `json:"X-Rate-Remaining"`
}

Full source: docs/examples/concepts/validations/validations.go

responses[rateLimited]
{
  "description": "RateLimited is a response carrying a validated header (a simple schema).",
  "headers": {
    "X-Rate-Remaining": {
      "minimum": 0,
      "type": "integer",
      "format": "int32",
      "description": "XRateRemaining is the remaining request budget."
    }
  }
}

Full source: docs/examples/concepts/validations/testdata/header.json

What’s next

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Examples & defaults

Example values and defaults are documentation that travels with the schema: an example: shows a caller what a value looks like, a default: declares what the field is when the caller omits it. Both are typed to the field β€” a numeric default on an integer field is a JSON number, not a string. The panes below pair the annotated Go with the fragment the scanner emits, from the test-covered docs/examples/concepts/examples package.

For the exact value shapes these keywords accept, see Keywords.

example

example: <value> attaches an example to the property, coerced to the field’s type β€” Hello, world! stays a string, 3 becomes a number.

Annotated Go
// Greeting carries an example value for documentation.
//
// swagger:model
type Greeting struct {
	// Message is the greeting text.
	//
	// example: Hello, world!
	Message string `json:"message"`

	// Count is how many times to repeat it.
	//
	// example: 3
	Count int32 `json:"count"`
}

Full source: docs/examples/concepts/examples/examples.go

#/definitions/Greeting
{
  "type": "object",
  "title": "Greeting carries an example value for documentation.",
  "properties": {
    "count": {
      "description": "Count is how many times to repeat it.",
      "type": "integer",
      "format": "int32",
      "x-go-name": "Count",
      "example": 3
    },
    "message": {
      "description": "Message is the greeting text.",
      "type": "string",
      "x-go-name": "Message",
      "example": "Hello, world!"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"
}

Full source: docs/examples/concepts/examples/testdata/example.json

default

default: <value> sets the property’s default, again typed to the field β€” 8080 is a number, false a boolean, auto a string.

Annotated Go
// Settings carries default values applied when a field is omitted.
//
// swagger:model
type Settings struct {
	// Port is the listen port.
	//
	// default: 8080
	Port int32 `json:"port"`

	// Mode is the run mode.
	//
	// default: auto
	Mode string `json:"mode"`

	// Verbose toggles verbose logging.
	//
	// default: false
	Verbose bool `json:"verbose"`
}

Full source: docs/examples/concepts/examples/examples.go

#/definitions/Settings
{
  "type": "object",
  "title": "Settings carries default values applied when a field is omitted.",
  "properties": {
    "mode": {
      "description": "Mode is the run mode.",
      "type": "string",
      "default": "auto",
      "x-go-name": "Mode"
    },
    "port": {
      "description": "Port is the listen port.",
      "type": "integer",
      "format": "int32",
      "default": 8080,
      "x-go-name": "Port"
    },
    "verbose": {
      "description": "Verbose toggles verbose logging.",
      "type": "boolean",
      "default": false,
      "x-go-name": "Verbose"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"
}

Full source: docs/examples/concepts/examples/testdata/default.json

swagger:default

swagger:default is a narrow, value-only classifier hint placed on a var or const. It does not publish a spec entity of its own β€” it has no standalone output β€” so most spec defaults are carried by the default: keyword above rather than this annotation.

// DefaultPort is the fallback port used wherever Port is not supplied. The
// swagger:default annotation is a narrow value-only discovery hint.
//
// swagger:default
var DefaultPort = 8080

Full source: docs/examples/concepts/examples/examples.go

What’s next

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Other type decorators

Beyond validations, a couple of keyword decorators annotate a property’s or operation’s role. The panes below pair the annotated Go with the fragment the scanner emits, from the test-covered docs/examples/concepts/decorators package.

For the value shapes and legal contexts of each, see the Keyword reference.

readOnly

read only: true on a model field marks the property readOnly β€” the server sets it, clients must not.

Annotated Go
// Token is issued by the server.
//
// swagger:model
type Token struct {
	// ID is assigned by the server and cannot be set by clients.
	//
	// read only: true
	ID string `json:"id"`

	// Value is the token value.
	Value string `json:"value"`
}

Full source: docs/examples/concepts/decorators/decorators.go

#/definitions/Token
{
  "type": "object",
  "title": "Token is issued by the server.",
  "properties": {
    "id": {
      "description": "ID is assigned by the server and cannot be set by clients.",
      "type": "string",
      "x-go-name": "ID",
      "readOnly": true
    },
    "value": {
      "description": "Value is the token value.",
      "type": "string",
      "x-go-name": "Value"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/decorators"
}

Full source: docs/examples/concepts/decorators/testdata/readonly.json

deprecated

deprecated: true in a swagger:route / swagger:operation body marks the operation deprecated.

Annotated Go
// swagger:route GET /legacy/ping legacy ping
//
// Ping is the legacy health check.
//
// deprecated: true
//
// responses:
//
//	200: pingResponse

Full source: docs/examples/concepts/decorators/decorators.go

paths[/legacy/ping]
{
  "get": {
    "tags": [
      "legacy"
    ],
    "summary": "Ping is the legacy health check.",
    "operationId": "ping",
    "deprecated": true,
    "responses": {
      "200": {
        "$ref": "#/responses/pingResponse"
      }
    }
  }
}

Full source: docs/examples/concepts/decorators/testdata/deprecated.json

Info

deprecated is an operation-level flag in OpenAPI 2.0. The Schema object has no native deprecated, so deprecated: on a model field is not emitted on the property (and produces no x-deprecated).

What’s next

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Document metadata

A single swagger:meta block on a package doc comment carries the document’s top-level metadata: its info (title, description, version, license, contact), the host and basePath, the default schemes, and consumes/produces. The pane pairs the annotated package with the document it produces, from the test-covered docs/examples/concepts/meta package.

swagger:meta

The block lives in the package doc comment. The title comes from the first line with the Package <name> prefix stripped; the following paragraph becomes the description. The indented Key: value lines and list blocks populate the rest β€” License: and Contact: parse into structured objects.

Package doc comment
// Package meta Pet Store.
//
// A small API that demonstrates the document-level swagger:meta block: the
// package doc comment carries the spec's top-level metadata.
//
//	Schemes: https
//	Host: api.example.com
//	BasePath: /v1
//	Version: 1.2.0
//	License: Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0.html
//	Contact: API Team <api@example.com> https://example.com/support
//
//	Consumes:
//	  - application/json
//
//	Produces:
//	  - application/json
//
// swagger:meta
package meta

Full source: docs/examples/concepts/meta/doc.go

the document
{
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "schemes": [
    "https"
  ],
  "swagger": "2.0",
  "info": {
    "description": "A small API that demonstrates the document-level swagger:meta block: the\npackage doc comment carries the spec's top-level metadata.",
    "title": "Pet Store.",
    "contact": {
      "name": "API Team",
      "url": "https://example.com/support",
      "email": "api@example.com"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0.html"
    },
    "version": "1.2.0"
  },
  "host": "api.example.com",
  "basePath": "/v1",
  "paths": {}
}

Full source: docs/examples/concepts/meta/testdata/meta.json

For the full meta keyword surface (security definitions, external docs, extensions, terms of service), see the swagger:meta reference and the meta keywords.

What’s next

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Putting it together

This capstone scans a tiny annotated “petstore” package and produces a Swagger 2.0 spec β€” the concepts from the pages above, assembled into one runnable example. It is the worked version of usage as a library.

The annotated API

A package-level swagger:meta block sets the top-level metadata:

// Package petstore Petstore API
//
// A tiny pet store, used to demonstrate codescan annotations: the package
// comment is a `swagger:meta` block carrying the top-level metadata of the
// generated specification (title, description, version, base path, …).
//
//	Schemes: https
//	Version: 1.0.0
//	BasePath: /v1
//
//	Consumes:
//	- application/json
//
//	Produces:
//	- application/json
//
// swagger:meta

Full source: docs/examples/petstore/doc.go

A swagger:route registers a path and ties it to a response:

// swagger:route GET /pets pets listPets
//
// Lists all the pets in the store.
//
// responses:
//
//	200: petsResponse

Full source: docs/examples/petstore/pet.go

A swagger:model struct becomes a definition, with field comments driving validations:

// Pet is a single pet in the store.
//
// swagger:model Pet
type Pet struct {
	// The id of the pet.
	//
	// required: true
	// minimum: 1
	ID int64 `json:"id"`

	// The name of the pet.
	//
	// required: true
	// min length: 1
	Name string `json:"name"`

	// The tags associated with this pet.
	Tags []string `json:"tags,omitempty"`
}

Full source: docs/examples/petstore/pet.go

Running the scan

ScanPetstore builds the Options and calls codescan.Run:

opts := &codescan.Options{
	WorkDir:    workDir,                // module root to resolve patterns from
	Packages:   []string{"./petstore"}, // relative package pattern
	ScanModels: true,                   // also emit definitions for swagger:model types
}

doc, err := codescan.Run(opts)
if err != nil {
	return nil, err
}

Full source: docs/examples/basic/scan.go

The generated spec

Marshalling the returned *spec.Swagger to JSON yields the document below β€” the meta block became the top-level info / basePath, the swagger:route became the /pets path, and the swagger:model became the Pet definition:

{
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "schemes": [
    "https"
  ],
  "swagger": "2.0",
  "info": {
    "description": "A tiny pet store, used to demonstrate codescan annotations: the package\ncomment is a `swagger:meta` block carrying the top-level metadata of the\ngenerated specification (title, description, version, base path, …).",
    "title": "Petstore API",
    "version": "1.0.0"
  },
  "basePath": "/v1",
  "paths": {
    "/pets": {
      "get": {
        "tags": [
          "pets"
        ],
        "summary": "Lists all the pets in the store.",
        "operationId": "listPets",
        "responses": {
          "200": {
            "$ref": "#/responses/petsResponse"
          }
        }
      }
    }
  },
  "definitions": {
    "Pet": {
      "type": "object",
      "title": "Pet is a single pet in the store.",
      "required": [
        "id",
        "name"
      ],
      "properties": {
        "id": {
          "description": "The id of the pet.",
          "type": "integer",
          "format": "int64",
          "minimum": 1,
          "x-go-name": "ID"
        },
        "name": {
          "description": "The name of the pet.",
          "type": "string",
          "minLength": 1,
          "x-go-name": "Name"
        },
        "tags": {
          "description": "The tags associated with this pet.",
          "type": "array",
          "items": {
            "type": "string"
          },
          "x-go-name": "Tags"
        }
      },
      "x-go-package": "github.com/go-openapi/codescan/docs/examples/petstore"
    }
  },
  "responses": {
    "petsResponse": {
      "description": "petsResponse is the list of pets returned by listPets.",
      "schema": {
        "type": "array",
        "items": {
          "$ref": "#/definitions/Pet"
        }
      }
    }
  }
}

Full source: docs/examples/basic/testdata/swagger.json

This JSON is not hand-written: it is a golden file the example’s test regenerates and compares on every run (UPDATE_GOLDEN=1 go test ./...). Because the example is ordinary, test-covered Go, go test ./docs/examples/... keeps the page honest β€” if the scanner’s output changes, CI fails before the documentation can go stale.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Shaping the output

The same annotated Go can render into the spec in more than one shape. A handful of codescan.Options let you choose: should an alias become a $ref or expand inline? Should a field’s description survive next to a $ref? Should pointer fields be marked nullable?

Each guide here is task-oriented β€” “I want the output to look like this” β€” and shows the same input rendered both ways, as before/after golden output the example tests verify.

  • Limit what gets scanned β€” package patterns, working directory, include/exclude filters, tag filters, and dependency handling.
  • codescan never invents definitions β€” a type appears only when it is reachable or registered. Understand reachability and swagger:model so nothing goes missing or appears unexpectedly.
  • Mark pointer-typed fields as nullable with x-nullable, via SetXNullableForPointers.
  • Control the x-go-* vendor extensions codescan emits, or suppress them with SkipExtensions.
  • Keep a field’s description when its type resolves to a $ref, by wrapping the reference in a single-arm allOf (DescWithRef).
  • Choose how Go type aliases render β€” dissolved to their target, or exposed as a first-class $ref via swagger:model, with RefAliases / TransparentAliases.
  • Merge scanned discoveries on top of an existing Swagger document with InputSpec.
  • Scan source guarded by Go build constraints by passing build tags to the scanner.

For the field-by-field meaning of each option, see the Options godoc.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Shaping the output

Scoping the scan

Several options narrow what codescan looks at, independent of how individual types render. They decide which packages are loaded and which discovered operations survive into the spec.

Package patterns and WorkDir

Options.Packages takes relative go list-style patterns β€” ./petstore, ./... for a whole tree β€” resolved against Options.WorkDir (the module root). This is the worked form in the Getting started guide:

codescan.Run(&codescan.Options{
    WorkDir:    "/path/to/module",
    Packages:   []string{"./..."},
    ScanModels: true,
})

Include / Exclude

Options.Include and Options.Exclude are lists of regular expressions matched against package import paths. Include acts as an allow-list (when non-empty, only matching packages are scanned); Exclude removes matches. Use them to keep internal or generated packages out of the spec:

codescan.Run(&codescan.Options{
    Packages: []string{"./..."},
    Exclude:  []string{"/internal/", "/testdata/"},
})

Tag filters

Options.IncludeTags / Options.ExcludeTags filter operations by their Swagger tags after discovery β€” handy for publishing a public subset of an API while keeping the admin routes in the source:

codescan.Run(&codescan.Options{
    Packages:    []string{"./..."},
    ExcludeTags: []string{"admin", "internal"},
})

ExcludeDeps

By default codescan may follow types into dependency packages to resolve referenced models. Options.ExcludeDeps keeps the scan within your own module, leaving out types pulled in from dependencies.

Build constraints get their own guide β€” see Build tags.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

When the scanner emits a type

codescan does not emit a definition for every type it can see. A named type reaches the spec when either of these holds:

  • it is reachable β€” referenced (directly or transitively) from an operation, parameter, response, or another emitted model; or
  • it is registered β€” annotated swagger:model, which (with Options.ScanModels) publishes it even when nothing references it.

A type that is neither reachable nor registered is simply absent β€” the scanner never invents it. The package below has one of each case:

// Order is reached only through Cart below. A referenced named type is emitted
// as a $ref target even without swagger:model.
type Order struct {
	// ID is the order identifier.
	ID string `json:"id"`
}

// Cart references Order, so Order gets a definition and the field a $ref.
//
// swagger:model
type Cart struct {
	// Order is the referenced (and therefore emitted) nested model.
	Order Order `json:"order"`
}

// Standalone is never referenced, but swagger:model together with ScanModels
// publishes it anyway.
//
// swagger:model
type Standalone struct {
	// Label is a free-text label.
	Label string `json:"label"`
}

// Orphan is never referenced and carries no swagger:model β€” the scanner does
// not invent it, so it never reaches the spec.
type Orphan struct {
	// Secret is internal.
	Secret string `json:"secret"`
}

Full source: docs/examples/shaping/discovery/discovery.go

Scanned with ScanModels: true, the definitions are:

{
  "Cart": {
    "type": "object",
    "title": "Cart references Order, so Order gets a definition and the field a $ref.",
    "properties": {
      "order": {
        "$ref": "#/definitions/Order"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/discovery"
  },
  "Order": {
    "description": "Order is reached only through Cart below. A referenced named type is emitted\nas a $ref target even without swagger:model.",
    "type": "object",
    "properties": {
      "id": {
        "description": "ID is the order identifier.",
        "type": "string",
        "x-go-name": "ID"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/discovery"
  },
  "Standalone": {
    "description": "Standalone is never referenced, but swagger:model together with ScanModels\npublishes it anyway.",
    "type": "object",
    "properties": {
      "label": {
        "description": "Label is a free-text label.",
        "type": "string",
        "x-go-name": "Label"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/discovery"
  }
}

Full source: docs/examples/shaping/discovery/testdata/definitions.json

  • Cart β€” a swagger:model root.
  • Order β€” has no swagger:model, yet it is emitted (as a $ref target) because Cart references it. You do not need to annotate every nested type.
  • Standalone β€” a swagger:model that nothing references; ScanModels publishes it anyway.
  • Orphan β€” neither referenced nor annotated, so it never appears.
Info

If a model is missing from your spec, it is almost always unreachable: no operation/parameter/response/model leads to it. Either reference it, or annotate it swagger:model and scan with ScanModels.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Nullable pointers

Swagger 2.0 has no native nullable flag; the go-openapi toolchain uses the x-nullable vendor extension. Options.SetXNullableForPointers decides whether pointer-typed struct fields acquire it automatically. The model below has two pointer fields:

// Profile has required and optional (pointer) fields.
//
// swagger:model
type Profile struct {
	// Name is always present.
	Name string `json:"name"`

	// Nickname is optional.
	Nickname *string `json:"nickname"`

	// Age is optional.
	Age *int32 `json:"age"`
}

Full source: docs/examples/shaping/nullable/nullable.go

Scanned with the option off (default) and on, the pointer fields differ:

Default
{
  "type": "object",
  "title": "Profile has required and optional (pointer) fields.",
  "properties": {
    "age": {
      "description": "Age is optional.",
      "type": "integer",
      "format": "int32",
      "x-go-name": "Age"
    },
    "name": {
      "description": "Name is always present.",
      "type": "string",
      "x-go-name": "Name"
    },
    "nickname": {
      "description": "Nickname is optional.",
      "type": "string",
      "x-go-name": "Nickname"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nullable"
}

Full source: docs/examples/shaping/nullable/testdata/off.json

SetXNullableForPointers: true
{
  "type": "object",
  "title": "Profile has required and optional (pointer) fields.",
  "properties": {
    "age": {
      "description": "Age is optional.",
      "type": "integer",
      "format": "int32",
      "x-go-name": "Age",
      "x-nullable": true
    },
    "name": {
      "description": "Name is always present.",
      "type": "string",
      "x-go-name": "Name"
    },
    "nickname": {
      "description": "Nickname is optional.",
      "type": "string",
      "x-go-name": "Nickname",
      "x-nullable": true
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nullable"
}

Full source: docs/examples/shaping/nullable/testdata/on.json

codescan.Run(&codescan.Options{
    Packages:                []string{"./..."},
    ScanModels:              true,
    SetXNullableForPointers: true,
})
Info

omitempty changes the meaning. A pointer field tagged json:"…,omitempty" is treated as optional (may be absent) rather than nullable (may be null), so it does not receive x-nullable even with the option on. Drop omitempty when you mean the value can be present-but-null.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Vendor extensions

By default codescan records where each spec object came from in Go via x-go-name (and x-go-package on definitions) β€” useful for round-tripping and code generation. Options.SkipExtensions removes them for a leaner spec.

// Widget is a small model.
//
// codescan records each field's Go origin as vendor extensions unless
// SkipExtensions is set.
//
// swagger:model
type Widget struct {
	// Label is the display label.
	Label string `json:"label"`

	// Size is the widget size in pixels.
	Size int32 `json:"size"`
}

Full source: docs/examples/shaping/extensions/extensions.go

Default
{
  "description": "codescan records each field's Go origin as vendor extensions unless\nSkipExtensions is set.",
  "type": "object",
  "title": "Widget is a small model.",
  "properties": {
    "label": {
      "description": "Label is the display label.",
      "type": "string",
      "x-go-name": "Label"
    },
    "size": {
      "description": "Size is the widget size in pixels.",
      "type": "integer",
      "format": "int32",
      "x-go-name": "Size"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/extensions"
}

Full source: docs/examples/shaping/extensions/testdata/off.json

SkipExtensions: true
{
  "description": "codescan records each field's Go origin as vendor extensions unless\nSkipExtensions is set.",
  "type": "object",
  "title": "Widget is a small model.",
  "properties": {
    "label": {
      "description": "Label is the display label.",
      "type": "string"
    },
    "size": {
      "description": "Size is the widget size in pixels.",
      "type": "integer",
      "format": "int32"
    }
  }
}

Full source: docs/examples/shaping/extensions/testdata/on.json

codescan.Run(&codescan.Options{
    Packages:       []string{"./..."},
    ScanModels:     true,
    SkipExtensions: true,
})

SkipExtensions removes the scanner-derived x-go-* extensions. Extensions you author yourself (via the Extensions: keyword) are not affected.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Descriptions beside a $ref

When a struct field’s only decoration is a description and its Go type resolves to a named model (a $ref), JSON Schema draft 4 cannot carry a sibling description next to a $ref. Options.DescWithRef decides what happens to that description.

// Address is a referenced model.
//
// swagger:model
type Address struct {
	// Street is the street line.
	Street string `json:"street"`
}

// Person references Address through a field whose only decoration is a
// description.
//
// swagger:model
type Person struct {
	// Home is where the person lives.
	Home Address `json:"home"`
}

Full source: docs/examples/shaping/descref/descref.go

By default the description is dropped (a bare $ref); with DescWithRef it is preserved by wrapping the $ref in a single-arm allOf:

Default β€” description dropped
{
  "description": "Person references Address through a field whose only decoration is a\ndescription.",
  "type": "object",
  "properties": {
    "home": {
      "$ref": "#/definitions/Address"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/descref"
}

Full source: docs/examples/shaping/descref/testdata/off.json

DescWithRef: true
{
  "description": "Person references Address through a field whose only decoration is a\ndescription.",
  "type": "object",
  "properties": {
    "home": {
      "description": "Home is where the person lives.",
      "allOf": [
        {
          "$ref": "#/definitions/Address"
        }
      ],
      "x-go-name": "Home"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/descref"
}

Full source: docs/examples/shaping/descref/testdata/on.json

codescan.Run(&codescan.Options{
    Packages:    []string{"./..."},
    ScanModels:  true,
    DescWithRef: true,
})
Info

When the field carries more than a description β€” a validation override or a user-authored extension β€” the allOf wrapper is emitted regardless of this flag, because the override would otherwise be lost. DescWithRef only governs the description-only case.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Alias rendering

A Go type alias (type Price = Money) is, to the Go type system, literally the same type as its target. codescan’s default is to treat it that way: at a use site the alias dissolves to its target, producing no definition of its own.

Annotated Go
// Money is the underlying model.
//
// swagger:model
type Money struct {
	// Cents is the amount in cents.
	Cents int64 `json:"cents"`

	// Currency is the ISO currency code.
	Currency string `json:"currency"`
}

// Price is a Go alias of Money. By default an alias is a Go implementation
// detail: at use sites it dissolves to its target, producing no definition of
// its own.
type Price = Money

// Invoice references Price; the field resolves to Money.
//
// swagger:model
type Invoice struct {
	// Total is the invoice total.
	Total Price `json:"total"`
}

Full source: docs/examples/shaping/aliases/aliases.go

#/definitions/Invoice
{
  "type": "object",
  "title": "Invoice references Price; the field resolves to Money.",
  "properties": {
    "total": {
      "$ref": "#/definitions/Money"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases"
}

Full source: docs/examples/shaping/aliases/testdata/invoice.json

Invoice.total is typed Price, but the field resolves straight to #/definitions/Money β€” Price itself never appears.

Exposing an alias as a first-class entity

This is an advanced, rarely-needed case. To keep the alias name in the spec (its own definition that other schemas $ref), annotate the alias with swagger:model. Two top-level options then govern how that first-class alias definition is shaped:

  • default (expand) β€” the alias definition is a structural copy of the target.
  • RefAliases: true β€” the alias definition is a $ref chain to the target ({"$ref": "#/definitions/Money"}), preserving the alias name at use sites.
  • TransparentAliases: true β€” aliases dissolve to their target everywhere, overriding the per-declaration annotation (use sites become $ref to the target, as in the default-dissolve example above).

For the precise per-mode contract and the canonical witnesses, see the swagger:alias reference and the fixtures/enhancements/alias-calibration-embed golden trio.

Note

Most APIs never need first-class aliases β€” prefer naming a real swagger:model type over aliasing one. Reach for RefAliases / TransparentAliases only when you specifically need to control whether an alias name survives in the output.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Overlaying a spec

Options.InputSpec seeds the scan with an existing *spec.Swagger: codescan merges what it discovers on top of it rather than starting from a blank document. Use it to keep hand-authored top-level metadata or a hand-written definition, or to compose a spec across several scans.

The scanned package contributes one model:

// Widget is discovered by the scan and merged onto the input spec.
//
// swagger:model
type Widget struct {
	// ID identifies the widget.
	ID string `json:"id"`
}

Full source: docs/examples/shaping/overlay/overlay.go

Given a base document with metadata and a hand-authored Health definition, the scan preserves all of it and adds the discovered Widget:

InputSpec (base)
{
  "swagger": "2.0",
  "info": {
    "title": "Inventory API",
    "version": "1.0.0"
  },
  "host": "api.example.com",
  "basePath": "/v1",
  "paths": null,
  "definitions": {
    "Health": {
      "type": "object",
      "properties": {
        "ok": {
          "type": "boolean"
        }
      }
    }
  }
}

Full source: docs/examples/shaping/overlay/testdata/base.json

After the scan
{
  "swagger": "2.0",
  "info": {
    "title": "Inventory API",
    "version": "1.0.0"
  },
  "host": "api.example.com",
  "basePath": "/v1",
  "paths": {},
  "definitions": {
    "Health": {
      "type": "object",
      "properties": {
        "ok": {
          "type": "boolean"
        }
      }
    },
    "Widget": {
      "type": "object",
      "title": "Widget is discovered by the scan and merged onto the input spec.",
      "properties": {
        "id": {
          "description": "ID identifies the widget.",
          "type": "string",
          "x-go-name": "ID"
        }
      },
      "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/overlay"
    }
  }
}

Full source: docs/examples/shaping/overlay/testdata/merged.json

var base spec.Swagger
_ = json.Unmarshal(baseSpecJSON, &base)

doc, _ := codescan.Run(&codescan.Options{
    Packages:   []string{"./..."},
    ScanModels: true,
    InputSpec:  &base,
})

The document’s info, host, basePath and the hand-authored Health definition survive untouched; only the discovered definitions are added.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Build tags

Go files can be guarded by //go:build constraints. By default codescan loads a package under the default build configuration, so tag-gated files are skipped. Options.BuildTags passes the tags through to the package loader, so the annotations in those files are scanned too.

The package has an always-present model plus this one, in a file that opens with the constraint //go:build experimental:

// Experimental is only scanned when the "experimental" build tag is set.
//
// swagger:model
type Experimental struct {
	// Beta flags a beta-only feature.
	Beta bool `json:"beta"`
}

Full source: docs/examples/shaping/buildtags/experimental.go

Scanned with no tags and with experimental, the gated Experimental model appears only in the second:

Default
{
  "Stable": {
    "type": "object",
    "title": "Stable is always scanned.",
    "properties": {
      "name": {
        "description": "Name is the feature name.",
        "type": "string",
        "x-go-name": "Name"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/buildtags"
  }
}

Full source: docs/examples/shaping/buildtags/testdata/off.json

BuildTags: experimental
{
  "Experimental": {
    "type": "object",
    "title": "Experimental is only scanned when the \"experimental\" build tag is set.",
    "properties": {
      "beta": {
        "description": "Beta flags a beta-only feature.",
        "type": "boolean",
        "x-go-name": "Beta"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/buildtags"
  },
  "Stable": {
    "type": "object",
    "title": "Stable is always scanned.",
    "properties": {
      "name": {
        "description": "Name is the feature name.",
        "type": "string",
        "x-go-name": "Name"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/buildtags"
  }
}

Full source: docs/examples/shaping/buildtags/testdata/on.json

codescan.Run(&codescan.Options{
    Packages:   []string{"./..."},
    ScanModels: true,
    BuildTags:  "experimental",
})

BuildTags accepts the same comma-separated form as go build -tags.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Annotation index

The complete swagger:* vocabulary, one row each. By example jumps to the tutorial that shows the annotation as runnable Go next to the spec it produces; Reference jumps to the exhaustive rule in the Maintainers compendium.

AnnotationAttaches toProducesBy exampleReference
swagger:metapackage doctop-level info, host, basePath, schemes, …examplereference
swagger:modeltype declarationa definitions entryexamplereference
swagger:strfmttype declaration{type: string, format: …} at every useexamplereference
swagger:enumnamed typean enum array (+ x-go-enum-desc)examplereference
swagger:allOfembedded field / structan allOf compositionexamplereference
swagger:aliastype aliasa $ref to the target (vs inline expansion)how-toreference
swagger:routefunc / var doca paths entry + operationexamplereference
swagger:operationfunc / var doca paths entry (YAML body)examplereference
swagger:parametersstruct declarationparameters on the named operation(s)examplereference
swagger:responsestruct declarationa responses entryexamplereference
swagger:ignoretype / field docexcludes the declarationexamplereference
swagger:namefield / method docrenames a JSON propertyexamplereference
swagger:typetype / field docoverrides the inferred Swagger typeexamplereference
swagger:fileparam / response field{type: file}examplereference
swagger:defaultvalue / field doca default-value anchorexamplereference

Keywords, not annotations

Validations, examples and defaults inside a block are driven by keywords (minimum:, pattern:, enum:, example:, default:, …), not annotations. See the Validations and Examples & defaults tutorials, and the Keyword reference.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Maintainers

This section is the reference compendium: the precise, exhaustive description of the language codescan parses. It is written for people who want the full contract β€” annotation authors looking up an exact rule, and contributors porting, extending, or debugging the parser.

If you are learning codescan by example, start with the Tutorials instead β€” they show the same concepts as runnable Go with the spec they produce, side by side. The Annotation index cross-references every annotation to both its tutorial and its entry here.

The four documents

  • The swagger:* annotation vocabulary: what each produces, where it attaches, and the keywords it admits.
  • Per-keyword reference card: every keyword form, its value shape, and the contexts where it is legal.
  • The smaller languages embedded in annotation bodies: the Parameters/Responses grammars, YAML surfaces, and prose classification.
  • The formal ISO-14977 EBNF the parser implements, from comment preprocessing through the typed walker.
  • Annotations β€” the swagger:* vocabulary: what each annotation does, where it attaches, its argument shape, and the keywords it admits. The author-facing normative reference.
  • Keywords β€” the per-keyword reference card: every keyword: value form, its value shape, and the contexts where it is legal.
  • Sub-languages β€” the smaller languages embedded inside annotation bodies (Parameters: / Responses: grammars, YAML surfaces, prose classification).
  • Grammar β€” the formal ISO-14977 EBNF the parser implements, from comment preprocessing through the typed walker.
Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Maintainers

Annotations

Annotations are the swagger:<name> markers the scanner recognises in Go doc comments. Each annotation classifies the surrounding declaration β€” telling the scanner “this is a model definition”, “this is a route handler”, “this is meta-information about the API” β€” and opens the door for keywords inside the same comment block.

There are twelve annotations. They divide cleanly by what they attach to:

  • Spec-level: swagger:meta.
  • Model declarations: swagger:model, swagger:strfmt, swagger:enum, swagger:allOf, swagger:alias.
  • Operation declarations: swagger:route, swagger:operation.
  • Companion declarations: swagger:parameters, swagger:response.
  • Local hints: swagger:ignore, swagger:name, swagger:type, swagger:file, swagger:default.

This file is the author-first reference. Each entry covers:

  • What the annotation does and what it produces in the spec.
  • Where in the Go source it goes (package doc, type doc, field doc).
  • The shape of any argument the annotation accepts.
  • A short Go sample.
  • A pointer to the keywords that are legal inside the block.
  • A pointer to a real fixture in this repo for the full executable example.

For the per-keyword reference, see keywords.md. For the embedded sub-languages (Parameters: and Responses: body grammars, YAML extensions, etc.), see sub-languages.md. For the formal grammar, see grammar.md.


Table of contents


How annotations attach

An annotation is recognised when it appears at the start of a comment line in a doc comment. Leading whitespace, the // marker, and any /* */ block-comment continuation noise are stripped β€” the lexer applies the same content-prefix-trim that every other godoc-aware tool does.

Annotations attach to whichever Go declaration owns the comment group:

  • Package doc (// Package foo … followed by package foo) β€” carries swagger:meta.
  • Type declaration (type T struct { … }, type T int, type T = Other) β€” carries swagger:model, swagger:strfmt, swagger:enum, swagger:allOf, swagger:alias, swagger:ignore, swagger:type.
  • Function or variable declaration (func ServeAPI() { … }, var DoIt = func() { … }) β€” carries swagger:route, swagger:operation.
  • Struct field doc β€” carries swagger:name, swagger:type, swagger:ignore, plus any of the keyword reference entries legal in schema / param / header context.

One comment group may carry MORE than one annotation when the combinations are semantically compatible β€” e.g. swagger:model + swagger:type together overrides the auto-detected Go type while still publishing the model. The grammar parses both and the builder honours both.

The first annotation in source order wins as the “primary” classifier β€” for example, a comment carrying swagger:model followed by swagger:ignore produces a model (the ignore is silently overridden because only the source-order-first annotation drives the short-circuit). Subsequent annotations are still parsed and visible via Block.AnnotationKind()-iteration, but the primary classifier determines which builder owns the decl.

Annotation argument shapes

After the swagger:<name> head, an annotation may carry positional arguments. The shapes:

  • No args: swagger:meta, swagger:ignore, swagger:enum, swagger:allOf, swagger:file, swagger:default β€” bare annotation, the surrounding decl supplies the entity name.
  • One IDENT arg: swagger:model Pet, swagger:response errorResponse, swagger:strfmt uuid, swagger:name fullName, swagger:type integer, swagger:alias TimestampAlias β€” the argument overrides or names the entity.
  • One IDENT arg, optional: swagger:model (bare β€” derives the name from the Go decl) vs swagger:model Pet (overrides).
  • List of IDENT args: swagger:parameters listItems createItem β€” declares the parameters group as legal for multiple operations.
  • Header line: swagger:route GET /pets pets users listPets and swagger:operation GET /pets users listPets β€” a structured header carrying method, path, tags, and operation ID. See the per-annotation entries for the exact rules.

swagger:meta

What it does. Declares the package as the OpenAPI spec container. The scanner reads the package doc comment for top-level spec fields: title (via stripPackagePrefix of the doc’s first line), description, license, contact, host, basePath, version, schemes, consumes, produces, securityDefinitions, extensions, and the rest of the meta keyword surface.

Where it goes. On the package doc comment.

Argument shape. No args. Bare annotation.

Sample.

// Package petstore Petstore API.
//
// The purpose of this application is to provide an application
// that is using plain Go code to define an API.
//
//     Schemes: http, https
//     Host: petstore.swagger.io
//     BasePath: /v2
//     Version: 1.0.0
//
//     Consumes:
//       - application/json
//
//     Produces:
//       - application/json
//
// swagger:meta
package petstore

Legal keywords. All meta single-line keywords (schemes, version, host, basePath, license, contact) plus the meta-scope body keywords (consumes, produces, security, securityDefinitions, extensions, infoExtensions, tos, externalDocs).

Full example. fixtures/goparsing/spec/api.go.


swagger:model

What it does. Declares a Go type as a published model. The scanner walks the type, emits a schema into the spec’s definitions map, and resolves cross-references between models.

Where it goes. On a type declaration (type T struct { … }, type T int, type T = Other, …).

Argument shape. Optional IDENT β€” the name the model takes in definitions. Default: the Go type’s name.

Sample.

// Pet is the petstore's primary entity.
//
// swagger:model
type Pet struct {
	// ID is the unique identifier.
	ID int64 `json:"id"`

	// Name is the pet's display name.
	Name string `json:"name"`

	// Tags categorise the pet.
	Tags []string `json:"tags,omitempty"`
}

With a name override:

// swagger:model PetWithExtras
type DetailedPet struct { … }

The type is published as #/definitions/PetWithExtras.

Legal keywords. All schema keywords plus the length / array / numeric validations on field doc comments.

Full example. fixtures/enhancements/named-struct-tags-ref/types.go.


swagger:strfmt

What it does. Marks a named type as a custom string format. Wherever the type appears as a field, the emitted schema is {type: string, format: <name>}. Useful for UUID, Email, URL-style types that have a Go type but should serialise as a JSON string with a known format.

Where it goes. On a type declaration whose underlying form is a string-marshalable type (typically implementing encoding.TextMarshaler or encoding.TextUnmarshaler).

Argument shape. Required IDENT β€” the format name (uuid, email, mac, etc.).

Sample.

// MAC is a hardware address rendered as a colon-separated hex string.
//
// swagger:strfmt mac
type MAC string

func (m MAC) MarshalText() ([]byte, error) { return []byte(m), nil }
func (m *MAC) UnmarshalText(b []byte) error { *m = MAC(b); return nil }

A field typed MAC emits as {type: string, format: mac}. The underlying MAC type does NOT appear as a top-level model definition (strfmt-tagged structs are replaced by their format at every reference).

Legal keywords. None at the type level beyond swagger:strfmt itself; the format name is the entire surface.

Full example. fixtures/enhancements/text-marshal/types.go.


swagger:enum

What it does. Marks a string-typed (or integer-typed) named type as an enum and collects the type’s const declarations. The values are applied inline on each model field that references the type: the property gets an enum array plus an x-go-enum-desc extension carrying the per-value godoc descriptions in <value> <doc-text> shape. The enum type itself is not emitted as a standalone definition β€” the values travel with each referencing property.

(Edge case: if swagger:enum names a type for which no matching const values are found, the enum semantics are dropped and the type falls through to ordinary type resolution β€” typically a plain definition referenced by $ref, with no enum array.)

Where it goes. On a named type declaration. The type’s const values are discovered via Go’s type-system traversal; they do not need to live in the same file. The values surface only when a model reaches the enum type through a field.

Argument shape. IDENT naming the type whose const values to collect (its own name).

Sample.

// Priority is the urgency level on a task.
//
// swagger:enum Priority
type Priority string

const (
	// PriorityLow is for tasks that can wait.
	PriorityLow Priority = "low"

	// PriorityMedium is the default.
	PriorityMedium Priority = "medium"

	// PriorityHigh is for tasks that must run soon.
	PriorityHigh Priority = "high"
)

// Task references Priority, which is what makes the enum reachable.
//
// swagger:model
type Task struct {
	Priority Priority `json:"priority"`
}

Produces (extract) β€” the values land on Task’s priority property, not on a Priority definition:

{
  "Task": {
    "type": "object",
    "properties": {
      "priority": {
        "type": "string",
        "enum": ["low", "medium", "high"],
        "x-go-enum-desc": "low PriorityLow is for tasks that can wait.\nmedium PriorityMedium is the default.\nhigh PriorityHigh is for tasks that must run soon."
      }
    }
  }
}

Legal keywords. Schema-context keywords. The enum: keyword can ALSO be used inline on the type doc to force a value set; when present, it overrides the const-derived values and the x-go-enum-desc is recomputed (or dropped) accordingly.

Full example. fixtures/enhancements/enum-overrides/types.go.


swagger:allOf

What it does. Marks a struct as participating in an allOf composition. The struct’s fields plus any embedded swagger:model-tagged base produce an allOf: [$ref base, {inline fields}] schema. The companion convention is to embed the base type as an anonymous field with this annotation on the embedding’s doc comment (or on the embedded type itself).

Where it goes. On a struct field that embeds another type, or on a struct type that has at least one embedded base.

Argument shape. No args.

Sample.

// Animal is the abstract base.
//
// swagger:model
type Animal struct {
	Kind string `json:"kind"`
}

// Dog is an Animal with a breed.
//
// swagger:model
type Dog struct {
	// swagger:allOf
	Animal

	Breed string `json:"breed"`
}

Produces:

"Dog": {
  "allOf": [
    {"$ref": "#/definitions/Animal"},
    {
      "type": "object",
      "properties": {
        "breed": {"type": "string", "x-go-name": "Breed"}
      }
    }
  ]
}

Legal keywords. Schema-context keywords on the inline-object member (the second allOf element).

Full example. fixtures/enhancements/allof-edges/types.go.


swagger:alias

What it does. Marks a Go alias declaration (type T = Other) as a model that should publish as a $ref to Other’s definition rather than as a duplicate of Other’s schema.

The scanner also honours RefAliases and TransparentAliases top-level options, which can globally enable alias-as-ref behaviour without per-decl annotation. swagger:alias is the per-decl override for cases where the global mode isn’t appropriate.

Where it goes. On a type alias declaration.

Argument shape. Optional IDENT β€” the published name. Default: the alias’s Go name.

Sample.

// Timestamp aliases time.Time. The published model carries
// format: date-time via the time.Time β†’ strfmt resolution.
//
// swagger:alias
type Timestamp = time.Time

Without the annotation (and without global RefAliases), the alias either expands the target’s full schema or is silently ignored depending on context.

Legal keywords. Schema-context keywords.

Full example. fixtures/enhancements/ref-alias-chain/types.go.


swagger:route

What it does. Declares an HTTP route + operation in one annotation. The header line carries the method, path, optional tags, and the operation ID; the comment body carries the operation’s metadata (consumes / produces / schemes / security / parameters / responses / extensions).

This is the terser of the two operation-declaration annotations. Most go-swagger projects use swagger:route for hand-written operations.

Where it goes. On a function or variable declaration whose doc comment carries the annotation. The Go entity itself doesn’t have to be a handler β€” the annotation publishes a path/operation independent of the carrier.

Argument shape. Header line:

swagger:route <METHOD> <path> [tag1 tag2 …] <operationID>
  • <METHOD> β€” GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS. Case insensitive.
  • <path> β€” starts with /. Supports path-parameter braces: /items/{id}.
  • [tag1 tag2 …] β€” optional whitespace-separated list of tags. At least two characters each.
  • <operationID> β€” the unique operation identifier.

A godoc-style identifier may precede the annotation on the same comment line:

// ListPets swagger:route GET /pets pets users listPets

That leading identifier is recognised as a godoc convention and is not part of the annotation surface.

Sample.

// ListPets swagger:route GET /pets pets users listPets
//
// List pets filtered by some parameters.
//
//     Consumes:
//       - application/json
//
//     Produces:
//       - application/json
//
//     Schemes: http, https
//
//     Security:
//       api_key:
//       oauth: read, write
//
//     Parameters:
//       + name: limit
//         in: query
//         type: integer
//         minimum: 1
//         maximum: 100
//
//     Responses:
//       200: body:[]Pet the pet list
//       default: response:genericError
func ListPets() {}

Legal keywords. All body keywords legal in route context (consumes, produces, schemes, security, parameters, responses, extensions) plus inline deprecated:.

The Parameters: and Responses: sub-languages are documented in sub-languages.md Β§parameters and sub-languages.md Β§responses.

Full example. fixtures/enhancements/routes-full-petstore-shape/handlers.go.


swagger:operation

What it does. Same payload as swagger:route but with a different body shape: instead of the structured Parameters: / Responses: keyword surface, swagger:operation’s body is a single YAML document spelling out the OpenAPI operation object directly.

Use swagger:operation when you want to author the operation in YAML (closer to the OpenAPI spec text) or when the operation has shapes the keyword surface doesn’t cover.

Where it goes. Same as swagger:route β€” function or variable doc comment.

Argument shape. Same header shape as swagger:route:

swagger:operation <METHOD> <path> [tag1 tag2 …] <operationID>

Sample.

// swagger:operation GET /items/{id} items getItem
//
// ---
// summary: Get item by ID
// parameters:
//   - name: id
//     in: path
//     required: true
//     type: integer
// responses:
//   '200':
//     description: the requested item
//     schema:
//       $ref: '#/definitions/Item'
//   default:
//     $ref: '#/responses/genericError'
func GetItem() {}

The --- delimits the YAML body; everything between the fences is parsed as an OpenAPI 2.0 operation object.

Legal keywords. None inside the YAML body (it’s structurally YAML, not the keyword grammar). The header line is the entire annotation surface.

Full example. fixtures/enhancements/parameters-map-postdecl/api.go.


swagger:parameters

What it does. Declares a Go struct as the parameters set for one or more operations. Each field of the struct becomes one parameter on the named operation(s). The field’s doc comment carries the parameter’s in:, required:, validation, and description.

Where it goes. On a struct declaration.

Argument shape. Required IDENTs β€” the operation IDs this parameters set applies to. At least one. The same operation ID may appear in multiple swagger:parameters annotations to compose a parameter set from several structs.

Sample.

// ListItemsParams declares pagination + filter parameters for the
// listItems operation.
//
// swagger:parameters listItems
type ListItemsParams struct {
	// Offset is the page offset.
	//
	// in: query
	// minimum: 0
	// default: 0
	Offset int `json:"offset"`

	// Limit is the page size.
	//
	// in: query
	// minimum: 1
	// maximum: 100
	// default: 20
	Limit int `json:"limit"`

	// Tag is the filter tag.
	//
	// in: query
	// required: false
	Tag string `json:"tag,omitempty"`
}

Legal keywords on fields. param-context keywords (in, required, the numeric / length / format validations, default, example, enum, allowEmptyValue, collectionFormat).

Full example. fixtures/enhancements/simple-schema-violation/api.go.


swagger:response

What it does. Declares a Go struct as a named response object, emitted into the spec’s top-level responses map. Routes / operations reference it by name via the response sub-language (Responses: body in swagger:route, or the YAML $ref form in swagger:operation).

The struct’s fields contribute the response shape:

  • A field named Body (or carrying in: body) becomes the response body schema.
  • Other fields carrying in: header become response headers.

Where it goes. On a struct declaration.

Argument shape. Optional IDENT β€” the published response name. Default: the Go type’s name.

Sample.

// GenericError is the catch-all error response.
//
// swagger:response genericError
type GenericError struct {
	// in: body
	Body struct {
		// Message is the human-readable error message.
		Message string `json:"message"`

		// Code is the machine-readable error category.
		Code string `json:"code,omitempty"`
	}

	// X-Request-ID echoes the request correlation header.
	//
	// in: header
	XRequestID string `json:"X-Request-ID"`
}

Routes can then reference it via response:genericError in their Responses: body.

Legal keywords on body field. Schema-context keywords. Legal keywords on header field. Header-context keywords β€” numeric / length / format validations, pattern, enum, default, example, collectionFormat. required: is silently dropped on headers (the OAS v2 Header object does not carry a required field).

Full example. fixtures/enhancements/routes-full-petstore-shape/handlers.go.


swagger:ignore

What it does. Excludes the surrounding declaration from the generated spec. The scanner sees the decl and the doc, classifies it, then drops it.

Where it goes. On a type declaration to exclude the whole type, or on a struct field doc to exclude that one field.

Argument shape. No args.

Sample (type):

// Internal is not exposed.
//
// swagger:ignore
type Internal struct {
	SecretField string
}

Sample (field):

type User struct {
	Name string `json:"name"`

	// PasswordHash is internal.
	//
	// swagger:ignore
	PasswordHash string `json:"-"`
}

Interaction: when swagger:ignore appears AFTER another classifier on the same comment block (e.g., swagger:model first, then swagger:ignore), the first annotation wins and the ignore is silently overridden. Place swagger:ignore first if you genuinely want the decl excluded.

Full example. fixtures/enhancements/top-level-kinds/types.go.


swagger:name

What it does. Overrides the JSON property name that a struct field or interface method renders as. By default the scanner derives names from json:"…" struct tags (or the Go identifier for fields / methods with no tag); swagger:name is the per-field override when the tag-based shape isn’t appropriate β€” typically on interface methods, which cannot carry struct tags.

Where it goes. On a struct field doc OR an interface method doc.

Argument shape. Required IDENT β€” the JSON property name to use.

Sample (interface method):

// UserProfile is the user's profile interface.
//
// swagger:model
type UserProfile interface {
	// ID is the user identifier.
	ID() string

	// FullName is the user's display name.
	//
	// swagger:name fullName
	FullName() string
}

Without swagger:name, the method FullName() would publish as property FullName (PascalCase). The annotation renames it to fullName.

Legal keywords. None β€” the override name is the entire surface.

Full example. fixtures/enhancements/interface-methods/types.go.


swagger:type

What it does. Overrides the inferred Swagger type for a named type or struct field. The Go type’s natural inference (struct β†’ object, named string β†’ string, time.Time β†’ date-time, …) is replaced with the annotation’s argument.

Where it goes. On a type declaration OR a struct field doc.

Argument shape. Required IDENT β€” the Swagger type name (string, integer, number, boolean, array, object).

Sample (type-level override):

// ULID is a Crockford-base32 unique identifier rendered as a string.
//
// swagger:type string
type ULID [16]byte

Fields typed ULID emit as {type: string} regardless of the underlying [16]byte shape.

Sample (field-level override):

type Document struct {
	// Body is an opaque payload published as a string blob.
	//
	// swagger:type string
	Body json.RawMessage `json:"body"`
}

Interaction: when combined with swagger:strfmt on the same type, both apply β€” the strfmt format goes onto the published {type: string, format: …}.

Full example. fixtures/enhancements/named-struct-tags-ref/types.go.


swagger:file

What it does. Marks a parameter or response body as a binary file ({type: file}). The scanner emits the file-type marker without further introspection of the Go type.

Where it goes. On a struct field doc inside a swagger:parameters (multipart file upload) or swagger:response (file download) struct.

Argument shape. No args.

Sample.

// UploadParams declares a multipart file upload.
//
// swagger:parameters uploadFile
type UploadParams struct {
	// File is the uploaded asset.
	//
	// in: formData
	// swagger:file
	File io.ReadCloser `json:"file"`
}

Legal keywords. Standard parameter / response keywords; the file marker stacks with in: and other parameter shape keywords.


swagger:default

What it does. Marks the surrounding declaration as the spec’s default value for the corresponding shape. Used in narrow contexts where the scanner expects an explicit anchor for a default.

This annotation is value-only β€” there’s no exported entity it publishes; it’s a classifier hint the scanner consumes during discovery.

Where it goes. On a value declaration (var, const) or a struct field.

Argument shape. No args.

Sample.

// DefaultLimit is the default page size used wherever Limit is not
// supplied by the caller.
//
// swagger:default
var DefaultLimit = 20

This annotation has a narrow surface and is not commonly authored directly. Most spec defaults are carried by the default: keyword on the relevant field.


Annotation Γ— keyword compatibility matrix

A quick orientation for which annotations can carry which keyword families. See keywords.md for the per-keyword contracts.

AnnotationNumeric/length validationsSchema decoratorsin:Meta keywordsParameters: bodyResponses: bodyYAML body
swagger:metaβ€”β€”β€”βœ…β€”β€”βœ… (security defs, extensions)
swagger:modelβœ… (on fields)βœ…β€”β€”β€”β€”β€”
swagger:strfmtβ€”β€”β€”β€”β€”β€”β€”
swagger:enumβ€”(enum keyword via const)β€”β€”β€”β€”β€”
swagger:allOfβœ… (on member fields)βœ…β€”β€”β€”β€”β€”
swagger:aliasβ€”β€”β€”β€”β€”β€”β€”
swagger:routeβ€”(deprecated only)β€”(schemes/consumes/produces/security)βœ…βœ…(extensions)
swagger:operationβ€”β€”β€”β€”β€”β€”βœ… (full op as YAML)
swagger:parametersβœ… (on fields)βœ… (on fields)βœ…β€”β€”β€”β€”
swagger:responseβœ… (on header fields)βœ… (on body field)βœ… (body/header)β€”β€”β€”β€”
swagger:ignoreβ€”β€”β€”β€”β€”β€”β€”
swagger:nameβ€”β€”β€”β€”β€”β€”β€”
swagger:typeβ€”β€”β€”β€”β€”β€”β€”
swagger:fileβ€”β€”β€”β€”β€”β€”β€”
swagger:defaultβ€”β€”β€”β€”β€”β€”β€”

A blank cell means the keyword family is not legal in that context; attempting to use it emits CodeContextInvalid and the keyword is dropped.

Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Keyword reference

This document catalogs the keyword: value forms recognised inside annotation blocks. The keywords come in two flavours:

  • Inline keywords β€” one line, keyword: value shape, with the value classified by a value shape (number, integer, boolean, string, …).
  • Body keywords β€” a header line followed by indented continuation lines. The body’s interpretation depends on the keyword (a flat token list for Consumes:, a YAML map for SecurityDefinitions:, a per-line sub-language for Parameters: / Responses: on swagger:route).

The reader-friendly orientation is in annotations.md (which annotation accepts which keywords, with examples); this file is the per-keyword reference card. Implementers wanting the formal productions should read grammar.md.


Table of contents


Reading the tables

Each keyword entry carries:

  • Name β€” canonical spelling. This is what Property.Keyword.Name compares equal to. Comparisons are case-insensitive on the canonical spelling and on every alias.
  • Aliases β€” alternate spellings the lexer accepts. They map to the canonical name at lex time; consumers never see alias values.
  • Value shape β€” the lexical category of the value. See value shapes for what each one means and how it surfaces to consumers.
  • Contexts β€” the family-level scopes where the keyword is legal. Using a keyword outside its legal contexts emits a CodeContextInvalid diagnostic and the keyword is dropped from the affected block.

Value shapes

The grammar’s lexer classifies every value into one of these shapes. The shape determines which Walker callback fires for the property and which field of Property.Typed carries the parsed value.

ShapeTyped payloadExample value forms
numberfloat64 (with optional </<=/>/>=/= prefix)5, 1.5, <10, >=0, =42
integerint645, 100
booleanbooltrue, false, 1, 0
stringraw string^[a-z]+$, date-time, multipart/form-data
comma-listraw string; split on , by Property.AsList()http, https, a,b,c
enum-optiontyped string (closed-vocab match)csv, pipes for collectionFormat:
raw-blockaccumulated body lines on Property.Bodymulti-line YAML, indented token lists
raw-valuethe verbatim post-colon text on Property.Value42, "orange", [1, 2, 3]

When typing fails (e.g. maximum: notanumber) the lexer emits a CodeInvalidNumber / CodeInvalidInteger / CodeInvalidBoolean diagnostic and the property reaches the Walker with a zero-value payload. Consumers gate on Property.IsTyped() to skip malformed-typed values; the corresponding builder field stays unwritten.

Annotation contexts

The closed set of contexts a keyword can legally appear in. A keyword table entry’s Contexts field combines these:

ContextMeaning
paramParameter doc on a swagger:parameters struct field, or a + name: chunk inside swagger:route Parameters:
headerHeader field on a swagger:response struct
schemaTop-level model or struct field on a swagger:model
itemsItems-level (array element) validation on either parameter or schema
routeRoute-level metadata under swagger:route
operationInline operation metadata under swagger:operation
metaPackage-level metadata under swagger:meta
responseResponse-level decorations

Summary table

The full keyword surface, in the order the keyword table declares them. Detailed entries follow this table.

KeywordAliasesShapeContexts
maximummaxnumberparam, header, schema, items
minimumminnumberparam, header, schema, items
multipleOfmultiple of, multiple-ofnumberparam, header, schema, items
maxLengthmax length, max-length, maxLen, max len, max-len, maximum length, maximum-length, maximumLength, maximum len, maximum-lenintegerparam, header, schema, items
minLengthmin length, min-length, minLen, min len, min-len, minimum length, minimum-length, minimumLength, minimum len, minimum-lenintegerparam, header, schema, items
patternβ€”stringparam, header, schema, items
maxItemsmax items, max-items, max.items, maximum items, maximum-items, maximumItemsintegerparam, header, schema, items
minItemsmin items, min-items, min.items, minimum items, minimum-items, minimumItemsintegerparam, header, schema, items
uniqueβ€”booleanparam, header, schema, items
collectionFormatcollection format, collection-formatenum-option (csv, ssv, tsv, pipes, multi)param, header, items
defaultβ€”raw-valueparam, header, schema, items
exampleβ€”raw-valueparam, header, schema, items
enumβ€”raw-valueparam, header, schema, items
requiredβ€”booleanparam, schema
readOnlyread only, read-onlybooleanschema
discriminatorβ€”booleanschema
deprecatedβ€”booleanoperation, route, schema
inβ€”enum-option (query, path, header, body, formData)param
schemesβ€”raw-block (token list)meta, route, operation
versionβ€”stringmeta
hostβ€”stringmeta
basePathbase path, base-pathstringmeta
licenseβ€”stringmeta
contactcontact info, contact-infostringmeta
consumesβ€”raw-block (token list)meta, route, operation
producesβ€”raw-block (token list)meta, route, operation
securityβ€”raw-block (security requirements)meta, route, operation
securityDefinitionssecurity definitions, security-definitionsraw-block (YAML map)meta
responsesβ€”raw-block (response sub-language)route, operation
parametersβ€”raw-block (parameter chunk sub-language)route, operation
extensionsβ€”raw-block (YAML map of x-* entries)meta, route, operation, schema, param, header
infoExtensionsinfo extensions, info-extensionsraw-block (YAML map of x-* entries)meta
tosterms of service, terms-of-service, termsOfServiceraw-block (prose paragraph)meta
externalDocsexternal docs, external-docsraw-block (YAML map)meta, route, operation, schema

Numeric validations

Apply to numeric schema types (integer, number). On a typed schema with a non-numeric type, these keywords emit CodeShapeMismatch and drop. On a typeless schema (no type: declared upstream), they apply best-effort.

maximum

Upper bound on a numeric value. Alias: max.

The value may carry a leading comparison operator that becomes the exclusive/inclusive bound:

  • maximum: 10 β€” inclusive (≀ 10).
  • maximum: <10 β€” exclusive (< 10).
  • maximum: <=10 β€” inclusive (same as no prefix).
  • maximum: =10 β€” inclusive.

Maps to schema.maximum and schema.exclusiveMaximum.

// Limit is the cap on items per page.
//
// maximum: 100
// minimum: 1
type Limit int

β€” from fixtures/enhancements/... (any numeric-validation fixture).

minimum

Lower bound on a numeric value. Alias: min. Same operator-prefix shape as maximum. Maps to schema.minimum and schema.exclusiveMinimum.

multipleOf

Divisibility constraint. The value must be a positive number. Aliases: multiple of, multiple-of. Maps to schema.multipleOf.

// AllowedStep enforces increments of 5.
//
// multipleOf: 5
type AllowedStep int

Length / array validations

maxLength / minLength apply only to string-typed schemas; maxItems / minItems apply only to array-typed schemas. Using the wrong pairing emits CodeShapeMismatch and drops the keyword.

maxLength

Maximum string length. Many aliases for ergonomic spelling: max length, max-length, maxLen, max len, max-len, maximum length, maximum-length, maximumLength, maximum len, maximum-len. Maps to schema.maxLength.

minLength

Minimum string length. Same alias set as maxLength with min in place of max. Maps to schema.minLength.

maxItems

Maximum array length. Aliases: max items, max-items, max.items, maximum items, maximum-items, maximumItems. Maps to schema.maxItems.

minItems

Minimum array length. Same alias shape as maxItems with min in place of max. Maps to schema.minItems.

// Tags is a non-empty, bounded list.
//
// minItems: 1
// maxItems: 20
// unique: true
type Tags []string

Format validations

pattern

A regex constraint on a string value. The pattern is preserved verbatim on schema.pattern. The grammar runs a best-effort RE2 compile (Go’s regex engine) on the value; if it fails, a CodeInvalidAnnotation diagnostic surfaces with the compile error. The value still lands on the schema β€” downstream tools may use JSON Schema’s wider regex dialect.

// Slug is a URL-friendly identifier.
//
// pattern: ^[a-z0-9-]+$
type Slug string

unique

Marks an array-typed schema as set-valued (no duplicates). Boolean. Maps to schema.uniqueItems.

collectionFormat

How an array value is serialised on the wire. Closed-vocab:

  • csv β€” comma-separated (default).
  • ssv β€” space-separated.
  • tsv β€” tab-separated.
  • pipes β€” pipe-separated.
  • multi β€” repeated ?key=val&key=val2 (query params only).

Aliases: collection format, collection-format. Maps to parameter.collectionFormat / items.collectionFormat. Schema-level contexts ignore this keyword (it’s a SimpleSchema concept; schemas serialise via application/json).

When the source value doesn’t match the closed vocab, the raw value is preserved verbatim on the parameter (matches the original behaviour where pipe as a typo for pipes round-trips).

// Tags is the form-data array of label tokens.
//
// in: query
// type: array
// collectionFormat: csv
// items.type: string
type TagsParam []string

Schema decorators

default

Default value for a schema or simple-schema field. Raw-value shape β€” the post-colon text is captured verbatim and coerced against the resolved schema type at write time (ParseDefault / CoerceValue).

Multi-line bodies are accepted for complex literals:

// Limits is the throughput envelope.
//
// default:
//   {
//     "rps": 100,
//     "burst": 200
//   }
type Limits struct { ... }

Single-line form for primitives:

// Page is the page number.
//
// in: query
// type: integer
// default: 1
type PageParam int

example

An example value for the schema, surfaced in tooling. Same raw-value shape as default. Maps to schema.example (or parameter.example for SimpleSchema parameters).

enum

A closed set of allowed values. Three accepted surface forms:

  • Comma list: enum: red, green, blue β€” split on , and trimmed.
  • JSON array: enum: ["red", "green", "blue"] β€” parsed via YAML/JSON.
  • Multi-line list with - markers:
    enum:
      - red
      - green
      - blue

Each element is coerced against the resolved schema type. Maps to schema.enum.

For string-typed enums driven by Go const declarations the swagger:enum annotation is the more idiomatic surface β€” it picks up the constant names AND their godoc descriptions and produces an x-go-enum-desc extension alongside the enum values. The enum: keyword is the manual override.

required

Marks a field as required. Boolean.

  • On a swagger:model struct field: adds the field’s name to the enclosing schema’s required array.
  • On a swagger:parameters struct field: sets parameter.required.
  • On a swagger:response header: not applicable; the keyword is silently dropped (response headers don’t carry required).

readOnly

Marks a schema property as read-only. Aliases: read only, read-only. Maps to schema.readOnly.

Schema-only β€” emitting readOnly: inside a SimpleSchema context (non-body parameter, response header) emits CodeUnsupportedInSimpleSchema and drops the keyword.

discriminator

Marks the property as the discriminator for an allOf polymorphic schema. Boolean. Writes the property’s name onto the enclosing schema’s discriminator field. Schema-only.

deprecated

Marks the carrying entity as deprecated. Boolean. Legal on operations (operation.deprecated), routes (operation.deprecated on the synthesised op), and schemas (some downstream tools render this).


Parameter location

in

Where the parameter value comes from. Closed-vocab:

  • query β€” query string parameter.
  • path β€” path-parameter substitution.
  • header β€” request header.
  • body β€” request body (JSON, etc.).
  • formData β€” form-data body field (note: form accepted as an alias inside swagger:route Parameters: chunks; the lexer normalises to formData at the canonical surface).

A non-matching value emits a context-invalid diagnostic; the parameter loses its in and may end up incorrectly classified downstream.

// PageParams declares pagination query parameters.
//
// swagger:parameters listItems
type PageParams struct {
	// in: query
	// minimum: 0
	Offset int `json:"offset"`

	// in: query
	// minimum: 1
	// maximum: 100
	// default: 20
	Limit int `json:"limit"`
}

Meta single-line keywords

Single-line keywords under swagger:meta. Values are taken as-is from the post-colon string.

schemes

Accepted URL schemes. Flexible list β€” all forms below produce the same ["http", "https"] output:

Schemes: http, https
Schemes:
  - http
  - https
Schemes: http
  - https

Maps to spec.schemes. See sub-languages.md Β§flex-lists for the unified rule.

version

API version string. Maps to info.version.

host

Default host. Defaults to localhost when empty. Maps to spec.host.

basePath

URL base path. Maps to spec.basePath. Aliases: base path, base-path.

license

License declaration. Two forms accepted:

License: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0.html

…where the trailing token starting with a URL scheme becomes license.url and the prefix becomes license.name. A bare name with no URL is accepted too.

contact

Contact declaration. Author writes a Name <email> URL triple, in any order. The grammar recognises:

  • Name <email@example.com> β€” Go’s net/mail.ParseAddress form.
  • Name <email@example.com> http://example.com β€” same + trailing URL.
  • Just a URL, no name.

Aliases: contact info, contact-info. Maps to info.contact.


Body keywords

Body keywords have a header line ending in : and indented continuation lines. The body’s structure depends on the keyword. See sub-languages.md for the full sub-language specifications; this section covers the keyword shape.

consumes / produces

Media-type lists. Same flex-list rule as schemes: β€” comma inline, multi-line bare, YAML - markers, or any combination. Maps to consumes / produces on the surrounding scope (spec, operation).

Consumes:
  - application/json
  - application/xml

Produces: application/json

security

Security-requirements list. Each line is one requirement of shape schemeName: scope1, scope2. An empty scope list (schemeName:) means “no scopes required, but the scheme must be active.”

Security:
  api_key:
  oauth2: read, write

Maps to security (array of single-key maps).

securityDefinitions

YAML map declaring security schemes. The body is parsed as YAML directly into the spec.securityDefinitions shape β€” see OAS v2 Β§5.2.16.

SecurityDefinitions:
  api_key:
    type: apiKey
    in: header
    name: X-API-Key
  oauth2:
    type: oauth2
    flow: implicit
    authorizationUrl: https://example.com/auth
    scopes:
      read: read access
      write: write access

Aliases: security definitions, security-definitions. Meta-only.

responses

Per-route / per-operation response declarations. Each line is one response in the form <code>: <tokens>. See sub-languages.md Β§responses for the full per-line grammar.

Responses:
  200: body:User the requested user
  404: description: not found
  default: response:genericError

parameters

Per-route / per-operation parameter declarations. Body is a sequence of + name: chunks (the + is the chunk-start sigil; - is accepted as an alias). See sub-languages.md Β§parameters for the full per-chunk grammar.

Parameters:
  + name: id
    in: path
    type: integer
    required: true
  + name: limit
    in: query
    type: integer
    default: 20
    minimum: 1
    maximum: 100

extensions / infoExtensions

Vendor extension declarations as a YAML map. Keys must start with x- or X-; non-x-* keys emit CodeInvalidAnnotation and drop.

  • extensions: lands the entries on the surrounding scope (spec.extensions, operation.extensions, schema.extensions, …).
  • infoExtensions: is meta-only; entries land on info.extensions.
Extensions:
  x-internal-id: 42
  x-feature-flags:
    - alpha
    - beta
  x-nested:
    enabled: true
    rate: 0.5

Aliases: info extensions, info-extensions (for infoExtensions).

tos

Terms-of-service prose paragraph. Multi-line body is joined with \n after dropping whitespace-only lines. Aliases: terms of service, terms-of-service, termsOfService. Maps to info.termsOfService. Meta-only.

externalDocs

External documentation pointer as a YAML map with description and url keys. Aliases: external docs, external-docs. Multi-context (meta, route, operation, schema).

ExternalDocs:
  description: Reference documentation
  url: https://example.com/docs

Cross-keyword interactions

A handful of keyword interactions are worth flagging:

  • default + example + enum on the same field: all three may co-occur. The values are coerced against the resolved schema type independently. If enum is declared and default is not a member of it, no diagnostic fires today β€” downstream JSON Schema validation catches it.
  • type + numeric validations + format on a body parameter: the schema dispatcher’s checkShape gates numeric / length validations against the resolved type. format is type-blind (any format string lands).
  • required on a $ref'd field: writes to the enclosing schema’s required array (the standard JSON-Schema-draft-4 shape). If the field has sibling overrides, the $ref rewrites into an allOf compound β€” see grammar.md Β§refoverride.
Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Sub-languages

The annotation body grammar is not a single language β€” it’s a top-level keyword grammar that embeds several smaller languages inside specific body keywords. Each embedded language has its own shape rules.

This document catalogs the embedded languages and how they fit together. For the per-keyword surface, see keywords.md; for the formal grammar that hosts them, see grammar.md.


Table of contents


Prose classification

Comment lines that don’t match any keyword head OR YAML fence OR annotation marker are classified as prose β€” free-form text. The lexer splits prose into two token kinds:

  • TITLE β€” the first paragraph of prose, expected to fit on a short summary line.
  • DESC β€” every prose paragraph after the title (or following a blank line within the first paragraph).

Three heuristics decide the title-vs-desc boundary, evaluated in order. The first to fire wins:

  1. Blank-line split. Any blank line inside the prose run ends the title paragraph and starts the description.
  2. Closing punctuation. If the first prose line ends with Unicode punctuation (., ?, !, …, :, …), the title is just that one line; everything after becomes description.
  3. Markdown ATX heading. If the first prose line matches markdown’s # Heading shape, the # markers are stripped and the remaining text becomes the title.

When no heuristic fires, the entire prose run is title (the schema builder later collapses to a description-only schema when appropriate).

Package <name> prefix strip

The swagger:meta annotation’s title comes from the package doc comment, which by Go convention starts with Package <name>. The spec builder strips that prefix before publishing:

// Package petstore Petstore API.
//
// Description of the petstore service.
//
// swagger:meta
package petstore

Produces info.title = "Petstore API." (the Package petstore prefix stripped) and info.description = "Description of the petstore service."

Only the capital-P Package form is recognised β€” author prose like “package this carefully” is not chopped.

Comment-marker noise stripping

Block-comment routes (/* swagger:route … */) typically carry indented continuation lines:

/* swagger:route POST /pets pets createPet

	Create a pet based on the parameters.

	Consumes:
		- application/json
*/
func CreatePet() {}

The lexer strips the leading whitespace (\t, *, /, |) per line via trimContentPrefix before classification.

Markdown semantics that survive

  • Dash lists in descriptions are preserved verbatim. A line starting with - foo lands in the description as "- foo" (not "foo").
  • --- lines open a YAML fence β€” see YAML extensions below.

Flex-list

Body keywords that publish a flat list of tokens (schemes:, consumes:, produces:) accept multiple surface forms uniformly. The unified reader is Property.AsList().

Accepted forms

# Inline, comma-separated
Schemes: http, https

# Multi-line, indented bare lines
Schemes:
  http
  https

# Multi-line, YAML-style dash markers
Schemes:
  - http
  - https

# Inline value plus indented continuation
Schemes: http
  - https

# All combinations of the above
Consumes: application/json, application/xml
  - application/protobuf

All five forms produce the same ["http", "https"] (or ["application/json", "application/xml", "application/protobuf"]) output.

Algorithm

For each input line β€” Property.Value first (if non-empty), then each line of Property.Body:

  1. Trim surrounding whitespace.
  2. Drop a leading - YAML marker if present.
  3. Re-trim whitespace.
  4. Comma-split.
  5. Trim each token; drop empties.

Aggregate into a single slice in source order.

What flex-list does NOT touch

  • Enum values (enum: ...) β€” their elements may themselves be complex (JSON arrays, quoted strings with commas). enum: keeps its raw-value path; the value coercion layer handles array / comma-list / multi-line shapes per the schema type.
  • Parameters chunks β€” the + name: chunk grammar is not a simple token list; see Β§parameters.
  • YAML structural bodies β€” securityDefinitions:, extensions:, infoExtensions: parse the body as YAML directly; their structure isn’t a flat list. See Β§yaml-extensions.

Parameters

The Parameters: body in swagger:route and swagger:operation carries a sequence of parameter declarations separated by + name: chunks (the + is the chunk-start sigil; - is accepted as an alias for forward compatibility with proper YAML).

Chunk shape

Parameters:
  + name: id
    in: path
    type: integer
    description: the item identifier
    required: true
  + name: limit
    in: query
    type: integer
    minimum: 1
    maximum: 100
    default: 20
  + name: body
    in: body
    type: User
    required: true

Per-chunk fields

The fields are classified into head fields (consumed by the orchestrator to populate the *spec.Parameter shell) and validation fields (lowered to grammar properties and dispatched through the standard validation pipeline).

Head fields:

FieldLands onNotes
name:parameter.nameRequired. Identifies the parameter.
in:parameter.inOne of path / query / header / body / formData. form accepted as an alias for formData.
type:parameter.type (for SimpleSchema) or determines the body $refFor non-body: one of string / integer / number / boolean / array. For body: a Go ident referring to a swagger:model-declared type, optionally with [] array prefixes ([][]Pet). bool accepted as an alias for boolean.
format:parameter.format or parameter.schema.formatFree-form string. Applied after validation dispatch so it doesn’t interfere with default/example coercion.
description:parameter.descriptionFree-form prose.
required:parameter.requiredBoolean.
allowempty: / allowemptyvalue:parameter.allowEmptyValueBoolean.

Validation fields: any other recognised keyword β€” min, max, minLength, maxLength, minItems, maxItems, pattern, unique, collectionFormat, default, example, enum. These are looked up via grammar.Lookup (which accepts canonical names + aliases) and dispatched through the standard handlers seam.

Empty chunks and unknown keys

  • A bare + (or -) sigil with no follow-up content emits a CodeInvalidAnnotation diagnostic and is dropped. The legacy parser silently emitted an empty Parameter{} object β€” current behaviour rejects it.
  • Unknown keys (typos like defualt:) emit CodeInvalidAnnotation and drop. The legacy parser silently discarded them.

Body parameters

When in: body, the orchestrator looks up type: as either:

  • A primitive (string, integer, number, boolean, array, object) β€” emits a typed schema with the primitive on parameter.schema.type.
  • A Go ident β€” emits a $ref to #/definitions/<Ident>. With [] prefixes, wraps the ref in nested array schemas.

Validation properties on a body chunk apply to the schema, gated by the schema’s resolved type via checkShape. A min: 0 on a body chunk with type: Pet (object) emits CodeShapeMismatch and drops; a min: 0 with type: integer lands on the schema’s minimum.

Validation on SimpleSchema (non-body) parameters

For in: other than body, validation properties apply directly to the parameter (not to a sub-schema). Type-gating still applies: minLength on type: integer emits a diagnostic and drops.


Responses

The Responses: body in swagger:route carries one response declaration per line. Each line has the shape:

<code>: <token>*

where <code> is default (case-insensitive) or a decimal HTTP status code, and <token> is either a tag:value form or an untagged token.

Recognised tags

TagValue shapeLands on
body:Go ident with optional [] prefixes (body:[]Pet)A $ref to #/definitions/<name>, array-wrapped per [] count
response:Go ident referring to a swagger:response-declared typeA $ref to #/responses/<name>
description:Free-form prose (rest of line)response.description

Untagged token rules

  • The first untagged token defaults to a response ref. The orchestrator resolves it against the operation’s responses map first, then falls back to definitions β€” if found in definitions (not responses), it’s silently promoted to a body ref.
  • Subsequent untagged tokens accumulate into the description.

Examples

Responses:
  200: User the user as returned                  # untagged β†’ response="User", desc="the user as returned"
  200: body:User the user                         # body ref + description
  200: response:userResponse the user             # named response ref
  201: body:Pet the created pet
  404: description: not found
  default: response:genericError
  default: body:[]ErrorList the error list        # array-wrapped body ref

Diagnostics

  • Unknown tag (200: weird:value) β€” emits CodeInvalidAnnotation and drops the line.
  • Duplicate body/response tags on one line (200: body:Pet response:errors) β€” emits CodeInvalidAnnotation; the line drops.
  • Space-separated body Foo (instead of body:Foo) β€” detected as a likely typo and dropped with diagnostic. The legacy parser silently treated it as response="body" (a dangling ref to a non-existent response).
  • Unresolvable response ref β€” when a response name appears in neither responses nor definitions, the line drops with diagnostic. The legacy parser emitted a dangling $ref.

Empty value lines

A line like 204: with nothing after the colon produces a Response with the code and an empty description. This is intentional β€” some authors want a 204 No Content with no body and no description.


YAML extensions

Several body keywords parse their body as YAML directly:

  • extensions: and infoExtensions: β€” a YAML map of x-* entries.
  • securityDefinitions: β€” a YAML map matching OAS v2’s securityDefinitions shape.
  • externalDocs: β€” a YAML map with description and url keys.

Extension typing

Extension values are NOT coerced to strings β€” they preserve their YAML-typed form: bool, float64, string, []any, or map[string]any for nested structures.

Extensions:
  x-feature-flags:
    - alpha
    - beta
  x-rate-limit:
    requests: 100
    window: 60
  x-internal: true
  x-version: 0.5

Produces (extract):

"x-feature-flags": ["alpha", "beta"],
"x-rate-limit": {"requests": 100, "window": 60},
"x-internal": true,
"x-version": 0.5

x-* name gating

Keys that don’t start with x- or X- emit a CodeInvalidAnnotation diagnostic and drop. The build still succeeds. Authors who relied on the legacy “hard error on non-x-*” behaviour see a diagnostic + a clean spec missing the typo’d key.

Extensions:
  x-good: 1
  not-good: 2   # β†’ diagnostic, dropped

YAML body delimitation

The YAML extension bodies use indentation to delimit. A line that returns to the indentation level of the keyword head β€” or introduces a sibling keyword β€” terminates the body. The grammar also recognises --- fence pairs around the body (matching the swagger:operation YAML shape) and absorbs them silently.


Security requirements

The security: body (in swagger:meta, swagger:route, and swagger:operation) carries OAuth-style security requirements where each line is one requirement.

Shape

Each line: schemeName: scope1, scope2, …

  • schemeName matches a scheme declared in securityDefinitions.
  • Scope list is comma-separated; trimmed; empties dropped.
  • An empty scope list (schemeName:) means “this scheme is required, no scopes.” Common for apiKey and basic.

Example

Security:
  api_key:
  oauth2: read, write
  oauth2: admin

Produces:

"security": [
  {"api_key": []},
  {"oauth2": ["read", "write"]},
  {"oauth2": ["admin"]}
]

Each requirement is a single-key map; the array is an OR relationship (the request satisfies security if it matches ANY entry).


Contact / License

Inline single-line meta keywords with structured value parsing.

Contact

The contact: value carries up to three components: name, email, URL. Recognised forms:

Contact: Name <email@example.com> https://example.com
Contact: Name <email@example.com>
Contact: https://example.com
Contact: <email@example.com>

The grammar splits the value on the first URL prefix it finds (https://, http://, ftps://, ftp://, wss://, ws://), then parses the prefix portion as Name <email> via Go’s net/mail.ParseAddress.

  • A malformed Name <email> head (e.g., unbalanced angle brackets) surfaces as an error from Block.Contact(); the meta builder propagates it as a build failure.
  • An empty contact line produces an empty Contact value (no error, no diagnostic β€” equivalent to omitting the keyword).

Aliases: contact info, contact-info.

License

The license: value is split similarly:

License: Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
License: MIT
License: https://opensource.org/licenses/Custom

Same URL-prefix detection. Everything before the URL is the license name; the URL (when present) is the license URL. Either part may be empty.

License does NOT use mail.ParseAddress β€” the name is taken as raw text up to the URL boundary.


Sub-language interactions

Two interaction points worth flagging:

  • Block-comment continuation lines and the parameters/responses sub-languages. A /* swagger:route … */ block with Parameters: inside requires the chunk-start sigils (+ / - ) to be at the start of the trimmed line. Block-comment continuation noise (\t, *) is stripped first; if your editor inserts a * continuation marker, the lexer handles it transparently.
  • Flex-list and description: on a parameter chunk. description: is a head field, not a list β€” it does NOT comma-split. Authors who write description: foo, bar get a single description "foo, bar", not two descriptions. (This was a real ambiguity in older versions of go-swagger; the current grammar resolves it cleanly.)
Last edited by: fredbi Jun 13, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Grammar

The formal grammar of the codescan annotation surface. This document specifies the language a Go comment must conform to so that the scanner classifies it, dispatches it to the right builder, and populates the OpenAPI spec deterministically.

Audience. Implementers β€” anyone porting, extending, or debugging the parser. Annotation authors typically need annotations.md and keywords.md instead.

The grammar is layered:

  1. Preprocess β€” comment-marker stripping (see Β§preprocess).
  2. Lex β€” terminal token emission, including multi-line body accumulation (see Β§lexer).
  3. Parse β€” block construction, family dispatch, keyword classification (see Β§parser).
  4. Walk β€” typed dispatch through grammar.Walker callbacks to the builders (see Β§walker).

The productions below operate on the lexer’s terminal alphabet, not raw text. Per-terminal lexical detail (how the lexer recognises a number, a string, an annotation, …) is described in Β§lexer; the EBNF that follows consumes pre-classified terminals.

The grammar is rigorous ISO-14977 EBNF. Required vs. optional arguments, value typing, and family membership are grammar-visible β€” every legality constraint expressible by token sequencing is expressed that way.


Table of contents


Preprocess

Input is a *ast.CommentGroup from go/parser. Each *ast.Comment in the group is one source-level comment node (either // … or /* … */). The preprocessor produces a flat sequence of Line structs, each one source line with:

  • Line.Text β€” content after comment-marker stripping and leading content-prefix trim.
  • Line.Raw β€” content after comment-marker stripping only (preserves leading whitespace).
  • Line.Pos β€” token.Position of the first content byte.

Stripping rules:

  • For // comments: drop the // marker. Line.Text runs trimContentPrefix (strips leading \t*/|); Line.Raw keeps the post-marker spacing.
  • For /* */ block comments: split body on newlines.
    • First line: drop the /* marker.
    • Continuation lines: run stripBlockContinuation (strips leading whitespace + optional * continuation marker + one following space), then trimContentPrefix.
    • Last line: drop the trailing */.

trimContentPrefix strips \t*/ and a single trailing | from the line head. It does NOT strip - (so YAML list markers and markdown dash items survive intact).

For synthetic per-line comments produced by upstream tooling (notably parsers.ParseRoutePathAnnotation), a // prefix is prepended before stripping so the // branch fires and the leading whitespace gets shed correctly.


Lexer

The lexer turns a []Line into a []Token ending in TokenEOF. Pipeline:

  1. Line classifier β€” emit one preliminary token per line (annotation / keyword / fence / blank / text).
  2. Body accumulator β€” fold multi-line bodies (OPAQUE_YAML, RAW_BLOCK_, RAW_VALUE_) into single body tokens.
  3. Prose classifier β€” re-type surviving text tokens as TokenTitle / TokenDesc.

Terminal vocabulary

Annotation name terminals (TokenAnnotation)

Each recognises an annotation name only β€” positional arguments are emitted as separate terminals.

TerminalAnnotation
ANN_MODELswagger:model
ANN_RESPONSEswagger:response
ANN_PARAMETERSswagger:parameters
ANN_ROUTEswagger:route
ANN_OPERATIONswagger:operation
ANN_METAswagger:meta
ANN_STRFMTswagger:strfmt
ANN_ALIASswagger:alias
ANN_NAMEswagger:name
ANN_ALLOFswagger:allOf
ANN_ENUMswagger:enum
ANN_IGNOREswagger:ignore
ANN_DEFAULTswagger:default
ANN_TYPEswagger:type
ANN_FILEswagger:file

Argument terminals

TerminalRecognises
IDENT_NAMEIdentifier-shaped token. Used for every named arg and reference.
JSON_VALUERFC-8259 JSON literal (string / number / boolean / null / array / object).
RAW_VALUEVerbatim non-LF text β€” fallback when JSON_VALUE recognition fails.
TYPE_REFClosed vocab: string / integer / number / boolean / array / object / file / null.
HTTP_METHODGET / POST / PUT / PATCH / HEAD / DELETE / OPTIONS / TRACE (case-insensitive).
URL_PATHRFC-3986 URL path token (used as the second positional arg of OperationArgs).

Keyword head terminals (TokenKeyword)

Each recognises the keyword name only. See keywords.md for the complete keyword surface.

Inline value terminals

The lexer types values per their lexical shape; semantic coercion against the Go target happens in the analyzer.

TerminalRecognises
NUMBER_VALUESigned decimal literal (integer or fractional).
INT_VALUEUnsigned decimal integer.
BOOL_VALUEtrue / false (case-insensitive).
STRING_VALUEVerbatim non-LF text.
COMMA_LIST_VALUEComma-separated list of strings, trim-stripped.
ENUM_OPTION_VALUEOne of a closed token set declared per keyword (query/path/… for in:, csv/ssv/… for collectionFormat).

When the lexer fails to type a value against its keyword’s expected shape, the property reaches the analyzer with Property.Typed.Type == ShapeNone and a CodeInvalidNumber / CodeInvalidInteger / CodeInvalidBoolean diagnostic is emitted.

Multi-line body terminals

Single tokens spanning multiple source lines. The lexer absorbs the head and the body lines.

TerminalParent keywordBody shape
RAW_BLOCK_CONSUMESconsumesFlat token list (see sub-languages Β§flex-list)
RAW_BLOCK_PRODUCESproducesFlat token list
RAW_BLOCK_SCHEMESschemesFlat token list
RAW_BLOCK_SECURITYsecuritySecurity requirements (see sub-languages Β§security-requirements)
RAW_BLOCK_SECURITY_DEFINITIONSsecurityDefinitionsYAML map
RAW_BLOCK_RESPONSESresponsesResponse sub-language (see sub-languages Β§responses)
RAW_BLOCK_PARAMETERSparametersParameter chunk sub-language (see sub-languages Β§parameters)
RAW_BLOCK_EXTENSIONSextensionsYAML map of x-* entries
RAW_BLOCK_INFO_EXTENSIONSinfoExtensionsYAML map of x-* entries
RAW_BLOCK_TOStosFree-form prose paragraph
RAW_BLOCK_EXTERNAL_DOCSexternalDocsYAML map
RAW_VALUE_DEFAULTdefaultRaw value text
RAW_VALUE_EXAMPLEexampleRaw value text
RAW_VALUE_ENUMenumComma list, JSON array, or YAML dash list

Body accumulation

A raw-block / raw-value keyword opens a body. The body terminates at the next sibling structural token in the same family β€” either another TokenAnnotation, another body-keyword head whose context makes it a sibling, or TokenEOF.

Blank lines do NOT terminate the body. They are absorbed as visual separators inside list-shaped bodies.

For raw-block heads, the inline post-colon value (when non-empty) is prepended to the body as its first line. This means Consumes: application/json (inline single value) and Consumes:\n - application/json (multi-line body) both yield the same body content; consumers don’t need to special-case the inline form.

YAML fence handling

A line whose trimmed content is exactly --- opens (or closes) a YAML fence. While the cursor sits between matching fences:

  • Annotation and keyword recognition is suspended; every line emits as tokenRawLine carrying the verbatim source text.
  • The body accumulator captures the fenced region as a single OPAQUE_YAML token attached to the surrounding annotation (typically swagger:operation or a fenced extensions body).
  • A missing closing fence emits a CodeUnterminatedFence diagnostic; the OPAQUE_YAML token is marked truncated and the builder degrades gracefully.

Prose classification

Surviving tokenText tokens (not consumed by a body, not an annotation or keyword head) re-type as either TokenTitle or TokenDesc per three heuristics evaluated in order:

  1. Blank-line split β€” a blank line inside the prose run ends the title and starts the description.
  2. Closing punctuation β€” if the first prose line ends with Unicode punctuation, the title is just that one line.
  3. Markdown ATX heading β€” if the first prose line matches markdown’s # Heading shape, the # markers are stripped and the line becomes the title.

When no heuristic fires, the entire prose run is title.

See sub-languages.md Β§prose-classification for the author-facing description.


Parser

The parser consumes the lexer’s terminal stream and produces typed Block values, one per *ast.CommentGroup. A single comment group may produce MORE than one Block when multiple annotations appear (each annotation closes the preceding Block and opens a fresh one).

Top-level dispatch

CommentBlock     = AnnotatedBlock | UnboundBlock ;

AnnotatedBlock   = SchemaBlock
                 | OperationFamilyBlock
                 | MetaBlock
                 | ClassifierBlock ;

UnboundBlock     = [ Description ] , UnboundBlockBody ;

The dispatcher reads the first ANN_* terminal; its identity selects the family. If no annotation appears, the input is an UnboundBlock β€” typically a Go struct field with description-only documentation.

Block.AnnotationKind() returns the family discriminator. Block.AnnotationArg() returns the leading IDENT argument (if any) without requiring the caller to type-assert on the typed Block kind.

Schema family

Bodies of swagger:model, swagger:parameters, swagger:response, swagger:name.

SchemaBlock          = SchemaAnnotation
                     , [ Title ]
                     , [ Description ]
                     , SchemaAnnotationBody ;

SchemaAnnotation     = ModelAnnotation
                     | ResponseAnnotation
                     | ParametersAnnotation
                     | NameAnnotation ;

ModelAnnotation      = ANN_MODEL ,      [ IDENT_NAME ] ;
ResponseAnnotation   = ANN_RESPONSE ,   [ IDENT_NAME ] ;
ParametersAnnotation = ANN_PARAMETERS , IDENT_NAME , { IDENT_NAME } ;
NameAnnotation       = ANN_NAME ,       IDENT_NAME ;

SchemaAnnotationBody = { SchemaBodyItem } ;
UnboundBlockBody     = { SchemaBodyItem } ;

SchemaBodyItem       = Validation
                     | SchemaDecorator
                     | ExtensionsBlock
                     | ExternalDocsBlock
                     | BLANK ;

Validation           = NumericValidation
                     | StringValidation
                     | ArrayValidation
                     | EnumValidation
                     | RequiredLine
                     | ReadOnlyLine ;

NumericValidation    = NumericKw , NUMBER_VALUE ;
NumericKw            = KW_MAXIMUM | KW_MINIMUM | KW_MULTIPLE_OF ;

StringValidation     = KW_PATTERN , STRING_VALUE
                     | StringLengthKw , INT_VALUE ;
StringLengthKw       = KW_MAX_LENGTH | KW_MIN_LENGTH ;

ArrayValidation      = ArrayCountKw , INT_VALUE
                     | KW_UNIQUE , BOOL_VALUE
                     | KW_COLLECTION_FORMAT , ENUM_OPTION_VALUE ;
ArrayCountKw         = KW_MAX_ITEMS | KW_MIN_ITEMS ;

EnumValidation       = RAW_VALUE_ENUM ;
RequiredLine         = KW_REQUIRED , BOOL_VALUE ;
ReadOnlyLine         = KW_READ_ONLY , BOOL_VALUE ;

SchemaDecorator      = RAW_VALUE_DEFAULT
                     | RAW_VALUE_EXAMPLE
                     | DiscriminatorLine
                     | DeprecatedLine ;

DiscriminatorLine    = KW_DISCRIMINATOR , BOOL_VALUE ;
DeprecatedLine       = KW_DEPRECATED , BOOL_VALUE ;

Operation family

swagger:route and swagger:operation are distinct block productions because their bodies differ structurally β€” swagger:route accepts the structured keyword surface; swagger:operation accepts an OPAQUE_YAML body.

OperationFamilyBlock = RouteBlock | InlineOperationBlock ;

RouteBlock           = ANN_ROUTE , OperationArgs
                     , [ Title ]
                     , [ Description ]
                     , RouteBody ;

InlineOperationBlock = ANN_OPERATION , OperationArgs
                     , [ Title ]
                     , [ Description ]
                     , InlineOperationBody ;

OperationArgs        = HTTP_METHOD , URL_PATH , { IDENT_NAME } , IDENT_NAME ;
                      (* Trailing IDENT_NAME is the OperationID;
                         the run between URL_PATH and the OpID is
                         the tag list. *)

RouteBody            = { CommonOperationBodyItem | BLANK } ;

InlineOperationBody  = { CommonOperationBodyItem
                       | OPAQUE_YAML
                       | BLANK } ;

CommonOperationBodyItem = OperationKeyword
                        | OperationDecorator
                        | OperationRawBlock
                        | ExtensionsBlock
                        | ExternalDocsBlock ;

OperationKeyword     = KW_SCHEMES , COMMA_LIST_VALUE ;

OperationDecorator   = DeprecatedLine ;

OperationRawBlock    = RAW_BLOCK_CONSUMES
                     | RAW_BLOCK_PRODUCES
                     | RAW_BLOCK_SECURITY
                     | RAW_BLOCK_RESPONSES
                     | RAW_BLOCK_PARAMETERS ;

The <GoIdent> swagger:route ... godoc-prefix exception (which allows a leading Go identifier on the route annotation line) is absorbed by the lexer; the EBNF sees a plain ANN_ROUTE.

Meta family

swagger:meta defines top-of-spec metadata.

MetaBlock            = ANN_META
                     , [ Title ]
                     , [ Description ]
                     , MetaBody ;

MetaBody             = { MetaBodyItem | BLANK } ;

MetaBodyItem         = MetaKeyword
                     | MetaRawBlock
                     | ExtensionsBlock
                     | InfoExtensionsBlock
                     | ExternalDocsBlock ;

MetaKeyword          = KW_VERSION , STRING_VALUE
                     | KW_HOST , STRING_VALUE
                     | KW_BASE_PATH , STRING_VALUE
                     | KW_LICENSE , STRING_VALUE
                     | KW_CONTACT , STRING_VALUE
                     | KW_SCHEMES , COMMA_LIST_VALUE ;

MetaRawBlock         = RAW_BLOCK_CONSUMES
                     | RAW_BLOCK_PRODUCES
                     | RAW_BLOCK_SCHEMES
                     | RAW_BLOCK_SECURITY
                     | RAW_BLOCK_SECURITY_DEFINITIONS
                     | RAW_BLOCK_TOS ;

Classifier family

Single-purpose annotations that classify the surrounding declaration without carrying their own body.

ClassifierBlock      = StrfmtBlock
                     | AliasBlock
                     | AllOfBlock
                     | EnumBlock
                     | IgnoreBlock
                     | DefaultClassifierBlock
                     | TypeBlock
                     | FileBlock ;

StrfmtBlock          = ANN_STRFMT , IDENT_NAME , [ Title ] , [ Description ] ;
AliasBlock           = ANN_ALIAS ,  [ IDENT_NAME ] , [ Title ] , [ Description ] ;
AllOfBlock           = ANN_ALLOF , [ Title ] , [ Description ] ;
EnumBlock            = ANN_ENUM , [ IDENT_NAME ] , [ Title ] , [ Description ] ;
IgnoreBlock          = ANN_IGNORE , [ Title ] , [ Description ] ;
DefaultClassifierBlock = ANN_DEFAULT , [ Title ] , [ Description ] ;
TypeBlock            = ANN_TYPE , TYPE_REF , [ Title ] , [ Description ] ;
FileBlock            = ANN_FILE , [ Title ] , [ Description ] ;

Classifiers are stateless markers β€” they carry no validation body of their own. The surrounding declaration’s other annotations (or the absence thereof) determine where the classification lands.


Cross-cutting productions

These appear in multiple families and share a single production.

ExtensionsBlock      = RAW_BLOCK_EXTENSIONS ;
InfoExtensionsBlock  = RAW_BLOCK_INFO_EXTENSIONS ;
ExternalDocsBlock    = RAW_BLOCK_EXTERNAL_DOCS ;

Title                = TokenTitle ;
Description          = TokenDesc , { TokenDesc | BLANK , TokenDesc } ;
BLANK                = TokenBlank ;

Vendor extensions (ExtensionsBlock, InfoExtensionsBlock) accept YAML map bodies; non-x-* keys emit CodeInvalidAnnotation and drop. The lexer additionally surfaces them via Block.Extensions() with an Extension.Source discriminator (KwExtensions vs KwInfoExtensions) so consumers can route to the correct spec field (spec.extensions vs info.extensions).


Walker

Block.Walk(grammar.Walker{...}) dispatches Properties through typed callbacks. The Walker maps a Property to a callback by Keyword.Shape:

ShapeCallbackPayload
ShapeNumberNumber(p, float64, exclusive bool)
ShapeIntInteger(p, int64)
ShapeBoolBool(p, bool)
ShapeStringString(p, string) β€” value on p.Value
ShapeEnumOptionString(p, string) β€” closed-vocab token on p.Typed.String
ShapeRawBlockRaw(p) β€” caller reads p.Body / p.Raw
ShapeRawValueRaw(p)
ShapeCommaListRaw(p) β€” caller splits via Property.AsList
ShapeNone (failed typing)Raw(p) β€” diagnostic fired separately

Additional callbacks fire outside the per-Property dispatch:

  • Title(s string) β€” once, before any property, if non-empty.
  • Description(s string) β€” once, before any property, if non-empty.
  • Extension(ext grammar.Extension) β€” once per typed extension.
  • Diagnostic(d grammar.Diagnostic) β€” block-level diagnostics fire before Title; per-property diagnostics fire immediately before the property’s main callback.

Walker.FilterDepth gates property callbacks by Property.ItemsDepth. Pass 0 for level-0 properties (default); pass N for items-level N; pass AllDepths (-1) for every depth.

For full Walker contract see the grammar package README.


Diagnostics

The grammar emits typed diagnostics for malformed input, recovered where possible:

CodeSeverityTrigger
CodeInvalidAnnotationWarningUnknown tag, malformed annotation arg, dropped malformed property
CodeInvalidNumberWarningNumber-typed value failed lexical parse
CodeInvalidIntegerWarningInteger-typed value failed lexical parse
CodeInvalidBooleanWarningBoolean-typed value failed lexical parse
CodeShapeMismatchWarningKeyword applied to a schema type that doesn’t accept it (e.g. minLength on a number)
CodeContextInvalidWarningKeyword used outside its legal annotation context
CodeUnsupportedInSimpleSchemaWarningFull-schema-only keyword used in SimpleSchema (non-body param, header)
CodeInvalidYAMLExtensionsWarningYAML parse failed inside an extensions body
CodeUnterminatedFenceWarningYAML fence opened but not closed before EOF

All diagnostics drop the offending property / annotation / extension and continue the build. The accumulator on common.Builder collects them in source order; the consumer’s OnDiagnostic callback (if wired) fires inline.


What this grammar does not describe

The grammar’s job ends at producing typed Property and Block values. The analyzer (builders / spec orchestrator) owns:

  • Type coercion β€” default: 1.5 against an integer schema is a lexical success and an analyzer rejection. validations.CoerceValue and validations.ParseDefault apply the schema-type-aware coercion at write time.
  • Cross-reference resolution β€” $ref targets, alias-chain resolution, post-decl discovery. The grammar emits the names; the analyzer resolves them.
  • Schema-shape gating β€” validations.IsLegalForType decides whether minLength applies to the resolved schema type. The grammar always emits the property; the handler dispatch decides whether to write it.
  • Ordering & merging across multiple comment groups β€” when several swagger:parameters Foo Bar Baz declarations contribute to the same operation, the spec builder merges them.

The grammar is also deliberately single-pass β€” it never revisits a *ast.CommentGroup after Parse(cg) returns. The common.Builder blockCache memoises results across the analyzer’s recursive type descent (see common README Β§blockcache).