VeloIQ ships with four layers of access control that go from coarse to fine: global role permissions, model-level exceptions, field-level exceptions, and row-level filtering. All four layers are opt-in and can be combined freely on any model.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.
RBAC — role-based access control
The three RBAC layers work together as a funnel. Each layer can only narrow the access that the layer above it grants — it can never expand it.Layer 1 — Global role permissions
Define your roles inVeloIQConfig and pass them to create_veloiq_app(). Each RoleDef maps a role name to a set of allowed HTTP methods. VeloIQ seeds these roles to the database on startup and makes them editable at runtime through the admin UI.
Use the built-in constants to assign method sets:
| Constant | Allowed methods |
|---|---|
ALL_METHODS | GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD |
WRITE_METHODS | GET, POST, PUT, PATCH, OPTIONS, HEAD |
READ_METHODS | GET, OPTIONS, HEAD |
Layer 2 — Model-level exceptions
Use the@model_access decorator to override which actions a role may perform on a specific model. Roles not listed in @model_access continue to inherit their global permissions unchanged.
Exceptions are restrictive only.
@model_access can reduce what a role may do on a model — it cannot grant actions that the role’s global permissions don’t already include.Layer 3 — Field-level exceptions
Useveloiq_field() to restrict read and write access per field. The backend CRUD router enforces these at runtime, and veloiq generate emits readRoles and writeRoles into the TypeScript schema so the frontend can hide or disable restricted inputs automatically.
veloiq_field() accepts all the same keyword arguments as pydantic.Field and is fully compatible with TimestampedModel and FrameworkModel.
ReBAC — relationship-based access control
ReBAC applies row-level filtering based on the data itself. Use it when access depends on ownership, tenant membership, or any relationship between a user and a record. Apply the@rebac decorator to a model class; the framework then filters every query for that model before returning results.
@rebac(owner_field=...)
Restrict each user to only the rows they created. Pass the name of the column that holds the owner’s user ID.
@rebac(tenant_field=...)
Isolate rows by tenant. Each user sees only rows whose tenant_id matches a tenant they belong to.
@rebac(filter=...)
For custom access logic, pass a lambda that receives the current user, the model class, and the database session and returns a SQLAlchemy WHERE clause. Use rebac_subquery() to inherit access from a parent model through a relationship.
rebac_subquery()
rebac_subquery(ModelClass, user, session) returns a subquery of primary keys from ModelClass that the current user may access. The target model must itself carry a @rebac decorator. Circular dependencies between models raise a ValueError at runtime.
Use it to chain access through a relationship: if a user can access a Folder, they can access the Document rows inside it.
Key behaviors
- Inaccessible rows return 404, not 403. This prevents leaking information about which IDs exist.
- Multiple patterns are OR-combined. If you pass more than one of
filter,owner_field, andtenant_fieldto the same@rebaccall, a row is visible if any single pattern allows it.
After changing models
Runveloiq generate after adding or changing any veloiq_field(), @model_access, or @rebac annotation. This updates the TypeScript schemas so the frontend enforces the same restrictions as the backend.
Models
Define fields and use veloiq_field() for field-level access
Modules
Understand how modules and auto-loading work