# How Ghost's Settings Cache System Works and Handles Invalidation

> Discover how Ghost's settings cache system works and handles invalidation. Learn how the CacheManager optimizes performance by reducing database lookups.

- Repository: [Ghost/Ghost](https://github.com/TryGhost/Ghost)
- Tags: internals
- Published: 2026-05-18

---

**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/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/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 rows
- **`this.settingsOverrides`** – Runtime configuration overrides from environment variables
- **`this.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/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/core/boot.js).

### Boot Sequence

During startup, the settings service invokes `settingsCache.init(events, settingsCollection, calculatedFields, cacheStore, overrides)` with the following parameters:

1. **Events emitter** – For registering invalidation listeners
2. **Settings collection** – A Bookshelf collection of all current settings rows
3. **Calculated fields** – Array of derived setting definitions
4. **Cache store** – The storage backend (typically from `@tryghost/settings-cache` package)
5. **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`](https://github.com/TryGhost/Ghost/blob/main/cache-manager.js) register listeners for three specific events:

- `settings.edited`
- `settings.added`
- `settings.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.

```javascript
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`](https://github.com/TryGhost/Ghost/blob/main/public.js), calling `_doGet` for each key while also evaluating special calculated fields like `transistor_portal_enabled` (lines 26-44).

```javascript
// 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:

1. Invokes `this.settingsCache.reset()` to clear the underlying store
2. Removes all registered event listeners
3. Requires calling `init()` again to repopulate from the database

```javascript
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

```javascript
const settingsCache = require('../../shared/settings-cache');

const blogTitle = settingsCache.get('title');
console.log('Site Title:', blogTitle);

```

### Example 2: Access Public Settings Payload

```javascript
const settingsCache = require('../../shared/settings-cache');

const publicSettings = settingsCache.getPublic();
console.log('Frontend configuration:', publicSettings);

```

### Example 3: Force Manual Cache Refresh

```javascript
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`](https://github.com/TryGhost/Ghost/blob/main/core/shared/settings-cache/calculated-fields.js):

```javascript
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 `CacheManager` instance serves the entire Ghost process, exported from [`core/shared/settings-cache/index.js`](https://github.com/TryGhost/Ghost/blob/main/core/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`, and `settings.deleted` ensure 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 `settingsOverrides` map 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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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`](https://github.com/TryGhost/Ghost/blob/main/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.