vanilo/framework
Vanilo Framework is a modular e-commerce foundation for Laravel, providing products, pricing, carts, checkout, orders, payments, shipping, and promotions to help you build custom online stores quickly.
Vanilo is not a shop-in-a-box — it's a set of ~20 composable e-commerce modules (products, cart, checkout, orders, payment, shipping, taxation, properties, channels) glued together by Concord, Konekt's module/contract system. vanilo/framework is the metapackage that pulls all of them in; you supply the storefront, the views, and the business rules.
Minimum path:
composer require vanilo/framework # match the PHP/Laravel matrix for your Vanilo major
php artisan vendor:publish --tag=vanilo # config + migrations (check the tags it lists)
php artisan vendor:publish --tag=concord
php artisan migrate
Then confirm what actually got registered:
php artisan concord:modules # every module, its contracts and models
php artisan concord:models
Look here first, in this order:
config/concord.php — the module registry. Commenting a module out is how you amputate features you don't want.config/vanilo/*.php (product, cart, checkout, order, payment, shipping, taxation) — every default table name, model class, and behaviour toggle lives here.vendor/vanilo/*/src/Contracts/ — this is the real API surface. Code against these interfaces, not the Eloquent models.vendor/vanilo/*/src/Providers/ModuleServiceProvider.php — shows what each module registers (routes, bindings, event listeners, migrations).First use case — a working cart → order flow without any admin UI. Wire three controller actions against the facades:
use Vanilo\Cart\Facades\Cart;
public function add(Product $product)
{
Cart::addItem($product, request('qty', 1));
return redirect()->route('cart.show');
}
public function show()
{
return view('cart.show', [
'items' => Cart::getItems(),
'total' => Cart::total(),
]);
}
use Vanilo\Checkout\Facades\Checkout;
public function begin()
{
$checkout = Checkout::create(Cart::getFacadeRoot()->getCart());
$checkout->begin();
// ->update([...], 'billing'), ->update([...], 'shipping'), ->complete()
}
public function complete()
{
$checkout->complete();
$order = $checkout->getOrder();
}
That single flow touches cart, checkout, order, and (via the payment step) payment — it's the fastest way to see how the pieces fit before you customise anything.
Code against contracts, resolve through proxies. Every entity has an interface (Vanilo\Product\Contracts\Product, Vanilo\Order\Contracts\Order, Vanilo\Payment\Contracts\Payment, …) bound to a concrete model via config. In your own code use the contract in type hints and constructor injection; when you need the concrete class (route model binding, ::query(), factories), use the Concord proxy:
use Vanilo\Order\Models\OrderProxy;
$orderClass = OrderProxy::modelClass(); // respects config/vanilo/order.php
To swap a model, extend the shipped one, point the config key at yours, and keep the contract. This is the supported extension mechanism — don't fork the package.
Cart is session-first. Cart::addItem() lazily creates a cart in the session. On login you generally want to adopt it (Cart::exists() / rebind to the user) and Cart::destroy() on logout, or you'll get ghost carts and duplicate line items after a re-login. For "buy now" or API flows, construct a cart explicitly instead of relying on the session facade.
Checkout is a state machine, not a form handler. Checkout::create($cart) → begin() → update($data, $step) per step (billing, shipping, payment, …) → complete(). Each update() validates the payload for that step and advances state. Keep the step order consistent with config; skipping steps out of order is where most checkout bugs come from. Persist the Checkout instance between requests (it's designed to be serialised into the session).
Orders: create from cart, then let events drive the rest. Order::createFromCart($cart) is the canonical path — it copies items, billpayer, shipping address, and totals. Afterwards, treat order status and payment status as two independent state machines; fire your own listeners on OrderCreated/payment-status changes rather than mutating statuses inline in controllers.
Payments are gateway-pluggable. Create a Payment against an order (or any Payable), attach a PaymentMethod (configured gateway), then hand off to the gateway. Gateways are registered per config/vanilo/payment.php and follow a request/response object contract — meaning you can add a custom driver without touching core. Keep a webhook/callback route per gateway and reconcile via payment events, not via the redirect.
Products, variants, properties, taxonomies. Price and stock may live on a variant rather than the product itself — check before assuming $product->price is authoritative. Arbitrary attributes go through Property/PropertyValue (typed: text, number, boolean, select), and categories go through Taxonomy/Taxon. Both are polymorphic-ish and worth seeding before you build catalogue screens.
Channels scope everything. In multi-store setups, Channel + the channel-aware traits determine which products, payment methods, and shipping methods are visible. If something "disappears" in a second store, it's almost always a missing channel association.
Grow by module, not by patch. For real customisation, generate a Concord module (php artisan list concord shows the generators) and put your domain logic there — it gets config, migrations, contracts, and model swapping for free, and it survives Vanilo upgrades cleanly.
vanilo/framework requires everything, including gateways and tax rules you may never use. In production, require the individual modules (vanilo/product, vanilo/cart, vanilo/order, …) and disable the rest in config/concord.php.vanilo/* packages must share the same major, and that major is pinned to a Laravel major. Composer conflicts after adding one gateway package are the single most common install failure — bump everything together.php artisan config:clear (and re-cache) — stale module registries produce baffling "class not found" or "contract not bound" errors. If it works in tinker but not in the app, this is why.number_format() in Blade will eventually disagree with the order total by a cent.vanilo/framework ships zero front-end. The admin is a separate vanilo/admin package. Budget for building views, auth integration, and checkout templates.$order->status = ...; save() bypasses listeners and breaks reporting/inventory integrations.php artisan concord:modules to confirm registration → OrderProxy::modelClass() in tinker to confirm which class is actually bound → check storage/logs for gateway request/response payloads → verify the channel association last.The framework rewards you for staying inside its contract-and-module seams and punishes shortcuts around them — nearly every "Vanilo bug" I've seen traced back to a swapped model without a cleared config cache, or business logic written directly against an Eloquent model instead of the contract.
How can I help you explore Laravel packages today?