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

Product Laravel Package

vanilo/product

Vanilo Product is the catalog foundation for Laravel e-commerce, offering products, product variants, master products and product types with flexible attributes, pricing and inventory-ready models you can extend to build a custom storefront.

View on GitHub
Deep Wiki
Context7

Getting Started

vanilo/product is almost never installed alone — it is the domain module underpinning vanilo/framework, vanilo/cart, and vanilo/order. The package depends on konekt/concord for its contract/model binding, so treat it as a building block rather than a standalone library.

Minimal install:

composer require vanilo/product

Migrations are shipped in database/migrations and must be published/copied before usage (Concord provides php artisan vendor:publish --tag=concord-migrations, or copy them into database/migrations). Then php artisan migrate.

The Concord module is auto-discovered; if you use Laravel's package discovery, Vanilo\Product\Providers\ModuleServiceProvider registers the models, contracts, and migrations. If you build your own module list, register it explicitly.

Where to look first, in order:

  1. src/Contracts/ — Product, ProductVariant, ProductType, and the interfaces they satisfy (including Konekt\Concord\Contracts\Concord bindings).
  2. src/Models/Product.php — the canonical read of every column, cast, and relationship.
  3. src/Models/ProductVariant.php and MasterProductVariant.php — this is where most surprises live.
  4. database/migrations/*_create_products_table.php — the actual column set for your installed version (the schema has changed between v3 and v4; price/stock moved into sibling modules).

First use case — read products for a catalog page:

use Vanilo\Product\Models\Product;
use Vanilo\Product\ProductState;

$products = Product::query()
    ->where('state', ProductState::ACTIVE)
    ->with(['type', 'variants'])
    ->paginate(24);

Or with the built-in scope:

$products = Product::active()->with('type')->get();

Creating a product:

$product = Product::create([
    'name'  => 'Trail Runner',
    'sku'   => 'TR-001',
    'state' => ProductState::ACTIVE,
]);

slug is handled by the model (Spatie-style Sluggable-equivalent or a mutator depending on version) — verify in the model if slugs come out unexpectedly.

Implementation Patterns

Contracts over concrete models. Because Concord binds contracts to models, resolve through the container in application code:

$productClass = app(\Vanilo\Product\Contracts\Product::class);
$product = $productClass::findOrFail($id);

This is the pattern that keeps you compatible when a project later swaps in a custom subclass via Concord::registerModel(...).

Variants vs. the master variant. A Product may have zero or more ProductVariant rows. For products without variants, the package exposes a masterVariant accessor (a transient MasterProductVariant synthesized from the parent's own fields). Always branch on $product->hasVariants() before iterating $product->variants:

if ($product->hasVariants()) {
    foreach ($product->variants as $variant) {
        // $variant->sku, $variant->price (if pricing module installed)
    }
} else {
    // use $product->masterVariant
}

Cart integration via Buyable. Both Product and ProductVariant implement the Buyable contract from vanilo/contracts. That is what lets vanilo/cart accept them:

Cart::addItem($product, 2);

If you subclass Product, you must preserve the Buyable method implementations (getId(), getName(), getPrice(), getSku()) — most breakages in this area come from overriding getPrice().

Product types. ProductType is a lightweight classification record. Filtering by type is the common entry point for per-vertical catalogs:

$shoes = Product::where('product_type_id', $shoeType->id)->active()->get();

product_type_id is nullable, so seed at least one type before relying on it in queries.

Category attachment. Categories live in vanilo/category; the linkage is a many-to-many pivot declared on the category side. Attach from the category, not the product, unless you've confirmed the inverse relation exists:

$category->products()->attach($product->id);

Eager loading. type and variants are the two relations hit on virtually every catalog listing. Add them to with([...]) unconditionally — N+1 here is the single most common performance regression in Vanilo apps.

Repository/service indirection. For anything beyond trivial CRUD, wrap creation in an action class rather than calling Product::create() inline. You'll want a single place to enforce SKU uniqueness, trigger media uploads, and dispatch product-created events.

Testing. Use the package's own factories (Product::factory(), ProductVariant::factory() if present) plus RefreshDatabase. Seed ProductType rows explicitly; tests that assume a default type exist are the most common source of flaky fixtures.

Gotchas and Tips

Schema drift between major versions. Vanilo 3 kept price and stock columns on products; Vanilo 4 pushed pricing into vanilo/pricing and stock into a dedicated module. Read database/migrations in the installed tag before writing accessors — blog posts and Stack Overflow answers routinely describe the wrong schema.

state is an enum, not a string. It is backed by Vanilo\Product\ProductState and cast on the model. Comparisons like $product->state === 'active' silently fail; use the enum:

if ($product->state === ProductState::ACTIVE) { ... }

The active() scope handles this for you — prefer it.

Concord model binding and the cache. If you Concord::registerModel('product', CustomProduct::class), you must clear Concord's registry cache (php artisan concord:cache or the equivalent in your version) or the old model will keep resolving. This trips people up in CI where caches are warm.

Slug uniqueness. Slugs are generated from name; creating two products with the same name in the same request can race the unique index. Catch QueryException on 23000 or generate the slug yourself and pass it in.

Price accessors vary by version. If vanilo/pricing is present, $product->price may return a Price model while getPrice() (the Buyable method) returns a float. Don't assume they're interchangeable in Blade — decide once and stay consistent.

Soft deletes. Product uses SoftDeletes. Product::all() excludes trashed rows, but relations can still surface them depending on the pivot. Add ->withTrashed() deliberately when needed, and remember slugs of soft-deleted products still occupy the unique index.

Extension points worth knowing. Custom attributes: subclass Product and register via Concord, or use the properties module if you don't need new columns. Media: the package integrates with Spatie Media Library in the framework layer — check whether the trait is on the model or expected in your subclass. Events: Vanilo fires product lifecycle events through the framework; hook there rather than in model observers for anything order-related.

Don't query products directly from views. Go through a repository or scope so that state filtering, tenant/channel scoping, and trashed handling stay in one place — you'll otherwise discover four different "active product" definitions across the codebase.

Low star count is not a risk signal here. This package has 19 stars because it lives inside the Vanilo ecosystem, not because it is unused. Judge it by the health of vanilophp/framework and the release cadence, not by this repo alone.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package