The BoxyHQ SaaS Starter Kit is an enterprise-focused open-source boilerplate with 4,700+ GitHub stars. It stands out for its SAML SSO, SCIM directory sync, and audit log features — capabilities typically found in expensive enterprise software.
We ran it through a comprehensive PLG skills audit — analyzing signup flow CRO, feature gating, trial optimization, product analytics, self-serve motion, and paywall design.
Overall PLG score: 4.2/10 — Best-in-class enterprise features, working Stripe checkout, and Mixpanel analytics. But no plan-based feature gating or trial optimization.
The full audit
| Category | Score | Key finding |
|---|---|---|
| Signup flow | 7/10 | 4 fields, GitHub/Google OAuth, magic link, reCAPTCHA |
| Feature gating | 3/10 | Strong RBAC, but no plan-based tier gating |
| Trial optimization | 1/10 | Stripe supports trialing status, but not implemented |
| Product analytics | 4/10 | Mixpanel integrated with 10+ events tracked |
| Self-serve motion | 7/10 | Full Stripe checkout and billing portal |
| Paywall/upgrade CRO | 3/10 | Billing page exists, but no in-app upgrade prompts |
1. Signup flow analysis
What's implemented
The signup form collects 4 fields with Yup validation:
// components/auth/Join.tsx
const JoinUserSchema = Yup.object().shape({
name: Yup.string().required().max(maxLengthPolicies.name),
email: Yup.string().required().email().max(maxLengthPolicies.email),
password: Yup.string()
.required()
.min(passwordPolicies.minLength)
.max(maxLengthPolicies.password),
team: Yup.string().required().min(3).max(maxLengthPolicies.team),
});
Form fields: 4 required (name, email, password, team name)
Authentication options:
- Email + password
- Magic link
- GitHub OAuth
- Google OAuth
- SAML SSO (enterprise)
Unique features:
Work email enforcement for B2B focus:
// pages/api/auth/join.ts
if (!isEmailAllowed(email)) {
throw new ApiError(400,
`We currently only accept work email addresses for sign-up...`
);
}
Google reCAPTCHA v3 integration to prevent spam signups.
Signup event tracking:
// pages/api/auth/join.ts line 134
recordMetric('user.signup');
What's missing
- No progressive profiling (all 4 fields required upfront)
- No social login value extraction (pre-filling name from OAuth)
- No signup page view tracking
- No form abandonment analytics
2. Feature gating audit
What's implemented
Strong role-based access control with three roles:
// lib/permissions.ts
export const permissions: RolePermissions = {
OWNER: [
{ resource: 'team', actions: '*' },
{ resource: 'team_member', actions: '*' },
{ resource: 'team_payments', actions: '*' },
{ resource: 'team_sso', actions: '*' },
{ resource: 'team_dsync', actions: '*' },
{ resource: 'team_audit_log', actions: '*' },
],
ADMIN: [
{ resource: 'team', actions: '*' },
{ resource: 'team_member', actions: '*' },
// ... most permissions except some payment actions
],
MEMBER: [
{ resource: 'team', actions: ['read', 'leave'] },
],
};
Permission hook for UI-level checks:
// hooks/useCanAccess.ts usage example
{canAccess('team_payments', ['read']) && (
<BillingSection />
)}
Environment-based feature flags:
// lib/env.ts
teamFeatures: {
sso: process.env.FEATURE_TEAM_SSO !== 'false',
dsync: process.env.FEATURE_TEAM_DSYNC !== 'false',
webhook: process.env.FEATURE_TEAM_WEBHOOK !== 'false',
auditLog: process.env.FEATURE_TEAM_AUDIT_LOG !== 'false',
payments: Boolean(process.env.STRIPE_SECRET_KEY),
},
What's missing
No plan-based feature gating. Premium features are hidden by role, not by subscription tier.
Missing components:
- No subscription tier checks in code
- No usage limits (API calls, storage, seats)
- No upgrade prompts when limits are reached
- No "3 of 5 seats used" messaging
- No freemium model
3. Trial optimization
What's implemented
Stripe subscription status includes trialing:
// pages/api/webhooks/stripe.ts comment
// type Stripe.Subscription.Status = "active" | "canceled" |
// "incomplete" | "past_due" | "paused" | "trialing" | "unpaid"
But that's just a comment — no actual trial logic.
What's missing
Trials are not implemented:
- No
trial_period,trial_ends_atfields in database - No trial-based feature access
- No trial reminder emails
- No trial countdown UI
- No trial-to-paid conversion prompts
- No trial extension functionality
4. Product analytics
What's implemented
Mixpanel integration with automatic pageviews:
// pages/_app.tsx
useEffect(() => {
if (env.mixpanel.token) {
mixpanel.init(env.mixpanel.token, {
debug: true,
ignore_dnt: true,
track_pageview: true,
});
}
}, []);
Server-side event recording with OpenTelemetry:
// lib/metrics.ts
export const recordMetric = (metric: AppEvent) => {
incrementCounter({
meter: packageInfo.name,
name: `${prefix}.${metric}`,
});
};
Events tracked:
user.signupuser.password.updateduser.password.requestuser.password.resetteam.fetchedteam.createdinvitation.createdinvitation.fetchedinvitation.removedmember.created
What's missing
- No feature adoption tracking (SSO setup, webhook creation)
- No user identification with custom properties
- No checkout events (
checkout_started,payment_success) - No funnel tracking
- No error tracking for signup/payment failures
5. Self-serve motion
What's implemented
Full Stripe checkout with session creation:
// pages/api/teams/[slug]/payments/create-checkout-session.ts
const checkoutSession = await stripe.checkout.sessions.create({
customer,
mode: 'subscription',
line_items: [{ price, quantity }],
success_url: `${env.appUrl}/teams/${team.slug}/billing`,
cancel_url: `${env.appUrl}/teams/${team.slug}/billing`,
});
Stripe billing portal for self-serve management:
// components/billing/LinkToPortal.tsx
const openStripePortal = async () => {
const response = await fetch(
`/api/teams/${team.slug}/payments/create-portal-link`,
{ method: 'POST' }
);
const result = await response.json();
window.open(result.data.url, '_blank');
};
Webhook handling for subscription lifecycle:
// Handles these events:
'customer.subscription.created'
'customer.subscription.updated'
'customer.subscription.deleted'
What's missing
- No instant signup flow (requires team creation first)
- No one-click upgrade buttons in-app
- No usage-based pricing support
- No payment method management (only via Stripe portal)
- No invoice history page
- No dunning/failed payment recovery
6. Paywall and upgrade CRO
What's implemented
Billing page with plan display:
// components/billing/ProductPricing.tsx
{plans.map((plan) => (
<div key={plan.id}>
<h3>{plan.name}</h3>
<p>{plan.description}</p>
{hasActiveSubscription(price) ? (
<Button disabled>{t('current')}</Button>
) : (
<PaymentButton price={price} />
)}
<ul>
{plan.features.map((feature) => (
<li><CheckIcon /> {feature}</li>
))}
</ul>
</div>
))}
Current plan indicator shows which plan is active.
What's missing
No in-app paywalls:
- No "upgrade to use X feature" prompts
- No usage limit warnings ("You've used 100/1000 API calls")
- No feature comparison table
- No discount/promo code UI
- No trial CTA
- No urgency messaging (limited-time offers)
- Landing page pricing uses "Lorem Ipsum" placeholder text
Enterprise features
Where BoxyHQ excels is enterprise readiness:
| Feature | Status |
|---|---|
| SAML SSO | Full implementation via Jackson |
| SCIM directory sync | User provisioning from IdP |
| Audit logs | Activity tracking with Retraced |
| Webhooks | Svix integration |
| API keys | Team-level API key management |
These features make it ideal for selling to enterprises, but they're not gated by subscription tier — they're either on or off via environment variables.
What you need to add
Priority 1: Plan-based gating
Connect subscription tiers to feature access:
// Example implementation needed
function canAccessFeature(team, feature) {
const plan = team.subscription?.plan;
const planFeatures = {
free: ['basic_features'],
pro: ['basic_features', 'sso', 'audit_logs'],
enterprise: ['basic_features', 'sso', 'audit_logs', 'scim'],
};
return planFeatures[plan]?.includes(feature);
}
Priority 2: In-app upgrade prompts
Add contextual upgrade messaging:
{!canAccessFeature(team, 'sso') && (
<UpgradePrompt
feature="SAML SSO"
plan="Pro"
message="Enable single sign-on for your team"
/>
)}
Priority 3: Trial system
Add trial period tracking:
ALTER TABLE subscriptions ADD COLUMN trial_ends_at TIMESTAMP;
Then build trial emails and countdown UI.
First steps to PLG
BoxyHQ has the billing — now add the activation layer:
Week 1: Connect plans to features
The RBAC system is strong, but it's role-based not plan-based. Add plan checks:
// lib/subscription.ts
export function getTeamPlan(team) {
const sub = team.subscriptions.find(s => s.status === 'active');
return sub?.plan || 'free';
}
export function canAccessFeature(team, feature) {
const plan = getTeamPlan(team);
const planFeatures = {
free: ['basic'],
pro: ['basic', 'sso', 'audit_logs'],
enterprise: ['basic', 'sso', 'audit_logs', 'scim'],
};
return planFeatures[plan]?.includes(feature);
}
Week 2: Expand Mixpanel tracking
Mixpanel is integrated but underutilized. Add events:
// In your checkout flow
mixpanel.track('checkout_started', { plan: 'pro', team_size: team.members.length });
mixpanel.track('checkout_completed', { plan: 'pro', mrr: 49 });
// Feature usage
mixpanel.track('feature_used', { feature: 'sso', plan: 'pro' });
mixpanel.track('upgrade_prompt_shown', { feature: 'audit_logs' });
Week 3: Add trial with enterprise features
For B2B SaaS, let trials include premium features:
// When creating checkout
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
subscription_data: {
trial_period_days: 14,
metadata: { trial_features: 'sso,audit_logs,scim' }
},
// ...
});
Week 4: Build upgrade prompts
Show contextual upgrade CTAs when users try restricted features:
// When SSO is accessed but not available
{!canAccessFeature(team, 'sso') && (
<Card>
<h3>Enable SAML SSO</h3>
<p>Upgrade to Pro for enterprise single sign-on</p>
<Button onClick={() => router.push('/billing')}>
Upgrade to Pro
</Button>
</Card>
)}
Enterprise PLG metrics
- Team signup → first SSO connection (activation for enterprise)
- Trial → paid conversion by feature usage
- Seat expansion rate (teams adding members)
- Feature adoption: which enterprise features drive retention?
When to use this template
Good fit:
- You're selling to enterprises that need SSO and SCIM
- You want working Stripe checkout out of the box
- You need audit logs for compliance
Not ideal:
- You want a freemium model with plan-based gating
- You need trial optimization features
- You're targeting SMB with simpler requirements
Conclusion
The BoxyHQ SaaS Starter Kit scores 4.2/10 for PLG readiness. It's the most enterprise-ready open-source boilerplate we've reviewed, with SAML SSO, SCIM, and audit logs that typically cost thousands in enterprise software.
The self-serve motion is solid (working Stripe checkout and billing portal), and Mixpanel analytics provide basic tracking. But the absence of plan-based feature gating means you can't create a true freemium or tiered pricing model without significant custom development.
Best for: Teams building B2B SaaS for enterprise customers who need compliance features but will handle pricing tiers themselves.
Frequently asked questions
Is BoxyHQ SaaS Starter Kit really free?
Yes, it's fully open source under Apache 2.0 license. All features including SAML SSO, SCIM, and Stripe integration are included. There's no paid tier.
How does it compare to other enterprise SaaS starters?
It has the strongest enterprise feature set (SSO, SCIM, audit logs) of any open-source option. However, it lacks PLG features like trial optimization and plan-based gating that templates like Launch MVP include.
Can I use it for a freemium product?
You'll need to add plan-based feature gating yourself. The template has role-based access (Owner/Admin/Member) but doesn't restrict features by subscription tier.
Does it support multi-tenancy?
Yes, it has full team/workspace support with member invitations, role management, and team-scoped resources.
This analysis was performed using PLG Skills, an open-source framework for product-led growth audits. Skills used: signup-flow-cro, feature-gating, trial-optimization, product-analytics, self-serve-motion, paywall-upgrade-cro.