Skip to content
Menu

Documentation

Add your module

A module gets its screens from a declaration, not from frontend code. The console draws lists, details, forms and commands from it.

Declare a resource

Create admin.py inside the module. This one is for the module sandloom new --module notes generates, whose API lists, shows and creates items:

RESOURCES = [
    {
        "name": "notes",
        "label": "Notes",
        "access": {"authenticated": True},
        "list": {"path": "/api/v1/notes/items", "method": "GET"},
        "pagination": {"mode": "offset", "offset": "skip", "limit": "limit", "max_page_size": 50},
        "columns": ["title", "owner_id"],
        "detail": {"path": "/api/v1/notes/items/{item_id}", "method": "GET"},
        "show": ["id", "title", "owner_id"],
        "create": {"path": "/api/v1/notes/items", "method": "POST", "fields": ["title"]},
        "labels": {"id": "ID", "title": "Title", "owner_id": "Owner"},
        "scope": {"parameter": None, "label": "Organization"},
    }
]

The console pages through a list, so the API has to accept an offset. The generated list takes only limit: add skip: Annotated[int, Query(ge=0)] = 0 to the route and pass it to the query as .offset(skip).

Here scope means a required, explicitly selected tenant UUID. The top bar provides its selector; requests send X-Tenant-Id. The API must use TenantScope and authorize the caller; the selector does not authenticate membership. If the API also puts that tenant in the path, set parameter to its actual path parameter name. Without a selected scope the resource makes no requests. Changing scope cancels reads and replaces the query cache, cursor chains and UI store. Unscoped resources such as identity administration omit scope entirely.

Generate the manifest

uv run sandloom generate

This checks the declaration against your application's real API and writes <app>/shared/admin-manifest.json. A field or route that doesn't exist fails here, not in the browser. Restart the application and reload the page: the section is there.

Commit the file. sandloom test fails when it is out of date (SANDLOOM-T016).

Generation imports your application's code but doesn't enter its lifespan, run migrations or query a database. Run it with the full set of modules, even if you deploy them as separate services.

Keep declarations and APIs together

Change admin.py and the API it describes in the same commit. If you need more than a declaration can express, see Develop the console.

Failure Check
An operation is not owned by a module Bind the module's own /api/v1/ endpoint. Don't point a declaration at another module's API.
An unsupported field or schema Use a supported scalar input, or a compiled component.