1. Overview & features
ShortLink turns long URLs into short, shareable links and records detailed analytics for every visit. It is plain PHP with no framework and no build step, so it installs on ordinary shared hosting (cPanel, Hostinger, Plesk, XAMPP) in a couple of minutes.
Core features
- Login-free shortening — paste a URL on the home page and get a 7-character short link straight away.
- Duplicate protection — the same long URL always returns the same short code instead of creating a new one.
- Loop protection — URLs pointing at your own domain are rejected, so a short link can never redirect to itself.
- Click analytics at
/{code}/stats— total clicks, unique and returning visitors, today / 7-day / 30-day totals, week-on-week trend, a daily (or weekly) timeline, devices, browsers, operating systems, countries, busiest hours, traffic sources, top referrers and a recent-activity table. - Banner ad system new — image or HTML banners (ad-network snippets) on the home and stats pages, with impression and click tracking, CTR reporting, optional expiry dates, editing and three rotation modes.
- White-label settings — site name, SEO description/keywords, logo and favicon are all editable from the dashboard.
- Dual storage engine — choose MySQL or a flat-file JSON store during installation. Both expose exactly the same features.
- Privacy minded — public stats pages never expose IP addresses, and the JSON data folder plus banner uploads are blocked from web access.
2. Server requirements
| Requirement | Notes |
|---|---|
| PHP 7.4 or newer (8.x recommended) | Developed and tested on PHP 8.2 / 8.3. |
Apache with mod_rewrite | Required for pretty links such as
/{code} and /{code}/stats. On Nginx use an equivalent
try_files rule. |
json, session, fileinfo extensions |
Present in almost every PHP build. fileinfo validates image uploads. |
mbstring (optional) | Renders the country flag emoji in analytics. |
curl (optional) | Resolves the visitor's country. Without it, countries show as Unknown. |
pdo_mysql (MySQL mode only) | Not needed with the JSON engine. |
| Write permission for the script folder | The installer writes
config.php; the app writes into data/ and uploads/. |
upload_max_filesize ≥ 2Mpost_max_size ≥ 3M |
Only relevant when uploading 2 MB banner images. |
3. Package contents
Upload the whole tree below, keeping the folder structure exactly as it is.
/
├── .htaccess Pretty-URL rewrite rules + directory browsing off
├── index.php Front controller: router, home page, analytics page
├── admin.php Admin dashboard (links, banner ads, white label)
├── install.php Installation wizard (delete after installing)
├── recover.php Password recovery using the License UUID
├── page.php Terms / Privacy / Contact pages
├── config.sample.php Example configuration (the installer writes config.php)
├── api/
│ ├── shorten.php Creates short links
│ └── stats.php Analytics JSON endpoint for the stats page
├── includes/
│ ├── db.php Storage abstraction - MySQL and JSON engines + banner schema
│ ├── functions.php Short-code generator, URL/IP/user-agent helpers
│ ├── banners.php Banner domain: types (image/HTML), expiry, rotation, admin UI
│ ├── ads.php Public endpoints /ad/impression/{id} and /ad/click/{id}
│ ├── header.php Site navigation
│ └── footer.php Footer (Terms, Privacy, Contact)
├── css/
│ └── style.css Legacy stylesheet + banner image rules
├── js/
│ ├── analytics.js Stats-page rendering (charts, tiles, tables)
│ ├── chart.min.js Chart.js 4.4.9 (bundled, no CDN required)
│ └── main.js Legacy helper, not loaded by the current UI
├── uploads/
│ └── banners/
│ └── .htaccess Blocks script execution inside the upload folder
├── data/
│ └── .htaccess Blocks web access to the JSON data store
└── Doc/
└── index.html This documentation
config.php (created by
the installer), install.lock, and the contents of data/ and
uploads/ (runtime data and your own branding images).Doc/ folder is only for your reference. You can keep it on the
server (it is linked from nowhere) or delete it once you are familiar with the script.4. Upload the files
Option A — cPanel / shared hosting
- Open File Manager and go to
public_html(orpublic_html/your-folderfor a subfolder install). - Upload the package ZIP and choose Extract in the same directory, so that
index.phpsits directly inside the target folder. - Select the extracted folder, choose Permissions and set 755 (with "recurse into subdirectories").
Option B — FTP (FileZilla and similar)
- Connect to your host and open the web root (
public_html,htdocs,www). - Upload every file and folder from the package, preserving the directory structure.
- Make sure hidden files are visible in your FTP client so that
.htaccessanddata/.htaccessare transferred too — they are what makes pretty links and data protection work.
Option C — local test server (XAMPP / WAMP / MAMP)
- Copy the package into
C:\xampp\htdocs\shortlink(Windows) or/Applications/XAMPP/htdocs/shortlink(macOS). - Start Apache (and MySQL if you want to use MySQL mode) from the XAMPP control panel.
- Continue with the installer at
http://localhost/shortlink/install.php.
php -S localhost:8000 router.php (the router script is only needed for pretty links
and is not part of the package).5. Run the installer
Open the installer in your browser:
https://your-domain.com/install.php
http://localhost/shortlink/install.php (local test server)
Step 1 — Requirements check
The wizard verifies the PHP version, extensions and folder permissions. If anything fails it tells you exactly what to fix (usually folder permissions of 755).
Step 2 — Configuration
| Field | What to enter |
|---|---|
| License UUID | Your purchase/installation UUID
(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). It is stored in
config.php and doubles as the master key for password recovery, so keep it
safe. |
| Site Name | Shown in the navigation, page titles and footer. |
| Site Installation URL | The exact URL where the script lives,
without a trailing slash — e.g. https://short.link or
https://example.com/shortlink. Every link, logo and banner is built from this
value, so it must be correct. |
| Database selection | JSON (flat file) for zero-setup hosting; MySQL for high-traffic sites. You pick one at install time (see section 8 to switch later). |
| MySQL host / name / user / password | Only when MySQL is
selected. On cPanel create the database and user first and use the
user_dbname form. The installer creates all tables
(shortened_urls, clicks, admins,
banners, banner_stats) automatically. |
| Admin username / password | The login for
/admin.php. Passwords are stored as bcrypt hashes. |
CREATE privileges the first time,
because the wizard runs CREATE DATABASE IF NOT EXISTS when the database does not exist
yet.Step 3 — Installation complete
When the success screen appears:
config.phphas been generated with your settings.install.lockhas been created, which stops anyone from reinstalling or hijacking the script.- The admin account exists, and in MySQL mode all tables have been created.
install.php from the server now. The lock file
already blocks it, but removing the file is the safest option.6. Post-install checklist
- Delete
install.phpfrom the server (keep a local copy in case you reinstall later). - Confirm
config.phpexists in the script root. If it does not, the folder was not writable — set permissions to 755 and run the installer again. - Open the home page and shorten a test URL. Copy the short link and open it: it should redirect to the destination and then show up in the analytics page.
- Log in to
/admin.phpand- upload your logo and favicon and set the SEO description/keywords (White Label Settings);
- upload at least one banner and choose a rotation mode (Banner Ads);
- check that the click counter for your test link shows 1.
- Recommended — enable HTTPS so short links and analytics run over TLS.
- Optional — set your timezone so analytics match your local clock: add
date_default_timezone_set('Asia/Kolkata');near the top ofindex.php, or setdate.timezoneinphp.ini. Without it, "Today" and the busiest-hour chart use the server default (often UTC).
7. Installing in a subfolder
ShortLink works at any depth. The only rule is that the Site Installation URL in the installer must match where the script actually lives:
| Location on the server | Site Installation URL |
|---|---|
public_html/ | https://your-domain.com |
public_html/shortlink/ | https://your-domain.com/shortlink |
public_html/tools/url/ | https://your-domain.com/tools/url |
C:\xampp\htdocs\shortlink | http://localhost/shortlink |
The bundled .htaccess rewrites relative to its own directory, so subfolder installs need
no manual rewrite edits. On Nginx add:
location /shortlink/ {
try_files $uri $uri/ /shortlink/index.php?$query_string;
}
8. Reinstalling / changing the storage engine
To run the installer again — for example to switch from JSON to MySQL — you must remove the guard files:
- Delete
install.lock. - Delete
config.php. - Open
install.phpagain and complete the wizard.
data/ folder (JSON mode) or dump your MySQL database, then import the rows into
the new engine manually — the tables/JSON keys mirror each other one to one.data/urls.json and data/banners.json and calls the same storage layer
the admin panel uses.9. Shortening links
Everything happens on the home page — no login required.
- Paste the long URL into the input field (include
http://orhttps://). - Press Shorten. A green result card appears with the finished link.
- Press Copy to copy it to the clipboard, or open the link directly.
- Open
/{code}/stats(or click Stats in the admin link table) to watch the traffic.
Behaviour worth knowing
| Situation | What ShortLink does |
|---|---|
| The same URL is submitted twice | Returns the existing short code, so you never create duplicates of the same destination. |
| A URL pointing at your own domain | Rejected with "Security Error: You cannot shorten URLs belonging to this domain." — this prevents redirect loops. |
| Missing/invalid scheme | Rejected with a message asking for
http:// or https://. |
| Short code format | 7 characters from 0-9 a-z A-Z (62 possibilities per
character), retried until unique. |
| Redirect type | 301 Moved Permanently, so browsers and search engines
follow the link efficiently. |
home position is used — see section 12.10. The analytics page
Every short code has its own report at /{code}/stats, for example
https://your-domain.com/5cnLTLu/stats. The page is public (anyone who knows the code can
view it) but it is marked noindex so search engines stay out, and it never reveals IP
addresses.
| Panel | What it shows |
|---|---|
| Total Clicks (large card) | All recorded visits, plus a trend chip comparing the last 7 days with the previous 7 days and the time of the most recent click. |
| Destination URL (large card) | The long URL, the short link, and the date the link started collecting data. |
| KPI tiles | Today, Last 7 Days, Last 30 Days, Countries and Referrers, each with a contextual hint (for example "previous 7 days: 12"). |
| Clicks Over Time | Daily bars for the last 14 days. If the link is quiet it widens to 30 or 90 days, and for older links it switches to weekly bars so the chart still shows the traffic. Best day and busiest hour are listed in the corner. |
| Devices / Browsers / Operating Systems | Doughnut charts built from the user agent of each visit. |
| Top Countries | Horizontal bars with flags, from the visitor's IP
(requires the optional curl extension). |
| Activity by Hour | 24 bars showing the time of day people click; the busiest hour is highlighted. |
| Traffic Sources | Visits grouped into Direct / Search Engines / Social Networks / Email / Other Referrals. |
| Top Referrers | The referring domains with click counts and percentages. |
| Recent Activity | The 10 latest visits: relative and exact time, browser/OS/device, country and source. Hover the visitor to see the full user-agent string. IP addresses are never displayed. |
Buttons on the report
- Refresh — reloads the numbers immediately (the page also auto-refreshes every 60 seconds while the tab is visible).
- Copy Short Link — copies the
/ad-aware short URL. - ← Shortener — back to the home page.
XX. On live hosting with real visitors the country codes fill in automatically.11. The admin panel
Log in at /admin.php with the account created during installation. The dashboard has three
sections.
Manage Links
- Table of every short link: code, destination, click count and creation date.
- Stats opens the analytics page for that link in a new tab.
- Delete removes the link and all of its click history (a confirmation dialog appears first).
- Pagination appears automatically once you have more than 10 links.
Banner Ads
Add image or HTML banners, choose the slot, set weights and an optional expiry date, edit an existing banner, flip the rotation mode and read the performance of each creative. Full details in section 12.
White Label Settings
- Site Name — navigation, page titles and the footer.
- Logo — PNG/JPG/GIF/WebP/Ico; replaces the text logo in the navigation bar.
- SEO Meta Description and Keywords — written into the page
<meta>tags. - Favicon —
.icoor.png.
define() lines in
config.php (and appends any that are missing), so the file must stay writable.Logging out
The navigation bar shows Dashboard and Logout while you are signed in. Sessions are PHP sessions, so closing the browser does not sign you out — use Logout on a shared computer.
13. Terms / Privacy / Contact pages
These are static pages served by page.php and linked from the footer:
| Page | URL |
|---|---|
| Terms of Service | /page.php?p=terms |
| Privacy Policy | /page.php?p=privacy |
| Contact | /page.php?p=contact |
includes/footer.php if you white-label the site.14. Password recovery
If you lose the admin password, open:
https://your-domain.com/recover.php
Enter the License UUID (the INSTALL_UUID value from
config.php, which is the same UUID you entered during installation) and choose a new
username and password. No e-mail is required.
INSTALL_UUID in config.php to a new UUID-value of your choosing (then update your
own records).php -r "echo password_hash('newpass', PASSWORD_DEFAULT);" and store it in
data/admin.json (JSON mode) or update the admins row (MySQL mode).15. config.php reference
config.php is generated by the installer; the admin panel updates some values for you. Never
commit it to a public repository.
| Constant | Purpose |
|---|---|
SITE_NAME | Site title used in the UI and page titles. |
SITE_DESCRIPTION | Meta description (added by White Label Settings). |
SITE_KEYWORDS | Meta keywords (added by White Label Settings). |
SITE_URL | The most important value. Base URL with no
trailing slash, e.g. https://example.com/shortlink. All links, redirects, asset
paths and ad endpoints derive from it. |
INSTALL_UUID | License/installation key; master key for
recover.php. |
DB_TYPE | 'json' or 'mysql'. |
DB_HOST, DB_NAME, DB_USER, DB_PASS |
MySQL connection details (ignored in JSON mode). |
JSON_DATA_DIR | Data folder for JSON mode, default
__DIR__ . '/data'. |
BANNER_ROTATION_MODE | 'random',
'sequential' or 'weighted'. Written by the admin panel; defaults to
weighted if the line is absent. |
Where the data lives
| Engine | Links | Clicks | Admin | Banners |
|---|---|---|---|---|
| JSON | data/urls.json |
data/clicks/{code}.json | data/admin.json |
data/banners.json + data/banner_stats/{id}.json |
| MySQL | shortened_urls | clicks |
admins | banners + banner_stats |
data/ folder and
uploads/ is a complete backup. In MySQL mode use mysqldump (or the cPanel backup
tool) plus the uploads/ folder.16. Troubleshooting
| Symptom | Cause & fix |
|---|---|
| Short links give 404 (the home page works) | The rewrite rules are not
active. Check that .htaccess was uploaded (enable "show hidden files" in your FTP
client), that Apache has mod_rewrite enabled and that the vhost allows
AllowOverride All. On Nginx, add the try_files rule from
section 7. |
| Page loads but looks unstyled, logo or banner image missing | SITE_URL does not match the real address. It must include the subfolder if any
and must have no trailing slash. Fix it in config.php (or reinstall). |
| Installer says the folder is not writable | Set the script folder to
755 and, on some hosts, 775. Then reload the installer. |
| 500 Internal Server Error after upload | Usually a permission or PHP version problem: set folders to 755 and files to 644, confirm PHP 7.4+ is selected in your hosting panel, then check the server error log. |
| Blank white page | A fatal PHP error is being hidden. Temporarily add
ini_set('display_errors', 1); error_reporting(E_ALL); at the top of the failing
script, reproduce the error, then remove it again. |
| Charts are empty on the stats page | Confirm
js/chart.min.js and js/analytics.js were uploaded. If a link has no
clicks yet the page shows "No data yet" placeholders by design. |
| Analytics counters stay at 0 | The click is only recorded when
somebody opens the short link itself (not the stats page). Also note browsers cache
301 redirects, so a link opened repeatedly from the same browser may not hit the
server every time — test with a different browser or a private window. |
| Countries always show Unknown | Either the optional curl
extension is missing, the server cannot reach the geo service, or you are testing from a private
IP (localhost). Real visitors on live hosting resolve normally. |
| "Today" / busiest hour look shifted | Server timezone. Set
date.timezone in php.ini or call
date_default_timezone_set() in index.php (see
section 6). |
| Banner upload fails with file too large | Raise
upload_max_filesize to 2M and post_max_size to
3M or higher, or optimise/compress the image below the limit. |
| A banner is not showing | Check the Banner Ads list: a Paused banner is not served, an Expired one (red badge next to its date — unbounded banners show Unlimited) is dropped from rotation until you extend the date, and the position must match the page you are looking at. If every banner for that slot is paused or expired, the "Advertise here" placeholder is shown on purpose. |
| Impressions are 0 while the banner is visible | The tracking pixel is blocked (ad blocker or privacy extension) or the page is served from cache. Test in a private window with extensions disabled. |
| Admin login fails | Use /recover.php with your License
UUID to set a new username and password (section 14). |
| Envato/softaculous points to install.php again | Delete
install.lock and config.php only when you actually intend to
reinstall — the lock file exists to protect your installation. |
| Redirect goes to the wrong place after you edit a destination | Your browser cached the old 301. Clear the cache or test in a private window;
the stored target itself is correct. |
https://your-domain.com/api/stats.php?code=YOURCODE
— if that returns JSON, storage and PHP are fine and the problem is in the front end (assets, JS,
templates).17. Security notes
| Item | Recommendation |
|---|---|
install.php | Delete it after installation. install.lock
already blocks re-running, but removing the file removes the risk entirely. |
config.php | Holds your database password and License UUID. Keep it private, and never publish it in a repository or support thread. |
| License UUID | Works as the master key for recover.php. Store it
somewhere safe (a password manager). |
data/.htaccess and uploads/banners/.htaccess | Must be uploaded. The first blocks direct downloads of your JSON data, the second prevents script execution inside the upload folder. |
| Admin password | Use a long, unique password; the admin panel is public and rate-limits nothing beyond PHP itself. |
| Public analytics | Anyone with a short code can read that link's statistics, so treat codes as private. Visitor IP addresses are never displayed: the analytics page and its JSON feed expose only the country, browser, operating system, referring site and timestamps. |
| HTTPS | Strongly recommended. Without it, admin credentials travel in clear text. |
| Banner target URLs | Only you can set them, and clicks always redirect to the stored value — the endpoints cannot be used to redirect visitors to arbitrary third-party sites. |
| HTML banners (raw code) | Whatever you paste into an HTML banner is stored and printed into the page as-is, scripts and iframes included. Treat the admin panel as a fully trusted tool: only use snippets from ad networks you have vetted, never share admin credentials, and delete the banner when the campaign ends. The image type has no such surface. |
| Directory listing | Options -Indexes is already set in the bundled
.htaccess files. |
| Backups | Back up data/ + uploads/ (JSON) or your database
(MySQL) on a schedule, and keep a copy off the server. |
18. FAQ
Do visitors need an account to shorten a link?
No. Only the admin panel at /admin.php is protected.
Which storage mode should I choose?
JSON if you want a zero-configuration install and modest traffic — one file per short code, easy to back up. MySQL if you expect heavy traffic or many links, because the database handles concurrent writes better than flat files.
Can I switch from JSON to MySQL later?
Yes, by reinstalling (see section 8), but your existing links, clicks and banners are not migrated automatically — export or re-create them.
Can I change the short codes?
Codes are generated automatically (7 characters, base62) and duplicates of the same destination share one code. There is no manual "custom alias" field in this version.
How long are click statistics kept?
Forever, until you delete the link (which deletes its clicks too). There is no automatic pruning, so very busy links accumulate large files in JSON mode.
How many banners can I add?
No limit, and you can mix both types. Add as many as you like per position and let the rotation mode decide which one shows.
Can I use an ad network snippet instead of an image?
Yes — choose the HTML code banner type and paste the snippet: a
<script> tag, an <iframe>, or plain
<a><img> markup. It is output verbatim. Because the snippet carries its own links,
clicks are not counted for HTML banners, but impressions still are. A target URL is not needed (and is not
stored) for them.
What happens to a banner when its expiry date passes?
It stops being served right away and the slot falls back to the next active banner, or to the "Advertise here" placeholder. The banner stays in the admin list with an Expired badge and keeps all of its statistics — open Edit, set a new date (or tick Unlimited) and save to bring it back. Banners created before this feature existed count as unlimited.
Can I edit a banner instead of deleting and re-adding it?
Yes — use Edit in the banner list. You can change the type, the image (leave the file field empty to keep the current one), the target URL, the HTML code, the position, the weight and the expiry date. Impressions and clicks are never reset by an edit, and switching a banner from Image to HTML keeps its stored file, so nothing is lost if you switch back.
Do banners appear on the 404 page or the Terms page?
No. Banner slots exist only on the home page (home) and the analytics page
(stats).
Why does my country show as "Unknown"?
You are almost certainly testing from a private IP address (localhost), which is never sent to the geo
lookup service. Real visitors resolve normally, provided the curl extension is enabled.
Can I remove the "Powered by" credit in the footer?
The credit lives in includes/footer.php and can be edited, but please respect the licence
terms of your purchase.
Will the analytics charts keep working offline?
Yes. Chart.js is bundled in js/chart.min.js, so no CDN connection is required.
Something is broken — where do I start?
Work through section 16, then check the browser console (F12) for missing assets and the server error log for PHP messages.