OPEN SWAG GO
Features

Endpoints

Every API operation in open-swag-go is described by an Endpoint struct. You define the HTTP method, path, parameters, request body, and responses — open-swag-go turns them into a valid OpenAPI 3.x spec.

The Endpoint Struct

endpoint.go
type Endpoint struct {
	Method      string       // GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE (case-insensitive)
	Path        string       // "/users/{id}", "/users/:id", "/files/*path" or "/users/{id:[0-9]+}"
	OperationID string       // Overrides the generated operationId (v1.3.0+)
	Summary     string       // Short one-line description
	Description string       // Detailed explanation (supports Markdown)
	Tags        []string     // Grouping tags for the docs UI sidebar
	Parameters  []Parameter  // Explicit path, query, header and cookie parameters
	QueryParams interface{}  // Struct whose fields become query parameters
	PathParams  interface{}  // Struct whose fields become path parameters
	RequestBody *RequestBody // Request body definition (nil for GET/DELETE)
	Responses   Responses    // map[int]ResponseSpec keyed by status code
	Security    []string     // Security scheme names, e.g. openswag.SecurityBearerAuth
	Deprecated  bool         // Marks the operation as deprecated
}

A minimal endpoint only needs Method, Path, and at least one Response:

openswag.Endpoint{
	Method:  "GET",
	Path:    "/health",
	Summary: "Health check",
	Responses: openswag.Responses{
		200: {Description: "OK"},
	},
}

Path styles (v1.3.0+)

Path can be written the way your router writes it. Router-style segments are normalised to OpenAPI {name} segments in the generated spec:

You writeSpec path
/users/{id}/users/{id}
/users/:id (Gin, Echo, Fiber)/users/{id}
/files/*path (wildcard)/files/{path}
/users/{id:[0-9]+} (chi regex)/users/{id}

Every {name} segment also gets a required string path parameter automatically, unless you define a parameter with that name yourself.

Operation IDs (v1.3.0+)

Every operation in the spec has an operationId. When OperationID is empty, one is derived from the method and the normalised path in lowerCamelCase, with By in front of each path parameter:

EndpointGenerated operationId
GET /users/{id}getUsersById
POST /userspostUsers
GET /users/{id}/orders/{orderId}getUsersByIdOrdersByOrderId
GET /api/v1/user-profilesgetApiV1UserProfiles
GET /getRoot

openapi-typescript keys its generated operations interface by it, and orval names its functions and hooks after it (orval refuses specs without one). Set OperationID when you want a specific name:

openswag.Endpoint{
	Method:      "GET",
	Path:        "/users/{id}",
	OperationID: "getUser",
}

Validate at startup (v1.3.0+)

Add and AddAll never fail. If two endpoints share a method and path, BuildSpec silently keeps the last one; an unsupported method is skipped. Call Validate once after registering everything and treat an error as fatal:

main.go
docs.AddAll(endpoints...)
if err := docs.Validate(); err != nil {
	log.Fatal(err)
}

Validate returns one joined error listing every duplicate method+path pair (compared after upper-casing the method and normalising the path, so GET /users/:id and get /users/{id} collide), every unsupported HTTP method, and every duplicate operationId.

Parameters

Use Parameter to describe path, query, header, or cookie inputs.

parameter.go
type Parameter struct {
	Name        string       // Parameter name, e.g. "id" or "page"
	In          string       // Location: "path", "query", "header", or "cookie"
	Description string       // Human-readable description
	Required    bool         // Whether the parameter is mandatory
	Schema      *spec.Schema // e.g. spec.NewSchema("integer"); nil means string
	Example     interface{}  // Optional example value shown in the docs UI
}

Schema is a *spec.Schema from github.com/gopackx/open-swag-go/pkg/spec, not a Go value to reflect over. Leave it nil for a plain string parameter. The helpers openswag.PathParam, QueryParam, RequiredQueryParam, HeaderParam and CookieParam build string parameters for you.

Path parameters

Path parameters map to {placeholder} segments in the endpoint path. They are always required.

openswag.Endpoint{
	Method:  "GET",
	Path:    "/users/{id}",
	Summary: "Get user by ID",
	Parameters: []openswag.Parameter{
		{
			Name:        "id",
			In:          "path",
			Description: "Unique user identifier",
			Required:    true,
			Schema:      spec.NewSchema("integer"),
		},
	},
	Responses: openswag.Responses{
		200: {Description: "User found"},
		404: {Description: "User not found"},
	},
}

Query parameters

Query parameters are appended to the URL as ?key=value. They are typically optional.

openswag.Endpoint{
	Method:  "GET",
	Path:    "/products",
	Summary: "List products",
	Parameters: []openswag.Parameter{
		{
			Name:        "page",
			In:          "query",
			Description: "Page number (1-based)",
			Required:    false,
			Schema:      spec.NewSchema("integer"),
		},
		{
			Name:        "limit",
			In:          "query",
			Description: "Items per page (max 100)",
			Required:    false,
			Schema:      spec.NewSchema("integer"),
		},
		{
			Name:        "category",
			In:          "query",
			Description: "Filter by category slug",
			Required:    false,
			Schema:      spec.NewSchema("string"),
		},
	},
	Responses: openswag.Responses{
		200: {Description: "Paginated product list"},
	},
}

RequestBody

Use RequestBody to describe the payload for POST, PUT, and PATCH operations.

request_body.go
type RequestBody struct {
	Description string      // What the body represents
	ContentType string      // MIME type, e.g. "application/json"
	Required    bool        // Whether the body is mandatory
	Schema      interface{} // Go struct for schema generation
}

Since v1.3.0 a named struct passed as Schema (and every named struct nested in it) is emitted once under components.schemas and referenced with $ref, on both request bodies and responses; see Schema Components.

openswag.Endpoint{
	Method:  "POST",
	Path:    "/users",
	Summary: "Create a user",
	Tags:    []string{"Users"},
	RequestBody: &openswag.RequestBody{
		Description: "User to create",
		ContentType: "application/json",
		Required:    true,
		Schema:      CreateUserRequest{},
	},
	Responses: openswag.Responses{
		201: {Description: "User created"},
		400: {Description: "Validation error"},
	},
}

Response

Each endpoint declares one or more responses for different status codes. Endpoint.Responses is a Responses map (map[int]ResponseSpec), and openswag.Response(desc, schema) builds a ResponseSpec.

response.go
type ResponseSpec struct {
	Description string      // What this response means
	Schema      interface{} // Go struct for schema generation (optional)
}

The status code is the map key in Endpoint.Responses — it is not a field on ResponseSpec. Endpoints typically include at least a success response and one or two error responses:

Responses: openswag.Responses{
	200: {Description: "Successful response", Schema: UserResponse{}},
	400: {Description: "Invalid request parameters", Schema: ErrorResponse{}},
	404: {Description: "Resource not found"},
}

Struct-Based Query and Path Parameters

Instead of listing parameters one by one, you can use a Go struct with tags to define them all at once. Pass it as Endpoint.QueryParams or Endpoint.PathParams; open-swag-go reads the struct fields and generates the parameter list automatically.

struct_params.go
// ListProductsParams defines query parameters as a struct.
type ListProductsParams struct {
	Page     int    `query:"page"     description:"Page number"       example:"1"`
	Limit    int    `query:"limit"    description:"Items per page"    example:"20"`
	Category string `query:"category" description:"Filter by category"`
	SortBy   string `query:"sort_by"  description:"Sort field"        example:"created_at"`
}
 
// GetProductParams defines path parameters as a struct.
type GetProductParams struct {
	ID int64 `path:"id" description:"Product ID" example:"42"` // path params are always required
}

Pass the structs to the endpoint:

openswag.Endpoint{
	Method:      "GET",
	Path:        "/products",
	Summary:     "List products with filtering",
	Tags:        []string{"Products"},
	QueryParams: ListProductsParams{},
	Responses: openswag.Responses{
		200: {Description: "Product list"},
	},
}
 
openswag.Endpoint{
	Method:     "GET",
	Path:       "/products/{id}",
	Summary:    "Get product by ID",
	PathParams: GetProductParams{},
}

Parameter fields follow the same tag rules as request bodies (v1.3.0+): swagger:"required" or a validator required rule makes a query parameter required, and description, format, example, enum and validator constraints (min, max, oneof, …) are applied to the parameter schema. A doc tag is used as the description when description is absent. A pointer field is treated as optional, not nullable. Path parameters are always required, whatever their tags say. There is no required:"true" tag.

This approach keeps parameter definitions type-safe and co-located with the handler logic.

Location-Specific Tags (v1.1.0+)

When building parameters from structs, open-swag-go now prefers location-specific tags over generic ones. Use the query tag for query parameter names and the path tag for path parameter names. The json tag is used as a fallback.

location_tags.go
type SearchParams struct {
	// "q" is used as the query parameter name (not "search_query")
	Query    string `query:"q"       json:"search_query" description:"Search term"`
	Page     int    `query:"page"    description:"Page number"     example:"1"`
	PageSize int    `query:"per_page" json:"page_size" description:"Results per page" example:"25"`
}
 
type GetItemParams struct {
	// "id" is used as the path parameter name (not "item_id")
	ID int64 `path:"id" json:"item_id" description:"Item identifier" example:"42"`
}

This gives you control over the external parameter name independently of the JSON field name used in serialization.

Co-Located Endpoint Definitions

A common pattern is to define endpoints right next to the handler functions they describe. This keeps documentation and implementation in sync and makes it easy to spot when they drift apart.

handlers/products.go
package handlers
 
import (
	openswag "github.com/gopackx/open-swag-go"
	"github.com/gopackx/open-swag-go/pkg/spec"
)
 
// ProductEndpoints returns all product-related endpoint definitions.
func ProductEndpoints() []openswag.Endpoint {
	return []openswag.Endpoint{
		listProducts(),
		getProduct(),
		createProduct(),
	}
}
 
func listProducts() openswag.Endpoint {
	return openswag.Endpoint{
		Method:      "GET",
		Path:        "/products",
		Summary:     "List products",
		Tags:        []string{"Products"},
		QueryParams: ListProductsParams{},
		Responses: openswag.Responses{
			200: {Description: "Product list"},
		},
	}
}
 
func getProduct() openswag.Endpoint {
	return openswag.Endpoint{
		Method:  "GET",
		Path:    "/products/{id}",
		Summary: "Get product by ID",
		Tags:    []string{"Products"},
		Parameters: []openswag.Parameter{
			{Name: "id", In: "path", Required: true, Schema: spec.NewSchema("integer")},
		},
		Responses: openswag.Responses{
			200: {Description: "Product details"},
			404: {Description: "Product not found"},
		},
	}
}
 
func createProduct() openswag.Endpoint {
	return openswag.Endpoint{
		Method:  "POST",
		Path:    "/products",
		Summary: "Create a product",
		Tags:    []string{"Products"},
		RequestBody: &openswag.RequestBody{
			Description: "Product to create",
			ContentType: "application/json",
			Required:    true,
			Schema:      CreateProductRequest{},
		},
		Responses: openswag.Responses{
			201: {Description: "Product created"},
			400: {Description: "Validation error"},
		},
	}
}

Then in your main file, collect all endpoint groups:

main.go
endpoints := append(
	handlers.ProductEndpoints(),
	handlers.UserEndpoints()...,
)
 
docs := openswag.New(cfg)
 
docs.AddAll(endpoints...)
if err := docs.Validate(); err != nil { // v1.3.0+: fail fast on duplicates
	log.Fatal(err)
}

This pattern scales well as your API grows — each domain package owns its endpoint definitions, and the main file just assembles them.