initbiz/oc-cumuluscore-plugin
CumulusCore is the foundation plugin for building SaaS applications on October CMS, providing shared models, services, and backend components for multi-tenant setups, plans, subscriptions, and related core functionality. It is designed to be extended by Cumulus-based plugins and custom projects.
Install the plugin and its billing sibling (if you need Stripe) via Composer:
composer require initbiz/oc-cumuluscore-plugin
php artisan october:migrate
Then do these three things, in order — they are the real "hello world" of Cumulus:
User model with the Cumulus trait so it can own a plan and expose feature helpers:
// plugins/acme/site/models/User.php
use CumulusCore\Traits\CumulusUser;
class User extends \October\Rain\Auth\Models\User
{
use CumulusUser;
}
config/cumulus/features.php (or publish via php artisan vendor:publish --provider="CumulusCore\ServiceProvider"). Every gate you will ever use is keyed off this array — a feature is either boolean (max_users → true/false) or limited (max_projects → integer).false/0 by default and you'll chase phantom bugs.First use case worth wiring: gate a customer dashboard page. Register the plugin's middleware in your theme's routes.php or page settings, then in Twig:
{% if hasFeature('dashboard') %}
{% partial 'dashboard' %}
{% else %}
{{ 'Upgrade required'|_ }}
{% endif %}
That single check — hasFeature() — is the workhorse you'll reach for 80% of the time.
Twig gating (the everyday pattern). Cumulus registers three Twig functions you will use constantly:
hasFeature('key') → boolean, respects plan + feature flag.limitFeature('key') → integer limit, ideal for {% for %} caps and progress bars.canUseFeature('key') → considers current usage vs. limit (pair with a Usage recorded via the API below).{% set max = limitFeature('max_projects') %}
{% for project in projects|slice(0, max) %}{# ... #}{% endfor %}
<p>{{ projects|length }} / {{ max }} projects used</p>
Route/page middleware. Register CumulusCore\Classes\Middleware\CumulusMiddleware in a route group when the whole area requires a paid plan. It redirects (not 403) unauthenticated/unsubscribed users to a configurable upgrade page — good for UX, but see Gotchas.
Programmatic checks in PHP. Use the trait methods inside controllers/components:
if ($this->user->hasFeature('api_access')) { ... }
$limit = $this->user->getFeatureLimit('max_projects');
Track consumption with $this->user->addUsage('max_projects', 1) (or your own Usage record keyed on the feature row) so canUseFeature() has real numbers to work with.
Hook into lifecycle events. Extend without forking by listening for the plugin's events — e.g. cumulus.user.attached.to.plan and cumulus.subscription.activated. Typical job: send a welcome email or seed starter data when a user first gets a plan.
Event::listen('cumulus.user.attached.to.plan', function ($user, $plan) {
\Acme\Site\Models\Workspace::createFor($user);
});
Billing integration. If you're selling plans, add initbiz/oc-cumulusbilling-plugin (Stripe) and let it own plan changes — Cumulus Core is the feature-gate layer, Stripe is the money layer. Keep those responsibilities separate; don't mutate plan_id by hand in production.
Custom feature providers. When a limit isn't a plain integer (e.g. "storage in GB" or "seats per team"), extend the feature model or add a provider class in config/cumulus/features.php so getLimit() reads from your own source. This is the cleanest extension point Cumulus exposes.
Backend reporting. Cumulus ships backend lists for Plans, Features, and Users-with-plans. For a SaaS admin dashboard, extend those controllers (PlanController::extendListColumns) rather than building a parallel UI.
users exists. If you install it into a fresh project before running October's base migrations, its foreign keys fail. Run october:migrate on October first, then install Cumulus.hasFeature() silently returns false on a User model missing CumulusUser. Symptom: "it works in tinker but not on the site" usually means a different User class (a custom auth model, or an extended one in a plugin) didn't get the trait.features.php or a plan in the backend, run php artisan cache:clear — otherwise you'll debug stale limits for an hour.CumulusMiddleware needs cumulus_upgrade_page (or equivalent) configured. On an API route that means a client gets HTML or a redirect instead of JSON — write your own middleware for API surfaces.feature_overrides column and wrap the trait methods, or you'll fight every upgrade path.cumulus.user.registered flow so they land on a default free plan immediately.getFeatureLimit() can return "5" rather than 5 — cast before arithmetic, especially when computing remaining = limit - used.cumulus.* events and the backend controller extend* hooks for anything custom. Fork only if you need new Twig functions — and then PR them upstream, since the plugin is small (15 stars) and unlikely to gain them otherwise.hasFeature mocks. Create three plans (free/pro/enterprise) with overlapping features and use those in feature tests; mocking the trait misses caching and DB-relationship bugs that only show up against real plan rows.How can I help you explore Laravel packages today?