Feature Flags
TinyKit Pro uses a database-driven feature flag system for runtime configuration. This approach enables you to enable or disable major features dynamically t...
TinyKit Pro uses a database-driven feature flag system for runtime configuration. This approach enables you to enable or disable major features dynamically through the admin panel without code changes or deployments.
Feature Settings System
Core Implementation
Feature settings are stored in the siteSettings database table and accessed via the useFeatureSettings() hook:
import { useFeatureSettings } from "@/lib/features";
function MyComponent() {
const { enableOrganizations, isLoading } = useFeatureSettings();
if (isLoading) return <Skeleton />;
if (!enableOrganizations) {
return null; // Hide org-related UI
}
return <OrganizationSection />;
}Admin Configuration
Feature settings can be toggled in the admin panel:
Admin > Site Settings > General > Feature Settings
- Toggle switches for each feature
- Changes take effect immediately for all users
- No restart or redeployment required
Available Feature Settings
Organization Features (enableOrganizations)
Controls the complete organization management system, including multi-user workspaces, role-based access control, and organization-specific billing.
Default: true
When enabled (true):
- Full organization management capabilities
- Organization creation, invitations, and management
- Organization-specific billing and subscriptions
- Admin panel includes organization statistics and management
- Navigation includes organization-related links
When disabled (false):
- All organization UI elements are hidden
- Admin panel hides organization sections
- Product listings show only personal plans
- Navigation excludes organization links
- Backend data remains intact for future re-enabling
Affected Areas:
- Organization routes (
/orgs/*) - Admin dashboard statistics and recent activity
- Admin sidebar navigation
- Product management (hides organization products)
- Notification targeting (hides org-specific options)
Implementation Patterns
Frontend Usage with Hook
import { useFeatureSettings } from "@/lib/features";
function ProductTypeSelector() {
const { enableOrganizations } = useFeatureSettings();
return (
<Select>
<SelectItem value="personal">Personal</SelectItem>
{enableOrganizations && (
<SelectItem value="org">Organization</SelectItem>
)}
</Select>
);
}Conditional Navigation
import { useFeatureSettings } from "@/lib/features";
function NavigationMenu() {
const { enableOrganizations } = useFeatureSettings();
return (
<nav>
<Link href="/home">Home</Link>
{enableOrganizations && <Link href="/orgs">Organizations</Link>}
</nav>
);
}Loading State Handling
import { useFeatureSettings } from "@/lib/features";
function ConditionalFeature() {
const { enableOrganizations, isLoading } = useFeatureSettings();
// Show nothing while loading to prevent flash
if (isLoading) return null;
if (!enableOrganizations) return null;
return <OrganizationFeature />;
}Backend Integration
Public Query for Feature Settings
// convex/siteSettings/public/queries.ts
export const getFeatureSettings = query({
args: {},
handler: async (ctx) => {
const settings = await ctx.db.query("siteSettings").first();
return {
enableOrganizations: settings?.enableOrganizations ?? true,
};
},
});Admin Mutation for Updating Settings
// convex/siteSettings/private/mutations.ts
export const updateFeatureSettings = mutation({
args: {
enableOrganizations: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
const { userId } = await requireAccess(ctx, { userRole: ["admin"] });
const settings = await ctx.db.query("siteSettings").first();
if (settings) {
await ctx.db.patch(settings._id, {
enableOrganizations: args.enableOrganizations,
});
}
},
});Architecture
Provider Pattern
The feature settings are delivered via React Context:
// src/lib/providers/feature-settings-provider.tsx
export function FeatureSettingsProvider({ children }: { children: ReactNode }) {
const featureSettings = useQuery(
api.siteSettings.public.queries.getFeatureSettings
);
const value: FeatureSettings = {
enableOrganizations: featureSettings?.enableOrganizations ?? true,
isLoading: featureSettings === undefined,
};
return (
<FeatureSettingsContext.Provider value={value}>
{children}
</FeatureSettingsContext.Provider>
);
}Real-time Updates
Because the feature settings use Convex's useQuery, changes made in the admin panel automatically propagate to all connected clients in real-time. No page refresh is required.
Adding New Feature Flags
1. Update Database Schema
Add to convex/siteSettings/schema.ts:
export const siteSettingsFields = {
// ... existing fields
enableNewFeature: v.optional(v.boolean()),
};2. Update Feature Settings Query
Add to convex/siteSettings/public/queries.ts:
export const getFeatureSettings = query({
handler: async (ctx) => {
const settings = await ctx.db.query("siteSettings").first();
return {
enableOrganizations: settings?.enableOrganizations ?? true,
enableNewFeature: settings?.enableNewFeature ?? true, // Add new flag
};
},
});3. Update Admin Mutation
Add to convex/siteSettings/private/mutations.ts:
export const updateFeatureSettings = mutation({
args: {
enableOrganizations: v.optional(v.boolean()),
enableNewFeature: v.optional(v.boolean()), // Add new flag
},
// ... handler
});4. Update Provider Types
Add to src/lib/providers/feature-settings-provider.tsx:
type FeatureSettings = {
enableOrganizations: boolean;
enableNewFeature: boolean; // Add new flag
isLoading: boolean;
};5. Add Admin UI
Add toggle to src/features/site-settings/admin/feature-settings-card.tsx.
6. Update Documentation
Document the new feature flag in this file.
Best Practices
Development Guidelines
- Default Enabled: New features should default to
truefor backward compatibility - Graceful Degradation: Apps should work well with features disabled
- Clear Naming: Use descriptive feature flag names with
enableprefix - Documentation: Always document what each flag controls
Performance Considerations
- Single Query: All feature settings come from one query
- Real-time Sync: Changes propagate automatically via Convex
- Default Values: Sensible defaults while loading
- Minimal Re-renders: Context prevents unnecessary updates
Migration from Environment Variables
If migrating from environment-based feature flags:
- Add Database Field: Add the setting to siteSettings schema
- Update Provider: Add the setting to the FeatureSettingsProvider
- Replace Usage: Replace
process.env.NEXT_PUBLIC_*checks with hook - Remove Env Var: Delete from
.envfiles and setup wizard - Update Tests: Remove env var mocking in tests
Next: Organizations → | ← Previous: Billing