Atlas of Bretonnia

Modders' workshop · guide · 52 min read

RPFM, the guide: adding a unit, a faction and a lord to Total War: Warhammer III

RPFM (Rusted PackFile Manager) opens and builds Total War packs. This guide shows step by step how to use it, then walks through three complete procedures for Total War: Warhammer III: adding a unit, a faction, a legendary or playable lord. The pitfalls come from the community and from our own project.

Reviewed on 25 September 2026, update 9.0. Written against its sources, RPFM 5.0.6's code and manual (labels and locations), the game's data and the Assembly Kit on patch 8.1; what 9.0 changed on our project has been added, the rest has not been rechecked point by point. When in doubt, check, and tell us.

What RPFM does, and what a pack is§

A Total War mod is a .pack file, an archive the game reads from its data folder. RPFM (Rusted PackFile Manager, by Frodo45127, under the MIT licence) opens the game's packs read-only and builds yours. A pack holds tables (db/), texts (text/db/<file>.loc), Lua scripts (script/), images (ui/), model definitions (variantmeshes/) and, for a campaign, its start position (campaigns/<campaign>/startpos.esf).

Anatomy of a packatlas_chevaliers.packyour mod: an archive, placed in the game's data folderdb/land_units_tables/never rename or move it (RPFM)atlas_land_unitsyours, with your prefix; @ or ! in front: only to modify agame rowdata__the game table's name: never in your packtext/db/atlas.locone key per sentence; applies to every languagescript/campaign/mod/atlas_script.lualoaded by the game on its ownui/units/icons/<card>.pngthe unit card: 60 × 130ui/flags/<faction>/the flagsui/portraits/portholes/portrait_settings*.binportrait settings, per art setvariantmeshes/variantmeshdefinitions/*.variantmeshdefinitiona new modelcampaigns/<campaign>/startpos.esfmade by the game; one per campaigncampaign_maps/<map>/hlp_data.esf · spd_data.esfmade by the game, for a new mapsettings.rpfm_reserved · notes.rpfm_reservedRPFM's settings and notes, ignored by the gameTables and .loc files: merged by key.Other files: same path, only one wins.
A pack stores each file at its path. The fragments of one table (a _tables folder), like .loc files, are merged by key; for any other file, two files at the same path do not both load: only one wins. In gold, what the game makes; in grey, what only RPFM reads.

This guide follows RPFM 5.0.6, the latest stable release as of 24 September 2026, and tables read on patch 8.1. For update 9.0, released the same day, RPFM's schemas were published that very day; the tables cited here have not been reread one by one since: when in doubt, check. Every change follows these six steps:

  1. Find

    The closest game row.

    View → Toggle Global Search Window (Ctrl+Shift+F)

  2. Copy

    Into your table (db/<table>_tables/<file>), under a key of your own (the same one to modify a game row).

    right-click the pack → Create… → Create DB, then Paste as New Row

  3. Name

    The texts shown to the player, in text/db/<file>.loc.

    right-click the pack → Create… → Create Loc

  4. Check

    References, keys, empty fields.

    Diagnostics panel → Check PackFile

  5. Try

    The pack installed in <game>\data\, in game.

    right-click the pack's root → Install

  6. Publish

    The optimised version.

    right-click the pack's root → Save Pack For Release… (on a copy)

The words of the trade§

WordMeaning
Packa mod's archive, placed in the game's data folder
Tablea table of the game's database, stored in db/<table>_tables/
Keywhat identifies a row, unique in its table (sometimes several columns)
Fragmentone table file; the game reads every fragment of a table, its own and the mods'
Datacorea fragment named like the game's (data__), which replaces the whole table
.loca text file: one key per displayed sentence
Schemathe description of the tables: columns, keys, links (RPFM's, or the kit's: TWaD_*.xml)
Dependencies cachethe game's data, prepared by RPFM for searching and checking
Assembly KitCreative Assembly's official kit (Steam → Library → Tools)
Subtypea character's exact type (agent_subtypes)
Art seta character's set of appearances (campaign_character_art_sets)
Startposstartpos.esf, a campaign's turn-0 save
Pack rootthe first line of the pack tree, bearing the pack's name; its right-click menu holds Install, Uninstall, Save Pack For Release… and Build Startpos
PaletteRPFM's search by name: Ctrl+Shift+P for an action, Ctrl+P for a file
%APPDATA%your Windows account's settings folder: type %APPDATA% into File Explorer's address bar
Military groupmilitary_group, a faction's recruitment group: a unit allowed to a group is allowed to all its factions (in CA's data, Bretonnia's factions have wh_main_group_bretonnia)
twad_key_deletesthe table that removes game rows at launch (Warhammer III, since patch 6.3): columns table_name and key
MixerMIXER – Mixu's Unlocker, a mod by Mixu: its startpos makes every faction of Immortal Empires and Realm of Chaos playable; according to the wiki, it is most players' only startpos mod
Error / Warning / Infothe three severities of the diagnostics: Error, a problem in game; Warning, a likely problem; Info, worth knowing. Aim for zero Error

Installing and setting up RPFM 5.0.6§

Download RPFM from its releases, then, once and for all:

  1. PackFile → Settings. Game Paths: the Game Folder (the folder of the game's executable) and the Assembly Kit Folder. Extra Paths: the MyMod's Folder, outside Program Files, where Windows protects writing. Default Game: Warhammer 3.
  2. About → Check Updates, then, in the Update Manager, the button of the Schema Updates line: the schema tells RPFM how to read each table of the current patch (you can also tick Check Schema Updates on Start in the settings).
  3. Game Selected → Generate Dependencies Cache, a few minutes: RPFM prepares the game's data for its searches and checks. Without the Assembly Kit set up, it warns that its diagnostics will give false positives.
Setting up RPFM: three movesPackFile → SettingsSettingsGame PathsWarhammer 3Game Folder<game>Assembly Kit Folder<Assembly Kit>Extra PathsMyMod's Folder<MyMod>not in Program Files: Windows protects writing thereDefault GameWarhammer 31About → Check UpdatesUpdate ManagerProgram Updates:Schema Updates:Lua Autogen Updates:Old AK Updates:also, in Settings: tick Check Schema Updates on Start2Game Selected → Generate Dependencies Cachethe game's datathe cachea few minutes; for its searches and checksWithout the Assembly Kit set up, RPFM warns: falsepositives in the diagnostics.3After a game update: do 2 and 3 again.
Schematic, a common layout. Three settings to make once, in order: the folders (1), the table schemas (2), the dependencies cache (3); after a game update, do 2 and 3 again.

The basic moves§

RPFM 5.0.6's window, area by area:

atlas_chevaliers.packPackFile MyMod View Game Selected Tools Aboutatlas_main_unitsatlas.locPack Contentsatlas_chevaliers.packdbmain_units_tablesatlas_main_unitstextdbatlas.locunitland_unitcasteatlas_chevaliersatlas_chevaliersmelee_cavalrySave Pack As…Save Pack For Release…InstallUninstallChange PackFile Type ▸Optimize PackFileBuild StartposDependenciesGame FilesParent FilesAssembly Kit Filesright-click → ImportDiagnosticsCheck PackFileErrorWarningInfoErrorInvalid referenceatlas_main_unitsland_unitGlobal Search · Ctrl+Shift+F123456789
  1. The menusPackFile, MyMod, View, Game Selected, Tools, About. Settings: PackFile → Settings (Ctrl+,); schemas: About → Check Updates; the cache: Game Selected → Generate Dependencies Cache.
  2. The tabsone per open file.
  3. Pack Contentsthe pack's tree; its first line, the root, bears the pack's name.
  4. The root's right-click menuSave Pack As…, Save Pack For Release…, Install, Uninstall, Change PackFile Type, Optimize PackFile, Build Startpos (and other actions): what concerns the whole pack.
  5. Dependenciesthe files of the game, of parent mods and of the Assembly Kit, read-only; right-click → Import brings one into your pack.
  6. The open tableyour row, highlighted; in red, an invalid reference: no land_units row bears this key yet. The atlas_main_units file has no @: it adds a new key.
  7. DiagnosticsCheck PackFile reads the pack through; each line has a severity: Error, Warning or Info. Aim for zero Error.
  8. Global Searchhidden at first: View → Toggle Global Search Window (Ctrl+Shift+F); sources Game Files or Assembly Kit Tables.
  9. The status barshort messages from the last operation.
Schematic, usual layout. Settings, schemas and the cache are in the menus (1); what concerns the whole pack is on its root's right-click menu (4). Any action can also be found by name in the palette: Ctrl+Shift+P. The Special Stuff and Edit → Preferences menus of older tutorials do not exist in 5.0.6.

Creating your mod§

MyMod → New MyMod. Pick the game (Warhammer 3) and a name with underscores, no spaces and no .pack (author_description). RPFM creates the pack <MyMod>\warhammer_3\<name>.pack and, next to it, the folder <MyMod>\warhammer_3\<name>\ for your working files (plus a Git repository or a VSCode project if you tick them), then opens the pack. The MyMod folder must be set first, or RPFM refuses.

Finding game data§

View → Toggle Global Search Window (Ctrl+Shift+F): Game Files source (or Assembly Kit Tables), DB type (Loc for texts), then the key, underscores included; a click opens the table at the right row, read-only. Other ways: PackFile → Load All CA Packs (read-only; greyed out when the dependencies cache is loaded), or View → Toggle Dependencies Window, right-click → Import, which brings a game file into your pack: rename a table brought in this way at once (F2), as under the name data__ it would replace the game's.

Creating a table§

Right-click the pack → Create… → Create DB; pick the table (land_units_tables) and name the file with your prefix: db/land_units_tables/atlas_land_units. The name matters: when your file and the game's (data__) give the same key, the one whose name sorts first wins. To add rows (new keys), any name of your own will do; to modify a game row (same key), start the name with @ or !.

Starting from a game row§

  1. In the game's table, select the model row (click its number): Copy… → Copy.
  2. In your table: Paste as New Row (Ctrl+Shift+V). To add a row, change the key first: otherwise your row adds nothing, it competes with the game's row.
  3. To duplicate one of your rows: Clone… → Clone and Append.

Following a key§

Right-click a cell → Go To… → Go To Definition (the row cited), Go To Loc Entry (its text) or Find References (everything that cites this key); Rename References renames a key everywhere (the manual calls it Cascade Edition). This is how you take a unit or a lord apart, table after table.

Following a keyatlas_chevaliersright-click a cellGo To…Find References3Rename References4Go To Definition1Go To Loc Entry2building_units_allowed · unitunits_custom_battle_permissions · unit…3Find Referenceswhat cites itmain_unitsunitatlas_chevaliers3land_unitatlas_chevaliers11Go To Definitionthe row it citesland_unitskeyatlas_chevaliers22Go To Loc Entryits texttext/db/atlas.locland_units_onscreen_name_atlas_chevalierstextthe name shown4Rename References: renames the key everywherethe manual calls it Cascade Edition
Schematic: right-clicking a cell gives four moves that follow a key from table to table: the row it cites (1), its text (2), everything that cites it (3); Rename References renames it everywhere (4). Each cell bears the number of the move that starts from it.

Writing texts§

  1. Right-click the pack → Create… → Create Loc, for example text/db/atlas.loc.
  2. Three columns: key (the key), text (the displayed text) and tooltip, whose role is unknown according to RPFM's manual: do as CA's rows do.
  3. The key has the form <table>_<column>_<row key>. Example: the name of unit atlas_chevaliers (column onscreen_name of land_units) has the key land_units_onscreen_name_atlas_chevaliers.
  4. Right-click the pack → Generate Loc Data produces two files. zzz_…: the keys your tables are missing; rename it or copy its lines into your .loc, then fill them in. aaa_…: the game texts your tables cite; keep only what you change. Publish neither under its original name: RPFM rewrites them at every generation, and the optimiser removes aaa_… (and zzz_… if it is empty).
A text keyland_unitsthe table+_onscreen_name_the column+atlas_chevaliersthe row's keyland_units_onscreen_name_atlas_chevaliersin your .loc: the unit's nameRight-click the pack → Generate Loc Datazzz_…your keys with no text: rename it, or copy its rows into your.loc, then fill them inaaa_…game texts your tables cite: keep only what you changeNeither one under its original name: RPFM rewrites them atevery generation; the optimiser removes aaa_… (and zzz_… ifit is empty).
A text key follows the form <table>_<column>_<row key>. Generate Loc Data makes two files: zzz_… for your keys with no text, aaa_… for the game texts your tables cite.

Checking, testing, publishing§

The diagnostics§

View → Toggle Diagnostics Window → Check PackFile, before every test: a fault caught here saves a pointless game launch. Each line has a severity: Error (a problem in game), Warning (a likely problem), Info (worth knowing); aim for zero Error. The figure follows a mod from the diagnostics to the game.

From diagnostics to the gameCheck PackFilein the Diagnostics panelError, Warning, Info: aim for zero Error.1Ctrl+Ssaves the open packs.2Right-click the root → InstallRPFM copies the pack into the game's data folder.3Tick the modin the launcher, Kaedrin's Mod Manager or Runcher.Missing from the list: right-click the root → ChangePackFile Type → Mod, and Index Includes Timestampunticked.4Try it in gamea unit: custom battle; a faction, a lord: new campaign.The game crashes at launch: back to 1Invalid reference, Value Cannot be Empty,Banned TableA script does nothing: read its logscript/enable_console_logging →script_log_*.txtThe game closes at every launch: a leftover lineuser.script.txt:quit_after_campaign_processing;5
Five steps, in order: every test in game starts with Check PackFile. If the game crashes, a script stays silent or the game keeps closing, the red exit tells you where to look.

RPFM checks:

Trying it in game§

  1. Ctrl+S (saves the open packs), then right-click the pack's root (the first line of the tree, bearing its name) → Install: RPFM copies the pack into <game>\data\.
  2. Tick the mod in Creative Assembly's launcher, or in a third-party manager (Kaedrin's Mod Manager, Runcher).
  3. Same menu → Uninstall: removes the copy.

Publishing§

Keep your working pack: copy it first (or Save Pack As… under another name), because Save Pack For Release… saves the optimised version in place of the open pack. Right-click the pack's root → Save Pack For Release…: the optimiser opens, then the pack is saved; Optimize PackFile, in the same menu, does the same work without saving, so you can review first. The optimiser only does what you tick:

Publishing without breaking anythingA copy firstWorking packkept as it isIts copythe one you publishSave Pack As…, under another name, also makes a copy.1In the copy: Save Pack For Release…, on the root'sright-click menuatlas_chevaliers.packSave Pack As…Save Pack For Release…Optimize PackFileOptimize PackFile, same menu: the same work without saving, toreview first.It saves the optimised version in place of the open pack.2The optimiser only does what is tickedIdentical to the game, duplicates, empty filesticked by defaultCompress the packticked by defaultUnused art sets and variants of portraitsunticked by defaultImport datacores into twad_key_deletesunticked by default; tick it if your pack holds a copiedtable (Warhammer III)3After the datacores optiontwad_key_deletestable_name · keycheck it, then rename it: RPFM would rewrite itdb/land_units_tables/data__the copied game table: delete it yourself4
Schematic. Save Pack For Release… replaces the open pack with its optimised version: work on a copy. The optimiser only does what is ticked (here, its default settings); its datacores option writes the removed rows into twad_key_deletes, and deleting the copied table afterwards is up to you.

After a game update§

  1. About → Check Updates, schemas button.
  2. Game Selected → Generate Dependencies Cache: otherwise RPFM compares your tables with an outdated game.
  3. On every table flagged Outdated table: right-click → Update Table.
  4. Read through the new columns: RPFM puts default values in them, not necessarily the right ones for your rows.

With 9.0, RPFM's schemas came out on the day of the update. If you also write to the Assembly Kit: Steam updates it after the game (for us, an hour later) and rewrites raw_data\db; the rows you had added there disappear. Check and restore them before regenerating the dependencies cache and before building anything.

Pack rules: who wins over whom§

The game reads every fragment of a table, its own and every mod's: as long as the keys differ, the rows add up. When two fragments give the same key, the one whose name sorts first wins; and a fragment carrying the exact path of a game file replaces it entirely.

The figure below shows who wins when several fragments of the same table give the same key.

The fragments of a tabledb/land_units_tables/! # $ % & ' ( ) + , - ; = @ 0-9 a-z [ ] ^ _ ` { } ~read in name order, first to last (wiki, Submodding page)@my_modwh_dlc07_brt_cav_knights_errant_0 · value Ayour mod, sorted before the gamedata__wh_dlc07_brt_cav_knights_errant_0 · value Bthe game's tablezmy_modloseswh_dlc07_brt_cav_knights_errant_0 · value Cyour mod, sorted after the game: the game's row wins, your value isignoredThe game keeps value A: the first name in sort order wins.data__ · in your modSame path as the game's file, so it replaces it entirely: adatacore. CA's updates to this table no longer get through;two datacores of the same table exclude each other.Removing a game rowtwad_key_deletes: table_name = land_units · key = <key>right-click the game's rows→ Add Selection to Key DeletesWarhammer III, since patch 6.3
Three fragments of one table give the same key: the game keeps the one whose name sorts first; the others are ignored. A z name is for new rows that other mods should be able to correct (RPFM manual, Datacores page). A fragment named like the game's file replaces it entirely; to remove a game row, a twad_key_deletes table.

Example: the game's battle units table is the file db/land_units_tables/data__. According to RPFM's manual, @my_mod comes before it, zmy_mod after it, and a fragment named data__ replaces it entirely: that is a “datacore”, which the diagnostics flag (“Leaving them named data__ will overwrite the base game files”, Casualfield warns).

Four rules that prevent the worst surprises§

Your texts, in every languageThe game loads your .loc files whatever the player'slanguage.A new keyland_units_onscreen_name_atlas_chevaliersyour text, in every languageA game key reusedfactions_screen_name_wh_main_brt_bordeleauxreplaces the official text, in every languageFor a second language: a second packin text/db/, read in name order:!atlas_de.locexample: the translation pack, your keys translated; sortedfirst, it winsatlas.locyour mod
A mod's .loc applies to every language: put only new keys in it. For a second language, a second pack takes up your keys in a .loc whose name starts with ! (wiki, Submodding page).

Your first mod, in ten minutes§

Before the three procedures, a short test that also checks your setup: making a game unit dearer in custom battles. The wiki's RPFM For Dummies tutorial starts the same way, with a table edit found through the global search.

A first mod in ten minutesMyMod → New MyModWarhammer 3; RPFM creates the pack, then opens it.my_first_mod.pack1View → Toggle Global Search WindowCtrl+Shift+F; Game Files, DB, then the key:wh_dlc07_brt_cav_knights_errant_0main_units_tablesthe game's table, read-only2Copy… → Copyclick the row's number, then right-click3Create… → Create DBright-click your pack: table main_units_tables, name@my_main_unitsboth files, read in name order:@my_main_unitsyours, sorted first: it winsdata__the game's4Paste as New Row · Ctrl+Shift+Vsame key: you modify the game's unitmultiplayer_cost700999its price in custom battles; 700 in CA's data5Diagnostics panel → Check PackFileno Error line6Ctrl+S, then right-click the root → Installtick the mod in the launcherIn a custom battle, Bretonnia's Knights Errant cost999.7
Seven moves, from the game's table to the test in game: your @my_main_units file sorts before the game's (data__), so its row wins, and the Knights Errant go from 700 to 999 in custom battles. To add a unit instead of modifying one, you change the key: that is procedure 1.
  1. MyMod → New MyMod: game Warhammer 3, name my_first_mod.
  2. View → Toggle Global Search Window (Ctrl+Shift+F): Game Files source, DB type; search for wh_dlc07_brt_cav_knights_errant_0, then click the main_units_tables result: the game's table opens at the right row, read-only.
  3. Click the row's number, then right-click → Copy… → Copy.
  4. Right-click your pack → Create… → Create DB: table main_units_tables, name @my_main_units. The @ makes your file sort before the game's (data__): so yours wins.
  5. Right-click in the empty table → Paste as New Row (Ctrl+Shift+V). Keep the key: here, you modify the game's unit. Set multiplayer_cost (its price in custom battles; 700 in CA's data) to 999.
  6. Diagnostics panel → Check PackFile: no Error line.
  7. Ctrl+S, then right-click the pack's root → Install; tick the mod in the launcher. In a custom battle, Bretonnia's Knights Errant cost 999.

To add a unit instead of modifying one, you change the key: that is procedure 1.

Procedure 1: adding a unit§

The safe way: copy a game unit that looks like yours, then change what must change. Example followed: Bretonnia's Knights Errant, wh_dlc07_brt_cav_knights_errant_0.

Before you start: the model unit§

The next figure shows a unit's tables and the columns that link them.

A unitmain_unitskey: unitland_unit → land_unitscosts, caste, ui_unit_group_landminimumland_unitskey: key (same as unit at CA)stats, weaponsman_entity → battle_entitiesprimary_melee_weapon → melee_weaponsminimumunit_variantskey: faction + unitunit → land_unitsvariant → variantsunit_card → ui/units/iconsminimumui/units/icons/<card>.pnga 60 × 130 PNG imageminimumvariantskey: variant_nameonly for a new model→ .variantmeshdefinitionoptionalunit_description_short_texts· unit_description_historical_textsa text of the unit's ownoptionalunits_to_groupings_military_permissionskeys: unit, military_groupmilitary_group = factions.military_group(wh_main_group_bretonnia)campaignbuilding_units_allowedkey: key (a unique number)building → building_levelsstables 2 to 5: one row eachcampaignunits_custom_battle_permissionskey: unit + faction + general_unitcustom battlegeneral_unit = 0minimumtext/db/<mod>.locland_units_onscreen_name_<key>minimum
A unit's tables, as a tree: each card gives its key and links. Minimum: what custom battle needs; campaign: on top of that, to recruit it in the campaign; optional: depending on the unit. The unit card is an image (a 60 × 130 PNG), not a table.

The unit's steps§

  1. Global Search for the model's key: it appears in 24 tables of the kit; the table below gives the ones you need.
  2. For each: Create DB (a file with your prefix), Paste as New Row, then change what makes the unit and keep the rest (see “What changes, what stays”). Like CA, give main_units.unit and land_units.key the same key.
  3. Set the stats, costs, appearance and card, then the recruitment, the custom battle and the texts.
  4. Check: right-click your key → Find References. RPFM's manual advises this check after adding a unit: it shows whether the unit is wired up everywhere it should be (costs, recruitment, factions).
  5. Diagnostics, Install, custom battle test.
TableWhat to setKeyLinks
main_unitsthe copy; costs, caste, ui_unit_group_landunitland_unit → land_units.key
land_unitsthe copy; stats, weaponskeyman_entity → battle_entities; primary_melee_weapon → melee_weapons; short_description_text → unit_description_short_texts; historical_description_text → unit_description_historical_texts
unit_variantsname = your key (as CA does), faction empty (= every faction), variant, unit_cardfaction + unitunit → land_units.key; variant → variants.variant_name; the card: ui/units/icons/<unit_card>.png
variantsonly for a new model: variant_filenamevariant_name→ variantmeshes/variantmeshdefinitions/<name>.variantmeshdefinition
units_to_groupings_military_permissionsthe unit and the military groupboth columnsmilitary_group = factions.military_group (for example wh_main_group_bretonnia)
building_units_allowedbuilding, unit, key (a unique number), faction emptykeybuilding → building_levels.level_name; unit → main_units
units_custom_battle_permissionsthe battle faction (CA uses wh_main_brt_bretonnia), general_unit = 0unit + faction + general_unit→ main_units, factions
unit_description_short_texts, unit_description_historical_textsonly for a text specific to the unitkeycited by land_units
Copying a CA unitCA's keywh_dlc07_brt_cav_knights_errant_0your keyatlas_chevaliersIn green, what changes; “kept”, CA's value.Your files: db/<table>_tables/atlas_…no @: you are addingmain_unitsunitCA's key→atlas_chevaliersland_unitCA's key→atlas_chevalierscastemelee_cavalrykeptland_unitskeyCA's key→atlas_chevaliersmelee_attack28keptunit_variantsnameCA's key→atlas_chevaliersfaction(empty)keptunitCA's key→atlas_chevaliersvariantwh_dlc07_brt_knights_errantkeptunit_cardwh_dlc07_brt_knights_errantkeptname: not in the key; CA puts the unit's key there, andunit_variants_colours cites it.units_to_groupings_military_permissionsunitCA's key→atlas_chevaliersmilitary_groupwh_main_group_bretonniakeptbuilding_units_allowedkey→4 new numbersunitCA's key→atlas_chevaliersbuildingwh_main_brt_stables_2 … _5keptat CA: 60317, 60343, 63497, 63640, one row per stable; and two Copherports.the numbers: right-click → Generate Ids.units_custom_battle_permissionsunitCA's key→atlas_chevaliersfactionwh_main_brt_bretonniakeptgeneral_unit0kepttext/db/atlas.loca file of yourskeyland_units_onscreen_name_atlas_chevalierstextthe name shownA CA key left behind: your row competes with CA's.
The guide's example, filled in with CA's Knights Errant rows (Assembly Kit, patch 8.1): your key wherever CA's appears (unit_variants' name included) and new numbers in building_units_allowed, in green; the rest kept for a first try, in grey. Check: right-click your key → Find References.

What changes, what stays§

RPFM also has a Unit Editor, experimental according to its manual; this guide follows the tables route.

The unit's optional tables§

According to Casualfield's guide, depending on what the unit must do:

The unit's texts§

The unit's minimum viable set§

The unit's pitfalls§

What the unit needs from the startpos§

Nothing, except to exist from turn 0 (start_pos_land_units, start_pos_settlement_garrisons, mercenary pools); even then, a script spares you the startpos: cm:create_force_with_general places an army, cm:add_unit_to_faction_mercenary_pool stocks a pool.

Checking that the unit works§

  1. Diagnostics: no invalid reference, no missing text.
  2. Custom battle with the chosen battle faction: the unit is in the list, with its card and its name; in battle, the model is whole.
  3. New campaign: the unit can be recruited at every building level listed.

Procedure 2: adding a faction§

A faction fits in a few tables, but it only exists on the map through the startpos.

Before you start: what to gather§

A target campaign, an existing culture and subculture, the flags, and time to generate a startpos.

The figure shows a faction's tables, and the path from the start tables to the startpos.

A factionfactionskey: keysubculture → cultures_subculturesname_group → names_groupsmilitary_group → groupings_militaryflags_path = ui\flags\<key>ui/flags/<key>/mon_24.png · mon_64.png · mon_256.png…the flags: images to supplyfaction_agent_permitted_subtypeskey: faction + agent + subtypesubtype → agent_subtypesthe lords and heroes allowedtext/db/<mod>.locfactions_screen_name_<key>factions_screen_adjective_<key>factions_screen_name_when_rebels_<key>factions_attack_desc_<key>factions_defend_desc_<key>start_pos_factionskey: ID (a new integer)faction, campaign, playable, is_majorcai_personality_grouprequiredstart_pos_regionskey: region + campaignowning_faction → start_pos_factions.IDstart_pos_characterskey: IDfaction → start_pos_factions.IDType = generalministerial_position = faction_leaderstart_pos_land_unitskey: idgeneral → start_pos_characters.IDunit_type → main_unitsThen the startposstart_pos_*your tablesthe gamebuilds itstartpos.esfin the packOne startpos per campaign; Immortal Empires and Realm ofChaos: Mixer.
A faction: its factions row, its flags, its allowed lords, its texts and its start_pos_* tables. The game builds the startpos from them, only one per campaign, which your mod replaces entirely: for Immortal Empires and Realm of Chaos, the community goes through Mixer.

The faction's steps§

  1. factions: copy a neighbouring faction (wh_main_brt_bordeleaux) under a new key; set subculture, name_group, military_group, flags_path = ui\flags\<key> and the colours (or Tools → Faction Painter).
  2. The flags: a ui/flags/<key>/ folder like CA's: mon_24.png, mon_64.png, mon_256.png (24, 64 and 256 pixels), mon_64_glow.png, mon_icon.png, mon_rotated.png, mon_banner.dds (CA's own file names, read on patch 8.1). Which ones are essential is not confirmed: supply the full set.
  3. faction_agent_permitted_subtypes: the model's rows, which say which lords and heroes the faction can have (Bordeleaux has 12: general, colonel, minister, champion, wizard).
  4. The texts, then the start tables of the table below, in the kit (Dave) or in the pack (RPFM), plus start_pos_character_to_settlements for a garrisoned leader and start_pos_diplomacy.
  5. The startpos, regenerated (see “What needs a new startpos”), stored in campaigns/<campaign>/startpos.esf in the pack; then Diagnostics, Install (except for a pack opened from the data folder, which Install refuses), new campaign.
TableWhat to setKeyLinks
factionsthe factionkeysubculture → cultures_subcultures; name_group → names_groups; military_group → groupings_military
faction_agent_permitted_subtypesthe lords and heroes allowedfaction + agent + subtype→ agent_subtypes
start_pos_factionsID (a new integer), faction, campaign, playable, is_major, cai_personality_groupID→ factions, campaigns
start_pos_regionsowning_faction = that ID, faction_capitalregion + campaign→ start_pos_factions.ID
start_pos_charactersthe leader: Type = general, subtype, ministerial_position = faction_leader, portrait_id, startx and starty, Name and SurnameIDfaction → start_pos_factions.ID; Name → names.id; portrait_id → campaign_character_art_sets
start_pos_land_unitsthe army: unit_type, general, soldiersid→ main_units; → start_pos_characters.ID
A faction's flagsfactionsflags_pathui\flags\<key>ui/flags/<key>/mon_24.png24 pxmon_64.png64 pxmon_256.png256 pxFour more files:mon_64_glow.png100 × 100mon_icon.png256 × 256mon_rotated.png112 × 90mon_banner.dds256 × 256 (DXT5)“mon” is CA's own file name; names and sizes read from the game's files(patch 8.1)?Which ones are essential: not confirmed.Supply the full set.
A faction's ui/flags/<key>/ folder, like CA's: three sizes (24, 64 and 256 pixels) and four more files, names and sizes read from the game's files (patch 8.1), “mon” included. Which ones are essential is not confirmed: supply the full set.

The faction's texts§

factions_screen_name_<key>, factions_screen_adjective_<key>, factions_screen_name_when_rebels_<key>, factions_attack_desc_<key> and factions_defend_desc_<key>; the leader's name comes from names_name_<id>.

The faction's minimum viable set§

factions, the flags, faction_agent_permitted_subtypes, the texts, start_pos_factions, a region or a leader with his army, and the regenerated startpos.

The faction's pitfalls§

What the faction needs from the startpos§

Everything that makes it exist: the wiki lists playable factions, like those that appear during a game, among what needs a startpos. Once the faction is in the startpos, scripts take over: cm:transfer_region_to_faction(region, faction), cm:create_force_with_general(...), or CA's Campaign Custom Starts system (regions, armies, diplomacy, effects).

Checking that the faction works§

  1. Clean diagnostics; campaigns/<campaign>/startpos.esf is really in the pack, dated from the generation.
  2. Start tables served through a mod pack: %APPDATA%\The Creative Assembly\Warhammer3\crash_report\bad_mods_report.txt names the faulty record, one at a time.
  3. New campaign: the faction is on the map, in its region, with its flag and its name; its leader leads his army.
  4. If playable, it can be picked on the selection screen once procedure 3 is done for its leader.

Procedure 3: adding a legendary or playable lord§

Three stages: A, the lord exists in the database; B, a script makes him appear in an existing faction; C, he can be picked on the “New Campaign” screen, and only this stage needs a startpos. Prerequisites: his battle unit (procedure 1), his model, his portrait.

A lord in three stagesNo startposAThe databaseagent_subtypesunits_custom_battle_permissionsand the look, the skills, the texts;general_unit = 1: for custom battlesHe can be picked in a custom battle.BA script?cm:spawn_character_to_poolfor a faction that already existsto be tested: unproven in Warhammer IIINew game: among the lords to recruit.With a regenerated startposCThe “New Campaign” screenstart_pos_charactersstart_pos_starting_general_optionsfrontend_faction_leadersstartpos.esfand playable = 1 in start_pos_factionsHis record shows on the selection screen.
Three stages: A, the lord exists in the database; B, a script makes him appear in a faction that already exists (to be tested); C, he can be picked on the “New Campaign” screen. Only stage C needs a regenerated startpos.

Stage A: the lord in the database§

  1. agent_subtypes: copy a neighbouring lord (wh_dlc07_brt_alberic); auto_generate unticked for a unique character, associated_unit_override = his unit, recruitment_category = legendary_lords, recruitable = 1 and cap = -1 (like Alberic). Leave magic_lore empty: filled in, with legendary_lords, it can hide the other legendary lords of the recruitment pool (wiki, Agent subtypes tables page).
  2. The appearance: campaign_character_art_sets → campaign_character_arts (a unique id) → agent_uniforms → variants.
  3. The portrait: Warhammer III reads it from ui/portraits/portholes/portrait_settings*.bin, arranged by art set (for Alberic, portrait_settings__3.bin, a 300 × 164 thumbnail: read on CA's files, patch 8.1). In RPFM: Create… → Create Portrait Settings, then its editor. In CA's rows, campaign_character_arts.portrait stays empty.
  4. The skills: character_skill_node_sets (column agent_subtype_key), character_skill_node_set_items (set, item), character_skill_nodes (tier = the tree's column, indent = the row), character_skills then character_skill_level_to_effects_junctions, character_skill_node_links for the arrows.
  5. faction_agent_permitted_subtypes (the faction, general, the subtype) and names (new id values for the forename and the surname). For custom battles: units_custom_battle_permissions with general_unit = 1, general_portrait and general_uniform.
Stage A: the databaseagent_subtypeskey: keyassociated_unit_override → main_unitsrecruitment_category = legendary_lordsauto_generate unticked: a unique charactercampaign_character_art_setskey: art_set_idagent_subtype → agent_subtypescampaign_character_artskey: iduniform → agent_uniformsagent_uniformskey: uniform_namefilename, battle_filename → variantsui/portraits/portholes/portrait_settings*.binportrait settings, per art setcharacter_skill_node_setskey: keyagent_subtype_key → agent_subtypescharacter_skill_node_set_itemskey: set + itemitem → character_skill_nodescharacter_skill_nodeskey: keytier: the column (below 48); indent: the rowcharacter_skill_key → character_skillscharacter_skillsits effects:character_skill_level_to_effects_junctionsfaction_agent_permitted_subtypeskey: faction + agent + subtypeagent = generalthe faction may have himnameskey: idnew ids for the first and last name; their texts:names_name_<id>
Stage A: the lord's tables as a tree, from his subtype to his look, his portrait, his skill tree, his faction and his name.
TableWhat to setKeyLinks
agent_subtypesthe subtypekeyassociated_unit_override → main_units
campaign_character_art_setsthe art setart_set_idagent_subtype → agent_subtypes
campaign_character_artsthe appearance, per leveliduniform → agent_uniforms
agent_uniformsthe campaign map and battle modelsuniform_namefilename, battle_filename → variants
character_skill_node_setsthe skill treekeyagent_subtype_key → agent_subtypes
character_skill_node_set_itemsthe tree's nodesset + item→ character_skill_nodes
character_skill_nodesa skill's positionkeycharacter_skill_key → character_skills
frontend_faction_leadersthe selection screen recordkey (free)agent_subtype_record, faction
start_pos_starting_general_optionsthe link from the character to his recordidgeneral → start_pos_characters.ID
The skill tree: a gridtier→ the column01234indentthe row ↓012This node: tier = 2, indent = 1the arrows:character_skill_node_linksthe lord's tree:character_skill_node_set_itemstier: below 48 (wiki); beyond that, the node does not work
In character_skill_nodes, tier gives a node's column and indent its row (Assembly Kit schema); tier stays below 48 (wiki). The arrows come from character_skill_node_links.

The lord's texts§

The lord's minimum viable set§

agent_subtypes, the battle unit, the art set, arts, uniform and variant, faction_agent_permitted_subtypes, names, the texts, and a script call.

Stage B: spawning him by script§

Without a startpos, a script can add the lord to a faction that already exists. This skeleton follows the signatures in CA's scripts, to be tested:

-- skeleton, to be tested: a lord in an existing faction's pool
cm:add_first_tick_callback(function()
  if cm:is_new_game() then
    cm:spawn_character_to_pool("wh_main_brt_bordeleaux", "names_name_<id>", "names_name_<id>",
      "", "", 30, true, "general", "<subtype>", true, "<art set>")
  end
end)
One call, piece by piececm:spawn_character_to_pool(1"wh_main_brt_bordeleaux",The faction (it exists)2"names_name_<id>",First name (names)3"names_name_<id>",Surname (names)4"",Clan: empty5"",Other name: unused, empty630,Age7true,Male8"general",Agent type9"<subtype>",Your subtype10true,Immortal11"<art set>"Its art set)?To be tested: skeleton unproven in Warhammer III.cm:is_new_game(): the call only happens at the start ofa new game
Stage B's call, argument by argument, in the order of CA's scripts: the faction, first name and surname (names_name_<id> keys), clan, other name, age, male, type, subtype, immortal, art set. To be tested.

Stage C: picking him on the “New Campaign” screen§

  1. start_pos_characters: the character (see procedure 2).
  2. start_pos_starting_general_options: general (the character) → frontend_faction_leader (his record). Without this table, no lord can be picked.
  3. A frontend_faction_leaders record, with a free key: character_image (an 85 × 115 portrait, read on CA's at patch 8.1), uniform (the .csc scene), video, loading_screen_image, agent_subtype_record, faction.
  4. start_pos_factions.playable = 1, then the regenerated startpos.
Stage C: the selection screenstart_pos_factionskey: IDplayable = 1start_pos_characterskey: IDthe character: see procedure 2start_pos_starting_general_optionskey: idwithout this table, no lord can be pickedfrontend_faction_leaderskey: key (free)startpos_map = defaultagent_subtype_record, factioncharacter_image (85 × 115)loading_screen_imageoverride_force_location_x/ya fraction of the map; −1 = defaultfactionhis factiongeneralthe characterfrontend_faction_leaderhis recordThen a regenerated startpos: only this stage needs one.
Stage C: start_pos_starting_general_options links the startpos character to his frontend_faction_leaders record (startpos_map = default), then the startpos is regenerated.

The lord's pitfalls§

The selection screen's markerthe campaign map, in hexes0↔ width↕ heightxyoverride_force_location_x = x / widthoverride_force_location_y = y / heightat CA, Eltharion: 0.398 and 0.420−1: the default position; a lord in a garrison, withoutthis setting, stays in the corner of the Map tab.
override_force_location_x and _y place a lord's marker on the selection screen's map: his position in hexes, divided by the map's width for x and by its height for y; −1 keeps the default position.

Checking that the lord works§

  1. Diagnostics: no undeclared art set or variant, no invalid reference.
  2. Stage A: in a custom battle, he can be picked as his faction's general, with his portrait and his uniform.
  3. Stage B: in a new game with the faction, he is among the lords to recruit, or at the head of his army. If not, read the script log: a non-empty script/enable_console_logging file in a loaded pack makes the game write script_log_*.txt files.
  4. Stage C: on the “New Campaign” screen, his record appears with his portrait and his description, his marker is in place on the Map tab, and the game starts with him.

What needs a new startpos§

Startpos or script?One startpos per campaign; Immortal Empires and Realm ofChaos: Mixer.Needs the startposPlayable factions, or onesthat appear during the gameA culture's agent typesLandmark slotsFaction potentialNon-aggression and militaryaccessA fixed ID (CQI) for theleader: for a quest battleNames offered for recruitmentScript or databaseRegiments of renowncm:add_unit_to_faction_mercenary_poolRegion, leader, army, items:Campaign Custom Startscm:create_force_with_generalcm:transfer_region_to_factionThe selection screen's markeroverride_force_location_x/yTech tree: nodes with afaction column, plus a scriptA faction's features, hordeor not (feature forest)cm:add_or_remove_faction_featuresDatabase only: units, subtypes, looks, skills, texts,the factions table.
The startpos records turn 0: what it holds only changes if it is regenerated, and a mod replaces it entirely. From the wiki's Startpos page: on the left, what requires it; on the right, what a script or the database does without it.

The startpos is the turn-0 save: what it records only changes if you regenerate it. The Assembly Kit's schema marks with a flag, requires_startpos_reprocess, the columns that go into it: read it in TWaD_<table>.xml. On patch 8.1, 42 tables carry one:

Units, subtypes, appearances, skills, texts and the factions table change in the database alone. Three ways produce a startpos: RPFM's Build Startpos, BOB's Process start pos action, or a user.script.txt written by hand. In our workshop, only writing it by hand worked: the tools guide.

Build Startpos, in RPFM§

Right-click the pack's root → Build Startpos has the game itself generate the startpos (palette, Ctrl+Shift+P: “Build Startpos”). Version 5.0.6 no longer has a Special Stuff menu: the “Special Stuff → …” paths of older tutorials and of the wiki apply to version 4.

Build StartposThe start tables in the packcreate them, or import them from the Assembly Kit throughthe Dependencies panel.start_pos_*campaigns1The pack opened from the game's data folderSave Pack As… into data, or PackFile → Open From Data;otherwise RPFM refuses.<game>\data\<mod>.pack2Right-click the root → Build Startpospick the campaign; Process HLP and SPD data: for a new maponly.With this box, the time varies a lot: RPFM announces 10 to 30minutes, tw-modding well over an hour for a large map.3Build Startpos button: the game starts, builds,closesSteam running; Play in the launcher if needed.RPFM first wrote your user.script.txt (the old one kept as .bak);the third line only with Process HLP and SPD data:mod <mod>.pack;process_campaign_startpos <campaign>;process_campaign_ai_map_data;quit_after_campaign_processing;4Don't close RPFM here.Hit this when the game is closedRPFM puts startpos.esf (and the hlp and spd files) into thepack and restores your user.script.txt.campaigns/<campaign>/startpos.esf5Ctrl+S, then a new campaignthe pack is already in data.Before publishing: remove the start_pos_* tables from the pack.6
Build Startpos has the game itself build the startpos, from the start tables of the pack opened from data. Don't close RPFM before the click of step 5: it puts startpos.esf into the pack and restores your user.script.txt, otherwise the game would close at every launch.
  1. Put the start_pos_* and campaigns tables in the pack: create them, or import them from the Assembly Kit through the Dependencies panel. The instructions RPFM displays also ask for db/victory_objectives.txt, but not for Warhammer III (RPFM's code; changelog, version 4.3.11).
  2. The open pack must be saved in the game's data folder (Save Pack As…, or PackFile → Open From Data): otherwise RPFM refuses.
  3. Right-click the pack's root → Build Startpos; pick the campaign (the list comes from your campaigns table). Only tick Process HLP and SPD data (the AI's map data, hlp_data.esf and spd_data.esf) for a new map. The time varies a lot: 16 s for us (400 × 440 hexes); tw-modding's campaign map tutorial gives under a minute for spd, about five minutes for hlp on a medium map and well over an hour on a large one; RPFM announces 10 to 30 minutes.
  4. Click Build Startpos: the game starts (Steam open; click Play in the launcher if needed), generates, then closes by itself. Don't close RPFM meanwhile.
  5. Once the game has closed, click Hit this when the game is closed: RPFM puts campaigns/<campaign>/startpos.esf (and, if the option was ticked, the hlp and spd files) into the pack, and puts your user.script.txt back as it was.
  6. Save the pack (Ctrl+S), then start a new campaign.

One clarification: you write the start_pos_* tables in the kit (Dave) or in RPFM, but the game does not read the kit's XML files; for generation, they must therefore be in a loaded pack (for Build Startpos, the very pack it generates). Once the startpos is produced, remove them from the published pack: they are no longer used. In our workshop, the game validates a mod pack's start_pos_* tables at every launch, and %APPDATA%\The Creative Assembly\Warhammer3\crash_report\bad_mods_report.txt then names the faulty record.

RPFM for an AI: the MCP server§

Since version 5, RPFM has a server, rpfm_server.exe. It speaks MCP (Model Context Protocol), the standard plug for AI assistants: an AI connected to it opens packs, reads and writes tables, searches everywhere and runs diagnostics, in its own session, separate from RPFM's window.

  1. If RPFM's window is open, its server is already running: the window starts it itself. Otherwise, run rpfm_server.exe without arguments (it has no --help option: it starts at once). It listens on 127.0.0.1:45127 and stops when no session is left open.
  2. Point your assistant at http://127.0.0.1:45127/mcp (Streamable HTTP). With Claude Code: claude mcp add --transport http rpfm http://127.0.0.1:45127/mcp. Any Streamable HTTP MCP client works.
  3. Ask it to read the server's “Initialization guide” resource first: it describes the order of calls (select the game, check the schema, open the pack).
An AI plugged into RPFMOne session per connectionthe AI does not see your openpacks.RPFM's windowyour open packsrpfm_server127.0.0.1:45127The assistantyour AI/ws/mcpStreamable HTTPFor the startposAsk the human before launching the game.build_starpos"Success" = game launchedwait for the game to closebuild_starpos_poststores startpos.esf, restores user.script.txtsave_packfileto give up: build_starpos_cleanup
RPFM's window starts rpfm_server itself; the assistant plugs in through /mcp and gets its own session: it does not see the packs open in the window. For the startpos, build_starpos only launches the game; build_starpos_post stores the file.
set_game_selected   (warhammer_3, rebuild_dependencies: true)
→ is_schema_loaded            (otherwise update_schemas)
→ open_packfiles              (returns the pack_key)
→ open_pack_info              (the pack tree)
→ decode_packed_file          (e.g. db/land_units_tables/<file>)
→ fields_processed            (the rows' shape)
→ save_packed_file_from_view
→ diagnostics_check
→ save_packfile

For an AI reading this page§

For an AI helping to mod Total War: Warhammer III with RPFM, the essentials as checkable facts, taken on RPFM 5.0.6 and patch 8.1 of the game (September 2026), reviewed for update 9.0: check them again after an update (schemas, dependencies cache; and, if the Assembly Kit is used too, the rows added to raw_data\db, which Steam rewrites).

Frequently asked questions

Which RPFM version should I use for Warhammer III?

5.0.6, the latest stable release as of 24 September 2026, whose menus this guide follows. The online manual describes that same version, sometimes with labels that differ from the window: the palette (Ctrl+Shift+P) finds the action by name.

My mod crashes the game on launch: what should I check?

RPFM's diagnostics (Check PackFile). An invalid reference is one of the leading causes of crashes on launch, and an empty mandatory field may crash the game, often without an error message.

Do I need a new startpos to add a unit?

No. Only starting armies and garrisons and mercenary pools require one, and a script can handle them (cm:create_force_with_general, cm:add_unit_to_faction_mercenary_pool).

Can I add a faction without touching the startpos?

No: everything that makes it exist goes through the startpos, of which each campaign has only one, which a mod replaces entirely. Your mod will therefore be incompatible with any other startpos mod for that campaign. For Immortal Empires and Realm of Chaos, ask instead for your faction to be added to Mixer, most players' startpos mod, then do the rest by script.

Why doesn't my unit show up in custom battles?

It needs main_units, land_units, unit_variants, its card (a 60 × 130 PNG), its name in a .loc file and a units_custom_battle_permissions row for the battle faction, with general_unit set to 0.

How do I make a lord selectable on the new campaign screen?

The character in start_pos_characters, a start_pos_starting_general_options row linking him to a frontend_faction_leaders record (startpos_map set to default), start_pos_factions.playable set to 1, then a regenerated startpos.

Why do my texts replace the game's in every language?

The game loads a mod's .loc files whatever the player's language. Only add new keys, and make one pack per language.

How do I connect an AI to RPFM?

If RPFM's window is open, its server is already running; otherwise, run rpfm_server.exe (RPFM 5). Then add the MCP server http://127.0.0.1:45127/mcp to your assistant; with Claude Code: claude mcp add --transport http rpfm http://127.0.0.1:45127/mcp.

Why doesn't my mod show up in the launcher?

Check that it is installed in the game's data folder and that its type is Mod: right-click the pack's root, Change PackFile Type, Mod. In the same submenu, the Index Includes Timestamp box must be unticked: according to RPFM, a pack with it ticked does not show up in the official launcher.

Why does my change to a game unit do nothing?

Your table file sorts after the game's (data__): so the game's row wins. Start your file's name with @ or !, then install the pack again.

Some tables are flagged Outdated after a game update: what should I do?

Update the schemas (About, Check Updates), then the cache (Game Selected, Generate Dependencies Cache). Then right-click each flagged table, Update Table, and read through the new columns: RPFM puts default values in them.

How do I remove a game row?

With the twad_key_deletes table (Warhammer III, since patch 6.3), never by copying the game's table: create twad_key_deletes_tables once in your pack, select the rows in the game's table, then right-click, Add Selection to Key Deletes. Don't remove a row still cited by another table: the game would crash.

Why does the game close as soon as it starts?

A quit_after_campaign_processing; line was left in user.script.txt (%APPDATA%\The Creative Assembly\Warhammer3\scripts): a startpos generation was interrupted before its end. Remove the line or put back the previous file. With Build Startpos, always click Hit this when the game is closed, which puts it back.

This guide as plain text (Markdown), for AIs and offline reading

This is a community guide

A mistake, a pitfall you hit, a tip or a newer tool version: tell us, and every correction will be checked, then added with its source.

Suggest an idea
↑ Contents