# What Bobby Tables does

Bobby Tables is a desktop database client and SQL GUI for macOS and Linux. It connects to PostgreSQL, MySQL, MariaDB and SQLite, browses the catalog, edits rows in a grid with the generated SQL shown before it runs, runs SQL with autocomplete from the live catalog, draws the schema as an entity relationship diagram, and dumps a database to a SQL script the database's own client replays. The same window holds a dual-pane file manager for S3-compatible object storage, SFTP servers and local folders, so a bucket and a database sit in one list of connections. There is no server to run and no account to create, secrets stay in the operating system keychain, and the app sends no telemetry.

## Connections

One list covers databases and file storage. A database client entry and an S3 bucket are the same kind of thing here: a saved connection with a name, a color and a group, opened in a workspace window.

### Connection form

Add a connection by filling in host, port, user and database, or by pasting a connection URL the form fills itself from.

Each engine shows only the fields it uses. SQLite asks for a database file, S3 asks for a bucket and an endpoint, and SFTP accepts an sftp:// URL.

Test connection reports the server version and the round trip in milliseconds beside the button, before the connection is saved and before it has a name.

Default hosts are placeholders rather than values, so a remote host goes in without clearing a local address first.

_Since 0.1.0 · Free trial_

### Connection groups, colors and environments

Saved connections carry a group, a color and an environment tag, and the production tag tints the window chrome.

The welcome screen lists connections by group. The color travels with the connection into the status bar of every workspace it opens.

The environment tag is a separate field from the name, so a production database announces itself in the window rather than in a naming convention.

_Since 0.1.0 · Free trial_

### SSH tunnels

Reach a database through a bastion host with a key file, a password or ssh-agent, without running an ssh command beside the app.

Host aliases, users, ports, identity files and one ProxyJump hop are read from ~/.ssh/config, so a host already reachable from a terminal is reachable here.

Host keys are checked against ~/.ssh/known_hosts, with a confirmation the first time a host is seen and a refusal when a known key changes.

Where a tunnel needs the system client instead, the connection can spawn ssh -N -L rather than using the built-in SSH stack.

_Since 0.1.0 · Free trial_

### TLS options

Each server connection carries an SSL mode and optional certificate files, set in the connection form rather than in a URL parameter.

The modes are Disable, Allow, Prefer, Require, Verify CA and Verify full. New server connections default to Prefer.

A CA certificate applies to the verifying modes; a client certificate and client key apply to every mode except Disable.

_Since 0.1.0 · Free trial · PostgreSQL, MySQL, MariaDB_

### Read-only connections

A connection marked read-only opens a read-only session on the server and disables editing in the interface.

PostgreSQL sessions set default_transaction_read_only, MySQL sessions set a read-only transaction, and SQLite opens the file in read-only mode.

The interface disables editing as well, so a mistake is refused twice rather than relying on either half alone. The status bar carries a read-only badge.

_Since 0.1.0 · Free trial_

### Keychain secrets

Database passwords, S3 secret keys and SSH key passphrases go to the operating system keychain, never to a configuration file.

Connection definitions live in connections.json in the app config directory; the secret fields are stored separately in the macOS Keychain or the Linux Secret Service.

Where no keychain is available the connection form says so and the app asks for the secret on connect instead of saving it somewhere weaker.

_Since 0.1.0 · Free trial_

### Workspaces and windows

Several connections stay open at once, each in its own workspace, and workspaces can be split across windows.

Switch connection is Cmd+Shift+K on macOS and Ctrl+Shift+K on Linux; switch database is Cmd+K or Ctrl+K; a new window is Cmd+Alt+N or Ctrl+Alt+N.

Closing a workspace checks for uncommitted edits first, and opening one shows progress rather than a frozen switcher.

_Since 0.2.0 · Free trial_

### Session restore

The app offers to restore the connections, tabs and panes that were open when it last closed.

The offer never blocks the window: searching, hovering and refreshing carry on while it is on screen, and starting anything new begins a fresh session instead.

A restored database session that has expired is reconnected before the first read, and a restored file pane finishes reconnecting before its first listing.

_Since 0.1.0 · Free trial_

### Encrypted connection export and import

Move a set of connections to another computer as one file, encrypted with a password you choose.

Export connections and Import connections are on the welcome screen. Passwords are included only when you ask for them; otherwise the file carries definitions alone.

An import is checked as a whole before anything is written, and its preview says how many connections still fit under the free trial cap.

_Since 0.1.0 · Free trial_

## SQL editor and results

The SQL editor is a first-class half of the app rather than a box beside the grid. It knows the catalog it is connected to, and results are held in Rust and paged into the window instead of arriving as one enormous payload.

### SQL editor

A CodeMirror editor with syntax highlighting for the connected dialect, multiple cursors, comments and formatting.

Cmd+Enter or Ctrl+Enter runs the current statement or the selection; Cmd+Shift+Enter or Ctrl+Shift+Enter runs everything in the tab.

Cmd+I or Ctrl+I reformats the statement and Cmd+Shift+I or Ctrl+Shift+I collapses it back to one line. Cmd+/ or Ctrl+/ comments a line.

A server error is reported at its position in the editor, with the database's own message, code, hint and detail rather than a summary of them.

_Since 0.1.0 · Free trial_

### Autocomplete from the live catalog

Completion offers the schemas, tables, views and columns of the database you are connected to, not a generic keyword list.

Suggestions are labelled with what they are, so a table and a column of the same name are told apart in the list.

The catalog behind it is the same one the sidebar loads, so a table created in another tab appears after a refresh rather than a reconnect.

_Since 0.1.0 · Free trial_

### Directive autocomplete

Typing two underscores inside an AS alias suggests display directives, their arguments, enum names and goto targets, one segment at a time.

Completion inserts only the missing segment and leaves the rest of the alias and its quotes alone.

An alias the app cannot honor is underlined while it is being typed, so a bad directive is a squiggle in the editor rather than a warning on the results.

The alias must use an explicit AS. Aliases without it get neither completion nor diagnostics.

_Since 0.10.0 · Free trial_

### Query history and saved queries

Every statement the app runs is recorded, and a query worth keeping is saved by name and reopened from the sidebar.

History and Saved are tabs in the sidebar. Cmd+Shift+H or Ctrl+Shift+H focuses history; Cmd+Shift+S or Ctrl+Shift+S saves the current statement.

Both live in a SQLite file in the app data directory on this computer. Nothing is uploaded and nothing is synchronized.

Cmd+P or Ctrl+P opens anything by name, saved queries and database objects together.

_Since 0.1.0 · Free trial_

### EXPLAIN and EXPLAIN ANALYZE

Run the current statement through the server's planner without rewriting it by hand.

Cmd+Shift+E or Ctrl+Shift+E explains the statement; adding Alt runs the analyzing form, which executes it.

The output arrives as an ordinary result set, so it can be read, copied and exported like any other.

_Since 0.1.0 · Free trial_

### Paged results

Result sets stay in Rust and the window asks for pages of 500 rows, so a large result does not stall the interface.

A result is collected up to 100,000 rows or 256 MB, whichever comes first, and says so when it was truncated rather than pretending it is complete.

A script that returns several result sets keeps them all, and a cell whose type has no decoder shows a placeholder instead of failing the row around it.

The grid virtualizes rows and columns, so scrolling a wide table holds a few hundred cells in the window at a time.

_Since 0.1.0 · Free trial_

### Query cancellation

A running statement is cancelled from the window with Cmd+. on macOS or Ctrl+. on Linux.

PostgreSQL is cancelled out of band on a second connection, MySQL with KILL QUERY from a spare connection kept for it, and SQLite through an interrupt handle.

The handle is captured before the statement is dispatched, so cancelling never has to wait for the connection it is cancelling.

_Since 0.1.0 · Free trial_

## Browse and edit

The core loop of a database GUI: open an object, read the grid, change cells, read the SQL the change will run, then commit it. Nothing reaches the server until you commit.

### Catalog tree

Schemas, tables, views, materialized views, functions and sequences in the sidebar, with a filter box and optional row estimates.

Row counts come from the server's own estimate above a threshold and from an exact count below it, so a large table does not cost a full scan to display.

Single-click opens an object in a preview tab that the next single-click replaces; double-click or an edit pins it.

_Since 0.1.0 · Free trial_

### Table data

A table opens as pages of 500 rows with sorting on a header click and a filter bar that shows the SQL it will run.

Cmd+F or Ctrl+F opens the filter bar: rows of column, operator and value, combined with AND, with an SQL preview above the grid.

NULL renders as an italic NULL and never as an empty string, and a long text, JSON or binary cell opens in a Quick Look popup.

Cmd+Ctrl+[ and Cmd+Ctrl+] switch between Data and Structure on macOS.

_Since 0.1.0 · Free trial_

### Inline row editing

Change a cell in the grid, add a row, duplicate one or delete a selection, without writing an UPDATE statement.

Cmd+I or Ctrl+I adds a row, Cmd+D or Ctrl+D duplicates one, and Backspace or Delete removes the selected rows.

Cmd+Shift+N sets a cell to NULL and Cmd+Shift+D sets it to DEFAULT, so both stay values you can choose rather than an empty box.

A table with no primary key and no unique index is read-only in the grid, with a banner saying why rather than a silent failure on commit.

_Since 0.1.0 · Free trial_

### Pending edits and generated SQL

Edits collect in a pending buffer, and the exact statements they will run can be read before anything is sent.

Cmd+Shift+P or Ctrl+Shift+P shows the generated SQL. Cmd+S or Ctrl+S commits the buffer in one transaction; Cmd+Shift+Backspace discards it.

Numbers and booleans are written unquoted, NULL is written as NULL, and identifiers and literals are quoted for the dialect in front of you.

The same generator backs Copy as SQL INSERT, so what you copy out is what the app would have run.

_Since 0.1.0 · Free trial_

### Safe mode and destructive-statement confirmation

Per connection, the app can confirm every write, confirm every statement, or stay out of the way.

Safe mode is Off, Warn writes or Warn all. The classification happens in Rust and anything it does not recognize counts as a write.

Dropping or truncating from the sidebar always confirms and shows the exact statement. A confirmation can be suppressed for the session or for the saved connection, except on production connections and mandatory operations.

Cmd+Shift+C or Ctrl+Shift+C opens the console log, which lists every statement the app has executed.

_Since 0.1.0 · Free trial_

### Structure tab

A table's columns, indexes, foreign keys, constraints, triggers and full DDL, on a tab beside its data.

Structure edits go through the same pending, preview and commit loop as row edits.

DDL runs inside a transaction where the engine supports transactional DDL, and warns first where it does not.

_Since 0.1.0 · Free trial_

### Row inspector

The focused row opens as a field tree, where a JSON column is a structure to expand rather than one long line of text.

Space opens the panel. Expand all, collapse all, wrap text and copy are in the panel menu; Cmd+C copies the focused value and Cmd+Shift+C copies its subtree as JSON.

JSON is parsed by the app's own reader, so a 20-digit integer and a trailing zero survive the trip rather than being rounded by the browser engine.

Cmd+F searches field names and values together, Enter and Shift+Enter step through matches, and switches narrow the panel to matching fields or read the query as a regular expression.

Any parsed field can be switched back to its exact stored text from that field's menu.

_Since 0.8.0 · Free trial_

### Foreign key expansion

A foreign key in the row inspector opens the row it points at, directly beneath the field.

A chain such as order to customer to account to invoice reads as one panel rather than four tabs, up to ten hops deep.

Loaded rows are reused, so twenty orders sharing one customer open without another query, and collapsing a field cancels a lookup still in flight.

Where the account cannot read the referenced table the panel says Permission denied rather than repeating the driver's message.

_Since 0.8.0 · Free trial_

## Result display directives

A query can decide how its own results are drawn, by suffixing a column alias. The SQL stays portable, the stored value stays authoritative, and copy and export always use the raw value rather than the rendering.

### Result display directives

A suffix on a column alias changes how that column is drawn, and the heading keeps the part before the suffix.

The form is base__format__name, with two underscores as the separator. Selecting paid AS "paid__format__check" draws a checkmark under a heading that still reads paid.

A directive that cannot be honored falls back to plain text with the reason attached to the column or the cell, and never fails the query.

Right-click a heading and choose Treat alias as plain text to switch a column back to its full alias and raw values for the current result.

Preferences, Data has a switch to turn directive interpretation off entirely.

_Since 0.9.0 · Free trial_

### Display formatters

Nine formatters ship: check, link, email, enum, image, money, progress, stars and unixtime.

check draws a checkmark or a cross for a boolean, link and email make a cell actionable, and image draws a thumbnail from an http or https URL at a size you choose.

money writes a number as currency in the display locale, taking the code from the alias or from the default in Preferences, Data. progress draws a bar and stars a rating with partial fill.

unixtime turns an epoch integer into a readable date, reading seconds, milliseconds, microseconds or nanoseconds, in local time, in UTC or as ISO 8601.

enum resolves an id to a label through an enums.json file in the app data directory, which is local to one computer and never travels with a saved query.

A value outside the range of progress or stars keeps its real text and its spoken value; only the visual fill is clamped.

_Since 0.9.0 · Free trial_

### Related-table navigation

The goto directive turns a value into a link that opens the target table filtered to the row it names.

The forms are goto__table, goto__schema__table, goto__table__column and goto__schema__table__column. A table-only target works where the table has exactly one primary key column.

Identifiers are quoted for the dialect and the cell value is sent as a bound parameter, never concatenated into SQL, and navigation never crosses a connection.

_Since 0.9.0 · Free trial_

## Schema diagram

An entity relationship diagram drawn from what the database declares. It reads metadata only, so a line appears where there is a foreign key and nowhere else.

### Schema diagram

Draws the tables, columns, keys and declared foreign keys of a database as a map you can search, expand and rearrange.

Open one from View, Schema Diagram (Cmd+Ctrl+E on macOS, Ctrl+Alt+E on Linux), from the sidebar, from a schema, from a table, or from a table's structure view. Each entry point picks its own starting scope.

It never reads row data and never guesses a relationship from a name such as customer_id. A line appears only where the database declares a foreign key.

Cards carry column types, primary and foreign key markers and nullability; lines carry crow's-foot cardinality and fall back to a plain arrow where the engine gave too little metadata to tell.

A table pulled in from outside the scope is marked External, and a table the account cannot inspect shows a locked placeholder rather than invented metadata.

Search, focus, expand and remove work over the canvas, brackets step between related tables, and a Contents list gives assistive technology a way through the same graph.

_Since 0.9.0 · Included with a license · [Docs](https://bobbytables.app/docs/schema-diagram)_

### Diagram export

A diagram saves as a PNG file or goes to the clipboard as an image, rendered from the diagram rather than the window.

The image is drawn from the graph, so the result does not depend on what was scrolled into view, and no selection, hover or loading state can appear in it.

Exports use a fixed light palette on an opaque white background, and the status strip reports the scale whenever the image was reduced to stay inside 40 megapixels.

_Since 0.9.0 · Included with a license_

## Database snapshots

A snapshot dumps a database's structure, its data, or both, into a single file the database's own command line client replays. No pg_dump is needed, no format of ours is involved, and the file outlives this app.

### Database snapshots

Dump structure, data or both to a SQL script the database's own client replays, with no external tool installed.

Create snapshot is on a connection in the sidebar, in the connection tree and in Open anything. Content is Structure and data, Structure only or Data only.

Scope is a tree of schemas and tables, and each table carries its own structure and data choices, so a large log table can keep its CREATE TABLE while its rows are left out.

Data is written between the tables and the indexes, foreign keys and triggers, the same split pg_dump uses, so a restore loads rows into bare tables and builds the indexes afterwards.

SQLite can also take a page-consistent copy of the database file itself, compacted or as it stands.

_Since 0.10.0 · Free trial_

### Snapshots without locks

A snapshot reads inside one read-only transaction on a connection it opens for itself, so it never locks a table.

PostgreSQL reads every table inside one repeatable-read, read-only transaction, which is how pg_dump does it, and sets a lock wait so it never makes other sessions queue behind it.

MySQL and MariaDB start a consistent snapshot in a read-only transaction, which is what mysqldump does with a single transaction. LOCK TABLES and FLUSH TABLES WITH READ LOCK are never issued.

The workspace you started from keeps browsing and querying while the dump runs, and a Gentle pace caps reading at about 20 MB a second where you want the server left alone.

_Since 0.10.0 · Free trial_

### Snapshots streamed to a bucket or an SFTP server

A snapshot writes to a local file or streams to an S3 or SFTP connection, with nothing staged on this computer.

A database larger than the free space on this computer can still be snapshotted to a bucket, because the bytes are uploaded as they are read.

gzip is the default codec, zstd is offered where you know zstdcat is available at the far end, and None writes the plain script.

Cancelling leaves nothing behind: no file under the real name, no partial file, no half-finished upload and no open transaction.

The SQLite page copy is the exception. It is written to this computer's temporary directory first, and the dialog names that volume and checks its free space rather than the destination's.

_Since 0.10.0 · Free trial_

### External dump engine

Where pg_dump, mysqldump or mariadb-dump is installed, the dialog offers it as an alternative engine and says what changes.

The file then holds everything that tool dumps, ownership and grants included, and pg_dump can write its custom format for selective restores with pg_restore.

The external run shares the app's own transaction, so it is exactly as consistent as the built-in one, and it is never allowed to lock a table or take a password on its command line.

A tool too old for the server is shown with the reason rather than hidden, so pg_dump 15 against PostgreSQL 17 explains itself instead of disappearing.

_Since 0.10.0 · Free trial · PostgreSQL, MySQL, MariaDB_

### Snapshot preflight

Before anything is read, the dialog reports the size, the free space, the coverage and anything about this server worth knowing first.

It names what a structure snapshot does and does not reproduce, object kind by object kind, rather than leaving you to discover it at restore time.

Warnings are ordered most actionable first: a read-only destination, low disk space, a PostgreSQL standby that may cancel a long read, MyISAM tables that cannot be point-in-time consistent, and routines the account cannot read.

The restore command is shown before you start and again on the finished job row, where Copy restore command puts it on the clipboard.

_Since 0.10.0 · Free trial_

## Data in and out

Getting rows out of a database and back into one, without a second tool.

### Export to CSV, JSON and SQL

Export a whole table with its current filters, the current page, a selection of rows and columns, or a query result.

The formats are CSV, JSON as an array or as newline-delimited records, and SQL INSERT statements.

Export uses the stored value, never the rendering, so a column drawn as a checkmark or a currency exports as what the database holds.

_Since 0.1.0 · Free trial_

### CSV import

Load a CSV file into a table through a wizard that maps its columns and reports what it did.

The mapping step reads the file's header and lets each column be pointed at a table column or left out.

A failed import reports the error where it happened and offers a retry, rather than leaving a blank screen.

_Since 0.1.0 · Free trial_

### Opening a SQLite file from the desktop

A .sqlite, .sqlite3, .db or .db3 file opens in the app from Finder or a Linux desktop file association.

The app registers as an editor for those extensions, so Open With reaches it without a connection being created by hand first.

_Since 0.1.0 · Free trial · SQLite_

## Files

A dual-pane file transfer client for S3-compatible object storage, SFTP servers and local folders, in the same window and the same connection list as the databases. Any pane can transfer to any other pane.

### Dual-pane file workspace

Two independent panes, each browsing a bucket, an SFTP server or a local folder, with transfers running in either direction.

Each pane keeps its own location, filter, sort and hidden-file setting, and says what kind of source it is browsing.

Tab moves between panes, Cmd+Shift+O or Ctrl+Shift+O opens the current folder in the other pane, and Cmd+U or Ctrl+U swaps the two.

Rows are virtualized and counts arrive as the listing streams in, so a prefix holding tens of thousands of objects fills in progressively rather than after a wait.

_Since 0.4.0 · Free trial_

### S3-compatible storage connections

Connect to AWS S3, Cloudflare R2, MinIO and Backblaze B2 with a bucket, a region or endpoint, and a key pair.

Every field has focused help: temporary session tokens, custom endpoints, root prefixes and path-style URLs are explained where they are entered.

A root prefix is the highest visible location for that connection, and navigation that looks absolute or uses .. cannot escape it.

The secret access key and any session token go to the keychain in separate fields from the connection definition.

_Since 0.4.0 · Free trial_

### SFTP connections

Browse and transfer over SFTP, set up by pasting an sftp:// URL or filling in host, port, user and an authentication method.

Authentication is ssh-agent, a key file with an optional passphrase, or a password. Aliases, users, ports and one ProxyJump hop come from ~/.ssh/config.

The first time a host key is seen it asks; a host whose key has changed is refused rather than silently accepted.

Browsing stays inside a configurable root folder, which defaults to the login home. A sleep, a network change or a server restart is recovered by dialling a fresh session on the next operation.

An upload writes to a temporary name and renames into place, so a failure or a cancellation never overwrites the original or leaves a partial file.

_Since 0.6.0 · Free trial_

### File operations and jobs

Copy, move, rename, delete, upload, download and new folder, each running as a job with progress and a cancel.

The function keys follow the pattern a file manager user expects: F2 rename, F5 copy, F6 move, F7 new folder, F8 delete.

A conflict prompts before anything is overwritten, panes accept drag and drop from each other and from the desktop, and a read-only connection has its mutating actions disabled with the reason in a tooltip.

Renaming a folder on SFTP is one call however many files it holds, rather than a copy of every object followed by a delete of every object.

_Since 0.4.0 · Free trial_

### Server-side copies

A copy that stays inside one server or one S3 account runs there, including objects larger than 5 GB.

Copying between folders on one SFTP server runs on that server. Copying between buckets or prefixes on one S3 account is done by S3, with multipart copy above 5 GB.

Transfers between two different servers relay through the app, because a push between two remote hosts would depend on agent forwarding and tools installed on the far side.

The confirmation and the job row both say which route a transfer took, so it is never a guess whether bytes crossed your network.

_Since 0.7.0 · Free trial_

### Previews, filtering and sorting

Space previews the selected file as bounded text, JSON, an image or its metadata, without downloading the whole object.

Cmd+F or Ctrl+F filters the listing, headers sort it, and a switch shows or hides dotfiles.

Cmd+L or Ctrl+L edits the path directly, and Enter opens a folder while Backspace goes up one.

_Since 0.4.0 · Free trial_

### Presigned URLs

An S3 object can be shared as a temporary signed link from the pane it is sitting in.

The link is generated with the connection's own credentials and copied to the clipboard, so sharing a file does not mean sending the file.

_Since 0.4.0 · Free trial_

## Bucket tools

The bucket-wide operations an S3 browser normally leaves to the console or the command line: copying a bucket into another bucket, clearing one out completely, and adding a provider's buckets in bulk.

### Bucket backups

Save a backup between two S3 connections and run it when you want, either as a dated folder or as a mirror.

Back up bucket is on an S3 connection and in the pane header. A timestamped run writes a dated folder and deletes nothing; a mirror run copies what changed, skips what is already there, and can remove destination objects the source no longer has.

Copies stay on the server when both connections use the same account, including objects over 5 GB, and pass through this computer only when they have to.

Before anything is copied the dialog says which route the run will take, where it will write, how many objects the source holds if you ask it to count, and whether the destination accepts the storage class you picked.

A run interrupted by a crash or a quit picks up where it stopped rather than copying everything again, and the Backups panel keeps every run with its counts, its route and any objects that failed.

_Since 0.10.0 · Free trial_

### Empty bucket

Remove every object in a bucket, including old versions, delete markers and unfinished multipart uploads.

It deletes while it lists, so a bucket holding millions of objects empties in one pass instead of one folder at a time.

Unfinished uploads are the ones interrupted transfers leave behind, which keep costing money and which block a bucket deletion; they are aborted once the objects are gone.

The dialog names the connection, the bucket, the endpoint host and the scope, counts objects live while it reads, and enables its button only once the bucket name has been typed exactly. Deleting the bucket itself is a separate checkbox that runs last and only when nothing failed.

_Since 0.10.0 · Free trial_

### Cloudflare R2 import

Paste one Cloudflare API token to discover buckets across every account and jurisdiction it can reach, then add the ones you pick.

Import buckets from Cloudflare is on the welcome screen. The importer derives the S3 credentials from the token, marks buckets you already have, and groups the new connections by account.

Each selected bucket is verified with those credentials before it is saved, so a bucket that fails stays unsaved and shows its error in the result.

The token itself exists only for the duration of the discovery request and is never stored.

_Since 0.11.0 · Free trial_

## Trust and updates

What the app sends, what it stores, and how a new version reaches you. The short version: credentials stay in the keychain, nothing is measured, and an update is verified before it is installed.

### No telemetry

The app has no analytics and no crash reporting. Your connections, credentials, queries and files never leave your computer.

There is no analytics system to switch off and no remote feature flags. Local diagnostics carry counts, durations and error categories, never names, hosts, SQL or values.

Remote image previews in query results are off by default, because loading an image would reveal your network address to the host serving it.

_Since 0.1.0 · Free trial_

### Signed updates

An installed macOS app or Linux AppImage downloads, verifies and installs a new version in the same window, then relaunches.

Progress is visible while it downloads, the signature is checked before it installs, and a package whose signature does not verify is never installed.

Debian package installs keep the browser download path, because a .deb cannot be replaced in place.

Every release ships SHA-256 checksums, and the macOS package is signed, notarized and stapled.

_Since 0.5.0 · Free trial_

### Update checks

The app checks for a new version once a day in the background, and on demand from the menu.

Check for Updates sits beside About in the Bobby Tables menu on macOS and in Help on Linux. A current version or a network error opens a dialog that stays on screen.

Dismissing a notice keeps automatic checks quiet about that version, and a manual check still offers it, so a dismissal made by accident can be undone.

_Since 0.1.0 · Free trial_

### Errors that stay on screen

A failed query, import, export, catalog refresh or file operation reports where it happened and offers a retry.

The editor, the results and any pending edits stay where they were, rather than being replaced by a blank pane.

An unexpected application error opens a recovery screen with a reload action and a copy-details action, instead of a white window.

_Since 0.1.1 · Free trial_

## License and free trial

The whole app is one download. The free trial is the same binary with two caps on it, and a license lifts them.

### Free trial

Without a license the app runs with one saved database connection and one saved file connection, both open at once, and no expiry date.

There is no countdown, no account and no nagging. Delete a connection to try another one.

Databases and file sources are counted separately, so trying the S3 half of the app does not cost the SQL half.

Everything else is uncapped. The schema diagram is the one feature a license adds; see the pricing page for what a license costs.

_Since 0.10.0 · Free trial_

### License activation

A key entered under Preferences, General, License lifts the connection caps and unlocks the schema diagram.

Activation is verified offline against a public key compiled into the app, so the app works on a plane. The key itself is exchanged once and never stored on this computer.

The app checks in quietly in the background, and a network outage or a server error leaves an activated license exactly as it was.

When a license lapses the app keeps running and keeps your data. It returns to the free trial caps and says so, and renewing picks up without re-entering a key.

_Since 0.7.0 · Free trial_

## Questions this app answers

### What is a good PostgreSQL GUI for Linux?

Bobby Tables runs natively on Linux x86_64 as a Debian package or an AppImage, and connects to PostgreSQL, MySQL, MariaDB and SQLite. It browses the catalog, edits rows in a grid, runs SQL with autocomplete from the live catalog, and draws the schema as a diagram. The Debian package is the primary Linux build.

### Is there a TablePlus alternative that works on Ubuntu?

Bobby Tables is a desktop database client for macOS and Linux, with a Debian package tested on Ubuntu 22.04 and later. It covers the same core loop: browse the catalog, edit rows in a grid, read the generated SQL before committing, run SQL with schema-aware autocomplete. It adds a dual-pane file manager for S3 and SFTP.

### Which database clients are not a subscription?

Bobby Tables is licensed rather than rented: a license is bought outright and the app keeps running when it lapses, dropping back to the free trial caps instead of locking you out. The free trial has no expiry date and needs no account. The pricing page carries the current terms and amounts.

### How can I edit rows in a MySQL table without writing UPDATE statements?

Open the table and change cells in the grid. Cmd+I or Ctrl+I adds a row, Cmd+D duplicates one, and Backspace deletes the selected rows. Edits stay in a pending buffer until Cmd+S or Ctrl+S commits them in one transaction. A table with no primary key or unique index stays read-only, with a banner saying why.

### How do I see the SQL a GUI is about to run before it runs it?

Bobby Tables stages every grid edit in a pending buffer and renders the statements from it. Cmd+Shift+P or Ctrl+Shift+P shows the exact SQL before anything is sent, and Cmd+S commits the buffer in one transaction. Cmd+Shift+Backspace discards it. The console log lists every statement the app has already executed.

### How do I connect to a Postgres database through an SSH bastion?

Turn on the SSH tunnel section of the connection and point it at the bastion. Host aliases, users, ports, identity files and one ProxyJump hop are read from ~/.ssh/config, and authentication can be ssh-agent, a key file or a password. Host keys are checked against known_hosts, with a prompt the first time.

### How can I browse a JSON column as a tree instead of one long string?

Press Space on a row to open the row inspector, which renders a JSON field as an expandable tree with expand all, collapse all and wrap in the panel menu. Cmd+C copies the focused value and Cmd+Shift+C copies its subtree as JSON. Any field can be switched back to its exact stored text.

### How do I follow a foreign key from a row to the row it points at?

In the row inspector, a foreign key expands the referenced row directly beneath the field, so a chain of four tables reads as one panel rather than four tabs. Loaded rows are reused, so many rows sharing one parent cost one query. Expansion goes up to ten hops deep and the rows shown are read-only.

### How do I make a query result show a checkmark, a star rating or a currency without changing the data?

Suffix the column alias with a display directive. Selecting a column AS "paid__format__check" draws a checkmark under a heading that still reads paid, and money, stars, progress, unixtime, link, email, image and enum work the same way. The stored value is untouched, and copy and export use it rather than the rendering.

### How do I draw an ER diagram of an existing Postgres or MySQL database?

Open View, Schema Diagram (Cmd+Ctrl+E on macOS, Ctrl+Alt+E on Linux), or start from a schema or a table in the sidebar. The diagram reads metadata only and draws a line only where the database declares a foreign key. It saves as PNG or goes to the clipboard. A license is needed for it.

### How do I dump a Postgres database without installing pg_dump?

Choose Create snapshot on the connection. The built-in engine reads every table inside one repeatable-read, read-only transaction and writes a plain SQL script that psql replays, with no external tool involved. Where pg_dump is installed it is offered as an alternative engine. There is no restore button; the finished job shows the command.

### How do I take a consistent MySQL dump without locking tables?

A snapshot starts a consistent snapshot inside a read-only transaction, which is what mysqldump does with a single transaction. LOCK TABLES and FLUSH TABLES WITH READ LOCK are never issued. Only transactional tables are point-in-time consistent, so the preflight lists every non-InnoDB table in scope by name before you start.

### How do I back up a database straight to S3 without staging it on my laptop?

Pick an S3 or SFTP connection as the snapshot destination. The dump is streamed as it is read, so nothing is written to this computer first and a database larger than your free disk space can still be snapshotted. The one exception is a SQLite page copy, which is staged locally and says so.

### How do I copy files between an SFTP server and an S3 bucket?

Open the SFTP server in one pane of the Files workspace and the bucket in the other, then copy with F5 or drag the selection across. Transfers between two different servers relay through the app, and the confirmation and the job row both say so. Progress is shown and the job can be cancelled.

### How do I copy an S3 object over 5 GB between buckets without downloading it?

Copy it between two panes on the same S3 account. The copy is performed by S3 itself, using multipart copy above the 5 GB single-request limit, so the bytes never cross your network. The route is shown in the confirmation and again on the job row, so you can tell before it starts.

### How do I back up one S3 bucket to another on a schedule I run myself?

Save a bucket backup between two S3 connections and start it whenever you want. A timestamped run writes a dated folder; a mirror run copies what changed and can delete what the source no longer has. Nothing runs in the background: every run is one you start. Interrupted runs resume where they stopped.

### How do I mirror a Cloudflare R2 bucket to Backblaze B2?

Add both as S3-compatible connections, then save a bucket backup from one to the other in mirror mode. Because the accounts differ, the objects relay through the app, and the dialog says so before the first object moves. Storage classes are checked against the destination before the run rather than partway through it.

### How do I empty a versioned S3 bucket, including delete markers and unfinished multipart uploads?

Choose Empty bucket on the S3 connection. It removes objects, old versions, delete markers and the unfinished multipart uploads that interrupted transfers leave behind, deleting while it lists so millions of objects take one pass. You have to type the bucket name before it starts, and deleting the bucket itself is a separate checkbox.

### How do I add all my Cloudflare R2 buckets to a client at once?

Choose Import buckets from Cloudflare on the welcome screen and paste one Cloudflare API token. The importer finds buckets across every account and jurisdiction the token reaches, derives the S3 credentials, marks the ones you already have, and verifies each bucket before saving it. The token is never stored.

### Is there a dual-pane file manager for S3 and SFTP on macOS?

The Files workspace in Bobby Tables is two independent panes, each browsing an S3-compatible bucket, an SFTP server or a local folder, with transfers in either direction. Function keys follow the usual pattern: F2 rename, F5 copy, F6 move, F7 new folder, F8 delete. It runs on Apple Silicon Macs and on Linux.

### Which database client keeps passwords in the macOS Keychain?

Bobby Tables stores database passwords, S3 secret keys and SSH key passphrases in the macOS Keychain, or the Secret Service on Linux, and keeps them out of its own configuration file. Where no keychain is available it says so in the connection form and asks for the secret on connect instead.

### Which SQL client works on an Apple Silicon Mac natively?

Bobby Tables ships an arm64 macOS build, signed, notarized and stapled, for macOS 11 and later. It is the only macOS package: there has been no Intel build since 0.9.0, and an Intel install is told that updates are not published for it rather than being offered one it cannot run.

### Can I try it free without an account or a time limit?

Yes. Download it and use it with one saved database connection and one saved file connection, both open at once. There is no expiry date, no countdown and no account. Delete a connection to try another. The schema diagram is the one feature that needs a license; everything else is uncapped.

### What does the app send over the network?

Activating a license sends the key, a machine identifier, your hostname, the platform and the app version to api.bobbytables.app. After that a licensed app sends its signed receipt back about once a day and on an update check. The free trial sends only the update check. There is no analytics and no crash reporting.

### Does Bobby Tables run on Windows?

No. There is no Windows build and none is in progress. Bobby Tables runs on macOS on Apple Silicon, and on Linux x86_64 as a Debian package or an AppImage, needing WebKitGTK 2.48 or later. The AppImage is a secondary package and may fail on Mesa 25 or newer.

### Which databases does Bobby Tables support?

Four: PostgreSQL, MySQL, MariaDB and SQLite. MariaDB is recognized from the server it answers with and gets its own introspection rather than MySQL's. There is no support for MongoDB, Redis, SQL Server, Oracle, ClickHouse, DuckDB, Snowflake or BigQuery, and none is announced.

### Can I open a SQLite file by double-clicking it?

Yes. Bobby Tables registers as an editor for .sqlite, .sqlite3, .db and .db3 files, so Open With reaches it on macOS and a Linux desktop file association does the same. The file opens as a connection without one being created by hand first.

### How do I stop a client from running a DELETE by accident?

Set the connection to read-only, which opens a read-only session on the server and disables editing in the interface, or set safe mode to Warn writes or Warn all. Dropping and truncating from the sidebar always confirm and show the exact statement, and a production environment tag tints the window chrome.

### How do I move my saved connections to a new computer?

Choose Export connections on the welcome screen, which writes one file encrypted with a password you choose, then Import connections on the other computer. Passwords are included only if you ask. There is no cloud sync: the file is the transfer, and an import is checked as a whole before anything is written.

### How do I export a filtered table to CSV?

Filter the table with Cmd+F or Ctrl+F, then export. The scope choices are the whole table with its current filters, the current page, the selected rows and columns, or a query result, and the formats are CSV, JSON and SQL INSERT statements. Export writes the stored value, never the on-screen rendering.

## What it does not do

- There is no Windows build. macOS and Linux are the supported platforms, and Windows has been out of scope since the first release.
- There is no Intel macOS build. The macOS package has been Apple Silicon only since 0.9.0. An Intel install is told that updates are not published for it.
- The engines are PostgreSQL, MySQL, MariaDB and SQLite. There is no MongoDB, Redis, SQL Server, Oracle, ClickHouse, DuckDB, Snowflake or BigQuery support.
- There is no restore button for a database snapshot. The finished job shows the exact command that replays the file, and switches to pg_restore for a custom-format archive.
- Snapshots cannot be resumed, scheduled or encrypted, and are not split into volumes. A snapshot is a transaction, so an interrupted one is restarted rather than continued.
- Bucket backups never run on their own. A saved backup runs when you start it. There is no scheduler and nothing runs in the background.
- Connections do not sync between computers. Export an encrypted connection file and import it on the other computer.
- There is no telemetry and no crash reporting.
- The AppImage may fail on Mesa 25 or newer. The Debian package is the primary Linux package and is unaffected.
- The free trial is one saved database connection and one saved file connection.
- There is no plugin system, no chat assistant, no charts or dashboards, and no database user or role management.
- Cross-server file transfers relay through the app rather than being pushed server to server. A server-side push would depend on agent forwarding, host-key state on the source server and tools installed on the far side.

## Platforms and requirements

| Platform | Architecture | Requires | Package | Download |
| --- | --- | --- | --- | --- |
| macOS | Apple Silicon (arm64) | macOS 11 or later | Signed and notarized .dmg | https://bobbytables.app/download/macos-arm64 |
| Linux | x86_64 | WebKitGTK 2.48 or later (Ubuntu 22.04 or later) | Debian package (.deb) | https://bobbytables.app/download/linux-deb |
| Linux | x86_64 | WebKitGTK 2.48 or later (Ubuntu 22.04 or later) | AppImage | https://bobbytables.app/download/linux-appimage |

### Engines

- **PostgreSQL.** Often written Postgres. Every feature lands here first. Introspection reads pg_catalog directly rather than information_schema, so nothing is hidden or slow.
- **MySQL.** Read through the text protocol, so a column of any type renders without a per-type decoder.
- **MariaDB.** Recognized from the server it answers with, and given its own introspection and syntax rather than MySQL's.
- **SQLite.** A statically compiled SQLite. Database files open from the app, from Finder, and from a Linux desktop file association.

### File sources

- **AWS S3.** Region, custom endpoint, path-style URLs, temporary session tokens and a root prefix are all connection fields.
- **Cloudflare R2.** Buckets can be added one at a time, or imported in bulk from one Cloudflare API token.
- **MinIO.** Set the S3 API endpoint of the server and leave the region blank.
- **Backblaze B2.** Connects over the B2 S3-compatible endpoint.
- **SFTP.** SFTP version 3 over the app's own SSH stack, with ssh_config aliases, ProxyJump and known-hosts checking.
- **Local folders.** A pane on this computer, so a bucket and a folder sit side by side.

## Privacy

- Activating a license sends the license key, a machine identifier, your hostname, the platform and the app version to api.bobbytables.app.
- After activation a licensed app sends its signed receipt back to the same host twice: a background check about once a day, and an update check.
- The free trial sends only the update check, with no receipt attached.
- Nothing else leaves your computer: no analytics, no crash reports, and never your connections, credentials, queries or files.
- Database passwords, S3 secret keys and SSH key passphrases are stored in the operating system keychain, not in the app's configuration file.
- Remote image previews in query results are off by default, because loading an image reveals your network address to the host serving it.

## About

Bobby Tables is made by Altumo Software (https://altumo.com) in Vancouver, British Columbia, Canada. The first release was 2026-08-29. A free trial with no expiry date, and a license bought outright that lifts its caps. The pricing page carries the current terms.
