What Solidity events are

An event is a declaration in a contract that creates a log entry when it is emitted. These logs are stored in the transaction receipt and can be accessed by off-chain applications such as:

  • Web frontends
  • Backend services
  • Indexers like The Graph
  • Monitoring and alerting systems
  • Analytics dashboards

Unlike storage variables, events are not meant for on-chain reading by other contracts. Their purpose is to make contract activity observable outside the blockchain.

Why events matter

Blockchain state is expensive to read and update. Events provide a cheaper way to expose important changes without storing extra data in contract storage. For example, instead of keeping a separate history array of transfers, a token contract can emit a Transfer event every time tokens move.

This gives you:

  • Lower gas cost than storing historical records
  • Easier integration with user interfaces
  • Better support for audit trails and analytics
  • A standard way to track contract behavior

When to use events

Use events whenever a state change should be visible to off-chain systems.

Common examples include:

  • A user deposits or withdraws funds
  • Ownership changes
  • A new order is created or filled
  • A voting action is recorded
  • A configuration parameter is updated

Do not use events as a replacement for essential contract state. If your contract logic depends on a value, store it in state variables. Events are for notification, not for persistent business logic.

Events vs storage

FeatureEventsStorage
PurposeNotify off-chain systemsPersist contract state
Readability from contractsNot directly usableDirectly readable
Gas costLower for loggingHigher for persistent writes
Historical trackingGoodRequires custom design
Suitable for business logicNoYes

A good rule is: if the contract must remember it, store it; if external systems only need to know it happened, emit an event.


Declaring an event

An event is declared with the event keyword.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract PaymentVault {
    event Deposit(address indexed sender, uint256 amount);
}

This declaration defines a log schema. It does not emit anything by itself. To create a log entry, you must call emit inside a function.

emit Deposit(msg.sender, msg.value);

Emitting an event in a contract

Here is a practical example of a simple vault contract that accepts deposits and withdrawals.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract PaymentVault {
    mapping(address => uint256) public balances;

    event Deposit(address indexed sender, uint256 amount);
    event Withdrawal(address indexed recipient, uint256 amount);

    function deposit() external payable {
        require(msg.value > 0, "No ether sent");

        balances[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;

        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "Transfer failed");

        emit Withdrawal(msg.sender, amount);
    }
}

What this example shows

  • Deposit is emitted after the balance is updated
  • Withdrawal is emitted after the transfer succeeds
  • The event includes the actor and the amount
  • The balances mapping remains the source of truth for contract logic

This pattern makes the contract easy to monitor from a frontend or backend service.


Indexed parameters and filtering

Solidity allows you to mark up to three event parameters as indexed. Indexed parameters are stored in the log topics, which makes them searchable by off-chain tools.

event Deposit(address indexed sender, uint256 amount);

Here, sender is indexed, so a client can filter logs by sender address.

Why indexing is useful

If your application needs to query logs for a specific user, order, or asset, indexing makes that possible efficiently. For example:

  • Find all deposits made by one address
  • Track all transfers involving a specific token ID
  • Watch updates for a given marketplace listing

Important trade-off

Indexed parameters are easier to filter, but the full value is not always as convenient to decode in every context. Also, only three parameters can be indexed per event, so choose them carefully.

A practical guideline:

  • Index identifiers you will query often
  • Do not index everything
  • Keep the event focused on the most useful search keys

Designing useful event payloads

A good event should be informative, compact, and stable.

Include the minimum useful context

For most contracts, an event should answer these questions:

  • Who triggered the action?
  • What happened?
  • How much or which asset was involved?
  • What identifier or reference ties this to application state?

For example, a marketplace contract might emit:

event OrderFilled(
    uint256 indexed orderId,
    address indexed buyer,
    address seller,
    uint256 price
);

This gives off-chain systems enough information to update a UI, record analytics, and reconcile business logic.

Avoid redundant data

Do not emit values that can be trivially derived from other event fields or from current state. Extra data increases gas usage and can make logs harder to interpret.

For example, if msg.sender is already the caller, you usually do not need to emit both caller and sender unless they represent different concepts.

Keep names consistent

Use clear, action-oriented names:

  • Deposit
  • Withdrawal
  • OrderCreated
  • OrderCancelled
  • RoleGranted

Avoid vague names like Updated unless the event is very small and the context is obvious.


Event patterns in real contracts

Different contract types benefit from different event designs.

Token contracts

Token contracts typically emit events for transfers and approvals. This is how wallets and explorers track balances and allowances.

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

These events are so common that many tools expect them by convention.

Governance contracts

A governance system might emit:

event ProposalCreated(uint256 indexed proposalId, address indexed proposer, string description);
event VoteCast(uint256 indexed proposalId, address indexed voter, bool support, uint256 weight);

This lets a frontend display proposal history and vote participation without scanning storage-heavy data structures.

Marketplace contracts

A marketplace might use:

event ListingCreated(uint256 indexed listingId, address indexed seller, uint256 price);
event ListingPurchased(uint256 indexed listingId, address indexed buyer, uint256 price);
event ListingCancelled(uint256 indexed listingId, address indexed seller);

These events provide a complete activity trail for each listing.


Emitting events at the right time

The order of operations matters. Emit events only after the relevant state change has succeeded.

Good practice

  1. Validate inputs
  2. Update state
  3. Perform external interactions if needed
  4. Emit the event after success

In many cases, emitting after the state update is the clearest approach because the log reflects the final result.

Example: state first, event second

balances[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawal(msg.sender, amount);

This ensures the event is only emitted when the withdrawal completes successfully.

Why this matters

If you emit an event too early and a later step fails, the whole transaction reverts and the log disappears. That is usually fine, but it can make code harder to reason about. Emitting after success keeps the event aligned with the final contract state.


Events and contract upgrades

Even in non-upgradeable contracts, event design deserves attention because logs become part of your application’s public interface.

Treat event schemas as stable APIs

Changing event names or parameter order can break:

  • Frontend listeners
  • Indexers
  • Analytics jobs
  • Monitoring scripts

If you need to evolve an event, consider adding a new event instead of changing the old one.

Example strategy

Suppose you originally emit:

event OrderCreated(uint256 indexed orderId, address indexed seller, uint256 price);

Later you want to include a token address. Rather than changing the old event, add a new one:

event OrderCreatedV2(
    uint256 indexed orderId,
    address indexed seller,
    address token,
    uint256 price
);

This preserves compatibility with existing consumers.


Common mistakes to avoid

1. Using events as storage

Events are not readable by other contracts. If your contract needs to know a past value, store it in state.

2. Emitting too much data

Large event payloads increase gas costs and can make logs harder to process. Keep them focused.

3. Forgetting indexed fields

If you know you will query by address or ID, index those fields from the start.

4. Emitting before validation

If a function may revert, emit the event only after all checks and critical operations succeed.

5. Changing event signatures casually

A signature change can break off-chain consumers. Treat event definitions as part of your public interface.


Practical checklist for event design

Before finalizing an event, ask:

  • Will off-chain systems need to filter by this field?
  • Is the event name specific and action-oriented?
  • Does the payload contain enough context to interpret the action?
  • Is any field redundant?
  • Could this schema need to remain stable for a long time?

A well-designed event usually has a small number of fields, one or two indexed identifiers, and a clear semantic meaning.


Summary

Events are the bridge between on-chain execution and off-chain applications. They let your Solidity contract publish structured logs that frontends, indexers, and monitoring systems can consume efficiently.

Use events when you want to:

  • Track important contract actions
  • Support filtering and analytics
  • Reduce storage usage
  • Make your application easier to integrate

Design them carefully, keep them stable, and emit them only when the underlying state change is complete. That approach will make your contracts easier to maintain and much easier to build on.

Learn more with useful resources