ShortLink — Installation & User Guide

Self-hosted URL shortener with click analytics and a built-in banner ad manager.

Version 2.0 PHP 7.4 – 8.3 MySQL or JSON storage No framework required

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.
Who needs the admin panel? Shortening and analytics are public. The admin panel is only for managing links, banners and branding.

2. Server requirements

RequirementNotes
PHP 7.4 or newer (8.x recommended)Developed and tested on PHP 8.2 / 8.3.
Apache with mod_rewriteRequired 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 folderThe installer writes config.php; the app writes into data/ and uploads/.
upload_max_filesize ≥ 2M
post_max_size ≥ 3M
Only relevant when uploading 2 MB banner images.
Everything works on a plain XAMPP installation. On shared hosting, PHP 8.x with the default extension set is enough.

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
Files you will never upload: config.php (created by the installer), install.lock, and the contents of data/ and uploads/ (runtime data and your own branding images).
The 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

  1. Open File Manager and go to public_html (or public_html/your-folder for a subfolder install).
  2. Upload the package ZIP and choose Extract in the same directory, so that index.php sits directly inside the target folder.
  3. Select the extracted folder, choose Permissions and set 755 (with "recurse into subdirectories").

Option B — FTP (FileZilla and similar)

  1. Connect to your host and open the web root (public_html, htdocs, www).
  2. Upload every file and folder from the package, preserving the directory structure.
  3. Make sure hidden files are visible in your FTP client so that .htaccess and data/.htaccess are transferred too — they are what makes pretty links and data protection work.

Option C — local test server (XAMPP / WAMP / MAMP)

  1. Copy the package into C:\xampp\htdocs\shortlink (Windows) or /Applications/XAMPP/htdocs/shortlink (macOS).
  2. Start Apache (and MySQL if you want to use MySQL mode) from the XAMPP control panel.
  3. Continue with the installer at http://localhost/shortlink/install.php.
No Apache? PHP's built-in server works for development: 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

FieldWhat to enter
License UUIDYour 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 NameShown in the navigation, page titles and footer.
Site Installation URLThe 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 selectionJSON (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 / passwordOnly 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 / passwordThe login for /admin.php. Passwords are stored as bcrypt hashes.
MySQL mode also needs 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.php has been generated with your settings.
  • install.lock has been created, which stops anyone from reinstalling or hijacking the script.
  • The admin account exists, and in MySQL mode all tables have been created.
Delete install.php from the server now. The lock file already blocks it, but removing the file is the safest option.

6. Post-install checklist

  1. Delete install.php from the server (keep a local copy in case you reinstall later).
  2. Confirm config.php exists in the script root. If it does not, the folder was not writable — set permissions to 755 and run the installer again.
  3. 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.
  4. Log in to /admin.php and
    • 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.
  5. Recommended — enable HTTPS so short links and analytics run over TLS.
  6. Optional — set your timezone so analytics match your local clock: add date_default_timezone_set('Asia/Kolkata'); near the top of index.php, or set date.timezone in php.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 serverSite 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\shortlinkhttp://localhost/shortlink
No trailing slash, and the sub-path must match the folder name exactly (case-sensitive on Linux). A wrong value shows up as unstyled pages, a missing logo or broken banner images.

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:

  1. Delete install.lock.
  2. Delete config.php.
  3. Open install.php again and complete the wizard.
Your data is not migrated automatically. Before switching engines, back up the 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.
Moving to MySQL on a site that was installed with JSON: simply reinstall in MySQL mode and then re-create your links/banners from the admin panel, or import them with a small script that reads 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.

  1. Paste the long URL into the input field (include http:// or https://).
  2. Press Shorten. A green result card appears with the finished link.
  3. Press Copy to copy it to the clipboard, or open the link directly.
  4. Open /{code}/stats (or click Stats in the admin link table) to watch the traffic.

Behaviour worth knowing

SituationWhat ShortLink does
The same URL is submitted twiceReturns the existing short code, so you never create duplicates of the same destination.
A URL pointing at your own domainRejected with "Security Error: You cannot shorten URLs belonging to this domain." — this prevents redirect loops.
Missing/invalid schemeRejected with a message asking for http:// or https://.
Short code format7 characters from 0-9 a-z A-Z (62 possibilities per character), retried until unique.
Redirect type301 Moved Permanently, so browsers and search engines follow the link efficiently.
A banner (or the "Advertise here" placeholder) is shown above the input field when the 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.

PanelWhat 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 tilesToday, Last 7 Days, Last 30 Days, Countries and Referrers, each with a contextual hint (for example "previous 7 days: 12").
Clicks Over TimeDaily 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 SystemsDoughnut charts built from the user agent of each visit.
Top CountriesHorizontal bars with flags, from the visitor's IP (requires the optional curl extension).
Activity by Hour24 bars showing the time of day people click; the busiest hour is highlighted.
Traffic SourcesVisits grouped into Direct / Search Engines / Social Networks / Email / Other Referrals.
Top ReferrersThe referring domains with click counts and percentages.
Recent ActivityThe 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.
Countries show "Unknown" on a local test server. Private IP addresses (127.0.0.1, 192.168.x.x) are never sent to the geo lookup service, so they are recorded as 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 — .ico or .png.
Saving this form rewrites the matching 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.

12. Banner ads

ShortLink can rotate its own banner ads in two positions. A banner is either an uploaded image or a block of HTML code, and each one can carry an optional expiry date. Impressions, clicks and CTR are tracked per banner — on both storage engines.

Slots

PositionWhere it appearsSuggested size
homeTop of the shortening card on the home page, above the URL input.468 × 60 or 728 × 90
statsTop of the analytics card, above the report.728 × 90 or 970 × 90

If no banner is active for a slot, a dashed "Advertise here" box is shown instead, linking to https://onwebco.com/contact.

Banner types

TypeWhat you provideClick tracking
Image A JPG, PNG, GIF or WebP file (max 2 MB) plus the Target URL a click should open. Yes — the image is wrapped in a link to /ad/click/{id}.
HTML code Any snippet you paste: a plain <a> / <img> pair, an <iframe> from an ad network, or a <script> tag. No — the snippet brings its own links, so only impressions are counted and the Clicks / CTR columns show an em dash.
HTML banners are printed exactly as pasted. The code is stored and output verbatim, so only use snippets from sources you trust, and never give anyone else access to the admin panel — see section 17. Impressions are still counted through the same hidden tracking pixel.
Both types can live in the same slot — the rotation modes treat them identically.

Adding a banner

  1. Choose the Banner type (Image file or HTML code); the form switches between the image and the code fields automatically.
  2. Image file: pick a JPG, PNG, GIF or WebP, maximum 2 MB. The type is verified by file content (not by file name) and the saved extension comes from the detected type. Then enter the Target URL (where a click should go) — only http:// / https:// URLs are accepted.
  3. HTML code: paste the snippet into the textarea. It is stored exactly as pasted and no target URL is needed (or stored).
  4. Pick the Position (Home page / Stats page).
  5. Set a Weight (1–100) — used only by weighted rotation.
  6. Set the Expiry date (the field is pre-filled with 30 days) or tick Unlimited (never expires).
  7. Press Add Banner. It goes live immediately.

Rotation modes

ModeBehaviour
RandomAny active banner for the slot is picked with equal probability.
SequentialRound-robin: the banner shown longest ago goes next (never-shown banners first), so impressions equalise across the slot.
WeightedProbability is proportional to the weight. With weights 5 and 1 you get roughly five impressions of the first banner for every one of the second.

The mode is stored in config.php as define('BANNER_ROTATION_MODE', 'weighted'); and applies to every slot. A brand-new installation does not contain that line yet and falls back to weighted; saving a mode in the admin panel adds it.

The banner list

  • Thumbnail (or an HTML chip), target URL, ID and date for each banner.
  • Type — Image or HTML.
  • Weight, Impressions, Clicks and CTR (clicks ÷ impressions as a percentage; an em dash until the first impression — and always for HTML banners, which are not click tracked).
  • Expires — the expiry date, Unlimited, or the date together with a red Expired badge and a "hidden while expired" note under the status.
  • Status — Active or paused, toggled with the Pause / Activate link.
  • Edit — re-opens the banner in a pre-filled form (type, fields, position, weight, expiry). Leave the file field empty to keep the current image; switching a banner from Image to HTML keeps the stored file, so nothing has to be re-uploaded if you switch back.
  • Delete — removes the record, the image file and its statistics.

Expiry dates

Every banner can expire: the visitor-facing slots stop serving it as soon as the stored date has passed, so an expired banner drops out of the rotation and the slot moves on to the next active banner (or to the "Advertise here" placeholder).

  • The banner itself is never deleted — it stays in the list with an Expired badge, so you can extend it, re-activate it or re-run it later.
  • Unlimited (the checkbox) stores no date at all and never expires — that is also what banners created by older versions use.
  • The check runs server-side on every page view, never in JavaScript, so it cannot be bypassed by the visitor or cached away.
  • To revive an expired banner: Edit → set a new date (or tick Unlimited) → Save Changes.
Expiry is the easiest way to run booked campaigns: add the creative, set the end date, and let the slot fall back on its own.

How tracking works

EndpointWhat happens
GET /ad/impression/{id}Records an impression and returns a 1×1 transparent GIF. The banner markup embeds it as a hidden pixel, so one page view equals one impression. ?format=204 returns an empty 204 instead.
GET /ad/click/{id}Records a click and issues a 302 redirect to the banner's stored target URL. The destination is read from storage and never from the query string, so the endpoint cannot be abused as an open redirect.
HTML bannersRendered inside a centred, max-width container together with the same hidden impression pixel, but without any click wrapper — the links in your snippet are used as they are, so Clicks and CTR stay at an em dash for them.
Pausing a banner stops it being served immediately: the slot falls back to the "Advertise here" placeholder for new page views, and no further impressions are counted. If a visitor already had a page open with that banner, its click link and pixel simply return to your home page without being recorded. Both endpoints live on your own domain, so ad blockers rarely interfere.

Best practice

  • Keep banners at the suggested pixel sizes — they scale down responsively but look sharpest at their native width.
  • Add UTM parameters to target URLs (for example ?utm_source=banner&utm_campaign=home) so you can measure each slot.
  • Use weighted rotation to favour your best creative, and check CTR in the banner list weekly.
  • Pause instead of deleting a banner you may re-run later — deleting also discards its statistics.
  • Set an Expiry date for anything with a fixed booking, and Unlimited for your own house ads.
  • Keep HTML snippets small and HTTPS-only — heavy third-party iframes slow down the home page they are embedded in.
Already installed? No reinstall is needed for this feature. On MySQL the three columns behind HTML banners and expiry dates (type, html_code, expiry_date) are added to the banners table automatically the first time the admin panel or a banner slot is loaded, and existing banners keep working untouched (they are treated as unlimited image banners). In JSON mode the new keys appear as soon as a banner is saved. A fresh installation gets them straight from install.php.

13. Terms / Privacy / Contact pages

These are static pages served by page.php and linked from the footer:

PageURL
Terms of Service/page.php?p=terms
Privacy Policy/page.php?p=privacy
Contact/page.php?p=contact
Edit this text before you go live. The bundled content is placeholder copy (it even contains a note addressed to the script buyer). Replace it with your own wording, your support email and your company details, then also update the footer credit in 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.

Treat the UUID as a master password. Anyone who knows it can reset the admin account of your installation. Keep it private, and if it ever leaks, change the value of INSTALL_UUID in config.php to a new UUID-value of your choosing (then update your own records).
Alternative — direct reset: generate a hash with 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.

ConstantPurpose
SITE_NAMESite title used in the UI and page titles.
SITE_DESCRIPTIONMeta description (added by White Label Settings).
SITE_KEYWORDSMeta keywords (added by White Label Settings).
SITE_URLThe 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_UUIDLicense/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_DIRData 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

EngineLinksClicksAdminBanners
JSONdata/urls.json data/clicks/{code}.jsondata/admin.json data/banners.json + data/banner_stats/{id}.json
MySQLshortened_urlsclicks adminsbanners + banner_stats
Backups: in JSON mode, archiving the data/ folder and uploads/ is a complete backup. In MySQL mode use mysqldump (or the cPanel backup tool) plus the uploads/ folder.

16. Troubleshooting

SymptomCause & 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 writableSet the script folder to 755 and, on some hosts, 775. Then reload the installer.
500 Internal Server Error after uploadUsually 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 pageA 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 pageConfirm 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 0The 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 UnknownEither 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 shiftedServer 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 largeRaise 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 showingCheck 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 visibleThe 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 failsUse /recover.php with your License UUID to set a new username and password (section 14).
Envato/softaculous points to install.php againDelete 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.
Two-minute diagnostic: open 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

ItemRecommendation
install.phpDelete it after installation. install.lock already blocks re-running, but removing the file removes the risk entirely.
config.phpHolds your database password and License UUID. Keep it private, and never publish it in a repository or support thread.
License UUIDWorks as the master key for recover.php. Store it somewhere safe (a password manager).
data/.htaccess and uploads/banners/.htaccessMust be uploaded. The first blocks direct downloads of your JSON data, the second prevents script execution inside the upload folder.
Admin passwordUse a long, unique password; the admin panel is public and rate-limits nothing beyond PHP itself.
Public analyticsAnyone 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.
HTTPSStrongly recommended. Without it, admin credentials travel in clear text.
Banner target URLsOnly 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 listingOptions -Indexes is already set in the bundled .htaccess files.
BackupsBack 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.