AI agent skill · by @kevinpbuckley
Project Settings
Configure Unreal Engine project settings and editor preferences — UI appearance, toolbar icons, scale, colors, and any UDeveloperSettings subclass (ProjectSettingsService). Use when the user asks to change a project setting or editor preference, configure a UDeveloperSettings category, or read/write project config values.
Archived 2026-10-23 · Source updated 2026-10-07 · License: MIT. Check the original repository for the latest version.
SKILL.md instructions
# Project Settings Skill
> 🔀 **Registered settings moved to the engine.** Category-based registered-setting access
> (`list_categories` / `list_settings` / `get_setting` / `set_setting` /
> `set_category_settings_from_json`) is now Unreal 5.8's native **`ConfigSettingsToolset`** — reach
> it with `call_tool` (run `describe_toolset` on `ConfigSettingsToolset` for its actions/params).
> VibeUE's `ProjectSettingsService` was trimmed to the delta the engine doesn't cover:
> **`discover_settings_classes` + direct INI read/write (`get_ini_value` / `set_ini_value` /
> `*_ini_array` / `list_ini_sections` / `list_ini_keys`) + config save**, plus
> **`set_settings_property` / `get_settings_property`** for one property of a settings class by
> name. Use those from `execute_python_code` as shown below.
## Critical Rules
### ⚠️ `EditorStyleSettings` is NOT a Python Class
`EditorStyleSettings` is **not** discoverable via `discover_python_class('unreal.EditorStyleSettings')` — it returns `PYTHON_CLASS_NOT_FOUND`. Reach it through the engine **`ConfigSettingsToolset`** as a settings category (via `call_tool`), or write its values directly via raw INI:
```python
# ❌ WRONG — will fail
discover_python_class('unreal.EditorStyleSettings') # NOT FOUND
# ✅ CORRECT — engine ConfigSettingsToolset (registered settings) via call_tool, OR the class by name:
result = unreal.ProjectSettingsService.set_settings_property(
"EditorStyleSettings", "AssetEditorOpenLocation", "MainWindow")
```
`set_ini_value` writes the file you name: a bare name resolves to the project `Config/` folder
(`"DefaultGame.ini"` → `Config/DefaultGame.ini`), an absolute path is used as-is, and the path must end
in `.ini`. A bare `"EditorPerProjectUserSettings.ini"` therefore writes
`Config/EditorPerProjectUserSettings.ini`, a file no config hierarchy reads. An absolute path does reach
a user-layer file under `Saved/Config/<Platform>Editor/`, but the editor keeps those files in memory and
rewrites them from memory when it saves its config (at the latest when it exits), which can silently drop
a direct write. Change user settings (editor preferences such as `EditorStyleSettings`) with
`set_settings_property`, which edits the editor's in-memory copy and saves it.
### ⚠️ Category vs Raw INI
Named registered-setting categories (`general`, `maps`, `EditorStyleSettings`, etc.) are handled by
the engine **`ConfigSettingsToolset`**. From VibeUE you instead address settings by their INI
**section / key / file**:
| Need | Use |
|------|-----|
| Project info, maps, any registered category by name | engine `ConfigSettingsToolset` via `call_tool` |
| Read/write a known INI section+key | `ProjectSettingsService.get_ini_value` / `set_ini_value` |
| Set one property of any loaded config class, live and saved as the Settings window saves it | `ProjectSettingsService.set_settings_property(class, property, value)` |
| Discover which classes/sections exist | `ProjectSettingsService.discover_settings_classes()` |
### ⚠️ Map Paths Must Be Full Asset Paths
```python
# WRONG - folder path
"/Game/Variant_Horror.Variant_Horror"
# CORRECT - full map asset path
"/Game/Variant_Horror/Lvl_Horror.Lvl_Horror"
```
Always verify with `does_asset_exist()` before setting.
### ⚠️ Always Check Operation Results
The INI write methods return an `FProjectSettingResult`:
```python
result = unreal.ProjectSettingsService.set_ini_value(
"/Script/EngineSettings.GeneralProjectSettings", "ProjectName", "MyGame", "DefaultGame.ini")
if not result.success:
print(f"Failed: {result.error_message}")
```
`set_ini_value` writes the key into the file on disk (the rest of the file, comments included, is
kept), reloads that config branch, and reads the file back: `success` is false when the value did not
land, and the file is then put back exactly as it was. A section or key containing a line break, a
section containing `]`, and a path that does not end in `.ini` are refused before anything is written.
A settings object read its config at startup, so it sees the new value after a restart
(`requires_restart` is true); `set_settings_property` applies the value live as well.
`get_ini_value`, `list_ini_keys` and `get_ini_array` read the file on disk as it is now (one file, not
the merged config hierarchy), so they see `set_ini_value`, `set_settings_property` and hand edits at
once. `get_ini_value` returns the plain `Key=` line, resolved as GConfig resolves it; `+Key=` /
`.Key=` array lines are not its value. `get_ini_array` returns the `Key=`, `+Key=` and `.Key=` lines in
file order (`-Key=` removals and `!Key=ClearArray` are not values). For the merged value a settings
object actually uses, read `get_settings_property`.
`set_settings_property` takes any loaded config class (`CLASS_Config`), not only the ones the
Settings window lists, and saves it the way the Settings window does: a `defaultconfig` class to its
project `Default*.ini`, a user-config class to its user file, any other class to its config branch
under `Saved/Config`. The value applies live (`PostEditChangeProperty` runs, as for an edit in the
details panel), so an engine or editor class changes behavior immediately. Per-object-config classes
are refused.
`set_ini_array` works only on a settings class section (`/Script/Module.Class`), through
`set_settings_property`: an array in a layered project file needs `!Key=ClearArray` / `+Key=`
lines relative to the lower layers, which only a settings object writes correctly. It replaces the
whole array. Each value is one element in Unreal text format: an element of a struct array is a
struct literal and goes in as written; an element of a string array is passed as the plain string and
quoted for you; any other element containing a space, comma, parenthesis or quote is quoted for you.
A section named by a config class's path is read and written in the section that class really uses.
Some classes keep an older name through `OverrideConfigSection`: `ProjectPackagingSettings` lives in
`/Script/DeveloperToolSettings` but its settings stay under `[/Script/UnrealEd.ProjectPackagingSettings]`,
so `get_ini_value` / `get_ini_array` / `list_ini_keys` / `set_ini_value` given the new path use that section.
```python
# struct array: elements are struct literals, passed as written
result = unreal.ProjectSettingsService.set_ini_array(
"/Script/DeveloperToolSettings.ProjectPackagingSettings", "DirectoriesToAlwaysStageAsNonUFS",
['(Path="Movies")', '(Path="Extras/Data")'], "DefaultGame.ini")
# string array: plain values
result = unreal.ProjectSettingsService.set_ini_array(
"/Script/DeveloperToolSettings.ProjectPackagingSettings", "CulturesToStage",
["en", "pt-BR"], "DefaultGame.ini")
```
(Registered-setting writes via the engine `ConfigSettingsToolset` return their own result — check it the same way.)
---
## Workflows
### Basic Project Info Setup
Project info (name/company/description) is a registered category — set it via the engine
**`ConfigSettingsToolset`** (`call_tool`). Or write the INI keys directly from VibeUE:
```python
import unreal
sec = "/Script/EngineSettings.GeneralProjectSettings"
file = "DefaultGame.ini"
unreal.ProjectSettingsService.set_ini_value(sec, "ProjectName", "My RPG Game", file)
unreal.ProjectSettingsService.set_ini_value(sec, "CompanyName", "Indie Studios", file)
unreal.ProjectSettingsService.set_ini_value(sec, "Description", "An epic fantasy adventure", file)
unreal.ProjectSettingsService.save_all_config()
```
### Configure Default Maps
```python
import unreal
# Registered "maps" category → engine ConfigSettingsToolset via call_tool, OR raw INI:
result = unreal.ProjectSettingsService.set_ini_value(
"/Script/EngineSettings.GameMapsSettings", "GameDefaultMap",
"/Game/Maps/MainMenu.MainMenu", "DefaultEngine.ini")
if result.success:
unreal.ProjectSettingsService.save_all_config()
print("Default map updated and saved")
```
### Set Editor Startup Map
```python
import unreal
# EditorStartupMap controls which level opens when the editor launches. It is a GameMapsSettings
# property (defaultconfig, saved in Config/DefaultEngine.ini).
# Registered "maps" category → engine ConfigSettingsToolset (via call_tool), OR the class by name:
# Use full asset path format: /Game/Path/To/Level.Level
result = unreal.ProjectSettingsService.set_settings_property(
"GameMapsSettings", "EditorStartupMap", "/Game/Levels/MyLevel.MyLevel")
if result.success:
print("Editor startup map updated and saved:", result.modified_settings)
else:
print("Failed:", result.error_message)
```
> 💾 **If the level must be saved first** (untitled level, or "save as <name> and set as startup"): the editor world comes from `unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()` (NOT `LevelEditorSubsystem`, NOT deprecated `EditorLevelLibrary`), then `unreal.EditorLoadingAndSavingUtils.save_map(world, "/Game/Maps/MyLevel")`. Full workflow: load the `level-actors` skill → "Save the Current Level".
### Discover Settings Classes
```python
import unreal
classes = unreal.ProjectSettingsService.discover_settings_classes()
for c in classes:
print(f"{c.class_name}: {c.property_count} properties in {c.config_file}")
```
### List Settings in a Category
Listing the settings inside a named category is an engine **`ConfigSettingsToolset`** action (via
`call_tool`). From VibeUE you can enumerate the raw INI keys of a known section instead:
```python
import unreal
sec = "/Script/EngineSettings.GeneralProjectSettings"
file = "DefaultGame.ini"
for key in unreal.ProjectSettingsService.list_ini_keys(sec, file):
print(f"{key} = {unreal.ProjectSettingsService.get_ini_value(sec, key, file)}")
```
### Find a Setting (When You Don't Know the Section)
`discover_settings_classes()` maps each settings class to its INI **section + file**, so you can
locate a setting without guessing Python class names, then read its keys via raw INI:
```python
import unreal
keyword = "AssetEditor" # partial match on class/section name
for c in unreal.ProjectSettingsService.discover_settings_classes():
if keyword.lower() in c.class_name.lower() or keyword.lower() in c.config_section.lower():
print(f"{c.class_name} -> section {c.config_section} in {c.config_file}")
for key in unreal.ProjectSettingsService.list_ini_keys(c.config_section, c.config_file):
print(f" {key}")
```
**Use `discover_settings_classes()` instead of guessing Python class names.** Many settings classes
(e.g., `EditorStyleSettings`) are NOT exposed as Python classes — edit them through the engine
`ConfigSettingsToolset` or via their INI section.
### Direct INI Access
```python
import unreal
# List sections
sections = unreal.ProjectSettingsService.list_ini_sections("DefaultEngine.ini")
# Get value
value = unreal.ProjectSettingsService.get_ini_value("/Script/Engine.Engine", "GameEngine", "DefaultEngine.ini")
# Set value
result = unreal.ProjectSettingsService.set_ini_value(
"/Script/MyGame.CustomSettings",
"EnableDebugMode",
"True",
"DefaultGame.ini"
)
```
---
## Data Structures
> **Python Naming Convention**: C++ types like `FProjectSettingResult` are exposed as `ProjectSettingResult` in Python (no `F` prefix).
### SettingsClassInfo (from `discover_settings_classes()`)
- `class_name`, `class_path`
- `config_section`, `config_file`
- `property_count`, `is_developer_settings`
### ProjectSettingResult (from INI write methods)
- `success`, `error_message`
- `modified_settings`, `failed_settings`
- `requires_restart`
## Sample scripts (run via `execute_python_code`)
- **`scripts/configure_setting.txt`** — discover settings classes and read/write a project setting.