A free, fast desktop database client. Tauri 2 + React + TypeScript + Rust.
New here? Take the 10-minute tour: docs/DEMO.md.
Ready-made packages are on the Releases page
(Linux x86_64 only for now; Windows and macOS are planned: #21, #20). They are built on
Ubuntu 22.04 by .github/workflows/release.yml, so they run on Ubuntu 22.04, Debian 12
and newer (glibc 2.35), and each release is smoke-tested on Ubuntu 24.04.
- Ubuntu / Debian —
.deb(TableMax_<version>_amd64.deb, Debian package nametable-max):sudo apt install ./TableMax_<version>_amd64.deb. apt pulls in WebKitGTK 4.1 and GTK 3 from the distribution. Runtablemaxor use the "TableMax" desktop entry (category Development). Remove withsudo apt remove table-max. - Other distributions — AppImage (
TableMax_<version>_amd64.AppImage), bundles WebKitGTK, GTK and their libraries (X11/Wayland, fonts and Mesa EGL/GL come from the system, as for any AppImage):chmod +x TableMax_*.AppImage && ./TableMax_*.AppImage. It needs FUSE 2 (libfuse2t64on Ubuntu 24.04,libfuse2on 22.04,fuse-libson Fedora); without it, run it withAPPIMAGE_EXTRACT_AND_RUN=1. Its GTK hook picks a light or dark Adwaita theme from the desktop'scolor-schemesetting, and the app's "System" theme follows that;APPIMAGE_GTK_THEME=Adwaita:darkforces dark. - Check a download:
sha256sum -c SHA256SUMS --ignore-missing.
pnpm build:app (tauri build) writes src-tauri/target/release/bundle/deb/*.deb and
bundle/appimage/*.AppImage. Neither bundles its own libc: they need a glibc at least as
new as the build machine's (an AppImage built on Ubuntu 26.04 needs glibc 2.43 and fails on
24.04 with version `GLIBC_2.43' not found). Build on the oldest distribution you want to
support. Check a build with bash scripts/release-smoke.sh glibc 2.35 <package>…,
… deb <file.deb> (clean ubuntu:24.04 container) and … appimage <file.AppImage> (Xvfb).
With Claude Code: /release [version] (or "make a release") runs the steps below, proposes
the version from the commits since the last tag, and asks before pushing the tag. By hand:
bash scripts/release-bump.sh <version>sets the version inpackage.json,src-tauri/tauri.conf.json,src-tauri/Cargo.tomlandsrc-tauri/Cargo.lock; commit it through a normal issue and PR (that PR also runs the Release build and smoke tests).- Once it is merged:
git tag -a v<version> origin/main -m "TableMax <version>" && git push origin v<version>(v<version>-beta.1and the like make a prerelease). - The Release workflow checks that the tag matches the three versions, builds, smoke-tests
and publishes the GitHub Release with both packages,
SHA256SUMSand generated notes. A failed run publishes nothing: fix, delete the tag, tag again. Then check the published files:gh release download v<version>andsha256sum -c SHA256SUMS.
Runtime notes: saving passwords in the keyring needs a Secret Service provider
(GNOME Keyring or KWallet, present on standard desktops); without one, choose "Ask each
time" for passwords. App data (connections, settings.json, the app's known_hosts, saved
queries) lives in ~/.local/share/io.github.cemoche.tablemax/ (the app identifier). Builds
made before the identifier changed (#49, before the first release) used
~/.local/share/dev.tablemax.app/: its content is moved at the first start of a newer build,
and SQLite connections to files inside it follow. Saved passwords stay
under the keyring service dev.tablemax.app.
The app icon is drawn for TableMax (assets/icon/tablemax.svg); pnpm icons regenerates
src-tauri/icons/ from it.
Prerequisites (Ubuntu): libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev, Node 22, pnpm, Rust, Docker.
pnpm install
pnpm dev # tauri dev
pnpm test # frontend tests (vitest)
pnpm test:rust # starts test databases, runs cargo tests
pnpm lint # tsc + clippyPick an issue with no assignee, assign yourself, work on a branch issue-<n>-<slug> and
open a pull request to main with Closes #<n>; the owner merges. Setup, workflow and
rules (for people and their Claude Code agents) are in CONTRIBUTING.md;
agent rules in CLAUDE.md.
TableMax is free software: you can redistribute it and modify it under the terms of the GNU General Public License, version 3 or (at your option) any later version. It is distributed without any warranty. See LICENSE for the full text.
The connections screen shows a welcome pane (Create connection…, Import
connections…) and the list of saved connections, filed in groups that fold. Ctrl+F
searches the list (name, group, host, database, path); the arrow keys move through the
rows, Enter or a double-click connects, right-click or Shift+F10 opens Connect /
Edit…. The form opens in place of the list (+, Ctrl+N, Create connection…,
Edit…; ← goes back, and unsaved edits are never dropped without asking). From an
open connection, Ctrl+Shift+K shows the list again.
- SSL / TLS: modes
disable,prefer,require(encrypted, certificate not checked),verify-ca(chain checked, not the name; PostgreSQL only for now) andverify-full(chain and host name). Optional CA, client certificate and key files. New connections default toverify-fullfor remote hosts andpreferfor local hosts or SSH tunnels. - SSH tunnels: password, private key (passphrase asked when the key is encrypted) or
SSH agent.
~/.ssh/configaliases are resolved (HostName,User,Port,IdentityFile). An unknown host key is shown as aSHA256:…fingerprint to confirm, then saved in the app'sknown_hosts; a changed key blocks the connection. - MongoDB (experimental, shown only with
TABLEMAX_EXPERIMENTAL=mongodb): MongoDB 4.4 or newer (older servers are refused with an explanation). TLS modesdisable,requireandverify-full, checked against the bundled Mozilla roots or the CA file (not the system store: a server signed by a company CA needs its CA file). Through an SSH tunnel only one host, withoutmongodb+srv://,replicaSetorverify-full. - Groups: connections can be filed under a group; groups fold in the connection list.
- URLs: "Copy URL" in the form (never includes the password) and "Import from URL…" in the form
(
postgresql://,mysql://,mariadb://,sqlite://;sslmode,ssh,ssh-authparameters). - Import connections… (on the connections screen): reads a
.tableplusconnectionfile with its export password, lists its connections (duplicates of existing ones are unchecked) and imports the chosen ones. Exports do not carry SSH key paths, SSL settings or colors: set them afterwards in the connection form.
Passwords, SSH passwords and key passphrases live only in the system keyring (or are
asked at each connect), never in app.db.
- Toolbar (it is also the title bar; a setting brings back the system one): sidebar and
connection buttons; Discard · Preview · Commit for the pending changes of a table
tab; safe mode, database (
Ctrl+K) and SQL (new SQL tab); in the middle the status pill (tag badge such asPROD, engine and version,SSHthrough a tunnel, connection, database,schema.object; click it for the connection details, Edit, Reconnect, Disconnect); reload (Ctrl+R), Open anything (Ctrl+P) and the row details panel. Aproductionconnection also shows[PROD]in the window title. - Open connections, the narrow column on the left: one tile per open connection, in
its colour (
Ctrl+Shift+[/Ctrl+Shift+]switch); the settings button sits at its bottom. - Sidebar (
Ctrl+0shows or hides it; drag its edge to resize): Items (tables, views and functions of the schema; a table or view's chevron lists its columns), Queries (saved queries) and History, each with its search (Ctrl+Shift+F). Single-click a table for a preview tab (italic, reused), double-click to keep it. At the bottom: + (new SQL tab), ⌄ (New SQL query, Refresh) and the schema picker. - Tabs: capsules sharing the bar, back / forward arrows (
Alt+←/Alt+→), the list of all tabs (⌄). Right-click a tab for New tab / Close / Close others / Rename… (F2). - Open anything (
Ctrl+P): tables, views, tabs and connections; type>first for the actions (each with its shortcut).
Opening a table shows its rows in a grid (300 rows a page). The bar under the grid holds
Data | Structure | Index (Alt+1 / Alt+2 / Alt+3), + Row, the count in the
middle (1–300 of 1,250 rows, 415 rows, or 1 of 415 rows selected), Filters,
Columns and the pager ‹ ⚙ › (⚙ sets the offset and the page size).
-
Grid: click a row number to select the row,
Shift+click to extend,Ctrl+click to add or remove a row; click a header to sort (ascending, descending, none), drag it to move the column, double-click its right edge to fit the width. Right-click a header for Filter with column, Hide column, Fit to content, Reset positions, Show all, Sort and Copy column name.Entershows the whole value of a cell (Quick Look, JSON indented). -
Editing: double-click a cell, press
Alt+Enteror start typing. Right-click a cell for Set value (NULL, EMPTY, DEFAULT for new rows), Revert cell, copies, Quick filter, Add row, Duplicate, Delete / Restore. Edits are not written right away: changed cells, new rows and rows to delete are highlighted as pending changes. Only tables with a primary key (or a unique index onNOT NULLcolumns) can be edited; views, tables without such a key, generated and binary columns are read-only, with the reason shown above the grid. SQL results are always read-only. -
Pending changes panel (below the grid): one line per change with its SQL (
UPDATE/INSERT/DELETE, values shown as literals) and an Undo button. Preview (Ctrl+Shift+P) opens it. -
Commit (
Ctrl+S) writes all pending changes in one transaction, values sent as bound parameters; the toolbar's Commit shows how many are pending. EachUPDATE/DELETEmust touch exactly one row: if the row was deleted or its key changed since it was loaded, everything is rolled back, nothing is written and the changes stay pending. Concurrent edits of other columns are not detected: the last write wins (like other desktop clients). A commit waiting for a row locked elsewhere gives up after 30 s (a normal failure, nothing written) and can be cancelled meanwhile (Cancel, everything rolled back). If the connection drops during a commit, the outcome is unknown: reload and check before committing again. Discard drops them (Ctrl+Shift+Backspaceasks first); closing a tab or workspace that has some asks first. Paging, sorting, filtering, reloading and following a foreign key wait until the pending changes are committed or discarded. -
Row details (
Ctrl+Space, orSpacein the grid): a panel on the right with the selected row's fields, a field search, indented JSON and long text loaded in full; edits there are pending changes too. It stays open across tabs; drag its edge to resize. -
Filters (
Ctrl+For Filters): a bar above the grid, one line per condition: a checkbox, the column (or Any column, or Raw SQL), the operator, the value, Apply (this condition alone), − and +. Apply All applies every ticked condition, joined by AND, or by OR from its ⌄ menu (Ctrl+Enter/Ctrl+Shift+Enter). The bar's footer has SQL, which shows theWHEREclause with the values written in (display only: the page always sends them as parameters; Copy copies it), Clear and the bar's shortcuts.Eschides the bar and keeps what is typed. A Raw SQL condition reads the quick syntax first, for examplestatus = delivered and total > 100:col = value(also!=,<>,<,>,<=,>=),col is null,col is not null;col in (a, b, c),col not in (…),col between 1 and 10,col not between …;col like 'A%',col not like …,col ilike …,col contains text,col not contains …;any contains text(alsoany = …,any not contains …,any like …) searches every column;- quote values with
'…'or"…"(the quote doubled escapes it:'O''Brien'), and columns with"…"or`…`; a keyword used as a value must be quoted ('and').
Anything else is sent as SQL as typed (only there is your text inserted in the query); the line says how it was read. With filters, the count is exact up to 500,000 rows, then shown as
500,000+. -
Columns (
Alt+F): tick the columns to show; widths, order and hidden columns are kept per table for the session. -
Foreign keys: a cell that references a row has a
↗marker.Ctrl+clickit, or right-click → Open referenced row (or the→ tablebutton in the row details), to show the referenced table filtered on that row in the same tab. The trail above the grid (orders › customers (id = 42)) goes back to any earlier table exactly as it was (filters, sort, page);Alt+←goes back one step. -
Copy:
Ctrl+CorCtrl+Shift+Ccopies the selected cells as TSV; right-click → Copy rows / Copy rows as TSV, CSV, CSV with a header, JSON, Markdown or SQLINSERT, or Copy selected cells (shown values, pending edits included). -
Structure (read-only): Columns (type, nullable, default, auto-increment or generated, comment; primary and foreign key columns marked), Foreign keys (columns → referenced table and columns,
ON UPDATE/ON DELETE; the table name opens it in a new tab) and DDL (highlighted, Copy button). Index lists the indexes (columns, unique, type and condition of a partial index; MySQL gives no type or condition). Refresh reloads them from the database.
- Toolbar above the editor: No limit ⌄ (add a
LIMITtoSELECTqueries, the value comes from the settings), Beautify (Ctrl+I, alsoCtrl+Shift+F: the selection or all of it), Save, and Run Current (Ctrl+Enter: the statement under the cursor, or the selection) with its ⌄ for Run All (Ctrl+Shift+Enter). The statement Run Current would run has a tinted background. - Results: Data (the rows, one Result N tab per result set when there are
several, with the row count and the time) and Message (one line per statement: rows
returned or changed, errors, cancel). An error or a cancel shows Message. The row
details panel (
SpaceorCtrl+Spacein the results grid, or the toolbar's right-panel button) shows the selected row of the result set on screen, read-only. - Completion: table and column names as you type;
Ctrl+Spaceasks for it. - History (sidebar, History): every statement run on a connection, newest first (the
last 1,000 are kept), with a search box. A click opens it in a new SQL tab; right-click for
Run, Copy, Insert into SQL editor and Delete. Statements containing the word
IDENTIFIEDorPASSWORD(CREATE/ALTER USER … IDENTIFIED BY,SET PASSWORD, PostgreSQL… PASSWORD '…') are never recorded. It may still hold other values you typed: "Clear history" erases it for the connection (deleted rows are overwritten inapp.db), and it is deleted with the connection. - Session restore: open workspaces and tabs come back after a restart, disconnected
until you activate them (
Ctrl+Shift+Rconnects the one shown). The text of each SQL tab is kept as typed, like an unsaved document, so whatever you typed there (a password included) is stored inapp.db. On Linux and macOSapp.dband the saved queries are readable by your user only. - Saved queries (sidebar, Queries, per connection):
Ctrl+Sasks for a name the first time, then overwrites; the tab takes the name and shows a dot while it differs from the saved file. A click opens it (or its open tab); right-click for Run, Copy, Insert into SQL editor, Rename… and Delete. - MySQL / MariaDB
DELIMITER: aDELIMITER //line changes the statement delimiter like in the mysql client and is not sent to the server. Without it, theBEGIN … ENDbody ofCREATE PROCEDURE/FUNCTION/TRIGGER/EVENTis still sent as one statement. Formatting is not available for a script withDELIMITERlines. - MySQL / MariaDB single-statement guard: each statement is checked before it is sent,
and anything the check cannot read with certainty is refused rather than sent. So some
valid scripts do not run as is: compound statements inside executable comments (the
/*!50003 CREATE TRIGGER … BEGIN … END */blocks ofmysqldumpoutput), dollar-quoted bodies (AS $$ … $$, MySQL 9 JavaScript routines), and anything at all while the session's client character set isbig5,cp932,gb18030,gbkorsjis(runSET NAMES utf8mb4to go back).
Settings (Ctrl+,, or the button at the bottom of the open connections column) has
five sections: General (system title bar, version), Table (page size, exact count
threshold), Editor (Auto LIMIT), Fonts & Themes (System / Light / Dark, Interface
size 90 % to 130 %, data and editor font sizes, editor font) and Keymap, where the
shortcuts below can be changed (Change, then press the new combination; one already in
use is refused) or reset. The grid's plain keys are fixed: Enter (Quick Look), Space
(row details), Delete (delete rows), Esc and Ctrl+click.
The defaults, on Linux and Windows. On macOS Ctrl is ⌘ and Alt is ⌥, except
Ctrl+Tab, Ctrl+Shift+Tab and Ctrl+Space, which stay Ctrl; the other macOS
differences are in the last column.
| Where | Shortcut | Action | macOS |
|---|---|---|---|
| Everywhere | Ctrl+P |
Open anything (> for actions) |
|
Ctrl+K |
Open a database or schema | ||
Ctrl+Shift+K |
Show the connection list | ||
Ctrl+N |
New connection | ||
Ctrl+, |
Settings | ||
Ctrl+T / Ctrl+E |
New SQL tab / open the SQL editor (new SQL tab) | ||
Ctrl+W |
Close the tab | ||
Ctrl+Tab / Ctrl+Shift+Tab |
Next / previous tab, most recent first | ||
Ctrl+] / Ctrl+[, Ctrl+PageDown / Ctrl+PageUp |
Next / previous tab in bar order | ||
Ctrl+1, Ctrl+2, Ctrl+3, Ctrl+4, Ctrl+5, Ctrl+6, Ctrl+7, Ctrl+8, Ctrl+9 |
Go to tab 1 … 9 | ||
Ctrl+Shift+] / Ctrl+Shift+[, Ctrl+Shift+PageDown / Ctrl+Shift+PageUp |
Next / previous open connection | ||
Alt+← / Alt+→ |
Back (the foreign key trail first) / forward | ||
Ctrl+0 |
Show or hide the sidebar | ||
Ctrl+Shift+F |
Search the sidebar (in the editor: format) | ||
Ctrl+Shift+H |
Show the query history | ||
Ctrl+R |
Reload the sidebar and the table data | ||
Ctrl+Shift+R |
Connect the disconnected connection shown | ||
Ctrl+= (Ctrl+Shift+=, Ctrl+Num +) / Ctrl+- (Ctrl+Num −) |
Larger / smaller data and editor fonts | ||
| Table tabs | Ctrl+S |
Commit the pending changes | |
Ctrl+Shift+Backspace |
Discard the pending changes (asks first) | ||
Ctrl+Shift+P |
Preview the SQL of the pending changes | ||
Ctrl+Space, Space in the grid |
Show or hide the row details | ||
Alt+1 / Alt+2 / Alt+3 |
Data / Structure / Index | also ⌘+⌥+[ / ⌘+⌥+] |
|
Alt+R |
Reload this table's data | ⌘+⌥+R |
|
Ctrl+F |
Show or hide the filter bar | ||
Alt+F |
Columns | ⌘+⌥+F |
|
Ctrl+I |
Add a row | ||
| Table grid | Enter / Alt+Enter |
Quick Look / edit the cell | |
Ctrl+Shift+C |
Copy the selected cells | ||
Ctrl+D |
Duplicate the selected rows | ||
Ctrl+Backspace, Delete |
Delete the selected rows | ||
Ctrl+← / Ctrl+→ |
Previous / next page | ||
Ctrl+click |
Follow a foreign key | ||
| Filter bar | Ctrl+B |
Turn the condition on or off | |
Ctrl+I / Ctrl+Shift+I |
Add a condition after this one / remove it | ||
Ctrl+Enter / Ctrl+Shift+Enter |
Apply all, joined by AND / by OR | ||
Ctrl+↑ / Ctrl+↓ |
Previous / next condition | ||
Alt+C / Alt+O |
Jump to the column / the operator | ⌘+← / ⌘+→ |
|
Esc |
Hide the bar | ||
| SQL tabs | Ctrl+S |
Save the query | |
Space, Ctrl+Space in the results grid |
Show or hide the row details | ||
| SQL editor | Ctrl+Enter / Ctrl+Shift+Enter |
Run the current statement (or the selection) / all | |
Ctrl+I, Ctrl+Shift+F |
Format (Beautify) | ||
Ctrl+/ |
Comment or uncomment the lines | ||
Ctrl+Space |
Show completions | ||
| Connection list | Ctrl+F |
Search the list |
A shortcut acts only where it belongs: in a text field or the editor, keys such as
Ctrl+←, Ctrl+I or Space keep their usual text meaning.
Letters and punctuation follow the character your layout types, and the digit row stays
digits: on QWERTZ Ctrl+/ is Ctrl+Shift+7; on AZERTY Ctrl+, is the "," key and
Ctrl+- is the ")" key or the keypad's − (the "-" key is Ctrl+6). Ctrl and a "+" key
typed without Shift enlarge the fonts (QWERTZ, Nordic layouts); on QWERTZ that key is no
longer Ctrl+], so the next tab is Ctrl+Tab or Ctrl+PageDown. On a non-Latin layout
(Russian, Hebrew, Greek…) keys go by their US legend, as in browsers: Ctrl+/ is the "/"
key. TableMax tells the layout from the last letter you typed, so right after switching
layouts type one letter before using a punctuation shortcut. The keypad's Enter works as
Enter.
Host tools needed by pnpm test:rust: docker compose (v2), openssl and ssh-keygen
(test certificates and keys), ssh-agent and ssh-add (agent authentication test);
pnpm demo:db also needs sqlite3. Bind mounts carry the SELinux z option, so
Fedora/RHEL hosts work as is.
scripts/test-dbs.sh up|down|env manages PostgreSQL 16 (port 54321), MySQL 8.4 (33061),
MariaDB 11.4 (33062), MongoDB 7.0 (27117; users root and reader, read only on
tablemax_test) and a test sshd (2222, user tunnel) via docker-compose.test.yml
(project tablemax-test). up first runs scripts/test-secrets.sh, which generates the
throwaway TLS certificates and SSH keys into .test-secrets/ (gitignored; existing keys
are kept, missing ones are added). scripts/test-dbs.sh env prints the TABLEMAX_TEST_*
exports, and scripts/test-dbs.sh down stops the containers and deletes their volumes.
The test sshd host keys (Ed25519 and ECDSA) are generated once in .test-secrets/ssh/host/,
so their fingerprints survive image rebuilds. pnpm demo:db seeds a demo database and
registers demo connections.
Each checkout has its own .test-secrets/. To run the tests of two worktrees at the same
time, give the second one its own compose project and host ports (the defaults are
tablemax-test, 54321, 33061, 33062, 2222 and 27117), and keep them set for every
command:
export TABLEMAX_TEST_PROJECT=tablemax-test-b TABLEMAX_TEST_PG_PORT=55321 \
TABLEMAX_TEST_MYSQL_PORT=34061 TABLEMAX_TEST_MARIADB_PORT=34062 TABLEMAX_TEST_SSH_PORT=3222 \
TABLEMAX_TEST_MONGO_PORT=27217
pnpm test:rust # `env` then exports URLs with these ports
scripts/test-dbs.sh down # stops this project onlypnpm demo:db follows the same variables.