Settings Store
The Settings Store is a flexible system for storing configuration data with support for scoping, permissions, and validation. It allows plugins and the core system to store and retrieve arbitrary JSON data with fine-grained control over access and isolation.
It provides a robust, secure, and flexible system for managing configuration data in your Vendure application. Use it to store user preferences, plugin settings, feature flags, and any other settings data your application needs.
The APIs in this guide were introduced in Vendure v3.4
Overview
The Settings Store provides:
- Scoped Storage: Data can be scoped globally, per-user, per-channel, or with custom scope
- Permission Control: Fields can require specific permissions to access
- Validation: Custom validation functions for field values
- GraphQL API: Admin API for reading and writing values
- Service API: Programmatic access via the SettingsStoreService
- Automatic Cleanup: Scheduled task to remove orphaned entries
Settings Store vs Custom Fields
Settings fields share some similarities to custom fields, but the important differences are:
- Custom fields are attached to particular Vendure entities. Settings fields are not.
- Defining a custom field adds a new column in the database, whereas settings fields do not.
- Custom fields are reflected in corresponding GraphQL APIs and in the Admin UI & Dashboard UIs.
- Custom fields are statically typed, whereas settings fields store any kind of JSON-serializable data.
Settings fields are best suited to storing config-like values that are global in scope, or which configure data for a particular plugin.
Defining Settings Fields
Settings fields are defined in your Vendure configuration using the settingsStoreFields
option:
- Basic Example
- Advanced Example
import { VendureConfig, SettingsStoreScopes } from '@vendure/core';
export const config: VendureConfig = {
// ... other config
settingsStoreFields: {
dashboard: [
{
name: 'theme',
scope: SettingsStoreScopes.user,
},
{
name: 'companyName',
scope: SettingsStoreScopes.global,
}
]
}
};
import { VendureConfig, SettingsStoreScopes, Permission } from '@vendure/core';
export const config: VendureConfig = {
// ... other config
settingsStoreFields: {
dashboard: [
{
name: 'theme',
scope: SettingsStoreScopes.user,
},
{
name: 'tableFilters',
scope: SettingsStoreScopes.userAndChannel,
}
],
payment: [
{
name: 'stripeApiKey',
scope: SettingsStoreScopes.global,
readonly: true, // Cannot be modified via GraphQL API
requiresPermission: Permission.SuperAdmin,
validate: (value, injector, ctx) => {
if (typeof value !== 'string' || !value.startsWith('sk_')) {
return 'Stripe API key must be a string starting with "sk_"';
}
}
}
],
ui: [
{
name: 'welcomeMessage',
scope: SettingsStoreScopes.channel,
validate: async (value, injector, ctx) => {
if (typeof value !== 'string' || value.length > 500) {
return 'Welcome message must be a string with max 500 characters';
}
}
}
]
}
};
Field Configuration Options
Each field supports the following configuration options:
Option | Type | Description |
---|---|---|
name | string | The field name (combined with namespace to create full key) |
scope | SettingsStoreScopeFunction | How the field should be scoped (see scoping section) |
readonly | boolean | If true, field cannot be modified via GraphQL API |
requiresPermission | Permission | Permission[] | Permissions required to access this field |
validate | function | Custom validation function for field values |
Scoping
The Settings Store supports four built-in scoping strategies:
import { SettingsStoreScopes } from '@vendure/core';
// Global - single value for entire system
SettingsStoreScopes.global;
// User-specific - separate values per user
SettingsStoreScopes.user;
// Channel-specific - separate values per channel
SettingsStoreScopes.channel;
// User and channel specific - separate values per user per channel
SettingsStoreScopes.userAndChannel;
You can also create custom scope functions:
const customScope: SettingsStoreScopeFunction = ({ key, value, ctx }) => {
// Custom scoping logic
const env = process.env.NODE_ENV === 'production' ? 'prod' : 'dev';
return `env:${env}`;
};
export const config: VendureConfig = {
settingsStoreFields: {
myNamespace: [
{
name: 'customField',
// The value will be saved with the scope
// "env:prod" or "env:dev"
scope: customScope,
},
],
},
};
GraphQL API
The Settings Store provides GraphQL queries and mutations in the Admin API:
Queries
# Get a single value
query GetSettingsStoreValue($key: String!) {
getSettingsStoreValue(key: $key)
}
# Get multiple values
query GetSettingsStoreValues($keys: [String!]!) {
getSettingsStoreValues(keys: $keys)
}
Mutations
Any kind of JSON-serializable data can be set as the value. For example: strings, numbers, arrays, or even deeply-nested objects and arrays.
# Set a single value
mutation SetSettingsStoreValue($input: SettingsStoreInput!) {
setSettingsStoreValue(input: $input) {
key
result
error
}
}
# Set multiple values
mutation SetSettingsStoreValues($inputs: [SettingsStoreInput!]!) {
setSettingsStoreValues(inputs: $inputs) {
key
result
error
}
}
By default, the Settings Store is not exposed in the Shop API.
However, you can expose this functionality via a custom mutations & queries
that internally use the SettingsStoreService
(see next section).
Usage Examples
- Single Value
- Multiple Values
// Setting a value
const result = await adminClient.query(gql`
mutation SetSettingsStoreValue($input: SettingsStoreInput!) {
setSettingsStoreValue(input: $input) {
key
result
error
}
}
`, {
input: {
key: 'dashboard.theme',
value: 'dark'
}
});
// Getting a value
const theme = await adminClient.query(gql`
query GetSettingsStoreValue($key: String!) {
getSettingsStoreValue(key: $key)
}
`, {
key: 'dashboard.theme'
});
// Setting multiple values
const results = await adminClient.query(gql`
mutation SetSettingsStoreValues($inputs: [SettingsStoreInput!]!) {
setSettingsStoreValues(inputs: $inputs) {
key
result
error
}
}
`, {
inputs: [
{ key: 'dashboard.theme', value: 'dark' },
{ key: 'dashboard.language', value: 'en' }
]
});
// Getting multiple values
const settings = await adminClient.query(gql`
query GetSettingsStoreValues($keys: [String!]!) {
getSettingsStoreValues(keys: $keys)
}
`, {
keys: ['dashboard.theme', 'dashboard.language']
});
// Returns: {"dashboard.theme": "dark", "dashboard.language": "en"}
Using the SettingsStoreService
For programmatic access within plugins or services, use the SettingsStoreService:
- Basic Usage
- Advanced Usage
import { Injectable } from '@nestjs/common';
import { SettingsStoreService, RequestContext } from '@vendure/core';
@Injectable()
export class MyService {
constructor(private settingsStoreService: SettingsStoreService) {}
async getUserTheme(ctx: RequestContext): Promise<string> {
const theme = await this.settingsStoreService.get<string>('dashboard.theme', ctx);
return theme || 'light'; // Default fallback
}
async setUserTheme(ctx: RequestContext, theme: string): Promise<boolean> {
const result = await this.settingsStoreService.set('dashboard.theme', theme, ctx);
return result.result;
}
}
import { Injectable } from '@nestjs/common';
import { SettingsStoreService, RequestContext } from '@vendure/core';
interface DashboardSettings {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
}
@Injectable()
export class DashboardService {
constructor(private settingsStoreService: SettingsStoreService) {}
async getDashboardSettings(ctx: RequestContext): Promise<DashboardSettings> {
const settings = await this.settingsStoreService.getMany([
'dashboard.theme',
'dashboard.language',
'dashboard.notifications'
], ctx);
return {
theme: settings['dashboard.theme'] || 'light',
language: settings['dashboard.language'] || 'en',
notifications: settings['dashboard.notifications'] ?? true,
};
}
async updateDashboardSettings(
ctx: RequestContext,
settings: Partial<DashboardSettings>
): Promise<{ success: boolean; errors: string[] }> {
const updates: Record<string, any> = {};
if (settings.theme) updates['dashboard.theme'] = settings.theme;
if (settings.language) updates['dashboard.language'] = settings.language;
if (settings.notifications !== undefined) {
updates['dashboard.notifications'] = settings.notifications;
}
const results = await this.settingsStoreService.setMany(updates, ctx);
return {
success: results.every(r => r.result),
errors: results.filter(r => !r.result).map(r => r.error || 'Unknown error')
};
}
}
SettingsStoreService Methods
Method | Description |
---|---|
get<T>(key, ctx) | Get a single value with optional type parameter |
getMany(keys, ctx) | Get multiple values efficiently in a single query |
set<T>(key, value, ctx) | Set a value with structured result feedback |
setMany(values, ctx) | Set multiple values with individual result feedback |
getFieldDefinition(key) | Get the field configuration for a key |
Orphaned Entries Cleanup
When field definitions are removed from your configuration, the corresponding database entries become "orphaned". The Settings Store includes an automatic cleanup system to handle this.
Manual Cleanup
You can also perform cleanup manually via the service:
// Find orphaned entries
const orphanedEntries = await settingsStoreService.findOrphanedEntries({
olderThan: '7d',
maxDeleteCount: 1000,
});
// Clean them up
const cleanupResult = await settingsStoreService.cleanupOrphanedEntries({
olderThan: '7d',
dryRun: false,
batchSize: 100,
});
Best Practices
- Use appropriate scoping: Choose the most restrictive scope that meets your needs
- Implement validation: Add validation for fields that accept user input
- Set permissions: Use
requiresPermission
for sensitive configuration data - Mark sensitive fields readonly: Prevent GraphQL modification of critical settings
- Consider value size limits: Large values can impact performance
Examples
Plugin Integration
import { VendurePlugin, SettingsStoreScopes } from '@vendure/core';
@VendurePlugin({
configuration: config => {
config.settingsStoreFields = {
...config.settingsStoreFields,
myPlugin: [
{
name: 'apiEndpoint',
scope: SettingsStoreScopes.global,
requiresPermission: Permission.UpdateSettings,
validate: value => {
if (typeof value !== 'string' || !value.startsWith('https://')) {
return 'API endpoint must be a valid HTTPS URL';
}
},
},
{
name: 'userPreferences',
scope: SettingsStoreScopes.userAndChannel,
},
],
};
return config;
},
})
export class MyPlugin {}
Frontend usage
import React from 'react';
import { useQuery, useMutation } from '@apollo/client';
import gql from 'graphql-tag';
const GET_THEME = gql`
query GetTheme {
getSettingsStoreValue(key: "dashboard.theme")
}
`;
const SET_THEME = gql`
mutation SetTheme($theme: String!) {
setSettingsStoreValue(input: { key: "dashboard.theme", value: $theme }) {
result
error
}
}
`;
export function ThemeSelector() {
const { data } = useQuery(GET_THEME);
const [setTheme] = useMutation(SET_THEME, {
refetchQueries: [GET_THEME],
});
const currentTheme = data?.getSettingsStoreValue || 'light';
const handleThemeChange = (theme: string) => {
setTheme({ variables: { theme } });
};
return (
<select value={currentTheme} onChange={e => handleThemeChange(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
);
}