shivella/laravel-bitly
Laravel Bitly is a simple wrapper for the Bitly API that lets Laravel apps shorten URLs using a clean facade. Configure your Bitly token, generate short links, and integrate URL shortening into your projects quickly.
Install via Composer
composer require shivella/laravel-bitly
Laravel 5.5+ auto-discovers Shivella\Bitly\BitlyServiceProvider and the Bitly facade. On older versions, register both manually in config/app.php.
Publish the config
php artisan vendor:publish --provider="Shivella\Bitly\BitlyServiceProvider"
This drops config/bitly.php which reads env('BITLY_ACCESS_TOKEN').
Get a token from the Bitly dashboard (Settings → API → Generic Access Token) and drop it into .env:
BITLY_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
First use case — shorten a URL in Tinker
php artisan tinker
use Shivella\Bitly\Facade\Bitly;
Bitly::getUrl('https://example.com/some/long/path');
// => "https://bit.ly/3xYzAbC"
If that returns a string, the whole package is wired correctly. If it fails, you'll typically see a Guzzle exception with the Bitly error payload — start by verifying the token and that outbound HTTPS to api-ssl.bitly.com is not blocked.
Where to look first in the source — src/Bitly.php has the five or six actual methods (getUrl, getClicks, getClicksSummary, and their underlying HTTP calls). It's a very small package; reading it end to end takes 5 minutes and tells you exactly what is and isn't supported.
Inject the service in a controller/job rather than reaching for the facade everywhere:
use Shivella\Bitly\Facade\Bitly;
public function store(StorePostRequest $request)
{
$shortUrl = Bitly::getUrl(route('posts.show', $post));
$post->update(['short_url' => $shortUrl]);
}
Store the bitlink, not just the short URL. Because getClicks() and getClicksSummary() operate on a bitlink (e.g. bit.ly/3xYzAbC), not the long URL, persist the short URL on the model so you can query analytics later without re-shortening.
$schema->string('short_url')->nullable()->index();
Cache aggressively. Bitly counts every shorten call against your rate limit. For URLs that are stable per record (canonical product pages, article permalinks), either shorten once at creation time (stored on the model) or cache the result:
$short = Cache::remember("bitlink:{$post->id}", now()->addDays(30), fn () =>
Bitly::getUrl($post->publicUrl())
);
Queue shortening in bulk imports. If you're migrating thousands of URLs, do it inside a job and throttle the queue. Bitly's free tier is limited (historically ~1000 links/month and a per-minute request rate); a synchronous loop will burn through the limit and start returning 429s.
class ShortenUrl implements ShouldQueue
{
public function handle(): void
{
$short = Bitly::getUrl($this->url);
// persist, then release with backoff if rate-limited
}
}
Wire it into notifications and SMS. This is the primary reason to have the package installed — anywhere you emit a URL (mail notifications, SMS via Twilio, tweet dispatch) replace the long URL with the shortened form so you get click telemetry for free.
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->line('New report ready.')
->action('View report', Bitly::getUrl($this->report->url()));
}
Use getClicks/getClicksSummary for lightweight dashboards. Pull click counts on a schedule into your own link_clicks table rather than hitting Bitly on every page load. A nightly php artisan links:sync-clicks command is the pattern that scales.
Keep a raw-client escape hatch. If you outgrow the packaged methods (custom-domain bitlinks, group-level analytics, bulk shorten endpoint), inject a Guzzle client or Bitly\BitlyClient alongside the facade and call the v4 API directly. The facade is a thin wrapper; don't fight it if you need more.
Token vs. client credentials are different. Bitly accepts an OAuth access token or client ID/secret, but this package only reads BITLY_ACCESS_TOKEN. If you have only client credentials, you must exchange them for a token out of band and put that token in .env.
Config caching. After changing config/bitly.php or BITLY_ACCESS_TOKEN, run php artisan config:clear (or config:cache again). Facades resolve fresh singletons per request but the config is snapshotted when cached — a common source of "why is it using the old token" confusion.
No custom domain support. The package calls POST /v4/shorten with just long_url, which means links land on bit.ly/... — not a branded domain you've configured in your Bitly account. If your marketing team needs yourbrand.co/xyz, this package won't do it; drop down to the raw v4 API and pass domain and group_guid.
Errors are Guzzle exceptions, not exceptions with useful fields unless you wrap them. Bitly returns structured errors like {"message":"INVALID_ARG_LONG_URL","resource":"bitlinks"}. Add a small helper:
try {
return Bitly::getUrl($url);
} catch (\Throwable $e) {
Log::warning('Bitly shorten failed', ['url' => $url, 'error' => $e->getMessage()]);
return $url; // graceful degradation
}
Falling back to the long URL is almost always better than crashing a mail send or a checkout page.
Validate before calling. Bitly rejects non-HTTP(S) or malformed URLs with a 4xx that's easy to miss in logs. Run the URL through filter_var($url, FILTER_VALIDATE_URL) first — cheaper than a network round trip.
Rate limiting / 429s. Bitly rate-limits per token. Wrap bulk loops with usleep() or a RateLimiter::attempt() and back off on 429 rather than retrying immediately. Don't rely on the package for retries — it does not implement them.
getClicks expects a bitlink, not a bit.ly URL string with different formatting. Pass bit.ly/xxxx or the full https://bit.ly/xxxx; passing the original long URL returns an error. Store both the long and short forms on your models to keep this unambiguous.
Facade accessor and aliasing. If you've aliased a different Bitly class elsewhere, the facade can silently resolve the wrong binding. Check config/app.php aliases and prefer the fully-qualified Shivella\Bitly\Facade\Bitly when there's any ambiguity.
Version pinning matters for older Laravel. The package is small and has had breaking-ish changes across Laravel major versions. Pin the exact version in composer.json ("shivella/laravel-bitly": "^4.0" or whatever the release notes indicate) and re-run your token-flow test in staging when upgrading Laravel.
Testability. The facade resolves a concrete Bitly instance, so in tests you can Bitly::shouldReceive('getUrl')->andReturn('https://bit.ly/test') — mock it aggressively so your test suite never makes real network calls. Alternatively, bind a fake in a service provider:
$this->app->instance(\Shivella\Bitly\Bitly::class, new FakeBitly());
Extension points are limited. The service provider binds the concrete class; there's no interface to swap against. If you want a provider-agnostic shortener (ShortenerContract implemented by Bitly, TinyURL, in-house), wrap this package in your own small adapter class rather than scattering Bitly::getUrl calls across the codebase. It's five extra lines now and saves a painful refactor later.
How can I help you explore Laravel packages today?