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

Followable Laravel Package

x-laravel/followable

A Laravel package that adds follow and unfollow behavior to your models. Attach polymorphic follower relationships, check whether a model is followed, and query followers or followings with a simple, reusable trait.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a new package, so start from scratch rather than adapting existing code.

  1. Install and publish. composer require the package, then publish its config and migrations (php artisan vendor:publish and pick the package's provider/tags — confirm the exact tags in the README, since v1.0.0 ships a configurable connection, table and model). Run php artisan migrate.
  2. Look first at three places: the Contracts namespace (what your models must implement), the Concerns traits (what behaviour you get for free), and the published config (connection/table/model overrides). The contracts are the stable API — type-hint against them, not against concrete models.
  3. Wire up your models. On the model that does the following (usually User), implement Contracts\CanFollow and use Concerns\Follower. On every model that can be followed (User, Team, Post, Project…), implement Contracts\HasFollowers and use Concerns\Followable. One model can use both — a User that follows other users is the normal case.
  4. First use case: a user follows a team.
$user->follow($team);          // create the follow
$user->isFollowing($team);     // true
$team->followers()->count();   // list/count
$user->followings();           // what this user follows
$user->unfollow($team);

That single flow — follow, check state, list/count, unfollow — covers most of what you'll build day to day. Everything else (approval, mutuals, pruning) layers on top.

Implementation Patterns

Two-sided traits, both on User. Because Follower and Followable are separate concerns, keep the naming straight in your head: followings() / sentFollows() are outgoing, followers() / receivedFollows() are incoming. Reach for followings() when rendering "who I follow" and followers() when rendering "who follows me".

Approval-gated follows. If the followable model implements Contracts\RequiresFollowApproval, a follow() call lands as a request rather than an active follow. Build the UI around the three states, not two:

$user->isFollowing($team);            // active
$user->hasRequestedToFollow($team);   // pending, sent by this user
$team->hasFollowRequestFrom($user);   // pending, seen from the owner side
$team->followRequests();              // inbox for the owner
$team->acceptFollowRequest($user);
$team->rejectFollowRequest($user);

Make the approval decision a method on the model (e.g. private team → approve, public team → auto-accept) so the rule lives next to the data rather than in controllers.

Render follow state in lists without N+1. This is the highest-value part of the package for typical UI work. On an index of users/posts, combine withFollowedBy($user) with isFollowedBy():

$posts = Post::query()->withFollowedBy($user)->paginate();
// blade: $post->isFollowedBy($user) — no query per row

Without the scope you'll fire one isFollowing() query per row on every listing page; this is the first thing to fix if a page feels slow.

Mutual follows ("friends", "connections"). isMutuallyFollowing(), mutualFollowers() and the withFollowsBack() scope let you build a connections tab or a feed restricted to people who follow back. Use withFollowsBack() when filtering a query (e.g. "posts from mutuals") and mutualFollowers() when you just need the collection.

Owner-initiated removal. unfollow() is the follower acting; removeFollower() is the followable model removing someone. Different authorisation, different controller — don't collapse them into one endpoint.

Events as the integration seam. Followed, Unfollowed, FollowRequested, FollowRequestAccepted, FollowRequestRejected, FollowRequestCancelled are all dispatched after commit, which is the right default. Put notifications, activity-log writes, feed fan-out and analytics in queued listeners (ShouldQueue) so a follow action stays a single fast write.

Pruning. Deleted models and followers are cleaned up automatically, but orphaned rows can still exist (raw deletes, truncations, restored dumps). The package registers a pruneable model; make sure model:prune is actually scheduled in your app and tune the configured frequency and chunk size for your table size. Don't also hand-roll your own cleanup job for the same table.

Config and multi-connection setups. If follows live on a different connection (separate reporting DB, tenant split), set it in config before migrating — the migration must target the same connection or you'll create the table in the wrong place. Custom pivot model is the extension point when you need extra columns on the follow row; subclass the package model rather than editing the published migration in place if you want upgrade paths.

Gotchas and Tips

  • Don't assume follow() means "following". With RequiresFollowApproval in play, after a follow() you must branch on hasRequestedToFollow() vs isFollowing(). Code that fires a "You're now following X" flash unconditionally will lie to users on private accounts.
  • Self-follow and duplicates are rejected at the package level. That means exceptions on a double-submit, not a silent no-op. Guard with isFollowing() before calling, and catch the failure in controllers where a stale form can resubmit.
  • withFollowedBy() is per-viewer. It takes the user whose state you're resolving, so it doesn't compose with response caching of an authenticated listing. Cache per-user or cache the neutral list and resolve state separately.
  • Morph maps bite later. Follower and followable types are stored polymorphically. If you adopt Relation::enforceMorphMap() after you have data, existing rows still hold FQCNs — plan a data migration, and set the morph map before launch.
  • After-commit events in tests. Listeners are dispatched after commit, so in transaction-wrapped tests (RefreshDatabase/DatabaseTransactions) the job/listener side effects may not appear where you expect. Assert wiring with Event::fake([Followed::class]) plus Event::assertDispatched() — faking works regardless — and test listener bodies directly with $this->travel/handle() rather than relying on end-to-end dispatch inside a rolled-back transaction.
  • Eager-load deliberately. followers()/followings() are polymorphic; loading them without constraints on a mixed-type collection will pull unexpected models. Constrain by type or withCount() when you only need numbers — withCount('followers') is far cheaper than loading the relation for a card grid.
  • Deletion order and app-level cascades. Cleanup covers follow rows, but your own dependent records (notifications, feed entries, counters) are not the package's responsibility — hook the Unfollowed event or add observers if you denormalise follower counts.
  • Casting/type-hint the contracts. Authorising with CanFollow/HasFollowers in policies and form requests keeps your app decoupled from the package's concrete classes, which makes replacement or a version bump far less painful.
  • Config naming is the one thing to verify yourself. Table name, connection and default follower model are all configurable, but the published config filename and env keys aren't part of the release notes — read the published file once and note the actual keys in your project README so the next developer doesn't guess.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource