Skip to content

Getting started

This page walks you from nothing to a working panel with your first table registered: install, connect your database, log in, register tables.

Prefer to click around first?

A hosted demo runs the bundled Acme dataset at https://demo.cartapel.com — no login needed. Or run the same demo locally with docker compose up from a repo checkout.

Install

cartapel is a single binary. Pull the Docker image, or build it from source:

bash
docker pull ghcr.io/de-rus/cartapel:latest
bash
# Requires a recent Rust toolchain and pnpm (for the embedded SPA).
cd ui && pnpm install && pnpm build && cd ..
cargo build --release
# → target/release/cartapel

The frontend is a single-page app compiled once and embedded into the binary. The result is entirely self-contained: no Node runtime at serve time, no static-file directory to ship.

First run

cartapel needs, at minimum, a database URL and a secret key:

bash
export CARTAPEL_SECRET_KEY="$(openssl rand -hex 32)"

cartapel serve \
  --db postgres://user:pass@host:5432/mydb \
  --schema public \
  --config ./admin \
  --data ./cartapel-data
  • --db — the Postgres connection URL. Also settable via CARTAPEL_DB, or as the URL of the primary source in config (see below).
  • --schema — the Postgres schema to introspect. Defaults to public; set the source's schemas list for more than one.
  • --config — a directory of HCL config files (see below). Optional, but without it no tables are exposed.
  • --data — where cartapel keeps its own state (users, sessions, audit log, config history) as a SQLite database. Defaults to ./cartapel-data.

On startup cartapel introspects your schema, loads the config directory, and logs what it found:

INFO cartapel: introspected 41 tables from schemas ["public"]
INFO cartapel: cartapel listening on http://127.0.0.1:8686/admin/

The panel is served under /admin by default, and http://…:8686/ redirects there. Change the mount path with --base-path--base-path '' serves at the root, --base-path /panel at a sub-path. It's applied at runtime, so one binary or image serves any prefix.

cartapel never writes to your database on its own

Your Postgres is only written to when a panel user edits a row, runs a bulk action, or imports data. All of cartapel's own bookkeeping lives in the separate SQLite state directory.

Log in

The first time serve runs against an empty state directory (zero users), cartapel bootstraps an admin account so you can log in:

bash
export CARTAPEL_ADMIN_EMAIL="[email protected]"
export CARTAPEL_ADMIN_PASSWORD="a-strong-password"
cartapel serve ...
  • With both env vars set, that exact account is created.

  • If CARTAPEL_ADMIN_EMAIL is unset, it defaults to admin@localhost.

  • If CARTAPEL_ADMIN_PASSWORD is unset, cartapel generates a random password and prints it once to the log:

    WARN cartapel: bootstrapped admin user [email protected] with password: 7hK2mQ...

Passwords are stored as argon2id hashes; login is rate-limited per IP.

Adding users later

cartapel user add [email protected] --role support --data ./cartapel-data creates or updates a user without the server running. See the CLI reference.

Register your first tables

The panel is an allowlist: only tables with a config file are exposed. An introspected-but-unconfigured table is absent from the navigation and 404s if you hit its URL directly. So a fresh panel starts empty — fill it with the setup wizard, or with config files by hand.

The setup wizard

When an admin logs into a panel with zero configured tables, cartapel redirects them to the setup wizard at /_setup. It lists every introspected table (with approximate row counts) and builds the whole first config on one screen:

  • Everything is pre-selected except framework noise (schema_migrations, _prisma_migrations, django_migrations, …) and tables without a primary key — both stay in the list, flagged, and can be ticked back on. Views are labeled too.
  • Tables arrive pre-sorted into suggested groups: one group per schema when you introspect several, otherwise by shared name prefix (order_items, order_events → an "Order" group).
  • You can rename any group inline, move a table to another group, or create a new group on the spot.

One click then writes the plan as a single atomic batch — a _group.hcl per group plus an empty config file per table (empty = introspected defaults) — and hot-swaps it into the live panel, no restart. If the config directory is read-only, nothing is written: the wizard shows every would-be file with a copy button so you can commit them to your repo instead.

By hand

The wizard writes ordinary files; you can just as well author them yourself. First, tell cartapel which database to read. The reserved config/cartapel.hcl declares the primary source; its URL comes from CARTAPEL_DB / --db:

hcl
# admin/config/cartapel.hcl
source "main" {
  type    = "postgres"
  url     = "env:CARTAPEL_DB"
  primary = true
}

Now expose a table. The smallest possible table config is an empty screen.hcl in a table folder under a group — the folder name is the table:

bash
mkdir -p admin/screens/catalog/products
touch admin/screens/catalog/products/screen.hcl

That empty screen.hcl registers the products table. It renders with introspected defaults: all columns in the list, sensible widgets for each type, FKs as links, timestamps localized.

From there you refine it:

hcl
# admin/screens/catalog/products/screen.hcl
label_plural = "Products"

list {
  columns = ["id", "name", "price", "in_stock", "created_at"]
  search  = ["name", "sku"]
  filters = ["in_stock"]
  sort    = "-created_at"
}

field "price" {
  widget = "money"
  params = { currency = "USD" }
}

field "in_stock" {
  widget = "toggle"
}

And a _group.hcl in the group folder names the sidebar group:

hcl
# admin/screens/catalog/_group.hcl
label = "Catalog"
icon  = "package"   # any lucide icon name
order = 1

Save the files — cartapel watches the config directory and hot-reloads on change (debounced; a broken edit keeps the last good config and logs the error). The Catalog group appears with your Products table inside it, no restart. In-app builder edits — including the setup wizard — hot-swap the same way.

What's next

Released under the MIT License.