myneid / laravel-db-tui
Interactive TUI database browser for Laravel — browse tables, sort columns, inspect rows, and run raw SQL
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- php-tui/php-tui: ^0.2
README
I built this because I kept getting frustrated bouncing between TablePlus, my terminal, and VSCode just to check on data or run a quick query. SSHing into a server to use the MySQL CLI is painful, and switching apps constantly breaks the flow. I wanted something that lived right in the console — where I already spend most of my time — that could handle the things I actually reach for a GUI to do: browse tables, sort, inspect a row, edit a value, run ad-hoc SQL. This is that tool.
An interactive terminal UI for browsing and editing your Laravel application's database — or any remote MySQL, PostgreSQL, or SQLite database — without leaving the command line.
┌─[Tables]──────────────┬─[users] (rows 1–200 of 4312)─────────────────────────┐
│ ▶ users │ id │ name │ email │ created_at│
│ posts │──────┼───────────────┼─────────────────────┼───────────│
│ comments │ 1 │ Alice Nguyen │ alice@example.com │ 2024-01-01│
│ password_resets │ 2 │ Bob Okonkwo │ bob@example.com │ 2024-01-02│
│ sessions │ 3 │ Carol Perez │ carol@example.com │ 2024-01-03│
│ jobs │ 4 │ Dave Müller │ dave@example.com │ 2024-01-04│
└───────────────────────┴───────────────────────────────────────────────────────┘
↑↓/jk: row | Tab/←: tables | Enter: detail | 1-9: sort | PgUp/n PgDn/p: page | s: SQL | q: quit
Features
- Browse any table — scrollable table list on the left, paginated row view on the right
- Sort columns — press
1–9to sort by that column, again to toggle ASC/DESC - Inspect rows — press
Enteron a row to see every field in a word-wrapped key/value layout - Edit and save rows — edit any field in the detail view and write it back to the database
- Raw SQL mode — type and execute arbitrary queries, click any result row to inspect it
- Saved connections — store named connection URLs in
~/.laravel-db-tui.json, outside any repo - Remote connections — connect to any MySQL, PostgreSQL, or SQLite database via URL
- Mouse support — click to select a table or row, scroll wheel to navigate
- Uses Laravel's existing config — zero setup for the default database connection
Requirements
- PHP 8.1+
- Laravel 10, 11, 12, or 13
- A terminal that supports ANSI escape codes (macOS Terminal, iTerm2, Windows Terminal, most Linux terminals)
Installation
composer require myneid/laravel-db-tui
Laravel's package auto-discovery will register the db:tui command automatically. No service provider registration needed.
First time publishing this package? Replace
myneidwith your actual Packagist vendor name incomposer.jsonand in allnamespacedeclarations across thesrc/files.
Quick start
# Open the default database connection configured in .env php artisan db:tui # Done. Use Tab to switch panels, ↑↓ to navigate, q to quit.
Usage
php artisan db:tui [options]
| Option | Description |
|---|---|
--connection=<name> |
Use a named connection from config/database.php (e.g. mysql, pgsql, sqlite) |
--url=<url> |
Connect to a remote database via URL — bypasses Laravel's config entirely |
--save-as=<name> |
Save the --url connection under a name for future use |
--saved=<name> |
Connect using a previously saved connection |
--list-saved |
Print all saved connection names and exit |
--forget=<name> |
Remove a saved connection and exit |
--url format
# MySQL / MariaDB php artisan db:tui --url="mysql://user:password@host:3306/database" # PostgreSQL php artisan db:tui --url="postgres://user:password@host:5432/database" # SQLite (absolute path) php artisan db:tui --url="sqlite:/absolute/path/to/database.sqlite" # SQLite (in-memory, useful for testing) php artisan db:tui --url="sqlite::memory:"
Passwords with special characters should be URL-encoded (e.g. p@ss → p%40ss).
Saved connections
Connection URLs (including passwords) are stored in ~/.laravel-db-tui.json in your home directory. This file is never inside a project, so it cannot be accidentally committed.
# Save a production URL — only needs to be done once php artisan db:tui --url="mysql://user:s3cr3t@prod-host/mydb" --save-as=production # Connect to it by name from any project php artisan db:tui --saved=production # See what's saved (passwords are masked in the output) php artisan db:tui --list-saved # Remove a saved connection php artisan db:tui --forget=production
Key bindings
Tables panel (left)
| Key | Action |
|---|---|
↑ / k |
Move up |
↓ / j |
Move down |
Enter, Tab, → |
Switch focus to the data panel |
s |
Open SQL editor |
q / Ctrl+C |
Quit |
Data panel (right)
| Key | Action |
|---|---|
↑ / k |
Previous row |
↓ / j |
Next row |
Enter |
Open row detail view |
Tab, ←, Shift+Tab |
Switch focus to the table list |
1–9 |
Sort by column N (press again to toggle ASC ↔ DESC) |
PgUp / p |
Previous page |
PgDn / n |
Next page |
Home |
First page, first row |
End |
Last page, last row |
s |
Open SQL editor |
q / Ctrl+C |
Quit |
Row detail view
Fields are word-wrapped. Edited-but-unsaved fields are shown in yellow with a ● marker. The title bar shows [UNSAVED CHANGES] when there is anything pending.
| Key | Action |
|---|---|
↑ / k |
Move to previous field |
↓ / j |
Move to next field |
e or Enter |
Start editing the focused field |
Backspace |
Delete last character (while editing) |
Enter |
Confirm the edit — marks field as dirty |
Esc |
Cancel the current edit without saving |
s |
Save all dirty fields to the database (UPDATE) |
Esc (not editing) |
Back to data panel |
q |
Quit |
Storing null: type the literal word
NULL(uppercase) in the edit buffer and confirm — the field will be written as a databaseNULL.
Saving uses the table's primary key as the WHERE clause when one is detected, and falls back to matching all original column values if no primary key is found.
SQL editor
┌─ SQL Enter: run | Esc: back ────────────────────────────────────────────────┐
│ SELECT * FROM users WHERE email LIKE '%@example.com'█ │
├─ Results (3 rows) ─────────────────────────────────────────────────────────────┤
│ id │ name │ email │ created_at │
│▶ 1 │ Alice Nguyen │ alice@example.com │ 2024-01-01 00:00:00 │
│ 2 │ Bob Okonkwo │ bob@example.com │ 2024-01-02 00:00:00 │
└────────────────────────────────────────────────────────────────────────────────┘
| Key | Action |
|---|---|
| Type normally | Build your query |
Backspace |
Delete last character |
Enter |
Execute the query |
↑ / k |
Move to previous result row |
↓ / j |
Move to next result row |
Esc |
Back to browse mode |
Non-SELECT statements (INSERT, UPDATE, DELETE, CREATE, etc.) are supported — the results panel shows the number of affected rows.
SQL result row detail
Click any result row, or navigate with ↑↓ and press Enter, to open a full word-wrapped key/value view of that row.
| Key | Action |
|---|---|
↑ / k |
Previous result row |
↓ / j |
Next result row |
Esc |
Back to SQL results |
q |
Quit |
SQL result rows are read-only — editing is only available for rows opened from a named table.
Mouse
| Action | Effect |
|---|---|
| Scroll up/down | Navigate rows or tables in the focused panel |
| Click a table name | Select that table and load its data |
| Click a data row | Select that row |
| Click a selected row | Open row detail view |
| Click a SQL result row | Open that row's detail view |
UI panels
Browse mode
┌─ Tables ───────────────┬─ Data / Row detail ────────────────────────────────────┐
│ │ │
│ table list │ paginated rows or single-row key/value view │
│ │ │
└────────────────────────┴─────────────────────────────────────────────────────────┘
help bar
The active panel is indicated by a double-line border (╔═╗) and brackets around the title. Inactive panels use a rounded border (╭─╮).
Row detail (edit mode)
╔═ Row 2 of 4312 [UNSAVED CHANGES] ══════════════════════════════════════════════╗
║ Column │ Value ║
║──────────────┼───────────────────────────────────────────────────────────────── ║
║ id │ 2 ║
║● name │ Bob Okonkwo ← highlighted (focused) ║
║ email │ bob-new@example.com ← yellow (dirty, unsaved) ║
║ created_at │ 2024-01-02 00:00:00 ║
╚══════════════════════════════════════════════════════════════════════════════════╝
e/Enter: edit field s: save Esc: back q: quit (type NULL to store null)
Architecture
The package follows a simple four-layer design:
DbTuiCommand — entry point, terminal setup, event loop
│
├── ConnectionStore — reads/writes ~/.laravel-db-tui.json
│
├── PdoConnection — database abstraction (SQLite / MySQL / PostgreSQL)
│ getTables, getColumns, getRows, getRowCount
│ getPrimaryKey, updateRow
│ executeRaw
│
├── App — all state + input event handling
│ Mode enum: Tables | Data | Row | Sql | SqlRow
│
└── Renderer — converts App state → php-tui widget tree (read-only)
State machine (App): The Mode enum drives which key bindings are active and which panel is rendered. State transitions are:
Tables ⇄ Data → Row (edit + save)
↘
Sql → SqlRow (read-only detail)
Sql is reachable from Tables or Data; Esc always returns to Tables.
Editing (App): Row detail tracks editFieldIndex, isEditing, editBuffer, and dirtyValues. Confirming an edit stages the change in dirtyValues; pressing s flushes all staged changes in a single UPDATE statement and refreshes the page.
Rendering (Renderer): Stateless — called every frame (~60 fps), reads App and builds a GridWidget tree. php-tui diffs the widget tree against the previous frame before writing to the terminal.
Database (PdoConnection): Raw PDO only — no Eloquent, no query builder. Identifiers are quoted per-driver to prevent injection from schema-derived names. Raw SQL mode executes user input directly (intentional for a developer tool). updateRow() uses the primary key as the WHERE predicate when available.
Customising
Page size
The default page size is 200 rows. To change it, set $app->limit before calling $app->init() in DbTuiCommand::handle():
$app = new App($db); $app->limit = 500; $app->init();
Adding a connection type
PdoConnection handles the three standard drivers. To add another (e.g. SQL Server via sqlsrv), extend PdoConnection and override getTables(), getColumns(), getPrimaryKey(), and qi().
License
MIT — do whatever you want with it.