SveltyCMS: System Settings Architecture
An overview of how dynamic settings are managed, cached, and synchronized, including language configuration and data preservation.
On this page
import { Callout } from “nextra/components”;
This document details the architecture of the SveltyCMS System Settings. The system is designed to be dynamic, performant, and secure, allowing administrators to manage the application’s runtime behavior without requiring code deployments.
Core Principles
-
Database as the Source of Truth: All dynamic system settings are stored in the database. This allows for real-time changes without needing to restart the server.
-
In-Memory Cache for Performance: For maximum performance, all settings are loaded from the database into a server-side in-memory cache at startup. All runtime requests read from this fast cache, avoiding database queries.
-
Filesystem for Defaults:
/src/routes/setup/seed.ts: Defines the initial, default values for all system settings and default roles. This provides a predictable fallback and is used to seed the database during the initial setup.- User roles are stored in the
auth_rolesdatabase collection and managed via the Access Management interface.
-
Reactive Client-Side Store: The
src/stores/global-settings.svelte.tsstore holds public settings and automatically updates in near real-time using a polling mechanism, ensuring a dynamic UI.
The Data Flow
SveltyCMS utilizes a two-tier data flow for settings: a high-frequency polling mechanism for public settings and a secure, batch-loading mechanism for the administrative dashboard.
Server-Side
- Batch Aggregation: The
/api/settings/allendpoint aggregates settings from all authorized groups into a single payload, reducing HTTP overhead during dashboard initialization. - Server Startup: The application loads all settings from the database into a fast, in-memory cache.
- Admin Update: An admin uses the UI to change a setting. The UI sends a
PUTrequest to the/api/settings/[group]endpoint. - Database Update: The API updates the setting in the database.
- Cache Invalidation: The API calls
invalidateSettingsCache()andloadSettingsFromDB()to immediately refresh the server-side cache with the new value. - Version Update: The API calls
updateVersion()to bump the settings version number. This signals to clients that settings have changed.
Client-Side (Public Settings)
- Initial Load: The client-side
global-settings.svelte.tsstore is populated with public settings on initial page load. - Polling for Versions: The store polls the
/api/settings/public/versionendpoint every 5 seconds. - Updating on Change: If the client detects a new version number, it automatically fetches the complete set of public settings from
/api/settings/publicand updates the store. This makes UI elements that depend on public settings (like the site name) reactive and update in near real-time.
The User Interface
The main UI for managing settings is located at /config/system-settings. It features:
- Optimized Batch Loading: Uses a single API call to load all authorized settings groups.
- Mobile-First Navigation: Horizontal scrolling tabs for small viewports.
- Sticky Action Bar: Floating footer providing “Save”, “Reset”, and “Export” actions.
- Sensitive Data Masking: Automatic masking of passwords and API keys.
- Searchable Interface: Filter settings groups by name or description.
- Real-time validation and unsaved changes indicators.
“Restart Required” Workflow
For critical settings that require a server restart, a clear workflow is implemented to ensure administrators are aware and can take action.
- State Tracking: When a setting group with
requiresRestart: trueis modified, the server sets arestartNeededflag. - Client-Side Polling: The main application layout polls the
/api/system/restart-requiredendpoint to check the status of this flag. - UI Notification: If a restart is needed, a prominent banner is displayed across the top of the application.
- Admin Action: For administrators, this banner includes a “Restart Now” button. Clicking this button sends a
POSTrequest to the secure/api/system/restartendpoint. - Graceful Restart: This endpoint is designed to integrate with a process manager like
pm2. It creates arestart.txtfile in the project root, which can be watched by the process manager to trigger a graceful restart of the application (e.g.,pm2 reload <app_name>).
Enterprise Scaling & Infrastructure
For high-traffic enterprise deployments, SveltyCMS utilizes a “Minimum Bootstrap” philosophy. This separates critical startup parameters from flexible runtime scaling settings.
“Minimum Bootstrap” Pattern
- Bootstrap Layer (
/config/private.ts): Reserved exclusively for the primary database connection strings and security essential (JWT secrets). This is the “static” foundation required for the CMS to boot. - Runtime Layer (
system_preferencesDB Table): Infrastructure scaling parameters like Read Replica URLs and Cloudflare CDN tokens are managed dynamically in the database.
This separation ensures that scaling operations (like adding a new geographic database replica) can be performed via the Admin Dashboard without touching environment variables or performing a full server restart.
Zero-Restart Hot Loading
SveltyCMS adapters support a configureReplicas() interface. When the SettingsService loads infrastructure settings from the database:
- It detects changes in the
DB_REPLICA_URLSJSON array. - It calls the database adapter’s
configureReplicas()method. - The adapter gracefully closes old connections and initializes new regional connection pools.
- The system immediately begins routing read queries to the new replicas based on the
x-svelty-regionheader.
Cloudflare CDN Integration
Scaling settings also include the SveltyCore CDN Bridge, which manages Cloudflare Cache Purging (Tag-based or Full). These tokens are stored securely in the private category of system settings, ensuring they are excluded from public API responses while remaining available to the CdnService on the server.
AI Configuration
SveltyCMS integrates with local and remote AI models to power advanced features like automated image tagging and the SveltyAgent assistant. These are configured in the AI group in System Settings.
USE_AI_TAGGING: Toggle to enable/disable AI-powered image analysis in the Media Gallery.AI_PROVIDER: Select betweenollama(local, privacy-first) oropenai(remote).OLLAMA_URL: The URL of your local Ollama instance (default:http://localhost:11434).AI_MODEL_VISION: The model used for vision tasks, such asllavaormoondream.
API Endpoints
GET /api/settings/[group]: Retrieves all settings for a specific group.PUT /api/settings/[group]: Updates one or more settings in a group.DELETE /api/settings/[group]: Resets all settings in a group to their default values.GET /api/settings/public: Retrieves all settings marked ascategory: 'public'.GET /api/settings/public/version: Gets the current version timestamp of the settings.GET /api/system/restart-required: Checks if a server restart is needed.POST /api/system/restart: Initiates the graceful restart process.
Configuration Management
Language Configuration Warnings
The Problem:
When you remove a language from the configuration (e.g., removing Spanish from ['en', 'de', 'fr', 'es']), the existing Spanish translations remain in the database but become inaccessible through the UI. If content is subsequently edited and saved, the widget system may overwrite the entire multilingual object, permanently deleting the Spanish translations.
Example Scenario:
// Initial configuration
AVAILABLE_CONTENT_LANGUAGES: ['en', 'de', 'fr', 'es']
// Database entry with all translations
{
title: {
en: "Hello World",
de: "Hallo Welt",
fr: "Bonjour le monde",
es: "Hola Mundo" // ← Spanish translation exists
}
}
// Admin removes 'es' from AVAILABLE_CONTENT_LANGUAGES
AVAILABLE_CONTENT_LANGUAGES: ['en', 'de', 'fr']
// Spanish data still in DB but UI no longer shows it
// User edits the entry in German, clicks Save
// Widget only knows about ['en', 'de', 'fr']
// After save - Spanish translation LOST FOREVER:
{
title: {
en: "Hello World",
de: "Hallo Welt (edited)",
fr: "Bonjour le monde"
// ❌ es: "Hola Mundo" - DELETED
}
}
Data Preservation Strategies:
-
Archive Before Removing (Recommended):
# Export all content before removing language npm run config:export # This creates backup with all translations intact # File: /config/sync/collections/[collection].json
2. **Database Backup:**
```bash
# MongoDB
mongodump --db=sveltycms --out=/backup/before-language-removal
# PostgreSQL
pg_dump sveltycms > backup_before_language_removal.sql
```
3. **Migration Script (Clean Removal):**
```typescript
// Remove Spanish translations from all entries
async function removeLanguageTranslations(languageToRemove: string) {
const collections = await db.content.nodes.getStructure("flat");
for (const collection of collections) {
const entries = await db.crud.findMany(collection.id, {});
for (const entry of entries) {
const updatedEntry = { ...entry };
// Remove language key from all translated fields
for (const [key, value] of Object.entries(updatedEntry)) {
if (typeof value === "object" && value !== null && languageToRemove in value) {
delete value[languageToRemove];
updatedEntry[key] = value;
}
}
await db.crud.update(collection.id, entry._id, updatedEntry);
}
}
console.log(`Removed all ${languageToRemove} translations`);
}
// Usage:
await removeLanguageTranslations("es");
// Now safe to remove 'es' from AVAILABLE_CONTENT_LANGUAGES
```
4. **Soft Delete (Keep in DB, Hide from UI):**
```typescript
// Instead of removing from AVAILABLE_CONTENT_LANGUAGES,
// add HIDDEN_CONTENT_LANGUAGES setting
{
AVAILABLE_CONTENT_LANGUAGES: ['en', 'de', 'fr'],
HIDDEN_CONTENT_LANGUAGES: ['es'], // ← Kept in DB but hidden
ARCHIVED_CONTENT_LANGUAGES: ['es'] // ← Mark as archived
}
// Widget logic:
const activeLanguages = AVAILABLE_CONTENT_LANGUAGES.filter(
lang => !HIDDEN_CONTENT_LANGUAGES.includes(lang)
);
```
**Restoring Lost Translations:**
If translations were accidentally deleted:
1. **From Export Backup:**
```bash
# Restore from most recent export before deletion
npm run config:import -- --file=/config/sync/backup-YYYY-MM-DD/
```
2. **From Database Backup:**
```bash
# MongoDB
mongorestore --db=sveltycms /backup/before-language-removal/sveltycms
# PostgreSQL
psql sveltycms < backup_before_language_removal.sql
```
3. **From Version Control:**
```bash
# If exports are version controlled
git checkout HEAD~5 -- config/sync/collections/*.json
npm run config:import
```
**Best Practice Workflow for Removing Languages:**
1. ✅ **Announce** removal to team (give notice period)
2. ✅ **Export** full configuration: `npm run config:export`
3. ✅ **Backup** database
4. ✅ **Run migration script** to clean translations (optional)
5. ✅ **Remove** language from `AVAILABLE_CONTENT_LANGUAGES`
6. ✅ **Test** on staging environment first
7. ✅ **Monitor** for issues after deployment
8. ✅ **Keep backups** for at least 30 days
<Callout type="info">
**Adding languages is always safe** - no existing data is affected. Only **removing** languages
poses data loss risks.
</Callout>
### Exporting (`config:export`)
When a full configuration export is run, the `ConfigExporter` service queries the `system_preferences` table and saves all non-secret settings to a file like `/config/sync/system/system.settings.json`.
<Callout type="warning">
**Security:** The export process must be configured to **exclude** secrets like `SMTP_PASSWORD`.
The `category: 'private'` property on a setting field ensures it is not included in public
exports.
</Callout>
### Importing (`config:import`)
When importing configuration to a new environment:
1. The `ConfigImporter` reads `system.settings.json` and populates the `system_preferences` table with the baseline configuration.
2. As a final step, the `ConfigImporter` calls `invalidateSettingsCache()`. This forces the application to reload its cache with the newly imported settings, bringing the entire system into a fully consistent state.
---
## Related
- [Architecture Overview](/docs/reference/architecture/index)
- [Security Overview](/docs/reference/security/index)
- [State Management](/docs/reference/architecture/state-management)