Examples
The canonical custom-module walkthrough: message, keeper, and wiring into an existing app.
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
type MsgMintItem struct {Owner stringItemID 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}
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
app.ModuleManager().AddModule(&module.AppModule{Keeper: itemsKeeper,})app.ModuleManager().RegisterOrderBeginBlockers(itemsmodule.ModuleName)
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.
