PHPackages                             ghostcompiler/ghostgrid - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ghostcompiler/ghostgrid

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

ghostcompiler/ghostgrid
=======================

Dynamic multi-tenant reseller commerce, hierarchical pricelists, storefront products, orders, services, and action-based provisioning for Laravel.

v1.0.0(1mo ago)11MITPHPPHP ^8.2CI passing

Since May 3Pushed 1mo agoCompare

[ Source](https://github.com/ghostcompiler/ghostgrid)[ Packagist](https://packagist.org/packages/ghostcompiler/ghostgrid)[ RSS](/packages/ghostcompiler-ghostgrid/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (8)Versions (2)Used By (0)

GhostCompiler GhostGrid
=======================

[](#ghostcompiler-ghostgrid)

Version: `1.0.0`

GhostGrid is a Laravel backend package for tenant-based reseller commerce. It gives your app accounts, account hierarchy, providers, product variants, pricelists, storefront products, order snapshots, provisioning tasks, and services.

It is not an admin panel. You build your own panel, API, checkout, billing area, or storefront on top of the package.

Supports Laravel `10`, `11`, `12`, and `13`. Laravel `13` is tested on PHP `8.3+`.

Install
-------

[](#install)

```
composer require ghostcompiler/ghostgrid
php artisan gg:init
php artisan migrate
```

Manual publish:

```
php artisan vendor:publish --tag=ghostgrid-config
php artisan vendor:publish --tag=ghostgrid-migrations
```

Setup User Model
----------------

[](#setup-user-model)

Add the trait to `app/Models/User.php`:

```
use GhostCompiler\GhostGrid\Support\Concerns\HasGhostGrid;

class User extends Authenticatable
{
    use HasGhostGrid;
}
```

Now your controllers can use the short helper:

```
$priceLists = ghostgrid()->priceLists();
$active = ghostgrid()->activePriceList();
$products = ghostgrid()->visibleStorefrontProducts();
```

In a job, seeder, or test where there is no logged-in user, pass the user:

```
$priceLists = ghostgrid($user)->priceLists();
```

You can also use the facade:

```
use GhostCompiler\GhostGrid\Facades\GhostGrid;

$grid = GhostGrid::forUser(auth()->user());
```

Mental Model
------------

[](#mental-model)

Your Laravel `users` table handles login. GhostGrid `accounts` are commerce identities.

```
Laravel User
  -> account_users
      -> GhostGrid Account
          -> Price Lists
          -> Storefront Products
          -> Orders
          -> Services

```

So `account_id` means `accounts.id`, not `users.id`.

The helper hides this most of the time:

```
$account = ghostgrid()->account();
$accounts = ghostgrid()->accounts();
```

Quick Start
-----------

[](#quick-start)

Create the seller account:

```
$account = ghostgrid()->createAccount([
    'name' => 'Acme Hosting',
    'type' => 'reseller',
]);
```

Create a provider record:

```
$provider = ghostgrid()->createProvider('hetzner', 'Hetzner');
```

Create a backend product, variant, and provider SKU mapping:

```
[$product, $variant] = ghostgrid()->createProduct(
    product: [
        'name' => 'Cloud VPS',
        'slug' => 'cloud-vps',
        'type' => 'vps',
    ],
    variant: [
        'name' => 'Hetzner CX32',
        'slug' => 'cx32',
        'billing_cycle' => 'monthly',
        'specs' => ['ram' => '4 GB', 'disk' => '80 GB SSD'],
    ],
    provider: $provider,
    mapping: [
        'provider_sku' => 'cx32',
        'priority' => 100,
        'config' => ['region' => 'fsn1'],
    ],
);
```

Create and assign a pricelist:

```
$priceList = ghostgrid()->createPriceList('Default VPS Pricing', [
    [
        'product_variant_id' => $variant->id,
        'billing_cycle' => 'monthly',
        'selling_price' => 1499,
        'setup_fee' => 0,
    ],
]);
```

Create a frontend/storefront product:

```
$storefrontProduct = ghostgrid()->createStorefrontProduct([
    'product_variant_id' => $variant->id,
    'price_list_id' => $priceList->id,
    'name' => 'Premium VPS 4GB',
    'slug' => 'premium-vps-4gb',
    'description' => 'Fast cloud server for growing sites',
    'actual_price' => 2000,
    'price' => 1499,
    'popular' => true,
    'visible' => true,
    'featured' => true,
    'features' => [
        ['label' => '4 GB RAM', 'icon' => 'server'],
        ['label' => '80 GB SSD', 'icon' => 'hard-drive'],
        ['label' => 'Free Setup', 'icon' => null],
    ],
]);
```

Show products on your frontend:

```
$products = ghostgrid()->visibleStorefrontProducts();
```

Pricelists
----------

[](#pricelists)

Most apps only need these methods:

```
// All pricelists owned by accounts linked to the current user.
$priceLists = ghostgrid()->priceLists();

// Active/default assigned pricelist for the current account.
$active = ghostgrid()->activePriceList();

// Pricelists that have an assignment.
$assigned = ghostgrid()->assignedPriceLists();

// Draft pricelists with no assignment.
$drafts = ghostgrid()->unassignedPriceLists();

// Find by ID or name, scoped to the current user.
$priceList = ghostgrid()->priceList('Default VPS Pricing');
```

Create a draft pricelist without assigning it:

```
$draft = ghostgrid()->createUnassignedPriceList('Draft Sale Pricing', [
    [
        'product_variant_id' => $variant->id,
        'billing_cycle' => 'monthly',
        'selling_price' => 1299,
    ],
]);
```

This is the same as:

```
$draft = ghostgrid()->createPriceList(
    name: 'Draft Sale Pricing',
    items: [
        [
            'product_variant_id' => $variant->id,
            'billing_cycle' => 'monthly',
            'selling_price' => 1299,
        ],
    ],
    assign: false,
);
```

Assign an existing pricelist later:

```
$assignment = ghostgrid()->assignPriceList($draft);
```

Assign it to a specific linked account:

```
$assignment = ghostgrid()->assignPriceList(
    priceList: $draft,
    account: $clientAccount,
);
```

Assign with a date window:

```
$assignment = ghostgrid()->assignPriceList(
    priceList: $draft,
    account: $clientAccount,
    isDefault: true,
    startsAt: '2026-06-01 00:00:00',
    endsAt: '2026-06-30 23:59:59',
);
```

Master Pricelist Sharing
------------------------

[](#master-pricelist-sharing)

GhostGrid supports a master pricelist that acts as the source of products a tenant may sell.

When a tenant creates a pricelist from a master:

- products with tenant price overrides become active
- products without tenant prices are copied as inactive
- inactive state is stored on `price_list_items.enabled`
- this is not a storefront visibility feature

```
$tenantPriceList = ghostgrid()->createPriceListFromParent(
    parent: $masterPriceList,
    name: 'Tenant VPS Pricing',
    itemOverrides: [
        $variant->id => [
            'selling_price' => 1499,
            'enabled' => true,
        ],
    ],
);
```

If a master product has no tenant price, GhostGrid creates it like this:

```
[
    'product_variant_id' => $variant->id,
    'selling_price' => '1000.00',
    'enabled' => false,
    'metadata' => [
        'inherited_from_parent' => true,
        'tenant_price_set' => false,
    ],
]
```

When the master pricelist gets new products, sync missing items into the tenant pricelist as inactive rows:

```
$createdInactiveItems = ghostgrid()->syncPriceListWithParent($tenantPriceList);
```

Pricing Rules
-------------

[](#pricing-rules)

By default:

- a child price cannot go below the parent `selling_price`
- if parent `min_selling_price` exists, that value becomes the minimum
- disabled parent products cannot be sold by child pricelists
- archived pricelists do not resolve as active pricelists
- helper methods only mutate records owned by accounts linked to the current user

Allow selling below the parent:

```
'allow_selling_below_parent' => true,
```

Storefront Products
-------------------

[](#storefront-products)

Backend variants are the real sellable products. Storefront products are frontend display records.

Example:

```
Backend variant: Hetzner CX32
Storefront product: Premium VPS 4GB

```

Create:

```
$storefrontProduct = ghostgrid()->createStorefrontProduct([
    'product_variant_id' => $variant->id,
    'price_list_id' => $priceList->id,
    'name' => 'Premium VPS 4GB',
    'slug' => 'premium-vps-4gb',
    'description' => 'Fast cloud server for growing sites',
    'actual_price' => 2000,
    'price' => 1499,
    'popular' => true,
    'features' => [
        ['label' => '4 GB RAM', 'icon' => 'server'],
        ['label' => 'Free Setup', 'icon' => null],
    ],
]);
```

Read visible products:

```
$products = ghostgrid()->visibleStorefrontProducts();
```

Hide:

```
ghostgrid()->hideStorefrontProduct($storefrontProduct);
```

Orders
------

[](#orders)

Use `CreateOrderFromCart` after the account has an active assigned pricelist.

```
use GhostCompiler\GhostGrid\Actions\CreateOrderFromCart;

$order = app(CreateOrderFromCart::class)->handle(
    accountId: ghostgrid()->account()->id,
    cartItems: [
        [
            'product_variant_id' => $variant->id,
            'billing_cycle' => 'monthly',
            'quantity' => 1,
        ],
    ],
);
```

GhostGrid snapshots at order time:

- product name
- variant name
- unit price
- setup fee
- provider ID
- provider SKU
- provider mapping config

Payment To Provisioning
-----------------------

[](#payment-to-provisioning)

After payment succeeds:

```
$paidOrder = ghostgrid()->markOrderPaid(
    order: $order,
    createProvisioningTasks: true,
    dispatch: true,
);
```

Create tasks without dispatching:

```
$tasks = ghostgrid()->createProvisioningTasksForOrder(
    order: $order,
    dispatch: false,
);
```

Provisioning task creation is idempotent per order item, provider, and action. If your webhook runs twice, duplicate create-tasks are not created.

Provider Adapters
-----------------

[](#provider-adapters)

Generate an adapter:

```
php artisan gg:vendor Hetzner
```

Register it:

```
'provider_registry' => [
    'hetzner' => App\GhostGrid\Providers\HetznerProvider::class,
],
```

Adapters implement:

```
use GhostCompiler\GhostGrid\DTO\ProvisioningResult;

interface ProvisioningProvider
{
    public function code(): string;
    public function name(): string;
    public function capabilities(): array;
    public function supports(string $action): bool;
    public function execute(string $action, array $payload): ProvisioningResult;
}
```

Dynamic actions can be anything your adapter supports:

```
create
suspend
unsuspend
terminate
renew
reboot
shutdown
power_on
reinstall
reset_password
upgrade
downgrade
sync_products
```

Execute manually:

```
use GhostCompiler\GhostGrid\Services\ProvisioningManager;

$result = app(ProvisioningManager::class)->execute(
    providerCode: 'hetzner',
    action: 'reboot',
    payload: ['remote_id' => 'server-123'],
);
```

Services
--------

[](#services)

Provisioning `create` success creates or updates a `services` record.

Service lifecycle helpers create provider action tasks:

```
ghostgrid()->suspendService($service, dispatch: true);
ghostgrid()->unsuspendService($service, dispatch: true);
ghostgrid()->renewService($service, dispatch: true);
ghostgrid()->terminateService($service, dispatch: true);
```

Update, Archive, Delete
-----------------------

[](#update-archive-delete)

Most helper methods accept a model instance or ID.

```
ghostgrid()->updateAccount($account, ['name' => 'Updated Hosting']);
ghostgrid()->archiveAccount($account);
ghostgrid()->deleteAccount($account);

ghostgrid()->updatePriceList($priceList, ['name' => 'Published Pricing']);
ghostgrid()->archivePriceList($priceList);
ghostgrid()->deletePriceList($priceList);

ghostgrid()->addPriceListItem($priceList, [
    'product_variant_id' => $variant->id,
    'billing_cycle' => 'monthly',
    'selling_price' => 1600,
]);
ghostgrid()->updatePriceListItem($item, ['selling_price' => 1700]);
ghostgrid()->disablePriceListItem($item);
ghostgrid()->deletePriceListItem($item);

ghostgrid()->hideStorefrontProduct($storefrontProduct);
ghostgrid()->deleteStorefrontProduct($storefrontProduct);
```

Because GhostGrid does not use database foreign key constraints, your app decides whether hard delete is allowed. For production commerce flows, prefer archive/disable helpers for live records.

ID Strategy
-----------

[](#id-strategy)

Default ID strategy is UUID.

Set before running migrations:

```
GHOSTGRID_ID_STRATEGY=uuid
# uuid, ulid, bigint
```

Rules:

- `uuid`: string UUID primary keys
- `ulid`: string ULID primary keys
- `bigint`: auto-incrementing big integer primary keys
- relationship columns follow the same strategy
- relationship columns are indexed

No Foreign Key Constraints
--------------------------

[](#no-foreign-key-constraints)

GhostGrid intentionally avoids database foreign key constraints.

Migrations use indexed relationship columns, but never:

```
foreign()
constrained()
references()
cascadeOnDelete()
nullOnDelete()
```

This gives reseller systems more flexibility for imports, provider sync, account movement, and recovery. Your app should enforce deletion rules at the application layer.

Config
------

[](#config)

Published config:

```
return [
    'id_strategy' => env('GHOSTGRID_ID_STRATEGY', 'uuid'),
    'default_currency' => env('GHOSTGRID_DEFAULT_CURRENCY', 'USD'),
    'allow_selling_below_parent' => env('GHOSTGRID_ALLOW_SELLING_BELOW_PARENT', false),
    'queue_connection' => env('GHOSTGRID_QUEUE_CONNECTION', env('QUEUE_CONNECTION', 'sync')),
    'provider_registry' => [],
    'model_overrides' => [],
    'storefront' => [
        'default_visible' => true,
        'default_featured' => false,
    ],
    'enabled_modules' => [
        'accounts' => true,
        'providers' => true,
        'pricing' => true,
        'storefront' => true,
        'orders' => true,
        'provisioning' => true,
        'services' => true,
    ],
];
```

Internal constants such as order statuses, provisioning statuses, service statuses, and default provisioning actions live in PHP classes, not public config.

Commands
--------

[](#commands)

```
php artisan gg:init
php artisan gg:vendor Hetzner
php artisan gg:sync hetzner
php artisan gg:demo
php artisan gg:check
```

Factories
---------

[](#factories)

GhostGrid ships factories for tests and seeders:

```
use GhostCompiler\GhostGrid\Models\Account;
use GhostCompiler\GhostGrid\Models\Product;
use GhostCompiler\GhostGrid\Models\ProductVariant;
use GhostCompiler\GhostGrid\Models\PriceList;
use GhostCompiler\GhostGrid\Models\StorefrontProduct;

$account = Account::factory()->create();
$product = Product::factory()->create();
$variant = ProductVariant::factory()->create(['product_id' => $product->id]);
$priceList = PriceList::factory()->create(['account_id' => $account->id]);
$storefront = StorefrontProduct::factory()->create([
    'account_id' => $account->id,
    'product_variant_id' => $variant->id,
]);
```

Advanced Raw Model API
----------------------

[](#advanced-raw-model-api)

Helpers are recommended for tenant-aware app code. Raw Eloquent models are available for seeders, internal admin tools, or custom workflows.

```
use GhostCompiler\GhostGrid\Models\Account;
use GhostCompiler\GhostGrid\Models\PriceList;

$account = Account::create(['name' => 'Acme Hosting']);

$priceList = PriceList::create([
    'account_id' => $account->id,
    'name' => 'Default Pricing',
    'currency' => 'USD',
]);

$priceList->items()->create([
    'product_variant_id' => $variant->id,
    'billing_cycle' => 'monthly',
    'selling_price' => 1499,
]);
```

Helper API Cheatsheet
---------------------

[](#helper-api-cheatsheet)

```
$grid = ghostgrid();

$grid->createAccount([...]);
$grid->account();
$grid->accounts();
$grid->createChildAccount([...]);
$grid->updateAccount($account, [...]);
$grid->deleteAccount($account);
$grid->archiveAccount($account);

$grid->createProvider('hetzner', 'Hetzner');
$grid->updateProvider($provider, [...]);
$grid->deleteProvider($provider);
$grid->disableProvider($provider);

$grid->createProduct($product, $variant, $provider, $mapping);
$grid->updateProduct($product, [...]);
$grid->deleteProduct($product);
$grid->archiveProduct($product);
$grid->updateProductVariant($variant, [...]);
$grid->deleteProductVariant($variant);
$grid->archiveProductVariant($variant);
$grid->updateProviderMapping($mapping, [...]);
$grid->deleteProviderMapping($mapping);
$grid->disableProviderMapping($mapping);

$grid->priceLists();
$grid->activePriceList();
$grid->assignedPriceLists();
$grid->unassignedPriceLists();
$grid->priceList($idOrName);
$grid->createPriceList('Default', $items);
$grid->createPriceList('Draft', $items, assign: false);
$grid->createUnassignedPriceList('Draft', $items);
$grid->createPriceListFromParent($masterPriceList, 'Tenant Pricing', $overrides);
$grid->syncPriceListWithParent($tenantPriceList);
$grid->assignPriceList($priceList);
$grid->updatePriceList($priceList, [...]);
$grid->deletePriceList($priceList);
$grid->archivePriceList($priceList);
$grid->addPriceListItem($priceList, [...]);
$grid->updatePriceListItem($item, [...]);
$grid->deletePriceListItem($item);
$grid->disablePriceListItem($item);

$grid->createStorefrontProduct([...]);
$grid->visibleStorefrontProducts();
$grid->updateStorefrontProduct($storefrontProduct, [...]);
$grid->deleteStorefrontProduct($storefrontProduct);
$grid->hideStorefrontProduct($storefrontProduct);

$grid->markOrderPaid($order, createProvisioningTasks: true, dispatch: true);
$grid->createProvisioningTasksForOrder($order);
$grid->suspendService($service);
$grid->unsuspendService($service);
$grid->renewService($service);
$grid->terminateService($service);
```

Docs Website
------------

[](#docs-website)

Open the static docs:

```
docs/index.html
docs/docs.html

```

GitHub Pages:

```
https://ghostcompiler.github.io/ghostgrid/

```

Testing
-------

[](#testing)

```
composer validate --strict
composer test
```

The repository includes GitHub Actions coverage for Laravel `10`, `11`, `12`, and `13`.

Security Notes
--------------

[](#security-notes)

- Helper methods are tenant-aware and only mutate records owned by accounts linked to the current user.
- Raw models/actions are intentionally available for advanced/internal app code, so protect them with your own policies/controllers.
- Provider credentials should be stored encrypted by your Laravel app.
- Keep provider adapter payloads minimal and avoid logging secrets.

Roadmap
-------

[](#roadmap)

- More optional helper methods for checkout flows
- More first-party provider examples
- More docs recipes for billing portals and reseller dashboards
- Additional test coverage for custom model overrides

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance92

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

37d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/fdfc8e246731b39477bdf1668097bd52dfa52a1a9588235cbdd75d2dc7c6b927?d=identicon)[ghostcompiler](/maintainers/ghostcompiler)

---

Tags

laravelmulti-tenantphppricelistsprovisioningreseller-commerce

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ghostcompiler-ghostgrid/health.svg)

```
[![Health](https://phpackages.com/badges/ghostcompiler-ghostgrid/health.svg)](https://phpackages.com/packages/ghostcompiler-ghostgrid)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9732.3M121](/packages/roots-acorn)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[spatie/laravel-health

Monitor the health of a Laravel application

88011.3M149](/packages/spatie-laravel-health)[illuminate/queue

The Illuminate Queue package.

20432.2M1.5k](/packages/illuminate-queue)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
