A dub.co-styled URL shortener dashboard built on YOURLS, for plain PHP/MySQL shared hosting
PHP
0
32 commits
updated Oct 1, 2026
A self-hosted URL shortener with a modern, SaaS-style dashboard, built on top of YOURLS so it runs on ordinary PHP/MySQL shared hosting -- no Node build step, no Docker, no serverless platform required.

Leanks is YOURLS underneath (redirect engine, database, click tracking) with:
YOURLS is plain PHP, MIT licensed, has no build step, and stores everything in a couple of MySQL
tables -- which makes it a good fit for cheap shared/cPanel hosting. Leanks keeps YOURLS' core
completely stock (redirect logic, database schema, click tracking, plugin API) and adds a small
plugin (app/user/plugins/leanks) plus an entirely custom front end (app/app/) on top, rather
than patching YOURLS' own files. That means YOURLS itself stays upgradeable in place. The one exception
is .htaccess: it carries one extra rewrite rule so a bare visit to the site root reaches
Leanks' default-domain-redirect setting instead of Apache/LiteSpeed's own directory listing (see
the comment in that file) -- config for routing, not a patch to YOURLS' PHP.
The dashboard's visual design is original -- a modern, SaaS-style interface built from scratch in plain CSS, replacing YOURLS' classic PHP admin panel look without touching YOURLS itself.
pdo_mysql extensionmod_rewrite (for pretty short URLs via .htaccess) -- other web servers work too,
see YOURLS' own docs for nginx/other rewrite rulesyouraccount_leanks) -- use the exact names it shows you, not the
short name you typed when creating them.app/ folder to your web root (e.g. public_html),
keeping the folder structure intact -- not web/ or branding/, which are repo-only and never
deployed to the PHP host.https://your-domain.com/setup/ in a browser and fill in the database details from step
1 and an admin username/password. This writes user/config.php, creates the database tables,
and activates the Leanks plugin automatically./setup folder once installed -- it can rewrite your
database credentials and shouldn't stay reachable.https://your-domain.com/app/ and log in with the admin account you just created.Prefer to configure by hand? Copy user/config-sample.php to user/config.php, fill in your
settings, then visit /admin/install.php (YOURLS' own installer) followed by /admin/plugins.php
to activate the "Leanks" plugin.
app/ The deployable PHP install -- upload its *contents* to your web root
admin/ Stock YOURLS admin (kept as a fallback/power-user UI)
app/ The Leanks dashboard (vanilla HTML/CSS/JS)
auth.php Thin JSON bridge into YOURLS' own login/session
index.html, login.html
css/app.css Design system (light/dark tokens, accent color derivation)
js/app.js Links view, settings, theme/accent, import/export UI
js/analytics.js Analytics view (chart, filters, breakdowns)
js/api.js, ui.js, login.js, theme-init.js
js/vendor/qrcode.js Vendored QR code generator (MIT, see licenses/)
images/ Leanks logo and favicons
includes/ Stock YOURLS core (untouched)
setup/ Web-based install wizard
user/
config-sample.php Manual-install config template
plugins/leanks/ The plugin: link metadata, tags, analytics, import/export,
settings, self-updater, redirect gating
licenses/ Third-party license texts (YOURLS, qrcode.js, Tabler Icons)
web/ The marketing site for leanks.cc (static HTML/CSS) -- repo-only, not deployed
to the PHP host
branding/ Logo and other brand assets -- repo-only
Password protection, expiration and click limits are enforced by hooking YOURLS'
redirect_shorturl action (see app/user/plugins/leanks/includes/redirect-gate.php): before a click
is logged and redirected, Leanks checks a small metadata table ({prefix}leanks_meta, one row per
protected/expiring link) and can intercept the request with a password prompt or an "expired"
page. Everything else -- the redirect itself, click counting, referrer/geo logging -- is stock
YOURLS.
The dashboard talks to YOURLS' existing admin-ajax.php for link create/edit/delete (reusing its
built-in nonce-based CSRF protection and session auth), plus a handful of custom leanks_*
actions the plugin registers on the same endpoint for listing, stats, analytics, tags, metadata, settings, and CSV import/export.
app/user/plugins/leanks/includes/import.php parses the uploaded file server-side with PHP's own CSV
parser and auto-detects columns by matching common header aliases (url/destination url,
short link/key/slug, title/name, creation date/created at, etc.) case-insensitively --
it doesn't require exact header names, so column order and naming don't need to match exactly.
Each row is created through the same yourls_add_new_link()
YOURLS itself uses, so duplicate URLs/keywords are caught the normal way and reported back per-row
rather than aborting the whole import; a supplied creation date is applied afterwards. Files over
2000 rows are processed in the first batch only -- re-upload the remainder in a second pass.
app/user/plugins/leanks/includes/export.php streams every link as a CSV with dub.co's column
names (Destination URL, Short link, Title, Creation date, Clicks, Tags), so the file re-imports
cleanly. Passwords, expirations and UTM fields aren't part of the import format and don't travel;
tag names are exported without their colors (re-imported tags get the default color). Commas,
semicolons and backslashes inside a tag name are backslash-escaped so they survive the round
trip, and titles starting with =, +, - or @ get a leading apostrophe so a spreadsheet
won't run them as formulas (import strips it again).
app/user/plugins/leanks/includes/update.php polls https://api.github.com/repos/creixems/leanks/releases/latest
(throttled to once every 24h) and shows a dashboard banner when a newer version is published.
Clicking Update now downloads that release's zip, verifies it against the checksum published
alongside it, backs up every file about to change, then applies the diff between the previous and
new release manifests (added/changed files are written, files removed upstream are deleted).
Nothing is applied until the checksum check passes and the backup succeeds; if applying the update
itself fails partway through, it's rolled back from that backup automatically. Past backups are
listed in the update modal with a manual Restore action, and the 3 most recent are kept.
Two things are deliberately never touched by an automated update: user/config.php (never part of
any release) and .htaccess (skipped if it differs from the shipped version, with the new version
saved to user/leanks-updates/htaccess.new for you to merge by hand) -- so a customized rewrite
config or local site settings are never silently overwritten. This requires the ZipArchive PHP
extension; hosts without it get a clear message instead of a broken update.
Releases are built by .github/workflows/release.yml: pushing a
vX.Y.Z tag (after bumping LEANKS_VERSION in
version.php to match) builds a zip of every
git-tracked file under app/ (with the app/ prefix stripped, so the zip mirrors a live install's
own root), a manifest.json of that file list, and a sha256 checksum, and attaches them
to a draft GitHub release. Drafts are invisible to /releases/latest, so publishing is a
separate, deliberate step after writing release notes.
No build step -- edit files and reload. app/ is the deployable install, so it's also the local
server's document root. To run it locally you need PHP and MySQL/MariaDB, e.g.:
cd app
php -S 127.0.0.1:8000 router.php # see below for router.php
Since php -S doesn't read .htaccess, use a tiny router script (inside app/, dev-only) that
mirrors it for local testing:
<?php
// app/router.php (dev only)
$root = $_SERVER['DOCUMENT_ROOT'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$file = $root . $path;
if ($path !== '/' && (is_file($file) || is_dir($file))) return false;
require $root . '/yourls-loader.php';
Then visit http://127.0.0.1:8000/setup/ to install against your local database.
MIT -- see LICENSE. Bundles YOURLS, qrcode.js and icons from Tabler Icons, all MIT licensed; see licenses/ for their original license texts.
PHP
74.4%
JavaScript
14.7%
CSS
6.3%
HTML
4.7%
A dub.co-styled URL shortener dashboard built on YOURLS, for plain PHP/MySQL shared hosting
PHP
0
32 commits
updated Oct 1, 2026
A self-hosted URL shortener with a modern, SaaS-style dashboard, built on top of YOURLS so it runs on ordinary PHP/MySQL shared hosting -- no Node build step, no Docker, no serverless platform required.

Leanks is YOURLS underneath (redirect engine, database, click tracking) with:
YOURLS is plain PHP, MIT licensed, has no build step, and stores everything in a couple of MySQL
tables -- which makes it a good fit for cheap shared/cPanel hosting. Leanks keeps YOURLS' core
completely stock (redirect logic, database schema, click tracking, plugin API) and adds a small
plugin (app/user/plugins/leanks) plus an entirely custom front end (app/app/) on top, rather
than patching YOURLS' own files. That means YOURLS itself stays upgradeable in place. The one exception
is .htaccess: it carries one extra rewrite rule so a bare visit to the site root reaches
Leanks' default-domain-redirect setting instead of Apache/LiteSpeed's own directory listing (see
the comment in that file) -- config for routing, not a patch to YOURLS' PHP.
The dashboard's visual design is original -- a modern, SaaS-style interface built from scratch in plain CSS, replacing YOURLS' classic PHP admin panel look without touching YOURLS itself.
pdo_mysql extensionmod_rewrite (for pretty short URLs via .htaccess) -- other web servers work too,
see YOURLS' own docs for nginx/other rewrite rulesyouraccount_leanks) -- use the exact names it shows you, not the
short name you typed when creating them.app/ folder to your web root (e.g. public_html),
keeping the folder structure intact -- not web/ or branding/, which are repo-only and never
deployed to the PHP host.https://your-domain.com/setup/ in a browser and fill in the database details from step
1 and an admin username/password. This writes user/config.php, creates the database tables,
and activates the Leanks plugin automatically./setup folder once installed -- it can rewrite your
database credentials and shouldn't stay reachable.https://your-domain.com/app/ and log in with the admin account you just created.Prefer to configure by hand? Copy user/config-sample.php to user/config.php, fill in your
settings, then visit /admin/install.php (YOURLS' own installer) followed by /admin/plugins.php
to activate the "Leanks" plugin.
app/ The deployable PHP install -- upload its *contents* to your web root
admin/ Stock YOURLS admin (kept as a fallback/power-user UI)
app/ The Leanks dashboard (vanilla HTML/CSS/JS)
auth.php Thin JSON bridge into YOURLS' own login/session
index.html, login.html
css/app.css Design system (light/dark tokens, accent color derivation)
js/app.js Links view, settings, theme/accent, import/export UI
js/analytics.js Analytics view (chart, filters, breakdowns)
js/api.js, ui.js, login.js, theme-init.js
js/vendor/qrcode.js Vendored QR code generator (MIT, see licenses/)
images/ Leanks logo and favicons
includes/ Stock YOURLS core (untouched)
setup/ Web-based install wizard
user/
config-sample.php Manual-install config template
plugins/leanks/ The plugin: link metadata, tags, analytics, import/export,
settings, self-updater, redirect gating
licenses/ Third-party license texts (YOURLS, qrcode.js, Tabler Icons)
web/ The marketing site for leanks.cc (static HTML/CSS) -- repo-only, not deployed
to the PHP host
branding/ Logo and other brand assets -- repo-only
Password protection, expiration and click limits are enforced by hooking YOURLS'
redirect_shorturl action (see app/user/plugins/leanks/includes/redirect-gate.php): before a click
is logged and redirected, Leanks checks a small metadata table ({prefix}leanks_meta, one row per
protected/expiring link) and can intercept the request with a password prompt or an "expired"
page. Everything else -- the redirect itself, click counting, referrer/geo logging -- is stock
YOURLS.
The dashboard talks to YOURLS' existing admin-ajax.php for link create/edit/delete (reusing its
built-in nonce-based CSRF protection and session auth), plus a handful of custom leanks_*
actions the plugin registers on the same endpoint for listing, stats, analytics, tags, metadata, settings, and CSV import/export.
app/user/plugins/leanks/includes/import.php parses the uploaded file server-side with PHP's own CSV
parser and auto-detects columns by matching common header aliases (url/destination url,
short link/key/slug, title/name, creation date/created at, etc.) case-insensitively --
it doesn't require exact header names, so column order and naming don't need to match exactly.
Each row is created through the same yourls_add_new_link()
YOURLS itself uses, so duplicate URLs/keywords are caught the normal way and reported back per-row
rather than aborting the whole import; a supplied creation date is applied afterwards. Files over
2000 rows are processed in the first batch only -- re-upload the remainder in a second pass.
app/user/plugins/leanks/includes/export.php streams every link as a CSV with dub.co's column
names (Destination URL, Short link, Title, Creation date, Clicks, Tags), so the file re-imports
cleanly. Passwords, expirations and UTM fields aren't part of the import format and don't travel;
tag names are exported without their colors (re-imported tags get the default color). Commas,
semicolons and backslashes inside a tag name are backslash-escaped so they survive the round
trip, and titles starting with =, +, - or @ get a leading apostrophe so a spreadsheet
won't run them as formulas (import strips it again).
app/user/plugins/leanks/includes/update.php polls https://api.github.com/repos/creixems/leanks/releases/latest
(throttled to once every 24h) and shows a dashboard banner when a newer version is published.
Clicking Update now downloads that release's zip, verifies it against the checksum published
alongside it, backs up every file about to change, then applies the diff between the previous and
new release manifests (added/changed files are written, files removed upstream are deleted).
Nothing is applied until the checksum check passes and the backup succeeds; if applying the update
itself fails partway through, it's rolled back from that backup automatically. Past backups are
listed in the update modal with a manual Restore action, and the 3 most recent are kept.
Two things are deliberately never touched by an automated update: user/config.php (never part of
any release) and .htaccess (skipped if it differs from the shipped version, with the new version
saved to user/leanks-updates/htaccess.new for you to merge by hand) -- so a customized rewrite
config or local site settings are never silently overwritten. This requires the ZipArchive PHP
extension; hosts without it get a clear message instead of a broken update.
Releases are built by .github/workflows/release.yml: pushing a
vX.Y.Z tag (after bumping LEANKS_VERSION in
version.php to match) builds a zip of every
git-tracked file under app/ (with the app/ prefix stripped, so the zip mirrors a live install's
own root), a manifest.json of that file list, and a sha256 checksum, and attaches them
to a draft GitHub release. Drafts are invisible to /releases/latest, so publishing is a
separate, deliberate step after writing release notes.
No build step -- edit files and reload. app/ is the deployable install, so it's also the local
server's document root. To run it locally you need PHP and MySQL/MariaDB, e.g.:
cd app
php -S 127.0.0.1:8000 router.php # see below for router.php
Since php -S doesn't read .htaccess, use a tiny router script (inside app/, dev-only) that
mirrors it for local testing:
<?php
// app/router.php (dev only)
$root = $_SERVER['DOCUMENT_ROOT'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$file = $root . $path;
if ($path !== '/' && (is_file($file) || is_dir($file))) return false;
require $root . '/yourls-loader.php';
Then visit http://127.0.0.1:8000/setup/ to install against your local database.
MIT -- see LICENSE. Bundles YOURLS, qrcode.js and icons from Tabler Icons, all MIT licensed; see licenses/ for their original license texts.
PHP
74.4%
JavaScript
14.7%
CSS
6.3%
HTML
4.7%