Code Generation
Goa’s code generation transforms your design into production-ready service
contracts, transports, clients, and documentation. goa example creates
runnable starter wiring, while your application supplies the business logic.
Command Line Tools
Installation
GOPROXY=direct go install goa.design/goa/v3/cmd/goa@fix/goa-generation-plan
Testing a generation preview
Pre-release versions are opt-in. Pin both the Goa module and thegoa command
to the same preview commit. The current work is on the
fix/goa-generation-plan preview branch
at
318c40614944e151ec7de2cfb712e0d08b73f7af.
Regenerate the complete gen/ directory, never mix stable and preview output,
then compile and test the complete application. Coordinate client and server
updates when the guide identifies a wire change. To return to stable, pin the
stable module and command together and regenerate everything again.Commands
All commands expect Go package import paths, not filesystem paths:
# ✅ Correct: using Go package import path
goa gen goa.design/examples/calc/design
# ❌ Incorrect: using filesystem path
goa gen ./design
Generate Code (goa gen)
goa gen <design-package-import-path> [-o <output-dir>]
The primary command for code generation:
- Processes your design package and generates implementation code
- Recreates the entire
gen/directory from scratch each time - Run after every design change
Create Example (goa example)
goa example <design-package-import-path> [-o <output-dir>]
A scaffolding command:
- Creates a one-time example implementation
- Generates handler stubs with example logic
- Run once when starting a new project
- Will NOT overwrite existing custom implementation
Show Version
goa version
Development Workflow
- Create initial design
- Run
goa gento generate base code - Run
goa exampleto create implementation stubs - Implement your service logic
- Run
goa genafter every design change
Best Practice: Commit generated code to version control rather than generating during CI/CD. This ensures reproducible builds and allows tracking changes in generated code.
Generation Process
When you run goa gen, Goa follows a systematic process:
1. Bootstrap Phase
Goa creates a temporary main.go that:
- Imports Goa packages and your design package
- Runs DSL evaluation
- Triggers code generation
2. Design Evaluation
- DSL functions execute to create expression objects
- Expressions combine into a complete API model
- Relationships between expressions are established
- Design rules and constraints are validated
3. Code Generation
- Validated expressions pass to code generators
- Templates render to produce code files
- Output writes to the
gen/directory
Goa resolves packages, declarations, names, imports, field paths, and known branches from the complete validated design before rendering. Templates write those choices directly. Generated programs branch only on values that arrive at runtime.
Generated Code Structure
A typical generated project:
myservice/
├── cmd/ # Generated example commands
│ └── calc/
│ ├── grpc.go
│ └── http.go
├── design/ # Your design files
│ └── design.go
├── gen/ # Generated code (don't edit)
│ ├── calc/ # Service-specific code
│ │ ├── client.go
│ │ ├── endpoints.go
│ │ └── service.go
│ ├── http/ # HTTP transport layer
│ │ ├── calc/
│ │ │ ├── client/
│ │ │ └── server/
│ │ └── openapi.json
│ └── grpc/ # gRPC transport layer
│ └── calc/
│ ├── client/
│ ├── server/
│ └── pb/
└── myservice.go # Your service implementation
Service Interfaces
Generated in gen/<service>/service.go:
// Service interface defines the API contract
type Service interface {
Add(context.Context, *AddPayload) (res int, err error)
Multiply(context.Context, *MultiplyPayload) (res int, err error)
}
// Payload types
type AddPayload struct {
A int32
B int32
}
// Constants for observability
const ServiceName = "calc"
var MethodNames = [2]string{"add", "multiply"}
Endpoint Layer
Generated in gen/<service>/endpoints.go:
// Endpoints wraps service methods in transport-agnostic endpoints
type Endpoints struct {
Add goa.Endpoint
Multiply goa.Endpoint
}
// NewEndpoints creates endpoints from service implementation
func NewEndpoints(s Service) *Endpoints {
return &Endpoints{
Add: NewAddEndpoint(s),
Multiply: NewMultiplyEndpoint(s),
}
}
// Use applies middleware to all endpoints
func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) {
e.Add = m(e.Add)
e.Multiply = m(e.Multiply)
}
Endpoint middleware example:
func LoggingMiddleware(next goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req any) (res any, err error) {
log.Printf("request: %v", req)
res, err = next(ctx, req)
log.Printf("response: %v", res)
return
}
}
endpoints.Use(LoggingMiddleware)
Client Code
Generated in gen/<service>/client.go:
// Client provides typed methods for service calls
type Client struct {
AddEndpoint goa.Endpoint
MultiplyEndpoint goa.Endpoint
}
func NewClient(add, multiply goa.Endpoint) *Client {
return &Client{
AddEndpoint: add,
MultiplyEndpoint: multiply,
}
}
func (c *Client) Add(ctx context.Context, p *AddPayload) (res int, err error) {
ires, err := c.AddEndpoint(ctx, p)
if err != nil {
return
}
return ires.(int), nil
}
HTTP Code Generation
Server Implementation
Generated in gen/http/<service>/server/server.go:
func New(
e *calc.Endpoints,
mux goahttp.Muxer,
decoder func(*http.Request) goahttp.Decoder,
encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
errhandler func(context.Context, http.ResponseWriter, error),
formatter func(ctx context.Context, err error) goahttp.Statuser,
) *Server
// Server exposes handlers for modification
type Server struct {
Mounts []*MountPoint
Add http.Handler
Multiply http.Handler
}
// Use applies HTTP middleware to all handlers
func (s *Server) Use(m func(http.Handler) http.Handler)
Complete server setup:
func main() {
svc := calc.New()
endpoints := gencalc.NewEndpoints(svc)
mux := goahttp.NewMuxer()
server := genhttp.New(
endpoints,
mux,
goahttp.RequestDecoder,
goahttp.ResponseEncoder,
nil, nil)
genhttp.Mount(mux, server)
http.ListenAndServe(":8080", mux)
}
Client Implementation
Generated in gen/http/<service>/client/client.go:
func NewClient(
scheme string,
host string,
doer goahttp.Doer,
enc func(*http.Request) goahttp.Encoder,
dec func(*http.Response) goahttp.Decoder,
restoreBody bool,
) *Client
Complete client setup:
func main() {
httpClient := genclient.NewClient(
"http",
"localhost:8080",
http.DefaultClient,
goahttp.RequestEncoder,
goahttp.ResponseDecoder,
false,
)
client := gencalc.NewClient(
httpClient.Add(),
httpClient.Multiply(),
)
result, err := client.Add(context.Background(), &gencalc.AddPayload{A: 1, B: 2})
}
gRPC Code Generation
Protobuf Definition
Generated in gen/grpc/<service>/pb/:
syntax = "proto3";
package calc;
service Calc {
rpc Add (AddRequest) returns (AddResponse);
rpc Multiply (MultiplyRequest) returns (MultiplyResponse);
}
message AddRequest {
int64 a = 1;
int64 b = 2;
}
Server Implementation
func main() {
svc := calc.New()
endpoints := gencalc.NewEndpoints(svc)
svr := grpc.NewServer()
gensvr := gengrpc.New(endpoints, nil)
genpb.RegisterCalcServer(svr, gensvr)
lis, _ := net.Listen("tcp", ":8080")
svr.Serve(lis)
}
Client Implementation
func main() {
conn, _ := grpc.Dial("localhost:8080",
grpc.WithTransportCredentials(insecure.NewCredentials()))
defer conn.Close()
grpcClient := genclient.NewClient(conn)
client := gencalc.NewClient(
grpcClient.Add(),
grpcClient.Multiply(),
)
result, _ := client.Add(context.Background(), &gencalc.AddPayload{A: 1, B: 2})
}
Customization
Type Generation Control
Force generation of types not directly referenced by methods:
var MyType = Type("MyType", func() {
// Force generation in specific services
Meta("type:generate:force", "service1", "service2")
// Or force generation in all services
Meta("type:generate:force")
Attribute("name", String)
})
Package Organization
Generate types in a shared package:
var CommonType = Type("CommonType", func() {
Meta("struct:pkg:path", "types")
Meta("type:generate:force")
Attribute("id", String)
})
Creates:
gen/
└── types/
└── common_type.go
struct:pkg:path gives the authored type one declaration in the selected
generated package, and every generated use imports that declaration. The Go
package name is the lowercase final path segment. If the relocated type contains
another authored type, that dependency must also declare an explicit
struct:pkg:path, usually the same package. Compiler-created nested types stay
with their owning authored type.
One authored declaration is reused across services and across payload, result, and error uses. When that exact type is a custom error, Goa adds the error methods beside the same declaration instead of generating a second type.
Field Customization
var Message = Type("Message", func() {
Attribute("id", String, func() {
// Override field name
Meta("struct:field:name", "ID")
// Add custom struct tags
Meta("struct:tag:json", "id,omitempty")
Meta("struct:tag:msgpack", "id,omitempty")
// Override type
Meta("struct:field:type", "bson.ObjectId", "github.com/globalsign/mgo/bson", "bson")
})
})
Protocol Buffer Customization
var MyType = Type("MyType", func() {
// Override protobuf message name
Meta("struct:name:proto", "CustomProtoType")
Field(1, "status", Int32, func() {
// Override protobuf field type
Meta("struct:field:proto", "int32")
})
// Use Google's timestamp type
Field(2, "created_at", String, func() {
Meta("struct:field:proto",
"google.protobuf.Timestamp",
"google/protobuf/timestamp.proto",
"Timestamp",
"google.golang.org/protobuf/types/known/timestamppb")
})
})
// Specify protoc include paths
var _ = API("calc", func() {
Meta("protoc:include", "/usr/include", "/usr/local/include")
})
OpenAPI Customization
By default Goa generates OpenAPI 2.0 and 3.0 documents. To also generate an OpenAPI 3.2.0 description, select it explicitly at the API level:
var _ = API("MyAPI", func() {
Meta("openapi:versions", "2.0", "3.0", "3.2")
Meta("openapi:path:3.2", "docs/openapi")
})
The selected versions are written as both JSON and YAML. The example above
generates gen/docs/openapi.json and gen/docs/openapi.yaml for OpenAPI 3.2;
without the path override, Goa writes gen/http/openapi3.2.json and
gen/http/openapi3.2.yaml. Version selection does not change the generated
service code.
var _ = API("MyAPI", func() {
// Control generation
Meta("openapi:generate", "false")
// Format JSON output
Meta("openapi:json:prefix", " ")
Meta("openapi:json:indent", " ")
// Disable example generation
Meta("openapi:example", "false")
})
var _ = Service("UserService", func() {
// Add tags
HTTP(func() {
Meta("openapi:tag:Users")
Meta("openapi:tag:Backend:desc", "Backend API Operations")
})
Method("CreateUser", func() {
// Custom operation ID
Meta("openapi:operationId", "{service}.{method}")
// Custom summary
Meta("openapi:summary", "Create a new user")
HTTP(func() {
// Add extensions
Meta("openapi:extension:x-rate-limit", `{"rate": 100}`)
POST("/users")
})
})
})
var User = Type("User", func() {
// Override type name in OpenAPI spec
Meta("openapi:typename", "CustomUser")
})
Types and Validation
Validation Enforcement
Goa validates data at system boundaries:
- Server-side: Validates incoming requests
- Client-side: Validates incoming responses
- Internal code: Trusted to maintain invariants
Pointer Rules for Struct Fields
Service types and transport types answer different questions. A service type represents a value after validation. A decoded transport type must also record whether an incoming field was absent so generated validation can reject a missing required value without rejecting an explicit zero value.
| Field | Service type | HTTP/JSON-RPC body | Protobuf request or response |
|---|---|---|---|
| Required primitive or primitive with a default | Value | Pointer when decoded for validation; value when encoded | Pointer for singular fields whose presence must be preserved |
| Optional primitive without a default | Pointer | Pointer | Pointer |
| Object | Pointer | Pointer | Pointer |
| Array or map | Value | Value | Value |
For HTTP and JSON-RPC, decoded input means a request on the server or a response on the client. Encoded client requests and server responses use values. In protobuf Go structs, required singular booleans, numbers, strings, enums, and their aliases are pointers in both requests and responses so validation can distinguish an omitted field from an explicit zero value. Byte slices remain slices, messages remain pointers, and Goa service structs keep their existing layout.
Example:
type Person struct {
Name string // required, direct value
Age *int // optional, pointer
Hobbies []string // array, no pointer
Metadata map[string]string // map, no pointer
}
ArrayOfRequired applies the same presence rule to array elements. Incoming
HTTP and JSON-RPC bodies use pointers for primitive elements and primitive
aliases so [null] can be rejected. Valid input becomes an ordinary value slice
in the service layer, and generated response bodies remain value slices.
Default Value Handling
- Marshaling: Default values initialize nil arrays/maps
- Unmarshaling: Default values apply to missing optional fields (not missing required fields)
Views and Result Types
Views control how result types are rendered in responses.
How Views Work
- Service method includes a view parameter
- A views package is generated at the service level
- View-specific validation is automatically generated
Server-Side Response
- Viewed result type is marshalled
- Nil attributes are omitted
- View name is passed in “Goa-View” header
Client-Side Response
- Response is unmarshalled
- Transformed into viewed result type
- View name extracted from “Goa-View” header
- View-specific validation performed
- Converted back to service result type
Default View
If no views are defined, Goa adds a “default” view that includes all basic fields.
Plugin System
Goa’s plugin system extends code generation. Plugins can:
- Add New DSLs - Additional design language constructs
- Modify Generated Code - Inspect and modify files, add new files
Example using the CORS plugin:
import (
. "goa.design/goa/v3/dsl"
cors "goa.design/plugins/v3/cors/dsl"
)
var _ = Service("calc", func() {
cors.Origin("/.*localhost.*/", func() {
cors.Headers("X-Shared-Secret")
cors.Methods("GET", "POST")
})
})
Common plugin use cases:
- Protocol support (CORS, etc.)
- Additional documentation formats
- Custom validation rules
- Cross-cutting concerns (logging, metrics)
- Configuration file generation
Released callbacks remain appropriate for plugins that edit generated values or files. A plugin that declares a package-level name must use the factory planning phase so Goa can reserve that name with every other declaration before rendering. See the Code Generation Architecture and the preview upgrade guide for the detailed plugin contract and migration steps.
See Also
- DSL Reference — Complete DSL reference for design files
- HTTP Guide — HTTP transport features and customization
- gRPC Guide — gRPC transport features and Protocol Buffers
- Quickstart — Getting started with code generation