OPEN SWAG GO

CORS Issues

Try-It Console Returns CORS Errors

Symptom: The try-it console (provided by the Scalar UI) shows CORS error or Failed to fetch when making requests.

Cause: Your API server doesn't include the correct CORS headers, or the browser blocks cross-origin requests from the docs UI to your API.

Solution: Configure CORS on your API server to allow requests from the docs origin:

import "github.com/rs/cors"
 
c := cors.New(cors.Options{
    AllowedOrigins:   []string{"https://docs.example.com"},
    AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
    AllowedHeaders:   []string{"Authorization", "Content-Type"},
    AllowCredentials: true,
})
 
handler := c.Handler(router)

open-swag-go has no CORS proxy option; CORS for API calls must be configured on the API server itself. The only CORS setting in the library is UIConfig.SpecCORSOrigin (v1.3.0+), which controls the Access-Control-Allow-Origin header on the openapi.json endpoint: "" sends *, "-" omits the header, and any other value is sent as-is.


Spec Generation Failures

Spec Generation Panics on Nil Pointer

Symptom: panic: runtime error: invalid memory address or nil pointer dereference when generating the spec.

Cause: An endpoint has a nil Response or RequestBody field that the generator tries to dereference.

Solution: Ensure all required fields are set on your endpoints:

docs.Add(openswag.Endpoint{
    Method:  "GET",
    Path:    "/api/users",
    Summary: "List users",
    Responses: openswag.Responses{
        200: {
            Description: "Success", // Don't leave this empty
            Schema:      []User{},
        },
    },
})

Struct Fields Not Appearing in Schema

Symptom: Some struct fields are missing from the generated OpenAPI schema.

Cause: Unexported fields and fields tagged json:"-" are skipped by the schema generator. Exported fields without a json tag do appear, but under their Go field name (Name, not name). In v1.0.x, embedded (anonymous) struct fields were also silently ignored, and before v1.3.0 an embedded struct whose type is unexported (type base struct{...} embedded as base) was dropped entirely.

Solution: Add json tags to all fields you want in the schema. For embedded structs, upgrade to v1.1.0+ where field promotion is supported, and to v1.3.0+ if the embedded type is unexported:

// Bad — id is unexported (skipped), Name has no json tag (appears as "Name")
type User struct {
    id   string
    Name string
}
 
// Good
type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}
 
// Good (v1.1.0+) — embedded fields are promoted
type Timestamps struct {
    CreatedAt time.Time `json:"created_at"`
}
 
type User struct {
    Timestamps
    ID   string `json:"id"`
    Name string `json:"name"`
}
 
// Good (v1.3.0+) — embedded unexported types are promoted like encoding/json does
type base struct {
    ID string `json:"id"`
}
 
type Order struct {
    base
    Total float64 `json:"total"`
}

Example Values Have Wrong Type in Spec

Symptom: Example values like example:"42" appear as strings "42" instead of integers in the OpenAPI spec.

Cause: In v1.0.x, example tag values were always stored as strings regardless of the field type.

Solution: Upgrade to v1.1.0+. The ConvertExampleToType function now automatically coerces example strings to the correct type (int64, float64, bool, etc.) based on the field's Go type.

Map Fields Show as Empty Object

Symptom: map[string]T fields generate {"type":"object"} without additionalProperties.

Cause: In v1.0.x, map value schemas were not generated.

Solution: Upgrade to v1.1.0+. Map types now correctly produce {"type":"object","additionalProperties":{...}} with the value type's schema.

interface/any Fields Show as Object Type

Symptom: Fields typed as interface{} or any generate {"type":"object"} instead of accepting any value.

Cause: In v1.0.x, interface{}/any types were incorrectly mapped to {"type":"object"}.

Solution: Upgrade to v1.1.0+. These types now produce an empty schema {} which correctly accepts any JSON value.

Localhost Server URL Shows Garbled Characters

Symptom: Using CommonServers.Localhost(8080) produces a URL with garbled characters instead of the port number.

Cause: In v1.0.x, port numbers were converted using string(rune(port)) which produces a Unicode character instead of the decimal string.

Solution: Upgrade to v1.1.0+ where port conversion uses strconv.Itoa(port) correctly.

Circular Reference in Schema

Symptom: Before v1.2.0, spec generation crashed with fatal error: stack overflow. On v1.2.0 (and on v1.3.0+ with InlineSchemas: true), the nested field shows up as a bare object with the description Recursive reference to ....

Cause: Your Go structs refer back to themselves (e.g. TreeNode has a Children []*TreeNode field, or User has a *User field). While every struct is inlined, the back-edge cannot be expanded again.

Solution: Upgrade to v1.3.0+. Named structs are emitted as schema components, so a recursive struct gets a real self-$ref and code generators produce a properly recursive type:

type TreeNode struct {
    Value    string      `json:"value"`
    Children []*TreeNode `json:"children,omitempty"`
}
"TreeNode": {
  "type": "object",
  "properties": {
    "value": { "type": "string" },
    "children": {
      "type": "array",
      "items": { "oneOf": [{ "$ref": "#/components/schemas/TreeNode" }, { "type": "null" }] }
    }
  }
}

The oneOf comes from the pointer element (*TreeNode is nullable); []TreeNode gives "items": {"$ref": "#/components/schemas/TreeNode"}.

The bounded placeholder only remains if you set Config.InlineSchemas: true (or stay on v1.2.0): the first level is expanded and the back-edge becomes {type: object, description: "Recursive reference to main.TreeNode"}. Using a pointer does not change that. Anonymous structs are always inlined, but they cannot refer to themselves.

Path Shows /users/:id Literally

Symptom: The spec contains a path like /users/:id or /files/*path, and Scalar or code generators treat :id as literal text.

Cause: Before v1.3.0, router-style segments were written to paths unchanged. OpenAPI requires {id}.

Solution: Upgrade to v1.3.0+. Endpoint.Path accepts /users/{id}, /users/:id, /files/*path and chi regex segments (/users/{id:[0-9]+}), and all of them are normalised to {name} in the spec.

Second Endpoint for the Same Method+Path Silently Replaced the First

Symptom: You registered two endpoints for GET /users/{id} (perhaps once as /users/:id) and only one appears in the docs, with no error. Or a HEAD/OPTIONS endpoint showed up as an empty path item.

Cause: Add never fails. BuildSpec keeps the last of duplicate endpoints and skips unsupported methods (before v1.3.0, HEAD/OPTIONS/TRACE produced an empty path item).

Solution: On v1.3.0+, call Validate once after registering endpoints and treat an error as fatal:

docs.AddAll(endpoints...)
if err := docs.Validate(); err != nil {
    log.Fatal(err) // e.g. openswag: duplicate endpoint GET /users/{id} (endpoints #3 and #7)
}

It reports duplicate method+path pairs (after upper-casing the method and normalising the path), unsupported HTTP methods, and duplicate operationIds.

orval: operationId Is Required

Symptom: orval (or another generator) rejects the spec because operations have no operationId, or generated function names are unhelpful.

Cause: Before v1.3.0, operationId was never set.

Solution: Upgrade to v1.3.0+. Every operation gets a default operationId from its method and path (GET /users/{id} → getUsersById). Set Endpoint.OperationID to choose your own name; docs.Validate() reports collisions.

Field Type Is an Array Like ["string", "null"]

Symptom: A property in openapi.json has "type": ["string", "null"] instead of "type": "string", and generated TypeScript has string | null.

Cause: This is expected on v1.3.0+. The field is a pointer (*string), so it can be JSON null. The spec is OpenAPI 3.1, which expresses that as a type array; the 3.0 nullable keyword is not used.

Solution: Nothing to fix if the field really can be null. If it cannot, use a non-pointer type. Tools that only understand OpenAPI 3.0 may not handle type arrays; use a generator version that supports OpenAPI 3.1.


Framework Mounting Problems

Chi: 404 on All Doc Routes

Symptom: Mounting with Chi returns 404 for /docs, /docs/openapi.json, etc.

Cause: Missing trailing wildcard in the route pattern.

Solution: Use the adapter's Mount function instead of manual mounting:

// Bad
r.Handle("/docs", docs.Handler())
 
// Good
import chiadapter "github.com/gopackx/open-swag-go/adapters/chi"
chiadapter.Mount(r, docs, "/docs")

Gin: Conflicting Route Handlers

Symptom: panic: handlers are already registered for this path when mounting docs.

Cause: Another route conflicts with the docs wildcard route.

Solution: Use MountGroup to mount docs under a route group:

import ginadapter "github.com/gopackx/open-swag-go/adapters/gin"
 
api := r.Group("/api")
ginadapter.MountGroup(api, docs)

Fiber: Docs Page Loads But UI Is Blank

Symptom: The docs path returns HTML but the Scalar UI doesn't render.

Cause: Fiber's default content type handling may interfere with static asset serving.

Solution: Ensure the Fiber adapter is mounted correctly and static assets are served with proper MIME types:

import fiberadapter "github.com/gopackx/open-swag-go/adapters/fiber"
 
fiberadapter.Mount(app, docs, "/docs")

If the issue persists, check that no middleware is modifying response headers for the /docs path.

Echo: Middleware Blocking Doc Routes

Symptom: Docs routes return 401 or 403 due to auth middleware.

Cause: Global auth middleware applies to the docs routes.

Solution: Mount docs before applying auth middleware, or use a group without the middleware:

import echoadapter "github.com/gopackx/open-swag-go/adapters/echo"
 
// Mount docs first (no auth)
echoadapter.Mount(e, docs, "/docs")
 
// Then apply auth middleware to API routes
api := e.Group("/api", authMiddleware)

General Issues

Docs Work Locally But Not in Production

Symptom: Everything works on localhost but the docs page is blank or returns errors in production.

Cause: Common causes include reverse proxy stripping paths, missing environment variables, or HTTPS/HTTP mismatch.

Solution:

  1. Check that your reverse proxy forwards the full path (including /docs/openapi.json)
  2. Verify environment variables for DocsAuth credentials are set
  3. Ensure the Server URL in your config matches the production URL:
docs := openswag.New(openswag.Config{
    Info: openswag.Info{
        Title:   "My API",
        Version: "1.0.0",
    },
    Servers: []openswag.Server{
        {URL: "https://api.example.com", Description: "Production"},
    },
})

Docs Page Returns 500 "DocsAuth enabled but no credentials configured"

Symptom: Every request to the docs page and openapi.json returns HTTP 500 with the body DocsAuth enabled but no credentials configured.

Cause: DocsAuth.Enabled is true, but APIKey is empty and Username/Password are not both set. This is often an environment variable that is missing in that environment. Before v1.3.0 the same misconfiguration returned 401 for every request, which looked like a wrong password.

Solution: Set APIKey, or both Username and Password, or set Enabled to false:

DocsAuth: &openswag.DocsAuth{
    Enabled:  os.Getenv("DOCS_PASSWORD") != "",
    Username: "admin",
    Password: os.Getenv("DOCS_PASSWORD"),
},