SaaS Platform Development
SaaS is not just "a website with login and subscription." It's a set of infrastructure solutions, each with non-trivial implementation: multi-tenancy, billing, onboarding, feature flags, rate limiting, audit logs. Errors in early architecture become very expensive during scaling.
Multi-tenancy: Shared Schema vs Separate Databases
This is the first architectural decision that can't easily be changed after launch — which is exactly why it must be made consciously.
Shared schema (tenant_id on each table) — most common approach. All tenants live in one database, each record marked with tenant_id. Simpler to maintain, migrations applied once, less operational complexity.
Problem: data leak between tenants is catastrophe. One forgotten WHERE tenant_id = ? in query — user sees someone else's data. In Laravel solved with Global Scope on all models:
protected static function booted(): void
{
static::addGlobalScope('tenant', function (Builder $builder) {
$builder->where('tenant_id', TenantContext::current()->id);
});
}
Plus — mandatory tests checking: request from tenant A can't return tenant B's data under any condition.
Separate databases — each tenant gets its own schema or database. Full data isolation, easier GDPR and ISO 27001 compliance (client data physically separated). Minus: migrations must apply to each tenant sequentially, at 1000+ tenants this is hours-long operation.
Hybrid approach — shared database for most tenants, dedicated database for enterprise customers paying for isolation. This is what mature SaaS products do.
Our standard choice for new SaaS — shared schema with strict tenant isolation via global scopes and PostgreSQL row-level security. RLS adds second protection layer at database level even if application errs:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::uuid);
Billing: Subscriptions, Trials, Upgrades
Billing is one of the most underestimated complexity blocks. Typical situations to handle:
- Upgrade mid-billing period (prorata)
- Downgrade with immediate or deferred effect
- Trial expired, user didn't add card — what happens to data?
- Payment failed — grace period, dunning emails, access blocking
- Refund on subscription cancellation
Stripe Billing covers most scenarios out-of-box — this is what we use by default. Webhook events: customer.subscription.updated, invoice.payment_failed, invoice.payment_succeeded, customer.subscription.deleted. Each handled idempotently.
Idempotency key on payment creation — mandatory. Without it, client-side retry logic can cause double charge.
For CIS markets — YuKassa or Tinkoff integration for subscriptions via recurring payments. Less convenient API than Stripe, but covers local legal requirements.
Onboarding: From Registration to "Aha Moment"
Technically, onboarding is a wizard with persistent state, can't be accidentally skipped and can't be done twice.
Typical implementation: onboarding_steps table with status of each step for each user. Middleware checks if onboarding complete, redirects to incomplete step. After completion — flag in user settings, middleware stops triggering.
Important nuance: onboarding should show real product progress, not abstract steps. "Create your first project" instead of "Complete account setup (step 3 of 7)."
Onboarding email sequence — separate module. Drip campaign via Mailgun/SendGrid with conditional logic: if user took key action — next email doesn't send or changes. Tools: Customer.io, Loops, or custom task queue with delayed jobs.
Feature Flags and Access Management
SaaS with multiple pricing plans requires granular feature management: Free plan sees A, Pro sees B and C, Enterprise sees everything plus D.
Don't do this via if ($user->plan === 'pro') scattered across code. Becomes unmaintainable fast.
Right approach — feature flag system. For Laravel: Gate + Policy checking via subscription plan, or separate features table with plan relationships. For frontend — context with flags loaded at app init.
Open source solution: Growthbook or Unleash — allows flag management via UI, A/B testing, gradual rollout.
Rate Limiting and API Protection
SaaS platform with public API must have rate limiting. Without it, one aggressive client knocks everyone else down.
In Laravel — RateLimiter facade or throttle middleware. For complex scenarios (different limits per plan, method-based limits) — Redis with sliding window counter:
Limits per plan: Free — 100 requests/hour, Pro — 1,000/hour, Enterprise — 10,000/hour. Headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset in every response — this is standard, clients expect it.
Monitoring and Audit Logs
Audit log is mandatory component for SaaS where multiple users work with shared data. "Who deleted this project?", "When did billing settings change?" — impossible to answer without log.
Table audit_logs: user_id, tenant_id, action, subject_type, subject_id, old_values, new_values, ip_address, created_at. Indexes by (tenant_id, created_at) and (subject_type, subject_id).
In Laravel — Observers on key models or owen-it/laravel-auditing package.
App monitoring: Sentry for exception tracking, Grafana + Prometheus or Datadog for metrics, Loki for logs. Alerts on: error rate > X%, response time p95 > 2s, failed payments spike.
Architecture for Scaling
MVP can launch on monolith — that's normal. Premature microservices architecture for early-stage SaaS — overengineering that slows development without real benefit.
Monolith → Modular monolith → Microservices — correct trajectory as load grows.
Horizontal scaling of Laravel monolith: Laravel Octane (Swoole/RoadRunner) removes bootstrap overhead per request, horizontal scaling through multiple instances behind Nginx/HAProxy, Redis for caches and queues, PostgreSQL with read replicas for reads.
Timeline Guidelines
| Stage | Timeline |
|---|---|
| MVP (core features + auth + billing) | 12–16 weeks |
| Full product with admin panel | 20–28 weeks |
| Enterprise SaaS with multi-tenancy + audit | 28–40 weeks |
Cost depends on number of roles, billing logic complexity, integration requirements, and load level. Calculated after detailed discovery.







