A hostable SQLite browser and editor like phpMyAdmin, but for SQLite. Standalone,
no build step, no toolchain. Just serve the src/ folder with PHP.
Built with the assistance of AI, and a lot of love for SQLite ❤️.
Connect & browse
- Login-protected, session + CSRF secured communication
- Server databases via
proxy.php(PDO/SQLite), local databases via in-browser sql.js (WASM) - Recent databases list (server and local), re-openable via the File System Access API
- JSON-aware cells (click to pretty-view) and a validating JSON editor for JSON columns
Schema & structure
- Create databases; create tables, views and virtual tables with builders
- Editable structure: rename table, add/rename/drop column, add/drop index
- Per-table metrics (rows, columns, indexes, foreign keys) and per-table
ANALYZE/REINDEX - Column/table profiler (distinct, nulls, min/max/avg per column)
- Compare two tables, or two server databases, and generate migration SQL
- Export the schema (DDL) or generate an OpenAPI/Swagger spec for the tables
SQL & performance
- Monaco SQL editor with autocomplete; run / run-as-script (wrappable in a transaction)
- Query analyzer: EXPLAIN QUERY PLAN tree, full-bytecode EXPLAIN, performance hints with "Fix this for me" actions, single/covering index suggestions (one-click apply), auto-analyze on run, and a query benchmark
Maintenance & operations
- Maintenance dialog (integrity check, FK check, optimize, analyze, reindex, vacuum, WAL checkpoint) with a run log, plus quick one-click maintenance buttons
- WAL / auto-vacuum toggles that reflect the live database state
- Shows the journal mode (WAL badge) and loaded SQLite extensions on open
- All multi-statement operations run inside a transaction (atomic, rollback on error)
Interface
- Material Design via BeerCSS; auto/light/dark theme; user-pickable accent colour; subtle theme-aware gradients; responsive; keyboard accessible; Atkinson Hyperlegible font
- Multilingual UI — English, Dutch, German, Frisian, Swedish — with browser auto-detection and a language picker in Preferences
- PHP 8.0+ with
pdo_sqliteandsessionextensions (PHP 8.4+ to load SQLite extensions) - Any web server that can serve PHP (Apache, nginx + php-fpm, or
php -Sfor testing)
- Serve the
src/directory as the document root. - Open the site. On first run no password is set, so LiteAdmin asks you to choose one
(this requires
config.jsonto be writable). The username isadminby default. - Change the password later via Preferences → Change password.
For a quick local try-out:
cd src
php -S 127.0.0.1:8000
The built-in PHP server is for development only; it ignores
.htaccessand will exposeconfig.json. Use a real web server in production.Session cookies are marked
Secureby default, so over plain HTTP (e.g. this local server) add"insecure_http": truetoconfig.jsonor the browser won't keep you logged in.
A signed apt repository is published at https://martijndeb.github.io/liteadmin.
# 1. Trust the repository signing key
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://martijndeb.github.io/liteadmin/pubkey.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/liteadmin.gpg
# 2. Add the repository (suite "stable", component "main")
echo "deb [signed-by=/etc/apt/keyrings/liteadmin.gpg] https://martijndeb.github.io/liteadmin stable main" \
| sudo tee /etc/apt/sources.list.d/liteadmin.list
# 3. Install
sudo apt update
sudo apt install liteadminThe package installs the app to /usr/share/liteadmin, keeps its configuration at
/etc/liteadmin/config.json and its databases under /var/lib/liteadmin/databases (both
symlinked into the app directory). It depends on php-fpm and php-sqlite3 and recommends
nginx — point your web server at /usr/share/liteadmin, then open the site and set a
password on first run.
The repository's Release file advertises the following fields, which you use to whitelist it:
| Field | Value |
|---|---|
| Origin | liteadmin |
| Label | liteadmin |
| Suite / Codename | stable |
| Component | main |
To let unattended-upgrades keep LiteAdmin up to
date, allow that origin. Create /etc/apt/apt.conf.d/51liteadmin:
Unattended-Upgrade::Origins-Pattern {
"origin=liteadmin,label=liteadmin,codename=stable";
};
Then make sure the tooling is installed and enabled, and dry-run to confirm the match:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
sudo unattended-upgrade --dry-run --debug 2>&1 | grep -i liteadminThe
origin,labelandcodenamekeys map directly to theReleasefields above; the shorthand"liteadmin:stable"(i.e.${origin}:${suite}) inUnattended-Upgrade::Allowed-Originsworks too.
The included Dockerfile builds a single, self-contained image running Caddy + php-fpm 8.4
(with pdo_sqlite). No build step, no external services.
docker build -t liteadmin .
docker run -d --name liteadmin -p 8080:80 \
-v liteadmin-data:/var/www/liteadmin/src/databases \
liteadminOpen http://localhost:8080 and choose a password on first run (the image ships with an empty password, so it starts on the setup screen).
- The
liteadmin-datavolume keeps your server databases across container restarts. - To persist the configuration too, bind-mount it — it must stay writable by the container's
www-dataso the setup and Change password screens can save:-v "$(pwd)/config.json:/var/www/liteadmin/src/config.json". - Session cookies are
Secureby default. When reaching the container over plain HTTP, set"insecure_http": trueinconfig.json, or (recommended) run it behind a TLS-terminating reverse proxy.
Or with Compose:
services:
liteadmin:
build: .
ports:
- "8080:80"
volumes:
- liteadmin-data:/var/www/liteadmin/src/databases
volumes:
liteadmin-data:{
"app": { "name": "LiteAdmin", "lang": "en", "max_rows": 1000, "buffer_rows": 200 },
"auth": { "username": "admin", "password_hash": "" },
"session":{ "timeout": 3600 },
"create_dir": "databases",
"databases": {
"sample": { "label": "Sample Database", "path": "databases/sample.sqlite", "readonly": false }
}
}auth.password_hash— an empty value triggers the first-run setup screen. The setup and the Change password feature write the bcrypt hash back toconfig.json, so the file must be writable by the web server for those to work (a warning is shown otherwise).databases— the allow-list of server databases.pathis relative tosrc/. Setreadonly: trueto forbid writes.create_dir— folder where new server databases are created and auto-discovered (managed databases). Set tonullto disable creating server databases.insecure_http— session cookies are markedSecureby default (correct behind a TLS-terminating reverse proxy, where$_SERVER['HTTPS']is empty). Set totrueonly for plain-HTTP access such as local development, otherwise the browser won't send the cookie.debug— whentrue, full error messages (which may include file paths) are returned to the client. Leave unset/falsein production; paths are stripped from errors by default.app.lang— default UI language (en,nl,de,fy,sv). Users can override it with the language picker; on first visit the browser language is auto-detected.- SQLite extensions (optional, server only, PHP 8.4+ with
Pdo\Sqlite): load shared libraries on connect.extensionsat the top level is loaded for every server database (defaults likevec0); a per-databaseextensionsarray adds more for just that one. Bare names (e.g.vec0) resolve underext_dir; values with a slash are relative tosrc/, and absolute paths are used as-is. The platform suffix (.so/.dylib/.dll) may be omitted. Paths only come from this file (never the client), and the loaded/failed state is shown on the database's Database tab. Example:
{
"ext_dir": "ext",
"extensions": ["vec0"],
"databases": {
"vectors": { "label": "Vectors", "path": "databases/vectors.sqlite", "extensions": ["fts5_ext"] }
}
}- Generate a new password hash:
php -r 'echo password_hash("your-password", PASSWORD_DEFAULT), "\n";'
LiteAdmin has a small plugin system so functionality ships — and installs — separately, including as
standalone .deb packages. Plugins are fully optional and off by default. Plugins live in
src/plugins/<name>/, but a plugin only becomes active when its name is listed in config.json:
{ "plugins": ["liteadmin-apikeys"] }With no plugins key nothing is active. The core package excludes src/plugins/, so on a
packaged install each plugin is shipped as its own .deb: it installs the code into
/usr/share/liteadmin/plugins/<name>/ and its postinst enables it by adding the name to plugins
in the config (uninstalling removes it again) — a one-command, reversible add-on.
src/plugins/<name>/
plugin.json manifest (name, version, server/client entry points)
plugin.php server side — a class with a register(PluginHost $host) method
plugin.js client side — export function register(api) { … }
Server API ($host):
route($action, $handler, ['auth' => …])— expose an endpoint atplugin.php.authis'session'(LiteAdmin admin, default),'public', or['guard' => 'apikey', 'scope' => 'write']to delegate to another plugin's auth guard.service($name, $obj)/getService($name)— publish/consume a shared service.guard($name, $handler)— publish a reusable auth guard.dataDir()— a writable per-plugin data directory (underdata_dir).on($event, $handler)/emit($event, $payload)— event hooks.
Client API (api): addStartCard({icon,title,subtitle,onOpen}), addTab({id,icon,label,render(panel, ws)}),
call(action, params) (posts to that plugin's endpoints), plus el, clear, t, toast, download.
Calls go through src/plugin.php, which handles discovery, the auth mode and JSON I/O.
src/plugins/liteadmin-apikeys/ issues API keys with read/write scopes (stored hashed). It is
the foundation other plugins build on: it publishes
- the
apikeysservice —validate($key, $scope)andkeyFromRequest(), and - the
apikeyguard — so any plugin route with['auth' => ['guard' => 'apikey', 'scope' => 'read']]requires a valid key, presented asX-Api-Key: <key>orAuthorization: Bearer <key>.
Once enabled, manage keys from the Plugins section on the start page.
Planned plugins that slot into the same system: a REST API exposing the generated OpenAPI
endpoints as CRUD, a code generator (tables → typed classes/definitions), and an MCP server —
each guarding its endpoints with the apikey guard above.
plugins— array of enabled plugin names. Absent/empty means no plugins are active.plugin_dir— runtime plugin directory (defaultplugins, relative tosrc/).data_dir— writable base for plugin data (defaultdata). On a packaged install point this at a writable location, e.g."data_dir": "/var/lib/liteadmin".
From a source checkout the bundled plugin is already in src/plugins/, so enabling it is just adding
"plugins": ["liteadmin-apikeys"] to config.json (and reloading php-fpm if you run one).
Add a packaging/<name>/control (see packaging/liteadmin-apikeys/, which also ships postinst/postrm
that toggle the plugin in the config) and build:
packaging/build-plugin.sh liteadmin-apikeys 0.3.0This produces liteadmin-apikeys_0.3.0_all.deb, which installs the plugin into
/usr/share/liteadmin/plugins/<name>/, depends on the liteadmin package, and enables itself on
install — no core changes required.
LiteAdmin ships with English, Dutch, German, Frisian and Swedish (src/i18n/*.json).
The UI auto-detects the browser language on first visit and can be changed any time via the
language picker in Preferences; app.lang in config.json sets the default.
To add a language: copy src/i18n/en.json to src/i18n/<code>.json, translate the values
(keep the keys), then add the code to SUPPORTED and a display name to LANG_NAMES in
src/js/i18n.js. All locale files share the same key set.
Released under the MIT License. Vendored libraries in src/vendor/ keep their
own respective licenses.