Development
This guide covers the development workflow, tests, demo mode, project structure, and deployment process for the SaaS Template.
Development Commands
Server & Assets
# PHP server, queue listener, Pail logs, and Vite together
composer dev
Database
php artisan migrate
Stripe & Queue
stripe listen --forward-to localhost:8000/stripe/webhook
Custom Artisan Commands
php artisan app:add-permissions-to-new-modules
Testing & Code Quality
The PHP suite uses Pest 4 on an in-memory SQLite database. Subscription tests live in tests/Feature/Subscription and cover the billing page, the Subscribed middleware, and the Product model. Demo mode tests live in tests/Feature/Demo.
composer test
GitHub Actions run Pint, the frontend format and lint scripts, and the test suite on pushes and pull requests to develop and main.
Demo Mode
Demo mode turns an instance into a public, resettable live demo. It exists on the feature/demo-mode branch and is off unless DEMO_MODE=true.
What It Does
- Banner — A bar at the top of the panel (login page included) reads "Live demo — data resets every night" and links to the product page set in
DEMO_PRODUCT_URL. - Demo accounts —
demo:seedcreates an admin (admin@demo.omega-studio-software.com, role Super Admin) and a user (user@demo.omega-studio-software.com, role Customer). Their shared password is defined inconfig/demo.php. - Seed data — Roles, permissions (if missing), role-permission assignments, and the three plans from
ProductSeeder. - Guarded credentials — Demo accounts cannot have their password or email changed, from the profile page or the Users resource, and cannot be deleted.
- Mail — The mail driver is forced to
log. - Uploads —
Demo::uploadMaxKilobytes()caps uploads at 2 MB in demo mode. - Stripe keys —
demo:seedwarns whenSTRIPE_SECRETstarts withsk_live_. Use test keys on demo instances. Plans without astripe_idare created in Stripe byProductSeeder.
Commands
php artisan demo:seed
Both commands fail when DEMO_MODE is off, and refuse to run unless APP_URL has a host starting with demo- or DEMO_ALLOW_RESET=true is set. When demo mode is on and the host allows it, demo:reset is scheduled daily at 03:00.
Configuration
- Name
DEMO_MODE- Type
- boolean
- Description
Enable demo mode. Default:
false
- Name
DEMO_ALLOW_RESET- Type
- boolean
- Description
Override the
demo-host check. Default:false
- Name
DEMO_PRODUCT_URL- Type
- string
- Description
Link target in the banner.
Project Structure
Application Core
app/Models/— User, Product, Role, Permission, Activity, Email, Sms.app/Policies/— AbstractPolicy and per-model policies, including ProductPolicy.app/Observers/— UserObserver, ProductObserver.app/Listeners/— AddUserFreeTrialOnUserCreation, StripeEventListener, FilamentEmailLogger.app/Traits/— HasSubscriptionAttributes, ImplementSubscriptionFunctions.app/Support/— Demo and DemoSchedule.
HTTP Layer
app/Http/Controllers/— SubscriptionController.app/Http/Middleware/— Subscribed, Customer.routes/web.php— Redirect from/and thesubscription.*routes.
Filament Admin Panel
app/Filament/Resources/— Products, Users, Roles, Activities, Emails, Sms.app/Filament/Pages/— BillingInformation, HealthCheckResults, ViewLog.app/Filament/Auth/— EditProfile.resources/views/filament/pages/— Billing page view with plan cards.
Database & Config
database/— Migrations, factories (User, Email, Product), and seeders.config/— Includescashier.php,permission.php,activitylog.php,backup.php,health.php,sentry.php, anddemo.php.
Tests, CI & Deploy
tests/Feature/Subscription/andtests/Feature/Demo/— Pest tests..github/workflows/—lint.ymlandtests.yml.Envoy.blade.php— Deployment tasks.
Deployment
Set APP_ENV=production and APP_DEBUG=false, use live Stripe keys only in production, change or remove the seeded Super Admin account, and never enable DEMO_MODE on a production database.
Using Laravel Envoy
Set DEPLOY_USER, DEPLOY_HOST, DEPLOY_REPOSITORY, and DEPLOY_APP_DIR (and optionally DEPLOY_BRANCH, default main), then run:
envoy run deploy
The deploy story runs pull-repository (git fetch and git pull), run-composer (composer install, composer dump-autoload, php artisan migrate --force, php artisan optimize:clear), and run-node (npm install and npm run build) on the target server.
Post-Deployment Steps
- Stripe webhook — Create an endpoint in the Stripe Dashboard pointing to
https://yourdomain.com/stripe/webhookand setSTRIPE_WEBHOOK_SECRET. - Queue worker — Run
php artisan queue:workunder a process manager. Webhook events are processed by a queued listener. - Scheduler — Add the Laravel scheduler to cron:
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
- Web server — Point the document root to
public/and makestorage/andbootstrap/cache/writable. - Products — Create plans in the Products resource (or run the product seeder) so Stripe products and prices exist before users register.
- Verify — Run
php artisan health:checkand open Health Check Results.