A minimal URL shortener for teams and clubs — no accounts, no passwords, no bloat. Easy & cheap hosting with just one PHP file and a MySQL table.
Your sports club or volunteer group needs short links for flyers and WhatsApp. You want anyone to be able to create them — but nobody wants another account, non-technical members won't remember a password they use twice a year, and you can't let just anyone publish under your domain.
Regular shorteners are either open to everyone (no control) or locked behind accounts (friction). Self-hosted options like YOURLS or Shlink are powerful but don't fit the "no accounts" requirement.
No accounts for submitters. Anyone submits a URL and slug — no login required. A simple arithmetic CAPTCHA keeps basic spam bots out.
One admin, full control. A single password gates the approval panel. Nothing goes live without approval.
Dead simple to host. One PHP file, one .htaccess, one MySQL table. No Composer, no Node, no Docker — runs on any shared hosting plan.
Webhook notifications (optional). Configure a webhook URL to automatically notify the admin when a new link is submitted (e.g. via a Webhook-to-push-notification mobile app). A flexible condition expression lets you verify the response to inform users whether they need to notify the admin manually in case of a failure.
REST API (optional). A simple API lets external tools submit links and manage approvals without touching the admin panel. Protected by a Bearer token.
QR codes included. Every active link gets a QR code download. Saves time for everyone.
Good fit: Sports clubs, scout groups, church communities — any group where many people occasionally need short links but nobody wants to manage accounts.
Not a fit: You need analytics, click tracking, link expiry, or high submission volume. Use YOURLS or Shlink instead.
- Apache with
mod_rewrite - PHP 7.1+ with
pdo_mysql - MySQL 5.7+ or MariaDB
git clone <repo> cd clubshort cp .env.example .env # fill in DB credentials and admin password
Point your Apache document root to httpdocs/. The .htaccess handles slug routing.
Note on
.envparsing: To keep this dependency-free,index.phpparses.envitself with a minimal line-by-line reader. It does not support multiline values, quoted strings, or variable interpolation — plainKEY=valuepairs only, comments starting with#are ignored. That covers everything in.env.example.
Create one table:
CREATE TABLE redirects ( slug VARCHAR(255) PRIMARY KEY, redirectUrl VARCHAR(2048) NOT NULL, metadata JSON, enabled BOOLEAN DEFAULT 0, createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
Copy .env.example to .env and fill in values:
| Variable | Required | Description |
|---|---|---|
DB_HOST |
Yes | MySQL host |
DB_NAME |
Yes | Database name |
DB_USER |
Yes | DB username |
DB_PASSWORD |
Yes | DB password |
ADMIN_PASSWORD |
Yes | Admin panel password |
APP_NAME |
No | Display name (default: URL Shortener) |
APP_BASE_URL |
No | Base URL for short links |
ADMIN_FIRST_NAME |
No | Admin first name shown in WhatsApp messages |
WHATSAPP_PHONE |
No | E.164 phone without + — enables WhatsApp notify button |
IMPRINT_URL |
No | Legal imprint URL — shows footer link when set |
PRIVACY_URL |
No | Privacy policy URL — shows footer link when set |
NOTIFY_WEBHOOK_URL |
No | POST a JSON payload to this URL on every new submission |
NOTIFY_WEBHOOK_SUCCESS_CONDITION |
No | Comma-separated conditions to verify the webhook response (e.g. success=true,delivered>0). Empty = any HTTP response counts as success |
API_KEY |
No | Enables the REST API; requests must supply Authorization: Bearer <key> |
Set API_KEY in .env to enable. All requests require the header Authorization: Bearer <API_KEY>.
| Method | Path | Description |
|---|---|---|
POST |
/api/submit |
Submit a new redirect. Body: {"slug":"...","redirectUrl":"...","username":"..."} |
POST |
/api/approve |
Enable, disable, or delete a redirect. Body: {"slug":"...","action":"enable|disable|delete"} |
GET |
/api/urls |
List all redirects. Optional query param: ?status=enabled|disabled|all (default: all) |
MIT