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 Shareable Models Laravel Package

sassnowski/laravel-shareable-models

Laravel package that turns any Eloquent model into a shareable resource with unique, revocable URLs. Add a trait, generate share links, and let recipients view records without writing custom sharing controllers, routes, or token handling.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the package

composer require sassnowski/laravel-shareable-models

2. Publish and run the migration

The package ships a shareable_links table (polymorphic shareable_id / shareable_type, a token, and a nullable expires_at). Publish config + migrations, then migrate:

php artisan vendor:publish --provider="Sassnowski\LaravelShareableModel\ShareableModelServiceProvider"
php artisan migrate

3. Add the trait to the model you want to share

use Sassnowski\LaravelShareableModel\Shareable\Shareable;

class Post extends Model
{
    use Shareable;
}

4. Create your first link

$link = $post->createShareableLink();

return $link->url; // fully-qualified URL built from the configured prefix + token

That's the whole first use case: "give this Post a public URL without exposing its ID." The URL points at a route the package registers under the shareable prefix; you supply the controller/middleware that decides what a visitor sees.

Where to look first

  • config/shareable.php — route prefix, middleware stack, and where invalid/expired links redirect (redirect_route, redirect_message). This is the file you will actually edit.
  • The published migration — confirm the indexes and decide whether you want a FK/cascade.
  • Sassnowski\LaravelShareableModel\Shareable\ShareableLink — the Eloquent model behind every link; read it before you try to extend anything.
  • The package service provider — shows the registered route name and the shareable middleware alias.

Implementation Patterns

The core loop: create → serve → revoke

The package is deliberately thin. It gives you a token→model lookup; you own the presentation.

// routes/web.php
Route::get('/s/{shareableLink}', ShowSharedPostController::class)
    ->middleware('shareable')
    ->name('posts.shared');
// ShowSharedPostController
public function __invoke(ShareableLink $shareableLink)
{
    $post = $shareableLink->shareable;   // the polymorphic parent

    abort_unless($post instanceof Post, 404);

    return view('posts.shared', ['post' => $post]);
}

The shareable middleware is the gatekeeper: it validates the token and expiration. On failure it redirects to config('shareable.redirect_route') with config('shareable.redirect_message') flashed to the session. On success it lets the request through so route-model binding hands you the ShareableLink.

Expiring links

Links are permanent by default (expires_at === null). For time-boxed sharing:

$link = $post->createShareableLink();
$link->expires_at = now()->addDays(7);
$link->save();

Use this for "preview before publish," client review links, or invite-style URLs. Keep the TTL in a config value or a model method rather than hard-coding addDays(7) in five controllers.

Listing and managing links

Because the relationship is one-to-many, a model can hold several links simultaneously — useful for "current link" vs. "legacy link," or per-recipient links.

$post->shareableLinks()->latest()->get();

// Revoke
$link->delete();

// "Rotate" — revoke everything, issue a fresh one
$post->shareableLinks()->delete();
$fresh = $post->createShareableLink();

Eager load when rendering an index: Post::withCount('shareableLinks') or with('shareableLinks') to avoid N+1s.

Pair it with an audit trail

The most common production increment is recording who created a link and why. Add columns to the published migration (or a side table) and set them in a small service:

class ShareLinkService
{
    public function forPost(Post $post, User $user, ?Carbon $expiresAt = null): ShareableLink
    {
        $link = $post->createShareableLink();
        $link->forceFill([
            'created_by' => $user->id,
            'expires_at' => $expiresAt,
        ])->save();

        return $link;
    }
}

When not to use it

If the share target is a single controller action you control, Laravel's native signed URLs (URL::temporarySignedRoute) already cover expiry and tamper-proofing without a table. Reach for this package when you need links to be revocable individually, listable, or reusable by multiple recipients — properties signed URLs don't give you.


Gotchas and Tips

Pin the package against your Laravel version. The last release is March 2023. Its illuminate/* constraint won't necessarily admit Laravel 11/12, and Composer may silently resolve an old framework. Check composer show sassnowski/laravel-shareable-models after install and run the test suite against your actual Laravel version before trusting it in a new app.

Renaming or namespacing the parent model breaks links. shareable_type stores the FQCN by default. If you introduce a morph map (Relation::enforceMorphMap), existing rows with raw class names stop resolving. Decide on a morph map before you generate links, or write a backfill migration.

Orphaned links are on you. The published migration's morphs() index does not enforce referential integrity — deleting a Post leaves its links behind, and a visit to a stale token will 404 inside your controller (or fatal in $link->shareable returning null). Either add a deleted model observer that purges shareableLinks(), or schedule a cleanup command:

ShareableLink::where('expires_at', '<', now()->subMonth())->delete();

Also guard the null case explicitly: abort_if($shareableLink->shareable === null, 404).

Config cache will bite you. config/shareable.php is read at boot; after editing redirect_route run php artisan config:clear or you'll chase a redirect to a route name that "definitely exists."

Route prefix collisions. The package registers routes under the configured prefix. If you already own /share or /s, either change shareable.prefix or you'll get last-registered-wins behavior that is maddening to debug.

Middleware order matters. The shareable middleware flashes a message to the session on failure, so it must run after web (session). If you wrap the route in api only, invalid links will throw instead of redirecting.

Treat the token as the entire credential. There is no per-user authorization — anyone with the URL gets in. That's the point, but it means:

  • Don't log full URLs; they land in access logs and Referer headers.
  • Add throttle: to the share route to blunt token guessing and scraping.
  • Never expose shareable_id or the token list in an API response to unauthorized users.
  • Rotate by delete() + recreate, not by editing token in place.

Extending the model is awkward. There's no first-class "custom ShareableLink" hook. If you outgrow it (custom token format, extra columns, soft deletes), you'll end up subclassing ShareableLink and rebinding it in the container — check the service provider first to see what's actually bindable in your version. Budget for this; it's the main reason teams outgrow the package.

Testing. Freeze time with Carbon::setTestNow() to assert expiration boundaries — an off-by-one on expires_at (inclusive vs. exclusive comparison) is the classic bug here. Assert both the happy path and the redirect-with-flash path so a future config change doesn't silently turn expired links into 500s.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource