How to Make a URL Shortener

July 29, 2026 | URLVanish Team | 10 min read

Short answer: a URL shortener is a table mapping a short code to a long URL, an endpoint that creates codes, and a catch-all route that redirects. The core is about fifty lines. The hard part is everything after that — abuse handling, uptime and permanence — because every link you issue breaks the day your service stops. This guide covers both: how to build one properly, and how to decide whether you should.

We run URLVanish, so we have opinions formed by operating one rather than by writing a tutorial. Where building your own is the right call, we will say so.

1. The data model

Almost everything you need is in one table:

CREATE TABLE urls (
    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
    alias         VARCHAR(32)  NOT NULL UNIQUE,
    original_url  TEXT         NOT NULL,
    user_id       BIGINT       NULL,
    status        ENUM('active','suspended') DEFAULT 'active',
    access_count  BIGINT       DEFAULT 0,
    created_at    TIMESTAMP    DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_alias (alias),
    INDEX idx_user  (user_id)
);

Three decisions in there are worth explaining, because they are the ones people get wrong:

2. Generating short codes

Two viable strategies, and the choice has real consequences.

Base62-encode the auto-increment ID

Take the row's numeric ID and encode it into [0-9a-zA-Z]:

function base62_encode(int $n): string {
    $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    if ($n === 0) return '0';
    $out = '';
    while ($n > 0) {
        $out = $chars[$n % 62] . $out;
        $n = intdiv($n, 62);
    }
    return $out;
}

Uniqueness is free because IDs are unique, and codes are as short as mathematically possible — 624 is already 14.7 million combinations. The cost is that links are sequential and enumerable. If /a7 exists, someone can trivially walk /a8, /a9 and harvest every link on your service. For a personal project that may be fine. For anything where users expect their links to be private, it is disqualifying.

Random codes with a collision check

function generate_alias(PDO $pdo, int $len = 8): string {
    $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $stmt  = $pdo->prepare('SELECT 1 FROM urls WHERE alias = ?');
    for ($attempt = 0; $attempt < 10; $attempt++) {
        $alias = '';
        for ($i = 0; $i < $len; $i++) {
            $alias .= $chars[random_int(0, 61)];
        }
        $stmt->execute([$alias]);
        if (!$stmt->fetchColumn()) return $alias;
    }
    throw new RuntimeException('Could not generate a unique alias');
}

Slightly longer codes and one extra query, but links are unguessable. Note random_int() rather than rand() — it is cryptographically secure, and with a predictable PRNG your “random” codes are enumerable too, which defeats the entire point.

Rule of thumb: if links should be private, use random codes. If maximum compactness matters and enumeration does not, encode the ID. URLVanish uses random codes, because people shortening a link generally do not expect strangers to be able to enumerate it.

Reserve your own routes first

Maintain a blocklist of aliases that must never be generated or claimed: api, admin, login, blog, assets, static, robots.txt, favicon.ico and every route your application already serves. Skip this and someone will eventually register /login as a short link and break your own sign-in page. Add profanity and brand-impersonation filtering to the same list if you allow custom aliases.

3. The redirect

A catch-all route, matched last, after every real route:

$alias = trim($_SERVER['REQUEST_URI'], '/');

$stmt = $pdo->prepare(
    'SELECT original_url, status FROM urls WHERE alias = ?'
);
$stmt->execute([$alias]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$row) {
    http_response_code(404);
    exit;
}

if ($row['status'] !== 'active') {
    header('Location: /suspended', true, 302);
    exit;
}

$pdo->prepare('UPDATE urls SET access_count = access_count + 1 WHERE alias = ?')
    ->execute([$alias]);

header('Location: ' . $row['original_url'], true, 301);
exit;

301 or 302?

This trips up nearly everyone:

Choose deliberately: 301 for permanent, fire-and-forget links; 302 if editability or accurate analytics matter. Deciding by accident is how people end up with a click counter stuck at 40 and no idea why.

Open redirect — do not skip this. Validate the destination scheme before storing it. Accept only http and https. Rejecting javascript:, data: and file: URLs is not optional — without it you have shipped a stored-XSS vector and a phishing relay that will be found by automated scanners within days of going live.

4. The parts that are actually hard

The code above works. It is also perhaps 10% of running a shortener.

Abuse

An open shortener is a magnet for phishing and malware distribution, and this begins within days of launch, not months. You need, at minimum: rate limiting per IP; a destination reputation check at creation time; an abuse-reporting route; and a way to suspend a link quickly. Without these, your domain lands on blocklists and every link you have issued stops working — including the legitimate ones. We covered the dynamics of this in why short links get blocked.

Uptime, permanently

This is the obligation people underestimate most. A shortener is not a normal app: every link you have ever issued is a permanent promise. Someone prints your link on a business card and expects it to work in five years. If your server goes away, every one of those links dies at once, with no fallback and no way to notify anyone. Google retired goo.gl and broke links across the entire web — if it can happen to Google, plan for it happening to you.

Performance on the hot path

Redirects should resolve in single-digit milliseconds. Put the alias lookup behind a cache (Redis or Memcached) once traffic justifies it, and make the click-count update asynchronous — write to a queue and batch the increments, rather than issuing a synchronous UPDATE on every single redirect.

Storage growth and analytics

The urls table is small and grows slowly. If you log individual click events for analytics, that table grows without limit and will dwarf everything else. Decide your retention policy before you start logging, not after the table reaches a hundred million rows.

5. Should you build one at all?

Build your own if:

  • You need links on your own branded domain, and branding is the point
  • You have compliance requirements about where link data is stored
  • Short links are core to your product rather than a convenience
  • You are doing it to learn — it is a genuinely good project for that

Use an existing service if:

  • You just need working short links
  • You do not want to own an uptime commitment measured in years
  • You do not want to run abuse moderation
  • You need QR codes, analytics or an API without building them

If you are shortening links from software, the free URLVanish API gives you the whole thing — POST a long URL, get a short one back — with no usage fee, no monthly cap and no infrastructure of your own. Self-hosted options such as YOURLS and Shlink are worth a look if you want to run it yourself but not write it yourself.

Skip the infrastructure

Free API, no usage limits, no uptime obligation of your own.

Read the API docs

Frequently asked questions

How do you make a URL shortener?

A table mapping a unique short code to a long URL, an endpoint that creates codes, and a catch-all route that looks up an incoming code and redirects. That core is about fifty lines in most frameworks. Abuse handling, uptime and permanence are the difficult parts.

How do you generate short codes?

Either base62-encode an auto-incrementing ID — shortest possible codes, but sequential and enumerable — or generate a random 6–8 character string and retry on collision, which is unguessable but needs a uniqueness check. Use random codes when links should be private.

Should a URL shortener use 301 or 302?

301 if the destination will never change and you want caching and link equity. 302 if you need editable destinations or accurate click counts, because a cached 301 stops reaching your server entirely.

What database should I use?

Any relational database is fine — MySQL, PostgreSQL and SQLite all handle this workload comfortably. The access pattern is a single indexed key lookup, which every database is good at. Add a cache layer when traffic justifies it, not before.

How do I stop my shortener being used for phishing?

Rate-limit creation per IP, check destination reputation at creation time, provide an abuse-reporting route, and be able to suspend a link quickly. Expect abuse attempts within days of launch. If your domain gets blocklisted, every link you have issued stops working.

Is it worth building your own URL shortener?

Worth it for branded domains, compliance requirements, or learning. Not worth it if you just need working short links — you would be taking on a permanent uptime obligation for something a free API already does.

Published July 29, 2026 by the URLVanish Team.

Related reading: How to shorten a URLFree URL shortener APIShort link safetyPermanent short links