-
Notifications
You must be signed in to change notification settings - Fork 0
JSON Structure Guide
This document provides a comprehensive guide to the JSON structure used by MCM Extender. For practical examples, refer to the MCMExtenderExample.json file included in the Example Menu downloads.
- Metadata
- Submenus
- Options
- Variable Objects
- Scripting Integration and Events
- Translations and Localization
- Best Practices & Common Pitfalls
- Complete Minimal Example
| Symbol | Name | Description |
|---|---|---|
| ✅ | Required | Element must be included. |
| 🔀 | Mutually Exclusive | Include one of the mutually exclusive elements. |
| ➕ | Optional | Element can be optionally included. |
| ❌ | Unsupported | Element is not supported. |
The root of the JSON file must contain metadata to provide information about the mod the menu applies to.
| Key | Type | Required | Description |
|---|---|---|---|
"modName" |
String | ✅ | A unique internal identifier for the mod. |
"displayName" |
String | ✅ | The mod name displayed in the MCM sidebar. |
"saveFile" |
String | ✅ | Path to the config file where settings are saved, relative to Data\config\. |
"minMCMVersion" |
Float | ✅ | The minimum required MCM Extender version. |
"requirements" |
Array | ➕ | Defines conditions for the menu to be displayed. See Requirements. |
"submenus" |
Map | ✅ | Contains all submenus for the mod. See Submenus. |
-
"plugin": Check if a mod ESM or ESP is enabled via IsModLoaded -
"file": Check if a file is exists via FileExists. Path is relative to theData\folder. -
"folder": Check if a folder is exists via FileExists. Path is relative to theData\folder. -
"dll": Check minimum xNVSE plugin version via GetPluginVersion. Requires numeric"version"element. -
"nvse": Check minimum xNVSE version via GetNVSEVersionFull. Requires numeric"version"element.
"requirements": [
{ "type": "plugin", "file": "SomeMod.esp" },
{ "type": "file", "file": "Config/SomeMod.ini" },
{ "type": "folder", "file": "Menus/SomeMod" },
{ "type": "dll", "file": "JohnnyGuitarNVSE", "version": 510 },
{ "type": "nvse", "version": 6.40 }
]You can also nest an array of requirements. This creates an "OR" block; the requirement is met if at least one of the conditions inside the nested array is true. This allows a single menu file to support multiple mod configurations.
"requirements": [
[
{ "type": "plugin", "file": "SomeMod.esp" },
{ "type": "file", "file": "NVSE/plugins/scripts/ln_SomeMod.txt" }
]
]You can use a combination of the regular requirements maps and nested arrays for more complex logic.
"requirements": [
{ "type": "plugin", "file": "SomeMod.esp" },
[
{ "type": "plugin", "file": "SomeOtherMod.esp" },
{ "type": "file", "file": "NVSE/plugins/scripts/ln_SomeOtherMod.txt" }
]
]Each menu is populated with submenus objects, sorted by consecutive string integer keys (e.g., "1", "2").
ℹ️ Note: The MCM framework limits each menu to a maximum of 10 submenus (from key
"1"to"10").
Submenu objects can include the following key-value pairs:
| Key Name | Value Type | Required | Description | Extra Info |
|---|---|---|---|---|
"columns" |
Int (1/2) | ✅ | Number of columns for the submenu page, columns are populated form left to right, top to bottom. | -- |
"listTitle" |
String | ✅ | Title of the submenu shown in the list on the left. | -- |
"pageTitle" |
String | ✅ | Title shown at the top of the submenu page. | -- |
"options" |
Map | ✅ | Map containing all the individual options for this submenu. | Submenu Options |
"enable" |
Int (0/1) | 🔀 | Controls submenu visibility. | Submenu Visibility |
"state" |
Object | 🔀 | Controls submenu visibility. | Submenu Visibility |
"active" |
Int (0/1) | ➕ | Marks the submenu as the default page to open, defaulting to submenu in the map if not included. | -- |
"requirements" |
Array | ➕ | Requirements array, submenu will only appear if requirements are met. | Requirements |
Include either the "enable" or "state" element to control a submenus visibility state.
If both options are accidentally included the "state" element will always take priority.
-
"enable": Controls the submenus's visibility.-
0: Hidden. The submenus is not visible. -
1: Enabled. The submenus is visible and can be interacted with.
-
-
"state"Preforms an equality check with a Variable Object and numeric"value".
"enable": 1,
"state": { "form": "SomeQuest", "numVar": "SomeVariable", "value": 1 },To create a menu with a single page and no sidebar list, define only one submenu object using the key "0".
"submenus" : {
"0": { ... }
}To create a menu with multiple pages accessible from the sidebar, add submenu objects with consecutively numbered string keys (e.g., "1", "2", "3").
You can optionally include a submenu with the key "0" to serve as a main or landing page. This page is only shown on menu open and cannot be returned to after selecting another submenu from the sidebar.
"submenus" : {
"0": { ... },
"1": { ... },
"2": { ... },
}Inside each submenu object, the "options" map lists all the individual setting controls. These are also sorted by string integer keys ("1", "2", etc.), which directly correspond to their display order in the MCM. You can skip numbers to create more complex layouts with empty spaces. These integer keys have a minimum value of "1".
ℹ️ Note: The MCM framework limits each submenu to a maximum of 36 options (from key
"1"to"36").
Each option object defines a single setting in the MCM. It has several possible elements depending on its type.
| Type | Name | Description |
|---|---|---|
0 |
Header/Image | A non-interactive display element. By default, it displays the "title" string in a large font. You can override this to display a .dds image file instead by using the "image" object. |
1 |
Dropdown | List of selectable string options. Supports up to 10 list entries. |
2 |
Integer Slider | Slider for selecting a whole number within a defined range. |
2.5 |
Float Slider | Slider for selecting a floating-point number within a defined range. |
3 |
Keybind | Control that prompts the user to press a key, returning a DirectX scancode. |
4 |
On/Off Toggle | Toggle that switches between "On" and "Off" strings. |
5 |
Checkbox | Toggle that uses a checkmark box. It is functionally identical to Type 4. |
6 |
String Toggle | Toggle that switches between custom "textOn" and "textOff" strings. |
7 |
Static Text | Non-interactive text label that displays a string. |
8 |
Multi-Slider | Single option that opens a slider menu to configure 2 or 3 separate numerical values at once. |
9 |
Color Picker | Displays a color swatch and opens a HSV Color Picker to set three R, G, and B values from 0-255. |
Option objects can include the following key-value pairs.
| Key Name | Value Type | Option Type | Required | Var Support | Description | Extra Info |
|---|---|---|---|---|---|---|
"type" |
Int/Float | Any | ✅ | ✅ | Option type | Option Types |
"title" |
String | Any | ✅ | ✅ | Display text | -- |
"enable" |
Int (0/1/2) | Any | 🔀 | ✅ | Controls option interactivity | Option Visibility |
"state" |
Object | Any | 🔀 | ❌ | Controls option interactivity | Option Visibility |
"mouse" |
String | Any | ➕ | ✅ | Mouseover help text | -- |
"refresh" |
Int (0/1) | Any | ➕ | ✅ | Forces the active menu to refresh | Option Refresh |
"indent" |
Float | Any | ➕ | ✅ | Indents option by float magnitude | -- |
"brightnessMul" |
Float | Any | ➕ | ✅ | Apply text brightness multiplier | -- |
"vars" |
Array |
1, 2, 2.5, 3, 4, 5, 6, 8, 9
|
✅ | ❌ | Array containing the variable objects the option will update | Option Variables |
"call" |
Object |
1, 2, 2.5, 3, 4, 5, 6, 8, 9
|
➕ | ❌ | Script function called whenever the option's value is changed. | Option Scripts |
"callOpen" |
Object |
2, 2.5, 8, 9
|
➕ | ❌ | Script function called when the scale menu is opened. | Option Scale Scripts |
"callClose" |
Object |
2, 2.5, 8, 9
|
➕ | ❌ | Script function called when the scale menu is closed. | Option Scale Scripts |
"callLoop" |
Object |
2, 2.5, 8, 9
|
➕ | ❌ | Script function callback loop when the scale menu is open. | Option Scale Scripts |
"scale" |
Map |
2, 2.5, 8
|
✅ | ❌ | Map defining the slider's range, increment, etc. | Option Scale Map |
"prefix" |
String |
2, 2.5, 8
|
➕ | ✅ | Prefix string before the value | -- |
"suffix" |
String |
2, 2.5, 8
|
➕ | ✅ | Suffix string after the value | -- |
"image" |
Map | 0 |
➕ | ❌ | Map defining the image's path, size, position, etc. | Option Image Map |
"strings" |
Array | 1 |
✅ | ❌ | Array of strings providing the text labels for option type 1 | Option String Array |
"stringsAlt" |
Array | 1 |
➕ | ❌ | Array of strings providing the text display labels for option type 1 | Option String Array |
"string" |
String | 7 |
✅ | ✅ | String value to display | -- |
"textOn" |
String | 6 |
➕ | ✅ | Custom text for toggle "On" state. | -- |
"textOff" |
String | 6 |
➕ | ✅ | Custom text for toggle "Off" state. | -- |
Variable Objects can be used to dynamically set the text or value of several option elements. When a Variable Object is used in place of a standard value (String, Int, or Float), the MCM Extender will read the specified variable and use it to populate that element's value. This feature is particularly useful for creating dynamic menu elements.
ℹ️ Note: See the
Var Supportcolumn in the Option Objects table for a complete list of supported elements. When used for display purposes, Variable Objects do not require theconfigINIanddefaultelements.
Example: You want to display a dynamic title string from a JIP Auxiliary-Variable:
"title": { "form": "Goo1", "auxVar": "*_MyAuxVar", "key": 0 }Example: You want to dynamically adjust an option's text brightness from a JIP Auxiliary-Variable:
"brightnessMul": { "form": "Goo1", "auxVar": "*_MyAuxVar", "key": 0 }Include either the "enable" or "state" element to control a option's visibility state.
ℹ️ Note: If both options are included, the
"state"element will always take priority.
-
"enable": Controls the option's interactivity.-
0: Hidden. The option is not visible. -
1: Enabled. The option is visible and can be interacted with. -
2: Disabled. The option is visible but greyed out and cannot be selected.
-
-
"state"Preforms an equality check with a Variable Object and numeric"value".- To make the option disabled (visible but greyed out) instead of hidden, add
"disable": 1to the state object.
- To make the option disabled (visible but greyed out) instead of hidden, add
"enable": 1,
"state": { "form": "SomeQuest", "numVar": "SomeVariable", "value": 1, "disable": 1 },The "refresh" element forces the active MCM menu to refresh itself.
At the moment it only has one possible value:
-
1: Forces all submenus to revalidate their"state"element
ℹ️ Note: This can be useful if you want an option to control a submenu's visibility state.
The "call" object takes 2 keys and calls a specified script function whenever the option's value is changed. This is useful for scripted changes that are not handled by other variable types.
"type" |
"value" |
Example | Extra Info |
|---|---|---|---|
"udf" |
UDF EditorID | "call": { "type": "udf", "value": "YourOptionUDF" } |
Call |
"file" |
CompileScript path | "call": { "type": "file", "value": "Folder/YourOptionUDF.gek" } |
CompileScript |
"event" |
Event name | "call": { "type": "event", "value": "MyCustomEvent" } |
DispatchEventAlt |
"snippet" |
Formatted script string | "call": { "type": "snippet", "value": "Player.AddItem Stimpak" } |
RunScriptSnippet |
"console" |
Formatted console string | "call": { "type": "console", "value": "TGM" } |
Console |
For udf, file, and event types, the function is passed the new value and contextual information:
Begin Function { float fValue, int iMenu, int iSubmenu, int iOption, array_var aMenuMap }
...
End
The special "callOpen", "callClose", and "callLoop" objects are variants of the standard "call" object. They are used to trigger a specified script function based on the state of the selected option's scale menu:
-
"callOpen": Executes the script function once when the scale menu is first opened. -
"callClose": Executes the script function once when the scale menu is closed. -
"callLoop": Executes the script function continuously while the scale menu is active.
For udf, file, and event types, the function is passed the scale menu values and contextual information:
Begin Function { int iSubmenu, int iOption, array_var aValues }
...
End
Option type 1 can optionally be used to display an image instead of a string header.
Specify the image properties by adding following key-value pairs to the image object:
| Key Name | Value Type | Required | Description |
|---|---|---|---|
"filename" |
String | ✅ | Path to the .dds image file to be displayed |
"width" |
Int | ✅ | Display width of the image in pixels |
"height" |
Int | ✅ | Display height of the image in pixels |
"systemcolor" |
Int | ✅ | Systemcolor tint applied to the image. See Systemcolor Indexes |
"offsetX" |
Int | ➕ | X offset from the center of the MCM options window |
"offsetY" |
Int | ➕ | Y offset from the center of the MCM options window |
Example: You want to display your mod's custom logo:
"image": {
"filename": "Interface/YourCustomLogo.dds",
"width": "700",
"height": "350",
"systemcolor": 5,
"offsetX": 0,
"offsetY": -30
}Systemcolor is the color tint that will be applied to your image.
The index corresponds to the tint of a specific interface component:
| Index | Color |
|---|---|
0 |
None |
1 |
HUD |
2 |
HUD Alt (Red) |
3 |
Terminal (Green) |
4 |
Pip-Boy |
5 |
Pause Menu |
This array provides the text labels for option type 1. The array index correspond to the variable's value, starting at 0.
"strings": [ "String 0", "String 1", "String 3" ]You can optionally include a stringsAlt array to override the string displayed for the selected option type 1 value on the menu page.
When provided, the string at the corresponding index in stringsAlt is displayed next to the option title, while the strings in strings are still used for the actual dropdown list. This is useful for displaying a shorter or less descriptive string in the main menu for brevity.
"strings": [ "List String 0", "List String 1", "List String 2" ],
"stringsAlt": [ "Display String 0", "Display String 1", "Display String 2" ]A scale object defines the appearance and parameters of the Scale Menu used by option types 2, 2.5, and 8.
A scale object can include the following key-value pairs:
| Key Name | Value Type | Option Type | Required | Description |
|---|---|---|---|---|
"title" |
String | Any | ➕ | Title shown in the scale menu (defaults to the option title if omitted) |
"suffix" |
String |
2, 2.5
|
➕ | Text appended after the displayed value |
"valueDecimal" |
Int |
2, 2.5
|
✅ | Number of decimal places to display |
"valueIncrement" |
Int/Float |
2, 2.5
|
✅ | Step size for each scale menu increment |
"valueMin" |
Int/Float |
2, 2.5
|
✅ | Minimum selectable value in the scale menu |
"valueMax" |
Int/Float |
2, 2.5
|
✅ | Maximum selectable value in the scale menu |
"setting1.." |
String | 8 |
✅ | Strings labeling each variable |
"value1Decimal..." |
Int | 8 |
✅ | Number of decimal places to display |
"value1Increment..." |
Int/Float | 8 |
✅ | Step size for each scale menu increment |
"value1Min..." |
Int/Float | 8 |
✅ | Minimum selectable value in the scale menu |
"value1Max..." |
Int/Float | 8 |
✅ | Maximum selectable value in the scale menu |
For option type 8, the required elements depend on the number of Variable Objects in the vars array.
Example: if the array includes two Variable Objects, the scale object might look like this:
"scale": {
"title": "Health Threshold",
"setting1": "Var1",
"setting2": "Var2",
"value1Decimal": 0,
"value2Decimal": 0,
"value1Increment": 10,
"value2Increment": 5,
"value1Min": 10,
"value2Min": 0,
"value1Max": 100,
"value2Max": 50
}The vars array contains one or more Variable Objects. Some option types accept a flexible number of Variable Objects, while others require a fixed amount. The table below shows how many Variable Objects each option type requires.
| Option Type | Variable Objects |
|---|---|
0 |
0 |
1 |
> 0 |
2 |
> 0 |
3 |
> 0 |
4 |
> 0 |
5 |
> 0 |
6 |
> 0 |
7 |
0 |
8 |
2-3 |
9 |
3 |
Variable Objects define which variables and INI config settings MCM Extender updates automatically when an option changes.
The simplest Variable Object is a key–value pair that saves the option value to your mod’s INI file.
| Key Name | Value Type | Required | Description |
|---|---|---|---|
"default" |
Int/Float | ✅ | The default value of the variable |
"configINI" |
String | ➕ | The section:key for the value in the config INI file |
{ "configINI": "General:iYourOption", "default": 1 }You can include a Variable Type in a Variable Object to have MCM Extender update it along with your config file.
To update a Variable Type without saving it to your config, omit the configINI element.
See Variable Type below for examples of all supported data types.
⚠️ Important: When entering the Player reference in aformelement, you must use the string"PlayerRef". Using"Player"will incorrectly point to the player's BaseForm.
Value of the numeric variable specified by name for the calling reference or specified scriptable object / quest.
-
"form": BaseForm or Reference containing the script (Editor ID String) -
"numVar": int/float variable name (String)
{ "form": "YourQuest", "numVar": "YourQuestInt", "configINI": "General:iYourOption", "default": 1 }Value of an array variable index specified by name for the calling reference or specified scriptable object / quest.
-
"form": BaseForm or Reference containing the script (Editor ID String) -
"arrVar": array_var variable name (String) -
"key": Array/Map key (Int or String)
{ "form": "YourQuest", "arrVar": "YourQuestArray", "key": 1, "configINI": "General:iYourOption", "default": 1 }-
"form": Reference containing the token (Editor ID String) -
"token": Token form (Editor ID String)
{ "form": "SunnyREF", "token": "ModToken", "configINI": "General:iYourOption", "default": 1 }
⚠️ Important: Ensure the specified Auxiliary-Variable index has been initialized.
-
"form": BaseForm or Reference Auxiliary variable owner (Editor ID String) -
"auxVar": Auxiliary variable name (String) -
"key": Index (Int)
{ "form": "Goo1", "auxVar": "*_MyAuxVar", "key": 0, "configINI": "General:iYourOption", "default": 1 }-
"form": BaseForm or Reference key -
"refMap": Ref map name (String)
{ "refMap": "*_MyRefMap", "form": "SunnyRef", "configINI": "General:iYourOption", "default": 1 }-
"auxStringMap": Auxiliary string map name (String) -
"key": Map key (String)
{ "auxStringMap": "*_MyStringMap", "key": "MyStringMapKey", "configINI": "General:iYourOption", "default": 1 }-
"configINI": Thesection:keyfor the value in the INI file. -
"iniPath": (Optional) A filepath relative toData\config\. Overrides the global"saveFile"from the metadata for this variable only.
{ "configINI": "General:iYourOption", "iniPath": "MyFolder/MyFile.ini", "default": 1 }-
"gameINI": Thekey:sectionfor the value in the INI file.
{ "gameINI": "fBlurRadiusPipboy:Pipboy", "configINI": "General:iYourOption", "default": 1 }-
"gameSetting": GameSetting name
{ "gameSetting": "iXPRewardPickLockEasy", "configINI": "General:iYourOption", "default": 1 }-
"global": Global Variable (Editor ID String)
{ "global": "TrapXPRewardEasy", "configINI": "General:iYourOption", "default": 1 }-
"uiTrait": UI trait name as defined in the XML hierarchy for the desired menu.
{ "uiTrait": "HUDMainMenu/YourUITile/_YourUITrait", "configINI": "General:iYourOption", "default": 1 }While MCM Extender can directly modify variables and INI files, your mod's scripts may need to react to these changes in real-time (e.g., to update visuals or running processes).
When the user closes the MCM menu, MCM Extender dispatches a MCMExtUpdate event. This event is passed an array of modNames that have been updated. Your scripts can listen for this event to know when to re-read settings and apply changes.
SetEventHandler "MCMExtUpdate" MyMCMMenuUpdateHandler
scn MyMCMMenuUpdateHandler
Begin Function { array_var aUpdatedMods }
; Check if your mod's modName is in the array and run update logic
if eval (Ar_Find "YourNodName" aUpdatedMods) != Ar_BadNumericIndex
; Run update logic here
endif
aUpdatedMods = ar_Null
End
When the user switches submenu, mod menu, or toggles the MCM window off, MCM Extender dispatches a MCMExtMenuState event.
This event is passed the active modName and submenu if selecting an MCM Extender supported menu. Otherwise it is passed an empty string and 0 if selecting a legacy MCM menu, or -1 if closing the MCM window.
SetEventHandler "MCMExtMenuState" MyMCMMenuStateHandler
scn MyMCMMenuStateHandler
Begin Function { string_var sModName, int iSubmenu }
; Check if your mod's submenu 1 has been selected
if eval sModName == "YourModName" && iSubmenu == 1
; Run update logic here
endif
sv_Destruct sModName
End
MCM Extender supports localization, allowing you to provide translations for any string in your menu. This is handled by pairing specially formatted JSON strings with a corresponding translation file.
The process involves two steps:
To mark a string value in your JSON file as translatable, prefix it with a dollar sign ($). This can be used for any string value, such as a mod's display name, a submenu title, or an option's mouseover text.
Example: You want to translate your mod's display name:
"modName": "MyAmazingMod",
"displayName": "$MyModName",Next, you must provide a file containing the translated text.
-
Location: This file must be placed in the
Data\MCM\translations\directory. -
Filename: The name of this file must exactly match the
modNameyou specified in your JSON metadata, with an .ini extension. For example, if yourmodNameis "MyAmazingMod", your translation file must be named MyAmazingMod.ini. -
Format: Inside this file, create a section called
[Translations]. Under this section, define each translation key from your JSON file (including the $) and set its value to the desired text.
Based on the JSON example above, the corresponding MyAmazingMod.ini might look like this:
[Translations]
$MyModName = Mod Name
$Submenu1List = Submenu List Name
$Submenu1Page = Submenu Page Name
$Option1Name = Option Name
$Option1Mouse = Option mouseover description.When the menu is loaded, MCM Extender will find any string value starting with $ and replace it with the corresponding text from your translation file.
To ensure your MCM Extender JSON is valid and works as expected, keep the following points in mind:
-
Numeric Keys are Strings: All keys in the
submenusandoptionsmaps must be strings, even if they are numbers (e.g., use"1":, not1:). - Case Sensitivity: The JSON parser is case-sensitive. Always match the casing shown in this guide.
-
Player Reference: When a variable requires a reference to the Player, always use
"PlayerRef". Using"Player"points to the Player's BaseForm. -
Path Separators: When defining file paths in keys like
"saveFile"or for requirements, it is safest to use forward slashes (/) (e.g.,"Config/MyMod.ini"). While backslashes (\) may work, forward slashes are more universally compatible.
ℹ️ Hotloading JSON: To quickly apply changes to your JSON file while in-game, you can hotload the updated file by simply closing and re-opening the pause menu. This forces the MCM Extender to re-read the configuration.
Here is a complete example of a minimal, valid JSON file for a single-page MCM. This demonstrates how the metadata, requirements, and a single submenu with two options are structured together.
{
"modName": "MyAmazingMod",
"displayName": "My Amazing Mod",
"description": "UNUSED",
"saveFile": "MyAmazingMod/settings.ini",
"minMCMVersion": 1.0,
"minModVersion": 1.0,
"requirements": [
{ "type": "plugin", "file": "MyAmazingMod.esp" }
],
"submenus": {
"0": {
"columns": 1,
"listTitle": "Main",
"pageTitle": "My Amazing Mod Settings",
"options": {
"1": {
"type": 0,
"enable": 1,
"title": "General Settings"
},
"2": {
"type": 4,
"enable": 1,
"title": "Enable Awesome Feature",
"mouse": "Toggles the main feature of the mod on or off.",
"vars": [
{ "configINI": "General:bAwesomeFeatureEnabled", "default": 1 }
]
}
}
}
}
}