> ## Documentation Index
> Fetch the complete documentation index at: https://docs.subscribd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Plan Items and Add-ons

> Composable, priced add-ons attached to plans with per-item cap behaviour

# Plan Items and Add-ons

`PlanItem` is the structured way to attach priced, quantified add-ons to a plan. Use it whenever a feature has a price, an included quantity, or an overage behaviour — not just a boolean flag.

## When to use PlanItem vs features JSON

| Use case                               | Use                                                                                        |
| -------------------------------------- | ------------------------------------------------------------------------------------------ |
| Boolean feature flag (`exports: true`) | `Plan.features` JSON column                                                                |
| Simple integer limit (`projects: 10`)  | `PlanItem` — migrate from features JSON for entitlement resolution via `included_quantity` |
| Priced add-on with overage billing     | `PlanItem` with `unit_price` and `cap_behavior`                                            |
| Metered usage charged at period end    | `PlanItem` with `pricing_rule = 'metered'`                                                 |

Booleans stay in `features`. Anything with a price or quantity goes in `PlanItem`.

## Creating plan items

```php theme={null}
use Pixelworxio\Subscribd\Models\Plan;
use Pixelworxio\Subscribd\Models\PlanItem;
use Pixelworxio\Subscribd\Enums\CapBehavior;

$pro = Plan::where('key', 'pro')->firstOrFail();

// 3 projects included; $10/mo per additional project; hard cap at 50
PlanItem::create([
    'plan_id'           => $pro->id,
    'key'               => 'projects',
    'name'              => 'Projects',
    'included_quantity' => 3,
    'unit_price'        => 1000,        // $10.00 in minor units (cents)
    'cap_behavior'      => CapBehavior::ChargeUntilCeiling,
    'ceiling'           => 50,
    'sort_order'        => 0,
    'active'            => true,
]);

// 5 team seats included; no overage allowed
PlanItem::create([
    'plan_id'           => $pro->id,
    'key'               => 'team_seats',
    'name'              => 'Team Seats',
    'included_quantity' => 5,
    'unit_price'        => 0,
    'cap_behavior'      => CapBehavior::Block,
    'sort_order'        => 1,
    'active'            => true,
]);
```

## CapBehavior enum

| Value                | Behaviour                                                                   |
| -------------------- | --------------------------------------------------------------------------- |
| `Block`              | Access denied when quantity exceeds `included_quantity`. No billing.        |
| `Charge`             | Bill at `unit_price` per unit beyond `included_quantity`, with no ceiling.  |
| `ChargeUntilCeiling` | Bill at `unit_price` per unit up to `ceiling`. Hard block beyond `ceiling`. |

## SubscriptionItem — per-subscriber state

When a subscriber has a plan with `PlanItem`s, a `SubscriptionItem` record is created per item at subscription time. `SubscriptionItem` tracks:

* `quantity` — how many units the subscriber is currently using
* `price_override` / `price_override_expires_at` — admin-set per-subscriber price
* `gateway_item_id` — the gateway's item ID for native gateways (Stripe)

```php theme={null}
// Get a subscriber's current project count
$subscription = $user->subscription();
$item = $subscription->items()->where('key', 'projects')->firstOrFail();
$item->quantity;          // int — current usage
$item->price_override;    // int|null — cents, null if no override
```

## Updating quantities

```php theme={null}
use Pixelworxio\Subscribd\Actions\UpdateQuantity;

$item = $user->subscription()->items()->where('key', 'projects')->firstOrFail();
app(UpdateQuantity::class)->execute($user->subscription(), quantity: 7, planItemKey: 'projects');
```

For gateways that manage items natively (Stripe), this also updates the subscription item at the gateway.

## Entitlement checks

`Entitlements` resolves plan items automatically:

```php theme={null}
use Pixelworxio\Subscribd\Facades\Entitlements;

// How many projects can this user have?
$limit = Entitlements::for($user)->limit('projects');   // int|null (null = unlimited)

// Can they create another project?
$canCreate = $limit === null || ($user->projects()->count() < $limit);
```

Resolution order:

1. Check `Plan.features[$key]` — if a value is set here, use it.
2. Check `PlanItem` by `key` — use `included_quantity` as the limit.

## Price overrides

Apply a per-subscriber price override to any `SubscriptionItem`:

```php theme={null}
use Pixelworxio\Subscribd\Actions\SetPriceOverride;
use Carbon\CarbonImmutable;

$item = $user->subscription()->items()->where('key', 'projects')->firstOrFail();

// Permanent override
app(SetPriceOverride::class)->execute(
    subscription: $user->subscription(),
    item: $item,
    price: 500,        // $5.00 per additional project (was $10.00)
    expiresAt: null,
);

// Time-limited override (reverts automatically at renewal)
app(SetPriceOverride::class)->execute(
    subscription: $user->subscription(),
    item: $item,
    price: 0,
    expiresAt: CarbonImmutable::parse('2026-12-31'),
);
```

When an override's `expires_at` passes at renewal, `RenewSubscription` fires `SubscriptionPriceOverrideReverted` and reverts to `price_snapshot`.

## Overage billing at renewal

At renewal, `RenewSubscription` automatically charges overages for items with `CapBehavior::Charge` or `CapBehavior::ChargeUntilCeiling`:

```
overage_units  = max(0, subscription_item.quantity - plan_item.included_quantity)
overage_amount = overage_units × effective_unit_price
```

`effective_unit_price` is `SubscriptionItem.price_override` when active, otherwise `PlanItem.unit_price`.

For gateways with native subscription item support (Stripe), overage is reported via the gateway's items API and included in the gateway's renewal invoice. For other gateways, subscribd issues a separate `charge()` call.

## Next steps

* **[Subscriptions](/subscriptions/creating)** — Creating subscriptions with plan items
* **[Entitlements](/entitlements/limits-and-usage)** — Checking limits and usage
* **[Multi-Currency](/advanced/multi-currency)** — Currency on plan items
* **[Bulk Operations](/advanced/bulk-operations)** — Bulk price overrides
