Examples

The canonical custom-module walkthrough: message, keeper, and wiring into an existing app.

Path: examples

Third-party documentation. This is independently authored analysis of the public Cosmos SDK codebase — not the official docs, and not reviewed or endorsed by the Cosmos SDK team.

Examples

The canonical SDK workflow is adding a custom module to an existing chain. The shape is always: define messages, implement keeper logic, register services and genesis, wire the module.

Anatomy of a module

A module directory typically contains: protobuf message definitions (proto/) with generated Go types, a keeper/ package holding all state access, a types/ package for shared structs and codec registration, and a module root implementing lifecycle interfaces. Codegen (protoc + a scaffolding tool if desired) produces most of the boilerplate.

Query services

Alongside message handlers, modules expose gRPC query services — read-only keepers methods surfaced over both gRPC and the REST gateway. Queries never mutate state and bypass gas-heavy write paths.

Testing pattern

Modules are unit-tested through their keepers against an in-memory store, and integration-tested at the app level by submitting test transactions through baseapp — the two layers catch different bug classes (state logic vs. routing/wiring).

Mainnet checklist

Before shipping a module to a live chain: register migrations if any schema changed, verify BeginBlocker ordering assumptions against every other live module's ordering, and confirm gas costs of new store operations — unbounded iteration over large stores is the classic app-chain denial-of-service.

Examples

A minimal custom message and handler

A minimal custom message and handler
typescript
type MsgMintItem struct {
Owner string
ItemID uint64
}
 
func (msg MsgMintItem) Route() string { return "items" }
 
func (k Keeper) MintItem(goCtx context.Context, msg *types.MsgMintItem) (*types.MsgMintItemResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
if k.GetOwner(ctx, msg.ItemID) != msg.Owner {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "not item owner")
}
k.SetItem(ctx, types.NewItem(msg.ItemID))
return &types.MsgMintItemResponse{}, nil
}
STATUSexample

Messages are plain data carrying authentication fields; the keeper validates authority against state before mutating it. The SDK wraps this handler in Go context bridging and panic recovery.

Registering the module in app wiring

Registering the module in app wiring
typescript
app.ModuleManager().AddModule(&module.AppModule{
Keeper: itemsKeeper,
})
app.ModuleManager().RegisterOrderBeginBlockers(itemsmodule.ModuleName)
STATUSexample

Modules register with the manager declaratively; ordering hooks like BeginBlock sequence are explicit so consensus-critical execution order is auditable in one place.

Edge Cases

  • State mutations must go through keeper methods only — direct store access from another module bypasses capability-based isolation and breaks upgrade tooling assumptions.
  • CheckTx executes only ValidateBasic plus ante-decoration checks; full handler logic runs in DeliverTx, so validation bugs can pass simulation and still fail (or misbehave) at block execution.
  • Store keys are global namespaces: two modules registering the same store key is a deployment-breaking collision caught only at app init.