Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Better Temporary Urls Laravel Package

friendsofcat/laravel-better-temporary-urls

A Laravel package for better temporary URLs: generate signed, expiring links for routes and files with a cleaner API, configurable lifetimes, and sensible security defaults. A drop-in upgrade for Laravel's built-in temporary URL helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install the version that matches your framework. 2.0.0 is the Laravel 9 line; projects on Laravel 8 or older must stay on ^0.5. Composer will not stop you from resolving a bad pair, so constrain explicitly: composer require friendsofcat/laravel-better-temporary-urls:^2.0.
  • No manual wiring. The service provider is auto-discovered. If the release ships a config file, publish it with php artisan vendor:publish --tag=temporary-urls (confirm the tag in the README — this package has never had a large docs surface, so treat the tag name as unverified until you see the output).
  • First thing to read: the facade and the builder class under src/. The entire value of the package is the fluent builder, so skim it before the README — the method names are the API, and they are short enough to read in one screen.
  • First use case: replace a call to URL::temporarySignedRoute() where you also need to attach context that isn't a route parameter. A typical day-one usage:
use FriendsOfCat\TemporaryUrl\Facades\TemporaryUrl;

$url = TemporaryUrl::route('downloads.show', ['download' => $download])
    ->withParameters(['user' => $user->id])   // extra query context
    ->validFor(30)                            // minutes
    ->generate();

Then hand $url to a notification, a redirect, or an API response. The receiving route keeps using Laravel's standard signed middleware — the package only mints URLs, it does not validate them.

Implementation Patterns

Wrap it, don't sprinkle it. Expiry policy is a security decision and it drifts fast if every controller picks its own TTL. Put a tiny factory in your app — app/Support/TemporaryLinks.php — that owns the TTLs and the parameter shape, and inject the package there. When someone asks "how long are download links valid", there is one answer in one file.

Generate URLs late, especially in queued mail. This is the single most common production bug with any signed-URL package. If you build the URL in the controller and pass it into a ShouldQueue mailable, the clock starts at dispatch, not at delivery. Build it inside toMail() (or a Mailable::build() that runs on the queue worker) so a backlog doesn't hand out expired links.

Split minting from validating. Minting happens in trusted code paths (controllers, jobs, console commands). Validation stays on the route:

Route::get('downloads/{download}', DownloadController::class)
    ->middleware('signed')
    ->name('downloads.show');

Use signed:relative only if you deliberately want relative URLs; the default absolute-URL check is stricter and worth keeping.

Freeze time in tests. Signed-URL expiry is time-dependent and otherwise only fails in production. Use Carbon::setTestNow() and assert both sides of the boundary: one request before expiry returns 200, one after returns 403. Also test that a URL built with tampered parameters returns 403 — that is the assertion that proves your extra parameters are actually covered by the signature.

Migrate incrementally from URL::temporarySignedRoute(). Both produce URLs that validate under the same signed middleware, because the signature covers the full URL including query string. That means per-route rollout is safe: swap one call site, deploy, watch, continue.

Reach for it in the same places you'd reach for signed URLs generally: local-disk download links that must not be guessable, invite/onboarding links, one-click unsubscribe, short-lived preview URLs for unpublished content. Anything where "authenticated user" is too coarse a check.

Gotchas and Tips

  • Treat 2.0.0 as a hard break, because the notes don't tell you what broke. The only listed change is "Laravel 9 support" against a jump from 0.5.0 — a major bump for a one-line changelog almost always means dropped support (Laravel ≤8 and/or PHP <8.0 in composer.json) and possibly signature/API adjustments. There is no upgrade guide. Read composer.json and git diff 0.5.0..2.0.0 before upgrading, and run your signed-URL feature tests as the gate rather than trusting a green composer update.
  • Pin, and be aware of bus factor. A single first-time contributor landed the Laravel 9 support. That's fine, but it means the package's forward compatibility rides on a small maintainer pool — pin to a range, and keep the wrapper class from the pattern above so you can swap in native URL::temporarySignedRoute() in an afternoon if you ever need to.
  • Never re-serialize a generated URL. Concatenating a fragment, sorting query params, or pushing the URL through http_build_query() a second time changes the string the signature was computed over and yields a 403. Generate once, store the whole string.
  • APP_URL / proxy mismatches cause the classic "works locally, broken in prod." The signature is computed on the absolute URL. Behind a load balancer, if TrustProxies isn't configured, Laravel sees http://internal-host while the browser uses https://example.com, and signed rejects everything. If signed fails in staging only, check this before you check the package.
  • Match the signature key across your fleet. Temporary URLs are HMACs over APP_KEY. A rolling deploy that reads a rotated key, or any multi-service setup sharing routes but not keys, will invalidate live links mid-flight. Rotate keys with that in mind.
  • Don't cache generated URLs unless the cache TTL is strictly shorter than the link lifetime — otherwise you serve a stale, already-expired URL and it looks like a middleware bug.
  • Email link scanners will consume your links. Outlook Safe Links and corporate mail scanners issue GETs against URLs in mail. If the signed route performs a destructive or single-use action, put a confirmation page behind the signed URL rather than doing the work on the GET.
  • Debug by diffing against native Laravel. When a URL 403s and you don't know why, generate the same route and expiry with URL::temporarySignedRoute() and compare the two strings. If they differ, it's a URL-assembly difference — usually encoding of route parameters rather than anything in the package. If they match and it still 403s, the problem is in your middleware, proxies, or key, not here.
  • Check whether you need it at all. If you only want "valid for N minutes", Laravel's built-in covers you and adds no dependency. The package earns its place when the fluent builder and extra-parameter handling cut real boilerplate — that's the honest test for keeping it in a fresh project.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route