Skip to content

Databases ​

Pomelo auto-provisions one database per workspace per template, using the shared service's admin credentials. You declare names with templates; Pomelo does the create/drop/seed dance.

Declare ​

Under a repo:

yaml
repos:
  api:
    databases:
      main: "{{branch.safe}}"          # primary DB
      test: "{{branch.safe}}_test"     # separate test DB
    env:
      DATABASE_URL: "postgres://{{shared.postgres.url}}/{{db.main}}"
      TEST_DATABASE_URL: "postgres://{{shared.postgres.url}}/{{db.test}}"

{{db.NAME}} resolves to the named entry above (session-prefixed) — names instead of positional indexes, so reordering the map never breaks a reference. {{db.NAME.url}} gives the full postgres://user:pass@host:port/name. The env vars produced for a workspace on branch feat/login:

DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/myproject_feat_login
TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/myproject_feat_login_test

{{branch.safe}} only turns / into _, so a hyphen stays (feat/login-page gives myproject_feat_login-page).

Shared service credentials ​

The credentials come from the db_user / db_password fields on the shared service (well-known Postgres fills them in by default):

yaml
shared_services:
  postgres:
    db_user: postgres
    db_password: postgres

{{shared.postgres.url}} expands to user:pass@host:port.

Seed from main ​

New workspaces can inherit their databases from the main workspace's copies instead of building them from scratch:

yaml
repos:
  api:
    seed_from_main: true   # clone api's DBs from main (CREATE DATABASE ... TEMPLATE)

Set up main once, and each new workspace clones the prepared databases in seconds, with main's sample data - the repo's own seed is skipped. If a main database is missing, or the copy fails, Pomelo creates it empty with a warning. See Workspace > Seed from main.

To (re)build main's data, right-click the main workspace and choose Prepare Main...: it drops and recreates main's databases, runs each repo's migrations, then seeds (pom prepare-main does the same).

Lifecycle ​

Databases are created when the workspace is created (and again, if missing, when one of its services starts), and dropped when you delete the workspace. pom db create|drop|reset [branch] manages them by hand, and pom db clean drops this project's databases no workspace uses.

Setup hooks ​

Use the repo-level setup: block for migrations / seeds that should run right after the workspace is created:

yaml
repos:
  api:
    setup:
      - go mod download
      - go run . migrate
      - go run . seed

These run after the worktree exists, the env file is written, and the databases have been created - so DATABASE_URL is set correctly. Repos set up in parallel, and a failed setup step warns instead of stopping the workspace. With no setup:, the repo's install, generate and migrate commands run in that order.

Browsing data in the app ​

The Database panel (ctrl-shift-d, or the Database button in the status bar) inspects the active workspace's data without a separate DB client. Pomelo already knows the connection, so there's nothing to wire up.

Its header names the workspace and has New Console, Refresh and Collapse All; Filter tables and columns narrows the tree.

Databasefeat-login
Filter tables and columns
CONSOLES
query 1main
DATABASES
api
myproject_feat_login
articles128
sessions42
users8
idbigint
emailtext
nametext
roletext
created_attimestamptz
redisshared
filesshared
The Database panel. Click a row to fold or open it.
  • Consoles - your saved SQL consoles.
  • Databases - each repo with its databases and the shared services it uses, then Other services. Postgres databases expand to their tables and views, and tables to their columns. Redis expands to its keyspaces, MinIO to its buckets, folders and objects.
users
Open Data
New Console with SELECT
Copy Name
Copy SELECT Statement
Show DDL
Truncate...
Drop Table...
Ask Claude about this table
A table's menu

Right-click:

  • A Postgres database - New Console, Refresh, Copy Name, Copy Connection URL, Open psql in Terminal, Copy Data from Main... and Reset Database... (both only in branch workspaces, and both ask first) and Ask Claude about this schema.
  • A table - Open Data, New Console with SELECT, Copy Name, Copy SELECT Statement, Show DDL, Truncate..., Drop Table... and Ask Claude about this table.
  • A column - Copy Name, Filter Data by this Column and Show Distinct Values.
  • A console - Open, Rename, Change Database... and Delete Console....
  • A Redis keyspace - Open Keys, Copy Pattern, Open redis-cli in Terminal and Delete Matching Keys....
  • A MinIO object - Open, Download, Copy Presigned URL, Copy Path and Delete....

Tables ​

api/dev/usersonfeat-login
DataStructureDDL
Details
WHEREid > 10
ORDER BYid
500 rows
idbigintemailvarchar(255)nametextorg_idbigintroletextsettingsjsonb
1101ada@example.comAda1Acmeadmin{"theme":"light","beta":true}
2102alan@example.comAlan2Globexmember{ 6 keys } 7.1 KB
3103grace@example.comGrace3InitechmemberNULL
4104linus@example.comLinus1Acmemember{"theme":"dark","beta":true}
5105margaret@example.comMargaret2Globexadmin{"theme":"light","beta":false}
6106ken@example.comKen3Initechmember{"theme":"dark","beta":false}
7107barbara@example.comBarbara1Acmemember{"theme":"light","beta":true}
8108dennis@example.comDennis2Globexmember{"theme":"dark","beta":false}
9109edsger@example.comEdsger3Initechadmin{"theme":"light","beta":false}
10110frances@example.comFrances1Acmemember{"theme":"dark","beta":true}
1-10 of 1234Export CSV
ValueRow
settingsjsonbrow 102
{ 6 keys }Expand allCollapseOpen in tab
{
"theme": "dark"
"beta": true
"digest": "weekly"
"locale": "en-US"
"notifications": { 2 keys }
"saved_views": [ 180 items ]
}
7.1 KBSet NULLCopy
A table tab with the Details side. Click a cell, the row numbers, the JSON folds, a filter box, Value or Row; double-click a name to edit it.

Click a table to open it as a data grid. Type a WHERE and ORDER BY to narrow and sort it (or click a column's header to sort), page through it 100, 500, 1000 or 5000 rows at a time (500 by default), drag a column's edge to resize it and click a cell to copy it. Export CSV writes the full result to ~/Downloads/<table>.csv.

Filter boxes under the column headers narrow by one column, together with the WHERE (press enter to run):

TypedKeeps rows where the column
anncontains ann (any case)
= 42is exactly 42
!= adminis anything but admin
null / not nullis NULL / is not NULL

Data, Structure and DDL switch what the tab shows. Structure lists the columns (type, NULL, default, primary and foreign keys), the indexes, and the tables whose foreign keys point here; click one to open it. DDL is the CREATE TABLE, with Copy.

api/dev/usersonfeat-login
DataStructureDDL
COLUMNTYPENULLDEFAULTKEY
idbigintnonextval('users_id_seq')primary
emailvarchar(255)no
nametextyes
org_idbigintyesorgs.id
roletextyes
settingsjsonbyes
REFERENCED BYON DELETE
orders.user_idcascade
login_tokens.user_idcascade
Structure: the columns, indexes and the foreign keys that point here.
api/dev/usersonfeat-login
DataStructureDDL
CREATE TABLE "public"."users" (
    "id" bigint DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
    "email" character varying(255) NOT NULL,
    "org_id" bigint,
    "settings" jsonb,
    CONSTRAINT "users_pkey" PRIMARY KEY (id),
    CONSTRAINT "users_org_id_fkey" FOREIGN KEY (org_id) REFERENCES orgs(id)
);
DDL: the table's CREATE TABLE.

Details ​

The side next to the grid (Details, or shift-enter) follows the selected cell:

  • Value - the whole value, however long. JSON opens as a tree in the order it was written: the first level is open, a folded object reads { 9 keys }, a long list shows 50 items then show N more, and Expand all / Collapse open or fold everything. A foreign key also shows the row it points at, with Open this row.
  • Row - the selected row as a record, the chosen column lit, then Referenced by: every table with a foreign key to this one and how many of its rows point at this row. Click one to open those rows.
api/dev/usersonfeat-login
DataStructureDDL
Details
WHEREid > 10
ORDER BYid
500 rows
idbigintemailvarchar(255)nametextorg_idbigintroletextsettingsjsonb
1101ada@example.comAda1Acmeadmin{"theme":"light","beta":true}
2102alan@example.comAlan2Globexmember{ 6 keys } 7.1 KB
3103grace@example.comGrace3InitechmemberNULL
4104linus@example.comLinus1Acmemember{"theme":"dark","beta":true}
5105margaret@example.comMargaret2Globexadmin{"theme":"light","beta":false}
6106ken@example.comKen3Initechmember{"theme":"dark","beta":false}
7107barbara@example.comBarbara1Acmemember{"theme":"light","beta":true}
8108dennis@example.comDennis2Globexmember{"theme":"dark","beta":false}
9109edsger@example.comEdsger3Initechadmin{"theme":"light","beta":false}
10110frances@example.comFrances1Acmemember{"theme":"dark","beta":true}
1-10 of 1234Export CSV
ValueRow
id102
emailalan@example.com
nameAlan
org_id2 - Globex (orgs)
rolemember
settings
jsonb - 7.1 KBCopyOpen in tab
{ 6 keys }
REFERENCED BY
orders.user_id4 rows
login_tokens.user_id1 rows
Double-click a value to edit it.
The Row side: the selected row as a record, the foreign key with its name, and the tables pointing at it.

Value or Row stays as you left it while you move around. In the grid a JSON cell over 2 KB reads as its summary ({ 9 keys } 21.3 KB), and a foreign key cell has an arrow that opens the row it points at, in that table's tab.

Editing ​

Double-click a cell (or press enter) to change it; a long or JSON value is edited in the Details side instead, where Set NULL and Revert are too. JSON opens there indented and colored, with line numbers, and goes back on one line when you keep it (cmd-enter), so only what you changed shows. Changes are not saved right away: they turn yellow and a bar counts them, naming the database they go into.

  • Review SQL opens the UPDATE statements in an editor tab, one per paragraph, to read before they run.
  • Discard drops them.
  • Apply (cmd-s) runs them all in one transaction, each row found by its primary key: if one fails, none is saved.
api/dev/usersonfeat-login
DataStructureDDL
Details
WHEREid > 10
ORDER BYid
500 rows
idbigintemailvarchar(255)nametextorg_idbigintroletextsettingsjsonb
1101ada@example.comAda1Acmeadmin{"theme":"light","beta":true}
2102alan@example.comAlan2Globexmember{ 6 keys } 7.1 KB
3103grace@example.comGrace3InitechmemberNULL
4104linus@example.comLinus1Acmeadmin{"theme":"dark","beta":true}
5105margaret@example.comMargaret2Globexadmin{"theme":"light","beta":false}
6106ken@example.comKen3Initechmember{"theme":"dark","beta":false}
7107barbara@example.comKen T.1Acmemember{"theme":"light","beta":true}
8108dennis@example.comDennis2Globexmember{"theme":"dark","beta":false}
9109edsger@example.comEdsger3Initechadmin{"theme":"light","beta":false}
10110frances@example.comFrances1Acmemember{"theme":"dark","beta":true}
2 changesnot saved - into myproject_feat-loginReview SQLDiscardApply S
ValueRow
roletextrow 104
admin
5 charsSet NULLRevertOpen in tabCopy
Two staged changes. Try Discard or Apply, or double-click another cell.

Primary key columns, views and tables without a primary key are read-only, and the side says why. Open in tab opens a value in the editor (JSON indented, keys in their order); saving that tab stages the change here, and text that is no longer valid JSON is refused.

Redis keys ​

Click a keyspace (session:*) to open its keys in a tab. MATCH is the pattern they are scanned with; change it and press enter. The keys are grouped by their next part (bull:mail:, bull:sms:), each with its type and how long it has left.

The selected key's value shows the way its type holds it:

  • string - the text, or a JSON tree when it is JSON.
  • hash - its fields and values.
  • list - each item with its index.
  • zset - each member with its score.
  • set - its members.

A long value shows its first 1000 items. TTL changes when the key expires (a number of seconds, or empty for never); Delete key and Delete all matching... ask first.

shared/redis/bull:*onfeat-login9 keys
bull:mail:7
stringid
hashmeta
listwait
zsetdelayed
hash11h 0m
hash22h 0m
hash33h 0m
bull:sms:2
listwait
setworkers1m
stringbull:mail:idCopy valueDelete key
{ 4 keys }Expand allCollapse
{
"name": "welcome"
"data": { 2 keys }
"opts": { 1 keys }
"timestamp": 1727700000000
}
JSON detected - 119 B
2 prefixesDelete all matching...
A keyspace tab. Click a key, fold a group, or change the pattern.
shared/redis/bull:*onfeat-login9 keys
bull:mail:7
stringid
hashmeta
listwait
zsetdelayed
hash11h 0m
hash22h 0m
hash33h 0m
bull:sms:2
listwait
setworkers1m
hashbull:mail:metaCopy valueDelete key
FIELDVALUE
opts{"maxLenEvents":10000}
versionbullmq:5.12.0
concurrency4
2 prefixesDelete all matching...
A hash, as fields and values.

Object storage ​

Click a bucket or a folder to open it as a folder tab; the chevron beside it only folds the tree. Clicking an object opens its folder with it chosen.

The tab lists the folders (how many files each holds and their size) and the objects (size, when they changed). Click a folder to go in and the path above to come back up (or cmd-backspace); find narrows the folder by name, and Upload here puts the files you pick into it. The side previews the chosen object (an image, or the first 64 KB of text) with Download, Copy URL (1 hour), Copy Path and Delete...; double-click an object to open it in its own tab.

api/files/uploads/exportsonfeat-loginUpload here
NameSizeModified
2025/61.0 MB214 files
customers.csv3.4 MB2026-09-28 09:15
orders-2026-09-27.json5.1 MB2026-09-27 18:42
report.xlsx60.0 KB2026-09-26 08:00
4 items
orders-2026-09-27.json
[
{ "id": 5120, "status": "paid", "total_cents": 4900 },
{ "id": 5119, "status": "refunded", "total_cents": 1200 }
]
... the first 64.0 KB of 5.1 MB shown
DETAILS
size5.1 MB
content-typeapplication/json
modified2026-09-27 18:42
DownloadCopy URL (1 hour)
Copy PathDelete...
A folder of a bucket. Click a folder, the path above, or a file.
api/files/uploads/avatarsonfeat-loginUpload here
NameSizeModified
user-104.png48.0 KB2026-09-30 10:40
user-109.png51.0 KB2026-09-30 10:12
2 items
user-104.png
DETAILS
size48.0 KB
content-typeimage/png
modified2026-09-30 10:40
DownloadCopy URL (1 hour)
Copy PathDelete...
An image, previewed at the side.

Consoles ​

A console is the editor with SQL highlighting, saved with the session (query 1, query 2, ...). Its tab shows the database it runs against, which the picker in its bar changes. cmd-enter (or Run) runs the selection or the statement at the caret, cmd-shift-enter runs them all, and results show below, up to 500 rows. Edits save as you type.

When a database is missing ​

When the panel cannot reach a database it says why - Database myproject_feat_login does not exist, or Can't reach Postgres at localhost:5432 - with what fits: Create database, Copy from main, Start shared services, Retry, Edit pom.yml, Show full error, Copy error and Fix with Claude.

Made for the checks you run constantly while coding - inspect a row, confirm a migration, tweak a query - right where you work. The workspace's AI agent can list tables and query the same databases over MCP while it works.