Guides

Practical guides.
Not API docs.

Real workflows people actually need help with — not a wall of method signatures. Three guides for now. More as the questions come in.

Guide 01 ~5 min

Set up a read-only user.

The common scenario: a stakeholder wants to see live data, an on-call engineer needs to investigate without risking a bad UPDATE, or your auditor needs query access. Don't share your admin password — make them a read-only user.

What "read-only" means in Ledger

Read-only is enforced at two layers:

  • MySQL grants — the underlying database user can only run SELECT, SHOW, EXPLAIN. Even if Ledger were bypassed entirely, the database itself wouldn't accept writes.
  • Ledger application — even within those grants, Ledger's read-only mode rejects write statements before they're sent. This catches multi-statement edge cases and gives clearer error messages.
Why two layers? Defense in depth. The MySQL layer is the source of truth — if it ever fails, the application layer catches the leak before damage. If the application layer ever fails, the MySQL layer holds.

Step by step

  1. Create the MySQL user. Log into Ledger as admin, open the SQL editor, paste:

    CREATE USER 'ledger_readonly'@'localhost' IDENTIFIED BY 'pick-a-strong-password';
    GRANT SELECT, SHOW VIEW, PROCESS ON *.* TO 'ledger_readonly'@'localhost';
    FLUSH PRIVILEGES;

    The PROCESS grant lets them see (but not kill) processes. Drop it if you don't want that.

  2. Add the Ledger user. Open the admin panel — typically at /admin/users in your install — and click "Add user". Username can match the MySQL one, password is separate.

  3. Enable read-only mode for this user. In the user form, check the Read-only toggle. This is the application-layer guard.

  4. Set the connection. In Ledger's database connection setting (per-user or global, depending on your install), use the ledger_readonly MySQL credentials for this user's session. Don't reuse the admin MySQL user.

  5. Test it. Log out, log back in as the new user. Try running an UPDATE. You should see "Read-only mode enabled — write statements are blocked" before the query even reaches MySQL.

One gotcha. If the read-only user needs to access a specific database, replace *.* in the GRANT with your_db.*. Granting on *.* includes the mysql system database — usually not what you want.
Guide 02 ~8 min

Use the SQL editor effectively.

The editor isn't a textarea with color. It's a context-aware code surface with autocomplete, multi-statement execution, EXPLAIN visualization, and persistent drafts. Knowing the patterns turns it from "good enough" into "the reason you switched tools."

Autocomplete that knows what you mean

Type a table alias and a dot — you get its columns. Not every column in the database; the columns of that specific table, even if it has the same column name as another table in the query.

SELECT u.    ← typing the dot triggers autocomplete for the `users` table
FROM users u
JOIN orders o ON o.user_id = u.id

The autocomplete walks the query both ways — so even when you go back and add columns to a SELECT at the top, the editor still knows what u and o refer to because it looks at the FROM and JOIN clauses below.

Keyboard shortcuts

  • Ctrl/Cmd + Enter — run the current statement (or selection)
  • Ctrl/Cmd + Shift + Enter — run all statements in the editor
  • Tab — accept autocomplete suggestion
  • Ctrl/Cmd + / — toggle line comment
  • Ctrl/Cmd + D — duplicate current line

Multi-statement execution

Paste a full migration script and run it as a batch. Statements separated by semicolons execute in sequence; each gets its own result card with row counts and timing. Execution stops on the first error.

USE my_database;

CREATE TABLE feature_flags (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(64) UNIQUE,
  enabled BOOLEAN DEFAULT 0
);

INSERT INTO feature_flags (name, enabled) VALUES
  ('new_dashboard', 0),
  ('experimental_search', 0);

SELECT * FROM feature_flags;

All four statements run in order. The trailing SELECT shows you what you just inserted.

For atomicity, wrap in a transaction. Multi-statement execution stops on the first error, but earlier statements are already committed. If you need all-or-nothing, wrap the batch in START TRANSACTION; ... COMMIT; and execution will respect MySQL's transaction semantics.

EXPLAIN visualization

Click Explain next to Run to see the query plan as a color-coded table. Key things to look for:

  • Red rows in the type columnALL means full table scan, which is expensive on large tables.
  • "Using filesort" or "Using temporary" warnings — these get highlighted in amber. Often indicates a missing index on an ORDER BY or GROUP BY column.
  • Inflated rows estimates — when the estimate is far larger than reality, statistics may be stale. Run ANALYZE TABLE to refresh.

Persistent drafts

Editor content auto-saves per database. Switch from users to orders, the editor changes. Switch back, your half-finished query is still there.

Drafts persist across browser restarts and across devices if you log in elsewhere. They're stored in Ledger's logs directory as plain JSON — you can grep them, version them, or back them up.

Drafts are not bookmarks. A draft is whatever was last in the editor — even if it's broken or half-typed. For queries you want to save deliberately, the Query history panel has a star button. Bookmarks-with-names are coming in v1.1.
Guide 03 ~15 min

Migrate from phpMyAdmin.

Migrations go wrong when you remove the old thing before the new thing is trusted. The strategy here is run both side by side until you're confident, then retire phpMyAdmin. Nothing in your database changes — only the tool pointing at it.

What gets migrated, what doesn't

What you don't need to migrate

  • Your databases. phpMyAdmin and Ledger both read from MySQL/MariaDB directly. The data lives in MySQL, not in the tool.
  • Your MySQL users. Both tools authenticate as MySQL users. Existing users keep working.
  • Backups. phpMyAdmin SQL dumps import cleanly into Ledger via the SQL editor or import panel.

What needs setting up fresh

  • Ledger admin accounts. Ledger uses its own user layer on top of MySQL. Set up admin users during install.
  • 2FA enrollment. If you use 2FA in phpMyAdmin, you'll re-enroll in Ledger. Different secret keys.
  • Favorites, bookmarks, query history. Not portable. Most of these you probably haven't been using anyway.

The side-by-side migration

  1. Install Ledger to a different path. If phpMyAdmin is at /phpmyadmin/, install Ledger to /ledger/. Both web apps coexist on the same Apache server, same MySQL backend.

    cd /var/www/html
    git clone https://github.com/ClearanceClarence/Ledger.git ledger
  2. Run the Ledger installer with your existing MySQL credentials. Use the same MySQL host/user/password phpMyAdmin uses. Visit http://your-server/ledger/ in a browser — installer loads automatically.

  3. Create a Ledger admin user. Step 2 of the installer. Set a strong password — this is the application-layer credential.

  4. Use both tools for a week. Run normal workflows in Ledger; fall back to phpMyAdmin if anything's missing or weird. File issues on GitHub for the gaps you hit.

  5. Once you trust it, harden Ledger. Enable 2FA on your account, set up an IP whitelist if appropriate, configure session timeouts, review the security page.

  6. Disable phpMyAdmin (don't delete yet). Either rename the folder (mv phpmyadmin _phpmyadmin_disabled) or block access via .htaccess:

    # Inside /var/www/html/phpmyadmin/.htaccess
    Require all denied

    Leave it disabled for a month. If nothing breaks, delete it.

If you used a phpMyAdmin config_storage database

phpMyAdmin has an optional configuration storage database that holds bookmarks, table relations, column info, and PDF schema layouts. Ledger doesn't read this database — it has its own state stored in flat files inside the install directory.

If you were heavily using config_storage for table relations, those relations are stored in your tables as actual foreign keys (or as comments) — Ledger reads them the same way. Bookmarks and PDF layouts don't transfer; you'll set those up in Ledger fresh.

Don't delete the phpMyAdmin config_storage database itself. Even after retiring phpMyAdmin, the actual foreign-key constraints in your tables are real schema. Drop the phpmyadmin system database (the one with tables like pma__bookmark, pma__relation) only — your application databases are separate.

Side-by-side comfort checklist

Before retiring phpMyAdmin, run through these:

  • You've successfully run a complex query in Ledger that you used to run in phpMyAdmin.
  • You've imported a SQL dump in Ledger and verified the data.
  • You've exported a database in Ledger's phpMyAdmin-compatible mode and verified the dump file looks right.
  • You've edited a row inline and confirmed it persisted.
  • You've checked the audit log to confirm queries are being logged the way you expect.
  • If you use 2FA, it's enrolled and working.
  • If you use IP whitelist, the production IP range is included and you've tested access from it.

If all of those work, you're done. Disable phpMyAdmin, give it a month, then delete.

All done ↑ Back to top

A guide you wish existed?

Open an issue with the workflow you're stuck on. Real questions become guides — guesses about what people might want stay drafts forever.

Ask in Discussions Install Ledger