Settings Plugin

A persistent key-value settings store for Svelar/SvelteKit with scoped settings (global, per-user, per-team), type-safe getters, in-memory caching, and pre-built UI form components. Inspired by Laravel's Spatie Settings package.

Package: @beeblock/svelar-settings

Install:

npx svelar plugin:install @beeblock/svelar-settings

Imports:

// Plugin registration
import { SvelarSettingsPlugin } from '@beeblock/svelar-settings/server';

// Core API
import { Settings, SettingsStore, HasSettings, setSettingsStore } from '@beeblock/svelar-settings';

// Server-side
import { SettingsController } from '@beeblock/svelar-settings/server';

// UI components
import { SettingsForm, SettingsField } from '@beeblock/svelar-settings/ui';

// Types
import type {
  SettingType,
  GroupType,
  SettingRecord,
  SettingsPluginConfig,
  SettingsScope,
  SettingsRequestBody,
  SettingsFieldDefinition,
} from '@beeblock/svelar-settings';

Quick Start

1. Register the Plugin

// src/lib/plugins.ts
import { SvelarSettingsPlugin } from '@beeblock/svelar-settings/server';

export const settingsPlugin = new SvelarSettingsPlugin({
  table: 'settings',
  cache: true,
  cacheTtl: 3600,
  prefix: '/api',
});

2. Use Settings Anywhere

import { Settings } from '@beeblock/svelar-settings';

// Global settings
await Settings.set('site_name', 'My Awesome App');
await Settings.set('maintenance_mode', false);
await Settings.set('max_upload_size', 10);
await Settings.set('features', { darkMode: true, beta: false });

const siteName = await Settings.getString('site_name');
const maintenance = await Settings.getBoolean('maintenance_mode');
const maxSize = await Settings.getNumber('max_upload_size');
const features = await Settings.getJson('features');

// Check existence
const exists = await Settings.has('site_name'); // true

// Remove a setting
await Settings.forget('old_setting');

// Get all settings (optionally filtered by prefix)
const allSettings = await Settings.all();
const mailSettings = await Settings.all('mail_');

Configuration

The SvelarSettingsPlugin constructor accepts:

Option Type Default Description
table string 'settings' Database table name
cache boolean true Enable in-memory caching
cacheTtl number 3600 Cache TTL in seconds
prefix string '/api' API route prefix

Core API

Settings Facade

The Settings object provides a global singleton for managing settings:

import { Settings } from '@beeblock/svelar-settings';

// Type-safe getters
await Settings.get<string>('key');              // Generic getter
await Settings.getString('key', 'default');     // String with default
await Settings.getNumber('key', 0);             // Number with default
await Settings.getBoolean('key', false);        // Boolean with default
await Settings.getJson<MyType>('key');           // JSON object

// Setters
await Settings.set('key', 'value');             // Auto-detects type
await Settings.setMany({                        // Set multiple at once
  'theme': 'dark',
  'locale': 'en',
  'notifications_enabled': true,
});

// Management
await Settings.has('key');                      // Check existence
await Settings.forget('key');                   // Delete a setting
await Settings.all();                           // Get all settings
await Settings.all('mail_');                    // Get settings by prefix

// Cache management
Settings.flushCache();                          // Clear the in-memory cache

Scoped Settings

Settings can be scoped to users or teams:

// Per-user settings
const userSettings = Settings.forUser(userId);
await userSettings.set('theme', 'dark');
await userSettings.set('notifications', true);
const theme = await userSettings.getString('theme', 'light');

// Per-team settings
const teamSettings = Settings.forTeam(teamId);
await teamSettings.set('plan', 'pro');
const plan = await teamSettings.getString('plan');

// All settings for a scope
const allUserSettings = await userSettings.all();

HasSettings Mixin

Add settings directly to any Model:

import { Model } from '@beeblock/svelar/database';
import { HasSettings } from '@beeblock/svelar-settings';

class User extends HasSettings(Model, 'user') {
  static table = 'users';
}

const user = await User.find(1);

// Access scoped settings through the model
await user.settings().set('theme', 'dark');
await user.settings().set('language', 'en');

const theme = await user.settings().getString('theme', 'light');
const language = await user.settings().getString('language', 'en');

// Get all settings for this user
const all = await user.settings().all();

The second argument to HasSettings is the GroupType ('user' or 'team').

SettingsStore

The underlying store that handles database operations and caching:

import { SettingsStore } from '@beeblock/svelar-settings';

const store = new SettingsStore({
  table: 'settings',
  cache: true,
  cacheTtl: 3600,
});

await store.get('global', null, 'key');
await store.set('user', 42, 'theme', 'dark');
await store.setMany('user', 42, { theme: 'dark', lang: 'en' });
await store.has('global', null, 'site_name');
await store.forget('global', null, 'old_key');
await store.all('user', 42, 'notification_');

store.flushCache();

Setting Types

Values are automatically serialized with type detection:

Type Example Stored As
'string' 'hello' "hello"
'number' 42 42
'boolean' true true
'json' { a: 1 } {"a":1}

Server-Side

SettingsController

Handles GET and POST requests for settings CRUD:

// src/routes/api/settings/+server.ts
import { SettingsController } from '@beeblock/svelar-settings/server';

export const GET = async (event) => SettingsController.handle(event);
export const POST = async (event) => SettingsController.handle(event);

GET parameters:

Parameter Type Description
action string get, set, setMany, has, forget, all
key string Setting key
group_type string global, user, team
group_id number Scope ID (required for user/team)
prefix string Filter prefix (for all action)

POST body:

{
  "action": "set",
  "key": "theme",
  "value": "dark",
  "group_type": "user",
  "group_id": 42
}
{
  "action": "setMany",
  "entries": {
    "theme": "dark",
    "language": "en"
  },
  "group_type": "user",
  "group_id": 42
}

UI Components

SettingsForm

Renders a complete settings form from field definitions:

<script lang="ts">
  import { SettingsForm } from '@beeblock/svelar-settings/ui';
  import type { SettingsFieldDefinition } from '@beeblock/svelar-settings';

  const fields: SettingsFieldDefinition[] = [
    {
      key: 'site_name',
      label: 'Site Name',
      type: 'text',
      placeholder: 'Enter site name',
      required: true,
    },
    {
      key: 'maintenance_mode',
      label: 'Maintenance Mode',
      type: 'toggle',
      description: 'Temporarily disable the site for maintenance.',
    },
    {
      key: 'max_upload_size',
      label: 'Max Upload Size (MB)',
      type: 'number',
      defaultValue: 10,
    },
    {
      key: 'theme',
      label: 'Default Theme',
      type: 'select',
      options: [
        { label: 'Light', value: 'light' },
        { label: 'Dark', value: 'dark' },
      ],
    },
    {
      key: 'welcome_message',
      label: 'Welcome Message',
      type: 'textarea',
    },
  ];
</script>

<SettingsForm
  {fields}
  apiUrl="/api/settings"
  groupType="global"
  onSave={() => console.log('Settings saved!')}
/>

SettingsField

Individual settings field component:

<script lang="ts">
  import { SettingsField } from '@beeblock/svelar-settings/ui';
</script>

<SettingsField
  key="site_name"
  label="Site Name"
  type="text"
  value="My App"
  onChange={(value) => console.log('Changed:', value)}
/>

SettingsFieldDefinition:

Property Type Description
key string Setting key
label string Display label
type 'text' | 'number' | 'toggle' | 'select' | 'textarea' | 'json' Field type
description string Help text below the field
placeholder string Input placeholder
options { label: string; value: string }[] Options for select fields
defaultValue unknown Default value
required boolean Whether the field is required
disabled boolean Whether the field is disabled

Migration SQL

CREATE TABLE IF NOT EXISTS settings (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  group_type TEXT NOT NULL DEFAULT 'global',
  group_id INTEGER,
  key TEXT NOT NULL,
  value TEXT,
  type TEXT NOT NULL DEFAULT 'string',
  created_at TEXT,
  updated_at TEXT,
  UNIQUE(group_type, group_id, key)
);

You can also get this SQL programmatically:

const sql = SvelarSettingsPlugin.migrationSql();
// or
const sql = Settings.migrationSql('custom_settings_table');

Full Working Example

// src/routes/api/settings/+server.ts
import { SettingsController } from '@beeblock/svelar-settings/server';

export const GET = async (event) => SettingsController.handle(event);
export const POST = async (event) => SettingsController.handle(event);
<!-- src/routes/admin/settings/+page.svelte -->
<script lang="ts">
  import { SettingsForm } from '@beeblock/svelar-settings/ui';
  import type { SettingsFieldDefinition } from '@beeblock/svelar-settings';

  interface Props {
    data: { settings: Record<string, unknown> };
  }
  let { data }: Props = $props();

  const fields: SettingsFieldDefinition[] = [
    { key: 'site_name', label: 'Site Name', type: 'text', required: true },
    { key: 'site_description', label: 'Site Description', type: 'textarea' },
    { key: 'maintenance_mode', label: 'Maintenance Mode', type: 'toggle' },
    { key: 'items_per_page', label: 'Items Per Page', type: 'number', defaultValue: 20 },
    {
      key: 'default_locale',
      label: 'Default Language',
      type: 'select',
      options: [
        { label: 'English', value: 'en' },
        { label: 'Portuguese', value: 'pt' },
        { label: 'Spanish', value: 'es' },
      ],
    },
  ];
</script>

<h1>Application Settings</h1>

<SettingsForm
  {fields}
  apiUrl="/api/settings"
  groupType="global"
  onSave={() => console.log('Settings saved!')}
/>
// src/routes/admin/settings/+page.server.ts
import { Settings } from '@beeblock/svelar-settings';

export async function load() {
  const settings = await Settings.all();
  return { settings };
}
Svelar © 2026 · MIT License