Skip to main content

Overview

All short links in shrtnr have an expiration date. This keeps the database clean, respects user privacy, and prevents dead links from accumulating. You can set expiration times from 1 to 30 days when creating or editing links.

Default Expiration

All links have automatic expiration:
  • Guest users: 7 days (fixed, cannot be changed)
  • Authenticated users: 7 days (can be customized from 1-30 days)
// From app/lib/links.ts:6-7
const DEFAULT_EXPIRY_DAYS = 7;
const MAX_EXPIRY_DAYS = 30;
The 30-day maximum ensures the database doesn’t accumulate permanent links while giving users flexibility for medium-term campaigns.

How Expiration Works

When a link is created:
1

Set expiration days

Choose how many days (1-30) until the link expires, or accept the default 7 days.
2

Calculate expiration date

shrtnr calculates the exact expiration timestamp: current time + (days × 24 hours).
3

Store in database

The expiration timestamp is stored in the expires_at column.
4

Set Redis TTL

The cache entry is given a matching TTL (time to live) in seconds.
// From app/lib/links.ts:39-40
const days = clampExpiryDays(expiresInDays);
const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000);

Setting Expiration on Creation

For Authenticated Users

On the home page, signed-in users can set custom expiration:
  1. Paste your URL
  2. Find the “Expires in (days, 1–30)” field
  3. Enter a number between 1 and 30
  4. Click Shorten
// From app/page.tsx:26,144-145
const [expiresInDays, setExpiresInDays] = useState(DEFAULT_EXPIRY_DAYS);

value={expiresInDays}
onChange={(e) => setExpiresInDays(Math.min(MAX_EXPIRY_DAYS, Math.max(1, Number(e.target.value) || 7)))}
The input automatically clamps values: entering 0 or negative numbers defaults to 7 days, while values over 30 are capped at 30 days.

For Guest Users

Guest users cannot customize expiration:
  • All guest links expire in exactly 7 days
  • No option to extend or shorten the expiration
  • Links cannot be managed after creation
To get custom expiration times, see the Authentication guide to create an account or sign in.

Editing Expiration Dates

Authenticated users can change expiration dates after creation:
1

Open your dashboard

Navigate to your dashboard (see Dashboard guide) to see all your links.
2

Click Edit on the link

Find the link you want to modify and click the Edit button.
3

Select new expiration date

Click the expiration date picker and choose a new date (up to 30 days from today).
4

Save changes

Click Save to update the expiration date.
// From app/dashboard/page.tsx:320-340
<Label>Expires (optional, max 30 days from today)</Label>
<Popover>
  <PopoverTrigger asChild>
    <Button variant="outline" className="w-full justify-start text-left font-normal">
      {editExpires ? format(editExpires, 'PPP') : 'Pick a date'}
    </Button>
  </PopoverTrigger>
  <PopoverContent className="w-auto p-0" align="start">
    <Calendar
      mode="single"
      selected={editExpires ?? undefined}
      onSelect={(d) => setEditExpires(d ?? null)}
      disabled={(d) => d < minDate || d > maxDate}
      initialFocus
    />
  </PopoverContent>
</Popover>

Expiration Date Constraints

When editing expiration:
  • Minimum date: Today (cannot set past dates)
  • Maximum date: 30 days from today
  • Null value: You can remove expiration by clearing the date (sets to null)
// From app/dashboard/page.tsx:121-123
const minDate = new Date();
const maxDate = new Date();
maxDate.setDate(maxDate.getDate() + 30);
Setting expiration to null (no date) may not be fully supported in the current implementation. The database expects an expiration timestamp.

Validation & Clamping

shrtnr automatically validates and corrects invalid expiration values:
// From app/lib/links.ts:20-23
function clampExpiryDays(days: number): number {
  if (Number.isNaN(days) || days < 1) return DEFAULT_EXPIRY_DAYS;
  return Math.min(MAX_EXPIRY_DAYS, Math.max(1, Math.floor(days)));
}
InputResultReason
07 daysBelow minimum, use default
-57 daysBelow minimum, use default
1515 daysValid, use as-is
4530 daysAbove maximum, clamp to max
7.87 daysDecimal floored to 7
NaN7 daysInvalid number, use default
When someone tries to access an expired link:
  1. Redis cache may have already evicted the entry (TTL expired)
  2. Database query checks if expires_at is in the past:
    WHERE short_code = ? AND (expires_at IS NULL OR expires_at > NOW())
    
  3. 404 response: If expired, return “Not found” without redirecting
  4. No click tracking: Expired links don’t record analytics
// From app/lib/links.ts:74-78
const result = await db`
  SELECT original_url FROM urls
  WHERE short_code = ${shortCode}
    AND (expires_at IS NULL OR expires_at > NOW())
  LIMIT 1
`;
Expired links return a standard 404 error page, not a custom “link expired” message.

Redis Cache TTL

The Redis cache respects link expiration:
// From app/lib/links.ts:60-61
const ttlSeconds = Math.max(1, Math.floor((expiresAt.getTime() - Date.now()) / 1000));
await redis.set(`${REDIS_PREFIX}${shortCode}`, trimmed, { ex: ttlSeconds });
  • TTL calculation: Expiration time minus current time, in seconds
  • Minimum TTL: 1 second (prevents immediate expiration)
  • Auto-eviction: Redis automatically removes expired entries
Redis TTL ensures expired links are removed from cache even if database cleanup hasn’t run yet.

Viewing Expiration in Dashboard

Your dashboard shows expiration dates for all links:

Desktop Table View

The “Expires” column displays formatted dates:
// From app/dashboard/page.tsx:216-220
<TableCell className="text-muted-foreground">
  {link.expires_at
    ? format(new Date(link.expires_at), 'MMM d, yyyy')
    : '—'}
</TableCell>
Example: Mar 15, 2026

Mobile Card View

Expiration appears in the link details:
// From app/dashboard/page.tsx:275
<span>{link.expires_at ? format(new Date(link.expires_at), 'MMM d, yyyy') : 'No expiry'}</span>
Links without expiration show ”—” (desktop) or “No expiry” (mobile), but this may not occur in practice since all links have expiration dates.

Best Practices

Expiration Strategy

Short-term campaigns (1-7 days)
  • Social media posts
  • Flash sales
  • Event registrations
Medium-term campaigns (7-14 days)
  • Newsletter links
  • Weekly promotions
  • Temporary landing pages
Long-term links (14-30 days)
  • Monthly campaigns
  • Seasonal promotions
  • Extended beta programs
Tip: Set expiration slightly longer than needed to avoid links expiring while campaigns are active.

Common Questions

Yes! Edit the link in your dashboard and set a new expiration date (up to 30 days from today). You can edit the link before it expires to extend it another 30 days. However, this requires manual intervention. Expired links remain in the database but won’t redirect. Future cleanup jobs may remove old expired links.

Can I remove expiration entirely?

The current implementation expects all links to have expiration dates. Removing expiration may not work as expected.
  • URL Shortening - Create short links with automatic expiration
  • Dashboard - View and edit link expiration dates
  • Custom Slugs - Create memorable links with custom expiration
  • Analytics - Track link performance before expiration

Build docs developers (and LLMs) love