npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

url-unshortener

v0.1.1

Published

Expand shortened URLs to their final destination. Zero dependencies.

Readme

url-unshortener

node license version

Expand shortened URLs to their final destination. Follows the full redirect chain using Node.js built-in HTTP — zero runtime dependencies.

npx unshort https://bit.ly/xyz
# https://example.com/the-real-page

Install

npm install url-unshortener
# or
bun add url-unshortener

Requires Node.js 18+


Library

expand(url, options?)

Expands a single URL and returns the final destination.

import { expand } from 'url-unshortener'

const result = await expand('https://bit.ly/xyz')

console.log(result.expandedUrl)      // 'https://example.com/the-real-page'
console.log(result.statusCode)       // 200
console.log(result.redirectChain)    // ['https://bit.ly/xyz', ...]

expandMany(urls, options?)

Expands multiple URLs concurrently. Always returns one result per input URL — failures are captured in result.error rather than throwing.

import { expandMany } from 'url-unshortener'

const results = await expandMany([
  'https://bit.ly/a',
  'https://t.co/b',
  'https://tinyurl.com/c',
])

for (const result of results) {
  if (result.error) {
    console.error(`${result.originalUrl} failed: ${result.error}`)
  } else {
    console.log(`${result.originalUrl} → ${result.expandedUrl}`)
  }
}

API Reference

ExpandOptions

| Option | Type | Default | Description | |---|---|---|---| | timeout | number | 10000 | Request timeout in milliseconds | | maxRedirects | number | 10 | Maximum redirects to follow | | userAgent | string | Chrome UA | User-Agent header | | headers | Record<string, string> | — | Additional request headers | | allowHttpDowngrade | boolean | true | Allow redirects from https: to http:. Set to false to reject downgrades | | allowedHosts | string[] | — | If set, only follow redirects to these hostnames. Requests to any other host throw | | blockPrivateIPs | boolean | true | Block requests to loopback, RFC1918, link-local, and cloud metadata addresses | | concurrency | number | 5 | Max concurrent expansions (expandMany only) |

ExpandResult

interface ExpandResult {
  originalUrl: string       // The URL you passed in
  expandedUrl: string       // Final destination after all redirects
  statusCode: number        // HTTP status of the final response
  redirectChain: string[]   // Each intermediate URL (not including final)
  error?: string            // Set on failure (expandMany only)
}

Error types

All errors extend ExpandError which exposes a url property pointing to the URL that caused the failure.

import { ExpandError, TimeoutError, MaxRedirectsError, InvalidUrlError } from 'url-unshortener'

try {
  await expand('https://bit.ly/xyz', { timeout: 3000, maxRedirects: 5 })
} catch (err) {
  if (err instanceof TimeoutError)      console.error('Timed out')
  if (err instanceof MaxRedirectsError) console.error('Too many redirects')
  if (err instanceof InvalidUrlError)   console.error('Bad URL or protocol')
  if (err instanceof ExpandError)       console.error('Expansion failed:', err.url)
}

CLI

npx unshort <url> [url2 ...] [options]
# alias: npx unshort <url> [url2 ...] [options]

| Flag | Description | |---|---| | --chain | Print each redirect hop | | --json | Output as JSON | | --timeout <ms> | Request timeout in ms (default: 10000) | | --help | Show usage |

Rich output

By default the CLI prints a human-readable summary for each URL:

  Original    https://bit.ly/xyz
  Expanded    https://example.com/the-real-page
  Status      200 OK
  Security    ✓ HTTPS
  Hops        2 redirects

  Query Parameters (3)
    utm_source    newsletter          TRACKING
    ref           homepage            AFFILIATE
    page          about               OTHER
  • Status is color-coded: green for 2xx, yellow for 3xx, red for 4xx/5xx.
  • Security flags ⚠ HTTP if the final URL is not HTTPS.
  • Hops is only shown when the URL required at least one redirect.
  • Query Parameters classifies each param as TRACKING (UTM, fbclid, gclid, …), AFFILIATE (ref, tag, partner, …), or OTHER.
  • Very long URLs are truncated in display mode. Use --json to get the full untruncated URL.

Column widths in the summary block adjust based on label length; the example above is representative.

Colors are automatically disabled when output is piped.

Examples

# Expand a single URL
npx unshort https://bit.ly/xyz

# Show the full redirect chain
npx unshort https://bit.ly/xyz --chain

# Expand multiple URLs at once
npx unshort https://bit.ly/a https://t.co/b

# Machine-readable JSON output (full URLs, no truncation)
npx unshort https://bit.ly/xyz --json

# Custom timeout
npx unshort https://bit.ly/xyz --timeout 5000

Security

This library makes outbound HTTP/S requests to arbitrary URLs and follows redirects. Treat it as an SSRF primitive — do not pass raw user input to expand() or expandMany() in a server-side context without caller-side controls.

Server-side usage

When expanding user-supplied URLs on a server, apply restrictions:

const result = await expand(userSuppliedUrl, {
  allowHttpDowngrade: false,              // reject https → http downgrades
  allowedHosts: ['bit.ly', 't.co', 'tinyurl.com'], // only known shorteners
  timeout: 5000,
  maxRedirects: 5,
})

What this library does not protect against

  • DNS rebindingblockPrivateIPs and allowedHosts check the URL hostname, not the resolved IP. A public hostname that resolves to a private IP at connection time is not blocked. Combine with network-level egress filtering if full SSRF protection is required.
  • Response body content — the library never reads or saves response bodies (it closes the connection after headers), so shell scripts and other payloads are not downloaded.

HTTPS → HTTP downgrade

By default, redirects from https: to http: are permitted (common in the wild). Set allowHttpDowngrade: false to reject them.


License

MIT — Aman Harsh