PHPackages                             ritechoice23/laravel-followable - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. ritechoice23/laravel-followable

ActiveLibrary[Database &amp; ORM](/categories/database)

ritechoice23/laravel-followable
===============================

A Laravel package to add follow/unfollow functionality to Eloquent models.

1.0.2(9mo ago)1381[2 PRs](https://github.com/ritechoice23/laravel-followable/pulls)MITPHPPHP ^8.2CI passing

Since Nov 6Pushed 3mo agoCompare

[ Source](https://github.com/ritechoice23/laravel-followable)[ Packagist](https://packagist.org/packages/ritechoice23/laravel-followable)[ Docs](https://github.com/ritechoice23/laravel-followable)[ GitHub Sponsors](https://github.com/ritechoice23)[ RSS](/packages/ritechoice23-laravel-followable/feed)WikiDiscussions master Synced 3d ago

READMEChangelog (3)Dependencies (12)Versions (6)Used By (0)

Laravel Followable
==================

[](#laravel-followable)

[![Latest Version on Packagist](https://camo.githubusercontent.com/c801fdda31ba779178a731c5062b2224651f650c798ce192afe318a683eecc3b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7269746563686f69636532332f6c61726176656c2d666f6c6c6f7761626c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ritechoice23/laravel-followable)[![GitHub Tests Action Status](https://camo.githubusercontent.com/38d8d5be9ea0f2113a7c8e02f6616679c9e8cba0594e994b3d9d9fed5a419769/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f7269746563686f69636532332f6c61726176656c2d666f6c6c6f7761626c652f72756e2d74657374732e796d6c3f6272616e63683d6d6173746572266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/ritechoice23/laravel-followable/actions?query=workflow%3Arun-tests+branch%3Amaster)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/6df9ffaefdfaf238f7db36a00c88fde5abf1832775791a7d146fa0123c0760cd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f7269746563686f69636532332f6c61726176656c2d666f6c6c6f7761626c652f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d6173746572266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/ritechoice23/laravel-followable/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amaster)[![Total Downloads](https://camo.githubusercontent.com/aaa2d8a2650ecc1b0f4cf25f04a21d98889f77fcc2d527a497a5c0f636e3d063/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7269746563686f69636532332f6c61726176656c2d666f6c6c6f7761626c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ritechoice23/laravel-followable)

**The only fully polymorphic follow package that handles mixed follower types elegantly with metadata support.**

A modern Laravel package that adds follow/unfollow functionality to Eloquent models with true bidirectional polymorphism. Any model can follow any other model, with intelligent handling of mixed-type relationships and optimized queries.

Why This Package?
-----------------

[](#why-this-package)

While other popular packages like [overtrue/laravel-follow](https://github.com/overtrue/laravel-follow) and [rennokki/laravel-eloquent-interactions](https://github.com/rennokki/laravel-eloquent-interactions) offer basic follow functionality, they often force you to hardcode the User model or require complex workarounds for heterogeneous relationships.

**Laravel Followable** was built to solve real-world challenges in complex applications where:

- Organizations follow other organizations
- Teams follow users and other teams
- Mixed-type followers need to be handled elegantly (Users + Teams + Organization following the same post)
- You need metadata for analytics (tracking follow sources, campaigns, referrers)
- Mutual relationships and bidirectional queries are essential
- Performance matters (optimized queries, no N+1 problems)

### What Makes It Unique

[](#what-makes-it-unique)

✅ **True Bidirectional Polymorphism** - Both follower AND followable can be ANY model type (User→Team, Team→Team, Organization→User, etc.)

✅ **Intelligent Mixed-Type Handling** - Smart `MixedModelsCollection` for when followers are of different types (Users + Teams + Organizations)

✅ **Rich Metadata Support** - Attach JSON metadata to track source, campaign data, referrer, or any custom attributes

✅ **Flexible Query API** - Separate optimized methods for single-type (`followers()`) vs multi-type (`followersGrouped()`) scenarios

✅ **Mutual Relationships** - Built-in support for identifying mutual follows and connections

✅ **Full MorphMap Support** - Works seamlessly with Laravel's `Relation::morphMap()` for cleaner database storage

✅ **Modern Architecture** - Uses latest Laravel features with 100+ comprehensive tests using Pest PHP

Features
--------

[](#features)

- **Fully Polymorphic**: Any model can follow any other model (User → Team, User → User, Team → Team, etc.)
- **Simple API**: Intuitive methods like `follow()`, `unfollow()`, `toggleFollow()`, `isFollowing()`
- **Expressive Scopes**: Chainable query scopes like `whereFollowing()` and `whereFollowers()`
- **Metadata Support**: Attach custom JSON metadata to follows
- **Zero Configuration**: Works out of the box with sensible defaults
- **Full Test Coverage**: Comprehensive Pest PHP test suite included

Installation
------------

[](#installation)

Install the package via composer:

```
composer require ritechoice23/laravel-followable
```

Publish and run the migrations:

```
php artisan vendor:publish --tag="followable-migrations"
php artisan migrate
```

Optionally, publish the config file:

```
php artisan vendor:publish --tag="followable-config"
```

Configuration
-------------

[](#configuration)

The published config file (`config/follow.php`) includes:

```
return [
    'table_name' => 'follows',
    'allow_self_follow' => false,
    'metadata_column' => 'metadata',
];
```

Usage
-----

[](#usage)

### Setup Models

[](#setup-models)

Add traits to your models:

```
use Illuminate\Database\Eloquent\Model;
use Ritechoice23\Followable\Traits\CanFollow;
use Ritechoice23\Followable\Traits\HasFollowers;

class User extends Model
{
    use CanFollow;      // Can follow other models
    use HasFollowers;   // Can be followed by other models
}

class Team extends Model
{
    use HasFollowers;   // Can be followed
}
```

### Basic Operations

[](#basic-operations)

```
// Follow a model
$user->follow($team);

// Unfollow a model
$user->unfollow($team);

// Toggle follow status
$user->toggleFollow($team);

// Check if following
if ($user->isFollowing($team)) {
    // User is following the team
}

// Check if followed by
if ($team->isFollowedBy($user)) {
    // Team is followed by user
}

// Get counts
$user->followingCount();  // Number of models user is following
$team->followersCount();  // Number of followers team has
```

### Working with Followers

[](#working-with-followers)

#### Get Actual Follower Models

[](#get-actual-follower-models)

The `followers()` method returns actual follower models (User, Team, etc.), not Follow pivot records:

```
// Get all followers (single type or homogeneous followers)
$followers = $team->followers()->get();

// Paginate followers
$followers = $team->followers()->paginate(15);

// Filter and query like any Eloquent relation
$activeFollowers = $team->followers()
    ->where('is_active', true)
    ->orderBy('name')
    ->get();

// Quick pagination helper
$followers = $team->followersPaginated(10);

// Filter by specific follower type
$userFollowers = $team->followersOfType(User::class)->get();
```

#### Handling Mixed Follower Types

[](#handling-mixed-follower-types)

When a model has followers of different types (e.g., both Users and Teams), use `followersGrouped()`:

```
// ✅ Best approach for mixed types
$grouped = $post->followersGrouped();
// Returns: ['App\Models\User' => Collection, 'App\Models\Team' => Collection]

// Iterate through each type
foreach ($grouped as $type => $followers) {
    echo "{$type}: {$followers->count()} followers\n";

    foreach ($followers as $follower) {
        // $follower is the actual User or Team model
        echo $follower->name;
    }
}

// Or query specific types separately
$userFollowers = $post->followers(User::class)->get();
$teamFollowers = $post->followers(Team::class)->get();
```

#### Counting Followers

[](#counting-followers)

```
// Total followers
$totalFollowers = $team->followersCount();

// Count by specific type
$userFollowers = $team->followersCount(User::class);
$teamFollowers = $team->followersCount(Team::class);
```

#### Access Follow Pivot Records

[](#access-follow-pivot-records)

When you need the actual Follow records (e.g., for metadata):

```
// Get Follow pivot records
$followRecords = $team->followRecords;  // Collection

// With eager loading
$followRecords = $team->followRecords()->with('follower')->get();

// Access metadata
foreach ($followRecords as $follow) {
    $metadata = $follow->metadata;
    $followerModel = $follow->follower;
}
```

### Follow with Metadata

[](#follow-with-metadata)

Attach custom data to follows:

```
$user->follow($team, [
    'source' => 'web',
    'campaign' => 'summer_2024',
    'referrer' => 'homepage'
]);

// Access and modify metadata (metadata is cast as array)
$follow = Follow::first();

// Set metadata
$metadata = $follow->metadata ?? [];
$metadata['key'] = 'value';
$follow->metadata = $metadata;
$follow->save();

// Get metadata
$value = $follow->metadata['key'] ?? null;

// Remove metadata key
$metadata = $follow->metadata;
unset($metadata['key']);
$follow->metadata = $metadata;
$follow->save();
```

### Query Scopes

[](#query-scopes)

Find models based on follow relationships:

```
// Find all users following a team
$users = User::whereFollowing($team)->get();

// Find all teams followed by a user
$teams = Team::whereFollowers($user)->get();

// Chain with other queries
$activeUsers = User::whereFollowing($team)
    ->where('status', 'active')
    ->orderBy('created_at', 'desc')
    ->get();
```

### Polymorphic Follows

[](#polymorphic-follows)

Follow any model type:

```
$user->follow($organization);  // User → Organization
$user->follow($anotherUser);   // User → User
$team->follow($anotherTeam);   // Team → Team
$user->follow($post);          // User → Post
```

### Relationships

[](#relationships)

Access follow relationships:

```
// Get all follows made by user (Follow records)
$user->followingRecords;

// Get actual follower models
$actualFollowers = $team->followers()->get();

// Get Follow pivot records
$followRecords = $team->followRecords;

// Eager load relationships on Follow records
$follows = Follow::with(['follower', 'followable'])->get();
```

### Working with Followings

[](#working-with-followings)

The `CanFollow` trait provides powerful methods to query what models a user is following.

#### Get Actual Followable Models

[](#get-actual-followable-models)

The `followings()` method returns actual followable models (User, Team, etc.), not Follow pivot records:

```
// Get all followings (single type or homogeneous followings)
$followings = $user->followings()->get();

// Paginate followings
$followings = $user->followings()->paginate(15);

// Filter and query like any Eloquent relation
$activeTeams = $user->followings()
    ->where('is_active', true)
    ->orderBy('name')
    ->get();

// Quick pagination helper
$followings = $user->followingsPaginated(10);

// Filter by specific followable type
$teamFollowings = $user->followingsOfType(Team::class)->get();
```

#### Handling Mixed Followable Types

[](#handling-mixed-followable-types)

When a user follows different types of models (e.g., both Users and Teams), use `followingsGrouped()`:

```
// ✅ Best approach for mixed types
$grouped = $user->followingsGrouped();
// Returns: ['App\Models\User' => Collection, 'App\Models\Team' => Collection]

// Iterate through each type
foreach ($grouped as $type => $followables) {
    echo "{$type}: {$followables->count()} followings\n";

    foreach ($followables as $followable) {
        // $followable is the actual User or Team model
        echo $followable->name;
    }
}

// Or query specific types separately
$userFollowings = $user->followings(User::class)->get();
$teamFollowings = $user->followings(Team::class)->get();
```

#### Counting Followings

[](#counting-followings)

```
// Total followings
$totalFollowings = $user->followingCount();

// Count by specific type
$userFollowings = $user->followingCount(User::class);
$teamFollowings = $user->followingCount(Team::class);
```

#### Access Following Records

[](#access-following-records)

When you need the actual Follow records (e.g., for metadata):

```
// Get Follow pivot records
$followingRecords = $user->followingRecords;  // Collection

// With eager loading
$followingRecords = $user->followingRecords()->with('followable')->get();

// Access metadata
foreach ($followingRecords as $follow) {
    $metadata = $follow->metadata;
    $followableModel = $follow->followable;
}
```

Important Notes
---------------

[](#important-notes)

### Use Cases for Different Methods

[](#use-cases-for-different-methods)

**For Followers (HasFollowers trait):**

**Use `followers()` when:**

- All followers are of the same type (e.g., only Users)
- You're filtering by a specific type
- You need to chain Eloquent query methods
- You're working with pagination

**Use `followersGrouped()` when:**

- A model has followers of multiple different types
- You need followers organized by their model type
- You want to iterate through each type separately

**Use `followRecords` when:**

- You need access to the Follow pivot records
- You want to work with follow metadata
- You need the follow timestamps or other pivot data

**For Followings (CanFollow trait):**

**Use `followings()` when:**

- Following models of the same type (e.g., only Teams)
- You're filtering by a specific type
- You need to chain Eloquent query methods
- You're working with pagination

**Use `followingsGrouped()` when:**

- Following multiple different types of models
- You need followings organized by their model type
- You want to iterate through each type separately

**Use `followingRecords` when:**

- You need access to the Follow pivot records
- You want to work with follow metadata
- You need the follow timestamps or other pivot data

### Performance Considerations

[](#performance-considerations)

The package uses optimized database queries with negligible overhead:

- **Single type queries**: Both `followers()` and `followings()` use efficient JOIN queries (1 query instead of N+1)
- **Multiple types**: `followersGrouped()` and `followingsGrouped()` fetch all types efficiently with batch queries
- **Smart Collection Wrapping**: `MixedModelsCollection` adds ~0.006ms overhead per operation (tested with 100 items)
- **Counting**: Direct COUNT queries on indexed columns
- **All queries leverage database indexes** for fast lookups
- **MorphMap Compatible**: Works seamlessly with morphMap for cleaner storage and better performance

**Benchmark Results** (50 followings, mixed types):

- Single type query: ~1.1ms
- Mixed types (2 types): ~16-20ms (includes multiple type queries + sorting)
- Grouped query: ~3.8ms (most efficient for mixed types)

💡 **Pro Tip**: For mixed-type scenarios, `followingsGrouped()` is 4x faster than `followings()->get()`

### Working with Mixed Follower Types

[](#working-with-mixed-follower-types)

If your model can be followed by different types (polymorphic scenario):

```
// ✅ Recommended: Use followersGrouped()
$grouped = $post->followersGrouped();
foreach ($grouped as $type => $followers) {
    // Each type's followers as proper model instances
}

// ✅ Alternative: Query specific types
$userFollowers = $post->followers(User::class)->get();
$teamFollowers = $post->followers(Team::class)->get();

// ✅ Or: Query and merge manually
$users = $post->followers(User::class)->get();
$teams = $post->followers(Team::class)->get();
$allFollowers = $users->merge($teams);
```

Advanced Usage
--------------

[](#advanced-usage)

### Prevent Self-Following

[](#prevent-self-following)

By default, models cannot follow themselves. Enable it in config if needed:

```
// config/follow.php
'allow_self_follow' => true,
```

### Idempotent Operations

[](#idempotent-operations)

Following an already-followed model returns `false` without creating duplicates:

```
$user->follow($team);  // true
$user->follow($team);  // false (already following)
```

### Database Indexes

[](#database-indexes)

The migration includes optimized indexes for performance:

- Unique composite index on follower and followable (prevents duplicates)
- Index on followable\_type and followable\_id (for lookups)
- Index on follower\_type and follower\_id (for reverse lookups)
- Index on created\_at (for trending queries)

Testing
-------

[](#testing)

Run the test suite:

```
composer test
```

Run tests with coverage:

```
composer test-coverage
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Daramola Babatunde Ebenezer](https://github.com/ritechoice23)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance69

Regular maintenance activity

Popularity14

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 75% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

275d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/62488893?v=4)[Daramola Babatunde Ebenezer](/maintainers/ritechoice23)[@ritechoice23](https://github.com/ritechoice23)

---

Top Contributors

[![ritechoice23](https://avatars.githubusercontent.com/u/62488893?v=4)](https://github.com/ritechoice23 "ritechoice23 (9 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

laravellaravel-followlaravel-followablelaravel-packagesearchlaravelmodeleloquenttraitmetadatacmsbloganalyticsarticlespostsFollowtaxonomycontent managementRelationshipsnormalizationcategorizationpolymorphictrendingritechoice23unfollow

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/ritechoice23-laravel-followable/health.svg)

```
[![Health](https://phpackages.com/badges/ritechoice23-laravel-followable/health.svg)](https://phpackages.com/packages/ritechoice23-laravel-followable)
```

###  Alternatives

[lacodix/laravel-model-filter

A Laravel package to filter, search and sort models with ease while fetching from database.

17562.4k](/packages/lacodix-laravel-model-filter)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3915.5k](/packages/rawilk-profile-filament-plugin)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5226.7k](/packages/simplestats-io-laravel-client)[giacomomasseron/laravel-models-generator

Generate Laravel models from an existing database

569.9k](/packages/giacomomasseron-laravel-models-generator)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
