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
go get goa.design/goa/v3@v3.31.1
go install goa.design/goa/v3/cmd/goa@v3.31.1
Upgrading to v3.31.1
The generation preview is now stable. v3.31.1 includes intentional breaking changes when upgrading from v3.30.x; read the upgrade guide before regenerating an existing application. Pin the Goa module and command to the same version, regenerate the completegen/ directory, and compile and
test the application. Coordinate client and server updates for the changed
message formats identified in the guide. For rollback, restore the previous
dependencies, generated output, and application code together.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’s generated transport decoders validate incoming requests on the server and incoming responses on the client. Service code then maintains the application invariants. Direct calls to a service or generated endpoint bypass transport decoding; those callers must supply valid values or validate at their own input boundary.
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.
This table describes ordinary scalar and object fields in Goa v3.31.1. Bytes, Any, and unions have distinct representations; inspect their generated types.
| 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. Required or defaulted scalar fields use values in encoded client requests and server responses; optional scalars without defaults remain pointers. 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.
Collection Presence
Required("items") and MinLength(1) express different constraints. For JSON, a required collection must be present and non-null, but [] or {} is valid unless a length constraint forbids it. Protobuf repeated and map fields cannot distinguish absent from empty after a wire round trip, so generated validation checks their length and contents rather than presence. Required singular scalars, messages, and oneofs retain their own presence checks.
Do not use nil versus empty Go collections to encode domain operations. Model an operation explicitly when callers must distinguish “leave unchanged” from “replace with empty.”
Default Value Handling
Defaults belong to the design and are applied by generated transport conversions. In preview gRPC decoding, an absent input receives its authored default; an explicit 0, false, or empty value remains explicit. Service-to-protobuf conversion preserves the value supplied by the service and does not replace zero values with defaults.
HTTP conversions have different rules: incoming body constructors apply defaults to missing values, and outgoing body constructors can apply authored defaults to zero-valued service fields. Inspect the generated constructor and decoder for the relevant direction when zero or absence has domain meaning; do not apply one default rule to every transport.
Views and Result Types
Views control how result types are rendered in responses.
How Views Work
- Define the attributes of each view in the result type.
- Goa generates the view representations, conversions, and validators.
- A method can select a fixed view in the design. For a dynamic unary result, the generated service method returns the view name with the result; streaming interfaces expose the generated view-selection operation.
Server-Side Response
The generated encoder selects the representation for the chosen view. Attributes outside that view are excluded; required attributes inside it remain part of the contract. Dynamic HTTP views carry the name in the Goa-View response header, and gRPC uses goa-view metadata. Preview JSON-RPC carries a dynamic view as { "view": ..., "body": ... } inside result; unviewed and fixed-view results do not use that envelope.
Client-Side Response
The generated client reads the selected view, decodes its representation, validates that view’s contract, and converts it to the service result. Custom clients must follow the representation of the selected transport and release.
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 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