The CMSaasStarter is a SvelteKit + Supabase SaaS template with marketing pages, blog, subscriptions, auth, and a user dashboard. It's one of the more complete SvelteKit options available.
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: 3.7/10 — Has the best Stripe integration of any SvelteKit starter we've reviewed (checkout + billing portal), but zero analytics and no feature gating.
The full audit
| Category | Score | Key finding |
|---|---|---|
| Signup flow | 7/10 | Supabase Auth UI with GitHub OAuth |
| Feature gating | 2/10 | Static pricing table, no runtime checks |
| Trial optimization | 1/10 | Stripe trial status awareness only |
| Product analytics | 0/10 | Documentation only, nothing installed |
| Self-serve motion | 9/10 | Full Stripe checkout + billing portal |
| Paywall/upgrade CRO | 3/10 | Pricing page exists, no in-app prompts |
1. Signup flow analysis
What's implemented
Supabase Auth UI component with GitHub OAuth:
// src/routes/(marketing)/login/login_config.ts
export const oauthProviders = ["github"] as Provider[]
// src/routes/(marketing)/login/sign_up/+page.svelte
<Auth
supabaseClient={data.supabase}
view="sign_up"
redirectTo={`${data.url}/auth/callback`}
providers={oauthProviders}
socialLayout="horizontal"
/>
The Supabase Auth UI handles:
- Email + password signup
- Email verification
- Password reset
- OAuth callbacks
What's missing
- Only 1 OAuth provider (GitHub) — no Google, Microsoft
- No custom signup tracking
- No form validation messaging customization
- No onboarding flow after signup
2. Feature gating audit
What's implemented
Static pricing table with Free/Pro/Enterprise tiers:
// src/routes/(marketing)/pricing/pricing_plans.ts
{
id: "pro",
name: "Pro",
description: "A plan to test the purchase experience...",
price: "$5",
stripe_price_id: "price_1NkdZCHMjzZ8mGZnRSjUm4yA",
features: [
"Everything in Free",
"Support us with fake money",
"Test the purchase experience",
],
}
Feature comparison with limits:
{
name: "Feature 3",
freeIncluded: true,
freeString: "3", // Free tier: 3 uses
proIncluded: true,
proString: "Unlimited", // Pro tier: unlimited
}
What's missing
No runtime enforcement. The pricing table shows limits, but the code never checks them:
- No
hasFeature()orcanAccess()functions - No middleware that gates routes by plan
- No usage counting or limit enforcement
- No locked UI components
- No "upgrade to access" redirects
All features are available to everyone regardless of plan.
3. Trial optimization
What's implemented
Stripe trial status awareness in subscription handling:
// src/routes/(admin)/account/subscription_helpers.server.ts
const primaryStripeSubscription = stripeSubscriptions.data.find((x) => {
return (
x.status === "active" ||
x.status === "trialing" || // Trial support
x.status === "past_due"
)
})
What's missing
- No trial period initialization
- No
trial_daysparameter in checkout - No trial expiration warnings in UI
- No trial reminder emails
- No "upgrade before trial ends" prompts
- No trial-to-paid conversion tracking
4. Product analytics
What's implemented
Documentation only. The analytics_docs.md file explains how to add PostHog or Google Analytics:
### PostHog
- Install PostHog JS Library: `npm install posthog-js`
- Set up in `src/routes/+layout.svelte`
What's missing
Nothing is actually installed:
- No analytics package in package.json
- No tracking calls in code
- No user identification
- No conversion tracking
- No funnel analysis
You cannot measure signup-to-paid conversion without adding analytics yourself.
5. Self-serve motion
What's implemented
This is where the template shines. Full Stripe checkout:
// src/routes/(admin)/account/subscribe/[slug]/+page.server.ts
const stripeSession = await stripe.checkout.sessions.create({
line_items: [{ price: params.slug, quantity: 1 }],
customer: customerId,
mode: "subscription",
success_url: `${url.origin}/account`,
cancel_url: `${url.origin}/account/billing`,
})
Automatic Stripe customer creation:
// Uses getOrCreateCustomerId() helper
// Creates Stripe customer on first checkout
Billing portal for self-serve management:
// src/routes/(admin)/account/(menu)/billing/manage/+page.server.ts
const portalSession = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${url.origin}/account/billing`,
})
What users can do via portal:
- Update payment methods
- View invoices
- Upgrade/downgrade plans
- Cancel subscription
What's missing
- No custom checkout page (redirects to Stripe)
- No dunning/failed payment recovery
- No usage-based pricing
6. Paywall and upgrade CRO
What's implemented
Pricing module with current plan indicator:
// src/routes/(marketing)/pricing/pricing_module.svelte
{#if plan.id === currentPlanId}
<div class="btn btn-outline btn-success">Current Plan</div>
{:else}
<a href={"/account/subscribe/" + plan?.stripe_price_id}>
<button class="btn btn-primary">{callToAction}</button>
</a>
{/if}
Feature comparison table showing what's included in each tier.
What's missing
- No upgrade prompts inside the app
- No usage limit warnings
- No "upgrade to unlock" overlays
- No annual/monthly pricing toggle
- No testimonials or social proof
- No FAQ on pricing page
Database schema
Uses Supabase with minimal schema:
| Table | Purpose |
|---|---|
profiles | User metadata |
stripe_customers | Stripe customer ID mapping |
contact_requests | Contact form submissions |
Missing tables:
- No trial tracking fields
- No feature usage metrics
- No subscription metadata (stored in Stripe only)
What you need to add
Priority 1: Analytics
Install PostHog and track key events:
posthog.capture('signup_completed');
posthog.capture('pricing_page_viewed');
posthog.capture('checkout_started', { plan: 'pro' });
posthog.capture('checkout_completed', { plan: 'pro' });
Priority 2: Feature gating
Create a subscription check helper:
async function getSubscription(userId: string) {
const customer = await getStripeCustomer(userId);
const subscriptions = await stripe.subscriptions.list({
customer: customer.id,
status: 'active'
});
return subscriptions.data[0];
}
function hasFeature(subscription, feature) {
const plan = getPlanFromPriceId(subscription?.price_id);
return PLAN_FEATURES[plan]?.includes(feature);
}
Priority 3: Trial system
Add trial period to checkout:
const stripeSession = await stripe.checkout.sessions.create({
// ... existing config
subscription_data: {
trial_period_days: 14,
},
});
First steps to PLG
CMSaasStarter has the best billing of any SvelteKit template. Add the activation layer:
Week 1: Add analytics
The docs mention PostHog but nothing is installed. Fix that:
npm install posthog-js
<!-- src/routes/+layout.svelte -->
<script>
import posthog from 'posthog-js';
import { browser } from '$app/environment';
if (browser) {
posthog.init('your_key', { api_host: 'https://app.posthog.com' });
}
</script>
Track key events:
signup_completedpricing_page_viewedcheckout_started(with plan name)subscription_activatedfeature_used(for adoption tracking)
Week 2: Add subscription checking
Create a helper to check plans:
// src/lib/subscription.ts
import { stripe } from '$lib/stripe';
export async function getSubscription(customerId: string) {
const subscriptions = await stripe.subscriptions.list({
customer: customerId,
status: 'active',
});
return subscriptions.data[0];
}
export function getPlanFromPriceId(priceId: string) {
const plans = {
'price_free': 'free',
'price_xxx': 'pro',
'price_yyy': 'enterprise',
};
return plans[priceId] || 'free';
}
Week 3: Gate features
Add plan-based access in your routes:
<!-- src/routes/(admin)/pro-feature/+page.svelte -->
<script>
export let data;
$: isPro = data.plan === 'pro' || data.plan === 'enterprise';
</script>
{#if isPro}
<ProFeatureContent />
{:else}
<UpgradePrompt feature="Pro Feature" />
{/if}
Week 4: Add trial with countdown
- Modify checkout to add 14-day trial
- Create a trial banner component:
<!-- src/lib/components/TrialBanner.svelte -->
<script>
export let trialEndsAt;
$: daysLeft = Math.ceil((new Date(trialEndsAt) - new Date()) / (1000 * 60 * 60 * 24));
</script>
{#if daysLeft > 0 && daysLeft <= 14}
<div class="alert alert-warning">
<span>Your trial ends in {daysLeft} days.</span>
<a href="/account/billing">Upgrade now</a>
</div>
{/if}
Key metrics to track
Once analytics is added, measure:
- Signup → first value action (define your "aha moment")
- Free → trial conversion rate
- Trial → paid conversion rate
- Time to upgrade (how long do users stay on free?)
When to use this template
Good fit:
- You want SvelteKit + Supabase
- You need working Stripe checkout and billing portal
- You prefer Supabase Auth over custom auth
Not ideal:
- You need feature gating out of the box
- You want analytics pre-configured
- You need trial optimization
Conclusion
The CMSaasStarter scores 3.7/10 for PLG readiness. It has the strongest self-serve billing of any SvelteKit template — full Stripe checkout with customer creation and billing portal access.
But it's missing the PLG fundamentals: no analytics to measure conversion, no feature gating to drive upgrades, and no trial system to reduce friction. The pricing table shows limits that aren't enforced anywhere in the code.
Best for: Teams who want solid Stripe billing with SvelteKit and will add analytics and feature gating themselves.
Frequently asked questions
How does this compare to JustShip?
CMSaasStarter has better Stripe integration — the webhooks actually update user state, and there's a billing portal. JustShip's Stripe webhooks are empty shells. But JustShip has PostHog integrated, while this template has no analytics.
Does it support multiple OAuth providers?
Only GitHub is configured by default. You can add Google, Microsoft, etc. through Supabase Auth settings, but you'll need to update the oauthProviders config.
Can I use it for a freemium product?
Not without significant work. The pricing page shows Free/Pro/Enterprise, but there's no code that restricts features based on plan. You'll need to build feature gating yourself.
What's the best SvelteKit starter for PLG?
None of them are particularly strong for PLG. CMSaasStarter has the best billing, but you'll need to add analytics and feature gating regardless of which template you choose.
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.