---
title: "Variants, SKUs & options — Shops for Laravel | Roundly"
description: "Sellable variants with their own SKU, price, currency, tax class and stock; options that compose them; and the automatic default variant."
url: https://roundly-consulting.com/open-source/docs/shops-for-laravel/variants-options
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Shops for Laravel](https://roundly-consulting.com/open-source/docs/shops-for-laravel.md)

# Variants, SKUs & options

Every sellable unit is a ProductVariant with its own sku, price, currency, tax class and stock. Every product gets one default variant when it is created — a real, sellable variant, not a placeholder that later variants replace. Set it up rather than leaving it at zero: price it for a simple product, or make it your first option variant and add the others next to it:

```php
use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Shops\Products\ProductVariant;

// The price cast writes the currency column from the Money.
$small = $product->defaultVariant;   // set up the default variant rather than leaving it at zero
$small->update(['sku' => 'WATER-0.5L', 'price' => Money::ofMinor(199, 'EUR'), 'stock' => 50]);

$large = $product->variants()->create([
    'sku' => 'WATER-1L', 'price' => Money::ofMinor(299, 'EUR'), 'stock' => 30, 'position' => 1,
]);

$product->price;                       // 1.99 EUR — the default (lowest-position) variant

// Re-pricing in another currency: set currency BEFORE price — the cast refuses
// to silently re-denominate a column that already holds another code.
$small->update(['currency' => 'USD', 'price' => Money::ofMinor(219, 'USD')]);

$product->defaultVariant;              // the lowest-position variant (WATER-0.5L)
$small->inStock(10);                   // bool — respects track_stock and the reserved quantity
$small->availableStock();              // stock - reserved
ProductVariant::query()->inStock(2);   // untracked, or at least 2 available
```

## The default variant

Every product gets one on created: SKU from the fallback-locale slug plus -DEFAULT (e.g. SPARKLING-WATER-DEFAULT), a zero price in the product’s shop currency (or the configured default), tax class standard, stock tracking on and zero stock. $product->defaultVariant and $product->price read the lowest-position variant, which is this one unless you reorder. Set its price and receive stock before selling it. SKUs are unique per product.

## Options

Options (Size, Colour) and their values model the axes of variation; a variant is the combination of one value per option. Resolve a variant from a set of option values:

```php
use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Shops\Products\Product;

$shirt = Product::create(['name' => 'T-Shirt']);

$size  = $shirt->options()->create(['name' => 'Size', 'position' => 0]);
$small = $size->values()->create(['value' => 'S', 'position' => 0]);
$large = $size->values()->create(['value' => 'L', 'position' => 1]);

// The default variant is a real, sellable variant — make it the first option variant:
$s = $shirt->defaultVariant;
$s->update(['sku' => 'TS-S', 'price' => Money::ofMinor(1990, 'EUR')]);
$s->optionValues()->attach($small);

$l = $shirt->variants()->create([
    'sku' => 'TS-L', 'price' => Money::ofMinor(1990, 'EUR'), 'position' => 1,
]);
$l->optionValues()->attach($large);

$shirt->variantFor([$small->id]);   // $s — exactly these option-value ids, or null
```

```php
$option->product();        // BelongsTo<Product>
$option->values();         // HasMany<ProductOptionValue>, ordered by position
$value->option();          // BelongsTo<ProductOption>
$value->variants();        // BelongsToMany<ProductVariant>
$variant->product();       // BelongsTo<Product>
$variant->optionValues();  // BelongsToMany<ProductOptionValue>
```

Options define what can be bought. For descriptive, filterable data such as material or weight, use the spec sheet (attributes) instead.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/shops-for-laravel.md)
