TinyKit Pro Docs

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

  1. Default Enabled: New features should default to true for backward compatibility
  2. Graceful Degradation: Apps should work well with features disabled
  3. Clear Naming: Use descriptive feature flag names with enable prefix
  4. Documentation: Always document what each flag controls

Performance Considerations

  1. Single Query: All feature settings come from one query
  2. Real-time Sync: Changes propagate automatically via Convex
  3. Default Values: Sensible defaults while loading
  4. Minimal Re-renders: Context prevents unnecessary updates

Migration from Environment Variables

If migrating from environment-based feature flags:

  1. Add Database Field: Add the setting to siteSettings schema
  2. Update Provider: Add the setting to the FeatureSettingsProvider
  3. Replace Usage: Replace process.env.NEXT_PUBLIC_* checks with hook
  4. Remove Env Var: Delete from .env files and setup wizard
  5. Update Tests: Remove env var mocking in tests

Next: Organizations → | ← Previous: Billing

On this page

Ship your startup faster. In minutes.

Get TinyKit Pro