x/counter, but uses a stripped-down version so you can focus on the core steps of building and wiring a module yourself.
By the end, you’ll have built a working module and wired it into a running chain. For a deeper dive into how modules work in the Cosmos SDK, see Intro to Modules.
Before continuing, you must follow the Prerequisites guide to make sure everything is installed.
Making modules
The Cosmos SDK makes it easy to build custom business logic directly into your chain through modules. Every module follows the same overall pattern:- Define messages: users can send
Addto increase the counter - Define queries: users can query
Countto read the current value - Define genesis state: the module starts with a count of
0
- Run
proto-gento generate the Go types and interfaces - Implement your business logic in a
keeperto store the count and update it - Implement
MsgServerandQueryServerto pass messages and queries into the keeper - Register the module in
module.go - Wire it into the chain in
app.go
Step 1: Setup
This tutorial uses thetutorial/start branch, which is a blank template for you to create the module from scratch and wire it into app.go.
- Clone the repo if you haven’t already:
- Check out the
tutorial/startbranch and make the new module directories:
x/counter/ and proto/example/counter/v1/.
Step 2: Proto files
Proto files are the source of truth for the module’s public API. You define messages and services here. For a deeper look at how protobuf is used across modules, see Encoding and Protobuf. In this tutorial, the counter module stores one number,Add increases it by the amount the user submits, and the query returns the current value.
First, create the three proto files:
tx.proto
This is the first module file you define. It declares the transaction message shape forAdd: what the user sends to increment the counter, and what the module returns after handling it. To learn more about how messages are defined and routed, see Messages. Add the following code to tx.proto.
query.proto
This file defines the read-only gRPC query service and the response type for fetching the current count. To learn more about how queries differ from transactions, see Queries. Add the following code toquery.proto.
genesis.proto
This file defines the data the module stores in genesis so the counter can be initialized when the chain starts. Add the following code togenesis.proto.
Step 3: Generate Code
- Make sure Docker is running.
- The first time you run proto-gen you need to build the builder image. Run the following commands:
x/counter/types/:
Do not edit generated files. Changes to public types belong in the proto files. Re-run make proto-gen after any proto change.
The most important generated output is the MsgServer and QueryServer interfaces. In Steps 5 and 6, you’ll implement them in keeper/msg_server.go and keeper/query_server.go.
Step 4: Types
Next, you’ll define the module types and identifiers inx/counter/types that the rest of the module depends on.
Create the two files for this section:
keys.go
This file defines the module’s basic identifiers: the module name used throughout the SDK, and the store key used to claim the module’s KV store namespace. For more on how modules access state through store keys, see How modules access state.ModuleName identifies the module throughout the SDK (routing, events, governance). StoreKey is the key used to claim the module’s isolated namespace in the chain’s KV store (set equal to ModuleName by convention).
Interface Registration
This file registers your generated message types with the SDK interface registry so the application can decode and route your module’s transactions correctly._Msg_serviceDesc is generated by make proto-gen — it describes the Msg gRPC service defined in tx.proto.
Step 5: Keeper
In this step, you create the keeper, which is the part of the module that owns the counter state and provides the methods the rest of the module will call. For a conceptual overview of the keeper’s role, see Keeper. Create the keeper file:collections.Item[uint64] is a typed KV store entry; the collections package handles encoding and namespacing. GetCount treats ErrNotFound as zero so the counter starts at zero without explicit initialization.
State layout
StoreKey("counter") is the module’s isolated namespace within the chain’s global KV store. No other module can read or write this namespace.collections.NewPrefix(0)is a single-byte prefix that identifies thecounteritem within the module’s namespace. A module with multiple items would useNewPrefix(0),NewPrefix(1), etc. to keep them separate.ErrNotFoundtreated as zero means the keeper never needs to explicitly set an initial value — the firstGetCountcall on a fresh chain returns0by convention.
Step 6: MsgServer
In this step, you implement the transaction handler for the generatedMsgServer interface. This is the code path that runs when a user submits tx counter add. For a conceptual overview of message execution, see Message execution.
Create the message server file:
MsgServer interface and forwards the Add transaction to the keeper’s AddCount method.
msgServer embeds *Keeper and delegates directly to AddCount. The handler itself contains no business logic.
Step 7: QueryServer
In this step, you implement the read-only query handler for the generatedQueryServer interface. This is the code path that runs when someone queries the current counter value. For more on how modules expose queries, see Queries.
Create the query server file:
QueryServer interface and returns the current counter value from the keeper.
Step 8: module.go
In this step, you connect your keeper and generated services to the Cosmos SDK module framework so the application knows how to initialize the module, expose its query routes, and register its transaction handlers. Create the module file:var _ interface = Struct{} block at the top is a Go compile-time check — if the struct is missing any required method, the build fails immediately.
RegisterServices is the most important method. It connects the generated server interfaces to your implementations, making them reachable from the SDK’s message and query routers.
Step 9: AutoCLI
In this step, you define the CLI metadata for your module. AutoCLI reads this configuration together with your proto services and generates theexampled query counter and exampled tx counter commands automatically.
Create the AutoCLI file:
AutoCLI how to expose the Count query and Add transaction as simple command-line commands.
PositionalArgs maps the first CLI argument to the add field in MsgAddRequest, so add 4 works instead of add --add 4.
Step 10: Wire into app.go
In this step, you wire your new module into the application so the chain creates its store, constructs its keeper, and includes it in module startup and genesis handling. For a full explanation of whatapp.go does and why the wiring order matters, see app.go Overview.
Open app.go and find each marker comment. Paste the code directly below it.
1. Imports
Add the counter module, keeper, and shared types imports toapp.go.
Find the comment in app.go and add the code directly below it.
2. Keeper Field
Store the counter keeper onExampleApp so the rest of the app can reference it.
3. Store Key
Give the counter module its own KV store namespace.4. Keeper Instantiation
Construct the counter keeper using the module store and app codec.5. Module Manager
Register the counter module with the app’s module manager.6. Genesis Order
Include the counter module when the app initializes state from genesis.7. Export Order
Include the counter module when the app exports state back out to genesis.Step 11: Build
Run the following to compile the app and make sure the new module wiring is valid before you try to run the chain.Step 12: Test your module
Now you’ll run the app locally and use one transaction plus one query to confirm the module works end-to-end.Start the chain
First, install the binary and start the demo chain.exampled and then runs scripts/local_node.sh, which:
- resets the local chain data
- initializes genesis
- creates and funds the
aliceandbobtest accounts - creates a validator transaction
- starts the chain
Submit a transaction
Open a second terminal and submit a transaction that adds4 to the counter:
code: 0, which means the chain accepted and executed the transaction without an application error:
Query the chain
Query the counter to confirm the stored value changed using the query command thatAutoCLI generated earlier:
Next steps
The simple counter module you built here follows the same structure as the fullx/counter example in the main branch. Next, you’ll see how the full module extends that foundation with features like params, fee collection, tests, and more.
Next: Full Counter Module Walkthrough →