PHPackages                             pedro-santiago/laravel-activity-feed - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. pedro-santiago/laravel-activity-feed

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

pedro-santiago/laravel-activity-feed
====================================

A robust Laravel package for creating activity feeds with dynamic entity resolution and multiple relationships

v0.1.0(6mo ago)001MITPHPPHP ^8.1|^8.2|^8.3CI failing

Since Oct 21Pushed 6mo agoCompare

[ Source](https://github.com/pedro-santiago/laravel-activity-feed)[ Packagist](https://packagist.org/packages/pedro-santiago/laravel-activity-feed)[ RSS](/packages/pedro-santiago-laravel-activity-feed/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (5)Versions (2)Used By (0)

Laravel Activity Feed
=====================

[](#laravel-activity-feed)

A robust Laravel package for creating activity feeds with dynamic entity resolution and multiple relationships. Perfect for building Facebook-style feeds, activity timelines, and audit logs.

Features
--------

[](#features)

- **Dynamic Entity Resolution**: Entity names automatically update when the underlying model changes (e.g., user renames)
- **Contextual Rendering**: Automatically renders "You" when viewing your own activities
- **Multiple Entities Per Feed Item**: Support for actor, subject, target, mentioned, and custom roles
- **Grouped Field Changes**: Track multiple field edits in a single feed item with detailed change history
- **Flexible Relationships**: Query feeds by any entity or combination of entities
- **Performance Optimized**:
    - Composite database indexes for fast queries
    - Built-in caching for rendered descriptions
    - Eager loading to prevent N+1 queries
    - Cursor pagination support
- **Configurable Retention**: Auto-cleanup old feed items
- **System Actions**: Support for activities without an actor (system-generated events)
- **Fluent API**: Intuitive, chainable builder pattern

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

[](#installation)

Install via Composer:

```
composer require pedro-santiago/laravel-activity-feed
```

Publish the configuration and migrations:

```
php artisan vendor:publish --tag=feed-config
php artisan vendor:publish --tag=feed-migrations
```

Run the migrations:

```
php artisan migrate
```

Quick Start
-----------

[](#quick-start)

### Basic Usage

[](#basic-usage)

```
use function PedroSantiago\ActivityFeed\feed;

// Log a simple activity
feed()
    ->withAction('created')
    ->withTemplate('{actor} created a new post')
    ->causedBy($user)
    ->performedOn($post)
    ->log();
```

### Multiple Entities

[](#multiple-entities)

```
feed()
    ->withAction('approved')
    ->withTemplate('{actor} approved {subject} for {amount}')
    ->causedBy($approver)
    ->performedOn($order)
    ->mentioning($requester)
    ->withProperties(['amount' => '$500'])
    ->log();
```

### System Actions (No Actor)

[](#system-actions-no-actor)

```
feed()
    ->withAction('system.restart')
    ->withTemplate('Approval flow restarted for {subject}')
    ->performedOn($order)
    ->withProperties(['reason' => 'Timeout'])
    ->log();
```

### Grouped Field Changes

[](#grouped-field-changes)

Track multiple field edits in a single feed item:

```
// Automatic detection from model changes
$order->update($request->validated());

feed()
    ->withAction('updated')
    ->withTemplate('{actor} {changes_summary} on {subject}')
    ->causedBy(auth()->user())
    ->performedOn($order)
    ->withModelChanges($order)  // Automatically tracks all changed fields
    ->log();

// Renders: "John Doe updated 3 fields on Order #123"
// With expandable details showing each field change
```

See [GROUPED\_CHANGES.md](GROUPED_CHANGES.md) for detailed documentation.

Using the HasFeed Trait
-----------------------

[](#using-the-hasfeed-trait)

Add the `HasFeed` trait to models that should have activity feeds:

```
use PedroSantiago\ActivityFeed\Traits\HasFeed;

class Order extends Model
{
    use HasFeed;
}
```

### Create Feed Items from Models

[](#create-feed-items-from-models)

```
// Using the trait
$order->logActivity(
    'updated',
    '{actor} updated the order status to {status}',
    ['status' => 'shipped']
);

// Or with the builder
$order->createFeedItem()
    ->withAction('updated')
    ->withTemplate('{actor} updated the order')
    ->causedBy($user)
    ->log();
```

### Query Feed Items

[](#query-feed-items)

```
// Get all feed items for an order
$feedItems = $order->feedItems()
    ->with('entities.entity')
    ->latest('occurred_at')
    ->get();

// Get feed items where the order is the subject
$subjectItems = $order->feedItemsAsSubject()->get();

// Get feed items where the user is the actor
$actorItems = $user->feedItemsAsActor()->get();
```

Querying Feeds
--------------

[](#querying-feeds)

### Filter by Entity

[](#filter-by-entity)

```
use PedroSantiago\ActivityFeed\Models\FeedItem;

// Get feed for a specific order
$feed = FeedItem::forEntity($order, 'subject')
    ->with('entities.entity')
    ->latestOccurred()
    ->get();
```

### Filter by Multiple Entities

[](#filter-by-multiple-entities)

```
// Get feed for all user's orders
$feed = FeedItem::forEntities($user->orders, 'subject')
    ->latestOccurred()
    ->cursorPaginate(20);
```

### Filter by Action

[](#filter-by-action)

```
// Single action
$approvals = FeedItem::ofAction('approved')->get();

// Multiple actions
$changes = FeedItem::ofAction(['created', 'updated', 'deleted'])->get();
```

### Filter by Date Range

[](#filter-by-date-range)

```
$recentFeed = FeedItem::inPeriod(
    now()->subDays(7),
    now()
)->get();
```

### Complex Queries

[](#complex-queries)

```
$feed = FeedItem::query()
    ->forEntity($user, 'actor') // User did something
    ->orWhereHas('entities', function($q) use ($user) {
        $q->where('role', 'mentioned')
          ->where('entity_type', User::class)
          ->where('entity_id', $user->id);
    }) // OR user was mentioned
    ->ofAction(['approved', 'declined'])
    ->inPeriod(now()->subMonth(), now())
    ->with('entities.entity')
    ->latestOccurred()
    ->cursorPaginate(20);
```

Rendering Feed Descriptions
---------------------------

[](#rendering-feed-descriptions)

Feed items use templates with placeholders that are resolved dynamically:

```
$feedItem = feed()
    ->withAction('approved')
    ->withTemplate('{actor} approved {subject} for {amount}')
    ->causedBy($john)
    ->performedOn($order)
    ->withProperty('amount', '$500')
    ->log();

// Render for a viewer
echo $feedItem->renderDescription($currentUser);
// Output: "John Doe approved Order #123 for $500"

// Render for the actor themselves
echo $feedItem->renderDescription($john);
// Output: "You approved Order #123 for $500"
```

### Template Placeholders

[](#template-placeholders)

- `{actor}` - The user/entity who performed the action
- `{subject}` - The primary entity being acted upon
- `{target}` - Additional target entity
- `{mentioned}` - Mentioned entity
- `{related}` - Related entity
- `{any_property_key}` - Any property from the properties array

### Custom Display Names

[](#custom-display-names)

Override `getFeedDisplayName()` in your models:

```
class User extends Model
{
    use HasFeed;

    public function getFeedDisplayName(): string
    {
        return $this->full_name;
    }
}
```

Builder API Reference
---------------------

[](#builder-api-reference)

### Actions

[](#actions)

```
->withAction(string $action)
```

### Templates

[](#templates)

```
->withTemplate(string $template)
->withDescription(string $template) // Alias
```

### Entities

[](#entities)

```
->causedBy(?Model $actor)           // Who did it
->by(?Model $actor)                 // Alias

->performedOn(?Model $subject)      // What was acted upon
->on(?Model $subject)               // Alias

->targeting(?Model $target)         // Target entity
->mentioning(?Model $mentioned)     // Mentioned entity
->relatedTo(?Model $related)        // Related entity

->addEntity(?Model $entity, string $role)  // Custom role
->addEntities(array $entities, string $role) // Multiple with same role
```

### Properties

[](#properties)

```
->withProperties(array $properties)
->withProperty(string $key, $value)
```

### Timing

[](#timing)

```
->occurredAt(Carbon|string $timestamp)
```

### Execution

[](#execution)

```
->log()  // Create and save the feed item
```

Model Scopes
------------

[](#model-scopes)

Available scopes on `FeedItem`:

```
->ofAction(string|array $action)
->inPeriod($startDate, $endDate)
->forEntity(Model $entity, ?string $role = null)
->forEntities($entities, ?string $role = null)
->latestOccurred()
```

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

[](#configuration)

Edit `config/feed.php`:

```
return [
    // Cache TTL for rendered descriptions (seconds)
    'cache_ttl' => 900, // 15 minutes

    // Retention period (days) - null for indefinite
    'retention_days' => 90,

    // Auto cleanup old items
    'auto_cleanup' => false,

    // Default pagination
    'per_page' => 20,

    // Always eager load these relationships
    'eager_load' => [
        'entities.entity',
    ],

    // Predefined actions
    'actions' => [
        'created', 'updated', 'deleted',
        'approved', 'declined', 'pending',
        'completed', 'cancelled', 'restored',
    ],
];
```

Cleanup Old Feed Items
----------------------

[](#cleanup-old-feed-items)

Run manually:

```
# Use configured retention period
php artisan feed:cleanup

# Override retention period
php artisan feed:cleanup --days=30

# Dry run to see what would be deleted
php artisan feed:cleanup --dry-run
```

Schedule in `app/Console/Kernel.php`:

```
protected function schedule(Schedule $schedule)
{
    $schedule->command('feed:cleanup')->daily();
}
```

Database Schema
---------------

[](#database-schema)

### feed\_items

[](#feed_items)

ColumnTypeDescriptionidbigintPrimary keyactionvarchar(50)Action typedescription\_templatetextTemplate with placeholderspropertiesjsonAdditional metadataoccurred\_attimestampWhen the action happenedcreated\_attimestampRecord creation timeupdated\_attimestampRecord update time**Indexes:**

- `action`
- `occurred_at`
- Composite: `(occurred_at, action)`

### feed\_item\_entities

[](#feed_item_entities)

ColumnTypeDescriptionidbigintPrimary keyfeed\_item\_idbigintForeign key to feed\_itemsentity\_typevarcharPolymorphic typeentity\_idbigintPolymorphic IDrolevarchar(50)Entity role (actor, subject, etc.)created\_attimestampRecord creation time**Indexes:**

- Composite: `(entity_type, entity_id, role)`
- Composite: `(feed_item_id, role)`

Performance Tips
----------------

[](#performance-tips)

1. **Always Eager Load**: Use `with('entities.entity')` to prevent N+1 queries
2. **Use Cursor Pagination**: For large datasets, use `cursorPaginate()` instead of `paginate()`
3. **Cache Rendered Descriptions**: Enabled by default with 15-minute TTL
4. **Index Custom Queries**: Add database indexes for frequently queried columns
5. **Cleanup Old Data**: Regularly run `feed:cleanup` to maintain performance

Example: Building a User Feed
-----------------------------

[](#example-building-a-user-feed)

```
use PedroSantiago\ActivityFeed\Models\FeedItem;

class FeedController extends Controller
{
    public function index(Request $request)
    {
        $user = $request->user();

        // Get feed for user's entities (orders, posts, etc.)
        $feed = FeedItem::query()
            ->forEntities($user->orders, 'subject')
            ->orForEntities($user->posts, 'subject')
            ->orForEntity($user, 'mentioned')
            ->with('entities.entity')
            ->latestOccurred()
            ->cursorPaginate(20);

        // Transform for display
        $items = $feed->map(function ($feedItem) use ($user) {
            return [
                'id' => $feedItem->id,
                'action' => $feedItem->action,
                'description' => $feedItem->renderDescription($user),
                'occurred_at' => $feedItem->occurred_at->diffForHumans(),
                'properties' => $feedItem->properties,
            ];
        });

        return response()->json($items);
    }
}
```

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT License

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

[](#contributing)

Contributions are welcome! Please submit pull requests or open issues.

Credits
-------

[](#credits)

- Inspired by [spatie/laravel-activitylog](https://github.com/spatie/laravel-activitylog)
- Built for Laravel 10+ and PHP 8.1+

###  Health Score

29

—

LowBetter than 60% of packages

Maintenance66

Regular maintenance activity

Popularity1

Limited adoption so far

Community4

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

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

Unknown

Total

1

Last Release

203d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/df63d6db84839fa3e8714fc19852680ff7f0560217739eb1b19277f2a37ff376?d=identicon)[pedro-santiago](/maintainers/pedro-santiago)

---

Tags

streamlaravelfeedactivitytimeline

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/pedro-santiago-laravel-activity-feed/health.svg)

```
[![Health](https://phpackages.com/badges/pedro-santiago-laravel-activity-feed/health.svg)](https://phpackages.com/packages/pedro-santiago-laravel-activity-feed)
```

###  Alternatives

[barryvdh/laravel-ide-helper

Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.

14.9k123.0M687](/packages/barryvdh-laravel-ide-helper)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[interaction-design-foundation/laravel-geoip

Support for multiple Geographical Location services.

17221.0k3](/packages/interaction-design-foundation-laravel-geoip)[glhd/special

1929.4k](/packages/glhd-special)[bjuppa/laravel-blog

Add blog functionality to your Laravel project

483.3k2](/packages/bjuppa-laravel-blog)

PHPackages © 2026

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