Settings are a general way for packages to persist and store data without being attached to a document.
Official Documentation
Legend
Setting.defineSchema // `.` indicates static method or property
ClientSettings#register // `#` indicates instance method or property
game.settings.register // The ClientSettings class is instantiated as part of the `game` object
Settings, like flags, are a way for packages to store and persist data. Unlike flags, Settings are not tied to a specific document.
For the vast majority of use-cases, settings are intended to be modified by a UI, either a Menu or within the Module Settings panel itself. These settings are intended to be used to modify the functionality of a package, rather than store arbitrary data for that module or system.
The following elements are crucial to understanding settings.
Settings have a scope field which indicates if it's part of the device's localStorage (scope: client) or if it should be stored in the world's database (scope: world). Starting in v13, scope: user is also available, storing the setting value for the specific User across any devices they might use.
If you are on a version older than v13 and you wish to store data specific to a user, consider instead storing the data as a flag on the user document. Alternatively, store the data as part of an object in the setting.
Client settings are always editable by any user, as they are device-specific. This works well for display-based settings.
World settings have a global permission level ("Modify Configuration Settings") that is shared with the ability to enable or disable modules. By default, only Assistant GMs and Game Masters can edit world settings. This is a critical limitation that may require sockets to work around.
The ClientSettings are a singleton class instantiated as part of the game object.
See Setting Types below for examples about the different types of settings that can be registered.
Settings should be registered during the
inithook.
All settings must be registered before they can be set or accessed. This needs to be done with game.settings.register, with game.settings being an instance of ClientSettings.
/*
* Create a custom config setting
*/
game.settings.register('myModuleName', 'mySettingName', {
name: 'My Setting',
hint: 'A description of the registered setting and its behavior.',
scope: 'world', // "world" = sync to db, "client" = local storage
config: true, // false if you dont want it to show in module config
type: Number, // You want the primitive class, e.g. Number, not the name of the class as a string
default: 0,
onChange: value => { // value is the new value of the setting
console.log(value)
},
requiresReload: true, // true if you want to prompt the user to reload
/** Creates a select dropdown */
choices: {
1: "Option Label 1",
2: "Option Label 2",
3: "Option Label 3"
},
/** Number settings can have a range slider, with an optional step property */
range: {
min: 0,
step: 2,
max: 10
},
/** "audio", "image", "video", "imagevideo", "folder", "font", "graphics", "text", or "any" */
filePicker: "any"
});
name and hint, and the labels in choices are localized by the setting configuration application on render, so you can register settings in init and just pass a localizable string for those valuesconfig defaults to undefined which behaves the same as falserequiresReload is useful for settings that make changes during the init or setup hooks.scope defaults to "client"type for complex settings that need data validation.filePicker restricts what kinds of files can be chosen for the settingThe type of a setting is expected to be a constructor which is used when the setting's value is gotten. The 'normal' primitive constructors cover all basic use cases:
You can use fundamental language constructs as types
There's also some lesser used primitive types that are nevertheless eligible
It is possible however to leverage this functionality to do some advanced data manipulation with a complex setting object during the get. Doing so has some gotchas surrounding the onChange callback.
class SomeClass {
constructor(parsedJson) {
this.merged = parsedJson?.foo + parsedJson?.bar;
this.foo = parsedJson?.foo;
this.bar = parsedJson?.bar;
}
}
game.settings.register('myModuleName', 'customClassSetting', { type: SomeClass });
game.settings.set('myModuleName', 'customClassSetting', {foo: 'foosius', bar: 'whatever'});
game.settings.get('myModuleName', 'customClassSetting').merged; // 'foosiuswhatever'
As an even more advanced use case, you could pass a DataModel as a setting to provide advanced validation; the type casting has a special case from these where it calls YourDataModel.fromSource.
When registering a setting, instead of passing a hard-coded string to name or hint, it is possible to pass a localization path to support translations. Both name and hint are run through game.i18n.localize before being displayed in the Setting UI.
Settings with
scope: worldcannot besetuntil thereadyhook.
A setting's value can be set with game.settings.set. It's important to note that a scope: world setting can only be set by a user with the "Modify Configuration Settings" permission (by default this is only Game Master and Assistant GM users), while scope: client settings will only persist on the user's local machine.
const whateverValue = 'foo';
game.settings.set('myModuleName','myModuleSetting', whateverValue);
Easily handled data:
- Objects and Arrays which do not contain functions
- Strings
- Numbers
- Booleans
A setting's value is stringified and stored as a string. This limits the possible values for a setting to anything which can survive a JSON.stringify() and subsequent JSON.parse().
Note that JSON.stringify will prefer to use a value's toJSON() method if one is available, all Foundry documents have such a method which strips the document back to its base data.
If you wish to improve validation when updating a complex setting, you should consider a data model or data field. If you're just using String or Number, it will run the new value through those primitives first before storing to the database (e.g. if the setting is type: Number, and someone passes set(scope, key, "5"), the setting will run Number("5") to cast the type). StringField and NumberField will accomplish similar casting behavior but also allow further refinements, such as whether a blank string is allowed or whether to enforce that the number is an integer.
Settings can be read with game.settings.get.
const someVariable = game.settings.get('myModuleName','myModuleSetting');
console.log(someVariable); // expected to be 'foo'
Unless a setting has actively been saved to the world database with a call to game.settings.set, it will fill in with the registered default. This means that if you update the default, it will automatically apply to not only new users but also current ones. This can be useful, but also means that you can't rely on a setting's value to detect "old" users in the caes of a setting that is tracking things like previous module versions if you aren't actively creating a database entry.
One way to check if there's a database-backed value is to call game.settings.storage.get("world").getSetting, which accesses the actual world collection of setting documents (comparable to game.actors). If that returns undefined, there's no underlying DB entry for the setting and it's just going to use the default. Note that the key is the concatenated namespace and settingName, e.g. core.compendiumConfiguration.
When getting a setting's value, the type of the setting registered is used by Core as a constructor for the returned value.
Example:
game.settings.register('myModuleName', 'myNumber', { type: Number });
game.settings.set('myModuleName', 'myNumber', 'some string');
game.settings.get('myModuleName', 'myNumber'); // NaN
For more information on the basic primitive constructors and how they convert values, this article has a good overview.
On the backend, Settings are fairly simple documents; they have an _id, key, value, and _stats field. They are the only document type to not have a flags field. Unlike every other primary document, their world collection is not a property of the Game class directly; instead, game.settings accesses the singleton instance of ClientSettings, which then has the actual WorldSettings instance as a sub-property. This is in part because there are actually two places to store settings; WorldSettings is shared database, but localStorage provides per-client settings separate from Foundry's normal document-based DB operations.
Where settings registration comes in is providing safeguards for the returned values
get and set operations check if a setting has been registeredtype gets used to cast the JSON stringified value of the Setting document, which is returned by the get operationThe other pieces of the registration are used by the SettingsConfig application for config: true; if you provide a DataField instance for the type, it will call that field's toInput function, and then appropriately label with the name and hint properties.
Here are some tips and tricks for working with settings.
There is no hook for when a setting changes, instead an onChange callback must be provided during registration (You of course could run Hooks.call() in that callback). This callback is fired after the setting has been set, meaning a settings.get inside this callback will return the new value of the setting, not the old.
The onChange callback does not fire if there are no differences between the value being set and the current value returned from settings.get.
This callback will fire on all clients for world scoped settings, but only locally for client scoped settings. Its only argument is the raw value of the setting that was set.
Because this
valueargument is not necessarily the same value that would be returned fromsettings.get, it is safer to get the new value in this callback if you intend to operate on it.
This section will provide snippets and screenshots for the various common setting configurations. These snippets have the minimum number of options required to display the setting and may require tweaking for your specific use case. They also make use of foundry.data.fields to make it easier to further customize the type behavior.

game.settings.register('core', 'myCheckbox', {
name: 'My Boolean',
config: true,
type: new foundry.data.fields.BooleanField(),
});
game.settings.get('core', 'myCheckbox'); // false

game.settings.register('core', 'myInput', {
name: 'My Text',
config: true,
type: new foundry.data.fields.StringField(),
});
game.settings.get('core', 'myInput'); // 'Foo'

game.settings.register('core', 'mySelect', {
name: 'My Select',
config: true,
type: new foundry.data.fields.StringField({
choices: {
"a": "Option A",
"b": "Option B"
},
}),
});
game.settings.get('core', 'mySelect'); // 'a'
The key of the choices object is what is stored in the setting when the user selects an option from the dropdown.
The values of the choices object are automatically run through game.i18n.localize before being displayed in the Setting UI.

game.settings.register('core', 'myNumber', {
name: 'My Number',
config: true,
type: new foundry.data.fields.NumberField(),
});
game.settings.get('core', 'myNumber'); // 1

game.settings.register('core', 'myRange', {
name: 'My Number Range',
config: true,
type: new foundry.data.fields.NumberField({
min: 0, max: 100, step: 10,
initial: 0, nullable: false
}),
});
game.settings.get('core', 'myRange'); // 50

game.settings.register('core', 'myFile', {
name: 'My File',
config: true,
type: String,
filePicker: true,
});
game.settings.get('core', 'myFile'); // 'path/to/file'
The following can be given to the filePicker option to change the behavior of the File Picker UI when it is opened. These are useful if you need the user to select only an image for instance.
'audio' - Displays audio files only'image' - Displays image files only'video' - Displays video files only'imagevideo' - Displays images and video files'folder' - Allows selection of a directory (beware, it does not enforce directory selection)'font' - Display font files only'graphics' - Display 3D files only'text' - Display text files only'any' - No different than trueIf the setting is registered with either the default filePicker: true or filePicker: 'folder' it is possible for a user to select a directory instead of a file. This is not forced however and the user might still select a file.
When saved, the directory path is the only string which is saved and does not contain information about the source which the directory was chosen from. Without strict assumptions and checking those assumptions, this kind of setting has a high chance of causing errors or unexpected behavior (e.g. creating a folder on the user's local storage instead of their configured S3 bucket).
Sometimes a package is more complex than a few settings will allow a user to configure. In these cases it is recommended to register a settings menu with game.settings.registerMenu, and manage the configuration with a FormApplication or Dialog. Note that registerMenu does not register a setting by itself, simply a menu button.
Menus work best when used in conjunction with a registered setting of type Object which has been set to config: false. A menu could also be used to control many individual settings if desired.
game.settings.registerMenu("myModule", "mySettingsMenu", {
name: "My Settings Submenu",
label: "Settings Menu Label", // The text label used in the button
hint: "A description of what will occur in the submenu dialog.",
icon: "fas fa-bars", // A Font Awesome icon used in the submenu button
type: MySubmenuApplicationClass, // A FormApplication subclass
restricted: true // Restrict this submenu to gamemaster only?
});
game.settings.register('myModuleName', 'myComplexSettingName', {
scope: 'world', // "world" = sync to db, "client" = local storage
config: false, // we will use the menu above to edit this setting
type: Object,
default: {}, // can be used to set up the default structure
});
/**
* For more information about FormApplications, see:
* https://hackmd.io/UsmsgTj6Qb6eDw3GTi5XCg
*/
class MySubmenuApplicationClass extends FormApplication {
// lots of other things...
getData() {
return game.settings.get('myModuleName', 'myComplexSettingName');
}
_updateObject(event, formData) {
const data = expandObject(formData);
game.settings.set('myModuleName', 'myComplexSettingName', data);
}
}
FormApplications in particular allow you to run any logic you want during the _updateObject method. This could be leveraged to accomplish many things:
_updateObject), you could run validation on user inputs before saving them to the setting value.Here are some common issues when working with settings.
Happens when game.settings.register is called with an invalid first argument. This first argument is intended to be the id or name of an active package.
game.settings.get and .set both throw this error if the requested setting has not been registered yet.
Foundry has a hard limit in ClientSettings##setWorld that prevents modifying the game settings prior to ready. This means any "module setup" type dialogs should wait until Hooks.once("ready".