How Ghost's Settings Cache System Works and Handles Invalidation
Ghost implements an in-memory settings cache system through the CacheManager class to eliminate database lookups on every request, automatically invalidating entries via event listeners that respond to settings.edited, settings.added, and settings.deleted events.
The settings cache system in TryGhost/Ghost stores all site-wide configuration in memory to avoid costly database queries during high-traffic requests. This singleton-based architecture ensures consistent performance across the application while maintaining data consistency through automatic synchronization with the underlying database. Understanding how this system initializes, stores, and invalidates settings is essential for debugging configuration issues or extending Ghost's core functionality.
Core Architecture of the Settings Cache System
The settings cache system centers around the CacheManager class defined in [core/shared/settings-cache/cache-manager.js](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/shared/settings-cache/cache-manager.js). This module exports a singleton instance from [core/shared/settings-cache/index.js](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/shared/settings-cache/index.js) that maintains several critical data structures:
this.settingsCache– The underlying key/value store holding database rowsthis.settingsOverrides– Runtime configuration overrides from environment variablesthis.publicSettings– A map declaring which keys are safe to expose to the frontend- Calculated fields array – Derived settings that depend on other values
Public Settings Map
The cache references [core/shared/settings-cache/public.js](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/shared/settings-cache/public.js) to determine which database keys should be exposed publicly. This file maps internal keys like title and accent_color to their external API representations, ensuring sensitive configuration remains server-side only.
Event-Driven Invalidation
Ghost's internal event emitter (located in core/server/lib/common/events.js) publishes CRUD events whenever the settings table changes. The CacheManager subscribes to these events during initialization to maintain cache consistency without manual intervention.
Settings Cache System Lifecycle and Initialization
The cache follows a strict initialization sequence triggered during Ghost's boot process in core/boot.js.
Boot Sequence
During startup, the settings service invokes settingsCache.init(events, settingsCollection, calculatedFields, cacheStore, overrides) with the following parameters:
- Events emitter – For registering invalidation listeners
- Settings collection – A Bookshelf collection of all current settings rows
- Calculated fields – Array of derived setting definitions
- Cache store – The storage backend (typically from
@tryghost/settings-cachepackage) - Overrides – Configuration values that supersede database entries
Population Phase
If settingsCollection.models exists, the system iterates through each model and calls _updateSettingFromModel, which executes this.set(key, model.toJSON()) to populate the cache. Configuration overrides are simultaneously stored in this.settingsOverrides for read-time merging.
Event Binding
Lines 73-77 in cache-manager.js register listeners for three specific events:
settings.editedsettings.addedsettings.deleted
When any of these events fire, _updateSettingFromModel updates the specific cache entry immediately.
Calculated Fields Handling
Lines 78-84 handle derived settings (such as transistor_portal_enabled). These fields declare dependents—source settings that influence their value. For each dependent, the cache registers a settings.<dependent>.edited listener that triggers _updateCalculatedField to recompute and store the derived value.
Accessing Cached Settings
The settings cache system provides three primary methods for retrieving configuration values.
Retrieving Individual Settings
The get(key, options) method returns resolved values, automatically parsing JSON and handling type coercion for booleans, strings, and numbers. Pass {resolve: false} to receive the raw database row structure.
const settingsCache = require('../../shared/settings-cache');
// Returns parsed value: "My Ghost Blog"
const blogTitle = settingsCache.get('title');
// Returns raw row object with metadata
const rawTitle = settingsCache.get('title', {resolve: false});
Public Settings API
getPublic() constructs the payload sent to frontend clients and theme rendering engines. This method iterates through the public settings map defined in public.js, calling _doGet for each key while also evaluating special calculated fields like transistor_portal_enabled (lines 26-44).
// Returns object with only public-safe settings
const publicSettings = settingsCache.getPublic();
// { site_uuid: "...", title: "...", description: "...", accent_color: "..." }
Internal Updates
set(key, value) writes a cloned copy directly into the underlying store. This method is reserved for internal use by the invalidation handlers and initialization routines.
Cache Invalidation and Refresh Mechanisms
The settings cache system employs event-driven invalidation to ensure consistency between the in-memory store and the database.
Automatic Invalidation Events
| Event | Invalidation Behavior |
|---|---|
settings.edited |
_updateSettingFromModel overwrites the cached entry with the updated model data |
settings.added |
New settings are inserted into the cache via the same update method |
settings.deleted |
The deleted model overwrites the cache entry, effectively clearing that key |
Dependent edited (e.g., settings.title.edited) |
Triggers _updateCalculatedField for any derived setting depending on that key |
Full Reset Capability
When the server requires a complete cache reload without restarting the process, CacheManager.reset(events) (lines 93-103) performs the following:
- Invokes
this.settingsCache.reset()to clear the underlying store - Removes all registered event listeners
- Requires calling
init()again to repopulate from the database
const settingsCache = require('../../shared/settings-cache');
const events = require('../../shared/events');
// Clear everything and unbind listeners
settingsCache.reset(events);
// Re-initialize from scratch
const Settings = require('../../models/setting');
const settingsCollection = await Settings.fetchAll();
const cacheStore = require('@tryghost/settings-cache').createMemoryCache();
settingsCache.init(events, settingsCollection, [], cacheStore, {});
Practical Code Examples
Example 1: Retrieve a Single Setting
const settingsCache = require('../../shared/settings-cache');
const blogTitle = settingsCache.get('title');
console.log('Site Title:', blogTitle);
Example 2: Access Public Settings Payload
const settingsCache = require('../../shared/settings-cache');
const publicSettings = settingsCache.getPublic();
console.log('Frontend configuration:', publicSettings);
Example 3: Force Manual Cache Refresh
const events = require('../../shared/events');
const settingsCache = require('../../shared/settings-cache');
// Reset and reinitialize (useful after direct database manipulation)
settingsCache.reset(events);
Example 4: Calculated Field Definition
Calculated fields like transistor_portal_enabled are defined in core/shared/settings-cache/calculated-fields.js:
module.exports = [
{
key: 'transistor_portal_enabled',
dependents: ['transistor', 'portal_enabled'],
getSetting: () => {
const transistor = settingsCache.get('transistor');
const portal = settingsCache.get('portal_enabled');
return Boolean(transistor) && Boolean(portal);
}
}
];
Summary
- Singleton Architecture: One
CacheManagerinstance serves the entire Ghost process, exported fromcore/shared/settings-cache/index.js - Lazy Resolution: The
get()method handles type coercion and JSON parsing automatically, with options to return raw database rows - Event-Driven Consistency: Listeners for
settings.edited,settings.added, andsettings.deletedensure the cache reflects database changes immediately without polling - Calculated Fields: Derived settings automatically recompute when their dependencies change through the dependent event system
- Runtime Overrides: The
settingsOverridesmap allows configuration injection without database writes, merged at read-time via the cache layer
Frequently Asked Questions
How does Ghost's settings cache system prevent stale data?
The cache prevents stale data by subscribing to Bookshelf model events in core/server/lib/common/events.js. Whenever a setting is edited, added, or deleted through Ghost's model layer, the corresponding event triggers _updateSettingFromModel, which immediately overwrites the affected cache entry with fresh data from the database.
What triggers invalidation in the Ghost settings cache?
Invalidation triggers include settings.edited events for updates, settings.added for new entries, and settings.deleted for removals. Additionally, calculated fields invalidate when their declared dependents publish edit events (e.g., settings.transistor.edited triggers recalculation of transistor_portal_enabled).
How are calculated settings like transistor_portal_enabled handled?
Calculated settings are defined in the calculated-fields configuration with a dependents array and a getSetting() function. The CacheManager registers specific listeners for each dependent key (lines 78-84 in cache-manager.js). When any dependent changes, _updateCalculatedField executes the getter function and stores the result, ensuring derived values remain consistent with their source data.
Can I manually reset the settings cache without restarting Ghost?
Yes, call settingsCache.reset(events) to clear the underlying store and remove all event listeners. This method (implemented in lines 93-103 of cache-manager.js) requires subsequently calling settingsCache.init() with a fresh settings collection to repopulate the cache. This approach is useful during migrations or when direct database modifications require cache synchronization.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →