> ## Documentation Index
> Fetch the complete documentation index at: https://docs.veloiq.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Defining Data Models in VeloIQ with SQLModel

> Learn how to define VeloIQ data models using FrameworkModel, TimestampedModel, and StandardModel base classes with relationships and field options.

Models are the foundation of every VeloIQ application. Define a model once in Python and the framework automatically generates a REST API, an SQLAdmin back-office view, and TypeScript schemas for the React frontend — no repetition required.

## Base classes

VeloIQ provides three base classes. Choose the one that matches your storage requirements; all three are imported directly from `veloiq_framework`.

### FrameworkModel

`FrameworkModel` is the minimal base. It provides a single auto-increment integer primary key named `id` and nothing else — no audit columns, no extra metadata. Use it when you want the UI to show only the fields you declare.

```python theme={null}
from veloiq_framework import FrameworkModel

class Category(FrameworkModel, table=True):
    __tablename__ = "category"
    name: str
    slug: str
```

### TimestampedModel

`TimestampedModel` extends `FrameworkModel` with automatic `created_at` and `updated_at` columns. The code generator appends these two fields after all fields you declare, so they appear last in every list, form, and detail view.

```python theme={null}
from veloiq_framework import TimestampedModel

class Order(TimestampedModel, table=True):
    __tablename__ = "order"
    reference: str
    total: float
    status: str = "pending"
```

<Tip>Use `TimestampedModel` as your default. Audit timestamps are almost always useful, and you can always switch to `FrameworkModel` later if a specific model genuinely doesn't need them.</Tip>

### StandardModel

`StandardModel` is for applications that must stay compatible with a CubicWeb database. It uses `eid` as the Python attribute name, mapped to the `cw_eid` physical column, and follows `cw_` column naming conventions. New greenfield applications should use `FrameworkModel` or `TimestampedModel` instead.

```python theme={null}
from veloiq_framework import StandardModel
from sqlmodel import Field
from sqlalchemy import Column, String

class LegacyItem(StandardModel, table=True):
    __tablename__ = "legacy_item"
    cw_name: str = Field(sa_column=Column("cw_name", String))
```

## Field types

Standard Python and SQLModel types work directly on all base classes. Use `Optional[T]` for nullable columns and set `default=None` or any other default value as needed.

```python theme={null}
from typing import Optional
from veloiq_framework import TimestampedModel

class Product(TimestampedModel, table=True):
    __tablename__ = "product"

    name: str
    price: float
    in_stock: bool = True
    description: Optional[str] = None
```

## Relations with `jm_relationship()`

Use `jm_relationship()` to declare SQLModel relationships. The function is a thin wrapper around SQLModel's `Relationship` that attaches cardinality metadata — the frontend reads this to render required/optional indicators and pagination hints.

### One-to-many

Declare the collection on the parent and the scalar reference on the child, each pointing back at the other with `back_populates`.

```python theme={null}
from typing import List, Optional
from sqlmodel import Field
from veloiq_framework import TimestampedModel, jm_relationship

class Customer(TimestampedModel, table=True):
    __tablename__ = "customer"
    name: str
    orders: List["Order"] = jm_relationship(back_populates="customer")

class Order(TimestampedModel, table=True):
    __tablename__ = "order"
    reference: str
    customer_id: Optional[int] = Field(default=None, foreign_key="customer.id")
    customer: Optional["Customer"] = jm_relationship(back_populates="orders")
```

### Self-referential

A model can reference itself to represent hierarchies such as tasks with subtasks. Self-referential relationships automatically trigger Miller column rendering in the frontend — clicking a row drills into its children in an adjacent column.

```python theme={null}
from typing import List, Optional
from sqlmodel import Field
from veloiq_framework import TimestampedModel, jm_relationship

class Task(TimestampedModel, table=True):
    __tablename__ = "task"
    title: str
    parent_id: Optional[int] = Field(default=None, foreign_key="task.id")
    subtasks: List["Task"] = jm_relationship(
        back_populates="parent",
        sa_relationship_kwargs={"foreign_keys": "[Task.parent_id]"},
    )
    parent: Optional["Task"] = jm_relationship(
        back_populates="subtasks",
        sa_relationship_kwargs={"foreign_keys": "[Task.parent_id]"},
    )
```

### `jm_relationship()` parameters

| Parameter                | Type          | Description                                                        |
| ------------------------ | ------------- | ------------------------------------------------------------------ |
| `back_populates`         | `str`         | Attribute name on the related model that points back to this one   |
| `min_items`              | `int`         | Minimum cardinality (default `0`)                                  |
| `max_items`              | `int \| None` | Maximum cardinality; `None` means unbounded (default `None`)       |
| `required`               | `bool`        | Whether the relation is required in UI forms (default `False`)     |
| `sa_relationship_kwargs` | `dict`        | Extra keyword arguments forwarded to SQLAlchemy's `relationship()` |

## Field options with `veloiq_field()`

Use `veloiq_field()` in place of a plain `Field()` to add per-role read and write restrictions to individual columns. For example, to allow only Admins to read or change an employee's salary:

```python theme={null}
from veloiq_framework import veloiq_field, TimestampedModel

class Employee(TimestampedModel, table=True):
    __tablename__ = "employee"
    name: str
    department: str
    salary: float = veloiq_field(
        default=0.0,
        read_roles=["Admin"],
        write_roles=["Admin"],
    )
```

`veloiq_field()` accepts all the same keyword arguments as `pydantic.Field`. See [Access Control](/concepts/access-control) for the full details on `read_roles`, `write_roles`, and how field-level restrictions interact with the broader RBAC system.

## Code generation

After defining or changing your models, run `veloiq generate` from the backend directory:

```bash theme={null}
veloiq generate
```

This writes two files for each module:

* `backend/app/modules/{module}/api.py` — CRUD REST endpoints (do not edit)
* `frontend/src/pages/{module}/{module}Schema.gen.ts` — TypeScript field definitions (do not edit)

Re-run `veloiq generate` every time you add, rename, or remove a field or model.

<CardGroup cols={2}>
  <Card title="Modules" href="/concepts/modules">
    Understand how modules are structured and auto-loaded
  </Card>

  <Card title="Access Control" href="/concepts/access-control">
    Apply RBAC and ReBAC to your models and fields
  </Card>
</CardGroup>
