Application Settings Guide
This guide explains how to use Fluxbase’s unified application settings system with the app.settings table.
Table of Contents
Section titled “Table of Contents”Overview
Section titled “Overview”Fluxbase provides a unified app.settings table in the app schema for storing all application-level configuration. This replaces the previous separate tables (dashboard.auth_settings, dashboard.system_settings, dashboard.custom_settings).
Key Features
Section titled “Key Features”- Unified Storage: One table for all settings with flexible JSONB values
- Categorization: Settings organized by category (auth, system, storage, functions, realtime, custom)
- Access Control: Built-in RLS policies with
is_publicandis_secretflags - Flexible Values: Store any JSON-serializable data
- Role-Based Editing: Control who can edit settings with
editable_byarray
Database Schema
Section titled “Database Schema”Table Structure
Section titled “Table Structure”CREATE TABLE app.settings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), key TEXT UNIQUE NOT NULL, value JSONB NOT NULL, value_type TEXT NOT NULL DEFAULT 'string' CHECK (value_type IN ('string', 'number', 'boolean', 'json', 'array')), category TEXT NOT NULL DEFAULT 'custom' CHECK (category IN ('auth', 'system', 'storage', 'functions', 'realtime', 'custom')), description TEXT, is_public BOOLEAN DEFAULT false, is_secret BOOLEAN DEFAULT false, editable_by TEXT[] NOT NULL DEFAULT ARRAY['dashboard_admin']::TEXT[], metadata JSONB DEFAULT '{}'::JSONB, created_by UUID, updated_by UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW());Default RLS Policies
Section titled “Default RLS Policies”-- Service role has full access (bypasses RLS anyway)CREATE POLICY "Service role has full access to app settings" ON app.settings FOR ALL TO service_role USING (true) WITH CHECK (true);
-- Anon/authenticated can read public, non-secret settingsCREATE POLICY "Public settings are readable by anyone" ON app.settings FOR SELECT TO anon, authenticated USING (is_public = true AND is_secret = false);
-- Authenticated users can read all non-secret settingsCREATE POLICY "Authenticated users can read non-secret settings" ON app.settings FOR SELECT TO authenticated USING (is_secret = false);SDK Usage
Section titled “SDK Usage”TypeScript/JavaScript SDK
Section titled “TypeScript/JavaScript SDK”Framework Settings (Structured)
Section titled “Framework Settings (Structured)”import { FluxbaseClient } from "@nimbleflux/fluxbase-sdk";
const client = new FluxbaseClient("https://api.myapp.com", "admin_key");
// Get all app settings (structured)const settings = await client.admin.settings.app.get();console.log(settings.authentication.enable_signup);console.log(settings.features.enable_realtime);
// Update framework settingsawait client.admin.settings.app.update({ authentication: { enable_signup: true, password_min_length: 12, require_email_verification: true, }, features: { enable_realtime: true, enable_storage: true, },});
// Use convenience methodsawait client.admin.settings.app.enableSignup();await client.admin.settings.app.setPasswordMinLength(12);await client.admin.settings.app.setFeature("realtime", true);
// Configure email providersawait client.admin.settings.app.configureSMTP({ host: "smtp.gmail.com", port: 587, username: "noreply@myapp.com", password: "app-password", use_tls: true,});Custom Settings (Key-Value)
Section titled “Custom Settings (Key-Value)”// Set custom settingsawait client.admin.settings.app.setSetting( "billing.tiers", { free: 1000, pro: 10000, enterprise: 100000, }, { description: "API quotas per billing tier", is_public: false, is_secret: false, },);
// Get single custom setting valueconst tiers = await client.admin.settings.app.getSetting("billing.tiers");console.log(tiers); // { free: 1000, pro: 10000, enterprise: 100000 }
// Get multiple custom settingsconst values = await client.admin.settings.app.getSettings([ "billing.tiers", "features.beta_enabled", "features.dark_mode",]);console.log(values);// {// 'billing.tiers': { free: 1000, pro: 10000, enterprise: 100000 },// 'features.beta_enabled': { enabled: true },// 'features.dark_mode': { enabled: false }// }
// List all custom settingsconst allSettings = await client.admin.settings.app.listSettings();allSettings.forEach((s) => console.log(s.key, s.value));
// Delete custom settingawait client.admin.settings.app.deleteSetting("billing.tiers");Public Access (Non-Admin Users)
Section titled “Public Access (Non-Admin Users)”// Users with regular tokens can access public settingsconst userClient = new FluxbaseClient("https://api.myapp.com", "user_token");
// Get single public setting (respects RLS)const betaEnabled = await userClient.settings.get("features.beta_enabled");console.log(betaEnabled); // { enabled: true }
// Get multiple public settingsconst publicValues = await userClient.settings.getMany([ "features.beta_enabled", "features.dark_mode", "public.app_version", "internal.secret_key", // Will be filtered out by RLS]);console.log(publicValues);// {// 'features.beta_enabled': { enabled: true },// 'features.dark_mode': { enabled: false },// 'public.app_version': '1.0.0'// // 'internal.secret_key' is omitted by RLS// }RLS Policies
Section titled “RLS Policies”Creating Custom Policies
Section titled “Creating Custom Policies”You can create custom RLS policies in user migrations to implement fine-grained access control.
Example: Users Can Only Read Specific Settings
Section titled “Example: Users Can Only Read Specific Settings”-- In /migrations/user/001_custom_settings_policies.sql
-- Allow authenticated users to ONLY read feature flagsCREATE POLICY "Users can read feature flags only" ON app.settings FOR SELECT TO authenticated USING ( key ~ '^features\.' -- Key starts with 'features.' AND is_public = true AND is_secret = false );Example: Category-Based Access
Section titled “Example: Category-Based Access”-- Only allow reading from 'custom' categoryCREATE POLICY "Users can read custom settings only" ON app.settings FOR SELECT TO authenticated USING ( category = 'custom' AND is_public = true );Example: Restrictive Policy (AND Logic)
Section titled “Example: Restrictive Policy (AND Logic)”-- Use RESTRICTIVE policy to enforce multiple conditionsCREATE POLICY "Restrict anon to specific keys" ON app.settings AS RESTRICTIVE FOR SELECT TO anon USING ( key IN ('features.beta_enabled', 'public.app_version') AND is_public = true AND is_secret = false );Example: Metadata-Based Access
Section titled “Example: Metadata-Based Access”-- Use metadata field for advanced access controlCREATE POLICY "Users can read settings with public_api flag" ON app.settings FOR SELECT TO authenticated USING ( (metadata->>'public_api')::boolean = true OR is_public = true );Backend Usage
Section titled “Backend Usage”Go Backend
Section titled “Go Backend”import ( "context" "time" "github.com/fluxbase/fluxbase/internal/auth")
// Initialize settings cachesettingsCache := auth.NewSettingsCache(settingsService, 5*time.Minute)
// Get boolean settingenableSignup := settingsCache.GetBool(ctx, "app.auth.enable_signup", false)if enableSignup { // Allow user registration}
// Get integer settingpasswordMinLength := settingsCache.GetInt(ctx, "app.auth.password_min_length", 12)
// Get string settingappVersion := settingsCache.GetString(ctx, "public.app_version", "1.0.0")
// Get JSON settingtype FeatureConfig struct { Enabled bool `json:"enabled"` Description string `json:"description"`}
var config FeatureConfigerr := settingsCache.GetJSON(ctx, "features.config", &config)if err != nil { log.Printf("Failed to get feature config: %v", err)}
// Get multiple settings at oncesettings, err := settingsCache.GetMany(ctx, []string{ "features.beta_enabled", "features.dark_mode", "public.app_version",})if err != nil { log.Printf("Failed to get settings: %v", err)}
// Environment Variable Override// Set FLUXBASE_AUTH_SIGNUP_ENABLED=true to override database value// Priority: Env Var > Cache > Database > DefaultCache Management
Section titled “Cache Management”// Invalidate specific settingsettingsCache.Invalidate("app.auth.enable_signup")
// Invalidate all cached settingssettingsCache.InvalidateAll()
// Check if setting is overridden by env varif settingsCache.IsOverriddenByEnv("app.auth.enable_signup") { log.Println("Setting is overridden by environment variable")}
// Get environment variable name for a settingenvVar := settingsCache.GetEnvVarName("app.auth.enable_signup")// Returns: "FLUXBASE_AUTH_SIGNUP_ENABLED"Migration Guide
Section titled “Migration Guide”Migrating from Old Settings Tables
Section titled “Migrating from Old Settings Tables”If you have existing data in the old settings tables, here’s how to migrate:
-- Migration script to move data from old tables to app.settings
-- Migrate dashboard.auth_settingsINSERT INTO app.settings (key, value, category, description, created_at, updated_at)SELECT key, value, 'auth' as category, description, created_at, updated_atFROM dashboard.auth_settingsON CONFLICT (key) DO NOTHING;
-- Migrate dashboard.system_settingsINSERT INTO app.settings (key, value, category, description, created_at, updated_at)SELECT key, value, 'system' as category, description, created_at, updated_atFROM dashboard.system_settingsON CONFLICT (key) DO NOTHING;
-- Migrate dashboard.custom_settingsINSERT INTO app.settings ( key, value, value_type, category, description, editable_by, metadata, created_by, updated_by, created_at, updated_at)SELECT key, value, value_type, 'custom' as category, description, editable_by, metadata, created_by, updated_by, created_at, updated_atFROM dashboard.custom_settingsON CONFLICT (key) DO NOTHING;
-- After verifying migration, drop old tables-- DROP TABLE dashboard.auth_settings;-- DROP TABLE dashboard.system_settings;-- DROP TABLE dashboard.custom_settings;Best Practices
Section titled “Best Practices”1. Use Appropriate Categories
Section titled “1. Use Appropriate Categories”// Framework settings (Fluxbase manages these)category: "auth" | "system" | "storage" | "functions" | "realtime";
// Your custom settingscategory: "custom";2. Set Access Flags Correctly
Section titled “2. Set Access Flags Correctly”// Public feature flags (anyone can read)is_public: true, is_secret: false
// Internal config (only authenticated users)is_public: false, is_secret: false
// Sensitive data (only service_role)is_public: false, is_secret: true3. Use Meaningful Keys
Section titled “3. Use Meaningful Keys”// Good - hierarchical, descriptive"billing.tiers.api_quotas";"features.beta.enabled";"integrations.stripe.webhook_secret";
// Bad - flat, unclear"config1";"setting_123";"data";4. Add Descriptions
Section titled “4. Add Descriptions”await client.admin.settings.app.setSetting('billing.tiers', {...}, { description: 'API request quotas per billing tier (free/pro/enterprise)'})5. Use Metadata for Advanced Use Cases
Section titled “5. Use Metadata for Advanced Use Cases”await client.admin.settings.app.setSetting('features.beta', {...}, { metadata: { public_api: true, version: '2.0', deprecated: false, rollout_percentage: 50 }})Examples
Section titled “Examples”Example 1: Feature Flag System
Section titled “Example 1: Feature Flag System”// Create feature flagsawait client.admin.settings.app.setSetting( "features.new_dashboard", { enabled: true, rollout: 100 }, { is_public: true, description: "New dashboard UI" },);
await client.admin.settings.app.setSetting( "features.ai_assistant", { enabled: false, rollout: 0 }, { is_public: true, description: "AI-powered assistant" },);
// Users check features in their appconst features = await userClient.settings.getMany([ "features.new_dashboard", "features.ai_assistant",]);
if (features["features.new_dashboard"]?.enabled) { // Show new dashboard}Example 2: API Quotas
Section titled “Example 2: API Quotas”// Set quotasawait client.admin.settings.app.setSetting('billing.quotas', { free: { requests: 1000, storage: 100 }, pro: { requests: 100000, storage: 10000 }, enterprise: { requests: -1, storage: -1 }}, { description: 'API and storage quotas per plan tier'})
// Backend checks quotaconst quotas = await settingsCache.GetJSON(ctx, "billing.quotas", "aConfig)userQuota := quotas[user.PlanTier]Example 3: A/B Testing Configuration
Section titled “Example 3: A/B Testing Configuration”// Configure A/B testsawait client.admin.settings.app.setSetting( "experiments.new_checkout", { enabled: true, variant_a: { traffic: 50, name: "Original" }, variant_b: { traffic: 50, name: "Simplified" }, }, { is_public: true, metadata: { experiment_id: "exp_001", started_at: "2024-01-01" }, },);Troubleshooting
Section titled “Troubleshooting”Setting Not Accessible
Section titled “Setting Not Accessible”Problem: User gets 403 when accessing a setting
Solution: Check RLS policies and access flags
-- Check setting access flagsSELECT key, is_public, is_secret, categoryFROM app.settingsWHERE key = 'your.setting.key';
-- Check active policiesSELECT * FROM pg_policiesWHERE schemaname = 'app' AND tablename = 'settings';Setting Not Found
Section titled “Setting Not Found”Problem: getSetting() returns 404
Solution: Verify setting exists and key is correct
-- List all settingsSELECT key, category FROM app.settings ORDER BY key;
-- Search for similar keysSELECT key FROM app.settings WHERE key LIKE '%search_term%';Cache Not Updating
Section titled “Cache Not Updating”Problem: Backend shows old value after update
Solution: Invalidate cache
settingsCache.Invalidate("app.auth.enable_signup")// orsettingsCache.InvalidateAll()