Skip to main content

Overview

pb-ext uses three source file directives to control API documentation generation:
DirectiveLocationPurposeRequired
// API_SOURCETop of .go fileMarks file for AST parsingYes (for handler files)
// API_DESC <text>Function doc commentSets OpenAPI descriptionNo (auto-generated fallback)
// API_TAGS <csv>Function doc commentSets OpenAPI tagsNo (auto-generated from path)
Directives are comment lines — they don’t affect runtime behavior, only documentation generation.

// API_SOURCE — File Marker

Purpose

Marks a Go file for AST parsing. Only files with this directive are analyzed for handler functions. Type definitions (structs) in imported packages are discovered automatically.

Placement

Place at the top of the file, before the package declaration or imports:
// API_SOURCE
package main

import (
    "net/http"
    "github.com/pocketbase/pocketbase/core"
)

func myHandler(c *core.RequestEvent) error {
    return c.JSON(http.StatusOK, map[string]any{"status": "ok"})
}

When to Use

Mark these files:
  • Handler files (handlers.go, routes.go, api.go)
  • Files containing func(c *core.RequestEvent) error functions
Don’t mark these files:
  • Type definition files (types.go, models.go) — auto-discovered via imports
  • Utility files without handlers (helpers.go, utils.go)
  • Test files (*_test.go)
// API_SOURCE
package main

import (
    "encoding/json"
    "net/http"
    "github.com/pocketbase/pocketbase/core"
)

// API_DESC Create a new todo item
// API_TAGS Todos
func createTodoHandler(c *core.RequestEvent) error {
    var req TodoRequest
    if err := json.NewDecoder(c.Request.Body).Decode(&req); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]any{"error": "Invalid JSON"})
    }
    // ...
}

// API_DESC Get all todos with optional filtering
// API_TAGS Todos
func getTodosHandler(c *core.RequestEvent) error {
    // ...
}

// API_DESC — Handler Description

Purpose

Sets the description field in the OpenAPI operation. This appears in Swagger UI and helps developers understand what the endpoint does.

Placement

Place in the function’s doc comment (the comment block directly above the function):
// API_DESC Get all todos with optional filtering by completion status and priority
func getTodosHandler(c *core.RequestEvent) error {
    // ...
}

Fallback Behavior

If omitted, the system auto-generates a description from the handler name:
Handler NameAuto-Generated Description
getTodosHandlerGet Todos
createTodoHandlerCreate Todo
updateTodoHandlerUpdate Todo
deleteTodoHandlerDelete Todo
listUsersHandlerList Users
Auto-generated descriptions are basic. Always add // API_DESC for better documentation.

Writing Good Descriptions

1

Start with a verb

Get, Create, Update, Delete, List, Search, Calculate, etc.
2

Explain the purpose

What does this endpoint do? What data does it return?
3

Mention key features

Filtering, pagination, optional parameters, special behavior.
4

Keep it concise

1-2 sentences. Save detailed docs for external documentation.
// API_DESC Handler
func getTodosHandler(c *core.RequestEvent) error {
    // Too vague, doesn't explain what it does
}

// API_TAGS — OpenAPI Tags

Purpose

Groups related endpoints in Swagger UI. Tags appear as collapsible sections in the navigation sidebar.

Placement

Place in the function’s doc comment, same as // API_DESC:
// API_DESC Create a new todo item
// API_TAGS Todos, Management
func createTodoHandler(c *core.RequestEvent) error {
    // ...
}

Format

Comma-separated list of tag names:
// Single tag
// API_TAGS Todos

// Multiple tags
// API_TAGS Todos, Management, Public

Fallback Behavior

If omitted, the system auto-generates a tag from the path:
PathAuto-Generated Tag
/api/v1/todosTodos
/api/v1/users/{id}Users
/api/v1/admin/statsAdmin

Best Practices

// User management endpoints
// API_TAGS Users
func listUsersHandler(c *core.RequestEvent) error { /* ... */ }

// API_TAGS Users
func getUserHandler(c *core.RequestEvent) error { /* ... */ }

// API_TAGS Users
func createUserHandler(c *core.RequestEvent) error { /* ... */ }
This groups all user-related endpoints under a single “Users” tag in Swagger UI.
// API_DESC Get current server time
// API_TAGS Utility, Public
func timeHandler(c *core.RequestEvent) error { /* ... */ }

// API_DESC Health check endpoint
// API_TAGS Utility, Monitoring
func healthHandler(c *core.RequestEvent) error { /* ... */ }
Endpoints appear in multiple tag groups.
// ❌ Inconsistent capitalization
// API_TAGS todos
// API_TAGS Todos
// API_TAGS TODO

// ✅ Consistent
// API_TAGS Todos
// API_TAGS Todos
// API_TAGS Todos

Indirect Parameter Detection

What Requires Annotations?

Nothing. Indirect parameter detection is fully automatic for helper functions that accept *core.RequestEvent as the first parameter.

How It Works

The parser automatically extracts parameters from helper functions:
func parseTimeParams(e *core.RequestEvent) timeParams {
    q := e.Request.URL.Query()
    return timeParams{
        Interval: q.Get("interval"),
        From:     q.Get("from"),
        To:       q.Get("to"),
    }
}

func getDataHandler(e *core.RequestEvent) error {
    params := parseTimeParams(e)
    // No annotation needed — params auto-detected: interval, from, to
}

Requirements for Auto-Detection

1

Helper must accept *core.RequestEvent first

func parseTimeParams(e *core.RequestEvent) timeParams { /* ... */ }
2

Helper must NOT return error

// ✅ Detected as helper
func parseTimeParams(e *core.RequestEvent) timeParams { /* ... */ }

// ❌ Detected as handler, not analyzed for params
func parseTimeParams(e *core.RequestEvent) error { /* ... */ }
3

Generic helpers need string literal param names

// ✅ Param name extracted: "page"
page := parseIntParam(e, "page", 1)

// ❌ Param name not extracted (variable, not literal)
paramName := "page"
page := parseIntParam(e, paramName, 1)

When Annotations Are Needed vs Auto-Detected

FeatureAuto-DetectedAnnotation Needed
Request bodyc.BindBody(&req), json.Decode(&req)
Response schemac.JSON(status, data)
Query parametersc.Request.URL.Query().Get("name")
Header parametersc.Request.Header.Get("name")
Path parametersc.Request.PathValue("id")
Indirect params (helpers)✅ Helper accepts *core.RequestEvent
Handler description⚠️ Fallback: handler name// API_DESC
Endpoint tags⚠️ Fallback: path-based// API_TAGS
File inclusion❌ Not parsed by default// API_SOURCE
In most cases, only // API_SOURCE is required. The other directives (// API_DESC, // API_TAGS) are optional but recommended for better documentation.

Real Annotated Examples from handlers.go

// API_DESC Create a new todo item
// API_TAGS Todos
func createTodoHandler(c *core.RequestEvent) error {
    // Auth check (auto-detected)
    if c.Auth == nil {
        return c.JSON(http.StatusUnauthorized, map[string]any{"error": "Authentication required"})
    }

    // Request body (auto-detected)
    var req TodoRequest
    if err := json.NewDecoder(c.Request.Body).Decode(&req); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]any{"error": "Invalid JSON payload"})
    }

    // Response (auto-detected)
    return c.JSON(http.StatusCreated, map[string]any{
        "message": "Todo created successfully! ✅",
        "todo": map[string]any{
            "id":    record.Id,
            "title": record.GetString("title"),
        },
    })
}
What’s detected:
  • Description: “Create a new todo item” (from // API_DESC)
  • Tags: [“Todos”] (from // API_TAGS)
  • Auth: Required (from if c.Auth == nil check)
  • Request: TodoRequest schema (from json.Decode(&req))
  • Response: Inline object schema with message and todo properties
// API_DESC Get all todos with optional filtering
// API_TAGS Todos
func getTodosHandler(c *core.RequestEvent) error {
    // Query params (auto-detected)
    completed := c.Request.URL.Query().Get("completed")
    priority := c.Request.URL.Query().Get("priority")

    // Response (auto-detected)
    return c.JSON(http.StatusOK, map[string]any{
        "todos": todos,
        "count": len(todos),
        "filters": map[string]any{
            "completed": completed,
            "priority":  priority,
        },
    })
}
What’s detected:
  • Description: “Get all todos with optional filtering”
  • Tags: [“Todos”]
  • Query params: completed, priority (both optional, type: string)
  • Response: Inline object with todos, count, filters properties
// API_DESC Get a specific todo by ID
// API_TAGS Todos
func getTodoHandler(c *core.RequestEvent) error {
    // Path param (auto-detected, required=true)
    todoID := c.Request.PathValue("id")

    // Response (auto-detected)
    return c.JSON(http.StatusOK, map[string]any{
        "message": "Todo retrieved successfully 📖",
        "todo": map[string]any{
            "id":    record.Id,
            "title": record.GetString("title"),
        },
    })
}
What’s detected:
  • Description: “Get a specific todo by ID”
  • Tags: [“Todos”]
  • Path param: id (required=true, type: string)
  • Response: Inline object with message and todo properties

Best Practices Summary

1

Mark handler files with // API_SOURCE

Place at the top of the file, before package declaration.
2

Add // API_DESC to all handlers

Don’t rely on auto-generated descriptions — write clear, concise explanations.
3

Use // API_TAGS for grouping

Consistent tag names improve Swagger UI navigation.
4

Extract parameter parsing into helpers

Domain helpers make code cleaner and params are still auto-detected.
5

Verify with the debug endpoint

Check /api/docs/debug/ast to confirm all metadata is detected.

Debugging Missing Metadata

If a parameter, schema, or description is missing from the OpenAPI spec:
curl -H "Authorization: Bearer YOUR_SUPERUSER_TOKEN" \
     http://localhost:8090/api/docs/debug/ast
Look for your handler in the handlers section. If it’s missing:
  • File doesn’t have // API_SOURCE
  • Function signature doesn’t match func(c *core.RequestEvent) error
For indirect parameter detection:
  • Helper must accept *core.RequestEvent as first parameter
  • Helper must NOT return error (that makes it a handler)
  • Generic helpers need string literal param names at call site
  • // API_SOURCE must be at the top of the file
  • // API_DESC and // API_TAGS must be in the function doc comment (directly above the function)
Look for parse errors in server logs:
api docs: failed to parse API_SOURCE file file=cmd/server/handlers.go err=...
Common causes: syntax errors, import issues, circular dependencies.

Next Steps