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


  • How to drive codescan: getting started, runnable examples, and the annotation / grammar reference.
  • 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 — grouped by what they shape: scope & discovery, names & $refs, titles & descriptions, field types & formats, and response bodies.
  • 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 Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of go-openapi codescan

Usage

This section covers how to use codescan in practice.

  • Runnable Go examples. Every snippet on these pages is extracted from real, compilable, test-covered source under docs/examples.
  • The annotation vocabulary, keyword reference and the formal grammar the scanner parses.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Usage

Examples

The code shown on these pages lives under docs/examples as a separate Go module. Snippets are injected via the code Hugo shortcode, so what you read here is exactly what CI compiles and tests.

  • The smallest end-to-end use of codescan: annotate a package, scan it, and get back a Swagger 2.0 document.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Examples

Scan a package

This example scans a tiny annotated “petstore” package and produces a Swagger 2.0 spec. 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 Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Reference

codescan parses a small annotation language layered on top of Go doc comments. The reference material currently lives alongside the source in docs/:

  • Annotations — the full annotation vocabulary (swagger:meta, swagger:route, swagger:model, swagger:parameters, swagger:response, …).
  • Keywords — every keyword recognized inside annotation blocks and where it applies.
  • Grammar — the formal grammar the parser implements.
  • Sub-languages — the embedded YAML / simple-schema surfaces.

TODO (scaffold): these documents are good candidates to migrate into the doc-site as first-class pages (one section per file), so they render with navigation and search rather than linking out to GitHub.

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

Project

This section holds material specific to this repository:

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

Last edited by: fredbi Aug 1, 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 Aug 1, 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 Aug 1, 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 Aug 1, 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 Aug 1, 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, TransparentAliasesAlias-handling knobs.
EmitRefSiblingsEmit a $ref’d field’s description and extensions as direct $ref siblings instead of an allOf wrap — see Descriptions beside a $ref.
SkipAllOfCompoundingNever wrap a $ref’d field in an allOf; emit a bare $ref and drop the decorations that need a compound — see Descriptions beside a $ref.
DescWithRefDeprecated — preserve a description-only $ref field via a single-arm allOf; prefer EmitRefSiblings.
SkipExtensionsSuppress x-go-* vendor extensions.
SkipEnumDescriptionsKeep the swagger:enum const→value mapping out of property/parameter descriptions (it still rides x-go-enum-desc).
EmitXGoTypeStamp x-go-type (the fully-qualified Go type) on every definition — see Vendor extensions.
SingleLineCommentAsDescriptionRoute single-line comments to description instead of title/summary — see Single-line comments.

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 Aug 1, 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 a Go const block as a spec enum — any constant expression, the type and format taken from the declaration, and the same members inline on parameters and headers.
  • How Go maps render as objects, which key types survive, and how to control a schema’s open/closed/typed extra keys with additionalProperties and patternProperties.
  • Model a Swagger 2.0 type hierarchy — a base type with a discriminator and subtypes that compose it with swagger:allOf.
  • Publish paths and operations — swagger:route and swagger:operation — with their parameters and responses.
  • Declare a parameter or response once and reuse it across operations through the spec-level shared namespace, with the wildcard swagger:parameters and swagger:response forms.
  • 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.
  • Declare security schemes in swagger:meta, require them per route, or keep security out of your code entirely and overlay it onto the spec.
  • 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 Aug 1, 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. Well-known standard-library types resolve automatically — a time.Time field, for instance, is published as {type: string, format: date-time}.

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

A swagger:model that nothing else references appears in definitions only when you scan with Options.ScanModels (the -m flag). See When the scanner emits a type.

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

Add swagger:model to the strfmt type to opt it into a first-class definition ({type: string, format: <name>}) that fields $ref instead of inlining — the general swagger:model ⇒ definition + $ref rule.

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.) A bare swagger:enum on the type declaration works too — the name is inferred.

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

Here the values inline on the referencing field. Add swagger:model to the enum type to make it a first-class definition (carrying the enum array) that fields $ref — again the swagger:model ⇒ definition + $ref rule.

Enums have more to them than a string const block: iota and computed members, what decides the emitted type / format, and the inline form parameters and headers take. They get their own page — Enumerations.

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

When the base also declares a discriminator, this composition becomes a Swagger 2.0 type hierarchy — see Polymorphic models.

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"`
}


// RawID is a custom 16-byte identifier — an array under the hood, so left to
// itself a field of this type would render as an array of integers.
type RawID [16]byte

// Coupon overrides the type of a single field directly on the field doc — no
// wrapper-type annotation. Code publishes as a bare string while RawID is left
// untouched everywhere else it appears.
//
// swagger:model
type Coupon struct {
	// Code is an opaque identifier published as a string.
	//
	// swagger:type string
	Code RawID `json:"code"`

	// Amount is the discount in cents.
	Amount int64 `json:"amount"`
}

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

swagger:type is an inline directive: it renders the chosen type in place and never emits a $ref. The value is a scalar type (string, integer, number, boolean, object, or a Go builtin like int64), []T for an array of an inlined type, inline to expand the field’s own Go type, or a known type name to inline that type. array is deprecated in favour of inline / []T, and file is rejected (use swagger:file). When combined with swagger:strfmt, the type wins and the format is kept only if compatible — see the reference.

The override also works on an individual field doc — no wrapper-type annotation. Here Code is published as a string while its RawID type is left untouched everywhere else:

Annotated Go
// RawID is a custom 16-byte identifier — an array under the hood, so left to
// itself a field of this type would render as an array of integers.
type RawID [16]byte

// Coupon overrides the type of a single field directly on the field doc — no
// wrapper-type annotation. Code publishes as a bare string while RawID is left
// untouched everywhere else it appears.
//
// swagger:model
type Coupon struct {
	// Code is an opaque identifier published as a string.
	//
	// swagger:type string
	Code RawID `json:"code"`

	// Amount is the discount in cents.
	Amount int64 `json:"amount"`
}

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

#/definitions/Coupon
{
  "description": "Coupon overrides the type of a single field directly on the field doc — no\nwrapper-type annotation. Code publishes as a bare string while RawID is left\nuntouched everywhere else it appears.",
  "type": "object",
  "properties": {
    "amount": {
      "description": "Amount is the discount in cents.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "Amount"
    },
    "code": {
      "description": "Code is an opaque identifier published as a string.",
      "type": "string",
      "x-go-name": "Code"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

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

swagger:name

swagger:name <name> overrides the JSON property name a field or method renders as. It works on a struct field (overriding the json: tag / Go field name) and on an interface method. It is most useful on interface methods, which publish one property per nullary method and cannot carry a json tag: below, StructType() would default to structType, and the annotation publishes it as jsonClass instead.

Note

swagger:name is the legacy annotation form. The name: keyword is the canonical, universal equivalent — it renames a property here exactly the same way, and is the only form that also works on parameters and response headers. Precedence: name: > swagger:name > json: tag > Go field name.

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
}


// Account shows the universal name: keyword renaming model struct fields. The
// same keyword used on parameters and response headers also sets a property key
// here, winning over a json tag, the legacy swagger:name annotation, and the Go
// field name.
//
// swagger:model
type Account struct {
	// Bal has no json tag; the keyword sets the property key directly.
	//
	// name: balance
	Bal float64

	// Currency carries both naming forms; the keyword wins over the
	// legacy annotation and the json tag.
	//
	// name: currencyCode
	// swagger:name legacyCurrency
	Currency string `json:"currency"`
}

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

The name: keyword does the same on a model property — and, being the universal form, with the same syntax you would use on a parameter or header. Here it names a tag-less field directly, and on a field that also carries a json: tag and a legacy swagger:name, the keyword wins (name: > swagger:name > json: tag > Go field name):

Annotated Go
// Account shows the universal name: keyword renaming model struct fields. The
// same keyword used on parameters and response headers also sets a property key
// here, winning over a json tag, the legacy swagger:name annotation, and the Go
// field name.
//
// swagger:model
type Account struct {
	// Bal has no json tag; the keyword sets the property key directly.
	//
	// name: balance
	Bal float64

	// Currency carries both naming forms; the keyword wins over the
	// legacy annotation and the json tag.
	//
	// name: currencyCode
	// swagger:name legacyCurrency
	Currency string `json:"currency"`
}

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

#/definitions/Account
{
  "description": "Account shows the universal name: keyword renaming model struct fields. The\nsame keyword used on parameters and response headers also sets a property key\nhere, winning over a json tag, the legacy swagger:name annotation, and the Go\nfield name.",
  "type": "object",
  "properties": {
    "balance": {
      "description": "Bal has no json tag; the keyword sets the property key directly.",
      "type": "number",
      "format": "double",
      "x-go-name": "Bal"
    },
    "currencyCode": {
      "description": "Currency carries both naming forms; the keyword wins over the\nlegacy annotation and the json tag.",
      "type": "string",
      "x-go-name": "Currency"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

Full source: docs/examples/concepts/models/testdata/namekeyword.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). It also works on a single struct field — placed on the field’s doc comment, it drops just that property from the model.

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

Decorating a $ref

When a field’s type resolves to a named model, the property is a $ref — and a bare $ref cannot carry sibling keywords (a JSON Schema draft-4 rule). So a field that is both a reference and decorated (a description, a vendor extension, a default/example, a validation override) would lose its decorations.

codescan avoids that by wrapping the $ref as a member of an allOf. The property is then an ordinary schema object, free to carry the decorations alongside the reference:

  • the description and any x-* extensions sit on the property itself;
  • a value override (default, example) rides a second allOf member;
  • required rides the parent model’s required list.
Annotated Go
// 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 that also carries a description and
// a vendor extension. A bare $ref cannot hold sibling keywords, so codescan
// wraps the reference as a member of an allOf — the property is then a normal
// schema that keeps the description and the x-* extension, and required goes to
// the parent. Nothing is dropped.
//
// swagger:model
type Person struct {
	// Home is where the person lives.
	//
	// required: true
	// extensions:
	//   x-ui-order: 3
	Home Address `json:"home"`
}

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

#/definitions/Person
{
  "description": "Person references Address through a field that also carries a description and\na vendor extension. A bare $ref cannot hold sibling keywords, so codescan\nwraps the reference as a member of an allOf — the property is then a normal\nschema that keeps the description and the x-* extension, and required goes to\nthe parent. Nothing is dropped.",
  "type": "object",
  "required": [
    "home"
  ],
  "properties": {
    "home": {
      "description": "Home is where the person lives.",
      "allOf": [
        {
          "$ref": "#/definitions/Address"
        }
      ],
      "x-go-name": "Home",
      "x-ui-order": 3
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/refoverride"
}

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

Nothing is dropped. The description-only case is the exception — it is governed by the DescWithRef option (see Descriptions beside a $ref) — and a default/example on a $ref’d field is shown in Examples & defaults.

Info

Same-name collisions. Two structs that share the same short name in different packages stay distinct: codescan keys each by a compiler-unique identity and qualifies the colliding ones with a package segment (billing.Account / identity.AccountBillingAccount / IdentityAccount), deterministically. Pin the names in your public contract with an explicit swagger:model <Name>, and let auto-resolution handle the rest — see Resolving $ref name conflicts.

What’s next

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

Enumerations

An enum in Go is a named type plus a block of constants. swagger:enum turns that pair into an enum array on every schema, parameter and header the type reaches. This page covers what the scanner accepts on the value side, what decides the emitted type / format, and the two shapes that do not work.

Every Go snippet below comes from the test-covered docs/examples/concepts/enums package, and every JSON pane is a golden file a test regenerates.

swagger:enum

swagger:enum <name> collects the const values declared with that type. A bare swagger:enum on the type declaration works too — the name is inferred from the declaration it sits on.

The enum type is emitted because something points at it: a model field, a parameter, a header. On its own it is unreachable, and unreachable types are not published. Add swagger:model to the enum type to make it a first-class definition (carrying the enum array) that fields $ref instead — the general swagger:model ⇒ definition + $ref rule.

Each member’s doc comment becomes a line of the x-go-enum-desc extension, and is appended to the property description. Set SkipEnumDescriptions to keep the mapping on the extension only.

Any constant expression, not just literals

The values come from the Go type-checker, which has already evaluated the const block. So the members do not have to be written as literals — anything the compiler can fold is collected.

iota is the case that matters most, because after the first line there is nothing left in the source to read: the following specs carry neither a type nor a value, and inherit both implicitly.

Annotated Go
// Weekday is an iota enum: only the first spec carries a type and a value, and
// every following one inherits both implicitly.
//
// swagger:enum Weekday
type Weekday int

const (
	// Sunday is the first day.
	Sunday Weekday = iota
	// Monday is the second day.
	Monday
	// Tuesday is the third day.
	Tuesday
)

// Schedule carries the enum, which is what makes it reachable and so emitted.
//
// swagger:model
type Schedule struct {
	// Day the job runs on.
	Day Weekday `json:"day"`
}

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

#/definitions/Schedule
{
  "type": "object",
  "title": "Schedule carries the enum, which is what makes it reachable and so emitted.",
  "properties": {
    "day": {
      "description": "Day the job runs on.\n0 Sunday is the first day.\n1 Monday is the second day.\n2 Tuesday is the third day.",
      "type": "integer",
      "format": "int64",
      "enum": [
        0,
        1,
        2
      ],
      "x-go-enum-desc": "0 Sunday is the first day.\n1 Monday is the second day.\n2 Tuesday is the third day.",
      "x-go-name": "Day"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/iota.json

Constant expressions and references to earlier members are collected the same way.

Annotated Go
// Level is built from a constant expression and from a reference to an earlier
// member — neither of which is a literal.
//
// swagger:enum Level
type Level int

const (
	// LevelLow is the floor.
	LevelLow Level = 1 << 3
	// LevelHigh doubles it.
	LevelHigh Level = LevelLow * 2
)

// Threshold carries the computed enum.
//
// swagger:model
type Threshold struct {
	// Level to alert at.
	Level Level `json:"level"`
}

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

#/definitions/Threshold
{
  "type": "object",
  "title": "Threshold carries the computed enum.",
  "properties": {
    "level": {
      "description": "Level to alert at.\n8 LevelLow is the floor.\n16 LevelHigh doubles it.",
      "type": "integer",
      "format": "int64",
      "enum": [
        8,
        16
      ],
      "x-go-enum-desc": "8 LevelLow is the floor.\n16 LevelHigh doubles it.",
      "x-go-name": "Level"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/expressions.json

The same goes for every other constant form Go offers: negative values, the non-decimal bases (0x2a, 0b101010, 0o52) and digit separators, values above MaxInt64 in an unsigned enum, true / false, rune literals, and both the raw and the escaped string forms.

Negative members are worth a pane of their own, since a signed constant is not a literal in the Go grammar — it is an expression wrapping one:

Annotated Go
// PanDirection straddles zero. A signed constant is not a literal in the Go
// grammar — it is an expression wrapping one — so these are the members that
// used to go missing.
//
// swagger:enum PanDirection
type PanDirection int8

const (
	// PanLeft pans to the left.
	PanLeft PanDirection = -1
	// NoPan holds the current position.
	NoPan PanDirection = 0
	// PanRight pans to the right.
	PanRight PanDirection = 1
)

// Camera carries the signed enum, declared int8.
//
// swagger:model
type Camera struct {
	// Pan direction of the camera.
	Pan PanDirection `json:"pan"`
}

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

#/definitions/Camera
{
  "type": "object",
  "title": "Camera carries the signed enum, declared int8.",
  "properties": {
    "pan": {
      "description": "Pan direction of the camera.\n-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
      "type": "integer",
      "format": "int8",
      "enum": [
        -1,
        0,
        1
      ],
      "x-go-enum-desc": "-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
      "x-go-name": "Pan"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/signed.json

The type comes from the declaration

type and format are read from the Go type you declared, never from the members. PanDirection above is an int8, so the property is {integer, int8} — even though every member would fit in a smaller or larger box.

This is also what makes the const block safe to reorder. Zoom is a float32 whose first member is written 0, an integer literal; the schema is a number enum regardless of which member comes first:

Annotated Go
// Zoom is a float32 enum whose FIRST member is written as an integer literal.
// The schema type follows the declared type, so the block can be reordered
// freely.
//
// swagger:enum Zoom
type Zoom float32

const (
	// ZoomNone is the neutral step.
	ZoomNone Zoom = 0
	// ZoomOut steps back.
	ZoomOut Zoom = -1.5
	// ZoomIn steps in.
	ZoomIn Zoom = 1.5
)

// Lens carries the float enum.
//
// swagger:model
type Lens struct {
	// Zoom step of the lens.
	Zoom Zoom `json:"zoom"`
}

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

#/definitions/Lens
{
  "type": "object",
  "title": "Lens carries the float enum.",
  "properties": {
    "zoom": {
      "description": "Zoom step of the lens.\n0 ZoomNone is the neutral step.\n-1.5 ZoomOut steps back.\n1.5 ZoomIn steps in.",
      "type": "number",
      "format": "float",
      "enum": [
        0,
        -1.5,
        1.5
      ],
      "x-go-enum-desc": "0 ZoomNone is the neutral step.\n-1.5 ZoomOut steps back.\n1.5 ZoomIn steps in.",
      "x-go-name": "Zoom"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/width.json

A type declared over another named type keeps what that type contributed. An enum written over a string format is still that format:

Annotated Go
// Kind is an enum written over a string format rather than over a plain string:
// the format of the type it is declared over comes with it.
//
// swagger:enum Kind
type Kind strfmt.UUID

const (
	// KindPrimary is the primary kind.
	KindPrimary Kind = "0a8bcf1e-0000-0000-0000-000000000000"
	// KindSecondary is the secondary kind.
	KindSecondary Kind = "0a8bcf1e-1111-1111-1111-111111111111"
)

// Label carries the formatted enum.
//
// swagger:model
type Label struct {
	// Kind of the label.
	Kind Kind `json:"kind"`
}

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

#/definitions/Label
{
  "type": "object",
  "title": "Label carries the formatted enum.",
  "properties": {
    "kind": {
      "description": "Kind of the label.\n0a8bcf1e-0000-0000-0000-000000000000 KindPrimary is the primary kind.\n0a8bcf1e-1111-1111-1111-111111111111 KindSecondary is the secondary kind.",
      "type": "string",
      "format": "uuid",
      "enum": [
        "0a8bcf1e-0000-0000-0000-000000000000",
        "0a8bcf1e-1111-1111-1111-111111111111"
      ],
      "x-go-enum-desc": "0a8bcf1e-0000-0000-0000-000000000000 KindPrimary is the primary kind.\n0a8bcf1e-1111-1111-1111-111111111111 KindSecondary is the secondary kind.",
      "x-go-name": "Kind"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

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

Parameters and headers

OpenAPI 2.0 forbids a $ref on a non-body parameter or a response header, so there the members and the format are written inline. Nothing changes on the annotation side — the same enum type reaches in: query, path, header and formData, and the items of an array-typed one:

Annotated Go
// SearchParams consumes an enum from a non-body parameter, where OpenAPI 2.0
// forbids a $ref: the members and the format ship inline.
//
// swagger:parameters search
type SearchParams struct {
	// Direction to pan while searching.
	//
	// in: query
	Pan PanDirection `json:"pan"`

	// Directions the client accepts.
	//
	// in: query
	Accepted []PanDirection `json:"accepted"`
}

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

[
  {
    "enum": [
      -1,
      0,
      1
    ],
    "type": "integer",
    "format": "int8",
    "x-go-enum-desc": "-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
    "x-go-name": "Pan",
    "description": "Direction to pan while searching.\n-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
    "name": "pan",
    "in": "query"
  },
  {
    "type": "array",
    "items": {
      "enum": [
        -1,
        0,
        1
      ],
      "type": "integer",
      "format": "int8"
    },
    "x-go-name": "Accepted",
    "description": "Directions the client accepts.",
    "name": "accepted",
    "in": "query"
  }
]

Full source: docs/examples/concepts/enums/testdata/params.json

Two shapes that do not work

A rune or byte enum emits integers. It is collected like any other, and 'a' reaches the spec as 97:

Annotated Go
// Letter is a rune enum. A rune is an int32, on the wire as much as in Go, so
// the members are code points — 'a' is 97.
//
// swagger:enum Letter
type Letter rune

const (
	// LetterA is the first letter.
	LetterA Letter = 'a'
	// LetterB is the second letter.
	LetterB Letter = 'b'
)

// Glyph carries the rune enum.
//
// swagger:model
type Glyph struct {
	// Letter of the glyph.
	Letter Letter `json:"letter"`
}

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

#/definitions/Glyph
{
  "type": "object",
  "title": "Glyph carries the rune enum.",
  "properties": {
    "letter": {
      "description": "Letter of the glyph.\n97 LetterA is the first letter.\n98 LetterB is the second letter.",
      "type": "integer",
      "format": "int32",
      "enum": [
        97,
        98
      ],
      "x-go-enum-desc": "97 LetterA is the first letter.\n98 LetterB is the second letter.",
      "x-go-name": "Letter"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/runes.json

That is unlikely to be what you pictured, and it is the only faithful answer: a scalar rune is an int32 on the wire as much as in Go, so json.Marshal writes 97, and encoding/json refuses to unmarshal "a" back into the field. A string-typed schema would describe a payload your own server rejects. If you meant characters, declare the type over string (type Letter string, LetterA Letter = "a") — that changes the wire, and the schema follows.

An alias to a basic type cannot host an enum.

type Unsigned = uint64          // an alias, not a new type

// swagger:enum Unsigned        // ← collects nothing
const Zero Unsigned = 0

The Go type-checker erases the alias, so Zero is indistinguishable from any other uint64 constant and there is no set of members to collect. Declare a real type instead (type Unsigned uint64). An alias to a named enum type is fine — the named type survives.

Where to go next

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

Maps & free-form objects

Not every object has a fixed set of named fields. A Go map models an object with dynamic keys; a struct can be marked open (extra keys allowed), closed (extra keys forbidden), or given a typed value schema for its extras. codescan expresses all of this with additionalProperties and patternProperties. The panes below are rendered from the test-covered docs/examples/concepts/maps package.

Maps become objects

A map field renders as {type: object} whose values all share one schema, carried as additionalProperties — the value schema is derived from the Go map’s element type:

Annotated Go
// Inventory shows a plain Go map. A map renders as an object whose values all
// share one schema, carried as additionalProperties.
//
// swagger:model
type Inventory struct {
	// Counts maps each SKU to its on-hand quantity.
	Counts map[string]int `json:"counts"`
}

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

#/definitions/Inventory
{
  "description": "Inventory shows a plain Go map. A map renders as an object whose values all\nshare one schema, carried as additionalProperties.",
  "type": "object",
  "properties": {
    "counts": {
      "description": "Counts maps each SKU to its on-hand quantity.",
      "type": "object",
      "additionalProperties": {
        "type": "integer",
        "format": "int64"
      },
      "x-go-name": "Counts"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/naturalmap.json

Which map keys work

A map key has to become a JSON object key — a string. codescan accepts every key type encoding/json can stringify: string kinds, every integer / unsigned kind (so map[int]V, map[uint8]V, …), and any type implementing encoding.TextMarshaler. An integer-keyed map is therefore still a valid object:

Annotated Go
// Lookups shows which Go map keys survive. A key is usable when encoding/json
// can stringify it: string kinds, every integer/unsigned kind, and types
// implementing encoding.TextMarshaler. Other keys (float, bool, struct without
// TextMarshaler) are dropped with a diagnostic.
//
// swagger:model
type Lookups struct {
	// ByName is keyed by a plain string.
	ByName map[string]int `json:"byName"`

	// ByCode is keyed by an integer — JSON stringifies it, so the map is still
	// an object with additionalProperties.
	ByCode map[int]string `json:"byCode"`
}

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

#/definitions/Lookups
{
  "description": "Lookups shows which Go map keys survive. A key is usable when encoding/json\ncan stringify it: string kinds, every integer/unsigned kind, and types\nimplementing encoding.TextMarshaler. Other keys (float, bool, struct without\nTextMarshaler) are dropped with a diagnostic.",
  "type": "object",
  "properties": {
    "byCode": {
      "description": "ByCode is keyed by an integer — JSON stringifies it, so the map is still\nan object with additionalProperties.",
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "x-go-name": "ByCode"
    },
    "byName": {
      "description": "ByName is keyed by a plain string.",
      "type": "object",
      "additionalProperties": {
        "type": "integer",
        "format": "int64"
      },
      "x-go-name": "ByName"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/keytypes.json

Info

A key JSON cannot stringify — a float, bool, a struct without TextMarshaler, or an interface — is not silently dropped: the map’s additionalProperties is omitted and a CodeUnsupportedType diagnostic (“additionalProperties dropped”) is raised. A json:"-" map field is muted before this check, so it never warns.

Open & closed objects

A struct normally renders with just its named properties and says nothing about extra keys. The decl-level swagger:additionalProperties <spec> marker decides the policy, where <spec> is true, false, or a value type:

// ClosedObject forbids any key beyond its named properties: the marker false
// closes the object.
//
// swagger:model
// swagger:additionalProperties false
type ClosedObject struct {
	A string `json:"a"`
	B int    `json:"b"`
}

// OpenObject keeps its named property and also allows arbitrary extra keys.
//
// swagger:model
// swagger:additionalProperties true
type OpenObject struct {
	A string `json:"a"`
}

// TypedObject complements its named property with typed (integer) extra values.
//
// swagger:model
// swagger:additionalProperties integer
type TypedObject struct {
	A string `json:"a"`
}

// RefObject references a model as the schema of its extra values.
//
// swagger:model
// swagger:additionalProperties Thing
type RefObject struct {
	A string `json:"a"`
}

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

false closes the object (only the named properties are valid); true opens it (any extra key is allowed):

false — closed object
{
  "description": "ClosedObject forbids any key beyond its named properties: the marker false\ncloses the object.",
  "type": "object",
  "properties": {
    "a": {
      "type": "string",
      "x-go-name": "A"
    },
    "b": {
      "type": "integer",
      "format": "int64",
      "x-go-name": "B"
    }
  },
  "additionalProperties": false,
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/closed.json

true — open object
{
  "type": "object",
  "title": "OpenObject keeps its named property and also allows arbitrary extra keys.",
  "properties": {
    "a": {
      "type": "string",
      "x-go-name": "A"
    }
  },
  "additionalProperties": true,
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/open.json

A type spec instead of a bool gives the extra values a schema — a primitive (or []T), or a model name that becomes a $ref (the same value-type grammar as swagger:type, except a type name resolves to a $ref):

integer — typed values
{
  "type": "object",
  "title": "TypedObject complements its named property with typed (integer) extra values.",
  "properties": {
    "a": {
      "type": "string",
      "x-go-name": "A"
    }
  },
  "additionalProperties": {
    "type": "integer"
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/typed.json

Thing — model values
{
  "type": "object",
  "title": "RefObject references a model as the schema of its extra values.",
  "properties": {
    "a": {
      "type": "string",
      "x-go-name": "A"
    }
  },
  "additionalProperties": {
    "$ref": "#/definitions/Thing"
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/ref.json

On a map type the marker overrides the element-derived value schema; on a struct it complements the named properties (as above). It composes with the object validations maxProperties / minProperties / patternProperties.

Info

The model name is a bare leaf: codescan resolves it in the annotating type’s own package first, then uniquely across the scanned model set, so a value type declared in another package resolves to a $ref by name. A leaf that matches a model in several packages is ambiguous — it is dropped with a validate.ambiguous-type-name diagnostic. See Resolving $ref name conflicts.

Per-field control

The same <spec> is available as a field keyword, additionalProperties: <spec>, decorating one struct field. On a map field it overrides the value schema; on a $ref’d field the value rides an allOf sibling so the reference is preserved:

Annotated Go
// Holder decorates individual fields with the additionalProperties: keyword.
//
// swagger:model
type Holder struct {
	// OverriddenMap keeps its map shape but overrides the value schema from the
	// Go element type (string) to integer.
	//
	// additionalProperties: integer
	OverriddenMap map[string]string `json:"overriddenMap"`

	// RefMap points the map values at a model.
	//
	// additionalProperties: Thing
	RefMap map[string]string `json:"refMap"`

	// ClosedRef references a model and forbids extra keys. Because the field is
	// a $ref, the value rides an allOf sibling so the reference is preserved.
	//
	// additionalProperties: false
	ClosedRef Thing `json:"closedRef"`
}

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

#/definitions/Holder
{
  "type": "object",
  "title": "Holder decorates individual fields with the additionalProperties: keyword.",
  "properties": {
    "closedRef": {
      "description": "ClosedRef references a model and forbids extra keys. Because the field is\na $ref, the value rides an allOf sibling so the reference is preserved.",
      "allOf": [
        {
          "$ref": "#/definitions/Thing"
        },
        {
          "additionalProperties": false
        }
      ],
      "x-go-name": "ClosedRef"
    },
    "overriddenMap": {
      "description": "OverriddenMap keeps its map shape but overrides the value schema from the\nGo element type (string) to integer.",
      "type": "object",
      "additionalProperties": {
        "type": "integer"
      },
      "x-go-name": "OverriddenMap"
    },
    "refMap": {
      "description": "RefMap points the map values at a model.",
      "type": "object",
      "additionalProperties": {
        "$ref": "#/definitions/Thing"
      },
      "x-go-name": "RefMap"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/fieldkeyword.json

Pattern properties

patternProperties constrains extra keys by a name regex rather than allowing all of them. The regex-only patternProperties: keyword maps a pattern to an empty (any-value) schema. The decl-level swagger:patternProperties "<re>": <spec>, … marker is the typed counterpart — each quoted regex pairs with a value spec (a primitive, or a model name that becomes a $ref):

Annotated Go
// TypedPatterns maps property-name regexes to typed value schemas. Each quoted
// regex pairs with a value spec — a primitive or a model name (which becomes a
// $ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0
// subset; codescan emits it ungated.
//
// swagger:model
// swagger:patternProperties "^x-": string, "^\d+$": integer, "^item-": Thing
type TypedPatterns struct {
	Known string `json:"known"`
}

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

#/definitions/TypedPatterns
{
  "description": "TypedPatterns maps property-name regexes to typed value schemas. Each quoted\nregex pairs with a value spec — a primitive or a model name (which becomes a\n$ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0\nsubset; codescan emits it ungated.",
  "type": "object",
  "properties": {
    "known": {
      "type": "string",
      "x-go-name": "Known"
    }
  },
  "patternProperties": {
    "^\\d+$": {
      "type": "integer"
    },
    "^item-": {
      "$ref": "#/definitions/Thing"
    },
    "^x-": {
      "type": "string"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/patterntyped.json

Note

Beyond Swagger 2.0. patternProperties is a JSON-Schema (draft-4) keyword, not part of the Swagger 2.0 Schema Object subset. codescan emits it ungated, consistent with go-openapi’s JSON-Schema-first stance — your downstream tooling must understand it. Each regex is RE2-hygiene-checked: one that does not compile raises a CodeInvalidAnnotation warning but is preserved on the schema.

Info

additionalProperties and patternProperties are object-schema keywords — they only ride on an object. They are the lowest-priority annotations: if a prior rule already fixed a non-object type (a swagger:type scalar, a swagger:strfmt, a special type), the marker is dropped with a CodeShapeMismatch diagnostic. Object schemas also have no OAS-2 SimpleSchema form, so neither keyword applies on a non-body parameter or a response header (it is dropped there with a diagnostic).

What’s next

  • ValidationsmaxProperties / minProperties and the regex-only patternProperties: keyword.
  • Model definitions — the $ref mechanics the typed value schemas rely on.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Polymorphic models

Swagger 2.0 expresses polymorphism with three ingredients:

  1. a base type that declares a discriminator — the property whose value says which concrete subtype a payload is;
  2. subtypes that include the base via allOf and add their own fields;
  3. a discriminator value per subtype (here, the subtype’s definition name).

The panes below are backed by the test-covered docs/examples/concepts/polymorphism package.

The base type

Mark one property discriminator: true. codescan writes that property’s name onto the schema’s discriminator. A discriminator property must also be required — a consumer cannot pick a subtype from a value that may be absent.

Annotated Go
// Pet is the polymorphic base type. The field marked `discriminator: true` names
// the property whose value tells a consumer which concrete subtype a payload is:
// codescan writes that property's name onto the schema's `discriminator`, and a
// discriminator property must be `required`.
//
// swagger:model
type Pet struct {
	// PetType selects the concrete subtype — its value is the subtype's
	// definition name (e.g. "Cat" or "Dog").
	//
	// discriminator: true
	// required: true
	PetType string `json:"petType"`

	// Name is common to every pet.
	//
	// required: true
	Name string `json:"name"`
}

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

#/definitions/Pet
{
  "description": "Pet is the polymorphic base type. The field marked `discriminator: true` names\nthe property whose value tells a consumer which concrete subtype a payload is:\ncodescan writes that property's name onto the schema's `discriminator`, and a\ndiscriminator property must be `required`.",
  "type": "object",
  "required": [
    "petType",
    "name"
  ],
  "properties": {
    "name": {
      "description": "Name is common to every pet.",
      "type": "string",
      "x-go-name": "Name"
    },
    "petType": {
      "description": "PetType selects the concrete subtype — its value is the subtype's\ndefinition name (e.g. \"Cat\" or \"Dog\").",
      "type": "string",
      "x-go-name": "PetType"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism",
  "discriminator": "petType"
}

Full source: docs/examples/concepts/polymorphism/testdata/base.json

The subtypes

Each subtype embeds the base as an anonymous field annotated swagger:allOf. The result is allOf: [ {$ref to the base}, {the subtype's own fields} ] — the same composition covered in Model definitions, now given meaning by the base’s discriminator.

Annotated Go
// Cat is a Pet subtype. `swagger:allOf` composes it as
// `allOf: [ $ref Pet, {its own fields} ]` — the composition Swagger 2.0
// polymorphism builds on.
//
// swagger:model
type Cat struct {
	// swagger:allOf
	Pet

	// HuntingSkill is how the cat hunts.
	HuntingSkill string `json:"huntingSkill"`
}

// Dog is a second Pet subtype.
//
// swagger:model
type Dog struct {
	// swagger:allOf
	Pet

	// PackSize is the size of the dog's pack.
	PackSize int32 `json:"packSize"`
}

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

#/definitions/Cat
{
  "description": "Cat is a Pet subtype. `swagger:allOf` composes it as\n`allOf: [ $ref Pet, {its own fields} ]` — the composition Swagger 2.0\npolymorphism builds on.",
  "allOf": [
    {
      "$ref": "#/definitions/Pet"
    },
    {
      "type": "object",
      "properties": {
        "huntingSkill": {
          "description": "HuntingSkill is how the cat hunts.",
          "type": "string",
          "x-go-name": "HuntingSkill"
        }
      }
    }
  ],
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism"
}

Full source: docs/examples/concepts/polymorphism/testdata/subtype.json

Dog follows the identical shape. A payload is then recognised as a Cat or a Dog by its petType value.

How subtypes are discovered

A family has an awkward property: the references all point upwards. A subtype $refs its base, and nothing ever $refs a subtype. So an API that returns the base — the whole point of polymorphism — names only the base, and the ordinary reachability rule would stop right there, leaving a discriminator with nothing to discriminate between.

codescan therefore looks the relation up backwards. When a definition that declares a discriminator enters the spec, every swagger:model that composes it under swagger:allOf is pulled in with it — wherever those subtypes are declared, including other packages. No ScanModels needed. The route below references only Pet:

Annotated Go
// PetResponse returns the polymorphic BASE — nothing in the API surface names
// Cat or Dog.
//
// swagger:response petResponse
type PetResponse struct {
	// in: body
	Body Pet `json:"body"`
}

// swagger:route GET /pets pets listPets
//
// Lists pets.
//
// responses:
//
//	200: petResponse

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

Whole spec, scanned WITHOUT ScanModels
{
  "swagger": "2.0",
  "paths": {
    "/pets": {
      "get": {
        "tags": [
          "pets"
        ],
        "summary": "Lists pets.",
        "operationId": "listPets",
        "responses": {
          "200": {
            "$ref": "#/responses/petResponse"
          }
        }
      }
    }
  },
  "definitions": {
    "Cat": {
      "description": "Cat is a Pet subtype. `swagger:allOf` composes it as\n`allOf: [ $ref Pet, {its own fields} ]` — the composition Swagger 2.0\npolymorphism builds on.",
      "allOf": [
        {
          "$ref": "#/definitions/Pet"
        },
        {
          "type": "object",
          "properties": {
            "huntingSkill": {
              "description": "HuntingSkill is how the cat hunts.",
              "type": "string",
              "x-go-name": "HuntingSkill"
            }
          }
        }
      ],
      "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism"
    },
    "Dog": {
      "title": "Dog is a second Pet subtype.",
      "allOf": [
        {
          "$ref": "#/definitions/Pet"
        },
        {
          "type": "object",
          "properties": {
            "packSize": {
              "description": "PackSize is the size of the dog's pack.",
              "type": "integer",
              "format": "int32",
              "x-go-name": "PackSize"
            }
          }
        }
      ],
      "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism"
    },
    "Pet": {
      "description": "Pet is the polymorphic base type. The field marked `discriminator: true` names\nthe property whose value tells a consumer which concrete subtype a payload is:\ncodescan writes that property's name onto the schema's `discriminator`, and a\ndiscriminator property must be `required`.",
      "type": "object",
      "required": [
        "petType",
        "name"
      ],
      "properties": {
        "name": {
          "description": "Name is common to every pet.",
          "type": "string",
          "x-go-name": "Name"
        },
        "petType": {
          "description": "PetType selects the concrete subtype — its value is the subtype's\ndefinition name (e.g. \"Cat\" or \"Dog\").",
          "type": "string",
          "x-go-name": "PetType"
        }
      },
      "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism",
      "discriminator": "petType"
    }
  },
  "responses": {
    "petResponse": {
      "description": "PetResponse returns the polymorphic BASE — nothing in the API surface names\nCat or Dog.",
      "schema": {
        "$ref": "#/definitions/Pet"
      }
    }
  }
}

Full source: docs/examples/concepts/polymorphism/testdata/spec-reachable-only.json

Cat and Dog are in there, and each pull is announced on the OnDiagnostic sink, so a definition you did not ask for by name is never a mystery:

scan.discovered-subtype: definition "Cat" discovered as a subtype of discriminated base "Pet"
scan.discovered-subtype: definition "Dog" discovered as a subtype of discriminated base "Pet"

Full source: docs/examples/concepts/polymorphism/testdata/hints.txt

Three consequences worth knowing:

  • Reachability, not existence. The trigger is the base entering the spec, not merely existing in the scanned source. A discriminated base that nothing references still emits nothing — bases are not roots, or every hierarchy in a shared library would land in every spec.
  • The family travels as a unit. With PruneUnusedModels a reachable discriminated base keeps its subtypes, even though no $ref reaches them; and an unreachable base is dropped together with its subtypes. You never get a base whose subtypes have vanished.
  • Only swagger:allOf counts. A plain embed inlines the base’s properties instead of composing them, so it is not a subtype relation. The DefaultAllOfForEmbeds option changes how embeds render, deliberately not which definitions exist.

Multi-level hierarchies

A subtype can be a base in its own right. Write the intermediate level as an interface — only an interface can be embedded by the concrete structs beneath it — composing its parent with swagger:allOf and declaring a discriminator of its own:

Annotated Go
// Shape is the root of the hierarchy: a base written as an interface, whose
// `discriminator: true` member names the property a consumer switches on.
//
// swagger:model
type Shape interface {
	// ShapeType selects the concrete subtype.
	//
	// discriminator: true
	// required: true
	// swagger:name shapeType
	ShapeType() string

	// swagger:name area
	Area() float64
}

// Polygon is a subtype of Shape AND a base of its own: it composes Shape as an
// allOf member and declares a second discriminator. An intermediate level is
// written as an interface, because only an interface can be embedded by the
// concrete structs below it.
//
// swagger:model
type Polygon interface {
	// swagger:allOf
	Shape

	// PolygonType selects the concrete polygon.
	//
	// discriminator: true
	// required: true
	// swagger:name polygonType
	PolygonType() string
}

// Square is a leaf, two levels down. It composes the INTERMEDIATE type, so it
// inherits Shape transitively.
//
// swagger:model
type Square struct {
	// swagger:allOf
	Polygon

	// Side is the length of a side.
	Side float64 `json:"side"`
}

Full source: docs/examples/concepts/polymorphism-nested/nested.go

#/definitions/Polygon — subtype AND base
{
  "description": "Polygon is a subtype of Shape AND a base of its own: it composes Shape as an\nallOf member and declares a second discriminator. An intermediate level is\nwritten as an interface, because only an interface can be embedded by the\nconcrete structs below it.",
  "allOf": [
    {
      "$ref": "#/definitions/Shape"
    },
    {
      "type": "object",
      "required": [
        "polygonType"
      ],
      "properties": {
        "polygonType": {
          "description": "PolygonType selects the concrete polygon.",
          "type": "string",
          "x-go-name": "PolygonType"
        }
      },
      "discriminator": "polygonType"
    }
  ],
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism-nested"
}

Full source: docs/examples/concepts/polymorphism-nested/testdata/intermediate.json

Discovery cascades: the route references Shape, Shape pulls Polygon, and Polygon — itself only just discovered — pulls Square. Note where each level’s discriminator lands, because the two differ:

levelshapediscriminator
root (Shape)a plain objectat the top level
intermediate (Polygon)allOf: [ $ref Shape, {own} ]inside its own allOf member
leaf (Square)allOf: [ $ref Polygon, {own} ]none of its own

A leaf never inherits its base’s discriminator as its own: it points at a discriminated base, which is what makes it a subtype, not a base.

Info

The discriminator value for each subtype is its definition name (Cat, Dog) — so petType must carry exactly "Cat" or "Dog". codescan does not implement a custom-value annotation (swagger:discriminatorValue), so the subtype name is the value. Keep the discriminator a plain string and required on the base; it is inherited by every subtype through the $ref.

What’s next

Last edited by: fredbi Aug 1, 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

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

The body can also carry an indented Parameters: block to declare simple parameters (path / query / header) inline — no swagger:parameters struct needed. For body parameters or parameter sets shared across operations, use swagger:parameters instead. The block syntax is covered in sub-languages.

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

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

A field marked in: body makes its Go type the request body schema — the usual shape for a POST or PUT payload:

Annotated Go
// CreatePetParams is the request parameter set for createPet. A field marked
// `in: body` makes its Go type the request body schema — the usual shape for a
// POST or PUT payload.
//
// swagger:parameters createPet
type CreatePetParams struct {
	// Body is the pet to create.
	//
	// in: body
	// required: true
	Body Pet `json:"body"`
}

// swagger:route POST /pets/import pets createPet
//
// responses:
//
//	200: petsResponse

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

parameters on createPet
[
  {
    "x-go-name": "Body",
    "description": "Body is the pet to create.",
    "name": "body",
    "in": "body",
    "required": true,
    "schema": {
      "$ref": "#/definitions/Pet"
    }
  }
]

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

When a parameter field’s Go type is a struct (or any type that has no simple Swagger representation), it cannot be a query/path/header parameter on its own. A swagger:type override collapses it to a simple parameter — a scalar, or a []-wrapped scalar for an array parameter:

Annotated Go
// Cursor is an opaque pagination token. As a struct it cannot be a query
// parameter on its own; swagger:type publishes it as a simple parameter.
type Cursor struct {
	Page  int
	Token string
}

// FilterPetsParams shows swagger:type on parameter fields: a struct-typed field
// is collapsed to a simple parameter. The override accepts a scalar or a
// []-wrapped scalar — the inline / type-name forms are rejected here (a
// non-body parameter has no schema to inline into).
//
// swagger:parameters filterPets
type FilterPetsParams struct {
	// After is an opaque cursor carried as a plain string query parameter.
	//
	// in: query
	// swagger:type string
	After Cursor `json:"after"`

	// Sort is a list of sort keys carried as an array-of-string query parameter.
	//
	// in: query
	// swagger:type []string
	Sort []Cursor `json:"sort"`
}

// swagger:route GET /pets/filter pets filterPets
//
// Filter pets with cursor pagination.
//
// responses:
//
//	200: description: matched pets

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

parameters on filterPets
[
  {
    "type": "string",
    "x-go-name": "After",
    "description": "After is an opaque cursor carried as a plain string query parameter.",
    "name": "after",
    "in": "query"
  },
  {
    "type": "array",
    "items": {
      "type": "string"
    },
    "x-go-name": "Sort",
    "description": "Sort is a list of sort keys carried as an array-of-string query parameter.",
    "name": "sort",
    "in": "query"
  }
]

Full source: docs/examples/concepts/routes/testdata/paramtype.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

externalDocs

An externalDocs: block (description + url) links an object out to external documentation. It rides an operation (in a swagger:route or swagger:operation body) and a full schema (a swagger:model). It is a full-Schema-only keyword: on a simple-schema parameter (anything but in: body) it is dropped with a diagnostic. The same ExternalDocs: block on a swagger:meta package populates the spec’s top-level externalDocs (see Document metadata).

Annotated Go
// swagger:route GET /pets/search pets searchPets
//
// Searches pets. The operation links out to external documentation.
//
// externalDocs:
//   description: Search guide
//   url: https://example.com/docs/search
//
// responses:
//
//	200: petsResponse

// CatalogEntry carries externalDocs at the schema level (the link rides the
// definition) and on its fields. (On a simple-schema parameter externalDocs is
// dropped with a diagnostic: it is a full-Schema-only keyword.)
//
// externalDocs: {description: "Catalog schema reference", url: "https://example.com/docs/catalog"}
//
// swagger:model
type CatalogEntry struct {
	// SKU is the catalog identifier.
	SKU string `json:"sku"`

	// Vendor is a plain field: externalDocs attaches directly to the property.
	//
	// externalDocs: {description: "Vendor field docs", url: "https://example.com/docs/vendor"}
	Vendor string `json:"vendor"`

	// Supplier is a $ref'd field: its sibling externalDocs lifts onto the
	// field's allOf compound (a bare $ref cannot carry sibling keywords).
	//
	// externalDocs: {description: "Supplier docs", url: "https://example.com/docs/supplier"}
	Supplier Supplier `json:"supplier"`
}

// Supplier is referenced by CatalogEntry.Supplier.
//
// swagger:model
type Supplier struct {
	// Name is the supplier name.
	Name string `json:"name"`
}

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

operation externalDocs
{
  "description": "Search guide",
  "url": "https://example.com/docs/search"
}

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

On a model the link rides the definition, and it also rides individual struct fields: on a plain field it attaches to the property directly; on a $ref’d field it is lifted onto the field’s allOf compound (a bare $ref cannot carry sibling keywords). The value can be written as the indented block above or as an equivalent inline { description: …, url: … } mapping — which reads better on a single-line doc comment:

Annotated Go
// swagger:route GET /pets/search pets searchPets
//
// Searches pets. The operation links out to external documentation.
//
// externalDocs:
//   description: Search guide
//   url: https://example.com/docs/search
//
// responses:
//
//	200: petsResponse

// CatalogEntry carries externalDocs at the schema level (the link rides the
// definition) and on its fields. (On a simple-schema parameter externalDocs is
// dropped with a diagnostic: it is a full-Schema-only keyword.)
//
// externalDocs: {description: "Catalog schema reference", url: "https://example.com/docs/catalog"}
//
// swagger:model
type CatalogEntry struct {
	// SKU is the catalog identifier.
	SKU string `json:"sku"`

	// Vendor is a plain field: externalDocs attaches directly to the property.
	//
	// externalDocs: {description: "Vendor field docs", url: "https://example.com/docs/vendor"}
	Vendor string `json:"vendor"`

	// Supplier is a $ref'd field: its sibling externalDocs lifts onto the
	// field's allOf compound (a bare $ref cannot carry sibling keywords).
	//
	// externalDocs: {description: "Supplier docs", url: "https://example.com/docs/supplier"}
	Supplier Supplier `json:"supplier"`
}

// Supplier is referenced by CatalogEntry.Supplier.
//
// swagger:model
type Supplier struct {
	// Name is the supplier name.
	Name string `json:"name"`
}

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

#/definitions/CatalogEntry
{
  "description": "CatalogEntry carries externalDocs at the schema level (the link rides the\ndefinition) and on its fields. (On a simple-schema parameter externalDocs is\ndropped with a diagnostic: it is a full-Schema-only keyword.)",
  "type": "object",
  "properties": {
    "sku": {
      "description": "SKU is the catalog identifier.",
      "type": "string",
      "x-go-name": "SKU"
    },
    "supplier": {
      "description": "Supplier is a $ref'd field: its sibling externalDocs lifts onto the\nfield's allOf compound (a bare $ref cannot carry sibling keywords).",
      "allOf": [
        {
          "$ref": "#/definitions/Supplier"
        }
      ],
      "x-go-name": "Supplier",
      "externalDocs": {
        "description": "Supplier docs",
        "url": "https://example.com/docs/supplier"
      }
    },
    "vendor": {
      "description": "Vendor is a plain field: externalDocs attaches directly to the property.",
      "type": "string",
      "x-go-name": "Vendor",
      "externalDocs": {
        "description": "Vendor field docs",
        "url": "https://example.com/docs/vendor"
      }
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/routes",
  "externalDocs": {
    "description": "Catalog schema reference",
    "url": "https://example.com/docs/catalog"
  }
}

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

What’s next

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

Sharing parameters & responses

When the same header, query parameter or error response appears on many operations, you don’t have to repeat it. codescan can publish a parameter or response once into the spec’s top-level parameters / responses maps, then reference it from each operation as a $ref. This is the OpenAPI 2.0 shared namespace (#/parameters/{name}, #/responses/{name}).

Each pane below pairs the annotated Go (left) with the exact fragment the scanner emits (right), from the test-covered docs/examples/concepts/sharedparams package. For the per-operation basics this builds on — the plain swagger:parameters <operationID> and swagger:response <name> forms — see Routes & operations.

Declaring a shared parameter

swagger:parameters * declares a struct whose fields are registered at the spec top level, #/parameters/{name}, keyed by each parameter’s resolved name. The bare * is register-only: it publishes the parameter but does not, by itself, attach it to any operation.

The convenience form swagger:parameters * <operationID>… does both at once — it registers the parameter and $refs it into the listed operations, which is handy for a small spec.

// CommonHeaders registers a reusable header parameter at the spec top
// level, #/parameters/X-Request-ID. The bare `*` target is register-only:
// it publishes the parameter but does not, by itself, attach it to any
// operation.
//
// swagger:parameters *
type CommonHeaders struct {
	// RequestID correlates a request across services.
	//
	// in: header
	RequestID string `json:"X-Request-ID"`
}

// AuthHeader registers #/parameters/X-API-Key and, in the same breath,
// $ref's it into the createPet operation — the convenient `* <opid>`
// form for a small spec.
//
// swagger:parameters * createPet
type AuthHeader struct {
	// APIKey authorises access.
	//
	// in: header
	// required: true
	APIKey string `json:"X-API-Key"`
}


// ErrorResponse is the common error envelope returned by every operation.
//
// swagger:response *
type ErrorResponse struct {
	// in: body
	Body struct {
		// Code is a machine-readable error code.
		Code int `json:"code"`
		// Message is a human-readable error message.
		Message string `json:"message"`
	} `json:"body"`
}

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

Both structs land in the top-level parameters map, each keyed by its parameter name (the json: tag, or a name: / swagger:name override):

{
  "X-API-Key": {
    "type": "string",
    "x-go-name": "APIKey",
    "description": "APIKey authorises access.",
    "name": "X-API-Key",
    "in": "header",
    "required": true
  },
  "X-Request-ID": {
    "type": "string",
    "x-go-name": "RequestID",
    "description": "RequestID correlates a request across services.",
    "name": "X-Request-ID",
    "in": "header"
  }
}

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

Referencing a shared parameter

Once a parameter is registered, an operation can pull it in by name. There are two reference channels:

  • swagger:parameters * <operationID> on the declaring struct — the convenience form above. AuthHeader uses it to inject X-API-Key into createPet.
  • swagger:parameters <operationID> <name>… as a standalone marker on the operation’s function — the scaling channel: the shared struct need not enumerate every operation that wants the parameter; instead each operation opts in next to its own swagger:route.
// ListPets lists pets.
//
// The standalone reference marker on the next line pulls the shared
// X-Request-ID parameter into this operation as a $ref. This is the
// scaling channel: the shared struct need not enumerate every operation
// that wants the parameter.
//
// swagger:route GET /pets pets listPets
// swagger:parameters listPets X-Request-ID
// Responses:
//
//	default: ErrorResponse
func ListPets() {}

// CreatePet creates a pet. Its X-API-Key comes from AuthHeader
// (#/parameters/X-API-Key, $ref'd via `* createPet`); its body comes from
// the inlined CreatePetParams.
//
// swagger:route POST /pets pets createPet
// Responses:
//
//	default: ErrorResponse
func CreatePet() {}

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

listPets opts in through the standalone marker, so its only parameter is a $ref to the shared X-Request-ID:

{
  "tags": [
    "pets"
  ],
  "operationId": "listPets",
  "parameters": [
    {
      "$ref": "#/parameters/X-Request-ID"
    }
  ],
  "responses": {
    "default": {
      "$ref": "#/responses/ErrorResponse"
    }
  }
}

Full source: docs/examples/concepts/sharedparams/testdata/listpets.json

createPet receives the $ref’d X-API-Key (from * createPet) alongside its own inlined body parameter — references and inline parameters coexist:

{
  "tags": [
    "pets"
  ],
  "operationId": "createPet",
  "parameters": [
    {
      "x-go-name": "Body",
      "name": "body",
      "in": "body",
      "required": true,
      "schema": {
        "$ref": "#/definitions/Pet"
      }
    },
    {
      "$ref": "#/parameters/X-API-Key"
    }
  ],
  "responses": {
    "default": {
      "$ref": "#/responses/ErrorResponse"
    }
  }
}

Full source: docs/examples/concepts/sharedparams/testdata/createpet.json

Path-item parameters

A parameter can also attach to a whole path rather than a single operation. swagger:parameters /path inlines a struct’s fields into the path-item’s parameters array, so every operation under that path inherits them.

// TenantHeader inlines a required header into the /pets/{id} path-item
// itself, so every operation under that exact path inherits it. The
// target is a literal path, and matching is exact — OAS2 has no path
// hierarchy, so this does NOT apply to /pets.
//
// swagger:parameters /pets/{id}
type TenantHeader struct {
	// Tenant scopes the request to a customer.
	//
	// in: header
	// required: true
	Tenant string `json:"X-Tenant"`
}

// GetPet fetches one pet. It declares no header of its own; X-Tenant
// reaches it through the /pets/{id} path-item parameter above.
//
// swagger:route GET /pets/{id} pets getPet
// Responses:
//
//	default: ErrorResponse
func GetPet() {}

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

X-Tenant now rides the /pets/{id} path-item; getPet inherits it without declaring a header of its own:

{
  "get": {
    "tags": [
      "pets"
    ],
    "operationId": "getPet",
    "responses": {
      "default": {
        "$ref": "#/responses/ErrorResponse"
      }
    }
  },
  "parameters": [
    {
      "type": "string",
      "x-go-name": "Tenant",
      "description": "Tenant scopes the request to a customer.",
      "name": "X-Tenant",
      "in": "header",
      "required": true
    }
  ]
}

Full source: docs/examples/concepts/sharedparams/testdata/pathitem.json

Warning

Exact path, no hierarchy. OpenAPI 2.0 has no path nesting, so the target is matched literally: swagger:parameters /pets/{id} applies to /pets/{id} only — not to /pets. Path-item parameters also co-exist with operation-level ones rather than replacing them; if an operation declares a parameter with the same (name, in), the operation’s wins at resolution time per the OAS2 rule.

To $ref an already-registered shared parameter into a path-item (instead of inlining a new one), use the reference form with a path target: swagger:parameters /path <name>… — the path-item analogue of the per-operation marker above.

Shared responses

Responses share the same way. swagger:response * registers a struct at #/responses/{name} (keyed by the Go type name). The * is a synonym for the bare/named swagger:response form — its job is to mark the response as a shared one. Operations then name it in their Responses: block and it is emitted as a $ref.

// ErrorResponse is the common error envelope returned by every operation.
//
// swagger:response *
type ErrorResponse struct {
	// in: body
	Body struct {
		// Code is a machine-readable error code.
		Code int `json:"code"`
		// Message is a human-readable error message.
		Message string `json:"message"`
	} `json:"body"`
}

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

The shared ErrorResponse lands in the top-level responses map:

{
  "ErrorResponse": {
    "description": "ErrorResponse is the common error envelope returned by every operation.",
    "schema": {
      "type": "object",
      "properties": {
        "code": {
          "description": "Code is a machine-readable error code.",
          "type": "integer",
          "format": "int64",
          "x-go-name": "Code"
        },
        "message": {
          "description": "Message is a human-readable error message.",
          "type": "string",
          "x-go-name": "Message"
        }
      }
    }
  }
}

Full source: docs/examples/concepts/sharedparams/testdata/responses.json

Both routes write default: ErrorResponse, which resolves to a single shared $ref (visible as responses.default.$ref in the operation panes above) — one error envelope, defined once, referenced everywhere.

Conflicts, duplicates & dangling references

The shared namespace is referenced only by short name, so codescan cannot silently rename a collision the way it deconflicts model definitions. Instead it applies a deterministic, observable policy and reports every adjustment through Options.OnDiagnostic (the scan never fails on these — it keeps a valid spec and warns):

SituationPolicyDiagnostic
Two swagger:parameters * register the same namekeep-first (sorted by package path then position; never renamed) — later one droppedscan.shared-parameter-conflict
Two swagger:response * register the same namekeep-first; later one droppedscan.shared-response-conflict
A reference names a parameter no * registeredreference dropped (no dangling $ref emitted)scan.dangling-parameter-ref
An operation names an unregistered shared responsereference droppedscan.dangling-response-ref
A * <opid>… marker repeats an operation idduplicate droppedscan.duplicate-target
A reference repeats a parameter namecollapses to a single $refscan.duplicate-ref
Note

The shared parameters, responses and definitions namespaces are independent: #/parameters/Status, #/responses/Status and #/definitions/Status can all coexist. The resolved (name:-overridden) name is the key, so references must use that name — not the Go field name. An InputSpec overlay entry seeds the namespace and wins any keep-first conflict.

What’s next

Last edited by: fredbi Aug 1, 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:

  • Numericminimum, maximum, multipleOf (on Price).
  • Lengthmin length / max length (on Name).
  • Arraysmin items / max items / unique (on Tags).
  • Pattern — a regular expression (on SKU).
  • Enum — a fixed value set (on Grade).
  • Requiredrequired: 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

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

A Go map field has no simple-schema representation either: on a non-body parameter or a response header it is skipped with a validate.unsupported-in-simple-schema warning. Maps are only representable on a body schema (as object + additionalProperties).

An array element is itself a simple schema, so it may not be a $ref. A named-primitive element ([]Label, underlying string) expands inline to its type; an object element ([]SomeStruct) has no simple-schema form and dissolves to an empty items: {} with the same warning. Use a body schema for an array of objects.

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

On an object — property count and name patterns

The object-validation keywords constrain a free-form object as a whole rather than named fields: minProperties / maxProperties bound the property count, and patternProperties permits properties whose name matches a regex. They are schema-only — kept on an object-typed model, stripped (with a diagnostic) on a scalar model or a simple-schema parameter. For dynamic-key objects, the typed value forms, and additionalProperties, see Maps & free-form objects.

Annotated Go
// Attributes is a free-form object constrained by the object-validation
// keywords: it must carry between 1 and 10 properties, and any property whose
// name matches the regex is permitted. Object validations constrain the map of
// (additional) properties rather than named struct fields.
//
// minProperties: 1
// maxProperties: 10
// patternProperties: ^x-
//
// swagger:model Attributes
type Attributes map[string]any

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

#/definitions/Attributes
{
  "description": "Attributes is a free-form object constrained by the object-validation\nkeywords: it must carry between 1 and 10 properties, and any property whose\nname matches the regex is permitted. Object validations constrain the map of\n(additional) properties rather than named struct fields.",
  "type": "object",
  "maxProperties": 10,
  "minProperties": 1,
  "additionalProperties": {},
  "patternProperties": {
    "^x-": {}
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/validations"
}

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

What’s next

Last edited by: fredbi Aug 1, 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

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

The value is not limited to scalars. A JSON literal is parsed into a structured example — a { … } object on a map field, a [ … ] array on a slice field. A bare comma-separated list (example: a,b) is not split; it is kept verbatim as a string, so write example: ["a","b"] when you need an array.

On a plain string field a surrounding pair of double quotes is treated as delimiters and stripped — so example: "Foo" yields Foo, and example: "" sets an empty string (the same applies to default:). Bare values keep their text as-is.

Annotated Go
// Profile carries structured (non-scalar) example values. A JSON object literal
// on a map field and a JSON array literal on a slice field are parsed into
// structured examples — a bare comma-separated list would instead be kept
// verbatim as a string.
//
// swagger:model
type Profile struct {
	// Labels is a set of key/value labels.
	//
	// example: {"env":"prod","tier":"gold"}
	Labels map[string]string `json:"labels"`

	// Roles is the list of assigned roles.
	//
	// example: ["admin","auditor"]
	Roles []string `json:"roles"`
}

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

#/definitions/Profile
{
  "description": "Profile carries structured (non-scalar) example values. A JSON object literal\non a map field and a JSON array literal on a slice field are parsed into\nstructured examples — a bare comma-separated list would instead be kept\nverbatim as a string.",
  "type": "object",
  "properties": {
    "labels": {
      "description": "Labels is a set of key/value labels.",
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "x-go-name": "Labels",
      "example": {
        "env": "prod",
        "tier": "gold"
      }
    },
    "roles": {
      "description": "Roles is the list of assigned roles.",
      "type": "array",
      "items": {
        "type": "string"
      },
      "x-go-name": "Roles",
      "example": [
        "admin",
        "auditor"
      ]
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"
}

Full source: docs/examples/concepts/examples/testdata/complexexample.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

On a defined-type field

When a field’s type is a named (defined) type, it renders as a $ref to that type’s definition — and a $ref cannot carry sibling keywords. An example: or default: on such a field is therefore preserved on the override arm of an allOf compound, so the value still reaches the spec.

Annotated Go
// Currency is a named (defined) string type, so it earns its own definition and
// a field typed Currency renders as a $ref. A $ref cannot carry sibling
// keywords, so an example or default on such a field rides the override arm of
// an allOf compound — the value still reaches the spec.
//
// swagger:model
type Currency string

// Price shows example + default on a defined-type field.
//
// swagger:model
type Price struct {
	// Unit is the ISO currency code.
	//
	// default: USD
	// example: EUR
	Unit Currency `json:"unit"`
}

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

#/definitions/Price
{
  "type": "object",
  "title": "Price shows example + default on a defined-type field.",
  "properties": {
    "unit": {
      "description": "Unit is the ISO currency code.",
      "allOf": [
        {
          "$ref": "#/definitions/Currency"
        },
        {
          "default": "USD",
          "example": "EUR"
        }
      ],
      "x-go-name": "Unit"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"
}

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

A JSON object or array literal on a $ref’d field is coerced into a structured value on that override arm — exactly as it is on a plain field — so the example reads as real JSON, not an escaped string. A bare scalar is the exception: on the override arm the referenced type is unknown, so a scalar stays a string rather than being silently retyped.

Annotated Go
// Coordinates is a defined struct, so a field typed Coordinates renders as a
// $ref.
//
// swagger:model
type Coordinates struct {
	// Lat is the latitude.
	Lat float64 `json:"lat"`

	// Lng is the longitude.
	Lng float64 `json:"lng"`
}

// Place shows a JSON-object example on a $ref'd field. Because the field is a
// $ref, the example rides the override arm of the allOf — and a JSON object (or
// array) literal is coerced into a structured value there, exactly as it is on a
// plain field. A bare scalar would instead stay a string, since the referenced
// type is not known on the override arm.
//
// swagger:model
type Place struct {
	// At is the location.
	//
	// example: {"lat":48.85,"lng":2.35}
	At Coordinates `json:"at"`
}

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

#/definitions/Place
{
  "description": "Place shows a JSON-object example on a $ref'd field. Because the field is a\n$ref, the example rides the override arm of the allOf — and a JSON object (or\narray) literal is coerced into a structured value there, exactly as it is on a\nplain field. A bare scalar would instead stay a string, since the referenced\ntype is not known on the override arm.",
  "type": "object",
  "properties": {
    "at": {
      "description": "At is the location.",
      "allOf": [
        {
          "$ref": "#/definitions/Coordinates"
        },
        {
          "example": {
            "lat": 48.85,
            "lng": 2.35
          }
        }
      ],
      "x-go-name": "At"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"
}

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

On a response body

example: is not limited to model fields. On a swagger:response whose body is a top-level array (or other non-struct) type, the example lands on the response body schema:

Annotated Go
// NTPServers is a top-level array response carrying an example. The example
// lands on the response body schema rather than being dropped.
//
// swagger:response ntpServers
// example: ["10.0.0.1","10.0.0.2"]
type NTPServers []string

// swagger:route GET /ntp ntp listNTP
//
// responses:
//
//	200: ntpServers


// Pet is the response payload.
//
// swagger:model Pet
type Pet struct {
	Name string `json:"name"`
}

// PetResponse returns a pet, with one example payload per media type.
//
// The plural `examples:` keyword on a struct swagger:response is a YAML map
// keyed by media type, populating the OpenAPI response `examples` object.
//
// swagger:response petResponse
//
// examples:
//
//	application/json:
//	  name: Fluffy
//	application/xml: "<pet><name>Fluffy</name></pet>"
type PetResponse struct {
	// in: body
	Body Pet `json:"body"`
}

// swagger:route GET /pets pets listPets
//
// responses:
//
//	200: petResponse

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

responses[ntpServers]
{
  "description": "NTPServers is a top-level array response carrying an example. The example\nlands on the response body schema rather than being dropped.",
  "schema": {
    "type": "array",
    "items": {
      "type": "string"
    },
    "example": [
      "10.0.0.1",
      "10.0.0.2"
    ]
  }
}

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

Response examples by media type

A response can carry an examples: map keyed by media type — these populate the OpenAPI response examples object, one example payload per content type. Both annotation styles support it.

In a swagger:operation YAML body, examples: sits under the response code:

// swagger:operation GET /status status getStatus
//
// ---
// responses:
//   '200':
//     description: Success
//     examples:
//       application/json:
//         hello: world

On a struct-based swagger:response, the same examples: block lives in the declaration comment (the plural examples: is the response keyword; the singular example: above is the schema decorator) and produces the same response examples object:

Annotated Go
// Pet is the response payload.
//
// swagger:model Pet
type Pet struct {
	Name string `json:"name"`
}

// PetResponse returns a pet, with one example payload per media type.
//
// The plural `examples:` keyword on a struct swagger:response is a YAML map
// keyed by media type, populating the OpenAPI response `examples` object.
//
// swagger:response petResponse
//
// examples:
//
//	application/json:
//	  name: Fluffy
//	application/xml: "<pet><name>Fluffy</name></pet>"
type PetResponse struct {
	// in: body
	Body Pet `json:"body"`
}

// swagger:route GET /pets pets listPets
//
// responses:
//
//	200: petResponse

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

responses[petResponse]
{
  "description": "PetResponse returns a pet, with one example payload per media type.\n\nThe plural `examples:` keyword on a struct swagger:response is a YAML map\nkeyed by media type, populating the OpenAPI response `examples` object.",
  "schema": {
    "$ref": "#/definitions/Pet"
  },
  "examples": {
    "application/json": {
      "name": "Fluffy"
    },
    "application/xml": "\u003cpet\u003e\u003cname\u003eFluffy\u003c/name\u003e\u003c/pet\u003e"
  }
}

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

Because the example lives on the response, one shared model can carry a different example per response code and per operation — a 200 and a 404 that both return the same error model each show their own illustrative payload, with no need for a distinct struct per case.

What’s next

Last edited by: fredbi Aug 1, 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

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

This is the idiomatic way to model server-set fields (an id, a createdAt) that appear in responses but should not be supplied on create — one model, marked readOnly, rather than separate request/response structs. (codescan does not hide fields per operation; if you truly need different shapes, declare distinct request and response models.)

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


// Gadget is a deprecated model. OpenAPI 2.0 has no native `deprecated` on a
// schema, so codescan emits `x-deprecated: true` — here triggered by the
// godoc-style "Deprecated:" paragraph, which is recognised on its own without a
// separate annotation. (The explicit `deprecated: true` annotation, shown on the
// operation above, has the same effect on a model or field.)
//
// Deprecated: superseded by the v2 widget API.
//
// swagger:model
type Gadget struct {
	// SerialNo is the legacy identifier.
	//
	// Deprecated: use the v2 identifier instead.
	SerialNo string `json:"serialNo"`

	// Name is the current display name.
	Name string `json:"name"`
}

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

On an operation, deprecated: true sets the native OpenAPI 2.0 deprecated field. OpenAPI 2.0 has no native deprecated on the Schema object, so on a model or model field codescan emits the x-deprecated: true vendor extension instead.

A godoc-style Deprecated: paragraph (the pkgsite convention) is an exact synonym for deprecated: true, recognised in any context. On a Go doc comment it is the natural form — a bare // deprecated: true line there reads as a malformed deprecation notice to Go linters, whereas the capitalised Deprecated: paragraph is idiomatic. Use deprecated: true in the indented route / operation bodies, and the Deprecated: paragraph on model and field doc comments; either yields the same result. x-deprecated carries semantic intent rather than reflection metadata, so it is emitted even when SkipExtensions is set.

A godoc Deprecated: paragraph marks a model and its fields — codescan emits x-deprecated: true on each (the explicit deprecated: true annotation has the same effect):

Annotated Go
// Gadget is a deprecated model. OpenAPI 2.0 has no native `deprecated` on a
// schema, so codescan emits `x-deprecated: true` — here triggered by the
// godoc-style "Deprecated:" paragraph, which is recognised on its own without a
// separate annotation. (The explicit `deprecated: true` annotation, shown on the
// operation above, has the same effect on a model or field.)
//
// Deprecated: superseded by the v2 widget API.
//
// swagger:model
type Gadget struct {
	// SerialNo is the legacy identifier.
	//
	// Deprecated: use the v2 identifier instead.
	SerialNo string `json:"serialNo"`

	// Name is the current display name.
	Name string `json:"name"`
}

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

#/definitions/Gadget
{
  "description": "Deprecated: superseded by the v2 widget API.",
  "type": "object",
  "title": "Gadget is a deprecated model. OpenAPI 2.0 has no native `deprecated` on a\nschema, so codescan emits `x-deprecated: true` — here triggered by the\ngodoc-style \"Deprecated:\" paragraph, which is recognised on its own without a\nseparate annotation. (The explicit `deprecated: true` annotation, shown on the\noperation above, has the same effect on a model or field.)",
  "properties": {
    "name": {
      "description": "Name is the current display name.",
      "type": "string",
      "x-go-name": "Name"
    },
    "serialNo": {
      "description": "SerialNo is the legacy identifier.\n\nDeprecated: use the v2 identifier instead.",
      "type": "string",
      "x-deprecated": true,
      "x-go-name": "SerialNo"
    }
  },
  "x-deprecated": true,
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/decorators"
}

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

What’s next

Last edited by: fredbi Aug 1, 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, and an ExternalDocs: block (description + url) populates the spec’s top-level externalDocs. An InfoExtensions: block adds x-* vendor extensions to the info object — this is where an x-logo (rendered by ReDoc / Swagger UI) goes.

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
//
//	ExternalDocs:
//	  description: Full API guide
//	  url: https://example.com/docs
//
//	Tags:
//	- name: pets
//	  description: Everything about your Pets
//	  externalDocs:
//	    description: Find out more
//	    url: https://example.com/docs/pets
//	- name: store
//	  description: Access to Petstore orders
//	  x-display-name: Store
//
//	SecurityDefinitions:
//	  basic_auth:
//	    type: basic
//	  api_key:
//	    type: apiKey
//	    in: header
//	    name: X-API-Key
//
//	Security:
//	  basic_auth:
//
//	InfoExtensions:
//	  x-logo:
//	    url: https://example.com/logo.png
//	    altText: Example
//
// 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",
    "x-logo": {
      "altText": "Example",
      "url": "https://example.com/logo.png"
    }
  },
  "host": "api.example.com",
  "basePath": "/v1",
  "paths": {},
  "securityDefinitions": {
    "api_key": {
      "type": "apiKey",
      "name": "X-API-Key",
      "in": "header"
    },
    "basic_auth": {
      "type": "basic"
    }
  },
  "security": [
    {
      "basic_auth": []
    }
  ],
  "tags": [
    {
      "description": "Everything about your Pets",
      "name": "pets",
      "externalDocs": {
        "description": "Find out more",
        "url": "https://example.com/docs/pets"
      }
    },
    {
      "description": "Access to Petstore orders",
      "name": "store",
      "x-display-name": "Store"
    }
  ],
  "externalDocs": {
    "description": "Full API guide",
    "url": "https://example.com/docs"
  }
}

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

Tags

A Tags: block declares the spec’s top-level tags — a YAML sequence of tag objects, each with a name, an optional description, a nested externalDocs, and any x-* vendor extensions. This is how you attach per-tag descriptions to the tags your routes reference (above, pets and store).

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

Security

The meta block above also declares SecurityDefinitions: (the auth schemes) and a Security: default — authentication is declared, not hand-rolled. Declaring schemes, requiring them per route, and overlaying security from outside the code have their own walkthrough: Security.

A build-time version

Version: is a static literal in source — there is no Options field for it. To stamp a version computed at build time, drive codescan as a library and set it on the returned document after Run:

doc, _ := codescan.Run(opts)
doc.Info.Version = buildVersion // e.g. injected via -ldflags "-X main.buildVersion=..."

Alternatively, overlay a base document that already carries the version with Options.InputSpec (see Overlaying a spec).

What’s next

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

Security

OpenAPI 2.0 splits authentication into two parts: security definitions name the schemes (an API key, OAuth2, HTTP Basic), and security requirements reference those schemes — document-wide and/or per operation. The panes below are backed by the test-covered docs/examples/concepts/security package.

Declare the schemes

A SecurityDefinitions: block in swagger:meta declares every scheme once; a Security: block sets the document-wide default requirement that applies to operations that do not state their own.

Package doc comment
// Package security Reports API.
//
// The swagger:meta block declares the security schemes once and sets the
// document-wide default requirement.
//
//	Version: 1.0.0
//
//	SecurityDefinitions:
//	  api_key:
//	    type: apiKey
//	    in: header
//	    name: X-API-Key
//	  oauth2:
//	    type: oauth2
//	    flow: accessCode
//	    authorizationUrl: https://example.com/auth
//	    tokenUrl: https://example.com/token
//	    scopes:
//	      read: read reports
//	      write: write reports
//
//	Security:
//	  - api_key: []
//
// swagger:meta
package security

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

securityDefinitions + security
{
  "security": [
    {
      "api_key": []
    }
  ],
  "securityDefinitions": {
    "api_key": {
      "type": "apiKey",
      "name": "X-API-Key",
      "in": "header"
    },
    "oauth2": {
      "type": "oauth2",
      "flow": "accessCode",
      "authorizationUrl": "https://example.com/auth",
      "tokenUrl": "https://example.com/token",
      "scopes": {
        "read": "read reports",
        "write": "write reports"
      }
    }
  }
}

Full source: docs/examples/concepts/security/testdata/schemes.json

The scheme type drives the rest: apiKey needs in + name, oauth2 needs a flow (and the URLs/scopes it implies), basic needs nothing more. The full scheme surface is in the securityDefinitions reference.

Require a scheme on a route

A route with no Security: keyword inherits the document-wide default (api_key, above). A route that needs something different states its own Security: requirement — here createReport requires oauth2 with the read and write scopes, overriding the default:

swagger:route
// listReports inherits the document-wide default requirement (api_key) — no
// Security: keyword is needed.
//
// swagger:route GET /reports reports listReports
//
// responses:
//   200: description: the reports

// createReport overrides the default with its own Security: requirement —
// oauth2 with the read and write scopes. The Security: block is YAML: a sequence
// of requirement objects, scopes as a flow (or block) list.
//
// swagger:route POST /reports reports createReport
//
// Security:
//   - oauth2: [read, write]
//
// responses:
//   201: description: created

// archiveReport requires BOTH schemes at once — two keys in a single sequence
// item are ANDed into one requirement object (separate items would mean OR).
//
// swagger:route POST /reports/archive reports archiveReport
//
// Security:
//   - api_key: []
//     oauth2: [write]
//
// responses:
//   200: description: archived

// publicReport opts out of the document default entirely — an empty
// `Security: []` emits an explicit empty requirement, marking the operation
// public regardless of the document-wide default.
//
// swagger:route GET /reports/public reports publicReport
//
// Security: []
//
// responses:
//   200: description: the public reports

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

[
  {
    "oauth2": [
      "read",
      "write"
    ]
  }
]

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

A Security: block is plain YAML — a sequence of requirement objects. Scopes are a flow list ([read, write]) or a block list; an empty list (api_key: []) is the scheme with no scopes. The combining rule follows OpenAPI 2.0:

  • multiple schemes in one item are ANDed — all are required;
  • separate items are ORed — satisfying any one grants access.

So requiring both an API key and an OAuth2 scope is two keys under a single sequence item:

swagger:route
// listReports inherits the document-wide default requirement (api_key) — no
// Security: keyword is needed.
//
// swagger:route GET /reports reports listReports
//
// responses:
//   200: description: the reports

// createReport overrides the default with its own Security: requirement —
// oauth2 with the read and write scopes. The Security: block is YAML: a sequence
// of requirement objects, scopes as a flow (or block) list.
//
// swagger:route POST /reports reports createReport
//
// Security:
//   - oauth2: [read, write]
//
// responses:
//   201: description: created

// archiveReport requires BOTH schemes at once — two keys in a single sequence
// item are ANDed into one requirement object (separate items would mean OR).
//
// swagger:route POST /reports/archive reports archiveReport
//
// Security:
//   - api_key: []
//     oauth2: [write]
//
// responses:
//   200: description: archived

// publicReport opts out of the document default entirely — an empty
// `Security: []` emits an explicit empty requirement, marking the operation
// public regardless of the document-wide default.
//
// swagger:route GET /reports/public reports publicReport
//
// Security: []
//
// responses:
//   200: description: the public reports

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

security on archiveReport (AND)
[
  {
    "api_key": [],
    "oauth2": [
      "write"
    ]
  }
]

Full source: docs/examples/concepts/security/testdata/and.json

A route’s requirements replace the document default for that operation. To make one operation public — opting out of the document-wide default — give it an empty Security: []. That emits an explicit empty requirement (distinct from omitting the keyword, which inherits the default):

swagger:route
// listReports inherits the document-wide default requirement (api_key) — no
// Security: keyword is needed.
//
// swagger:route GET /reports reports listReports
//
// responses:
//   200: description: the reports

// createReport overrides the default with its own Security: requirement —
// oauth2 with the read and write scopes. The Security: block is YAML: a sequence
// of requirement objects, scopes as a flow (or block) list.
//
// swagger:route POST /reports reports createReport
//
// Security:
//   - oauth2: [read, write]
//
// responses:
//   201: description: created

// archiveReport requires BOTH schemes at once — two keys in a single sequence
// item are ANDed into one requirement object (separate items would mean OR).
//
// swagger:route POST /reports/archive reports archiveReport
//
// Security:
//   - api_key: []
//     oauth2: [write]
//
// responses:
//   200: description: archived

// publicReport opts out of the document default entirely — an empty
// `Security: []` emits an explicit empty requirement, marking the operation
// public regardless of the document-wide default.
//
// swagger:route GET /reports/public reports publicReport
//
// Security: []
//
// responses:
//   200: description: the public reports

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

security on publicReport
{
  "security": []
}

Full source: docs/examples/concepts/security/testdata/public.json

The same works from a swagger:operation YAML body — a security: key there sets that operation’s requirement. (The schemes themselves are always global swagger:meta — OpenAPI 2.0 has no per-operation securityDefinitions.)

Keep security out of your code

Authentication is often handled by a layer in front of the app — a gateway or service mesh — and you may not want security details in the annotations at all. In that case, leave the code free of security annotations and overlay the schemes and requirements with Options.InputSpec:

// base carries only the security scheme + default requirement.
var base spec.Swagger
_ = json.Unmarshal(baseSpecJSON, &base)

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

The app package above (concepts/routes) carries no security annotations, yet the merged document is secured — the schemes and the default requirement come entirely from the base:

{
  "security": [
    {
      "api_key": []
    }
  ],
  "securityDefinitions": {
    "api_key": {
      "type": "apiKey",
      "name": "X-API-Key",
      "in": "header"
    }
  }
}

Full source: docs/examples/concepts/security/testdata/overlay.json

See Overlaying a spec for the full InputSpec merge semantics.

What’s next

Last edited by: fredbi Aug 1, 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.

Seeing it rendered

The same golden spec, rendered as live API documentation by Swagger UI — what a consumer of the generated document sees. This closes the loop the capstone is about: annotated Go → the Swagger 2.0 JSON above → the API docs those annotations produce. The widget reads the very same golden file, so the rendered view can’t drift from the JSON either.

Last edited by: fredbi Aug 1, 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, and a handful of codescan.Options (plus a few field-level annotations) let you choose. The guides are grouped by what they shape:

  • Scope & discovery — which packages are read and which types become definitions.
  • Names & $refs — the names definitions are published under and how references render.
  • Titles & descriptions — the human-readable text the spec carries.
  • Field types & formats — how an individual property renders.
  • Response bodies — describing a payload without a dedicated swagger:response struct.
  • Choose what gets scanned and which definitions land in the spec — package patterns and filters, when a type is emitted, pruning unreferenced models, overlaying an existing document, and build constraints.
  • Control the names definitions are published under and how references render — deconflicting collisions, deriving member names from struct tags, alias rendering, and a description sitting beside a $ref.
  • Shape the human-readable text — override godoc with API-facing title and description, route single-line comments to the description, keep annotations out of the godoc, and clean godoc doc-links out of generated prose.
  • Tune how an individual field renders — force a conformant format, mark pointer fields nullable, and control the x-go-* vendor extensions codescan emits.
  • Describe a concrete response payload without a dedicated swagger:response struct — declare the body inline on the route, or shadow a generic envelope’s payload with a doc-only struct.

Each guide 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. For the field-by-field meaning of every option, see the Options reference or the Options godoc.

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

Subsections of Shaping the output

Scope & discovery

These knobs decide the inputs and the surface of the scan: which packages codescan reads, which types become definitions, and how that set is trimmed or merged before anything is rendered.

  • 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.
  • Scan a shared library with swagger:model discovery, then keep only the definitions actually reachable from your API — the middle ground between “only what routes use” and “every model, used or not”.
  • 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.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Scope & discovery

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,
})

To produce several specs from one module — e.g. one per API version — run a scan per package tree (./v1/..., then ./v2/...) and write each result separately. There is no single-run “split by version”; the unit of a scan is the set of packages you pass.

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 Aug 1, 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; or
  • it is a subtype of an emitted discriminated base — a swagger:model that composes that base with swagger:allOf. This one runs against the reference direction (a subtype $refs its base, never the reverse), so it is the one case where a definition arrives without anything referencing it. See Polymorphic models.

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. For the opposite problem — a ScanModels scan that pulls in models you do not want, like Standalone — see Pruning unused models.

Generic and embedded types

codescan resolves types through go/packages type information, so two forms that look tricky still work:

  • Generics. An instantiated generic — WrappedRequest[Order], whether annotated swagger:parameters or swagger:model — emits the concrete type: the type argument is substituted, so a T-typed field becomes a $ref to the argument’s definition. The generic’s declaration may live in a different file from its instantiation. A free (un-instantiated) type parameter is skipped with a warning.
  • Embedded fields, including those from an external package or a type declared in another source file, are promoted into the embedding type (only exported members, recursively), and a custom field type resolves to its underlying type. An in: / required: annotation written on the embedded field itself applies to all the members it promotes. An embed that carries a json: tag (Base \json:“base”`) is **not** promoted — it nests as a single property of that name (a $ref` to the embedded type), matching Go’s own JSON encoding.
  • A type the scanner cannot model — e.g. a bare function type — is skipped with a warning rather than failing the whole scan; the annotated models around it are still emitted.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Pruning unused models

Options.ScanModels (the -m flag) publishes every swagger:model type it finds, whether or not anything references it — see When the scanner emits a type. That is exactly what you want when the annotated package is the contract. It is the wrong default when you point codescan at a large shared model library and only care about the slice your API actually exposes: the spec fills up with definitions no operation, parameter or response ever references.

Options.PruneUnusedModels is the middle ground. It runs swagger:model discovery as usual, then drops every discovered definition that is not reachable from your API surface.

Three emission modes

The same source renders three ways, depending on two options:

ModeOptionsWhat is emitted
Reachable only(default)Only models reachable from an operation, parameter or response — discovery-driven. A swagger:model that nothing references is not emitted.
Every modelScanModelsEvery swagger:model type, reachable or not. The library’s whole annotated surface lands in definitions.
Models, then prunedScanModels + PruneUnusedModelsDiscovery runs as in Every model, then the unreachable definitions are pruned away — you keep the reachable subset, including models discovered only because swagger:model published them.

PruneUnusedModels is a modifier on ScanModels. Without ScanModels the emitted set is already reachable-only, so the flag has nothing to do: it is a no-op and says so with a single informational diagnostic.

What counts as reachable

A definition survives the prune when it is reachable — directly or transitively through any $ref — from one of these roots:

  • an operation’s body parameters and response schemas;
  • a top-level shared response or parameter;
  • a definition supplied via InputSpec.

The walk follows references through every schema shape — properties, allOf / anyOf / oneOf, array items, additionalProperties, and so on — and terminates cleanly on recursive or cyclic models. A model referenced only by another unreferenced model is itself unreachable, so the whole dead subtree is removed, not just its entry point.

One rule does not follow a $ref: a reachable definition that declares a discriminator also keeps its subtypes. They compose the base rather than being referenced by it, so the walk cannot see them and a polymorphic family would otherwise be pruned down to its base alone. The family travels as a unit — an unreachable base is still dropped, together with its subtypes. See Polymorphic models.

Those shared response / parameter roots are pruned too. A shared parameter or response that no operation and no path-item references is itself dropped (with a scan.pruned-unused Hint) — and because that happens before the definition walk reads its roots from the same #/parameters / #/responses maps, a model kept alive only by a now-pruned shared object becomes prunable in turn. Shared objects supplied through InputSpec are pinned, exactly like definitions.

Info

Definitions you supply through InputSpec are pinned: they are never pruned, and they seed the reachability roots, so anything they $ref survives too. The prune only ever removes definitions codescan discovered, never ones you handed it.

Pruning happens before name resolution

This is the part that makes pruning more than a convenience. codescan keys every definition by a compiler-unique identity while it builds, then a final stage projects each one back to the shortest unique name — deconflicting cross-package collisions along the way (billing.Account / identity.AccountBillingAccount / IdentityAccount; see Resolving $ref name conflicts).

PruneUnusedModels runs before that name-resolution stage. So when one half of a colliding pair is unused, it is pruned first — and the collision never happens. The surviving model keeps its clean, unqualified name instead of being pushed to a package-qualified one to avoid a twin that is not even in your spec. Pruning a shared library this way removes a whole class of surprising #/definitions/<Pkg><Name> renames that only existed because of models you were not using.

Diagnostics

The prune is never silent. Through the OnDiagnostic sink codescan reports:

  • scan.pruned-unused — one informational diagnostic per pruned definition, located at the originating Go type, so you can see exactly what was dropped and why; and the single no-op notice when the flag is set without ScanModels.
  • scan.renamed-definition — one per collision the name stage did resolve, located at the Go type, recording the final name it landed under. With pruning on, collisions that vanish produce no such diagnostic at all.

When to use it

  • Reach for PruneUnusedModels when you scan a shared or third-party model package with -m and want only the definitions your API actually exposes — the reachable subset, with the noise dropped and the collision churn gone.
  • Stay on plain ScanModels when the annotated package is itself the published contract and every swagger:model is meant to appear.
  • Stay on the default (neither flag) when you only ever want what your routes reference; there is nothing extra to discover or prune.

What’s next

Last edited by: fredbi Aug 1, 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 Aug 1, 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 Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Names & $refs

Once codescan knows which definitions to emit, these knobs govern how they are named and referenced: the definition names that form your published $ref contract, where member names come from, and the shape a reference takes in the output.

  • When two Go types want the same definition name, codescan keeps them distinct with deterministic, package-qualified names — and you stay in control of the $ref names that form your published contract.
  • Derive property, parameter and header names from a struct tag other than json (form, xml, …) via NameFromTags.
  • Emit interface-method property names verbatim (ID, CreatedAt) instead of the auto-jsonified spelling (id, createdAt), with SkipJSONifyInterfaceMethods.
  • Choose how Go type aliases render — dissolved to their target, or exposed as a first-class $ref via swagger:model, with RefAliases / TransparentAliases.
  • Render a plain struct embed as an allOf composition — a $ref to the embedded model plus a sibling member for the embedding struct’s own fields — instead of inlining the promoted properties, with DefaultAllOfForEmbeds.
  • Control how a field’s description and extensions are rendered when its type resolves to a $ref — wrapped in an allOf, emitted as direct siblings (EmitRefSiblings), or dropped (SkipAllOfCompounding).
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Names & $refs

Resolving $ref name conflicts

A Swagger definition is keyed by a single short name (#/definitions/Account), but a Go program routinely has several types that would map to that name — the same leaf declared in different packages, or a swagger:model Account override applied twice. codescan keys every definition by a compiler-unique identity (<package-path>/<name>) while it builds, then a final reduce stage projects each identity back to the shortest name that is still unique. The result is deterministic regardless of discovery or map-iteration order: no silent overwrite, no lost definition.

The panes below are backed by the test-covered docs/examples/shaping/nameconflicts package tree.

When names collide

Two packages each declare an Account, with entirely different fields:

// Account is the billing view of a customer account.
//
// swagger:model Account
type Account struct {
	// the current balance, in minor units
	Balance int64 `json:"balance"`
	// the ISO-4217 currency code
	Currency string `json:"currency"`
}

Full source: docs/examples/shaping/nameconflicts/billing/account.go

// Account is the identity view of a customer account.
//
// swagger:model Account
type Account struct {
	// the login email
	Email string `json:"email"`
	// whether the email has been verified
	Verified bool `json:"verified"`
}

Full source: docs/examples/shaping/nameconflicts/identity/account.go

A Dashboard model references both, so they are discovered together:

// Dashboard references the same-named Account from two packages plus both
// ledger entries, forcing all of them to be discovered together. The two
// Accounts collide on the short name and are deconflicted by package segment;
// the refs below point at the resolved names, never a bare "Account".
//
// swagger:model Dashboard
type Dashboard struct {
	Billing  billing.Account  `json:"billing"`
	Identity identity.Account `json:"identity"`
	// the ledger entry that kept the name "Entry"
	Primary ledger.Entry `json:"primary"`
	// the duplicate that reverted to its Go name "Reversal"
	Secondary ledger.Reversal `json:"secondary"`
}

Full source: docs/examples/shaping/nameconflicts/doc.go

Before name-identity, the two would have merged onto a single #/definitions/Account — a union of fields, last package wins, non-deterministically. Now each keeps its own definition and the references resolve to the deconflicted names:

{
  "description": "Dashboard references the same-named Account from two packages plus both\nledger entries, forcing all of them to be discovered together. The two\nAccounts collide on the short name and are deconflicted by package segment;\nthe refs below point at the resolved names, never a bare \"Account\".",
  "type": "object",
  "properties": {
    "billing": {
      "$ref": "#/definitions/BillingAccount"
    },
    "identity": {
      "$ref": "#/definitions/IdentityAccount"
    },
    "primary": {
      "$ref": "#/definitions/Entry"
    },
    "secondary": {
      "$ref": "#/definitions/Reversal"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nameconflicts"
}

Full source: docs/examples/shaping/nameconflicts/testdata/dashboard.json

How codescan resolves them automatically

The reduce stage gives every reachable identity the shortest acceptable name:

  • A globally unique leaf is lifted to its bare name — byte-identical to the pre-feature output, so the common case sees zero churn.
  • A colliding leaf is qualified with the minimal-depth PascalCase concat of its nearest package segments (billing.Account / identity.AccountBillingAccount / IdentityAccount), deepening one segment at a time until the whole group is unique. A validate.colliding-model-name diagnostic records each rename.

Every emitted definition also carries an x-go-package extension recording the source package, so even identically-shaped collisions stay traceable:

{
  "type": "object",
  "title": "Account is the billing view of a customer account.",
  "properties": {
    "balance": {
      "description": "the current balance, in minor units",
      "type": "integer",
      "format": "int64",
      "x-go-name": "Balance"
    },
    "currency": {
      "description": "the ISO-4217 currency code",
      "type": "string",
      "x-go-name": "Currency"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nameconflicts/billing"
}

Full source: docs/examples/shaping/nameconflicts/testdata/billingaccount.json

Info

The whole pass is a pure function of the reachable identity set, so the names are stable across runs — but they are derived from your package paths. Renaming or moving a package changes the segment used in a qualified name. Pin the names that matter (see below).

Same-package duplicates

A single package cannot own a definition name twice. If two Go types in the same package both claim swagger:model Entry, codescan keeps one (deterministically) and reverts the other to its Go type name, with a validate.duplicate-model-name diagnostic:

// Entry keeps the contested name: the definition is "Entry".
//
// swagger:model Entry
type Entry struct {
	Debit int64 `json:"debit"`
}

// Reversal also asks for "Entry". The name is already taken in this package, so
// it reverts to its Go name, "Reversal", and a diagnostic is raised.
//
// swagger:model Entry
type Reversal struct {
	Credit int64 `json:"credit"`
}

Full source: docs/examples/shaping/nameconflicts/ledger/ledger.go

Here Entry keeps the contested name and Reversal falls back to its Go name — the Dashboard refs above point at #/definitions/Entry and #/definitions/Reversal, never a merged Entry. This is a genuine authoring error (one package, one name); the fallback keeps the spec valid rather than silently dropping a model.

Referencing a model by leaf across packages

The type-name keywords — swagger:type, swagger:additionalProperties, and swagger:patternProperties — accept a bare leaf as their argument. codescan resolves it the same way the reduce stage does: the annotating type’s own package first, then uniquely across the scanned model set. A leaf unique in another package resolves to a $ref:

swagger:additionalProperties Widget
// Bag is an open object whose additional properties are catalog.Widget,
// named by the bare leaf "Widget" — resolved cross-package because that leaf is
// unique across the scanned model set.
//
// swagger:model Bag
// swagger:additionalProperties Widget
type Bag struct {
	ID string `json:"id"`
}

Full source: docs/examples/shaping/nameconflicts/doc.go

#/definitions/Bag
{
  "description": "named by the bare leaf \"Widget\" — resolved cross-package because that leaf is\nunique across the scanned model set.",
  "type": "object",
  "title": "Bag is an open object whose additional properties are catalog.Widget,",
  "properties": {
    "id": {
      "type": "string",
      "x-go-name": "ID"
    }
  },
  "additionalProperties": {
    "$ref": "#/definitions/Widget"
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nameconflicts"
}

Full source: docs/examples/shaping/nameconflicts/testdata/bag.json

If the leaf matches a model in several packages it is ambiguous: the reference is dropped (never guessed) and a validate.ambiguous-type-name diagnostic is raised. Disambiguate with a same-package type or pin the target with a swagger:model <Name> override. The same leaf rule applies to the additionalProperties: / swagger:patternProperties value forms covered in Maps & free-form objects.

Keeping the exposed names under your control

The generated $ref names are part of your published contract, so the author — not the resolver — should decide the ones that matter:

  • Pin a public name with an explicit swagger:model <Name>. A pinned name is the identity’s leaf, so two pinned names that still collide are deconflicted by package segment exactly like inferred ones — pin distinct names for the types in your public surface.
  • Let auto-resolution handle the rest. Incidental or internal collisions get a valid, stable, package-qualified name with no action from you.

Tuning the qualified names

Two scanner options steer the rare, deep collisions:

OptionDefaultEffect
NameConcatBudget0.65Readability cutoff in [0,1] (lower is more readable). A collision group whose best flat concat scores above the budget becomes a candidate for the hierarchical fallback. Raise toward 1.0 to accept longer concats; lower to fall back sooner.
EmitHierarchicalNamesfalseOpt into the fallback: over-budget groups are emitted as nested container definitions (#/definitions/<pkg>/<Name>, each tagged with x-go-package) instead of a long flat concat.
Warning

EmitHierarchicalNames is off by default on purpose. A nested definition is a deep JSON pointer that only ExpandSpec resolves, and a definitions-enumerating consumer (e.g. go-swagger codegen, one model per entry) sees the container nodes rather than the models. The always-correct flat concat stays the default; enable the nested shape only when you prefer it for the over-budget tail.

When to tune vs. let it auto-resolve

  • Pin the names that appear in your public API contract — clients, generated SDKs, and hand-written $refs depend on them.
  • Let auto-resolution handle incidental collisions between internal types; the qualified names are valid and stable.
  • Reach for EmitHierarchicalNames only when a few collision groups have package names long enough to make the flat concat unwieldy, and your consumers resolve $ref pointers (rather than enumerating definitions).

What’s next

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

Naming from struct tags

By default codescan derives a field’s spec name from its json: tag (then the Go field name). Options.NameFromTags lets you choose which struct-tag types supply the name, in precedence order — handy when your structs are tagged for another binding library (for example gin’s form:). It applies everywhere a name is derived from a field: schema properties, parameters, and response headers. The model below tags every field with both json: and form::

// Filter is a query model whose fields carry both json: and form: tags.
//
// swagger:model
type Filter struct {
	// SortKey selects the sort column.
	SortKey string `json:"sortKey" form:"sort_key"`

	// PageSize bounds the page length.
	PageSize int `json:"pageSize" form:"page_size"`
}

Full source: docs/examples/shaping/naming-from-tags/naming.go

Scanned with the default (["json"]) and with ["form","json"], the property names differ — form: wins because it is listed first:

Default (json)
{
  "type": "object",
  "title": "Filter is a query model whose fields carry both json: and form: tags.",
  "properties": {
    "pageSize": {
      "description": "PageSize bounds the page length.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "PageSize"
    },
    "sortKey": {
      "description": "SortKey selects the sort column.",
      "type": "string",
      "x-go-name": "SortKey"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/naming-from-tags"
}

Full source: docs/examples/shaping/naming-from-tags/testdata/default.json

NameFromTags: [form, json]
{
  "type": "object",
  "title": "Filter is a query model whose fields carry both json: and form: tags.",
  "properties": {
    "page_size": {
      "description": "PageSize bounds the page length.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "PageSize"
    },
    "sort_key": {
      "description": "SortKey selects the sort column.",
      "type": "string",
      "x-go-name": "SortKey"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/naming-from-tags"
}

Full source: docs/examples/shaping/naming-from-tags/testdata/form.json

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

The first listed tag that supplies a usable name wins; a tag that is absent or carries only options (e.g. ,omitempty) is skipped and the next is tried. An explicit empty list (NameFromTags: []string{}) consults no tag and falls back to the Go field name.

Info

Name only. NameFromTags changes only the name. The encoding/json directives — json:"-" (exclude), ,omitempty, ,string — are always read from the json tag, whatever names the field. Targeted renames (the name: keyword, swagger:name, and swagger:model {name}) still take precedence over any tag-derived name.

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

Interface-method property names

When a model’s shape is described by an interface, its methods have no natural JSON serialization — Go’s encoding/json can’t marshal interface methods, so there’s no struct tag to read a name from. codescan invents one by running its jsonify transform on the Go method name: IDid, CreatedAtcreatedAt. That “one size fits all” convention isn’t always what you want — an interface already named for its JSON shape, or a codebase with its own canonical-name discipline, wants the Go name kept as-is.

SkipJSONifyInterfaceMethods opts out of the mangler. With it set, an interface-method property is emitted under the Go method name verbatim. It is an opt-out and defaults to off; with it off, output is unchanged.

What changes

This model is an interface with two default-path methods and one carrying a swagger:name override:

// Account is a read model whose shape is described by interface methods.
//
// swagger:model Account
type Account interface {
	// ID is emitted as "id" by default; verbatim "ID" with the opt-out set.
	ID() string

	// CreatedAt is emitted as "createdAt" by default; verbatim "CreatedAt" with
	// the opt-out set.
	CreatedAt() string

	// swagger:name explicit_name
	//
	// A swagger:name override is taken verbatim either way — re-mangling would
	// camelCase it to "explicitName".
	OverriddenField() string
}

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

Scanned with the flag off the method names auto-jsonify; on, they ride through verbatim:

Default — jsonified
{
  "type": "object",
  "title": "Account is a read model whose shape is described by interface methods.",
  "properties": {
    "createdAt": {
      "description": "CreatedAt is emitted as \"createdAt\" by default; verbatim \"CreatedAt\" with\nthe opt-out set.",
      "type": "string",
      "x-go-name": "CreatedAt"
    },
    "explicit_name": {
      "description": "\nA swagger:name override is taken verbatim either way — re-mangling would\ncamelCase it to \"explicitName\".",
      "type": "string",
      "x-go-name": "OverriddenField"
    },
    "id": {
      "description": "ID is emitted as \"id\" by default; verbatim \"ID\" with the opt-out set.",
      "type": "string",
      "x-go-name": "ID"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/interfacenames"
}

Full source: docs/examples/shaping/interfacenames/testdata/account_off.json

SkipJSONifyInterfaceMethods — verbatim
{
  "type": "object",
  "title": "Account is a read model whose shape is described by interface methods.",
  "properties": {
    "CreatedAt": {
      "description": "CreatedAt is emitted as \"createdAt\" by default; verbatim \"CreatedAt\" with\nthe opt-out set.",
      "type": "string"
    },
    "ID": {
      "description": "ID is emitted as \"id\" by default; verbatim \"ID\" with the opt-out set.",
      "type": "string"
    },
    "explicit_name": {
      "description": "\nA swagger:name override is taken verbatim either way — re-mangling would\ncamelCase it to \"explicitName\".",
      "type": "string",
      "x-go-name": "OverriddenField"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/interfacenames"
}

Full source: docs/examples/shaping/interfacenames/testdata/account_on.json

Reading the two panes:

  • Default-path methods are jsonified. ID()id, CreatedAt()createdAt; the original Go name is preserved as the x-go-name extension.
  • With the opt-out, the Go name is the property name. ID and CreatedAt appear verbatim — and x-go-name drops, since it would now just repeat the property name.
  • A swagger:name override is verbatim either way. OverriddenField is published as explicit_name in both panes — the override already bypasses the mangler, so the flag never touches it (and never re-mangles it to explicitName).
Note

This flag only affects interface methods, which have no JSON serialization to mirror. Struct-field property names are untouched — they always reflect what encoding/json actually produces (see Naming from struct tags to source those from a different tag).

What’s next

Last edited by: fredbi Aug 1, 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/MoneyPrice 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:

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

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

// Fee is a FIRST-CLASS alias: the swagger:model annotation keeps the alias name
// in the spec instead of dissolving it to Amount.
//
// swagger:model
type Fee = Amount

// Receipt references the alias, not the target.
//
// swagger:model
type Receipt struct {
	// Charge is the fee charged.
	Charge Fee `json:"charge"`
}

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

Two top-level options then govern how that first-class alias definition is shaped. The panes below are the same package scanned under each.

Default — the alias definition is a copy

Fee is emitted as a structural duplicate of Amount, and Receipt.charge points at the alias:

Default (expand)
{
  "Amount": {
    "type": "object",
    "title": "Amount is the underlying model.",
    "properties": {
      "cents": {
        "description": "Cents is the amount in cents.",
        "type": "integer",
        "format": "int64",
        "x-go-name": "Cents"
      },
      "currency": {
        "description": "Currency is the ISO currency code.",
        "type": "string",
        "x-go-name": "Currency"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  },
  "Fee": {
    "description": "Fee is a FIRST-CLASS alias: the swagger:model annotation keeps the alias name\nin the spec instead of dissolving it to Amount.",
    "type": "object",
    "properties": {
      "cents": {
        "description": "Cents is the amount in cents.",
        "type": "integer",
        "format": "int64",
        "x-go-name": "Cents"
      },
      "currency": {
        "description": "Currency is the ISO currency code.",
        "type": "string",
        "x-go-name": "Currency"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  },
  "Receipt": {
    "type": "object",
    "title": "Receipt references the alias, not the target.",
    "properties": {
      "charge": {
        "$ref": "#/definitions/Fee"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  }
}

Full source: docs/examples/shaping/aliases-firstclass/testdata/expand.json

RefAliases: true
{
  "Amount": {
    "type": "object",
    "title": "Amount is the underlying model.",
    "properties": {
      "cents": {
        "description": "Cents is the amount in cents.",
        "type": "integer",
        "format": "int64",
        "x-go-name": "Cents"
      },
      "currency": {
        "description": "Currency is the ISO currency code.",
        "type": "string",
        "x-go-name": "Currency"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  },
  "Fee": {
    "description": "Fee is a FIRST-CLASS alias: the swagger:model annotation keeps the alias name\nin the spec instead of dissolving it to Amount.",
    "$ref": "#/definitions/Amount"
  },
  "Receipt": {
    "type": "object",
    "title": "Receipt references the alias, not the target.",
    "properties": {
      "charge": {
        "$ref": "#/definitions/Fee"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  }
}

Full source: docs/examples/shaping/aliases-firstclass/testdata/refaliases.json

RefAliases: true — the alias definition is a $ref chain

The right pane above: Fee becomes {"$ref": "#/definitions/Amount"}. One shape, two names — the alias survives at use sites without duplicating the target’s properties. Prefer this over the default whenever the alias is genuinely a synonym: a copy drifts the moment the target changes.

TransparentAliases: true — use sites dissolve

{
  "Amount": {
    "type": "object",
    "title": "Amount is the underlying model.",
    "properties": {
      "cents": {
        "description": "Cents is the amount in cents.",
        "type": "integer",
        "format": "int64",
        "x-go-name": "Cents"
      },
      "currency": {
        "description": "Currency is the ISO currency code.",
        "type": "string",
        "x-go-name": "Currency"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  },
  "Fee": {
    "description": "Fee is a FIRST-CLASS alias: the swagger:model annotation keeps the alias name\nin the spec instead of dissolving it to Amount.",
    "type": "object",
    "properties": {
      "cents": {
        "description": "Cents is the amount in cents.",
        "type": "integer",
        "format": "int64",
        "x-go-name": "Cents"
      },
      "currency": {
        "description": "Currency is the ISO currency code.",
        "type": "string",
        "x-go-name": "Currency"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  },
  "Receipt": {
    "type": "object",
    "title": "Receipt references the alias, not the target.",
    "properties": {
      "charge": {
        "$ref": "#/definitions/Amount"
      }
    },
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass"
  }
}

Full source: docs/examples/shaping/aliases-firstclass/testdata/transparent.json

Receipt.charge now points straight at #/definitions/Amount: the alias is gone from the reference graph.

Warning

Note what did not happen: Fee is still emitted. TransparentAliases governs how an alias renders at its use sites, not whether an annotated declaration produces a definition — so with ScanModels you get a Fee definition that nothing references. Add PruneUnusedModels to drop it, or simply do not annotate an alias you intend to dissolve.

The three modes at a glance:

Fee definitionReceipt.charge
default (expand)copy of Amount$ref: Fee
RefAliases: true$ref: Amount$ref: Fee
TransparentAliases: truecopy of Amount, unreferenced$ref: Amount

Wider calibration lives in 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.

The swagger:alias annotation is deprecated and has no effect — alias rendering is governed by the plain Go alias plus these options, or by swagger:model for a first-class definition.

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

Composing embeds with allOf

When a struct embeds another struct, Go promotes the embedded fields, and by default codescan mirrors that: the embedded type’s properties are inlined flat into the embedding schema. That is faithful to the Go value, but it loses the “this composes Base relationship — every embedding model emits its own flat copy of the embedded fields, and a client generator can’t recover the shared base type.

DefaultAllOfForEmbeds changes that. With the option on, a plain embed (one with no explicit name and no swagger:allOf tag) is rendered as an allOf member — exactly as if it carried swagger:allOf — so the composition relationship survives in the spec. It is opt-in and defaults to off; with it off, output is byte-identical to before.

What composes

This model embeds a swagger:model type (Base), a non-model type (Mixin), and adds an own field:

// Base is a reusable base model.
//
// swagger:model Base
type Base struct {
	ID   int64  `json:"id"`
	Name string `json:"name"`
}

// Mixin is a non-model embedded type (no swagger:model), reachable only through
// embedding. Under the flag it composes as an inline allOf member, since it has
// no definition of its own to $ref.
type Mixin struct {
	Note string `json:"note"`
}

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

// PlainEmbed embeds a model and a non-model plainly, plus an own field.
//
// swagger:model PlainEmbed
type PlainEmbed struct {
	Base
	Mixin

	Color string `json:"color"`
}

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

Scanned with the flag off the embedded properties inline flat; on, the embed becomes an allOf composition:

Default — inlined
{
  "type": "object",
  "title": "PlainEmbed embeds a model and a non-model plainly, plus an own field.",
  "properties": {
    "color": {
      "type": "string",
      "x-go-name": "Color"
    },
    "id": {
      "type": "integer",
      "format": "int64",
      "x-go-name": "ID"
    },
    "name": {
      "type": "string",
      "x-go-name": "Name"
    },
    "note": {
      "type": "string",
      "x-go-name": "Note"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/embedallof"
}

Full source: docs/examples/shaping/embedallof/testdata/plainembed_off.json

DefaultAllOfForEmbeds — composed
{
  "title": "PlainEmbed embeds a model and a non-model plainly, plus an own field.",
  "allOf": [
    {
      "$ref": "#/definitions/Base"
    },
    {
      "type": "object",
      "properties": {
        "note": {
          "type": "string",
          "x-go-name": "Note"
        }
      }
    },
    {
      "type": "object",
      "properties": {
        "color": {
          "type": "string",
          "x-go-name": "Color"
        }
      }
    }
  ],
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/embedallof"
}

Full source: docs/examples/shaping/embedallof/testdata/plainembed_on.json

Reading the composed pane, each embed takes the path its kind dictates:

  • A model embed becomes a $ref member. Base is a swagger:model, so it has its own definition and composes as {$ref: "#/definitions/Base"} — no copy of id / name.
  • A non-model embed becomes an inline member. Mixin carries no swagger:model, so it has no definition to point at; its note property rides an inline allOf member instead.
  • The embedding struct’s own fields move to a sibling member. color is no longer a top-level property — it lands in its own allOf arm alongside the composed embeds.

What’s left alone

The flag only changes the untagged, unnamed embed — every other embed shape is unaffected:

// PointerEmbed embeds a model through a pointer; the pointer is peeled and
// takes the same $ref path as a value embed.
//
// swagger:model PointerEmbed
type PointerEmbed struct {
	*Base

	Tag string `json:"tag"`
}

// NamedEmbed embeds Base under an explicit json name, so Go does not promote
// it: it stays a single nested property, identical on or off (go-swagger#2038).
//
// swagger:model NamedEmbed
type NamedEmbed struct {
	Base `json:"base"`

	Extra string `json:"extra"`
}

// TaggedEmbed already composes Base via an explicit swagger:allOf tag, so the
// flag does not change its shape.
//
// swagger:model TaggedEmbed
type TaggedEmbed struct {
	// swagger:allOf
	Base

	Field string `json:"field"`
}

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

  • Pointer embeds are peeled first, so *Base composes to the same $ref member as a value embed.
  • A json-named embed is not a promotion. Giving the embed a json tag (Base \json:“base”`) makes it a single nested property named base`, on or off — Go doesn’t promote a named embed (go-swagger#2038).
  • An explicit swagger:allOf embed already composes, so the flag is a no-op for it; it only makes allOf the default for untagged embeds.
  • Interface embeds compose via allOf regardless of this flag.
Note

DefaultAllOfForEmbeds is the global default-on switch for the same shape swagger:allOf produces per-embed. Reach for the annotation when only some embeds should compose; reach for the option when composition is your house style for every plain embed.

When an override cannot be composed

Composition has one limit worth knowing. Inlining an embed resolves an override — a field the enclosing struct re-declares wins, exactly as Go’s depth rule decides it. allOf instead accumulates: members conjoin, and a conjunction can only narrow, never replace. So a re-declaration that replaces is not expressible as composition:

  • re-declaring a promoted field to decorate it (add readOnly, a description, a validation) leaves the property in both members — valid, but a generator walking the members sees it twice;
  • re-declaring it with a different type yields {type: integer} and {type: string} for one property — a schema nothing can satisfy.

codescan does not guess which declaration you meant: many Go types can be written whose composition has no faithful schema, and inventing one would be deciding your intent. Resolve it yourself with swagger:omit on the embed, which drops the promoted twin so only your re-declaration survives:

type Decorated struct {
	// swagger:omit ID
	Base

	// ID is assigned by the server.
	//
	// read only: true
	ID int64
}

What’s next

Last edited by: fredbi Aug 1, 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 Go type resolves to a named model, the field becomes a $ref. Strict JSON Schema draft 4 (the dialect OpenAPI 2.0 is built on) says a $ref replaces its siblings — so a description, a validation, or an x-* extension written on that field cannot simply sit next to the $ref.

codescan’s default is to preserve those decorations by wrapping the reference in an allOf compound, which is the draft-4-correct shape. Three options tune this behaviour. The decorations split into two classes:

  • description & extensionssiblings-eligible: modern tooling (OpenAPI 3.1 / JSON Schema 2020-12, most Swagger-UI renderers) reads them directly beside a $ref.
  • validations & externalDocscompound-only: they have no valid bare-$ref form, so they can only ride an allOf compound.
// 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 decorated with a description and a
// vendor extension — both can, in principle, sit beside the $ref. How they are
// rendered depends on the options.
//
// swagger:model
type Person struct {
	// Home is where the person lives.
	//
	// extensions:
	//   x-ui-order: 3
	Home Address `json:"home"`
}

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

The default — an allOf wrapper

With no options set, the field’s description and extension are preserved by wrapping the $ref as the single member of an allOf; the decorations ride the outer schema:

{
  "description": "Home is where the person lives.",
  "allOf": [
    {
      "$ref": "#/definitions/Address"
    }
  ],
  "x-go-name": "Home",
  "x-ui-order": 3
}

Full source: docs/examples/shaping/refsiblings/testdata/default.json

This is the always-correct shape and needs no configuration — see also Decorating a $ref in the Model definitions tutorial.

Emit siblings directly — EmitRefSiblings

Set Options.EmitRefSiblings to render the description and extensions as direct siblings of the $ref, with no allOf wrapper — the leaner shape modern tools expect:

codescan.Run(&codescan.Options{
    Packages:        []string{"./..."},
    ScanModels:      true,
    EmitRefSiblings: true,
})
Default — allOf wrapper
{
  "description": "Home is where the person lives.",
  "allOf": [
    {
      "$ref": "#/definitions/Address"
    }
  ],
  "x-go-name": "Home",
  "x-ui-order": 3
}

Full source: docs/examples/shaping/refsiblings/testdata/default.json

EmitRefSiblings: true
{
  "description": "Home is where the person lives.",
  "x-ui-order": 3,
  "$ref": "#/definitions/Address"
}

Full source: docs/examples/shaping/refsiblings/testdata/siblings.json

Info

EmitRefSiblings only changes the cases where nothing else forces a compound. When the field also carries a validation or externalDocs (which cannot live beside a bare $ref), the allOf wrapper is still emitted and the description / extensions ride its outer schema.

Drop the compound entirely — SkipAllOfCompounding

Some downstream consumers — notably go-swagger’s code generator — expect a field that points at a model to be a bare $ref and do not handle the allOf-compounded shape. Set Options.SkipAllOfCompounding to never emit an allOf compound:

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

No compound is produced, so validations and externalDocs are dropped, and the description and extension go with them — leaving a bare $ref:

{
  "$ref": "#/definitions/Address"
}

Full source: docs/examples/shaping/refsiblings/testdata/skip.json

Every dropped decoration is reported through Options.OnDiagnostic (code validate.dropped-ref-sibling), so the loss is never silent. Combine it with EmitRefSiblings to keep the description and extensions as siblings while still dropping the compound-only validations:

codescan.Run(&codescan.Options{
    Packages:             []string{"./..."},
    ScanModels:           true,
    EmitRefSiblings:      true, // keep description / x-* as $ref siblings
    SkipAllOfCompounding: true, // drop validations / externalDocs, no allOf
})
Note

required: is never affected by any of these options. It is a property of the parent object (it lands in the parent’s required list), not a sibling of the $ref, so it is always preserved.

DescWithRef (deprecated)

Options.DescWithRef predates EmitRefSiblings and covers only the narrow description-only case: a $ref’d field whose sole decoration is a description. By default that description is dropped; DescWithRef preserves it 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

Warning

DescWithRef is deprecated — prefer EmitRefSiblings, which preserves both descriptions and extensions (as direct siblings). DescWithRef keeps its original behaviour for compatibility and is a no-op when EmitRefSiblings is set.

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

Titles & descriptions

The same Go doc comments feed both pkg.go.dev and your API documentation, and the two audiences rarely want the exact same words. These knobs let you keep a concise godoc while curating the title / description text the spec carries.

  • Replace the godoc-derived title and description with API-facing text using swagger:title and swagger:description — on models, fields, $ref’d fields and responses.
  • Carry a verbatim markdown body — tables, blank lines, indentation and all — into a description with the swagger:description | literal block-scalar marker, instead of letting Option B fold it.
  • Let swagger annotations live inside a struct body or as trailing comments so the godoc above each declaration stays clean — the AfterDeclComments opt-in.
  • Strip godoc doc-link brackets from generated descriptions and recompose resolvable links to each schema’s exposed name — the CleanGoDoc opt-in.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Titles & descriptions

Overriding titles & descriptions

A Go doc comment is written for Go readers. The same prose is not always what you want in the published API — a comment may explain internal usage, reference Go types, or simply read awkwardly to an API consumer. swagger:title and swagger:description let the spec text diverge from the godoc: the annotation replaces the prose-derived value, leaving the Go comment free to say whatever Go developers need.

This is the explicit counterpart to Single-line comments, which controls how a plain comment is implicitly routed to title vs description. Each pane below pairs the annotated Go (left) with the exact fragment the scanner emits (right), from the test-covered docs/examples/shaping/overrides package.

Overriding a model and its fields

swagger:title <text> sets the title; swagger:description <text> sets the description. Both sit in the comment block beside swagger:model (on a type) or beside a field’s other keywords. The model’s Go-facing godoc here is replaced wholesale by the two overrides:

// Widget is the Go-facing widget doc, written for Go readers.
//
// It explains internal Go usage that should not leak into the API spec.
//
// swagger:model
// swagger:title A Public Widget
// swagger:description A widget exposed via the public API.
type Widget struct {
	// ID explains the Go field for Go readers.
	//
	// swagger:description The unique widget identifier.
	ID string `json:"id"`

	// Label is the Go-facing field doc. Fields carry no title by default;
	// the override is the only way a property gets one.
	//
	// swagger:title Display Label
	// swagger:description Human-readable label shown to API consumers.
	Label string `json:"label"`

	// Plain keeps its godoc description because it carries no override.
	Plain string `json:"plain"`

	// Capacity combines a description override with an inline validation
	// keyword on the same field: the override applies AND maximum is kept,
	// because the override annotations dispatch through the schema family.
	//
	// swagger:description The maximum capacity, in liters.
	// maximum: 1000
	Capacity int64 `json:"capacity"`

	// Suppressed has a godoc that a bare swagger:description suppresses: the
	// empty value is applied (description omitted) and scan.empty-override is
	// raised, in case the bare marker was left behind by mistake.
	//
	// swagger:description
	Suppressed string `json:"suppressed"`

	// Notes carries a multi-line description override: the lines following the
	// annotation fold into the description until the blank line, joined with
	// newlines.
	//
	// swagger:description Free-form notes about the widget.
	// They may span several lines, all folded into one description.
	//
	// The blank line above terminates the override body; this paragraph is
	// ordinary godoc and is discarded (the override won).
	Notes string `json:"notes"`

	// Gadget is a $ref field carrying title + description overrides. They are
	// symmetric $ref siblings: kept under EmitRefSiblings, dropped to a bare
	// $ref under the default flags — the same rule a prose description follows.
	//
	// swagger:title Gadget Ref
	// swagger:description The attached gadget, described for API consumers.
	Gadget Gadget `json:"gadget"`
}

// Gadget is a plain referenced model.
//
// swagger:model
type Gadget struct {
	Serial string `json:"serial"`
}

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

{
  "description": "A widget exposed via the public API.",
  "type": "object",
  "title": "A Public Widget",
  "properties": {
    "capacity": {
      "description": "The maximum capacity, in liters.",
      "type": "integer",
      "format": "int64",
      "maximum": 1000,
      "x-go-name": "Capacity"
    },
    "gadget": {
      "$ref": "#/definitions/Gadget"
    },
    "id": {
      "description": "The unique widget identifier.",
      "type": "string",
      "x-go-name": "ID"
    },
    "label": {
      "description": "Human-readable label shown to API consumers.",
      "type": "string",
      "title": "Display Label",
      "x-go-name": "Label"
    },
    "notes": {
      "description": "Free-form notes about the widget.\nThey may span several lines, all folded into one description.",
      "type": "string",
      "x-go-name": "Notes"
    },
    "plain": {
      "description": "Plain keeps its godoc description because it carries no override.",
      "type": "string",
      "x-go-name": "Plain"
    },
    "suppressed": {
      "type": "string",
      "x-go-name": "Suppressed"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/overrides"
}

Full source: docs/examples/shaping/overrides/testdata/widget.json

A few things to read out of that pane:

  • title on a property comes only from an override. A field’s godoc becomes its description; codescan never derives a property title from prose, so swagger:title (as on label) is the only way to set one.
  • plain keeps its godoc — no override means no change. Overrides are strictly opt-in; un-annotated declarations behave exactly as before.

Multi-line descriptions

swagger:description may span several lines. The lines immediately following the annotation fold into one description (joined with newlines) and the body terminates at the first blank line, keyword, annotation, or end of comment. The notes field above shows this: its two prose lines fold together, and the ordinary godoc paragraph after the blank line is discarded.

To carry a body past a blank line — a markdown table, a multi-paragraph description — end the annotation line with a | literal block marker; see Markdown descriptions.

Keeping a co-located validation keyword

Because the override annotations dispatch through the schema family, a validation keyword on the same field still applies — they co-exist rather than one shadowing the other. The capacity field carries both swagger:description and maximum: 1000, and the output keeps both.

Suppressing a godoc comment

A bare swagger:description (no text, empty body) applies the empty value — a deliberate way to drop a godoc comment from the spec without deleting it from the source. Because a stray bare marker could also be an accident, codescan raises a scan.empty-override warning through OnDiagnostic. The suppressed field above emits no description at all.

Overrides beside a $ref

title and description are symmetric $ref siblings: on a field whose Go type is a referenced model, they follow the same preservation rule a prose description does. Under the default flags they drop to a bare $ref; with EmitRefSiblings they ride alongside the $ref as direct siblings.

Default — dropped to a bare $ref
{
  "$ref": "#/definitions/Gadget"
}

Full source: docs/examples/shaping/overrides/testdata/gadget_bare.json

EmitRefSiblings — kept as siblings
{
  "description": "The attached gadget, described for API consumers.",
  "title": "Gadget Ref",
  "$ref": "#/definitions/Gadget"
}

Full source: docs/examples/shaping/overrides/testdata/gadget_siblings.json

Responses and headers

swagger:description also overrides the description of a swagger:response and of its response headers. OpenAPI 2.0 Response and Header objects have no title field, so a swagger:title on a response or header is rejected with a parse.context-invalid diagnostic — the description override still applies.

// ErrorResponse is the Go-facing response doc, written for Go readers — it
// should not leak into the API spec.
//
// swagger:response errorResponse
// swagger:description The error payload returned to API consumers.
// swagger:title Ignored — responses have no title
type ErrorResponse struct {
	// XErrorCode is the Go-facing header doc.
	//
	// swagger:description The machine-readable error code.
	XErrorCode string `json:"X-Error-Code"`

	// ErrorBody carries the structured error.
	//
	// in: body
	Body ErrorBody `json:"body"`
}

// ErrorBody is the error payload returned in the response body.
//
// swagger:model
type ErrorBody struct {
	Message string `json:"message"`
}

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

{
  "description": "The error payload returned to API consumers.",
  "schema": {
    "$ref": "#/definitions/ErrorBody"
  },
  "headers": {
    "X-Error-Code": {
      "type": "string",
      "description": "The machine-readable error code."
    }
  }
}

Full source: docs/examples/shaping/overrides/testdata/errorresponse.json

Info

Precedence. An override always wins over the godoc-derived value. Absent → the godoc is used unchanged. Empty (bare marker) → the empty value is applied and scan.empty-override is raised. swagger:title is schema-only; on a response/header it is dropped with parse.context-invalid.

What’s next

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

Markdown descriptions

A multi-line swagger:description normally folds its body with the Option B rule: contiguous prose lines up to the first blank line, each trimmed. That’s right for a paragraph of prose, but it destroys markdown — a blank line ends the description, and leading indentation and table pipes are stripped. So a table or a multi-paragraph body never survives the trip into the spec.

Ending the annotation line with a lone | — the YAML literal block-scalar marker — opts the body into verbatim capture instead. Everything below is taken exactly as written — blank lines, indentation, table pipes and --- all preserved — until the next annotation or the end of the comment. It is opt-in per annotation; a plain swagger:description (no |) keeps the Option B behaviour unchanged.

Plain prose vs a verbatim body

These two models carry the same markdown body. The first uses an ordinary annotation; the second adds the | marker:

// Plain uses an ordinary description annotation.
//
// swagger:description
// Option B folds prose up to the first blank line, so only this sentence
// survives — the markdown table below never reaches the spec.
//
// | name | purpose |
// |------|---------|
// | foo  | bars    |
//
// swagger:model Plain
type Plain struct {
	Name string `json:"name"`
}

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

// Markdown opts into a verbatim body with the literal block marker.
//
// swagger:description |
// The body is captured **verbatim** — pipes, blank lines and all:
//
// | name | purpose |
// |------|---------|
// | foo  | bars    |
//
// - point one
// - point two
//
// swagger:model Markdown
type Markdown struct {
	// Name of the widget.
	//
	// swagger:description |
	// The name must be:
	//
	//   1. unique
	//   2. lowercase
	Name string `json:"name"`
}

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

The emitted descriptions diverge sharply:

Plain — Option B folds
{
  "description": "Option B folds prose up to the first blank line, so only this sentence\nsurvives — the markdown table below never reaches the spec.",
  "type": "object",
  "title": "Plain uses an ordinary description annotation.",
  "properties": {
    "name": {
      "type": "string",
      "x-go-name": "Name"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/markdowndesc"
}

Full source: docs/examples/shaping/markdowndesc/testdata/plain.json

swagger:description | — verbatim
{
  "description": "The body is captured **verbatim** — pipes, blank lines and all:\n\n| name | purpose |\n|------|---------|\n| foo  | bars    |\n\n- point one\n- point two",
  "type": "object",
  "title": "Markdown opts into a verbatim body with the literal block marker.",
  "properties": {
    "name": {
      "description": "The name must be:\n\n  1. unique\n  2. lowercase",
      "type": "string",
      "x-go-name": "Name"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/markdowndesc"
}

Full source: docs/examples/shaping/markdowndesc/testdata/markdown.json

  • Option B stops at the first blank line. The plain model’s description is just the opening sentence — the table that follows the blank line is dropped entirely (the original go-swagger#3211 grievance).
  • The | body is captured whole. Table leading pipes, the significant blank line, and the bullet list after it all ride through verbatim.
  • The marker never leaks. The trailing |, the swagger:description line itself, and the single godoc // convention space per line are all stripped; interior indentation and trailing whitespace (markdown hard breaks) are kept.
  • The title is unaffected. It still comes from the godoc preamble above the annotation — only the description body becomes verbatim.

It works on a field description just the same — the name property above keeps the indentation of its ordered list ( 1. unique).

Where the block ends

The literal block runs until the next annotation at the start of a line, or the end of the doc comment. In the examples above, the trailing swagger:model Widget line closes the block.

A swagger: token mid-line is ordinary prose and stays in the body — only a line that begins with an annotation terminates. Indentation doesn’t shield such a line, though: the comment prefix and leading whitespace are stripped before the check, so a line-leading swagger: inside an indented markdown code block still ends the block. Keep annotation-looking lines out of the verbatim body, or place them before the | annotation.

Note

This reframes go-swagger#3211: markdown is authored explicitly via swagger:description |, never recovered from ambient godoc prose. A plain doc comment stays plain — godoc and the spec keep their separate conventions.

What’s next

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

Single-line comments as descriptions

By the first-sentence convention, a single-line doc comment that ends in punctuation becomes the object’s title (on a model or the info block) or summary (on an operation); without trailing punctuation it is a description. That is the right default for most codebases, but some use single-line comments purely as prose — and then a stray period silently promotes the line to a title.

Options.SingleLineCommentAsDescription opts out of the promotion: a single-line comment is always a description, never a title / summary.

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

The witness pairs a model and an operation, each with a single-line comment that ends in a period:

// Gadget is a small device.
//
// swagger:model
type Gadget struct {
	Name string `json:"name"`
}

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

// swagger:route GET /gadgets gadgets listGadgets
//
// Lists every gadget in the catalog.
//
// responses:
//
//	200: gadgetsResponse

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

The same source, scanned both ways — the comment moves from title / summary to description uniformly:

Default — title / summary
{
  "modelDescription": "",
  "modelTitle": "Gadget is a small device.",
  "operationDescription": "",
  "operationSummary": "Lists every gadget in the catalog."
}

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

SingleLineCommentAsDescription: true
{
  "modelDescription": "Gadget is a small device.",
  "modelTitle": "",
  "operationDescription": "Lists every gadget in the catalog.",
  "operationSummary": ""
}

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

Info

Only the single-line case changes. A multi-line doc comment keeps the existing split — the first line (or the paragraph before the first blank line) stays the title, and the rest becomes the description. Reach for this option when your house style writes one-line prose descriptions and you don’t want them landing in title / summary; otherwise leave it off (the default) and write a two-line comment, or drop the trailing period, when you want a description.

What’s next

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

Keeping annotations out of the godoc

A godoc comment and an API description pursue different goals. The godoc is for the Go developers reading the package; the API text is for the consumers of the generated spec. By default codescan reads its annotations from the doc comment above a declaration, which mixes the two concerns — a swagger:model, maxProperties: or swagger:strfmt line sits right in the middle of the prose a Go reader sees.

AfterDeclComments separates them. With the option on, codescan also reads annotations placed inside a struct body (its leading comment) or inlined as a trailing comment on the same line as the declaration. The godoc above stays concise and human-facing while the swagger machinery lives out of it — same annotation grammar, no new syntax. It is the placement counterpart to overriding titles & descriptions, which separates the same two concerns at the text level.

Each pane below pairs the annotated Go (left) with the exact fragment the scanner emits (right), from the test-covered docs/examples/shaping/afterdecl package.

Inside a struct body, or trailing on a field

The swagger:model annotation (and any decl-level keyword such as maxProperties:) can live as the leading comment inside the struct body, above the first field. A field-level annotation like swagger:strfmt can ride a trailing comment on the field line. The godoc above Widget says nothing about swagger:

// Widget is a widget. This godoc stays clean — no swagger machinery here.
type Widget struct {
	// Widget is exposed to API consumers.
	//
	// swagger:model widgetModel
	// maxProperties: 5

	Name string `json:"name"`

	// Created is documented with a clean godoc; the format annotation is
	// inlined as a trailing comment.
	Created string `json:"created"` // swagger:strfmt date
}

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

Inlined on a defined type or alias

For a non-struct type — a defined type or a type alias — the annotation rides a trailing comment after the declaration:

// Count is a plain count. Clean godoc above; annotation inlined below.
type Count int // swagger:model countType

// Stamp is a string alias. Clean godoc above; annotation inlined trailing.
type Stamp = string // swagger:model stampType

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

Turning it on

AfterDeclComments is opt-in and defaults to off — so existing code, where a clean comment that happens to look like an annotation is just prose, is never reinterpreted:

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

With the option off, the inside-body and trailing annotations above are inert — the clean godoc carries no annotation, so nothing is discovered. With it on, the same source yields the three definitions, each with its keywords applied (and the clean godoc still supplies the human-facing title):

Default — annotations inert
{}

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

AfterDeclComments — discovered
{
  "countType": {
    "type": "integer",
    "format": "int64",
    "title": "Count is a plain count. Clean godoc above; annotation inlined below.",
    "x-go-name": "Count",
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/afterdecl"
  },
  "stampType": {
    "type": "string",
    "title": "Stamp is a string alias. Clean godoc above; annotation inlined trailing.",
    "x-go-name": "Stamp",
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/afterdecl"
  },
  "widgetModel": {
    "description": "Widget is exposed to API consumers.",
    "type": "object",
    "title": "Widget is a widget. This godoc stays clean — no swagger machinery here.",
    "maxProperties": 5,
    "properties": {
      "created": {
        "description": "Created is documented with a clean godoc; the format annotation is\ninlined as a trailing comment.",
        "type": "string",
        "format": "date",
        "x-go-name": "Created"
      },
      "name": {
        "type": "string",
        "x-go-name": "Name"
      }
    },
    "x-go-name": "Widget",
    "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/afterdecl"
  }
}

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

Info

Scope (v0.36). The opt-in covers type declarations — a struct’s inside-body leading comment, a struct field’s trailing comment, and the trailing comment of a defined type or alias. Routes and operations are already position-agnostic: a swagger:route / swagger:operation block inside a function body is discovered with or without this option. Const-based enums are a planned follow-up.

What’s next

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

Cleaning godoc doc-links

A Go doc comment can use godoc’s doc-link syntax — [Gadget], [Order.CustName], reference-style [text]: url lines. Those render as live links in pkg.go.dev, but carried verbatim into a spec title / description they read as bracket noise, and the bracketed Go identifier is rarely the name the schema is actually exposed under.

CleanGoDoc tidies that up. With the option on, godoc doc-link brackets are removed and — when a link resolves to a scanned schema — the span is recomposed to the name that schema is exposed under, so the prose stays true to the generated definitions. It applies only to godoc-derived prose; an author-written swagger:title / swagger:description override is deliberate text and is never touched.

Each pane below pairs the annotated Go (left) with the exact fragment the scanner emits (right), from the test-covered docs/examples/shaping/godoclinks package.

What gets cleaned

This model — its doc comment and its fields — is dense with doc-link syntax: a self-reference, links to other models, a pointer, a cross-package link, an unknown identifier, ordinary brackets, and a reference-definition line:

// Widget is the primary [Gadget] holder and references [Order.CustName].
//
// More detail mentions a [*Gadget] pointer, a [inventory.Ledger], and an
// unknown [Sprocket].
//
// swagger:model gizmo
type Widget struct {
	// Holder points at the [Gadget] that owns this widget.
	Holder string `json:"holder"`

	// Ledger is the cross-package [inventory.Ledger] reference.
	Ledger *inventory.Ledger `json:"ledger"`

	// Index is element [0] in the [see notes] list; the [id] stays bare.
	Index int `json:"index"`

	// Spec points at [Gadget]; the reference-definition line below is godoc
	// link plumbing that carries no prose.
	//
	// [the spec]: https://example.com/spec
	Spec string `json:"spec"`
}

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

Scanned with CleanGoDoc off the godoc is emitted verbatim; on, every doc-link is resolved or humanized and the reference-definition line is dropped:

Default — verbatim
{
  "description": "More detail mentions a [*Gadget] pointer, a [inventory.Ledger], and an\nunknown [Sprocket].",
  "type": "object",
  "title": "Widget is the primary [Gadget] holder and references [Order.CustName].",
  "properties": {
    "holder": {
      "description": "Holder points at the [Gadget] that owns this widget.",
      "type": "string",
      "x-go-name": "Holder"
    },
    "index": {
      "description": "Index is element [0] in the [see notes] list; the [id] stays bare.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "Index"
    },
    "ledger": {
      "$ref": "#/definitions/Ledger"
    },
    "spec": {
      "description": "Spec points at [Gadget]; the reference-definition line below is godoc\nlink plumbing that carries no prose.\n\n[the spec]: https://example.com/spec",
      "type": "string",
      "x-go-name": "Spec"
    }
  },
  "x-go-name": "Widget",
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/godoclinks"
}

Full source: docs/examples/shaping/godoclinks/testdata/gizmo_off.json

CleanGoDoc — cleaned
{
  "description": "More detail mentions a Gadget pointer, a Ledger, and an\nunknown sprocket.",
  "type": "object",
  "title": "Gizmo is the primary Gadget holder and references Order.customer_name.",
  "properties": {
    "holder": {
      "description": "Holder points at the Gadget that owns this widget.",
      "type": "string",
      "x-go-name": "Holder"
    },
    "index": {
      "description": "Index is element [0] in the [see notes] list; the [id] stays bare.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "Index"
    },
    "ledger": {
      "$ref": "#/definitions/Ledger"
    },
    "spec": {
      "description": "Spec points at Gadget; the reference-definition line below is godoc\nlink plumbing that carries no prose.",
      "type": "string",
      "x-go-name": "Spec"
    }
  },
  "x-go-name": "Widget",
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/godoclinks"
}

Full source: docs/examples/shaping/godoclinks/testdata/gizmo_on.json

Reading the cleaned pane:

  • Links recompose to the exposed name. [Gadget]Gadget (no override, so its Go name); the [Order.CustName] member link → Order.customer_name (the model name plus the field’s json name); and the leading self-name WidgetGizmo, because the model is published as swagger:model gizmo (restored to sentence case). A cross-package [inventory.Ledger] resolves through the file’s imports to Ledger.
  • Unresolved links are humanized. [Sprocket] names no scanned model, so it becomes the plain word sprocket rather than a dangling bracket.
  • Reference-definition lines are dropped. The [the spec]: https://… line on the spec field is link plumbing carrying no prose, so the whole line is removed.
Info

It recomposes to the final exposed name. The substitution runs after codescan resolves definition names, so a link to a model that gets renamed to deconflict a collision points at the renamed definition, not the original Go identifier.

Conservative by design

Only a genuine doc-link is rewritten — a dotted chain ([pkg.Type]) or an uppercase-led identifier ([Widget]). Ordinary prose brackets are left exactly as written, as the index field above shows: [0], [see notes] and the bare-lowercase [id] all survive untouched.

CleanGoDoc is opt-in and defaults to off — with it off, output is byte-identical to before, so existing specs never shift under you.

What’s next

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

Field types & formats

These knobs act at the level of a single property: the format it carries, whether a pointer is advertised as nullable, and the vendor extensions that record its Go provenance.

  • Override a Go-derived format (e.g. the vendor uint64/uint32 formats) with an official, JSON-conformant one using a field-level swagger:strfmt.
  • Mark pointer-typed fields as nullable with x-nullable, via SetXNullableForPointers.
  • Control the x-go-* vendor extensions codescan emits, or suppress them with SkipExtensions.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Field types & formats

Forcing a conformant format

codescan derives a JSON-Schema type and format from each Go type. For the unsized and large integer kinds it emits Go-specific vendor formatsuint64{type: integer, format: uint64}, uint32{integer, uint32}, and so on. These round-trip cleanly back to Go, but they are not part of the Swagger 2.0 format set, and a uint64 value can exceed what a JSON number safely represents.

When you need conformant, precision-safe output, place a field-level swagger:strfmt on the field to override just that property’s format. Overriding to int64 publishes the value as a string-encoded {type: string, format: int64}:

model
// Measurement forces a JSON-conformant format on a field. A uint64 field emits
// the Go-specific `{integer, format: uint64}` by default; overriding it with a
// field-level `swagger:strfmt int64` (below) yields a precision-safe,
// string-encoded `{string, format: int64}`.
//
// swagger:model
type Measurement struct {
	// Raw keeps the default Go-derived vendor format (uint64).
	Raw uint64 `json:"raw"`

	// Bounded is forced to a conformant, string-encoded int64.
	//
	// swagger:strfmt int64
	Bounded uint64 `json:"bounded"`
}

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

#/definitions/Measurement
{
  "description": "Measurement forces a JSON-conformant format on a field. A uint64 field emits\nthe Go-specific `{integer, format: uint64}` by default; overriding it with a\nfield-level `swagger:strfmt int64` (below) yields a precision-safe,\nstring-encoded `{string, format: int64}`.",
  "type": "object",
  "properties": {
    "bounded": {
      "description": "Bounded is forced to a conformant, string-encoded int64.",
      "type": "string",
      "format": "int64",
      "x-go-name": "Bounded"
    },
    "raw": {
      "description": "Raw keeps the default Go-derived vendor format (uint64).",
      "type": "integer",
      "format": "uint64",
      "x-go-name": "Raw"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/formats"
}

Full source: docs/examples/shaping/formats/testdata/measurement.json

Raw keeps the default {integer, uint64} vendor format; Bounded carries swagger:strfmt int64, so it renders as a string-encoded int64. The override is per-field — the underlying Go type is untouched everywhere else.

Info

swagger:strfmt also names a custom string format on a type declaration (e.g. a UUID type → {string, format: uuid}); see Model definitions → swagger:strfmt. The swagger:type annotation is the related tool when you want to override the whole type, not just its format — see Type discovery and the swagger:type reference.

Last edited by: fredbi Aug 1, 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 Aug 1, 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, and neither is x-deprecated (it carries semantic intent — see Other type decorators).

Stamping x-go-type

x-go-name and x-go-package record where a definition came from, but not the originating type’s own name. Options.EmitXGoType adds an x-go-type extension carrying the fully-qualified Go type (<package path>.<type name>) — useful for round-tripping a generated spec back to its source types:

codescan.Run(&codescan.Options{
    Packages:    []string{"./..."},
    ScanModels:  true,
    EmitXGoType: true,
})
Default — no x-go-type
{
  "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

EmitXGoType: 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",
      "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",
  "x-go-type": "github.com/go-openapi/codescan/docs/examples/shaping/extensions.Widget"
}

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

The stamp lands on the definition, beside x-go-package. It is opt-in and default-off, so existing specs are unchanged; it is presence-guarded, so it never overwrites the deliberate x-go-type the special-type recognizers already set (error, the unmodellable generic-type fallback). Like the other x-go-* extensions it rides the SkipExtensions umbrella — set SkipExtensions and no x-go-type is emitted either.

Enum descriptions

A swagger:enum type backed by Go const declarations folds the const→value mapping into the field’s description and duplicates it in the x-go-enum-desc extension. When the prose already says everything you want, the folded mapping is noise. Options.SkipEnumDescriptions keeps the authored prose as the description; the mapping then rides x-go-enum-desc only:

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

This knob is independent of SkipExtensions: set both to drop the mapping everywhere (no description folding, no x-go-enum-desc).

Authoring x-* on parameters and headers

The x-go-* extensions above are scanner-derived. To attach your own vendor extension — say x-example for a tool like Dredd — use an Extensions: block in the doc comment. It works on a model (the x-* lands on the definition), a model field, a parameter, and a response header alike. (A bare // x-example: 2 line would be read as the description; the Extensions: block is the supported form.)

// ListWidgetsParams decorates a query parameter with an author-supplied vendor
// extension through an Extensions: block — useful for tools (e.g. Dredd) that
// read x-example. A bare `x-example:` line would be swallowed as the
// description, so the Extensions: block is the supported form.
//
// swagger:parameters listWidgets
type ListWidgetsParams struct {
	// Page is the page number.
	//
	// in: query
	//
	// Extensions:
	//   x-example: 2
	Page int32 `json:"page"`
}

// WidgetList responds with a header that also carries a vendor extension —
// parameters and response headers both honour Extensions:.
//
// swagger:response widgetList
type WidgetList struct {
	// X-Rate-Limit is the per-window request budget.
	//
	// Extensions:
	//   x-units: requests-per-minute
	XRateLimit int32 `json:"X-Rate-Limit"`
}

// swagger:route GET /widgets widgets listWidgets
//
// responses:
//
//	200: widgetList

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

{
  "parameter": {
    "type": "integer",
    "format": "int32",
    "x-example": 2,
    "description": "Page is the page number.",
    "name": "page",
    "in": "query"
  },
  "responseHeader": {
    "type": "integer",
    "format": "int32",
    "description": "X-Rate-Limit is the per-window request budget.",
    "x-units": "requests-per-minute"
  }
}

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

Author-supplied extensions are not stripped by SkipExtensions — the fragment above is produced with SkipExtensions: true, yet x-example and x-units survive, because the flag only removes the scanner-derived x-go-* set.

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

Response bodies

When a handler’s actual Go return type doesn’t map cleanly to the payload you want documented, these knobs let you pin the response body the spec describes — inline on the route, or via a doc-only struct that stands in for a generic envelope.

  • Declare a route’s responses inline with the body: sub-language — a primitive, an array, or a model $ref — without writing a swagger:response struct.
  • Your handlers return one generic envelope with an interface{} payload, but you want the spec to describe a concrete type per operation. Doc-only structs that embed the envelope and shadow the payload field close the gap.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Response bodies

Inline response bodies

The Routes & operations tutorial declares each response as a swagger:response struct with a Body field. That is the right tool when a response is reused across operations or has headers. But when a response body is just “a string”, “an array of Pet, or “a Pet, the wrapper struct is pure boilerplate.

The Responses: block of a swagger:route accepts the body: sub-language, which names the body shape directly:

  • body:string (or number / integer / boolean) — a primitive body;
  • body:Pet — a $ref to the Pet definition;
  • body:[]Pet — an array of that $ref (repeat [] to nest deeper);
  • any trailing words after the body token become the response description; omit them and codescan derives one — the referenced model’s godoc (default above → the Pet doc comment), or the HTTP status reason for a numeric code (400 above → “Bad Request”).
swagger:route
// swagger:route GET /pets pets listPets
//
// Lists pets. Each response is declared inline with the body: sub-language — a
// primitive, an array of a model, or a single model $ref — so no wrapper
// response type is needed. Trailing words become the response description; omit
// them and codescan derives one (the model's godoc, or the HTTP status reason).
//
//	Responses:
//	  200: body:[]Pet the list of pets
//	  400: body:string
//	  default: body:Pet

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

paths[/pets]
{
  "get": {
    "description": "Lists pets. Each response is declared inline with the body: sub-language — a\nprimitive, an array of a model, or a single model $ref — so no wrapper\nresponse type is needed. Trailing words become the response description; omit\nthem and codescan derives one (the model's godoc, or the HTTP status reason).",
    "tags": [
      "pets"
    ],
    "operationId": "listPets",
    "responses": {
      "200": {
        "description": "the list of pets",
        "schema": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Pet"
          }
        }
      },
      "400": {
        "description": "Bad Request",
        "schema": {
          "type": "string"
        }
      },
      "default": {
        "description": "Pet is the model the inline responses reference.",
        "schema": {
          "$ref": "#/definitions/Pet"
        }
      }
    }
  }
}

Full source: docs/examples/shaping/inlineresponses/testdata/pathitem.json

No swagger:response struct is defined — the three responses are produced entirely from the body: tokens, and the Pet model is pulled into definitions because the body $refs reach it.

Info

A bare untagged token is read as a response name, never a type: 200: string is a (dangling) $ref to a response called string, not a primitive body. Use the explicit body:string form for a primitive. The full grammar — tags, untagged-token rules, and the reserved array/object/file keywords — is in sub-languages → Responses.

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

Documenting generic responses

A common Go pattern is a single response envelope — a JSend-style wrapper that every handler returns, with an open any (interface{}) field for the payload:

The generic envelope
// APIResponse is the one generic envelope every handler returns. Because Data is
// an open type (any, i.e. interface{}), the scanner can only render it as an
// open schema — it has no way to know which concrete payload a given operation
// puts there.
//
// swagger:model
type APIResponse struct {
	Status  string `json:"status"`
	Data    any    `json:"data"`
	Message string `json:"message,omitempty"`
}

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

definitions[APIResponse]
{
  "description": "APIResponse is the one generic envelope every handler returns. Because Data is\nan open type (any, i.e. interface{}), the scanner can only render it as an\nopen schema — it has no way to know which concrete payload a given operation\nputs there.",
  "type": "object",
  "properties": {
    "data": {
      "x-go-name": "Data"
    },
    "message": {
      "type": "string",
      "x-go-name": "Message"
    },
    "status": {
      "type": "string",
      "x-go-name": "Status"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"
}

Full source: docs/examples/shaping/genericenvelopes/testdata/apiresponse.json

codescan reads this faithfully: Data is any, so in the spec it becomes an open schema{"x-go-name":"Data"}, no type, no $ref. That is correct (the field genuinely accepts anything), but it is not what you want in an API contract, where each operation returns a specific payload.

codescan will not grow a per-route override syntax like swaggo’s body:APIResponse{Data: StatusReport} — that asks the scanner to invent a type the code never declares. Instead, declare the type: a doc-only struct that mirrors the envelope but pins the payload.

Embed the envelope, shadow the payload

The DRY way is to embed the generic envelope — promoting its Status and Message — and re-declare only the one opaque field with a concrete type:

Doc-only envelope
// StatusEnvelope documents the /status response: it embeds APIResponse and
// shadows the open Data with the concrete StatusReport. Handlers keep returning
// APIResponse — this type just gives the scanner a concrete shape.
//
// swagger:model
type StatusEnvelope struct {
	APIResponse

	// Data carries the concrete status report.
	Data StatusReport `json:"data"`
}

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

definitions[StatusEnvelope]
{
  "description": "StatusEnvelope documents the /status response: it embeds APIResponse and\nshadows the open Data with the concrete StatusReport. Handlers keep returning\nAPIResponse — this type just gives the scanner a concrete shape.",
  "type": "object",
  "properties": {
    "data": {
      "x-go-name": "Data",
      "$ref": "#/definitions/StatusReport"
    },
    "message": {
      "type": "string",
      "x-go-name": "Message"
    },
    "status": {
      "type": "string",
      "x-go-name": "Status"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"
}

Full source: docs/examples/shaping/genericenvelopes/testdata/statusenvelope.json

The locally-declared Data is shallower than the embedded one, so it wins — both in codescan’s view and in encoding/json at runtime. The result is a clean flat object whose data is a concrete $ref, with status and message carried over from the embed:

Generic — data is open
{
  "description": "APIResponse is the one generic envelope every handler returns. Because Data is\nan open type (any, i.e. interface{}), the scanner can only render it as an\nopen schema — it has no way to know which concrete payload a given operation\nputs there.",
  "type": "object",
  "properties": {
    "data": {
      "x-go-name": "Data"
    },
    "message": {
      "type": "string",
      "x-go-name": "Message"
    },
    "status": {
      "type": "string",
      "x-go-name": "Status"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"
}

Full source: docs/examples/shaping/genericenvelopes/testdata/apiresponse.json

Doc-only — data is concrete
{
  "description": "StatusEnvelope documents the /status response: it embeds APIResponse and\nshadows the open Data with the concrete StatusReport. Handlers keep returning\nAPIResponse — this type just gives the scanner a concrete shape.",
  "type": "object",
  "properties": {
    "data": {
      "x-go-name": "Data",
      "$ref": "#/definitions/StatusReport"
    },
    "message": {
      "type": "string",
      "x-go-name": "Message"
    },
    "status": {
      "type": "string",
      "x-go-name": "Status"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"
}

Full source: docs/examples/shaping/genericenvelopes/testdata/statusenvelope.json

You restate one field, not the whole envelope. Point the route’s response at the doc-only struct with the body: sub-language, and specialise the same envelope per operation with a different payload each time:

swagger:route
// swagger:route GET /status status getStatus
//
// Returns the service status wrapped in the generic envelope. The response is
// declared as the doc-only StatusEnvelope, so data is the concrete StatusReport
// rather than an open schema.
//
//	Responses:
//	  200: body:StatusEnvelope the status envelope

// swagger:route GET /users/{id} users getUser
//
// Returns a user wrapped in the same envelope, specialised to UserSummary.
//
//	Responses:
//	  200: body:UserEnvelope the user envelope

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

paths
{
  "/status": {
    "get": {
      "description": "Returns the service status wrapped in the generic envelope. The response is\ndeclared as the doc-only StatusEnvelope, so data is the concrete StatusReport\nrather than an open schema.",
      "tags": [
        "status"
      ],
      "operationId": "getStatus",
      "responses": {
        "200": {
          "description": "the status envelope",
          "schema": {
            "$ref": "#/definitions/StatusEnvelope"
          }
        }
      }
    }
  },
  "/users/{id}": {
    "get": {
      "tags": [
        "users"
      ],
      "summary": "Returns a user wrapped in the same envelope, specialised to UserSummary.",
      "operationId": "getUser",
      "responses": {
        "200": {
          "description": "the user envelope",
          "schema": {
            "$ref": "#/definitions/UserEnvelope"
          }
        }
      }
    }
  }
}

Full source: docs/examples/shaping/genericenvelopes/testdata/paths.json

Info

The handler never changes. Your code keeps returning the generic APIResponse; the doc-only structs exist only to give the scanner a concrete shape. They are valid Go, though — because the shadowing Data wins, StatusEnvelope{} marshals to exactly the same JSON the generic envelope would, so you may return one directly if you prefer a typed handler.

When embedding doesn’t fit

If your envelope has fields you would rather not promote — or you want the documented type to live behind a reusable swagger:response — restate the fields explicitly instead of embedding:

// swagger:model
type StatusEnvelope struct {
    Status  string       `json:"status"`
    Data    StatusReport `json:"data"`
    Message string       `json:"message,omitempty"`
}

This produces the same concrete data, at the cost of repeating every field. The embed-and-shadow form above is preferred whenever the envelope’s other fields map through unchanged.

Composing a body from several models

A related need is wrapping a response with an extra payload — say a domain model plus an auth token — rather than specialising one open field. Embed the parts with swagger:allOf and the body renders as the union of their $refs, with no open field at all:

swagger:allOf body
// AuthToken is an extra payload some responses wrap alongside the domain model.
//
// swagger:model
type AuthToken struct {
	Token string `json:"token"`
}

// LoginResult composes UserSummary with AuthToken via swagger:allOf, so the
// response body is the union of both models — a way to wrap a response with an
// added payload without an open Data field. The body renders as
// allOf:[{$ref:UserSummary},{$ref:AuthToken}].
//
// swagger:model
type LoginResult struct {
	// swagger:allOf
	UserSummary

	// swagger:allOf
	AuthToken
}

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

definitions[LoginResult]
{
  "description": "LoginResult composes UserSummary with AuthToken via swagger:allOf, so the\nresponse body is the union of both models — a way to wrap a response with an\nadded payload without an open Data field. The body renders as\nallOf:[{$ref:UserSummary},{$ref:AuthToken}].",
  "allOf": [
    {
      "$ref": "#/definitions/UserSummary"
    },
    {
      "$ref": "#/definitions/AuthToken"
    }
  ],
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"
}

Full source: docs/examples/shaping/genericenvelopes/testdata/compose.json

Point a response body at LoginResult, or embed the same swagger:allOf fields directly in an in:body field to compose the allOf inline on the response schema.

Last edited by: fredbi Aug 1, 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:additionalPropertiestype docobject additionalProperties (open / closed / typed)examplereference
swagger:alias (deprecated)type aliasno effect — alias rendering is controlled by Go aliases + optionshow-toreference
swagger:allOfembedded field / structan allOf compositionexamplereference
swagger:defaultvalue / field doca default-value anchorexamplereference
swagger:descriptiontype / field / response docoverrides the description (verbatim body with |)how-toreference
swagger:enumnamed typean enum array (+ x-go-enum-desc)examplereference
swagger:fileparam / response field{type: file}examplereference
swagger:ignoretype / field docexcludes the declarationexamplereference
swagger:metapackage doctop-level info, host, basePath, schemes, …examplereference
swagger:modeltype declarationa definitions entryexamplereference
swagger:namefield / method docrenames a JSON propertyexamplereference
swagger:omitembed / type docdrops named fields from what an embed promoteshow-toreference
swagger:operationfunc / var doca paths entry (YAML body)examplereference
swagger:parametersstruct declarationparameters on the named operation(s)examplereference
swagger:patternPropertiestype doctyped patternProperties (regex → value)examplereference
swagger:responsestruct declarationa responses entryexamplereference
swagger:routefunc / var doca paths entry + operationexamplereference
swagger:strfmttype declaration{type: string, format: …} at every useexamplereference
swagger:titletype / field docoverrides the titlehow-toreference
swagger:typetype / field docoverrides the inferred Swagger typeexamplereference

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 Aug 1, 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 and the options that drive it. It is written for people who want the full contract — annotation authors looking up an exact rule, library callers looking up an option, 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 reference documents

  • The swagger:* annotation vocabulary: what each produces, where it attaches, and the keywords it admits.
  • The keyword: value forms recognised inside annotation blocks — grouped by class, with the annotation contexts that accept each one and its value shape.
  • Every field of codescan.Options — its type, default, and effect — grouped by concern and cross-linked to the how-to that shows it in action.
  • 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.
  • Options — every field of codescan.Options: its type, default, and effect, cross-linked to the how-to that shows it in action. The library-caller reference.
  • 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 Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Maintainers

Roadmap

What’s next with this project?

timeline
    title Planned releases
    section Q1 2026
    ✅ v0.32.x (March 2026) : Repo carved out of go-swagger
                    : relint
                    : library setup (not env. sensitive)
                    : go1.25+
    section Q2 2026
    ✅ v0.33.x (April 2026) : Reduced exposed interface
                    : type array for parameters
                    : new package layout (internal, layered)
    ✅ v0.34.x (May 2026) : Grammar-based parser
                    : Replace regexp-based parser by lexer+grammar
                    : Fixed many parsing quirks
    ✅ v0.35.0 (June 2026) : Large bug-bashing
                    : Documentation site
                    : Fixes ~200+ go-swagger issues
                    : All validations
                    : Parser diagnostics
    section Q3 2026
    ✅ v0.35.x (July 2026) : Minor features
                    : more tunable knobs, new annotations
                    : Name conflict handling & circular $ref, missing validations, ... 
                    : go doc filter, private comments, inner markdown
    ✅ v0.36.0 (July 2026) : TUI
                    : interactive spec building with TUI tool
                    : polymorphic subtypes discovery
    🔶 v0.36.x (August 2026) : faster code scanner
                    : Optimized incremental type scanner
                    : dedicated CLI, independent from go-swagger
    ⬜ v0.37.0 (September 2026) : decouple from `Spec`
                    : go1.26+
                    : Internal model
                    : More go-swagger backlog fixes & tunable knobs
    section Q4 2026
    🔍 v0.38.x (Oct 2026) : LSP & IDE integration, playground UI (tentative)
    🔍 v0.39.x (Oct-Nov 2026) : OAI v3 support (tentative)
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

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 twenty 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, swagger:additionalProperties, swagger:patternProperties.
  • Operation declarations: swagger:route, swagger:operation.
  • Companion declarations: swagger:parameters, swagger:response.
  • Local hints & overrides: swagger:ignore, swagger:omit, swagger:name, swagger:title, swagger:description, swagger:type, swagger:file, swagger:default.

This section is the author-first reference. Each annotation has its own page covering what it produces, where it goes, its EBNF-like syntax, the keywords legal inside its block, and at least one worked example. Browse them below (sorted alphabetically), or start from the Annotation index for the one-row-each overview.

  • Deprecated no-op — alias rendering is controlled by Go aliases + options.
  • Marks a struct as participating in an allOf composition.
  • Classifier hint marking a value declaration as a spec default anchor.
  • Overrides the godoc-derived description on a model, field, response, or header.
  • Marks a named type as an enum and collects its const values.
  • Marks a parameter or response body as a binary file ({type: file}).
  • Excludes the surrounding declaration (or one field) from the generated spec.
  • Declares the package as the top-level OpenAPI spec container.
  • Overrides the emitted property name of a struct field or interface method.
  • Drops named fields from what an embed promotes into the enclosing schema.
  • Declares an HTTP route + operation in one annotation.
  • Overrides the godoc-derived title on a model or field.
  • Replaces a field’s or named type’s inferred Swagger type with an inlined type.

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.


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. Inside a grouped declaration (type ( A …; B … )) the comment on each individual spec is honoured independently — the annotation attaches to its own TypeSpec, not to the enclosing group — so two types in one group can carry distinct docs and annotations.
  • Function or variable declaration (func ServeAPI() { … }, var DoIt = func() { … }) — carries swagger:route, swagger:operation. These two are recognised whether the annotation sits in the function’s doc comment or inside the function body. A swagger:model or swagger:parameters declared on a type local to a function body is likewise discovered.
  • 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.

Warning

Recognition is purely positional: any comment line that begins with a swagger:<name> token is treated as that annotation — even when you meant it as prose. A description line like swagger:type controls the emitted type on a type’s doc comment is parsed as a swagger:type annotation. Keep annotation names mid-sentence in descriptions (The swagger:type directive …) or wrap them in backticks so the line does not start with the token.

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 pages for the exact rules.

Annotation × keyword compatibility matrix

A quick orientation for which annotations can carry which keyword families. See keywords.md for the per-keyword contracts, and each annotation’s own page for the detail.

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:title✅ (override)
swagger:description✅ (override)✅ (body/header)
swagger:type
swagger:additionalProperties✅ (object schema)
swagger:patternProperties✅ (object schema)
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 Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Subsections of Annotations

swagger:additionalProperties

Usage

// swagger:additionalProperties ( true | false | <type> )

What it does

Sets a schema’s additionalProperties — the policy for keys beyond the named properties.

On a struct it complements the named properties; on a map type it overrides the element-derived value schema; on a type that resolved to a bare $ref it defines a clean object. See the Maps & free-form objects tutorial.

Where it goes

On a type declaration (alongside swagger:model). A field-level equivalent exists as the additionalProperties: keyword.

Grammar (EBNF)

AdditionalPropertiesAnnotation = ANN_ADDITIONAL_PROPERTIES , ( BOOL_VALUE | ValueType ) ;
ValueType                      = TYPE_REF | IDENT_NAME | "[]" , ValueType ;

The required token is one of:

  • true — allow arbitrary extra keys (additionalProperties: true);
  • false — forbid extra keys, closing the object (additionalProperties: false);
  • a value type — a primitive / Go-builtin / []T, or a known type name (which resolves to a $ref, and is registered for discovery). This reuses the /codescan/maintainers/annotations/swagger-type/ value grammar, except a type name becomes a $ref rather than an inline expansion.

Supported keywords

None of its own. It composes with maxProperties / minProperties / patternProperties.

Example

Annotated Go
// Settings is an open object: it keeps its named property and complements it
// with typed (integer) extra values — the swagger:additionalProperties marker
// sets the policy for keys beyond the named ones.
//
// swagger:model
// swagger:additionalProperties integer
type Settings struct {
	Name string `json:"name"`
}

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

Generated spec
{
  "description": "Settings is an open object: it keeps its named property and complements it\nwith typed (integer) extra values — the swagger:additionalProperties marker\nsets the policy for keys beyond the named ones.",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "x-go-name": "Name"
    }
  },
  "additionalProperties": {
    "type": "integer"
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/addlpropstyped.json

Precedence — lowest priority. additionalProperties only rides on an object. If a prior rule fixed a non-object type (a swagger:type scalar, swagger:strfmt, a special type), the marker is dropped with a CodeShapeMismatch diagnostic. It has no OAS-2 SimpleSchema form, so it never applies on a non-body parameter or response header.

Full example. fixtures/enhancements/additional-properties/api.go.

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

swagger:alias

Warning

Deprecated. swagger:alias no longer affects the emitted spec. It is an empty sink that only raises a validate.deprecated diagnostic.

Usage

// swagger:alias [ IDENT_NAME ]

What it does

Nothing, today. Earlier documentation claimed it published a $ref to the alias target; that was never accurate. Its only real effect was to force a named primitive type to inline its scalar (e.g. {type: string}) instead of producing the $ref a named type otherwise gets — and that force-inline behaviour has been removed.

Where it went

On a type alias / named-type declaration.

Grammar (EBNF)

AliasBlock = ANN_ALIAS , [ IDENT_NAME ] , [ Title ] , [ Description ] ;

The optional IDENT_NAME is ignored — the annotation has no effect.

Migration

  • To inline a type at a use site, use swagger:type inline on the field (see swagger:type).
  • To publish a type as a first-class definition that fields $ref, use swagger:model.
  • To control alias rendering globally, use the RefAliases / TransparentAliases options. A plain (unannotated) Go alias type T = Other dissolves to its target by default. See Alias rendering.

Supported keywords

None — the annotation is inert.

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

swagger:allOf

Usage

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

Grammar (EBNF)

AllOfBlock = ANN_ALLOF , [ Title ] , [ Description ] ;

The annotation takes no arguments; an optional title/description may follow on the doc comment.

Supported keywords

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

Example

A struct embedding a swagger:model base with swagger:allOf on the embed produces an allOf of the base $ref and an inline-object member carrying the embedding struct’s own fields:

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

Generated spec
{
  "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

The same composition applies when the embedding struct is a swagger:response body: the embedded base emits an allOf: [{$ref}, …] arm only when it is a swagger:model (a definition exists to point at); an embedded swagger:response has its fields inlined instead.

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

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

swagger:default

Usage

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

Grammar (EBNF)

DefaultClassifierBlock = ANN_DEFAULT , [ Title ] , [ Description ] ;

Takes no argument — an optional title/description may follow on the doc comment.

Supported keywords

None of its own. Most spec defaults are instead carried by the default: keyword on the relevant field; this annotation has a narrow surface and is not commonly authored directly.

Example

swagger:default is value-only: it produces no definition, so there is no emitted spec to render. The source below shows the narrow classifier-hint form — in practice most defaults come from the default: keyword on a field.

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

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

swagger:description

Usage

// swagger:description <text>   (single line, or a blank-terminated body)
// swagger:description |        (opens a verbatim literal markdown block)

What it does

Replaces the godoc-derived description on a schema with explicit text.

By default a description comes from a declaration’s doc comment; swagger:description overrides it when the godoc prose isn’t what you want to publish. It is a schema-family override — a sibling of swagger:title.

A trailing | opens a verbatim literal markdown block: the body is captured exactly — blank lines, indentation, and table pipes preserved — until the next line-leading annotation or end of comment. See Markdown descriptions.

Where it goes

On a type (model) doc comment, a struct-field doc comment, a swagger:response struct, or a response header field.

Grammar (EBNF)

DescriptionAnnotation = ANN_DESCRIPTION , RAW_VALUE ;

RAW_VALUE is the rest of the head line; under Option B a blank-terminated body extends it, and a trailing | switches the body to verbatim literal capture. The annotation dispatches through the schema parser (not the classifier parser), so validation keywords co-located on the same comment group still surface.

Supported keywords

None of its own — the text (plus any folded body) is the entire argument. A bare swagger:description with no text suppresses the godoc-derived description and emits a CodeEmptyOverride diagnostic.

Example

A plain override on a model and its fields:

Annotated Go
// Widget is the Go-facing widget doc, written for Go readers.
//
// It explains internal Go usage that should not leak into the API spec.
//
// swagger:model
// swagger:title A Public Widget
// swagger:description A widget exposed via the public API.
type Widget struct {
	// ID explains the Go field for Go readers.
	//
	// swagger:description The unique widget identifier.
	ID string `json:"id"`

	// Label is the Go-facing field doc. Fields carry no title by default;
	// the override is the only way a property gets one.
	//
	// swagger:title Display Label
	// swagger:description Human-readable label shown to API consumers.
	Label string `json:"label"`

	// Plain keeps its godoc description because it carries no override.
	Plain string `json:"plain"`

	// Capacity combines a description override with an inline validation
	// keyword on the same field: the override applies AND maximum is kept,
	// because the override annotations dispatch through the schema family.
	//
	// swagger:description The maximum capacity, in liters.
	// maximum: 1000
	Capacity int64 `json:"capacity"`

	// Suppressed has a godoc that a bare swagger:description suppresses: the
	// empty value is applied (description omitted) and scan.empty-override is
	// raised, in case the bare marker was left behind by mistake.
	//
	// swagger:description
	Suppressed string `json:"suppressed"`

	// Notes carries a multi-line description override: the lines following the
	// annotation fold into the description until the blank line, joined with
	// newlines.
	//
	// swagger:description Free-form notes about the widget.
	// They may span several lines, all folded into one description.
	//
	// The blank line above terminates the override body; this paragraph is
	// ordinary godoc and is discarded (the override won).
	Notes string `json:"notes"`

	// Gadget is a $ref field carrying title + description overrides. They are
	// symmetric $ref siblings: kept under EmitRefSiblings, dropped to a bare
	// $ref under the default flags — the same rule a prose description follows.
	//
	// swagger:title Gadget Ref
	// swagger:description The attached gadget, described for API consumers.
	Gadget Gadget `json:"gadget"`
}

// Gadget is a plain referenced model.
//
// swagger:model
type Gadget struct {
	Serial string `json:"serial"`
}

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

Generated spec
{
  "description": "A widget exposed via the public API.",
  "type": "object",
  "title": "A Public Widget",
  "properties": {
    "capacity": {
      "description": "The maximum capacity, in liters.",
      "type": "integer",
      "format": "int64",
      "maximum": 1000,
      "x-go-name": "Capacity"
    },
    "gadget": {
      "$ref": "#/definitions/Gadget"
    },
    "id": {
      "description": "The unique widget identifier.",
      "type": "string",
      "x-go-name": "ID"
    },
    "label": {
      "description": "Human-readable label shown to API consumers.",
      "type": "string",
      "title": "Display Label",
      "x-go-name": "Label"
    },
    "notes": {
      "description": "Free-form notes about the widget.\nThey may span several lines, all folded into one description.",
      "type": "string",
      "x-go-name": "Notes"
    },
    "plain": {
      "description": "Plain keeps its godoc description because it carries no override.",
      "type": "string",
      "x-go-name": "Plain"
    },
    "suppressed": {
      "type": "string",
      "x-go-name": "Suppressed"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/overrides"
}

Full source: docs/examples/shaping/overrides/testdata/widget.json

The | literal block captures a verbatim markdown body (table and list preserved):

Annotated Go
// Markdown opts into a verbatim body with the literal block marker.
//
// swagger:description |
// The body is captured **verbatim** — pipes, blank lines and all:
//
// | name | purpose |
// |------|---------|
// | foo  | bars    |
//
// - point one
// - point two
//
// swagger:model Markdown
type Markdown struct {
	// Name of the widget.
	//
	// swagger:description |
	// The name must be:
	//
	//   1. unique
	//   2. lowercase
	Name string `json:"name"`
}

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

Generated spec
{
  "description": "The body is captured **verbatim** — pipes, blank lines and all:\n\n| name | purpose |\n|------|---------|\n| foo  | bars    |\n\n- point one\n- point two",
  "type": "object",
  "title": "Markdown opts into a verbatim body with the literal block marker.",
  "properties": {
    "name": {
      "description": "The name must be:\n\n  1. unique\n  2. lowercase",
      "type": "string",
      "x-go-name": "Name"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/markdowndesc"
}

Full source: docs/examples/shaping/markdowndesc/testdata/markdown.json

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

swagger:enum

Usage

// swagger:enum [ IDENT_NAME ]

What it does

Marks a named type over a string, integer, number or boolean as an enum and collects the type’s const declarations.

Values come from the Go type-checker, so any constant expression is collected — iota (including the implicit specs, which carry neither a type nor a value), computed members (1 << 3), references to earlier members, negative values, every integer base, values above MaxInt64 in an unsigned enum, rune literals (as code points), true / false, and both string forms. The emitted type / format come from the declared Go type, never from the members, so an int8 enum is {integer, int8} and reordering the const block cannot change the type. A type declared over another named type keeps that type’s format (type Kind strfmt.UUID stays format: uuid).

Two shapes do not work: an alias to a basic type cannot host an enum (the type-checker erases the alias, leaving nothing to collect), and a rune or byte enum emits integers, which is what those types are on the wire. See Enumerations.

  • Without swagger:model (the default): 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 a standalone definition.
  • With swagger:model: the enum becomes a first-class definition carrying the enum array (+ x-go-enum-desc), and referencing fields point at it via $ref — the general swagger:model ⇒ definition + $ref rule applied to enums.

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 $ref, 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.

Grammar (EBNF)

EnumBlock = ANN_ENUM , [ IDENT_NAME ] , [ Title ] , [ Description ] ;

The optional IDENT_NAME names the type whose const values to collect. On a type declaration the name is redundant, so the bare swagger:enum form is accepted and infers the name from the declared type: swagger:enum Priority and a bare swagger:enum on type Priority … are equivalent.

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

Example

A named type marked swagger:enum with const values, referenced by a model field, lands the values on that property (not on a standalone definition) together with the x-go-enum-desc extension:

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

Generated spec
{
  "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

By default the const→value mapping is folded into the property’s description and duplicated in x-go-enum-desc. Set the scanner option SkipEnumDescriptions: true to keep the authored prose as the description; the mapping then rides x-go-enum-desc only. See Vendor extensions.

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

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

swagger:file

Usage

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

Grammar (EBNF)

FileBlock = ANN_FILE , [ Title ] , [ Description ] ;

Takes no argument — an optional title/description may follow on the doc comment.

Supported keywords

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

Example

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

Generated spec
[
  {
    "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

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

swagger:ignore

Usage

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

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.

Where it goes

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

Grammar (EBNF)

IgnoreBlock = ANN_IGNORE , [ Title ] , [ Description ] ;

Takes no argument — an optional title/description may follow on the doc comment.

Supported keywords

None — the annotation is a stateless classifier marker.

Example

swagger:ignore produces no schema, so there is no live spec pane here: the type below is scanned, classified, then dropped — it never reaches definitions. On a type it excludes the whole declaration; on a struct field it excludes just that one property (e.g. a PasswordHash kept out of the wire shape).

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

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

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

swagger:meta

Usage

// swagger:meta

What it does

Declares the package as the OpenAPI spec container.

The scanner reads the package doc comment for the 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. No arguments — a bare annotation.

Grammar (EBNF)

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

The body is a MetaBody of single-line MetaKeywords (version, host, basePath, license, contact, schemes) and MetaRawBlocks (consumes, produces, security, securityDefinitions, tos). See grammar §meta-family.

Supported 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, tags). A Tags: block declares the spec’s top-level tags (name, description, nested externalDocs, x-* extensions per tag).

Example

Annotated Go
// 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
//
//	ExternalDocs:
//	  description: Full API guide
//	  url: https://example.com/docs
//
//	Tags:
//	- name: pets
//	  description: Everything about your Pets
//	  externalDocs:
//	    description: Find out more
//	    url: https://example.com/docs/pets
//	- name: store
//	  description: Access to Petstore orders
//	  x-display-name: Store
//
//	SecurityDefinitions:
//	  basic_auth:
//	    type: basic
//	  api_key:
//	    type: apiKey
//	    in: header
//	    name: X-API-Key
//
//	Security:
//	  basic_auth:
//
//	InfoExtensions:
//	  x-logo:
//	    url: https://example.com/logo.png
//	    altText: Example
//
// swagger:meta
package meta

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

Generated spec
{
  "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",
    "x-logo": {
      "altText": "Example",
      "url": "https://example.com/logo.png"
    }
  },
  "host": "api.example.com",
  "basePath": "/v1",
  "paths": {},
  "securityDefinitions": {
    "api_key": {
      "type": "apiKey",
      "name": "X-API-Key",
      "in": "header"
    },
    "basic_auth": {
      "type": "basic"
    }
  },
  "security": [
    {
      "basic_auth": []
    }
  ],
  "tags": [
    {
      "description": "Everything about your Pets",
      "name": "pets",
      "externalDocs": {
        "description": "Find out more",
        "url": "https://example.com/docs/pets"
      }
    },
    {
      "description": "Access to Petstore orders",
      "name": "store",
      "x-display-name": "Store"
    }
  ],
  "externalDocs": {
    "description": "Full API guide",
    "url": "https://example.com/docs"
  }
}

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

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

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

swagger:model

Usage

// swagger:model [<name>]   (where <name> overrides the definition name; defaults to the Go type name)

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.

The title/description split follows a heuristic: a single-line comment ending in a period becomes the title. One without a trailing period becomes the description; a multi-line comment uses the first line as title and the rest as description.

The descriptive prose must come before the swagger:model line — an annotation-first block still publishes the model but drops its title and description.

Where it goes

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

Grammar (EBNF)

ModelAnnotation = ANN_MODEL , [ IDENT_NAME ] ;

The optional IDENT_NAME is the name the model takes in definitions (default: the Go type’s name). It must be a plain identifier (a JSON label), not a Go-qualified name — a dotted name such as utils.Error is rejected with a warning and dropped. Cross-package types resolve automatically, so reference a model by its bare name.

The annotation opens a SchemaBlock body — its fields and their doc comments carry the schema validations.

Supported keywords

Every schema decorator and validation keyword is accepted on a field doc comment. A keyword that is not compatible with the field’s inferred schema type (e.g. minLength on an integer) is ignored and raises a diagnostic.

Example

The doc comment above the type drives the model’s name, title and description:

// Pet is the petstore's primary entity.            <- title (first line, ends with a period)
//
// A pet can be any little animal you care about.   <- description
// In this example the model name is inferred from the type name, here "Pet".
//
// 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"`
}

Pass an argument to override the name; the type is then published as #/definitions/PetWithExtras:

// swagger:model PetWithExtras
type DetailedPet struct {  }

A single field group declaring several names emits one property per name. A json: tag on the group cannot rename the individual fields — each keeps its own name — though tag options still apply:

Annotated Go
// Color is an RGBA colour. A single field group declaring several names emits
// one property per name — R, G, B and A each become their own integer property.
// A json tag on the group cannot rename the individual fields (each keeps its
// own name), though tag options such as omitempty still apply.
//
// swagger:model
type Color struct {
	R, G, B, A uint8 `json:",omitempty"`
}

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

Generated spec
{
  "description": "Color is an RGBA colour. A single field group declaring several names emits\none property per name — R, G, B and A each become their own integer property.\nA json tag on the group cannot rename the individual fields (each keeps its\nown name), though tag options such as omitempty still apply.",
  "type": "object",
  "properties": {
    "A": {
      "type": "integer",
      "format": "uint8"
    },
    "B": {
      "type": "integer",
      "format": "uint8"
    },
    "G": {
      "type": "integer",
      "format": "uint8"
    },
    "R": {
      "type": "integer",
      "format": "uint8"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

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

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

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

swagger:name

Usage

// swagger:name IDENT_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 overrides that derivation 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.

Grammar (EBNF)

NameAnnotation = ANN_NAME , IDENT_NAME ;

The required IDENT_NAME is the JSON property name to use.

Supported keywords

None — the override name is the entire surface.

Example

On an interface method, swagger:name overrides the property name the method would otherwise publish under (PascalCase Go method name) with the chosen JSON name:

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
}


// Account shows the universal name: keyword renaming model struct fields. The
// same keyword used on parameters and response headers also sets a property key
// here, winning over a json tag, the legacy swagger:name annotation, and the Go
// field name.
//
// swagger:model
type Account struct {
	// Bal has no json tag; the keyword sets the property key directly.
	//
	// name: balance
	Bal float64

	// Currency carries both naming forms; the keyword wins over the
	// legacy annotation and the json tag.
	//
	// name: currencyCode
	// swagger:name legacyCurrency
	Currency string `json:"currency"`
}

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

Generated spec
{
  "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

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

Deprecated / legacy forms

swagger:name is the legacy annotation form. The canonical, universal field-naming mechanism is the name: keyword, which works at every field site — model properties, interface methods, parameters, and response headers — with the precedence name: > swagger:name > json: tag > Go field name. swagger:name remains honoured (and idiomatic on interface methods, shown above), but reach for name: in new code; it is the only form that works on parameters and headers.

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

swagger:omit

Usage

// swagger:omit <field>[,<field>…]

What it does

Stops the named fields being promoted out of an embedded type, so they never reach the enclosing schema.

Embedding a shared type is how Go reuses a struct, but the reused type often carries more than one particular endpoint should: server-assigned fields on a create request, or a field the enclosing struct re-declares for itself. swagger:omit is how the author resolves that — codescan does not guess which fields were meant, it documents the type as written unless told otherwise.

Names are Go field names, never JSON aliases: the annotation acts before names are computed, so it is indifferent to json tags and to NameFromTags.

It is a pre-filter, not an edit of the finished schema. That is what makes it read the same whether the embed is inlined or composed into an allOf member (see Composing embeds with allOf): the field is simply never written.

Where it goes

Two placements:

  • on the embed — targets are plain field names of that embedded type. This is the ergonomic form and needs no qualification;
  • on the type declaration — for an embed you cannot annotate (a type you do not own, or one nested deeper). A bare name resolves against the promoted set; a dotted path names the embed chain, Base.ID or Outer.Inner.Deep.
// swagger:omit Base.ID,Created
type Decorated struct {
	Base
	// …
}

Embeds only. Every path segment but the last must name an embedded field: swagger:omit removes promoted content, which is the only thing the enclosing schema owns. To exclude a struct’s own field, use swagger:ignore on the field itself.

Grammar (EBNF)

OmitBlock  = ANN_OMIT , OmitTarget , { "," , OmitTarget } ;
OmitTarget = GoIdent , { "." , GoIdent } ;

The whole remainder of the line is the argument list; spaces after commas are allowed (swagger:omit ID, Created).

Supported keywords

None. swagger:omit is a classifier: it takes arguments and opens no keyword block.

Example

The go-swagger#1992 shape: a request body embeds the shared domain type, and the server-assigned fields are dropped from this body only.

Annotated Go
// CreateUserParams is the request body: the same User, minus the fields the
// server assigns. `swagger:omit` sits on the embed, so the targets are plain
// field names of the embedded type.
//
// swagger:parameters createUser
type CreateUserParams struct {
	// in: body
	Body struct {
		// swagger:omit ID,Created
		User
	}
}

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

The request body
{
  "type": "object",
  "properties": {
    "Name": {
      "type": "string"
    }
  }
}

Full source: docs/examples/concepts/omit/testdata/body.json

The shared type is never touched, so its own definition — which the response $refs — still documents every field:

{
  "description": "The point of the idiom is that you do not have to touch it.",
  "type": "object",
  "title": "User is the shared domain type — deliberately free of any swagger annotation.",
  "properties": {
    "Created": {
      "type": "string",
      "format": "date-time"
    },
    "ID": {
      "type": "integer",
      "format": "int64"
    },
    "Name": {
      "type": "string"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/omit"
}

Full source: docs/examples/concepts/omit/testdata/user.json

Diagnostics

All three are Hints — informational, never blocking:

codefires when
scan.omit-unresolvedthe target names no field of the embedded type: a typo, or a field renamed upstream
scan.omit-behind-refthe embed is composed as a $ref (an annotated swagger:model); Swagger 2.0 cannot subtract a property from a reference, so the omission is dropped rather than silently forking the definition
scan.shadowed-embed-fielda field re-declared with json:"-" carries the Go name of a promoted one — see below

swagger:omit is the only annotation whose output depends on a name the Go compiler never checks; everything else codescan emits is derived from types. scan.omit-unresolved is what stops it rotting silently when a field is renamed upstream, so wire OnDiagnostic if you rely on the annotation.

json:"-" does not hide a promoted field

Re-declaring a promoted field with json:"-" looks like it should hide it. It does not: encoding/json ignores a - field entirely, so it never enters the name set, never shadows the promoted one, and Go keeps marshalling the embedded field. swagger:omit is the annotation that removes it for real.

Deprecated

No. Added in v0.37.

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

swagger:operation

Usage

// swagger:operation METHOD PATH [tag …] OPERATION_ID

What it does

Declares an HTTP route + operation with a YAML-document body.

Same header line as /codescan/maintainers/annotations/swagger-route/ (method, path, optional tags, operation ID), 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

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.

Grammar (EBNF)

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

OperationArgs        = HTTP_METHOD , URL_PATH , { IDENT_NAME } , IDENT_NAME ;

InlineOperationBody is an OPAQUE_YAML document. The trailing IDENT_NAME is the operation ID; the run before it is the tag list. The header line shape authors rely on:

swagger:operation <METHOD> <path> [tag1 tag2 …] <operationID>
  • <METHOD>GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS. Case insensitive.
  • <path> — starts with /; supports path-parameter braces (/items/{id}). Only RFC 6570 Level-1 expansion (simple {name} substitution) is allowed; an inline regex constraint (/items/{id:[0-9]+}) is stripped to the bare form with a warning.
  • [tag1 tag2 …] — optional whitespace-separated tag list (at least two characters each).
  • <operationID> — the unique operation identifier.

Supported keywords

None inside the YAML body — it is structurally YAML, not the keyword grammar. The header line is the entire annotation surface.

Example

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

Generated spec
{
  "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

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

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

Deprecated / legacy forms

swagger:operation also accepts a structured Parameters: body (shared with /codescan/maintainers/annotations/swagger-route/). In that body the per-parameter chunk sigil + name: is the historic chunk-start; - name: is accepted as a YAML-friendly alias and is preferred for YAML-correctness. See the chunk grammar in /codescan/maintainers/sub-languages/#parameters.

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

swagger:parameters

Usage

// swagger:parameters OPERATION_ID [OPERATION_ID …]

What it does

Declares a Go struct as the parameter set for one or more operations.

Each field becomes one parameter on the named operation(s), and the field’s doc comment carries its in:, required:, validations, and description.

  • A parameter’s name comes from the field’s json: tag, falling back to the Go field name when there is no tag (the form: tag is not consulted). A name: keyword in the field doc takes precedence over both and is the canonical, preferred way to set the name — the legacy swagger:name annotation is inert here and emits a context-invalid diagnostic pointing at name:. See the universal name keyword.
  • Operation IDs accumulate: the same ID may appear in several swagger:parameters lines to compose a set from multiple structs, and one struct may carry several lines splitting a long ID list.
  • swagger:parameters declarations are collected across all scanned packages and matched to operations by ID, so a shared set can live in its own package.

Where it goes

On a struct declaration. A bare slice variable (var Filters []string) carries no per-field in:/type:/required:, so parameters must be a struct.

Grammar (EBNF)

ParametersAnnotation = ANN_PARAMETERS , IDENT_NAME , { IDENT_NAME } ;

The IDENT_NAME arguments are the operation IDs this set applies to (at least one). The first argument may instead be a * wildcard (spec-level shared #/parameters/{name}) or a /path (inlined into that exact path-item) — see Sharing parameters & responses.

The annotation opens a SchemaBlock body — field doc comments carry the parameter validations.

Supported keywords

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

Example

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

Generated spec
[
  {
    "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

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

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

swagger:patternProperties

Usage

// swagger:patternProperties "<regex>": <type> [ , "<regex>": <type> … ]

What it does

Adds typed patternProperties entries — each maps a property-name regex to a value schema.

It is the typed counterpart of the regex-only patternProperties: keyword (which uses an empty, any-value schema).

Note

patternProperties is a JSON-Schema (draft-4) keyword, beyond the Swagger 2.0 subset. codescan emits it ungated — your downstream tooling must understand it.

Where it goes

On a type declaration (alongside swagger:model).

Grammar (EBNF)

PatternPropertiesAnnotation = ANN_PATTERN_PROPERTIES , PatternPair , { "," , PatternPair } ;
PatternPair                 = STRING_VALUE , ":" , ValueType ;
ValueType                   = TYPE_REF | IDENT_NAME | "[]" , ValueType ;

A comma-separated list of "<regex>": <spec> pairs. The regex (STRING_VALUE) is double-quoted — it may contain spaces, colons, commas; only \" is an escape inside it, other backslashes like \d are preserved. Each <spec> reuses the /codescan/maintainers/annotations/swagger-type/ value grammar (primitive / []T / type-name → $ref).

Supported keywords

None of its own. It composes with maxProperties / minProperties / additionalProperties.

Example

Annotated Go
// TypedPatterns maps property-name regexes to typed value schemas. Each quoted
// regex pairs with a value spec — a primitive or a model name (which becomes a
// $ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0
// subset; codescan emits it ungated.
//
// swagger:model
// swagger:patternProperties "^x-": string, "^\d+$": integer, "^item-": Thing
type TypedPatterns struct {
	Known string `json:"known"`
}

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

Generated spec
{
  "description": "TypedPatterns maps property-name regexes to typed value schemas. Each quoted\nregex pairs with a value spec — a primitive or a model name (which becomes a\n$ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0\nsubset; codescan emits it ungated.",
  "type": "object",
  "properties": {
    "known": {
      "type": "string",
      "x-go-name": "Known"
    }
  },
  "patternProperties": {
    "^\\d+$": {
      "type": "integer"
    },
    "^item-": {
      "$ref": "#/definitions/Thing"
    },
    "^x-": {
      "type": "string"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"
}

Full source: docs/examples/concepts/maps/testdata/patterntyped.json

Precedence. Same lowest-priority, object-only rule as swagger:additionalProperties. Each regex is RE2-hygiene-checked: one that does not compile raises a CodeInvalidAnnotation warning but is preserved; a structurally malformed pair list is dropped with a diagnostic.

Full example. fixtures/enhancements/pattern-properties-typed/api.go.

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

swagger:response

Usage

// swagger:response [ IDENT_NAME ]

What it does

Declares a Go struct as a named response object.

It is 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. The body may be a struct, a $ref’d model, or a primitiveBody string emits schema: {type: string}.
  • Other fields default to response headers: a field with neither Body/in: body nor in: header is treated as a header, not a body property. A header’s key comes from the json: tag / Go field name, or a name: keyword (e.g. name: X-Rate-Limit) — the canonical, preferred form, see the name keyword.
  • An anonymously embedded struct marked in: body is the body (a $ref to the model), not a promotion of its fields.
  • An interface{} / any-typed field emits an empty schema ({}, or {type: array, items: {}} for a slice) — “any type”, valid OpenAPI 2.0.

Where it goes

On a struct declaration.

Grammar (EBNF)

ResponseAnnotation = ANN_RESPONSE , [ IDENT_NAME ] ;

The optional IDENT_NAME is the published response name (default: the Go type’s name). A * wildcard (swagger:response *) explicitly marks the response as a shared one, registered at #/responses/{name} for operations to $ref by name — see Sharing parameters & responses.

The annotation opens a SchemaBlock body.

Supported keywords

  • Body field: schema-context keywords.
  • Header field: header-context keywords — numeric / length / format validations, pattern, enum, default, example, collectionFormat. required: is silently dropped (the OAS v2 Header object has no required field).

Example

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

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

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

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

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

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

swagger:route

Usage

// swagger:route METHOD PATH [tag …] OPERATION_ID

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; see /codescan/maintainers/annotations/swagger-operation/ for the YAML-body alternative.

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.

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.

Grammar (EBNF)

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

OperationArgs = HTTP_METHOD , URL_PATH , { IDENT_NAME } , IDENT_NAME ;

The trailing IDENT_NAME is the operation ID; the run before it is the tag list. The header line shape authors rely on:

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}). Only RFC 6570 Level-1 expansion (simple {name} substitution) is allowed; an inline regex constraint (/items/{id:[0-9]+}) is stripped to the bare form with a warning.
  • [tag1 tag2 …] — optional whitespace-separated tag list (at least two characters each).
  • <operationID> — the unique operation identifier.

Supported keywords

All body keywords legal in route context (consumes, produces, schemes, security, parameters, responses, extensions, externalDocs) plus inline deprecated: and a body tags: list (a string list, unioned and deduplicated with the header-line tags). The Parameters: and Responses: sub-languages are documented in /codescan/maintainers/sub-languages/#parameters and /codescan/maintainers/sub-languages/#responses.

Example

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

Generated spec
{
  "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

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

Deprecated / legacy forms

In the Parameters: body the per-parameter chunk sigil + name: (used in the sample above) is the historic chunk-start; - name: is accepted as a YAML-friendly alias and is preferred for YAML-correctness. See the chunk grammar in /codescan/maintainers/sub-languages/#parameters.

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

swagger:strfmt

Usage

// swagger:strfmt FORMAT_NAME

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.

A field typed by the marked type emits with the format; the underlying type does NOT appear as a top-level model definition (strfmt-tagged types are replaced by their format at every reference). A slice carries the format onto its items: {type: array, items: {type: string, format: …}}.

Where it goes

On a type declaration whose underlying form is a string-marshalable type (typically implementing encoding.TextMarshaler / encoding.TextUnmarshaler). swagger:strfmt may also sit on a struct field doc to override just that field’s published format.

Grammar (EBNF)

StrfmtBlock = ANN_STRFMT , IDENT_NAME , [ Title ] , [ Description ] ;

The required IDENT_NAME is the format name (uuid, email, mac, …) — the entire surface of the annotation.

Supported keywords

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

Example

A named type marked swagger:strfmt (here a MarshalText/UnmarshalText hardware address) emits as {type: string, format: …} wherever it is referenced — a field typed MAC comes out as {type: string, format: mac}:

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

Generated spec
{
  "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

Adding swagger:model opts the type into a first-class definition carrying the full {type: string, format: …} schema, with referencing fields pointing at it via $ref — the general swagger:model ⇒ definition + $ref rule. Without swagger:model, the format inlines at every reference.

A field-level override targets one field’s format — e.g. // swagger:strfmt int64 on a uint64 field emits {type: string, format: int64}, a precision-safe, JSON-conformant string encoding (the conformant alternative to the Go-specific {integer, format: uint64} codescan emits for unsized/large ints by default).

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

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

swagger:title

Usage

// swagger:title <text>   (where <text> is the rest of the line)

What it does

Replaces the godoc-derived title on a schema with explicit text.

By default a model’s title comes from the first line of its doc comment; swagger:title overrides that when the prose isn’t the title you want to publish. It is a schema-family override — a sibling of swagger:description.

Where it goes

On a type (model) doc comment or a struct-field doc comment. It is schema-only: a response has no title (the annotation is ignored there), and on a non-body parameter or response header it is rejected with a CodeContextInvalid diagnostic.

Grammar (EBNF)

TitleAnnotation = ANN_TITLE , RAW_VALUE ;

RAW_VALUE is the rest of the head line, captured verbatim. The annotation dispatches through the schema parser (not the classifier parser), so validation keywords co-located on the same comment group still surface as schema validations.

Supported keywords

None of its own — the text is the entire argument. A blank swagger:title emits a CodeEmptyOverride diagnostic.

Example

Annotated Go
// Widget is the Go-facing widget doc, written for Go readers.
//
// It explains internal Go usage that should not leak into the API spec.
//
// swagger:model
// swagger:title A Public Widget
// swagger:description A widget exposed via the public API.
type Widget struct {
	// ID explains the Go field for Go readers.
	//
	// swagger:description The unique widget identifier.
	ID string `json:"id"`

	// Label is the Go-facing field doc. Fields carry no title by default;
	// the override is the only way a property gets one.
	//
	// swagger:title Display Label
	// swagger:description Human-readable label shown to API consumers.
	Label string `json:"label"`

	// Plain keeps its godoc description because it carries no override.
	Plain string `json:"plain"`

	// Capacity combines a description override with an inline validation
	// keyword on the same field: the override applies AND maximum is kept,
	// because the override annotations dispatch through the schema family.
	//
	// swagger:description The maximum capacity, in liters.
	// maximum: 1000
	Capacity int64 `json:"capacity"`

	// Suppressed has a godoc that a bare swagger:description suppresses: the
	// empty value is applied (description omitted) and scan.empty-override is
	// raised, in case the bare marker was left behind by mistake.
	//
	// swagger:description
	Suppressed string `json:"suppressed"`

	// Notes carries a multi-line description override: the lines following the
	// annotation fold into the description until the blank line, joined with
	// newlines.
	//
	// swagger:description Free-form notes about the widget.
	// They may span several lines, all folded into one description.
	//
	// The blank line above terminates the override body; this paragraph is
	// ordinary godoc and is discarded (the override won).
	Notes string `json:"notes"`

	// Gadget is a $ref field carrying title + description overrides. They are
	// symmetric $ref siblings: kept under EmitRefSiblings, dropped to a bare
	// $ref under the default flags — the same rule a prose description follows.
	//
	// swagger:title Gadget Ref
	// swagger:description The attached gadget, described for API consumers.
	Gadget Gadget `json:"gadget"`
}

// Gadget is a plain referenced model.
//
// swagger:model
type Gadget struct {
	Serial string `json:"serial"`
}

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

Generated spec
{
  "description": "A widget exposed via the public API.",
  "type": "object",
  "title": "A Public Widget",
  "properties": {
    "capacity": {
      "description": "The maximum capacity, in liters.",
      "type": "integer",
      "format": "int64",
      "maximum": 1000,
      "x-go-name": "Capacity"
    },
    "gadget": {
      "$ref": "#/definitions/Gadget"
    },
    "id": {
      "description": "The unique widget identifier.",
      "type": "string",
      "x-go-name": "ID"
    },
    "label": {
      "description": "Human-readable label shown to API consumers.",
      "type": "string",
      "title": "Display Label",
      "x-go-name": "Label"
    },
    "notes": {
      "description": "Free-form notes about the widget.\nThey may span several lines, all folded into one description.",
      "type": "string",
      "x-go-name": "Notes"
    },
    "plain": {
      "description": "Plain keeps its godoc description because it carries no override.",
      "type": "string",
      "x-go-name": "Plain"
    },
    "suppressed": {
      "type": "string",
      "x-go-name": "Suppressed"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/overrides"
}

Full source: docs/examples/shaping/overrides/testdata/widget.json

See Overriding titles & descriptions for the full walkthrough.

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

swagger:type

Usage

// swagger:type <type>   (where <type> is a scalar, []T, inline, or a known type name)

What it does

Replaces a field’s (or named type’s) inferred Swagger type with an inlined type.

swagger:type is an inline directive — it never emits a $ref; the chosen type is rendered directly in place (the default $ref-for-named-types is the no-annotation behaviour).

Where it goes

On a type declaration, a struct field doc, OR a swagger:parameters field doc.

Note

On a parameter field the override collapses the field to a simple parameter — useful when a struct- or defined-typed field would otherwise come out typeless (invalid Swagger 2.0). The argument is restricted to a scalar or a []-wrapped scalar there: the inline and type-name forms are rejected with a diagnostic, since a non-body parameter has no schema to inline a type into. A compatible swagger:strfmt on the same field still rides as a supplementary format.

Grammar (EBNF)

TypeBlock = ANN_TYPE , TYPE_REF , [ Title ] , [ Description ] ;

The required TYPE_REF is one of:

  • a scalar typestring, integer, number, boolean, object (or a Go-builtin spelling such as int64, uint32);
  • []T — an array whose items are the inlined T (recursive: [][]int64, []Custom);
  • inline — expand the field’s own Go type in place, instead of the $ref a named type would otherwise produce;
  • a known type name — inline that type’s schema (again, no $ref).

An unknown name falls back to inlining the field’s Go type, with a validate.unsupported-type diagnostic.

Supported keywords

None — the override type is the entire surface.

Example

Type-level override — a named type whose underlying shape is irrelevant to the wire form is inlined to the chosen scalar; fields typed by it emit as {type: string} regardless of the underlying shape:

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"`
}


// RawID is a custom 16-byte identifier — an array under the hood, so left to
// itself a field of this type would render as an array of integers.
type RawID [16]byte

// Coupon overrides the type of a single field directly on the field doc — no
// wrapper-type annotation. Code publishes as a bare string while RawID is left
// untouched everywhere else it appears.
//
// swagger:model
type Coupon struct {
	// Code is an opaque identifier published as a string.
	//
	// swagger:type string
	Code RawID `json:"code"`

	// Amount is the discount in cents.
	Amount int64 `json:"amount"`
}

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

Generated spec
{
  "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

Field-level override — the same directive on a single struct field replaces just that field’s inferred type in place (e.g. an opaque payload published as a string blob):

Annotated Go
// RawID is a custom 16-byte identifier — an array under the hood, so left to
// itself a field of this type would render as an array of integers.
type RawID [16]byte

// Coupon overrides the type of a single field directly on the field doc — no
// wrapper-type annotation. Code publishes as a bare string while RawID is left
// untouched everywhere else it appears.
//
// swagger:model
type Coupon struct {
	// Code is an opaque identifier published as a string.
	//
	// swagger:type string
	Code RawID `json:"code"`

	// Amount is the discount in cents.
	Amount int64 `json:"amount"`
}

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

Generated spec
{
  "description": "Coupon overrides the type of a single field directly on the field doc — no\nwrapper-type annotation. Code publishes as a bare string while RawID is left\nuntouched everywhere else it appears.",
  "type": "object",
  "properties": {
    "amount": {
      "description": "Amount is the discount in cents.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "Amount"
    },
    "code": {
      "description": "Code is an opaque identifier published as a string.",
      "type": "string",
      "x-go-name": "Code"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"
}

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

Interaction with swagger:strfmt. swagger:type wins on the type axis; a swagger:strfmt format on the same field is kept only when compatible with the resolved type (a string accepts any format, numeric types accept the numeric width formats), otherwise it is dropped with a shape-mismatch diagnostic. swagger:strfmt alone is unchanged. See swagger:strfmt.

Interaction with swagger:model. On a type declaration that also carries swagger:model, the override shapes the type’s first-class definition (e.g. swagger:type string + swagger:model → a {type: string} definition) and referencing fields $ref it. The field-level inline form above is the behaviour without swagger:model.

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

Deprecated / legacy forms

  • The array argument is deprecated — use inline, or []T for an explicit element type. It still works, with a validate.deprecated warning.
  • file as an argument is rejected with a diagnostic — use swagger:file.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Keyword reference

Keywords are the keyword: value lines that decorate an annotation block. They come in two flavours: inline (one line, keyword: value, the value classified by a value shape) and body (a header line plus indented continuation lines — a flat token list, a YAML map, or a per-line sub-language). Three things matter about any keyword: the class it belongs to, the annotation contexts that accept it, and its value shape.

This section groups the surface by class — pick the page that matches what you’re decorating. For the formal productions see grammar.md; for the value-shape and context-token reference tables see the Appendix.

Keyword classes

ClassCoversKeywords
Parameters & responsesrequest parameters and response headers (the reduced SimpleSchema surface)in, name, collectionFormat, examples, + the shared validations
Schema validations & decoratorsmodel schemas and struct fieldsmaximum/minimum/multipleOf, maxLength/minLength, maxItems/minItems, maxProperties/minProperties, pattern, patternProperties, additionalProperties, unique, default, example, enum, required, readOnly, discriminator, deprecated
Routes & operationsswagger:route / swagger:operation metadataschemes, consumes, produces, responses, parameters, tags
Securityauthentication requirements & scheme definitionssecurity, securityDefinitions
Spec metadataswagger:meta top-of-document fieldsversion, host, basePath, license, contact, tos, infoExtensions, externalDocs, extensions, tags
  • Keywords that decorate swagger:parameters fields and swagger:response headers — the reduced OAS 2.0 SimpleSchema surface, plus the parameter location and response-level examples.
  • Keywords that constrain and decorate a model schema or struct field — bounds, lengths, patterns, enums, defaults, and structural markers.
  • Keywords carried in a swagger:route or swagger:operation block — the operation’s transport metadata and its parameter and response bodies.
  • Keywords that wire authentication — the requirements that gate a spec, route, or operation, and the scheme catalogue declared once in meta.
  • Top-of-document keywords authored under swagger:meta — version, host, base path, license, contact, terms of service — plus the cross-cutting vendor-extension and external-docs keywords.
  • Reference tables — the value shapes the lexer classifies, and the meaning of each annotation-context token.

Context matrix

Which annotation family accepts a given keyword — the transpose of the annotation × keyword matrix. A ✅ means the keyword is legal on that annotation (on the annotation’s own block or on one of its fields); a blank means it is rejected there with a CodeContextInvalid diagnostic. The detailed entry for each keyword lives on its class page (linked above).

Keywordmetamodelparametersresponserouteoperation
maximum minimum multipleOf
maxLength minLength
maxItems minItems unique
pattern
collectionFormat
maxProperties minProperties
patternProperties additionalProperties
default example enum
required
readOnly discriminator
deprecated
in
name
examples
schemes consumes produces
security
securityDefinitions
responses parameters
tags
version host basePath license contact tos
infoExtensions
externalDocs
extensions

The parameters / response columns also cover the items sub-context (array elements): the array-element validations ride there too. model covers swagger:allOf member fields. See the Appendix for the precise meaning of each context token (param, header, schema, items, …).

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

Subsections of Keyword reference

Parameters & responses

These keywords decorate swagger:parameters fields and swagger:response headers. Both sites ride the reduced OAS 2.0 SimpleSchema surface: the validations constrain a primitive (or array-of-primitive) value, but the full-Schema-only keywords (maxProperties / minProperties / patternProperties / additionalProperties / readOnly / discriminator / externalDocs) do not apply here. The location keyword in and the universal name keyword are at home on this page; the validations are shared with Schema validations & decorators.

Summary

KeywordAliasesShapeContexts
instring (closed-vocab)param
namestringparam, header, schema, items
collectionFormatcollection format, collection-formatstring (closed-vocab)param, header, items
examplesYAML map (mime → payload)response
maximummaxnumberparam, header
minimumminnumberparam, header
multipleOfmultiple of, multiple-ofnumberparam, header
maxLengthmax length, maxLen, …integerparam, header
minLengthmin length, minLen, …integerparam, header
maxItemsmax items, maximumItems, …integerparam, header
minItemsmin items, minimumItems, …integerparam, header
patternstringparam, header
uniquebooleanparam, header
defaultraw-valueparam, header
exampleraw-valueparam, header
enumraw-valueparam, header
requiredbooleanparam

The validation rows (maximumrequired) are visiting here: they behave exactly as on schemas — see Schema validations & decorators. Two SimpleSchema restrictions apply on this page: required is dropped on response headers (it sets parameter.required only on a body/non-body param), and the object / structural keywords (maxProperties, minProperties, patternProperties, additionalProperties, readOnly, discriminator, externalDocs) are not legal here — placing one on a SimpleSchema site drops it with CodeUnsupportedInSimpleSchema.

Worked example(s)

A parameter set, every field carrying the SimpleSchema validation surface:

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

Generated spec
[
  {
    "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

A response with a validated header (note in is absent on header fields):

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

Generated spec
{
  "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

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 is accepted as an alias inside swagger:route Parameters: chunks; the lexer normalises it 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. The keyword is parameter-only — it has no meaning on a response header (the header name is the location).

Field naming

name

Sets the published name of any field it decorates, overriding the json: tag / Go field name. It is the one canonical field-naming keyword and works at every field site: a swagger:model property, an interface method, a swagger:parameters field (the parameter name), and a swagger:response header field (the Headers map key). Being structural, it is stripped from the description rather than leaking into it.

Precedence, most-explicit-wins and identical in every context:

name: keyword  >  swagger:name annotation  >  json: tag  >  Go field name

swagger:name is the older annotation form — still honoured, and idiomatic on interface methods — but name: is the universal keyword. Using swagger:name in a parameter or response-header context (where name: is canonical) is inert and now emits a context-invalid diagnostic pointing you at the keyword.

Wire serialisation

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. This is a SimpleSchema-only concept — schema-level contexts ignore it (schemas serialise via application/json). When the source value doesn’t match the closed vocab, the raw value is preserved verbatim on the parameter (so pipe as a typo for pipes round-trips).

Response examples

examples

Response-level examples on a swagger:response struct — a YAML map whose first-level keys are mime types and whose values are the example payloads, populating the OAS2 Response.examples field. This is the plural, response-scoped keyword; contrast the singular, schema/param/header-scoped example decorator. The swagger:operation YAML body carries examples natively (it is unmarshalled straight into the spec types); this keyword is the struct-swagger:response counterpart.

// swagger:response widgetResponse
//
// examples:
//
//	application/json:
//	  name: alice
//	  count: 3
//	application/xml: "<widget><name>alice</name></widget>"
type WidgetResponse struct {
	// in: body
	Body Widget `json:"body"`
}

See also Spec metadata for the document-level keywords that frame these operations.

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

Schema validations & decorators

These keywords decorate a swagger:model schema or any struct field doc comment. The validations constrain a value; the decorators carry defaults, examples, and structural markers. Several also apply to parameters and response headers — there they ride the reduced SimpleSchema surface (Parameters & responses).

Summary

KeywordAliasesShapeContexts
maximummaxnumberparam, header, schema, items
minimumminnumberparam, header, schema, items
multipleOfmultiple of, multiple-ofnumberparam, header, schema, items
maxLengthmax length, maxLen, …integerparam, header, schema, items
minLengthmin length, minLen, …integerparam, header, schema, items
maxItemsmax items, maximumItems, …integerparam, header, schema, items
minItemsmin items, minimumItems, …integerparam, header, schema, items
maxPropertiesmax properties, …integerschema
minPropertiesmin properties, …integerschema
patternstringparam, header, schema, items
patternPropertiespattern properties, pattern-propertiesstring (regex)schema
additionalPropertiesadditional properties, additional-propertiestrue/false/typeschema
uniquebooleanparam, header, schema, items
defaultraw-valueparam, header, schema, items
exampleraw-valueparam, header, schema, items
enumraw-valueparam, header, schema, items
requiredbooleanparam, schema
readOnlyread only, read-onlybooleanschema
discriminatorbooleanschema
deprecatedbooleanoperation, route, schema

The shared rows above (param/header/schema/items) are detailed here; on parameters and headers they behave the same, with the OAS 2.0 SimpleSchema restrictions noted on the Parameters & responses page. collectionFormat and in/name live there too.

Worked examples

Every validation on a model’s fields, side by side with the schema it produces:

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

Generated spec
{
  "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

The object-validation keywords constrain the map of properties rather than named fields:

Annotated Go
// Attributes is a free-form object constrained by the object-validation
// keywords: it must carry between 1 and 10 properties, and any property whose
// name matches the regex is permitted. Object validations constrain the map of
// (additional) properties rather than named struct fields.
//
// minProperties: 1
// maxProperties: 10
// patternProperties: ^x-
//
// swagger:model Attributes
type Attributes map[string]any

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

Generated spec
{
  "description": "Attributes is a free-form object constrained by the object-validation\nkeywords: it must carry between 1 and 10 properties, and any property whose\nname matches the regex is permitted. Object validations constrain the map of\n(additional) properties rather than named struct fields.",
  "type": "object",
  "maxProperties": 10,
  "minProperties": 1,
  "additionalProperties": {},
  "patternProperties": {
    "^x-": {}
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/validations"
}

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

Numeric validations

Apply to numeric schema types (integer, number). On a typed schema with a non-numeric type they emit CodeShapeMismatch and drop; on a typeless schema they apply best-effort.

maximum / minimum

Upper / lower bound on a numeric value (aliases max / min). The value may carry a leading comparison operator that sets the exclusive/inclusive bound:

  • maximum: 10 — inclusive (≤ 10);
  • maximum: <10 — exclusive (< 10);
  • maximum: <=10 / maximum: =10 — inclusive.

Map to schema.maximum/exclusiveMaximum and schema.minimum/exclusiveMinimum.

multipleOf

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

Length, array & object validations

maxLength / minLength apply only to string-typed schemas; maxItems / minItems only to array-typed; maxProperties / minProperties / patternProperties only to object-typed. The wrong pairing emits CodeShapeMismatch and drops. The object keywords are additionally full-Schema-only — no SimpleSchema (non-body param, header, items) form exists in OAS 2.0, so on such a site they drop with CodeUnsupportedInSimpleSchema.

maxLength / minLength

String length bounds. Many ergonomic aliases (max length, max-length, maxLen, maximumLength, …; min likewise). Map to schema.maxLength / schema.minLength.

maxItems / minItems

Array length bounds (aliases max items, maximumItems, …). Map to schema.maxItems / schema.minItems.

maxProperties / minProperties

Property-count bounds on an object schema (aliases max properties, …). Map to schema.maxProperties / schema.minProperties. Schema-only.

patternProperties

Constrains the names of properties on an object schema by regex. The argument is one regex string; each line adds an entry to schema.patternProperties mapping the regex to an empty value schema ({} — any value allowed). Repeated lines accumulate. Aliases pattern properties, pattern-properties. The regex is RE2-hygiene-checked: one that doesn’t compile raises CodeInvalidAnnotation but is preserved.

For typed value schemas (a regex → primitive or model $ref), use the decl-level swagger:patternProperties marker. patternProperties is JSON-Schema, beyond the Swagger 2.0 subset — see Maps & free-form objects.

additionalProperties

Policy for keys beyond the named properties on an object schema: true (allow any), false (close the object), or a value type (primitive / []T, or a model name → $ref). On a map field it overrides the Go element schema; on a $ref’d field the value rides an allOf sibling so the reference is kept. Aliases additional properties, additional-properties. Lowest-priority and object-only — dropped with CodeShapeMismatch on a non-object. The decl-level swagger:additionalProperties marker does the same on a type.

Format

pattern

A regex constraint on a string value, preserved verbatim on schema.pattern — including backslash escapes (\d, \., \n reach the spec as literal two-character sequences). The grammar runs a best-effort RE2 compile; a failure surfaces CodeInvalidAnnotation but the value still lands (downstream tools may use a wider regex dialect).

unique

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

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). Single-line for primitives (default: 1), multi-line bodies for complex literals:

// default:
//   { "rps": 100, "burst": 200 }

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). This is the singular, schema-scoped keyword; for the plural response-scoped examples (a map keyed by mime type) see Parameters & responses.

enum

A closed set of allowed values. Accepted forms: comma list (enum: red, green), bracketed comma list (enum: [red, green]), JSON array (enum: ["red","green"]), or a multi-line - list. Each element is coerced against the resolved type; maps to schema.enum.

For string enums driven by Go consts the swagger:enum annotation is more idiomatic — it picks up the constant names + godoc and produces x-go-enum-desc. The enum: keyword is the manual override. (Set SkipEnumDescriptions: true to keep the const→value mapping on x-go-enum-desc only, out of the description.)

required

Marks a field as required. Boolean.

  • On a swagger:model field: adds the field name to the schema’s required array.
  • On a swagger:parameters field: sets parameter.required.
  • On a swagger:response header: not applicable — silently dropped.

readOnly

Marks a schema property read-only. Aliases read only, read-only. Maps to schema.readOnly. Schema-only — inside a SimpleSchema context it drops with CodeUnsupportedInSimpleSchema.

discriminator

Marks the property as the discriminator for an allOf polymorphic schema. Boolean; writes the property name onto the schema’s discriminator. Schema-only. The property should also be required. Subtypes that allOf-embed the base inherit it; each subtype’s discriminator value is its definition name. See Polymorphic models.

deprecated

Marks the carrying entity deprecated. Boolean. On operations/routes it writes the native OAS 2.0 deprecated; OAS 2.0 has no Schema-object deprecated, so on a model or field it emits x-deprecated: true. A godoc Deprecated: paragraph is an exact synonym recognised in any context — and is idiomatic on Go doc comments. Because it carries intent, x-deprecated survives even under SkipExtensions.

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

Routes & operations

These keywords live inside a swagger:route or swagger:operation block. They carry the operation’s transport metadata — the URL schemes, the media types it consumes and produces — and the two sub-language bodies that declare its parameters and responses. Several of them double as document-wide defaults under swagger:meta (Spec metadata), where an operation-level value overrides the default.

Summary

KeywordAliasesShapeContexts
schemesflex-listmeta, route, operation
consumesflex-listmeta, route, operation
producesflex-listmeta, route, operation
responsessub-language (<code>: <tokens>)route, operation
parameterssub-language (+ name: chunks)route, operation
tagsstring list / tag objectsmeta, route, operation
deprecatedbooleanoperation, route, schema
securityYAML sequence (raw-block)meta, route, operation
externalDocsexternal docs, external-docs{description, url}meta, route, operation, schema
extensionsx-* YAML maproute, operation (cross-cutting)

The visiting rows are documented where they primarily apply: deprecated is detailed under Schema validations & decorators; security (and its securityDefinitions catalogue) under Security; externalDocs and extensions under Spec metadata.

Worked examples

A swagger:route block — the path-line annotation plus its transport metadata and a Responses: body — side by side with the path item it produces:

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

Generated spec
{
  "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

The swagger:operation long form carries the same metadata in a YAML body, including an inline parameters sequence:

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

Generated spec
{
  "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

Transport metadata

schemes

Accepted URL schemes for the operation. Flexible list — comma inline, multi-line bare, YAML - markers, or any combination all produce the same output (Schemes: http, https ≡ a - http / - https block). See sub-languages §flex-lists for the unified rule.

Maps to schemes on the enclosing operation. It is also a document default under swagger:meta (spec.schemes), where an operation-level value overrides the meta-level one — see Spec metadata.

consumes / produces

Media-type lists — the request body MIME types the operation consumes and the response MIME types it produces. Same flex-list rule as schemes: comma inline, multi-line bare, YAML - markers, or any combination.

Consumes:
  - application/json
  - application/xml

Produces: application/json

Map to consumes / produces on the surrounding scope. Like schemes, both are also swagger:meta document defaults overridden per operation.

Body sub-languages

responses

Per-route / per-operation response declarations. The body is one response per line in the form <code>: <tokens>, where <code> is an HTTP status (or default) and <tokens> names the body schema and/or description:

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

The full per-line grammar lives at sub-languages §responses.

parameters

Per-route / per-operation parameter declarations. The body is a sequence of + name: chunks — the + is the chunk-start sigil (- is accepted as an alias) — each chunk a small key/value block describing one parameter:

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

The full per-chunk grammar lives at sub-languages §parameters.

tags

Tag declarations whose shape depends on context:

  • In swagger:route / swagger:operation the body is a plain string list. It is unioned and deduplicated with the tags written on the annotation’s header line, and the result lands on the operation’s tags:

    Tags:
      - pets
      - store
  • In swagger:meta the body is instead a YAML sequence of tag objects emitted into the spec’s top-level tags — each with a name, an optional description, a nested externalDocs, and any x-* vendor extensions:

    Tags:
    - name: pets
      description: Everything about your Pets
      externalDocs:
        description: Find out more
        url: https://example.com/docs/pets
    - name: store
      x-display-name: Store

    The meta tag-objects form is also referenced from Spec metadata.

Visiting keywords

These keywords also appear in a route/operation block but are detailed on their home page:

  • deprecated — marks the operation deprecated (native OAS 2.0 deprecated). See Schema validations & decorators.
  • security — the per-route / per-operation requirement list (an empty Security: [] on an operation is an explicit public opt-out). See Security.
  • externalDocs — the operation’s external-documentation pointer. See Spec metadata.
  • extensions — vendor x-* entries on the operation. See Spec metadata.
Last edited by: fredbi Aug 1, 2026
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license.

Security

Two keywords carry authentication into the spec. security lists the requirements that gate the document, a route, or a single operation; securityDefinitions is the scheme catalogue — declared once in swagger:meta and referenced by name from every requirement. A requirement is only meaningful when the scheme it names is defined, so the two are almost always authored together (see Spec metadata for the rest of the swagger:meta surface and Routes & operations for where per-route requirements live).

Summary

KeywordAliasesShapeContexts
securityYAML sequence (raw-block)meta, route, operation
securityDefinitionssecurity definitions, security-definitionsYAML map (raw-block)meta

Worked example

The scheme catalogue and the document-wide default requirement, declared once in the package swagger:meta block — the schemes golden captures both securityDefinitions and the top-level security:

Annotated Go
// Package security Reports API.
//
// The swagger:meta block declares the security schemes once and sets the
// document-wide default requirement.
//
//	Version: 1.0.0
//
//	SecurityDefinitions:
//	  api_key:
//	    type: apiKey
//	    in: header
//	    name: X-API-Key
//	  oauth2:
//	    type: oauth2
//	    flow: accessCode
//	    authorizationUrl: https://example.com/auth
//	    tokenUrl: https://example.com/token
//	    scopes:
//	      read: read reports
//	      write: write reports
//
//	Security:
//	  - api_key: []
//
// swagger:meta
package security

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

Generated spec
{
  "security": [
    {
      "api_key": []
    }
  ],
  "securityDefinitions": {
    "api_key": {
      "type": "apiKey",
      "name": "X-API-Key",
      "in": "header"
    },
    "oauth2": {
      "type": "oauth2",
      "flow": "accessCode",
      "authorizationUrl": "https://example.com/auth",
      "tokenUrl": "https://example.com/token",
      "scopes": {
        "read": "read reports",
        "write": "write reports"
      }
    }
  }
}

Full source: docs/examples/concepts/security/testdata/schemes.json

A route then overrides that default with its own Security: requirement — here oauth2 with the read and write scopes:

Annotated Go
// listReports inherits the document-wide default requirement (api_key) — no
// Security: keyword is needed.
//
// swagger:route GET /reports reports listReports
//
// responses:
//   200: description: the reports

// createReport overrides the default with its own Security: requirement —
// oauth2 with the read and write scopes. The Security: block is YAML: a sequence
// of requirement objects, scopes as a flow (or block) list.
//
// swagger:route POST /reports reports createReport
//
// Security:
//   - oauth2: [read, write]
//
// responses:
//   201: description: created

// archiveReport requires BOTH schemes at once — two keys in a single sequence
// item are ANDed into one requirement object (separate items would mean OR).
//
// swagger:route POST /reports/archive reports archiveReport
//
// Security:
//   - api_key: []
//     oauth2: [write]
//
// responses:
//   200: description: archived

// publicReport opts out of the document default entirely — an empty
// `Security: []` emits an explicit empty requirement, marking the operation
// public regardless of the document-wide default.
//
// swagger:route GET /reports/public reports publicReport
//
// Security: []
//
// responses:
//   200: description: the public reports

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

Generated spec
[
  {
    "oauth2": [
      "read",
      "write"
    ]
  }
]

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

Keyword details

security

A YAML sequence of requirement objects parsed from the Security: body. The semantics are OAS 2.0:

  • multiple keys within one sequence item are ANDed — all of those schemes are required together ({api_key, oauth2} in one item);
  • separate items are ORed — satisfying any one item grants access;
  • a scheme’s value is its scope list, a flow ([read, write]) or block list. For non-scoped schemes (apiKey, basic) the list is empty (api_key: []), meaning the scheme is required with no scopes;
  • an empty top-level Security: [] on an operation emits an explicit empty requirement — an intentional public opt-out that overrides the document-wide default rather than inheriting it.

A bare top-level mapping (api_key: / oauth2: read, write, comma-split scopes) is still read as one OR requirement per key for back-compatibility. Maps to security on the enclosing object. Legal in swagger:meta (the document default), swagger:route, and swagger:operation. The full per-line body grammar lives at sub-languages §security requirements.

securityDefinitions

A YAML map, parsed directly into the spec.securityDefinitions shape — each entry is a named scheme (apiKey, oauth2, basic) with its OAS 2.0 fields (type, in, name, flow, authorizationUrl, tokenUrl, scopes, …); see OAS v2 §securityDefinitionsObject. Aliases security definitions, security-definitions. Meta-only — the scheme catalogue is declared once at the top of the document and referenced by name from every security requirement. Its detail anchor is #securitydefinitions.

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

Spec metadata

These keywords author the spec’s top-level fields. Most live in the package doc comment carrying the swagger:meta block; a couple are cross-cutting and merely have their home here — extensions lands on whatever scope it decorates, and externalDocs rides meta, operations, schemas, and struct fields alike. The remaining document-level concerns (schemes/consumes/produces, security/ securityDefinitions, the meta tags form) are owned by sibling pages and only visit this one.

Summary

KeywordAliasesShapeHome
versionstringhere
hoststringhere
basePathbase path, base-pathstringhere
licenseName [URL]here
contactcontact info, contact-infoName <email> [URL]here
tosterms of service, terms-of-service, termsOfServiceprosehere
infoExtensionsinfo extensions, info-extensionsx-* YAML maphere
extensionsx-* YAML maphere (cross-cutting)
externalDocsexternal docs, external-docs{description, url}here (cross-cutting)
schemesflex-list/codescan/maintainers/keywords/routes-and-operations/
consumes / producesflex-list/codescan/maintainers/keywords/routes-and-operations/
securityYAML/codescan/maintainers/keywords/security/
securityDefinitionssecurity definitions, security-definitionsYAML map/codescan/maintainers/keywords/security/
tagsYAML sequence/codescan/maintainers/keywords/routes-and-operations/#tags

The visiting rows are documented where they primarily apply: schemes, consumes, and produces are document-wide defaults overridden per operation (Routes & operations); security and securityDefinitions are detailed under Security; the swagger:meta tag-objects form of tags is described under Routes & operations.

Worked example

A complete swagger:meta block, side by side with the document-level spec it produces:

Annotated Go
// 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
//
//	ExternalDocs:
//	  description: Full API guide
//	  url: https://example.com/docs
//
//	Tags:
//	- name: pets
//	  description: Everything about your Pets
//	  externalDocs:
//	    description: Find out more
//	    url: https://example.com/docs/pets
//	- name: store
//	  description: Access to Petstore orders
//	  x-display-name: Store
//
//	SecurityDefinitions:
//	  basic_auth:
//	    type: basic
//	  api_key:
//	    type: apiKey
//	    in: header
//	    name: X-API-Key
//
//	Security:
//	  basic_auth:
//
//	InfoExtensions:
//	  x-logo:
//	    url: https://example.com/logo.png
//	    altText: Example
//
// swagger:meta
package meta

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

Generated spec
{
  "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",
    "x-logo": {
      "altText": "Example",
      "url": "https://example.com/logo.png"
    }
  },
  "host": "api.example.com",
  "basePath": "/v1",
  "paths": {},
  "securityDefinitions": {
    "api_key": {
      "type": "apiKey",
      "name": "X-API-Key",
      "in": "header"
    },
    "basic_auth": {
      "type": "basic"
    }
  },
  "security": [
    {
      "basic_auth": []
    }
  ],
  "tags": [
    {
      "description": "Everything about your Pets",
      "name": "pets",
      "externalDocs": {
        "description": "Find out more",
        "url": "https://example.com/docs/pets"
      }
    },
    {
      "description": "Access to Petstore orders",
      "name": "store",
      "x-display-name": "Store"
    }
  ],
  "externalDocs": {
    "description": "Full API guide",
    "url": "https://example.com/docs"
  }
}

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

Meta single-line keywords

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

version

API version string. Maps to info.version.

host

Default host for the API. Defaults to localhost when empty. Maps to spec.host.

basePath

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

license

License declaration, in two accepted forms:

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

The trailing token starting with a URL scheme becomes license.url; the prefix becomes license.name. A bare name with no URL is accepted too. Maps to info.license.

contact

Contact declaration. The 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> https://example.com — same, plus a trailing URL;
  • just a URL, with no name.

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

Document-level keywords

tos

Terms-of-service prose paragraph. The 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.

infoExtensions

Vendor-extension declarations as a YAML map, landed on info.extensions. Keys must start with x- or X-; a non-x-* key emits CodeInvalidAnnotation and drops. Meta-only. Aliases: info extensions, info-extensions.

InfoExtensions:
  x-logo:
    url: https://example.com/logo.png
    altText: Example

For the same map on the surrounding scope rather than info, use extensions.

extensions

Vendor-extension declarations as a YAML map, landed on the surrounding scope rather than on info: spec.extensions, operation.extensions, schema.extensions, parameter.extensions, header.extensions, and so on — including on parameters and response headers. Keys must start with x- or X-; a non-x-* key emits CodeInvalidAnnotation and drops.

Extensions:
  x-internal-id: 42
  x-feature-flags:
    - alpha
    - beta
  x-nested:
    enabled: true
    rate: 0.5

This keyword is cross-cutting — it is documented here as its home, but applies wherever a YAML body is parsed. For the meta-only info.extensions variant see infoExtensions.

externalDocs

External-documentation pointer as a YAML map with description and url keys. Aliases: external docs, external-docs.

Emitted on:

  • swagger:meta → the top-level externalDocs object (and, nested under a Tags: entry, that tag’s externalDocs);
  • swagger:route / swagger:operation → the operation’s externalDocs;
  • swagger:model (and any full Schema, e.g. a body parameter’s schema) → the schema’s externalDocs;
  • a struct field → the property’s externalDocs. On a $ref’d field (whose property is a bare $ref) it is lifted onto the wrapping allOf compound, alongside the field’s description and x-* siblings.

An empty block (no description/url) is skipped rather than emitting a bare externalDocs: {}. It is a full-Schema-only keyword: on a SimpleSchema site (a non-body parameter, response header, or items chain) it drops with a CodeUnsupportedInSimpleSchema diagnostic.

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

Like extensions, this keyword is cross-cutting; it is documented here as its home.

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

Appendix: shapes & contexts

The two reference tables behind the keyword class pages: the value shapes the lexer assigns to a keyword’s value, and the context tokens used in each page’s scoped summary table.

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. Each class page’s scoped summary table combines these in its Contexts column.

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

Using a keyword outside its legal contexts emits a CodeContextInvalid diagnostic and the keyword is dropped from the affected block. The Context matrix maps these tokens onto the annotation families.

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

Options reference

codescan.Options is the single configuration struct passed to codescan.Run. The zero value is a valid configuration — every flag defaults to false, every slice/map to nil, every numeric tunable to its built-in default. You set only what you need.

This page is the field-by-field catalogue. The godoc is the normative source; each field here links to the how-to guide that shows it on real input where one exists.

Note

codescan never writes to stdout or stderr. Every scan-time observation — a dropped construct, a rename, a prune — flows through the OnDiagnostic callback. See Diagnostics & observability below.

Inputs & scope

What gets loaded and which packages and types are in play. See Scope & discovery.

OptionTypeDefaultEffect
Packages[]stringnilPackage patterns to scan (e.g. ./...), resolved relative to WorkDir.
WorkDirstring"" (cwd)Working directory the package patterns and module resolution are rooted at.
BuildTagsstring""Go build tags to activate while loading, so tag-guarded source is scanned. See Build tags.
Include[]stringnilAllow-list of package path patterns; when non-empty only matching packages are scanned. See Scoping the scan.
Exclude[]stringnilDeny-list of package path patterns, applied after Include. See Scoping the scan.
IncludeTags[]stringnilAllow-list filtering routes/operations by their swagger tags.
ExcludeTags[]stringnilDeny-list filtering routes/operations by their swagger tags.
ExcludeDepsboolfalseSkip types reached through module dependencies, keeping the scan to first-party packages.
ScanModelsboolfalseAlso emit a definition for every swagger:model type, not just route-reachable ones. See When the scanner emits a type.
PruneUnusedModelsboolfalseWith ScanModels, drop discovered definitions not transitively reachable from a path, shared response/parameter, or InputSpec root. Runs before name reduction; InputSpec definitions are pinned. No-op without ScanModels. See Pruning unused models.
InputSpec*spec.SwaggernilBase document to overlay scanned discoveries onto; its definitions are pinned and seed pruning roots. See Overlaying a spec.

Names & references

How definitions are named and how $refs render. See Names & $refs.

OptionTypeDefaultEffect
NameFromTags[]stringnil (⇒ ["json"])Ordered struct-tag types a property/parameter/header name is derived from; first that supplies a name wins. Explicit empty slice ⇒ Go field name. Only the name — json encoding directives (-, ,omitempty, ,string) always come from json. See Naming from struct tags.
SkipJSONifyInterfaceMethodsboolfalseEmit interface-method property names verbatim (ID, CreatedAt) instead of auto-jsonifying them (id, createdAt). Only affects interface methods; struct fields and swagger:name overrides are unchanged. See Interface-method property names.
RefAliasesboolfalseRender Go type aliases as a first-class $ref (via swagger:model) instead of expanding them inline. See Alias rendering.
TransparentAliasesboolfalseMake aliases fully transparent — never creating a definition. See Alias rendering.
DefaultAllOfForEmbedsboolfalseRender a plain (untagged, unnamed) struct embed as an allOf member — a $ref for a model embed, an inline member otherwise — with the embedding struct’s own fields in a sibling member, instead of inlining promoted properties. json-named embeds, swagger:allOf embeds, and interface embeds are unaffected. See Composing embeds with allOf.
NameConcatBudgetfloat640 (⇒ 0.65)Readability cutoff [0,1] for the package-segment concatenation that deconflicts colliding definition names; lower scores are more readable. A group whose best concat scores above the budget is a candidate for the hierarchical fallback. See Resolving $ref name conflicts.
EmitHierarchicalNamesboolfalseFor the rare collision group whose best flat concat exceeds NameConcatBudget, emit nested container definitions (#/definitions/<pkg>/<Name>) instead of a long flat concat, with an explanatory diagnostic. The always-correct flat concat is the default. See Resolving $ref name conflicts.
EmitRefSiblingsboolfalseEmit a $ref’d field’s description and vendor extensions as direct $ref siblings ({$ref, description, x-*}) instead of an allOf wrap. Validations/externalDocs still force a compound. See Descriptions beside a $ref.
SkipAllOfCompoundingboolfalseNever emit an allOf compound for a $ref’d field. Validations/externalDocs are dropped (description/extensions too, unless EmitRefSiblings keeps them as siblings); each drop raises a diagnostic. required is unaffected. See Descriptions beside a $ref.
DescWithRefboolfalseDeprecated — prefer EmitRefSiblings. In the description-only case, wrap the $ref in a single-arm allOf to preserve the description (strict draft-4 shape). No-op when EmitRefSiblings is set. See Descriptions beside a $ref.

Titles & descriptions

The human-readable text the spec carries. See Titles & descriptions.

OptionTypeDefaultEffect
SingleLineCommentAsDescriptionboolfalseRoute every single-line doc comment to description, never to title/summary (the first-sentence convention otherwise applies). Multi-line comments keep the title/description split. See Single-line comments as descriptions.
AfterDeclCommentsboolfalseLet swagger annotations live inside a struct body or as a trailing comment, in addition to the doc comment above the declaration, so the godoc stays clean. v0.36 scope: type declarations (struct inside-body + alias trailing comment). See Keeping annotations out of the godoc.
CleanGoDocboolfalseStrip godoc doc-link brackets from generated title/description (humanizing unresolved ones, dropping reference-definition lines, recomposing resolved links to each schema’s exposed name). Applies only to godoc-derived prose; overrides are untouched. See Cleaning godoc doc-links.

Field types, formats & extensions

How an individual property renders. See Field types & formats.

OptionTypeDefaultEffect
SetXNullableForPointersboolfalseEmit x-nullable: true on pointer-typed fields. See Nullable pointers.
SkipExtensionsboolfalseSuppress all x-go-* vendor extensions in the output. See Vendor extensions.
EmitXGoTypeboolfalseStamp an x-go-type extension (fully-qualified originating Go type) on every emitted definition, for round-tripping a spec back to its Go types. Suppressed under SkipExtensions. See Vendor extensions.
SkipEnumDescriptionsboolfalseKeep the per-enum-value const-name mapping (from swagger:enum) out of the description, exposing it only via the x-go-enum-desc extension. Suppressed entirely under SkipExtensions.

Diagnostics & observability

Channels for what the scan observed; these do not change the output spec.

OptionTypeDefaultEffect
OnDiagnosticfunc(Diagnostic)nilInvoked once per diagnostic in source order (parser warnings, validation failures, prunes, renames). Diagnostics never block the build — invalid constructs are dropped from the spec while their explanation flows here. The only output channel. Experimental while LSP integration matures.
OnProvenancefunc(Provenance)nilInvoked once per anchor node in the produced spec, carrying its JSON pointer and the source position of the Go construct that produced it. Never blocks the build. Experimental while LSP/TUI integration matures.
DebugboolfalseDeprecated, ignored. The legacy stderr debug logger was retired; wire OnDiagnostic instead. Retained for API compatibility.

See also

  • Annotations — the swagger:* vocabulary the scanner reads from comments.
  • Keyword reference — the keyword: value forms inside annotation bodies.
  • Shaping the output — task-oriented how-tos that put these options to work on real input.
Last edited by: fredbi Aug 1, 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.

Tool-directive markers are dropped

Two families of non-swagger directive lines are filtered out of the prose surface, so they never leak into a title or description:

  • Go directives//go:generate, //nolint:foo, //lint:ignore (a lowercase word + : + an immediate argument, no leading space).
  • Kubernetes-style +marker comments — any line whose content begins with + immediately followed by a letter: +kubebuilder:…, +genclient, +k8s:…, +optional, as emitted by kubebuilder / controller-gen. Requiring a letter after the + keeps ordinary prose (+1 for …) intact. This is also why a stray +kubebuilder:default:=false no longer crashes the scan (go-swagger#3007) — the marker is dropped, not parsed as a keyword.

The +marker filter runs at the prose-classification stage (after annotation bodies are folded), so the swagger:route Parameters: + name: chunk separator — a + followed by a space — is unaffected.

Markdown semantics that survive

  • Bullet lists in descriptions are preserved. A line starting with - foo lands in the description as "- foo" (not "foo"). A markdown-style * foo or + foo bullet is recognised the same way and normalised to - foo (the same rewrite gofmt applies), so the two forms agree.
  • --- 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. The - marker may also be written as a markdown * or + bullet — all are normalised to the same list.

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 bodiessecurityDefinitions:, 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 keywordmin, 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:A scalar primitive (string / number / integer / boolean) OR a Go ident, each with optional [] prefixes (body:[]string, body:[]Pet)A primitive emits a typed schema; a Go ident emits a $ref to #/definitions/<name> — array-wrapped per [] count. The reserved keywords array / object / file / null are rejected with a diagnostic (use []T or a model name)
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. An untagged token is always read as a NAME, never a type: a bare 200: string is a (dangling) response ref, not a primitive body — use the unambiguous body:string form for a primitive body.
  • Subsequent untagged tokens accumulate into the description.

This block is a line-based sub-language, not YAML: each entry is a single <code>: <token>* line. A description must sit on that same line — either via the description: tag (403: description: Unauthorized) or as trailing untagged tokens (200: listResponse all the users). A nested description: written on an indented continuation line under a bare 403: is not parsed and yields an empty description; for that style, spell the operation out with a swagger:operation YAML body instead.

Examples

Responses:
  200: User the user as returned                  # untagged → response="User", desc="the user as returned"
  200: body:string the version                    # primitive body (use the body: tag) + description
  200: body:[]integer the id list                 # array-of-primitive body
  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. When the unresolved name is a primitive type spelling (200: string), the diagnostic points the author at the body: form (200: body:string).
  • Reserved body: type (200: body:object, body:file, body:array, body:null) — these look like a type but are not valid response body types; the line drops with a diagnostic suggesting a scalar primitive, []T, or a model name.

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 Aug 1, 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.Postoken.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_ADDITIONAL_PROPERTIESswagger:additionalProperties
ANN_PATTERN_PROPERTIESswagger:patternProperties
ANN_FILEswagger:file
ANN_TITLEswagger:title
ANN_DESCRIPTIONswagger:description

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
                      | TitleAnnotation
                      | DescriptionAnnotation ;

ModelAnnotation       = ANN_MODEL ,       [ IDENT_NAME ] ;
ResponseAnnotation    = ANN_RESPONSE ,    [ IDENT_NAME ] ;
ParametersAnnotation  = ANN_PARAMETERS ,  IDENT_NAME , { IDENT_NAME } ;
NameAnnotation        = ANN_NAME ,        IDENT_NAME ;
TitleAnnotation       = ANN_TITLE ,       RAW_VALUE ;
DescriptionAnnotation = ANN_DESCRIPTION , RAW_VALUE ;

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 ;

swagger:title / swagger:description are schema-family overrides — they replace the godoc-derived title / description on a model, field, response, or header. They dispatch through the schema parser (not the classifier parser), so validation keywords co-located on the same comment group still surface. The RAW_VALUE is the rest of the head line; swagger:description additionally folds a blank-terminated body (Option B) or, with a trailing |, a verbatim literal markdown block. A blank override emits CodeEmptyOverride; swagger:title is rejected with CodeContextInvalid on a non-body parameter or response header.

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 ;

…where both share the header arguments:

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 coerciondefault: 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 gatingvalidations.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).