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

Channel Laravel Package

vanilo/channel

Vanilo Channel adds sales channels to Laravel e-commerce applications built with Vanilo. Define multiple storefronts or markets and scope products, pricing, and availability to each channel for multi-store and multi-region selling.

View on GitHub
Deep Wiki
Context7

Getting Started

vanilo/channel is a sub-module of the Vanilo commerce suite, not a standalone library. In practice you almost never install it directly — it arrives as a dependency of vanilo/framework, so the first step is to confirm it is already there:

composer show vanilo/channel
php artisan migrate   # creates `channels` and `channelables`

Only add it explicitly if you are assembling a custom stack:

composer require vanilo/channel
php artisan vendor:publish --tag=vanilo-channel   # or via the installer / module provider

Publish the config before you change anything; the module is configured through config/vanilo.channel.php (model class override, channelable model map). Then read the source in this order — it is small enough to read end to end in 20 minutes:

  1. src/Contracts/Channel.php — the interface every consumer type-hints against.
  2. src/Models/Channel.php — Eloquent implementation, the configuration bag, and the current() proxy.
  3. src/ChannelState.php — Konekt enum (Draft / Active / Inactive), not a native PHP enum.
  4. src/ChannelContext.php + its contract — the container-bound "which channel am I in" singleton.
  5. src/Traits/Channelable.php — the polymorphic many-to-many side you attach to Product, Order, etc.

The canonical first use case is a single middleware that resolves a channel per request and puts it in context:

use Vanilo\Channel\Contracts\ChannelContext;
use Vanilo\Channel\Models\Channel;

public function handle($request, Closure $next)
{
    $channel = Channel::where('slug', $request->route('channel'))->firstOrFail();

    app(ChannelContext::class)->setCurrentChannel($channel);

    return $next($request);
}

Everything downstream — pricing, catalogs, cart, order attribution — reads from that context, so getting this one binding right is the whole onboarding.

Implementation Patterns

Resolve once, read everywhere. The context object is a container singleton. Bind it in middleware (or a route-binding callback) and never re-resolve the channel inside controllers/services; inject ChannelContext and call getCurrentChannel(). Downstream code stays channel-agnostic and testable — swap the context in tests.

Attach models via the Channelable trait. Add it to Product, Order, and anything else that should be channel-aware, then register the type in the channelables map in config/vanilo.channel.php. The channel side then exposes relations named after the map keys ($channel->products(), $channel->orders()), which is the idiomatic way to build per-storefront queries:

$channel->products()->attach($product);          // pivot row
$channel->products()->sync([$a, $b]);             // idempotent set

Product::whereHas('channels', fn ($q) => $q->whereKey($channel->id))->get();

Use the configuration JSON bag for per-channel settings. Tax region, currency, payment gateway keys, theme name — store them on the channel rather than building a settings table. It is a JSON column exposed as an array-like object; wrap access in a small value-object or accessor on your own Channel subclass so nobody reaches into raw JSON keys in a controller.

Route model binding by slug. Channels are user-facing storefronts; bind them {channel} → slug rather than ID so URLs stay readable and stable across environments.

Always pass the channel to jobs and events. Queue workers, scheduled commands, and listeners start with no channel in context. Serialise $channel->id into the job payload and re-set the context in handle() as the first statement. Same for console commands — resolve a default channel explicitly.

Seed idempotently. Channel::updateOrCreate(['slug' => 'webshop'], [...]) in a seeder, then assign channelables in a second pass — this keeps local resets and new environments from creating duplicate storefronts.

Testing. Refresh the database, create an Active channel in setUp(), and set it on the context. For multi-channel behaviour, run the same assertion inside a loop over channels — this catches global-scope bugs early.

Gotchas and Tips

  • There is no automatic channel scoping. Attaching the trait does not add a global scope. A product created without a channel is silently invisible to every storefront — add a creating hook or a failing test rather than discovering it in production.
  • Channel::current() is null in console, queues, and Nova/HTTP-free contexts. This is the single most common bug report. Never call it at the top level of a service provider or a model's boot().
  • Konekt enums are not PHP enums. $channel->state is an enum object; $channel->state === 'active' is false. Use ->equals(ChannelState::ACTIVE()), the isActive()-style magic checks, or compare ->value(). Passing the raw string into a where() clause works because the enum stringifies — don't rely on that everywhere.
  • ChannelContext is a singleton — beware Octane and long-running workers. State set during request A survives into request B. Either reset the context at the start of every request (in middleware, unconditionally) or bind it as a scoped instance. Symptom: users intermittently see another storefront's prices.
  • Slug uniqueness is global. If you want the same slug per region/domain, the built-in unique index blocks you — model that as a different channel identifier and add your own composite index via a separate migration.
  • Deleting a channel does not delete its products or orders. Only pivot rows should disappear (verify the FK cascade). Prefer Inactive state over deletion for anything that has ever processed an order — historical records point at the channel.
  • The channelables pivot has a unique constraint on (channel_id, channelable_type, channelable_id). attach() twice throws; use sync() or syncWithoutDetaching() in loops and idempotent imports.
  • Customising the model. You can swap in your own class via the config key and by binding your implementation to the Channel contract, but the shipped migration won't know about your extra columns — ship your own migration and keep the contract intact so framework code keeps working.
  • Namespace collision on channels. If you already have a channels table (chat, broadcasting), rename via the config before migrating rather than after.
  • Treat vanilo/framework as the source of truth for docs. This package is a small internal module with no standalone documentation and effectively no independent dependents; examples and changelogs live in the umbrella repo and the source itself. Pin it to the framework version you run, and when behaviour surprises you, read src/Models/Channel.php directly — it is a few hundred lines.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource