Addon/Feature System Development Guide
**Version:** 1.0
Best use case
Addon/Feature System Development Guide is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
**Version:** 1.0
Teams using Addon/Feature System Development Guide should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/addon-system/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How Addon/Feature System Development Guide Compares
| Feature / Agent | Addon/Feature System Development Guide | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
**Version:** 1.0
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Addon/Feature System Development Guide
**Version:** 1.0
**Purpose:** Enforce consistent patterns when creating new features/addons with proper feature gates
## 🎯 Quick Reference
When adding a new feature/addon to the platform, you MUST:
- ✅ Define a unique FEATURE_CODE
- ✅ Create feature file in `src/lib/features/`
- ✅ Create custom hook (`useXyzFeature`)
- ✅ Create Feature Gates (User + Admin)
- ✅ Use theme system for upgrade prompts
- ✅ Register feature in database
## 📚 Architecture Overview
```
Feature System Flow:
1. Database (feature_definitions) → Feature Code
2. Studio Subscription/Addon → Active Features
3. FeatureProvider → Context with hasFeature(), canUse()
4. Feature Gates → Conditional Rendering
5. Components → Protected Features
```
## 🔧 Step-by-Step: Creating a New Addon
### Step 1: Define Feature Code
```typescript
// src/lib/features/my-feature.tsx
'use client'
export const MY_FEATURE_CODE = 'my_feature_name'
```
**Naming Convention:**
- Use snake_case: `chat_messaging`, `studio_blog`, `checkin_system`
- Be descriptive: `video_on_demand` not `vod`
- Must match database entry in `feature_definitions.code`
### Step 2: Create Custom Hook
```typescript
// src/lib/features/my-feature.tsx
import { useFeatures } from './feature-context'
export function useMyFeature() {
const { hasFeature, canUse, loading } = useFeatures()
return {
// Ist das Feature aktiviert?
isMyFeatureEnabled: hasFeature(MY_FEATURE_CODE),
// Kann Feature genutzt werden? (Aktiv + Subscription gültig)
canUseMyFeature: canUse(MY_FEATURE_CODE),
// Lädt noch?
loading: loading,
// Feature Code für andere Components
featureCode: MY_FEATURE_CODE
}
}
```
**What the hook returns:**
- `isMyFeatureEnabled`: Feature exists in studio's active features
- `canUseMyFeature`: Feature exists AND subscription is active
- `loading`: True während features geladen werden
- `featureCode`: Der Feature-Code für generic components
### Step 3: Create Feature Gates
#### A) Simple Feature Gate (für User/Frontend)
```typescript
// src/lib/features/my-feature.tsx
import React from 'react'
export function MyFeatureGate({ children }: { children: React.ReactNode }) {
const { canUseMyFeature, loading } = useMyFeature()
// Während Laden: nichts anzeigen
if (loading) return null
// Feature nicht aktiv: nichts anzeigen
if (!canUseMyFeature) return null
return <>{children}</>
}
```
#### B) Admin Feature Gate (mit Upgrade-Hinweis)
```typescript
// src/lib/features/my-feature.tsx
import { activeTheme } from '@/config/theme'
import { H3 } from '@/components/ui/Typography'
export function AdminMyFeatureGate({
children,
fallback
}: {
children: React.ReactNode
fallback?: React.ReactNode
}) {
const { isMyFeatureEnabled, loading } = useMyFeature()
// Während Laden: Render children (Page hat eigene Loading-States)
if (loading) {
return <>{children}</>
}
// Custom Fallback?
if (!isMyFeatureEnabled && fallback) {
return <>{fallback}</>
}
// Feature nicht aktiv: Upgrade-Hinweis
if (!isMyFeatureEnabled) {
return (
<div className="p-8 text-center">
<div className="max-w-md mx-auto">
<svg
className="w-16 h-16 text-[rgb(23,23,23)] mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
<H3 className="mb-2">
My Feature nicht aktiviert
</H3>
<p className="text-[rgb(23,23,23)] mb-4">
Dieses Feature ist in Ihrem aktuellen Tarif nicht enthalten.
</p>
<a
href="/admin/einstellungen/tarife"
className={\`inline-flex items-center px-4 py-2 bg-gradient-to-r \${activeTheme.gradient} text-white rounded-lg hover:opacity-90 transition-all\`}
>
Tarif upgraden
</a>
</div>
</div>
)
}
return <>{children}</>
}
```
### Step 4: Use in Components
#### Option A: With Custom Feature Gate
```typescript
// In your component
import { MyFeatureGate } from '@/lib/features/my-feature'
export default function MyPage() {
return (
<MyFeatureGate>
{/* This only renders if feature is active */}
<div>Feature Content</div>
</MyFeatureGate>
)
}
```
#### Option B: With Generic FeatureGate
```typescript
import { FeatureGate } from '@/components/features/FeatureGate'
export default function MyPage() {
return (
<FeatureGate feature="my_feature_name">
<div>Feature Content</div>
</FeatureGate>
)
}
```
#### Option C: Conditional Rendering with Hook
```typescript
import { useMyFeature } from '@/lib/features/my-feature'
export default function MyComponent() {
const { canUseMyFeature, loading } = useMyFeature()
if (loading) return <LoadingSpinner />
if (!canUseMyFeature) return null
return <div>Feature Content</div>
}
```
### Step 5: Stripe Product erstellen
**WICHTIG:** Jedes Addon braucht ein Stripe Product, damit bei Studio-Erstellung keine neuen Produkte erstellt werden!
#### A) Stripe Product anlegen
```javascript
// Via Node.js Script oder Stripe Dashboard
require('dotenv').config({ path: '.env.local' });
const Stripe = require('stripe');
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const product = await stripe.products.create({
name: 'Bookicorn My Feature Name', // Prefix "Bookicorn " für Konsistenz
metadata: {
feature_code: 'my_feature_name', // Muss mit DB code übereinstimmen!
type: 'addon'
}
});
console.log('Product ID:', product.id); // z.B. prod_TnXXXXXXXXXX
```
**Oder via Stripe Dashboard:**
1. Dashboard → Products → Add Product
2. Name: `Bookicorn [Feature Name]`
3. Metadata hinzufügen: `feature_code` = `my_feature_name`, `type` = `addon`
### Step 6: Database Setup
#### A) Register Feature Definition (mit Stripe Product ID!)
```sql
INSERT INTO feature_definitions (
code,
name,
description,
category,
addon_price_monthly,
addon_price_yearly,
is_active,
metadata
) VALUES (
'my_feature_name', -- Must match FEATURE_CODE
'My Feature Name',
'Description of what this feature does',
'content', -- Category: core, content, marketing, etc.
9.99, -- Monthly price (if sold as addon)
99.99, -- Yearly price
true,
'{"status": "available", "stripe_product_id": "prod_TnXXXXXXXXXX"}'::jsonb
-- ↑ WICHTIG: Stripe Product ID hier eintragen!
);
```
#### Alternative: Bestehendes Feature updaten
```sql
UPDATE feature_definitions
SET metadata = metadata || '{"stripe_product_id": "prod_TnXXXXXXXXXX"}'::jsonb
WHERE code = 'my_feature_name';
```
#### B) Add to Subscription Plan (Optional)
```sql
-- Include feature in a plan
UPDATE subscription_plans
SET included_features = included_features || ARRAY['my_feature_name']
WHERE code = 'professional';
```
#### C) Or Create as Addon
```sql
-- Studio can buy as addon
INSERT INTO studio_feature_addons (
studio_id,
feature_id,
status,
billing_cycle,
price_override
) VALUES (
'studio-uuid',
(SELECT id FROM feature_definitions WHERE code = 'my_feature_name'),
'active',
'monthly',
NULL
);
```
## 📁 File Structure
```
src/
├── lib/
│ └── features/
│ ├── feature-context.tsx # Admin/Studio Feature Provider
│ ├── member-feature-context.tsx # Member Dashboard Feature Provider (NEW)
│ ├── my-feature.tsx # Your new feature
│ ├── chat-feature.tsx # Example: Chat (Admin)
│ ├── blog-feature.ts # Example: Blog
│ └── checkin-feature.tsx # Example: Check-in
├── components/
│ └── features/
│ └── FeatureGate.tsx # Generic Feature Gate
└── app/
└── admin/
└── my-feature/ # Admin pages for feature
└── page.tsx
```
## 👤 Member Dashboard Feature Gates
**WICHTIG:** Das Member Dashboard hat ein SEPARATES Feature System (`MemberFeatureContext`), weil:
- Ein Kunde kann bei MEHREREN Studios Mitglied sein
- Features werden über ALLE Studios aggregiert
- Feature ist aktiv wenn MINDESTENS EIN Studio es hat
### MemberFeatureContext vs FeatureContext
| Aspekt | FeatureContext (Admin) | MemberFeatureContext (Member) |
|--------|------------------------|-------------------------------|
| Scope | Einzelnes Studio | Alle Studios des Users |
| Provider | `FeatureProvider` | `MemberFeatureProvider` |
| Hook | `useFeatures()` | `useMemberFeatures()` |
| Logik | Studio hat Feature? | Irgendein Studio hat Feature? |
### Member Feature Hook erstellen
```typescript
// src/lib/features/member-feature-context.tsx enthält:
// 1. Feature Codes Definition
export const MEMBER_FEATURE_CODES = {
CHAT: 'chat_messaging',
CHECKIN: 'checkin_system',
// Neues Feature hier hinzufügen
MY_FEATURE: 'my_feature_code',
} as const
// 2. Convenience Hooks existieren bereits:
export function useMemberChatFeature() { ... }
export function useMemberCheckinFeature() { ... }
// 3. Neuen Convenience Hook hinzufügen:
export function useMemberMyFeature() {
const { hasFeature, hasFeatureInStudio, getStudiosWithFeature, loading } = useMemberFeatures()
const featureCode = MEMBER_FEATURE_CODES.MY_FEATURE
return {
isMyFeatureEnabled: hasFeature(featureCode),
hasMyFeatureInStudio: (studioId: string) => hasFeatureInStudio(featureCode, studioId),
studiosWithMyFeature: getStudiosWithFeature(featureCode),
loading,
featureCode
}
}
```
### Member Feature Gate erstellen
```typescript
// In member-feature-context.tsx oder eigene Datei
export function MemberMyFeatureGate({ children }: { children: React.ReactNode }) {
return (
<MemberFeatureGate feature={MEMBER_FEATURE_CODES.MY_FEATURE} silent>
{children}
</MemberFeatureGate>
)
}
```
### Verwendung im Member Dashboard
```typescript
// src/app/dashboard/page.tsx oder Member-Komponenten
import { useMemberMyFeature, MEMBER_FEATURE_CODES } from '@/lib/features/member-feature-context'
export default function MemberDashboard() {
// Option A: Mit spezifischem Hook
const { isMyFeatureEnabled } = useMemberMyFeature()
// Option B: Mit generischem Hook
const { hasFeature } = useMemberFeatures()
const hasMyFeature = hasFeature(MEMBER_FEATURE_CODES.MY_FEATURE)
// Option C: Prüfen für spezifisches Studio
const { hasFeatureInStudio } = useMemberFeatures()
const studioHasFeature = hasFeatureInStudio('my_feature_code', studioId)
return (
<>
{/* Bedingt rendern */}
{isMyFeatureEnabled && (
<MyFeatureSection />
)}
{/* Oder mit Gate Component */}
<MemberMyFeatureGate>
<MyFeatureSection />
</MemberMyFeatureGate>
</>
)
}
```
### Navigation Items bedingt anzeigen
```typescript
// src/components/member/shared/MemberNavigation.tsx
export function MemberSidebar({ ... }: MemberNavigationProps) {
// Feature von Props oder aus Context
const hasChatAddon = props.hasChatAddon // Vom Dashboard durchgereicht
return (
<nav>
{/* Immer sichtbare Items */}
<NavItem icon={Home} label="Home" ... />
<NavItem icon={Calendar} label="Kursplan" ... />
{/* Bedingt sichtbar basierend auf Feature */}
{hasChatAddon && (
<NavItem icon={MessageSquare} label="Nachrichten" ... />
)}
</nav>
)
}
```
### Wichtig: Studios mit MemberFeatureContext synchronisieren
```typescript
// src/components/member/hooks/useMemberData.ts
export function useMemberData({ userId }: UseMemberDataProps) {
// Context für Feature Sync holen
const { setStudios } = useMemberFeatures()
const loadDashboardData = async () => {
// ... Studios laden ...
const allStudios = Array.from(allStudiosMap.values())
setMyStudios(allStudios)
// WICHTIG: Studios mit MemberFeatureContext synchronisieren
setStudios(allStudios.map((s: any) => ({ id: s.id, name: s.name })))
}
}
```
## 🎨 Complete Example: Video-on-Demand Feature
```typescript
// src/lib/features/vod-feature.tsx
'use client'
import React from 'react'
import { useFeatures } from './feature-context'
import { activeTheme } from '@/config/theme'
import { H3 } from '@/components/ui/Typography'
// 1. Define Feature Code
export const VOD_FEATURE_CODE = 'video_on_demand'
// 2. Custom Hook
export function useVodFeature() {
const { hasFeature, canUse, loading } = useFeatures()
return {
isVodEnabled: hasFeature(VOD_FEATURE_CODE),
canUseVod: canUse(VOD_FEATURE_CODE),
loading: loading,
featureCode: VOD_FEATURE_CODE
}
}
// 3. User Feature Gate (simple)
export function VodFeatureGate({ children }: { children: React.ReactNode }) {
const { canUseVod, loading } = useVodFeature()
if (loading) return null
if (!canUseVod) return null
return <>{children}</>
}
// 4. Admin Feature Gate (with upgrade prompt)
export function AdminVodGate({
children,
fallback
}: {
children: React.ReactNode
fallback?: React.ReactNode
}) {
const { isVodEnabled, loading } = useVodFeature()
if (loading) {
return <>{children}</>
}
if (!isVodEnabled && fallback) {
return <>{fallback}</>
}
if (!isVodEnabled) {
return (
<div className="p-8 text-center">
<div className="max-w-md mx-auto">
<svg
className="w-16 h-16 text-[rgb(23,23,23)] mx-auto mb-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
<H3 className="mb-2">
Video-on-Demand nicht aktiviert
</H3>
<p className="text-[rgb(23,23,23)] mb-4">
Das VOD Feature ist in Ihrem aktuellen Tarif nicht enthalten.
</p>
<a
href="/admin/einstellungen/tarife"
className={\`inline-flex items-center px-4 py-2 bg-gradient-to-r \${activeTheme.gradient} text-white rounded-lg hover:opacity-90 transition-all\`}
>
Tarif upgraden
</a>
</div>
</div>
)
}
return <>{children}</>
}
```
**Usage in Component:**
```typescript
// app/admin/videos/page.tsx
import { AdminVodGate } from '@/lib/features/vod-feature'
export default function VideosPage() {
return (
<AdminVodGate>
<div>
{/* VOD Content here */}
</div>
</AdminVodGate>
)
}
```
## ✅ Checklist: New Addon/Feature
Before submitting/completing a new feature, verify:
### Code
- [ ] Feature Code defined (`MY_FEATURE_CODE`)
- [ ] Custom hook created (`useMyFeature`)
- [ ] User Feature Gate created (`MyFeatureGate`)
- [ ] Admin Feature Gate created with upgrade prompt (`AdminMyFeatureGate`)
- [ ] Theme system used (`activeTheme.gradient`)
- [ ] Typography components used (`H3` from `@/components/ui/Typography`)
- [ ] No hardcoded colors (use `activeTheme`)
- [ ] No hardcoded text (use translations if user-facing)
### Stripe (WICHTIG!)
- [ ] Stripe Product erstellt (Name: `Bookicorn [Feature Name]`)
- [ ] Product Metadata: `feature_code` und `type: addon`
- [ ] Product ID notiert: `prod_TnXXXXXXXXXX`
### Datenbank
- [ ] Feature in `feature_definitions` registriert
- [ ] `metadata.stripe_product_id` eingetragen!
- [ ] `metadata.status` = `available`
- [ ] `addon_price_monthly` und `addon_price_yearly` gesetzt
- [ ] Feature added to plan OR available as addon
### Testing
- [ ] Tested with feature enabled
- [ ] Tested with feature disabled (shows upgrade prompt)
- [ ] Studio-Erstellung getestet: Kein neues Stripe Product erstellt
## 🚨 Common Mistakes to Avoid
### ❌ WRONG: Hardcoded Colors
```typescript
<div className="bg-blue-500">...</div>
```
### ✅ RIGHT: Use Theme
```typescript
<div className={\`bg-gradient-to-r \${activeTheme.gradient}\`}>...</div>
```
### ❌ WRONG: No Loading State
```typescript
export function MyFeatureGate({ children }) {
const { canUseMyFeature } = useMyFeature() // Missing loading!
if (!canUseMyFeature) return null
return <>{children}</>
}
```
### ✅ RIGHT: Handle Loading
```typescript
export function MyFeatureGate({ children }) {
const { canUseMyFeature, loading } = useMyFeature()
if (loading) return null // ← Important!
if (!canUseMyFeature) return null
return <>{children}</>
}
```
### ❌ WRONG: Feature Code Mismatch
```typescript
// File: chat-feature.tsx
export const CHAT_FEATURE_CODE = 'messaging' // ❌
// Database: feature_definitions.code = 'chat_messaging' // ❌ Doesn't match!
```
### ✅ RIGHT: Matching Codes
```typescript
// File: chat-feature.tsx
export const CHAT_FEATURE_CODE = 'chat_messaging' // ✅
// Database: feature_definitions.code = 'chat_messaging' // ✅ Matches!
```
## 🔧 Feature Context Reference
The `FeatureProvider` provides these helper functions:
```typescript
const {
// Subscription & Plan
subscription, // StudioSubscription | null
plan, // SubscriptionPlan | null
// Features
features, // Set<string> - All active feature codes
featureList, // Feature[] - Full feature objects
addons, // FeatureAddon[] - Active addons
// Limits
limits, // Record<string, number | null>
usage, // Record<string, LimitUsage>
// Helpers
hasFeature, // (code: string) => boolean
canUse, // (code: string) => boolean
hasLimit, // (code: string) => boolean
getRemainingLimit, // (code: string) => number | null
isNearLimit, // (code: string, threshold?: number) => boolean
isAtLimit, // (code: string) => boolean
// State
loading, // boolean
error, // string | null
// Actions
refreshFeatures // () => Promise<void>
} = useFeatures()
```
## 📊 Database Schema Reference
### feature_definitions
```sql
id uuid PRIMARY KEY
code varchar UNIQUE -- 'chat_messaging', 'studio_blog'
name varchar -- 'Chat & Messaging'
description text
category varchar -- 'core', 'content', 'marketing'
addon_price_monthly numeric(10,2) -- Monatspreis als Addon
addon_price_yearly numeric(10,2) -- Jahrespreis als Addon
is_active boolean DEFAULT true
metadata jsonb -- WICHTIG: Enthält stripe_product_id!
created_at timestamptz
-- metadata Struktur:
-- {
-- "status": "available", -- oder "coming_soon"
-- "stripe_product_id": "prod_TnXXX", -- PFLICHT für Addons!
-- "featured": false,
-- "includes": ["Feature 1", "Feature 2"]
-- }
```
### studio_feature_addons
```sql
id uuid PRIMARY KEY
studio_id uuid REFERENCES studios
feature_id uuid REFERENCES feature_definitions
status varchar -- 'active', 'cancelled', 'cancelling'
billing_cycle varchar -- 'monthly', 'yearly', 'usage'
price_override numeric(10,2)
valid_until timestamptz -- For 'cancelling' status
created_at timestamptz
```
## 🎯 When This Skill Activates
This skill should be loaded when:
- Creating a new feature/addon
- Keywords: `addon`, `feature`, `feature gate`, `subscription`
- Working in `src/lib/features/`
- Creating feature-gated pages
- Setting up premium features
---
**Remember:** Consistency is key! Every addon should follow this exact pattern.Related Skills
advanced-features
Implement advanced task features - Priorities, Tags, Due Dates, Reminders, Recurring Tasks, Search, Filter, and Sort. Use when adding Phase 5 advanced functionality. (project)
advanced-features-2025
Advanced 2025 Claude Code plugin features. PROACTIVELY activate for: (1) Agent Skills with progressive disclosure (2) Hook automation (PreToolUse, PostToolUse, etc.) (3) MCP server integration (4) Repository-level configuration (5) Team plugin distribution (6) Context efficiency optimization Provides cutting-edge plugin capabilities and patterns.
add-new-feature
No description provided.
add-feature
Scaffold complete feature with types, repository, API routes, components, store actions, and tests. Use when adding major new functionality like water tracking, sleep tracking, etc.
add-feature-hook
Creates TanStack Query hooks for API features with authentication. Use when connecting frontend to backend endpoints, creating data fetching hooks.
adapter-development
Comprehensive guide for AIDB debug adapter development. Covers component-based architecture, language-specific patterns (Python/debugpy, JavaScript/vscode-js-debug, Java/java-debug), lifecycle hooks, process management, port management, launch orchestration, resource cleanup, child sessions, and common pitfalls. Essential for developing or maintaining AIDB debug adapters.
ADAPTATION_GUIDE
Use when adapting Droidz framework or creating custom workflows. Guide for customizing droids, skills, and commands for specific project needs.
active-learning-system
Эксперт active learning. Используй для ML с участием человека, uncertainty sampling, annotation workflows и labeling optimization.
synapse-action-development
Explains how to create Synapse plugin actions. Use when the user asks to "create an action", "write an action", uses "@action decorator", "BaseAction class", "function-based action", "class-based action", "Pydantic params", "ActionPipeline", "DataType", "input_type", "output_type", "semantic types", "YOLODataset", "ModelWeights", "pipeline chaining", or needs help with synapse plugin action development.
configuring-acquisition-system
Guides users through configuring data acquisition systems on local machines using MCP tools for hardware discovery and direct YAML file editing for system parameters. Covers working directory setup, hardware discovery, system configuration, and credential management. Use when setting up a new acquisition PC, reconfiguring hardware, or troubleshooting system configuration issues.
Achievements System
Rewarding players for completing specific goals through progress tracking, unlocking logic, rarity calculation, and achievement display for gamification and player engagement.
meta:cli-feature-creator
CLI Feature Creator wizard for adding new aaa CLI commands. Use when user asks to "add aaa command", "create CLI feature", "add CLI command", or needs to extend the aaa CLI with new functionality.