Why modules exist
A blockchain application needs to manage many independent concerns (accounts, balances, validator management, etc). Instead of placing all logic in a single monolithic state machine, the Cosmos SDK divides the application into modules. The SDK provides a base layer that allows these modules to operate together as a cohesive blockchain. Each module owns a slice of state, defines its messages and queries, implements its business rules, and hooks into the block lifecycle and genesis as needed. This keeps the application organized, composable, and easier to reason about. It also separates safety concerns between modules, creating a more secure system.What a module defines
A module is a self-contained unit of state and logic. At a high level, a module defines:- State: a
KVStorenamespace that contains the module’s data - Messages: the actions the module allows
- Queries: read-only access to the module’s state
MsgServer: validates, applies business logic, and delegates to the keeper- Keeper: the state access layer: the only sanctioned path to the store
- Params: governance-controlled configuration stored on-chain
State
State is the data persisted on the chain: everything that transactions can read from or write to. When a transaction is executed, modules apply state transitions or deterministic updates to this stored data. Each module owns its own part of the blockchain state. For example:- The
x/authmodule stores account metadata. - The
x/bankmodule stores account balances.
multistore. For example, the bank module’s store might contain entries like:
Messages
As you learned in the Transactions, Messages, and Queries section, each module defines the actions it allows via messages (sdk.Msg).
Messages are defined in the module’s tx.proto file under a service Msg block, and implemented by that module’s MsgServer. Here is a simplified example from the bank module:
service Msg block is referred to as the Msg service in the Cosmos SDK. The protobuf compiler generates a MsgServer interface from it, which the module implements in Go. BaseApp routes each incoming message by its type URL (for example, /cosmos.bank.v1beta1.MsgSend) to the correct implementation.
MsgSend above is an example of a message type definition. It represents a request to transfer tokens from one account to another. When included in a transaction and executed:
- The sender’s balance is checked.
- The amount is deducted from
from_address. - The amount is credited to
to_address.
Queries
Modules expose read-only access to their state through query services, defined inquery.proto. Queries do not modify state and do not go through block execution. For example:
Business logic
While messages define intent, theMsgServer and Keeper work together to execute that intent and apply state transitions to the module.
Business logic is conceptually split across two layers:
- The
MsgServerhandles the transaction-facing logic: it validates inputs, applies message-level business rules, and where required checks authorization. Once satisfied, it delegates state transitions to the keeper. - The
Keeperowns the module’s state: it defines the storage schema and provides the only authorized path for reading and writing it. Message handlers, block hooks, and governance proposals all go through keeper methods to make state changes. All state changes must go through the keeper, nothing accesses the store directly.
Message execution (MsgServer)
Each module implements a MsgServer, which is invoked by BaseApp’s message router when a message is routed to that module.
The MsgServer is responsible for:
- Checking authorization when required
- Delegating to keeper methods that validate inputs, enforce business rules, and perform state transitions
- Returning a response
Add handler which is the MsgServer method that processes a request to increment the counter:
Add is permissionless and delegates directly to the keeper. The handler itself contains almost no business logic. That keeps msg_server.go focused on request handling, while the keeper owns the actual state transition.
The full counter module example adds richer patterns on top of that minimal shape. For privileged messages like MsgUpdateParams, the MsgServer checks the caller against the stored authority before proceeding:
Keeper
A module’sKeeper is its state access layer. It owns the module’s KVStore and provides typed methods for reading and writing state. The store fields are unexported, so nothing outside the keeper package can access them directly.
The MsgServer and QueryServer both embed the keeper. It is best practice for business logic to be implemented in keeper methods rather than the MsgServer, so the same rules apply whether the caller is a message handler, a block hook, or a governance proposal.
The following keeper example is from the minimal counter module example. It holds a single state item and shows the smallest useful keeper shape.
counter uses collections.Item[uint64], a typed single-value entry backed by the module’s KV store using the Collections API. AddCount reads the current value, increments it, and writes it back. All state access goes through the keeper: nothing outside the keeper package can reach counter directly. For details on how the keeper opens its store from the context it receives, see Execution Context.
Inter-module access
Modules are isolated by default. Each module owns its state, and direct access to that state is restricted through the module’s keeper. Other modules cannot arbitrarily mutate another module’s storage. Instead, modules interact through explicitly defined keeper interfaces. For example:- The staking module calls methods on the bank keeper to transfer tokens.
- The governance module calls parameter update methods on other modules.
expected_keepers.go file that declares the interfaces it requires from other modules. This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose.
This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation.
Params
Most modules expose aParams struct: a set of configuration values stored on-chain that control the module’s behavior. Unlike regular state, params are intentionally stable: they only change through governance, not through user transactions. Examples include the minimum governance deposit, the maximum number of validators, or mint module inflation bounds. Params are stored under a single key (typically a collections.Item[Params]) and updated by submitting a governance proposal.
To update params, a governance proposal submits a MsgUpdateParams message. The MsgServer checks that the caller is the designated authority address (usually the governance module account) before writing the new values to the store. See Message execution (MsgServer) for a code example of this pattern.
The authority address is set at keeper construction time in app.go.
For chain-level consensus parameters, the x/consensus module manages them centrally; it also supports AuthorityParams, which lets governance update the authority address on-chain without a software upgrade.
Block hooks
Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces in theAppModule struct in module.go:
HasBeginBlocker— runs logic at the start of each blockHasEndBlocker— runs logic at the end of each blockHasPreBlocker— runs logic beforeBeginBlock, used for consensus parameter changes
ModuleManager in BaseApp, which calls each registered module’s hooks in a configured order. For the application wiring side, see Module Manager in app.go.
A module implements a hook by defining the corresponding method on its AppModule struct in module.go:
ModuleManager in app.go for the hooks to be called.
Genesis initialization
Modules define how their state is initialized when the chain starts. Each module implements:DefaultGenesis: returns the module’s default genesis stateValidateGenesis: validates the genesis state before the chain startsInitGenesis: writes the genesis state into the module’s store at chain startExportGenesis: reads the module’s current state and serializes it as genesis data
InitChain, BaseApp calls each module’s InitGenesis to populate its state from genesis.json.
For where this happens in the block lifecycle, see InitChain (genesis only).
Built-in and custom modules
The Cosmos SDK ships with a set of core modules that most chains use. Click any module to learn more:| Module | What it does |
|---|---|
| x/auth | Accounts, authentication, and transaction signing |
| x/bank | Token balances and transfers |
| x/staking | Validator set and delegations |
| x/gov | On-chain governance and proposals |
| x/distribution | Staking reward distribution |
| x/slashing | Validator penalty enforcement |
| x/mint | Token issuance |
| x/evidence | Submission and handling of validator misbehavior |
| x/upgrade | Coordinated chain upgrades |
| x/authz | Delegated message authorization |
| x/feegrant | Fee allowances between accounts |
| x/consensus | On-chain management of CometBFT consensus params |
| Module | What it does |
|---|---|
| Proof of Authority | Permissioned validator set managed by an on-chain authority, replacing token-based staking |
| Group | On-chain multisig accounts and collective decision-making with configurable voting policies |
| Network Manager | Infrastructure management for high-availability node operations |
Anatomy of a module (high-level)
Modules live under thex/ directory of an SDK application:
x/ is a self-contained module.
An application composes multiple modules together to form a complete blockchain.
Inside a module, you will typically see a structure like this:
x/:
keeper/: Contains theKeeperstruct (state access) and implementations of theMsgServerandQueryServerinterfaces.types/: Defines the module’s public types: generated protobuf structs, store keys, and theexpected_keepers.gointerfaces that declare what this module needs from other modules.module.go: Connects the module to the application and registers genesis handlers, block hooks, and message and query services..protofiles: Define messages, queries, state schemas, and genesis state. Go code is generated from these files and used throughout the module.
Modules in context
Putting everything together you’ve learned so far:- Accounts authorize transactions.
- Transactions carry messages and execution constraints.
- Blocks order transactions and define commit boundaries.
- Modules define the business rules of execution.
- MsgServer validates messages and orchestrates state transitions.
- Keeper performs controlled reads and writes to module state.
- State persists the deterministic result of execution.