Added two new gotchas to the modding reference: - Modifier icons must use common/modifier_icons/ indirection (bare names, not full DDS paths) to avoid doubled-path resolution - on_game_start events must use scope=none with every_living_character for character initialization Also added modifier_icons directory to the directory layout tree and two rows to the Common Pitfalls table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
22 KiB
CK3 Modding Reference
Practical reference for developing Crusader Kings III mods, with emphasis on total conversion considerations. Based on official wiki documentation, community guides, and patterns from major TC mods (AGOT, Elder Kings 2, Princes of Darkness, Atlantis template).
Mod Structure
Directory Layout
my_mod/ # Mod root folder
├── descriptor.mod # Inner descriptor (no path field)
├── thumbnail.png # Steam Workshop thumbnail
├── common/ # Game data definitions (80+ subdirs)
│ ├── bookmarks/ # Start screen scenarios
│ ├── buildings/ # Holdings and buildings
│ ├── casus_belli_types/ # War justifications
│ ├── character_interactions/ # Diplomatic actions
│ ├── council_positions/ # Council roles
│ ├── council_tasks/ # Council job actions
│ ├── culture/ # Culture definitions
│ │ ├── cultures/ # Culture entries
│ │ ├── pillars/ # Heritage, language, martial custom, etc.
│ │ └── traditions/ # Cultural traditions
│ ├── decisions/ # Player/AI decisions
│ ├── defines/ # Game constants and tunables
│ ├── dynasties/ # Dynasty definitions
│ ├── dynasty_houses/ # Cadet branches
│ ├── dynasty_legacies/ # Legacy tracks
│ ├── ethnicities/ # Portrait ethnicity definitions
│ ├── factions/ # Faction types
│ ├── governments/ # Government forms
│ ├── holdings/ # Holding types
│ ├── landed_titles/ # Title hierarchy (empire → barony)
│ ├── laws/ # Realm laws
│ ├── lifestyles/ # Lifestyle focuses and perks
│ ├── men_at_arms_types/ # Custom troop types
│ ├── modifier_icons/ # Icon definitions for modifiers
│ ├── modifiers/ # Static modifiers
│ ├── nicknames/ # Character nicknames
│ ├── on_action/ # Event hooks (game triggers)
│ ├── province_terrain/ # Terrain types per province
│ ├── religion/ # Religion definitions
│ │ ├── religions/ # Religion and faith entries
│ │ └── holy_sites/ # Holy site definitions
│ ├── schemes/ # Intrigue schemes
│ ├── script_values/ # Reusable numeric values
│ ├── scripted_effects/ # Reusable effect blocks
│ ├── scripted_modifiers/ # Reusable modifier blocks
│ ├── scripted_triggers/ # Reusable condition blocks
│ └── traits/ # Character traits
├── events/ # Event scripts
├── gfx/ # Graphics (icons, portraits, UI art)
│ └── interface/icons/traits/ # Trait icon DDS files
├── gui/ # UI layout overrides
├── history/ # Starting game state
│ ├── characters/ # Character definitions with dates
│ ├── provinces/ # Province culture/religion/holdings
│ └── titles/ # Title holder history
├── localization/ # Display strings
│ └── english/ # l_english.yml files
│ └── replace/ # Overrides for vanilla loc keys
└── map_data/ # Map files (TC only)
├── default.map # Province groupings
├── definitions.csv # Province ID → RGB mapping
├── geographical_regions/ # Regional groupings
├── provinces.png # Province map image
├── rivers.png # River overlay
└── heightmap files # Terrain elevation
my_mod.mod # Outer descriptor (has path field)
The .mod Descriptor Files
Every mod needs two descriptor files with identical content except for the
path field:
Outer file (my_mod.mod) — sits alongside the mod folder in the mod
directory. Includes the path field:
version="1.0.0"
tags={
"Total Conversion"
}
name="My Mod"
supported_version="1.12.*"
path="mod/my_mod"
Inner file (my_mod/descriptor.mod) — sits inside the mod folder.
Same content but without the path field:
version="1.0.0"
tags={
"Total Conversion"
}
name="My Mod"
supported_version="1.12.*"
Descriptor Fields
| Field | Required | Description |
|---|---|---|
name |
Yes | Display name in the launcher |
version |
No | Mod version string (informational only) |
supported_version |
Yes | Game version compatibility (wildcards OK: 1.12.*) |
path |
Outer only | Path to mod folder. Use forward slashes only. |
tags |
No | Category tags ("Total Conversion", "Gameplay", etc.) |
picture |
No | Thumbnail filename (e.g., thumbnail.png) |
remote_file_id |
No | Steam Workshop item ID (added automatically on upload) |
Mod Installation Paths
| Platform | Path |
|---|---|
| Windows | %USERPROFILE%\Documents\Paradox Interactive\Crusader Kings III\mod\ |
| Linux | ~/.local/share/Paradox Interactive/Crusader Kings III/mod/ |
| Mac | ~/Library/Application Support/Paradox Interactive/Crusader Kings III/mod/ |
Both the .mod file and the mod folder live in this directory.
File Loading and Override Rules
How CK3 Loads Mod Files
When a mod provides a file at the same path as a vanilla file, the mod's file replaces the entire vanilla file. This is whole-file replacement — you cannot partially override a file just by having the same filename.
LIOS — Last In, Only Served (Most Files)
For most game data (traits, decisions, events, etc.), when multiple definitions of the same object exist across different files, the last loaded definition wins. Files load in ASCIIbetical order by filename:
01_my_traits.txtloads after and overrides objects in00_vanilla_traits.txt- Within the same file, later definitions override earlier ones
This means you can override a single vanilla object by creating a new file that sorts after the vanilla file and redefining just that object.
FIOS — First In, Only Served (GUI Files)
GUI types and templates use the opposite rule: the first loaded definition wins. To override vanilla GUI, name your file to sort before the vanilla file.
Localization Override
Place overriding localization files in localization/<language>/replace/.
Files in the replace/ subfolder can override individual keys from vanilla
without errors. Overriding keys outside of replace/ works but produces
log warnings.
Multiple Mods
When two mods provide the same file, the mod lower in the playset (load order) takes priority.
Localization
File Format
Localization files are YAML with strict requirements:
- Encoding: UTF-8 with BOM (byte order mark). Files without BOM will silently fail to load or cause validation errors.
- Filename: Must end in
_l_<language>.yml(e.g.,mistborn_traits_l_english.yml) - First line: Must declare the language:
l_english:
Entry Format
l_english:
key_name:0 "Display text with $VARIABLE$ substitution"
another_key:1 "Text with [concept|E] references"
The number after the colon (:0, :1) is a version number used by Paradox
for tracking translation updates. For mods, use 0 or 1.
Tips
- Copy an existing vanilla
.ymlfile to inherit the correct BOM encoding - Every trait, event option, decision, title, modifier, and event description needs a localization entry
- Missing keys display as raw key names in-game (e.g.,
trait_misting_coinshot)
Localization Gotchas
Religion custom loc requires mapping blocks. Simply adding faith-prefixed
keys to your .yml files (e.g., my_faith_HighGodName:0 "The Creator") is
NOT sufficient. You must also add a localization = { } block to each
religion in the definition file that maps generic key names to your prefixed
loc strings:
my_religion = {
localization = {
HighGodName = my_faith_HighGodName
HighGodNamePossessive = my_faith_HighGodNamePossessive
PriestMale = my_faith_PriestMale
PriestFemale = my_faith_PriestFemale
# ... 128 keys total (see vanilla 00_christianity.txt for full list)
}
faiths = { ... }
}
Without these mapping blocks, CK3 cannot find your faith-specific loc keys
and ck3-tiger reports hundreds of missing-localization warnings.
Trait keys are looked up with double prefix. CK3 looks up traits as both
trait_name and trait_trait_name. If your traits use a trait_ prefix
(e.g., trait_misting_coinshot), you need loc entries for both
trait_misting_coinshot and trait_trait_misting_coinshot (name + desc).
Name list names need loc keys. Every name in male_names and
female_names blocks of a name list requires a localization entry
(Kelsier:0 "Kelsier"). Names without loc still work but generate
ck3-tiger warnings.
County titles need adjective keys. CK3 uses <county_key>_adj for
demonyms (e.g., c_luthadel_adj:0 "Luthadel"). These are separate from
the county name loc.
Culture/heritage/language need extra keys. Beyond the base key and
_desc, cultures need _prefix and _collective_noun; heritage and
language pillars need _name variants; traditions need _name variants
and culture_parameter_* bonus description keys.
Modifier icons use indirection via common/modifier_icons/. Do NOT put
full DDS paths in modifier icon = fields. CK3 resolves modifier icons
through definition files in common/modifier_icons/. Each definition maps a
bare name to a DDS path:
# common/modifier_icons/00_my_modifier_icons.txt
my_modifier_icon = {
positive = "gfx/interface/icons/modifiers/my_modifier_icon.dds"
}
Then reference the bare name in your modifier:
# common/modifiers/00_my_modifiers.txt
my_modifier = {
icon = my_modifier_icon
prowess = 5
}
If you use icon = "gfx/interface/icons/modifiers/my_icon.dds" directly, CK3
auto-prepends the path and appends .dds again, producing a doubled path like
gfx/.../gfx/.../my_icon.dds.dds that generates dozens of missing-file
warnings.
on_game_start has no character scope. Events listed in on_game_start
must use scope = none. They cannot be type = character_event. To
initialize characters at game start, use every_living_character inside the
event's immediate block:
my_init.0001 = {
scope = none
hidden = yes
immediate = {
every_living_character = {
limit = { is_ruler = yes }
# character-scoped effects here
}
}
}
Province history for total conversions. Vanilla provinces without any
history definition generate "Province with no county data" errors. Provide
holding = none defaults for unused provinces. But do NOT redefine provinces
that vanilla already has history for — that creates duplicate-field warnings.
Only cover the gap: provinces with zero history anywhere.
Events
Namespace Declaration
Every event file must declare its namespace at the top:
namespace = mistborn_snapping
Event Structure
mistborn_snapping.0001 = {
type = character_event
title = mistborn_snapping.0001.t
desc = mistborn_snapping.0001.desc
theme = intrigue
trigger = {
# Conditions that must be true
}
immediate = {
# Effects that run before the event displays
}
option = {
name = mistborn_snapping.0001.a
# Effects when this option is chosen
}
}
Event Types
character_event— Targets a character, shows a popupletter_event— Displayed as a letter/messagecourt_event— Royal court events (DLC)
Traits
Structure
trait_name = {
index = 200 # Unique index, avoid vanilla range
category = fame # fame, lifestyle, personality, etc.
genetic = yes # Can be inherited
inherit_chance = 15 # Base inheritance chance (%)
both_parent_has_trait_inherit_chance = 30
# Stat modifiers
prowess = 3
martial = 1
# Display
icon = "gfx/interface/icons/traits/trait_name.dds"
ruler_designer_cost = 50
shown_in_ruler_designer = yes
}
- Use
indexvalues 200+ to avoid conflicts with vanilla traits groupfield makes traits mutually exclusive within that groupbirth = <weight>controls random assignment at birth (0 = never)
Scripted Triggers and Effects
Triggers
Reusable conditions that return true/false:
is_allomancer_trigger = {
OR = {
has_trait = trait_misting_coinshot
has_trait = trait_misting_lurcher
has_trait = trait_mistborn
}
}
Effects
Reusable blocks of game actions:
activate_allomancy_effect = {
remove_trait = trait_allomantic_potential
random_list = {
30 = { add_trait = trait_misting_coinshot }
30 = { add_trait = trait_misting_lurcher }
5 = { add_trait = trait_mistborn }
}
}
On-Actions
Game hooks that fire events/effects when specific things happen:
on_birthday = {
events = {
mistborn_snapping.0001 # Check for natural snapping
}
effect = {
# Inline effects
}
}
Common hooks: on_birthday, on_yearly_pulse, on_game_start,
on_death, on_war_started, on_title_gain, on_marriage.
Decisions
my_decision = {
picture = "gfx/interface/illustrations/decisions/decision_something.dds"
is_shown = {
# Conditions for the decision to appear in the list
}
is_valid = {
# Conditions for the decision to be clickable
}
cost = {
gold = 100
prestige = 200
}
effect = {
# What happens when the player takes the decision
}
ai_will_do = {
base = 10 # AI weight for taking this decision
}
}
History Files
Characters
900001 = {
name = "Devinshae"
dynasty = dynasty_venture
religion = "steel_ministry"
culture = "noble_scadrian"
trait = trait_misting_seeker
trait = education_intrigue_4
father = 900000
900.1.1 = { birth = yes }
930.1.1 = { death = yes }
}
Dates use yyyy.mm.dd format. Use birth = yes and death = yes inside
date blocks. Character IDs should avoid vanilla ranges (use 900000+).
Titles
e_final_empire = {
900.1.1 = {
holder = 900001
}
}
Total Conversion Considerations
The Core Challenge
A total conversion must clear vanilla CK3 content while building an entirely new world. This means replacing or masking: the map, all provinces and titles, all cultures and religions, all characters and dynasties, all bookmarks.
Approaches to Vanilla Content
Option A: Replace with empty files — Create empty files matching every vanilla filename you want to suppress. Simple but tedious and fragile across game updates.
Option B: Preserve but hide (Atlantis approach) — Keep vanilla content as invisible placeholders, redirect references to dummy entries, hide elements through GUI overrides. More resilient to game patches.
Option C: replace_path directive — The replace_path field in
descriptor.mod should tell the engine to ignore entire vanilla directories.
In practice this has been unreliable in CK3 (reported broken since 1.1).
Don't depend on it; use Option A or B instead.
Map
Total conversions that want a custom map must provide:
map_data/provinces.png— Province boundaries (each province a unique RGB)map_data/definitions.csv— Maps province IDs to RGB colorsmap_data/rivers.png— River overlaymap_data/default.map— Province groupings and sea zonesmap_data/heightmapfiles — Terrain elevationmap_data/geographical_regions/— Regional groupings
Broken rivers.png files can crash the game. Test map changes incrementally.
If your TC doesn't need a custom map (e.g., uses abstract/minimal geography), you can reuse the vanilla map and just reassign provinces.
Portraits and DNA
- Custom portrait assets go in
gfx/portraits/ - Ethnicities are defined in
common/ethnicities/ - Characters without proper ethnicity/portrait setup will appear bald
- Add
has_scripted_appearance = yesto character history to use scripted (non-DNA) appearance - DNA strings can be generated using the in-game portrait editor (debug mode)
Culture and Religion Families
- Religion families (
family = rf_scadrial) are auto-created on first use but verify this works with your CK3 version - Culture pillars (heritage, language, martial_custom, ethos) must be defined before cultures that reference them
- File load order matters — name pillar files to sort before culture files
Bookmarks
Bookmarks define the start screen scenarios. Each needs:
- A bookmark definition in
common/bookmarks/ - Character entries with portraits and descriptions
- Localization for all display text
- At least one playable character with a valid title
What Bleeds Through
Even in a TC, vanilla content can appear unexpectedly:
- Decisions — Vanilla decisions may show up if not replaced/emptied
- Events — Vanilla on_action hooks still fire unless overridden
- GUI text — Hardcoded strings and tooltips reference vanilla concepts
- AI behavior — AI weights reference vanilla traits, cultures, religions
- Name lists — Without custom name lists, characters get vanilla names
Test thoroughly and check error.log for references to content you didn't
create.
Development Workflow
Setup
- Create your mod folder in the CK3 mod directory
- Create both
.modfiles (outer with path, inner without) - Add Steam launch options:
-debug_mode -develop - Create a playset in the launcher with your mod enabled
- Reload mods in launcher: Mods → Manage All Mods → Reload Installed Mods
Debug Mode
Launch with -debug_mode to enable:
- Console — Press tilde (
~) to open. Tab for command list. - error.log — Written to
Documents/Paradox Interactive/Crusader Kings III/logs/error.log. Check this after every launch. - Event testing —
test_event <id> <char_id>checks if an event can fire;debug_event <id> <char_id>forces it to fire
Useful Console Commands
| Command | Description |
|---|---|
event <id> |
Fire an event for the player character |
test_event <id> <char_id> |
Check if event can trigger (prints result) |
debug_event <id> <char_id> |
Force-fire event and print trigger info |
add_trait <trait> |
Add a trait to the player character |
remove_trait <trait> |
Remove a trait |
add_gold <amount> |
Add gold |
add_prestige <amount> |
Add prestige |
yesmen |
AI always accepts proposals |
observe |
Enter observer mode |
charinfo |
Toggle character debug info |
reload <file> |
Hot-reload a specific file |
Iterative Testing
- Make changes to mod files
- Launch game (or use
reloadfor supported file types) - Check
error.logfor new errors - Use console to test specific events/traits/decisions
- Fix issues, repeat
Publishing to Steam Workshop
- In the CK3 launcher, go to Mods
- Click "Upload Mod" → select your mod
- Set the suggested game version, add thumbnail and description
- The launcher handles packaging and upload
remote_file_idis added to your.modfile automatically
To update: make changes locally, then re-upload through the launcher.
Common Pitfalls
| Issue | Cause | Fix |
|---|---|---|
| Localization not loading | Missing UTF-8 BOM encoding | Re-save files as UTF-8 with BOM |
| Traits not appearing | Wrong index collision or missing loc |
Use unique indices 200+, add loc entries |
| Events silently failing | Namespace mismatch or missing event file | Check namespace declaration matches event prefix |
| Decision not showing | is_shown trigger failing |
Test individual trigger conditions in console |
| Vanilla content showing | Not all vanilla files overridden | Empty/replace all relevant vanilla files |
| Characters appear bald | Missing ethnicity or portrait data | Add has_scripted_appearance or define ethnicities |
| Game crash on load | Broken map files or circular references | Test incrementally, check error.log |
replace_path not working |
Known CK3 bug/limitation | Use empty file replacement instead |
| Wrong trait activating | LIOS override — file sort order wrong | Name files to sort correctly (00_, 01_ prefix) |
| Mod not detected | .mod file path wrong or uses backslashes |
Use forward slashes only in path field |
| Religion loc not found | Missing localization = { } mapping block in religion definition |
Add mapping block to each religion (see Localization Gotchas) |
| Hundreds of trait loc warnings | CK3 looks up trait_trait_X in addition to trait_X |
Add double-prefix loc entries for all traits |
| Province log flooding | Vanilla provinces without history in your TC | Add holding = none defaults for unused province IDs (skip vanilla-defined ones) |
| Modifier icon doubled paths | Full DDS path in modifier icon = field |
Use bare names via common/modifier_icons/ definitions (see Gotchas) |
| on_game_start scope errors | Character events/triggers in on_game_start |
Use scope = none events with every_living_character inside |