PLG Readiness Audit: Saasfly — 4/10
We audited this repo for product-led growth readiness using the open-source PLG Skills framework. Here's what we found across 6 categories.
Overall: 4/10 — Clean monorepo architecture with full Stripe checkout and PostHog. But the critical flaw: feature limits are defined in the UI and database, then not enforced in code. The gating logic is literally commented out.
| Category |
Score |
Finding |
| Signup flow |
7/10 |
Magic link + GitHub OAuth, multi-language (EN/JP/KO/ZH) |
| Feature gating |
2/10 |
Plan limits defined but NOT enforced |
| Trial optimization |
0/10 |
No trial system |
| Product analytics |
3/10 |
PostHog integrated, pageviews only |
| Self-serve motion |
8/10 |
Full Stripe checkout + billing portal |
| Paywall/upgrade CRO |
4/10 |
Clean pricing with annual/monthly toggle |
Critical: feature limits exist in UI but not in code
The pricing data defines clear limits per plan:
// apps/nextjs/src/config/price/price-data.ts
{ id: "starter", benefits: ["Up to 1 cluster per month"] },
{ id: "pro", benefits: ["Up to 3 clusters per month"] },
{ id: "business", benefits: ["Up to 10 clusters per month"] },
The database has a SubscriptionPlan enum:
// packages/db/prisma/schema.prisma
enum SubscriptionPlan { FREE PRO BUSINESS }
model Customer { plan SubscriptionPlan? }
But the cluster creation code ignores all of this:
// packages/api/src/router/k8s.ts
createCluster: protectedProcedure
.input(k8sClusterCreateSchema)
.mutation(async ({ ctx, input }) => {
// Creates cluster WITHOUT checking plan limits
const newCluster = await db
.insertInto("K8sClusterConfig")
.values({
name: input.name,
plan: SubscriptionPlan.FREE, // Always FREE, no limit check
authUserId: userId,
})
.returning("id")
.executeTakeFirst();
return { id: newCluster.id, success: true };
}),
And the UI upgrade toast is commented out:
// apps/nextjs/src/components/k8s/cluster-create-button.tsx
// COMMENTED OUT:
// if (response.status === 402) {
// return toast({
// title: "Limit of 1 cluster reached.",
// description: "Please upgrade to the PROD plan.",
// variant: "destructive",
// });
// }
A FREE user can create unlimited clusters. The pricing page promises limits that don't exist.
Self-serve billing: solid
The Stripe integration is well-implemented with smart routing — existing subscribers go to billing portal, new users go to checkout:
// packages/api/src/router/stripe.ts
if (customer && customer.plan !== "FREE") {
// Existing subscriber → billing portal
const session = await stripe.billingPortal.sessions.create({ ... });
} else {
// New subscriber → checkout
const session = await stripe.checkout.sessions.create({ mode: "subscription", ... });
}
Webhooks properly update the database on checkout.session.completed and invoice.payment_succeeded.
Analytics: PostHog with pageviews only
PostHog is initialized with custom pageview tracking in apps/nextjs/src/config/providers.tsx. But searching the entire codebase for posthog.capture returns exactly 1 result — the pageview. No signup, checkout, cluster creation, or limit-reached events.
What you'd need to add
1. Enforce limits in the API (critical). The plan and limits are defined — just add the check:
createCluster: protectedProcedure.mutation(async ({ ctx }) => {
const customer = await getCustomer(ctx.userId);
const clusterCount = await countClusters(ctx.userId);
const limits = { FREE: 1, PRO: 3, BUSINESS: 10 };
if (clusterCount >= limits[customer.plan || 'FREE']) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Cluster limit reached. Upgrade to create more.',
});
}
// ... create cluster
});
2. Uncomment the upgrade toast. The component exists. Uncomment it and wire up the error handling.
3. Add PostHog custom events:
posthog.capture('signup_completed', { method: 'magic_link' });
posthog.capture('cluster_created', { plan: customer.plan, count: clusterCount });
posthog.capture('limit_reached', { feature: 'clusters', plan: customer.plan });
posthog.capture('plan_upgraded', { from: 'FREE', to: 'PRO' });
4. Add trial support. Stripe integration is solid — add subscription_data: { trial_period_days: 14 } to the checkout session.
Full analysis with monorepo architecture review: Saasfly PLG Audit
Audit methodology: PLG Skills (open source). You can also run uvx skene-growth analyze . (skene-growth) to scan any codebase for PLG gaps.
PLG Readiness Audit: Saasfly — 4/10
We audited this repo for product-led growth readiness using the open-source PLG Skills framework. Here's what we found across 6 categories.
Overall: 4/10 — Clean monorepo architecture with full Stripe checkout and PostHog. But the critical flaw: feature limits are defined in the UI and database, then not enforced in code. The gating logic is literally commented out.
Critical: feature limits exist in UI but not in code
The pricing data defines clear limits per plan:
The database has a
SubscriptionPlanenum:But the cluster creation code ignores all of this:
And the UI upgrade toast is commented out:
A FREE user can create unlimited clusters. The pricing page promises limits that don't exist.
Self-serve billing: solid
The Stripe integration is well-implemented with smart routing — existing subscribers go to billing portal, new users go to checkout:
Webhooks properly update the database on
checkout.session.completedandinvoice.payment_succeeded.Analytics: PostHog with pageviews only
PostHog is initialized with custom pageview tracking in
apps/nextjs/src/config/providers.tsx. But searching the entire codebase forposthog.capturereturns exactly 1 result — the pageview. No signup, checkout, cluster creation, or limit-reached events.What you'd need to add
1. Enforce limits in the API (critical). The plan and limits are defined — just add the check:
2. Uncomment the upgrade toast. The component exists. Uncomment it and wire up the error handling.
3. Add PostHog custom events:
4. Add trial support. Stripe integration is solid — add
subscription_data: { trial_period_days: 14 }to the checkout session.Full analysis with monorepo architecture review: Saasfly PLG Audit
Audit methodology: PLG Skills (open source). You can also run
uvx skene-growth analyze .(skene-growth) to scan any codebase for PLG gaps.