All projects
In Progress · since October 2026 ·
next.jstypescriptpostgresqlbetter authshadcn/ui

portable-admin

One admin dashboard for all my small Postgres apps - connected in the UI, every change staged in a cart and applied with 2FA.


portable-admin

One admin for every app.

portable-admin is a single admin dashboard for all my small Postgres apps - Dönerbuch, Watchly, LapBattle and whatever comes next. It’s deployed once, and apps are connected in the UI by their database URL. It reads each app’s schema live, so a new app needs no code: every table can be browsed, searched, edited and deleted, and every change is logged and can be undone.


What it is

  • Connect, don’t deploy. Add an app with its database URL, test the connection, and its tables show up. No per-app admin to build or host.
  • Every table, generically. Search (accents folded, so “doner” finds “Döner”), filters, sorting, row pages with what references them, edit, delete, merge, notes and per-row history.
  • A cart for every write. Nothing writes directly. Every change is staged, previewed against the data as it is now, and only applied with a fresh 2FA code.
  • Undo for any schema. Every batch is journaled, cascades included, and can be undone - whatever the table’s primary key looks like.
  • Mods. Generic features an app switches on when its tables fit: Charts, Duplicates, Map, Moderation, Leaderboard, Casino and Economy.

Why it exists

Every small app needs an admin eventually - to fix a typo in a place name, ban a user, merge two duplicates or undo a mistake. Building one per app means the same pages over and over, each with its own login and its own security holes, and most of them never get built at all.

portable-admin builds it once. And because it holds write access to every connected app, it’s strict about it: one account, required 2FA, and no write without a fresh code.


Core features

FeatureWhat it does
🛒 CartEvery change waits here, grouped by app and previewed again before applying. Apply needs a TOTP code that’s accepted only once.
↩️ Undo & redoAny applied batch can be undone - re-inserting cascaded rows, restoring old values - and refuses if the data has moved on since.
🗂️ Tables & rowsBrowse, search, filter, bulk-edit and delete every table, with a dashboard of new rows per day and week.
👤 UsersFor apps on Better Auth: providers, sessions, sign out everywhere, and ban or lift a ban.
🪣 FilesWith S3: the bucket by folder, which row uses each file, and a finder for unused files.
🧩 ModsCharts, duplicates, a map, a moderation queue, leaderboards, casino and economy views - mapped to the app’s own tables.
⌨️ Command paletteCtrl/Cmd+K searches every table of an app at once.
📱 PhonesThe sidebar becomes a sheet and every table a list.

Vision

The core covers any app with a Postgres database. What comes next is more mods for patterns that show up across apps, and connecting each new app as it ships instead of building it an admin of its own.


Technical details

Stack

LayerChoice
FrameworkNext.js 16 (App Router), React 19
LanguageTypeScript
UIshadcn/ui (Radix), Tailwind CSS v4
Databasepostgres.js, straight to each app’s Postgres
AuthBetter Auth with required TOTP 2FA
Validationzod
DeploymentNixpacks on Coolify

Engineering decisions

  • Every write is a command. A command has a name, a zod schema and what it does. The UI only stages it; it runs only when the cart is applied with a 2FA code. A stolen session can read and stage, but not change anything.
  • A journal that commits with the change. Each app’s batch runs in one transaction together with its journal entry in the app’s own portable_admin schema - which is what makes undo safe.
  • Deletes include the cascade. The journal walks the foreign keys, snapshots and locks every row ON DELETE CASCADE will take, and stops before anything happens if a reference has no delete rule.
  • Rows named by their primary key, whatever it is. uuid, serial, text or composite - no id column is assumed anywhere in the core.
  • Identifiers only from the schema. Table and column names come from the introspected schema, never from a request; values are always parameters.
  • The app owns its schema. The admin never changes an app’s tables. Its own data lives in separate schemas, and app database URLs and S3 keys are encrypted with AES-256-GCM under a key that only lives in the environment.
  • Hidden means hidden. Columns that look secret never leave the server and can’t be written.

Get & use portable-admin

Deploy

git clone https://github.com/elRatto21/portable-admin.git
cd portable-admin
pnpm install
cp .env.example .env.local
pnpm dev

Set ADMIN_DATABASE_URL, ADMIN_ENCRYPTION_KEY, BETTER_AUTH_SECRET, BETTER_AUTH_URL and ADMIN_SETUP_TOKEN (see .env.example). On Coolify, deploy with Nixpacks and attach the app to each connected app’s network.

First run

  1. Open /setup, enter the setup token, your email and a password.
  2. Scan the QR code with your authenticator app and keep the backup codes.
  3. Under Apps → Connect an app, add a database URL and test the connection - ideally with a dedicated database role for the admin.

portable-admin - one admin for every app.