# How to Use Custom Dataclasses in GenLayer Contract Storage: A Complete Guide

> Learn to use custom dataclasses in GenLayer contract storage. Use the @allow_storage decorator and type-annotate fields in storage containers for seamless integration.

- Repository: [GenLayer Labs/genlayer-project-boilerplate](https://github.com/genlayerlabs/genlayer-project-boilerplate)
- Tags: how-to-guide
- Published: 2026-08-20

---

**Use the `@allow_storage` decorator before `@dataclass` to mark custom Python classes as safe for GenLayer contract storage, then reference them in `TreeMap`, `DynArray`, or other storage containers with type-annotated fields.**

GenLayer contracts rely on deterministic, on‑chain data structures for persistent state. While the platform provides built‑in storage types like `TreeMap`, `DynArray`, `Array`, `u256`, and `i256`, developers often need richer data models. The `@allow_storage` decorator enables custom Python dataclasses to be embedded directly in contract storage, with the GenVM handling automatic serialization and deserialization.

## Understanding the `@allow_storage` Decorator

The `@allow_storage` decorator signals to the GenVM linter that a class is safe for serialization and can persist in contract state. This marker is required before the class can be used as a field type or container value in any storage declaration.

Critical requirements for decorated classes:

- Must appear **before** `@dataclass` in the decorator stack
- All fields require storage‑compatible type annotations
- Must use the generated `__init__` from `@dataclass` — no custom `__init__` logic allowed
- Nested dataclasses must also be marked with `@allow_storage`

## Defining a Storage-Compatible Dataclass

Follow this pattern to create valid storage classes in your GenLayer contracts, as demonstrated in [[`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py)](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py#L8-L24):

```python
from dataclasses import dataclass
from genlayer import *

@allow_storage                 # Must precede @dataclass

@dataclass
class Bet:
    id: str
    has_resolved: bool
    game_date: str
    resolution_url: str
    team1: str
    team2: str
    predicted_winner: str
    real_winner: str
    real_score: str

```

Each field uses primitive storage types (`str`, `bool`). For nested structures, recursively apply `@allow_storage` to all dataclass components.

## Using Dataclasses in Contract Storage Declarations

Once marked, reference your dataclass in storage type annotations. The `FootballBets` contract in the boilerplate repository shows nested storage with custom dataclasses:

```python
class FootballBets(gl.Contract):
    bets: TreeMap[Address, TreeMap[str, Bet]]   # Bet used as nested value type

    points: TreeMap[Address, u256]

```

The `TreeMap` requires storage‑compatible key and value types. `Bet` satisfies this constraint because it is an allowed storage class.

## Creating and Persisting Dataclass Instances

Inside contract methods, instantiate and assign dataclasses directly. The GenVM automatically serializes on assignment:

```python
bet = Bet(
    id=bet_id,
    has_resolved=False,
    game_date=game_date,
    resolution_url=match_resolution_url,
    team1=team1,
    team2=team2,
    predicted_winner=predicted_winner,
    real_winner="",
    real_score="",
)
self.bets.get_or_insert_default(sender_address)[bet_id] = bet

```

The `get_or_insert_default` method returns a mutable `TreeMap` for the address, allowing direct indexed assignment of the `Bet` instance.

## Reading Stored Dataclass Values

Access stored dataclasses through normal indexing. Fields are available as attributes:

```python
stored_bet = self.bets[gl.message.sender_address][bet_id]
print(stored_bet.real_winner)   # Direct field access

```

The GenVM deserializes the stored representation back to a Python dataclass instance automatically.

## Complete Working Example

This standalone contract demonstrates the full pattern for using custom dataclasses in GenLayer storage:

```python

# contracts/example_contract.py

from dataclasses import dataclass
from genlayer import *

@allow_storage
@dataclass
class Profile:
    username: str
    score: u256
    active: bool

class GameStats(gl.Contract):
    profiles: TreeMap[Address, Profile]

    @gl.public.write
    def set_profile(self, username: str, score: int, active: bool) -> None:
        prof = Profile(username=username, score=score, active=active)
        self.profiles[gl.message.sender_address] = prof

    @gl.public.view
    def get_profile(self, addr: str) -> dict:
        prof = self.profiles[Address(addr)]
        return {"username": prof.username, "score": int(prof.score), "active": prof.active}

```

The `Profile` dataclass stores a username, score, and active status per address. The `set_profile` method creates and persists instances; `get_profile` demonstrates field access in view functions.

## Storage Type Compatibility and Restrictions

| Aspect | Requirement |
|--------|-------------|
| **Decorator order** | `@allow_storage` must precede `@dataclass` |
| **Field types** | Only primitives (`str`, `bool`, `int`), `u256`, `i256`, other `@allow_storage` dataclasses, or allowed containers (`TreeMap`, `DynArray`, `Array`) |
| **Mutability** | Python `list` and `dict` are prohibited unless wrapped in `DynArray` or `TreeMap` |
| **Defaults** | Avoid mutable default values; use `dataclass` generated `__init__` or immutable defaults |
| **Linter enforcement** | `genvm-lint` raises errors for unmarked classes used in storage |

## Key Source Files in the Boilerplate Repository

| File | Purpose |
|------|---------|
| [[`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py)](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) | Real‑world `Bet` dataclass with nested `TreeMap` storage |
| [[`CLAUDE.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/CLAUDE.md)](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/CLAUDE.md) | Official documentation of supported storage types and `@allow_storage` |
| [[`README.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/README.md)](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/README.md) | Contract development workflow overview |
| [[`frontend/lib/contracts/FootballBets.ts`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/frontend/lib/contracts/FootballBets.ts)](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/frontend/lib/contracts/FootballBets.ts) | TypeScript client accessing stored dataclass data |

## Summary

- Apply `@allow_storage` **before** `@dataclass` to enable storage compatibility
- Type-annotate all fields with storage‑compatible types only
- Use `@allow_storage` recursively for any nested dataclasses
- Assign instances directly to `TreeMap`, `DynArray`, or `Array` containers — GenVM handles serialization
- Access fields directly on retrieved instances; no manual deserialization required
- Run `genvm-lint` to catch storage violations before deployment

## Frequently Asked Questions

### What happens if I forget the `@allow_storage` decorator?

The `genvm-lint` tool will raise a compilation error when your dataclass is used in a storage type annotation. The contract will fail to build until the decorator is added in the correct position before `@dataclass`.

### Can I use regular Python `list` or `dict` inside a dataclass for GenLayer storage?

No. Standard Python containers are not deterministic and cannot be stored directly. Use `DynArray` for variable-length sequences or `TreeMap` for key-value mappings. These GenLayer types provide the required serialization guarantees for on-chain state.

### Does `@allow_storage` support inheritance between dataclasses?

As implemented in genlayerlabs/genlayer-project-boilerplate, each dataclass in the hierarchy must independently declare `@allow_storage`. The linter validates storage compatibility field-by-field, so parent classes without the decorator will cause compilation failures even if children are properly marked.

### Can I add methods to my `@allow_storage` dataclass?

Yes, methods are permitted. However, you cannot override `__init__` with custom logic — the generated `__init__` from `@dataclass` must remain intact for the GenVM to correctly instantiate stored objects during deserialization.