Skip to content

Monetization foundations

The plumbing for differentiating "free" vs "pro" features. No payment flow is active in v0.15.x — every feature is enabled, every quota is generous. This document describes the system so you know what's there and how to extend it when the time comes.


Where it lives

src/config/plans.ts — single source of truth.


The plans

ts
type PlanId = 'free' | 'pro'
  • Free (default for everyone in v0.15.x) — every feature enabled, generous quotas.
  • Pro (forward-looking placeholder) — same features today; intended to host higher quotas + future "pro-only" features.

getEffectivePlan() returns the active plan — currently always 'free'. When a payment integration ships, this is the single function to update.


Feature flags

41 flags covering every shipped capability. Sample:

ts
'tool.pencil':         { enabled: true, plan: 'free' },
'gamedev.autotile-47': { enabled: true, plan: 'free' },
'export.upscale-xbr':  { enabled: true, plan: 'free' },
// ... etc

Read via:

ts
import { useFeature } from '@/config/plans'

const { enabled, requiresUpgrade } = useFeature('gamedev.autotile-47')
if (!enabled) return <UpgradePrompt />

requiresUpgrade is true when the user's plan is below the feature's required plan. Always false in v0.15.x because the effective plan check passes for everything.


Quotas

Numeric limits that scale with plan tier:

QuotaFreePro
maxCanvasSize512 px4096 px
maxFrames2004096
maxLayersPerFrame32256
maxBackupsPerProject864
maxCustomBrushes64512
maxColorCycles32256
maxFrameTags64512
maxSlices641024
maxReferenceImages16128

Read via:

ts
import { getQuota } from '@/config/plans'

const max = getQuota('maxFrames')

The first call site already wired through is MAX_BACKUPS_PER_PROJECT in src/features/persistence/backups.ts — when more numeric limits get added across the codebase, they should all flow through getQuota() so plan upgrades are a single-config change.


Adding a feature flag

  1. Add the entry to FEATURE_FLAGS in plans.ts.
  2. At the call site where the feature is consumed, gate it:
    ts
    const { enabled, requiresUpgrade } = useFeature('my.new-feature')
    if (!enabled) {
      // Show disabled state or upgrade prompt
    }

Adding a quota

  1. Add the new field to the QUOTAS shape (both free and pro entries).
  2. Replace any hard-coded limit with getQuota('my-new-quota').

What's intentionally not here

  • Payment integration. This file knows nothing about Stripe, Paddle, or LemonSqueezy. When that ships, it lives in a separate module that calls _setEffectivePlanForDev (renamed) based on the user's verified subscription state.
  • UI for upgrade prompts. No Pro buttons, no "Upgrade to unlock" tooltips. Adding them is a UI-only change once the flag system is in active use.
  • Feature-flag panel for beta testers. The 0.7.1 polish round considered this; deferred until there's a meaningful difference between free and pro.

  • Project backups — uses getQuota('maxBackupsPerProject')
  • Preferences — separate concept: per-user editor settings, not per-plan capabilities

Motestack is a personal hobby project. The editor and these docs ship under no warranty — back up your `.mstack` files.