From saas-pro
Multi-tenant architecture, billing, subscription management, onboarding, RBAC. Use when building or reviewing SaaS platform features.
How this skill is triggered — by the user, by Claude, or both
Slash command
/saas-pro:saas-proThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build production SaaS platforms: multi-tenancy, subscription billing, user onboarding, RBAC, feature flags, and usage metering.
Build production SaaS platforms: multi-tenancy, subscription billing, user onboarding, RBAC, feature flags, and usage metering.
Use this when:
Use this ESPECIALLY when:
Don't skip when:
// Row-level security for tenant isolation
// Option A: Discriminator column (simpler, shared DB)
interface Project {
id: string
name: string
tenantId: string // Every row belongs to a tenant
// ...
}
// Global middleware enforces tenant isolation
async function requireTenant(req, res, next) {
const tenantId = req.headers['x-tenant-id']
if (!tenantId) return res.status(400).json({ error: 'X-Tenant-Id header required' })
req.tenantId = tenantId
// Verify user has access to this tenant
const membership = await db.tenantMembership.findUnique({
where: { userId_tenantId: { userId: req.user.id, tenantId } }
})
if (!membership) return res.status(403).json({ error: 'Not a member of this tenant' })
next()
}
// All queries scoped by tenantId
const projects = await db.projects.findMany({
where: { tenantId: req.tenantId, ownerId: req.user.id }
})
// Stripe integration
stripe.subscriptions.create({
customer: customer.id,
items: [{ price: priceId }],
trial_period_days: 14,
metadata: { tenantId: tenant.id },
})
// Webhook handler
app.post('/webhooks/stripe', async (req, res) => {
const event = stripe.webhooks.constructEvent(req.body, signature, webhookSecret)
switch (event.type) {
case 'customer.subscription.updated':
case 'customer.subscription.deleted':
await updateTenantSubscription(event.data.object)
break
case 'invoice.payment_succeeded':
await handlePaymentSuccess(event.data.object)
break
case 'invoice.payment_failed':
await handlePaymentFailure(event.data.object)
break
}
res.json({ received: true })
})
// DB-backed feature flags
interface FeatureFlag {
name: string
enabled: boolean
tenantOverrides: Record<string, boolean> // per-tenant overrides
userOverrides: Record<string, boolean> // per-user overrides
rolloutPercentage: number // gradual rollout
}
function isFeatureEnabled(flagName: string, req: Request): boolean {
const flag = await db.featureFlags.findUnique({ where: { name: flagName } })
if (!flag) return false
// Per-user override (highest priority)
if (flag.userOverrides?.[req.user.id] !== undefined) return flag.userOverrides[req.user.id]
// Per-tenant override
if (flag.tenantOverrides?.[req.tenantId] !== undefined) return flag.tenantOverrides[req.tenantId]
// Gradual rollout
if (flag.rolloutPercentage < 100) {
return hash(`${req.tenantId}:${flagName}`) % 100 < flag.rolloutPercentage
}
return flag.enabled
}
// Track API usage per tenant
async function trackUsage(tenantId: string, metric: string, amount: number = 1) {
const key = `usage:${metric}:${tenantId}:${formatDate(new Date(), 'yyyy-MM')}`
await redis.incrby(key, amount)
await redis.expire(key, 60 * 60 * 24 * 32) // Keep for 32 days
}
// Check limits
async function checkUsageLimit(tenantId: string, metric: string, limit: number): Promise<boolean> {
const current = await redis.get(`usage:${metric}:${tenantId}:${formatDate(new Date(), 'yyyy-MM')}`)
return parseInt(current || '0') < limit
}
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub haj1t/senior-dev-squad-skills --plugin saas-pro