app.go is where an application is assembled into a working chain. It creates the BaseApp instance that talks to CometBFT, allocates store keys, initializes keepers, registers modules, configures execution ordering, mounts stores, and sets lifecycle hooks and the AnteHandler. Finally, it seals the application with LoadLatestVersion.
The result is a single constructor, NewExampleApp, that returns a fully wired, ready-to-run chain.
Most examples on this page come from the counter module example in the example repo, where x/counter is wired into a fuller chain. The minimal counter module example shows the smaller app.go delta needed to add x/counter to a stripped-down app.
What app.go does
app.go performs a one-time, ordered initialization of the entire chain:
- Keepers require store keys, so keys come first.
- The
ModuleManagerdepends on keepers, so modules come after keeper construction. - Lifecycle hooks depend on the
ModuleManager, so hook wiring comes later. LoadLatestVersionsealsBaseApp, so it runs last.
The app struct
The application struct embedsBaseApp and holds all keepers and the ModuleManager:
*baseapp.BaseApp gives ExampleApp the full BaseApp interface: ABCI methods, message and query routers, store management, and lifecycle hooks. The keeper fields are exported so test code and CLI helpers can reference them. The keys map holds the KV store keys allocated during initialization. The real example app includes additional keepers and helper fields; this excerpt shows the part of the struct that matters for understanding the wiring pattern.
Creating BaseApp
NewExampleApp begins by setting up codecs and creating the BaseApp instance:
baseapp.NewBaseApp creates the BaseApp with a name, logger, database, and TxDecoder. The TxDecoder is how BaseApp turns raw transaction bytes from CometBFT into an sdk.Tx it can inspect and route. Additional functional options (baseAppOptions) let callers configure pruning, minimum gas prices, chain ID, and optimistic execution without modifying NewExampleApp directly. The full example app also wires legacy Amino support, tracing, and interface registration around this excerpt. See BaseApp Overview for a fuller description of its fields and behavior.
Allocating store keys
Each module that persists state needs a dedicated KV store key. All keys are allocated together before any keeper is created:types/keys.go (for example, countertypes.StoreKey = "counter"). NewKVStoreKeys takes those names and allocates a *storetypes.KVStoreKey for each one. Keys are passed to keeper constructors and later mounted on the CommitMultiStore via MountKVStores. No two modules share a key; that isolation is what keeps module state separate.
Initializing keepers
Each keeper is initialized with its store key, codec, and any dependencies on other keepers.ConsensusParamsKeeper is initialized first because it must call bApp.SetParamStore before any other keeper is created:
runtime.NewKVStoreService(key) wraps the raw store key in a service interface that keepers use to open their store from a context. This keeps keepers from holding direct references to the underlying store. Instead, they retrieve it at runtime from the context passed into each method.
The keeper initialization order matters: BankKeeper receives app.AccountKeeper as an argument, so AccountKeeper must be initialized first. The same dependency ordering applies throughout. The counter module example also passes app.BankKeeper into counterkeeper.NewKeeper, showing how custom modules depend on existing module services. Where modules are interdependent, hooks connect them after both keepers exist:
authtypes.NewModuleAddress(govtypes.ModuleName).String()) is the address that is allowed to call privileged messages such as MsgUpdateParams. Governance controls parameter changes by sending messages from the governance module account. See Params for how this pattern works.
Registering modules
After all keepers are initialized, the module manager is created with every module the application uses:module.NewManager takes a list of AppModule implementations. Each AppModule wraps a keeper and satisfies the interfaces the ModuleManager uses: genesis, block hooks, message and query service registration, and simulation support. The real example app includes the full built-in module set around x/counter; this excerpt shows the basic registration pattern. The BasicModuleManager is then derived from the ModuleManager for codec registration and default genesis handling.
Module Manager
TheModuleManager is the application’s registry of modules. It holds references to all AppModule instances and coordinates their participation in the block lifecycle. When BaseApp fires a lifecycle hook (PreBlock, BeginBlock, EndBlock, InitGenesis), it delegates to the ModuleManager, which calls each module’s corresponding method in the configured order.
The ModuleManager is also responsible for service registration: it iterates all modules and calls each module’s RegisterServices to register MsgServer and QueryServer implementations with BaseApp’s routers. For the execution-model view, see Module Manager in BaseApp.
Execution ordering
The order in which modules run their block hooks and genesis initialization matters. Some modules depend on others having already updated state. Ordering is configured explicitly after the module manager is created:SetOrderExportGenesis controls the order modules serialize their state when the chain is exported to a genesis file, for example during a hard fork or when creating a snapshot-based testnet. The export order can differ from the init genesis order; in the example chain they use different orderings.
The comments in the example app explain the reasoning: genutil must run after staking so that staking pools are initialized before genesis transactions are processed, and after auth so that it can access auth parameters.
Each hook type has its own ordering constraint:
PreBlock: runs beforeBeginBlock. Used for upgrades and consensus parameter changes that must take effect before the block begins.BeginBlock: runs at the start of each block. Used for per-block housekeeping such as minting inflation rewards and distributing staking rewards.EndBlock: runs after all transactions in the block. Used for logic that depends on cumulative block state, such as tallying governance votes or recalculating validator power.InitGenesis: runs once at chain start, populating each module’s store fromgenesis.json.
Routing setup
After execution ordering is configured, module services are registered withBaseApp’s routers:
RegisterServices iterates all modules and calls each module’s RegisterServices(cfg) method. Each module uses the configurator to register its MsgServer with the message router and its QueryServer with the gRPC query router. After this step, BaseApp can route any registered message type to the correct module handler, and any registered query to the correct query handler. The example app also registers an AutoCLI query service after this step.
The AutoCLI query service registration lets the CLI introspect module options without requiring per-module CLI command boilerplate. For how these services are exposed to clients, see CLI, gRPC, and REST.
Block proposal and vote extension handlers
BaseApp exposes four handlers for the ABCI 2.0 proposal phase: SetPrepareProposal, SetProcessProposal, SetExtendVoteHandler, and SetVerifyVoteExtensionHandler. All have sensible defaults. Chains that need custom behavior wire their handlers in app.go after the module manager is configured:
Mounting stores and setting hooks
With routing configured, stores are mounted and the application’s lifecycle hooks are set:MountKVStores registers each key with BaseApp’s CommitMultiStore. The hooks delegate to the ModuleManager:
EndBlocker delegates in the same way, and InitChainer delegates to ModuleManager.InitGenesis after decoding genesis.json. The AnteHandler is configured separately because it takes a TxConfig dependency:
AnteHandler runs before any message in a transaction executes. It verifies signatures, validates and increments the account sequence number, deducts fees, and meters gas. If it fails, the transaction is rejected before any module logic runs.
A PostHandler can also be registered with app.SetPostHandler. It runs after all messages in a transaction execute (regardless of whether they succeeded), in the same state branch, and is reverted if it fails. The SDK’s default PostHandler chain is minimal. For a deeper look at how the AnteHandler fits into transaction execution, see AnteHandler.
Sealing with LoadLatestVersion
The final step inNewExampleApp is loading the latest committed state:
LoadLatestVersion calls storeLoader to load the latest committed store state from the database, then calls Init, which validates that required components are configured, initializes the check state, and sets BaseApp.sealed to true. Any setter called after this point panics. This enforces that all wiring happens before the application starts serving requests.
On first launch, CometBFT calls InitChain which triggers InitChainer, which calls ModuleManager.InitGenesis to populate each module’s state from genesis.json.
How everything fits together
A Cosmos SDK chain is assembled into a single constructor function that returns a fully wired, ready-to-run chain. Each step builds on the previous:BaseApp:
InitChaincallsInitChainer, which runsModuleManager.InitGenesis.FinalizeBlockcallsPreBlocker,BeginBlocker, each transaction’sAnteHandlerand message handlers, thenEndBlocker.CheckTxvalidates a transaction through theAnteHandlerand writes to the internalCheckTxstate if it passes.Commitpersists the finalized block state.
ModuleManager coordinates in a fixed, declared order. See BaseApp Overview for how ABCI calls flow through the application, and Intro to Modules for how individual modules are structured. The next section, CLI, gRPC, and REST API, explains how clients interact with the chain once that wiring is in place.