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.