slvler/cuttly
A Laravel/PHP wrapper for the Cutt.ly URL shortener API. It lets you create and manage short links, use branded domains, and retrieve click analytics from your application, with straightforward configuration and a clean, simple integration for Laravel projects. Ideal for adding link shortening to La...
composer require slvler/cuttly. Laravel package auto-discovery registers the service provider and Cuttly facade; no manual registration needed on Laravel 8+.php artisan vendor:publish --tag=cuttly-config (creates config/cuttly.php). If the tag isn't registered, check php artisan vendor:publish and pick the CuttlyServiceProvider entry.CUTTLY_API_KEY=... to .env (get a key from the Cutt.ly dashboard). The config file reads from env('CUTTLY_API_KEY'); verify the exact key name in the published config, because wrappers in this genre sometimes use CUTTLY_KEY or similar and you'll silently hit an auth error otherwise.use Slvler\Cuttly\Facades\Cuttly;
$response = Cuttly::shorten('https://example.com/some/long/path');
// inspect the returned array/object per the README — Cutt.ly returns
// payloads like ['url' => ['status' => 7, 'shortLink' => '...']]
Where to look first: the repo README (it's the only real docs), then src/ — it's a thin wrapper, so the facade class and its underlying client show you every available method (shorten, stats, edit, delete are the Cutt.ly API surface). If a method you need isn't exposed, you'll extend it or call the HTTP client directly.
Wrap the facade in your own service. Don't scatter Cuttly:: calls through controllers — inject a UrlShortener class that returns a normalized DTO (shortUrl, status, raw). That gives you a single seam to swap providers later and a clean place to handle the array-shaped responses.
Persist the mapping, not just the string. Store original_url, short_url, provider_ref, created_at on your model. You'll want the original to rebuild links, detect duplicates, and reconcile if Cutt.ly changes a slug.
Idempotency for user-submitted links. Check your DB for an existing shortened row before calling the API — cheap optimization and it keeps you well under rate limits when a user pastes the same URL twice.
Queue it for bulk work. Shortening N links in a request will hit Cutt.ly's rate limits and slow page loads. Dispatch a job per URL (or small batches) and update the model on completion. Add $tries/backoff on the job since the wrapper typically doesn't retry for you.
Custom domain / custom slug params. Cutt.ly supports custom_domain and slug on the shorten call. Pass them through at construction time rather than post-processing the response — the API applies them server-side and errors if the slug is taken, which you need to surface to the user.
Stats integration. If you render click counts, cache Cuttly::stats($id) results for a few minutes and refresh via a scheduled command — otherwise you'll burn quota on dashboard refreshes.
Testing. Bind a fake or Http::fake() the underlying endpoint in tests. Cutt.ly returns HTTP 200 with a JSON status field even on some failures, so fake realistic payloads (both success and status != 7) to exercise your error branch.
status code inside the body, not just HTTP status. Always inspect the status/errors field before treating a response as success — a naive if ($response) will pass bad data through.example.com will fail or produce a broken link; validate with filter_var($url, FILTER_VALIDATE_URL) before calling.php artisan config:clear after editing .env; a cached config with a stale/missing key is the #1 "it worked yesterday" cause.['url']['shortLink'] defensively with data_get() and handle missing keys — the package doesn't always normalize.retry() from Laravel's helpers with a short backoff, and catch connection exceptions explicitly.$this->app->bind(...) in a service provider) if you need custom HTTP options, timeouts, or a different base URL for testing. Add a timeout to the Guzzle client — the default can hang your request thread.^1.x (or the current major) and check the changelog before minor bumps, since response shapes are the most likely thing to shift in a wrapper this small.How can I help you explore Laravel packages today?