Skip to main content

Bulk Operations

The BulkOperations service lets you perform administrative billing changes across many subscriptions at once. Use it for price increases, mass plan swaps, global coupon campaigns, and cleanup tasks.

Available operations

OperationWhat it does
swapPlansMove a set of subscriptions to a new plan
cancelSubscriptionsCancel a set of subscriptions
updatePricesApply a price override to a set of subscription items
applyCouponsApply a coupon to a set of subscriptions

Using the BulkOperations service

Inject or resolve the service and pass a query scope or collection:
use Pixelworxio\Subscribd\Support\BulkOperations;
use Pixelworxio\Subscribd\Models\Plan;
use Pixelworxio\Subscribd\Models\Subscription;

$operations = app(BulkOperations::class);

// Move all Legacy subscribers to the Pro plan
$legacyPlan = Plan::where('key', 'legacy')->firstOrFail();
$proPlan    = Plan::where('key', 'pro')->firstOrFail();

$operations->swapPlans(
    subscriptions: Subscription::wherePlanId($legacyPlan->id)->active(),
    newPlan: $proPlan,
    options: ['prorate' => 'none'],
);

Swap plans

$operations->swapPlans(
    subscriptions: Subscription::query()->wherePlanId($oldPlan->id),
    newPlan: $newPlan,
    options: ['prorate' => 'renewal'],  // 'now' | 'renewal' | 'none'
);

Cancel subscriptions

$operations->cancelSubscriptions(
    subscriptions: Subscription::query()
        ->pastDue()
        ->where('created_at', '<', now()->subMonths(3)),
    immediately: false,
);

Update prices (bulk price override)

Apply a promotional price to a set of subscription items. Useful for grandfathered pricing — existing subscribers keep their old price while new subscribers pay the new rate.
use Carbon\CarbonImmutable;

$operations->updatePrices(
    subscriptions: Subscription::query()->wherePlanId($plan->id),
    planItemKey: 'projects',
    newPrice: 800,   // $8.00 per additional project (down from $10.00)
    expiresAt: null, // Permanent override
);

// Or time-limited
$operations->updatePrices(
    subscriptions: Subscription::query()->wherePlanId($plan->id),
    planItemKey: null,  // null = base plan price override
    newPrice: 3900,
    expiresAt: CarbonImmutable::parse('2026-12-31'),
);

Apply coupons

use Pixelworxio\Subscribd\Models\Coupon;

$coupon = Coupon::where('code', 'LOYALTY20')->firstOrFail();

$operations->applyCoupons(
    subscriptions: Subscription::query()
        ->active()
        ->where('created_at', '<', now()->subYear()),
    coupon: $coupon,
);

Artisan command

The subscribd:bulk-operations command reads a JSON file of operation definitions and executes them in sequence. Useful for deployment scripts and cron-driven operations.
php artisan subscribd:bulk-operations --file=storage/bulk-ops.json
Operation file format:
[
  {
    "type": "swap_plans",
    "from_plan": "legacy",
    "to_plan": "pro",
    "options": { "prorate": "none" }
  },
  {
    "type": "apply_coupons",
    "coupon_code": "LOYALTY20",
    "filter": { "plan": "pro", "created_before": "2025-01-01" }
  }
]

Admin Livewire component

The admin.bulk-operations Livewire component provides a UI for running bulk operations from the admin panel:
<livewire:subscribd.admin.bulk-operations />
Features:
  • Plan swap with plan picker and proration option
  • Bulk cancel with immediately/grace-period toggle
  • Price override with per-item targeting and expiry date
  • Coupon application with code lookup
  • Progress indicator for large batches
  • Confirmation step before executing
For Filament users, this is available as a custom page registered by SubscribdFilamentPlugin.

Error handling

Bulk operations apply changes one subscription at a time inside a transaction per subscription. A failure on one subscription is logged and skipped — the operation continues with the remaining subscriptions. Check your logs for BulkOperation.failed entries after a run. To treat any failure as fatal (stop on first error), pass stopOnError: true:
$operations->swapPlans(
    subscriptions: $query,
    newPlan: $newPlan,
    stopOnError: true,
);

Next steps