Named Feeds
Introduction
Most apps show their activity to more than one audience. At Scoops Ahoy, the kitchen needs every order as it moves, the menu has its own change log, and a customer should see only their own order. When each screen filters activities in its own controller, those filters drift apart, and a newly recorded verb can appear on the customer's page without anyone deciding that it should.
A named feed defines an audience once, by name:
- Every screen shows the same verbs. A controller, a Filament widget and an API endpoint that retrieve the
'customer'feed all apply the same filter. - Verb coverage can be checked in CI. With restricted feeds and no unrestricted feed, the doctor warns about app-declared or recorded verbs that no feed includes or excludes. See Checking Verb Coverage for the exceptions.
- Each feed can link somewhere different. An order can open its ticket on the kitchen's board and its status page on the customer's.
- A feed class requires its subject. The customer's feed is always scoped to their order.
Defining Named Feeds
Define separate feeds for the kitchen's order board and the menu's change log:
Register each feed as a closure in a service provider's boot method. The closure receives the feed builder and configures its filters and mode:
use Storyfeed\Facades\Storyfeed;
use Storyfeed\FeedBuilder;
Storyfeed::feeds([
// The kitchen's order board: every order as it moves, with a customer's
// repeated orders folded into one row.
'kitchen' => fn (FeedBuilder $feed) => $feed
->only(['place', 'confirm', 'ready'])
->live(),
// The menu's change log: items added and prices changed, one row each.
'menu' => fn (FeedBuilder $feed) => $feed
->only(['publish', 'reprice'])
->log(),
]);Retrieving a Named Feed
Pass the feed name to the feed method on the Storyfeed facade:
use Storyfeed\Facades\Storyfeed;
Storyfeed::feed('kitchen')->get();To retrieve the menu's change log:
Storyfeed::feed('menu')->get();- Price
- $2.95
- Section
- Sundaes
- Available
- At the counter
- Orders
- 12
To retrieve a named feed for one model, pass the name to its storyfeed method. For example, $order->storyfeed('kitchen') applies the kitchen feed to that order.
An unknown feed name throws an UnknownFeed exception.
Defining Feed Classes
Use a feed class when a feed requires a subject, such as an order. Generate it with the make:feed Artisan command:
php artisan make:feed Customer --subject='App\Models\Order' --role=involvingThe command creates app/Feeds/CustomerFeed.php with an order constructor parameter. Edit the generated class to define its allowed verbs and mode:
<?php
namespace App\Feeds;
use App\Models\Order;
use Storyfeed\Feed;
use Storyfeed\FeedBuilder;
class CustomerFeed extends Feed
{
public function __construct(protected Order $order) {}
// What a customer may see: their order being placed, confirmed and made
// ready, one row each. Nothing about prices or staff notes.
public function define(FeedBuilder $feed): void
{
$feed->only(['place', 'confirm', 'ready'])->log();
}
// Which order: the one this feed was made for, on every read.
protected function scope(FeedBuilder $feed): void
{
$feed->involving($this->order);
}
}Pass the order to the feed class's make method:
use App\Feeds\CustomerFeed;
CustomerFeed::make($order)->get();See Commands for all make:feed options.
Defining and Scoping Hooks
The define method configures the feed without constructor values, including when the doctor checks verb coverage. The scope method applies the subject constraint whenever you retrieve the feed.
Additional query filters may narrow the results but cannot replace the constraints set by scope:
// throws FeedMisconfigured
CustomerFeed::make($order)->involving($other);
// fine
CustomerFeed::make($order)->only(['place'])->live();For a feed without a subject, omit the constructor and scope method:
<?php
namespace App\Feeds;
use Storyfeed\Feed;
use Storyfeed\FeedBuilder;
class KitchenFeed extends Feed
{
public function define(FeedBuilder $feed): void
{
$feed->only(['place', 'confirm', 'ready'])->live();
}
}Registering Feed Classes
Register a feed class to access it by name. You may register classes and closures together:
use App\Feeds\CustomerFeed;
use App\Feeds\KitchenFeed;
use Storyfeed\Facades\Storyfeed;
use Storyfeed\FeedBuilder;
Storyfeed::feeds([
'customer' => CustomerFeed::class, // named explicitly
KitchenFeed::class, // named 'kitchen', from the class
'menu' => fn (FeedBuilder $feed) => $feed->only(['publish', 'reprice'])->log(),
]);Using a Feed's Name
Linking Per Feed
A model's link resolver can call the context's feed method to get the registered feed name. Use it to return a kitchen ticket URL, customer status URL, or no link:
use Storyfeed\FeedContext;
static::feedMediaUsing(
fn (FeedContext $context) => match ($context->feed()) {
'kitchen' => route('kitchen.ticket', $context->routeKey()),
'customer' => route('orders.status', $context->routeKey()),
// an ad-hoc feed reports no name; without this arm the match throws
default => null,
},
);On the kitchen feed:
On a feed with no name:
Checking Verb Coverage
The doctor checks app-declared and recorded verbs; unused package-default verbs are excluded. When at least one restricted feed exists and no unrestricted feed is declared, a verb that no restricted feed includes or excludes produces a feeds.unclassified warning. Run with --fail-on=warning in CI to fail on these warnings.
Use ->unrestricted() to declare that a feed includes every verb. Alongside a restricted feed, this changes undecided-verb findings to informational feeds.unrestricted, which does not fail at the warning threshold. With no restricted feeds, the check reports informational feeds.none_restricted and returns. With no registered feeds, it returns without a finding.
Narrowing a Named Feed
You may change a named feed's mode or narrow its verb filters. The only method cannot add verbs excluded by its definition:
use Storyfeed\Facades\Storyfeed;
// reads only 'place': 'note' is not in the declared list
Storyfeed::feed('kitchen')->only(['place', 'note'])->get();See Filtering by Verb for the only and except methods.
Authorizing Feed Access
A named feed filters activities. Your application must authorize access:
- Check a policy in your controller to determine whether the customer may access the order.
- Each included activity returns its complete
datapayload. Store only values that the feed's audience may access.
For customer-facing feeds, use the only method with explicit verb names. The except method allows new verbs unless excluded, and wildcards allow new verbs that match.