Skip to main content

Content Registry

UModContentRegistry is a GameInstance subsystem that parses each mod's ModContributions.json and makes their contributions available to your game. It handles three types of contributions automatically.

Contribution Types

1. New Content

Mods can add entirely new assets — new maps, new items, new characters — using their plugin mount point (/Plugins/ModId/). These are available via the Asset Registry without any special handling.

Use QueryModAssets() on UModLoaderSubsystem to find new content from mods:

FModAssetFilter Filter;
Filter.AssetClass = UStaticMesh::StaticClass();
Filter.NameContains = "chair";

TArray<FAssetData> Assets = ModLoader->QueryModAssets(Filter);

2. Class Overrides

Mods can replace one of your Blueprint classes with their own version. Declared in ModContributions.json:

{
"ClassOverrides": [
{
"Original": "/Game/Characters/BP_Enemy.BP_Enemy_C",
"Replacement": "/Game/Mods/MyMod/Characters/BP_BetterEnemy.BP_BetterEnemy_C"
}
]
}

When you spawn actors using SpawnResolvedActor(), ModKit transparently substitutes the override:

UModContentRegistry* Registry = GetGameInstance()->GetSubsystem<UModContentRegistry>();

// Spawns BP_BetterEnemy if a mod overrides BP_Enemy
AActor* Enemy = Registry->SpawnResolvedActor(
GetWorld(),
BP_Enemy_Class,
SpawnTransform
);

Conflict Policy

When multiple mods override the same class, EModConflictPolicy determines what happens. It is configured by you, the game developer, in Project Settings → Plugins → ModKit SDK (ClassConflictPolicy for classes, CollectionConflictPolicy for DataTables) — it is not set per-override in ModContributions.json:

PolicyBehavior
PriorityRespect the configured mod load order (default for class overrides)
LastWinsThe last-loaded mod's override wins
ErrorLog an error and reject the conflicting override

Querying Overrides

UModContentRegistry* Registry = GetGameInstance()->GetSubsystem<UModContentRegistry>();

// Resolve what class would be spawned for a given original class
UClass* Resolved = Registry->ResolveClass(BP_Enemy_Class);

// Get all registered overrides
TArray<FModClassOverride> AllOverrides = Registry->GetAllClassOverrides();

3. DataTable Merging

Mods can add or replace rows in your DataTables without shipping the whole table. ModKit detects DataTable assets in the mod's PAK at the same path as the original table, then merges them row-by-row at runtime.

Original game table at /Game/Data/DT_Items:

RowNameDamage
swordIron Sword15
bowWooden Bow8

Mod adds /Plugins/MyMod/DT_Items (mirrors the original path layout via contributions):

RowNameDamage
bowElven Bow20
staffMagic Staff25

Merged result (accessible via UModContentRegistry):

RowSourceNameDamage
swordgameIron Sword15
bowmod (override)Elven Bow20
staffmod (new)Magic Staff25
{
"CollectionMerges": [
{
"TargetDataTable": "/Game/Data/DT_Items",
"Rows": [
{ "RowName": "bow", "DataPath": "/Game/Mods/MyMod/Data/Row_Bow.Row_Bow" },
{ "RowName": "staff", "DataPath": "/Game/Mods/MyMod/Data/Row_Staff.Row_Staff" }
]
}
]
}

Explicit CollectionMerges are optional — a DataTable shipped at the mirror path (see the tip below) is merged automatically without any declaration.

Accessing Merged Tables

UModContentRegistry* Registry = GetGameInstance()->GetSubsystem<UModContentRegistry>();

// Rows contributed to a DataTable via JSON CollectionMerges declarations
TArray<FModTableRow> Rows = Registry->GetMergedCollectionRows(
FSoftObjectPath(TEXT("/Game/Data/DT_Items.DT_Items")));

// Paths of all DataTables that received mod row merges
TArray<FString> MergedTables = Registry->GetMergedDataTablePaths();

ModContributions.json Format

Full example of a ModContributions.json:

{
"ClassOverrides": [
{
"Original": "/Game/Characters/BP_Enemy.BP_Enemy_C",
"Replacement": "/Game/Mods/MyMod/Characters/BP_BetterEnemy.BP_BetterEnemy_C"
}
],
"CollectionMerges": [
{
"TargetDataTable": "/Game/Data/DT_Items",
"Rows": [
{ "RowName": "staff", "DataPath": "/Game/Mods/MyMod/Data/Row_Staff.Row_Staff" }
]
}
]
}

New content (maps, items, etc.) needs no declaration — any asset in the mod plugin is available automatically. Only class overrides and DataTable merges are declared.

Automatic Detection

ModKit can auto-detect DataTable merges by path mirroring — if a mod has a DataTable at the same /Game/ path as an existing table, it's treated as a merge candidate even without a CollectionMerges entry.

UModContentRegistry Reference

FunctionReturnsDescription
SpawnResolvedActor(World, Class, Transform)AActor*Spawn with class override applied
ResolveClass(OriginalClass)UClass*Get the effective class (mod override or original)
GetAllClassOverrides()TArray<FModClassOverride>All registered class overrides
GetContentByType(Type)TArray<FModContentEntry>New-content entries of a given type (e.g. "Map"); empty = all
GetMergedCollectionRows(DataTable)TArray<FModTableRow>Rows contributed to a DataTable via CollectionMerges
GetMergedDataTablePaths()TArray<FString>Paths of DataTables that received mod row merges
GetContributionsByMod(ModId)FModContributionsAll contributions registered by a mod