Renaming Content Safely
What you can rename in a pack for free, what needs a redirects entry, and the one rename redirects genuinely can't fix.
Sooner or later you’ll want to rename something in a pack that’s already shipped. Three different things can be “renamed,” and only one of them is free. This walks through all three, the exact mechanism behind the safe one, and a worked before/after so you can copy-paste the real fix.
The three renames
Section titled “The three renames”| What you rename | Changes the generated class path? | Safe by default? | Fix |
|---|---|---|---|
A file (super-ingot.item.yml → ingot.item.yml) | No | Yes, always | Nothing: the filename is never part of any generated identity |
An id (id: MyItem → id: MyItemV2) | Yes | No | Add a redirects: entry in pack.yml (see below) |
A pack’s ref | Yes, for every id in the pack | No, and there’s no fix | Don’t. See “Renaming the pack ref” below |
Generated classes live at /KDataForge/Gen/<PackRef> and are named Gen_<PackRef>_<Id>. This
naming is deterministic and built from PackRef + id, never from a file path
(Generated class identity).
That’s why the file is free and the other two aren’t: the file was never part of the identity to
begin with.
What redirects: actually does
Section titled “What redirects: actually does”Syntax, in pack.yml:
ref: MyPackversion: 2.0.0redirects: OldId: NewId # OldId: NewId, one entry per renamed idUnder the hood (FKDFDynamicContentRegistry::ApplyRedirects in
Source/KDataForge/Private/Content/KDFDynamicContent.cpp), each entry becomes one
FCoreRedirect built from the pack’s current ref:
const FString OldPath = MakeClassPackageName(PackRef) + TEXT(".") + MakeClassName(PackRef, Redirect.Key);const FString NewPath = MakeClassPackageName(PackRef) + TEXT(".") + MakeClassName(PackRef, Redirect.Value);Redirects.Emplace(ECoreRedirectFlags::Type_Class, OldPath, NewPath);So a MyPack redirect MySuperIngot: SuperIngotMk2 registers a Type_Class redirect from
/KDataForge/Gen/MyPack.Gen_MyPack_MySuperIngot to /KDataForge/Gen/MyPack.Gen_MyPack_SuperIngotMk2
via FCoreRedirects::AddRedirectList. When an old save deserialises a reference to the old class
path, the engine’s own class-path resolution walks through FCoreRedirects first and lands on the
new class instead. The object loads normally, with no missing-class warning.
It has to ship in the same version bump as the rename. ApplyRedirects runs once per pack ref,
every session, right before any generated class can be referenced
(UKDFSubsystem::ApplyStages, guarded by mRedirectedPacks so a live a session restart doesn’t
re-register it).
There’s nothing baked into the save. The redirect is re-derived from pack.yml on every load. That
has one important consequence: if you ever delete the redirects: entry in a later version, the
redirect stops being registered on the very next launch, and any save that still references the old
class path breaks again.
There’s no expiry or “safe after N versions” in the code. Keep the entry in redirects: for as long
as you want to support saves made before the rename. In practice that means indefinitely, unless
you’re certain every player has re-saved since.
Worked scenario: MySuperIngot → SuperIngotMk2 in v2.0.0
Section titled “Worked scenario: MySuperIngot → SuperIngotMk2 in v2.0.0”Before (pack version 1.0.0), generates /KDataForge/Gen/MyPack.Gen_MyPack_MySuperIngot:
type: itemitems: - id: MySuperIngot parent: /Game/FactoryGame/Resource/Parts/IronIngot/Desc_IronIngot.Desc_IronIngot_C properties: - path: mDisplayName value: Super IngotAfter: rename the id, bump pack.yml, and add the redirect in the same version:
type: itemitems: - id: SuperIngotMk2 # renamed parent: /Game/FactoryGame/Resource/Parts/IronIngot/Desc_IronIngot.Desc_IronIngot_C properties: - path: mDisplayName value: Super Ingotref: MyPackname: My Content Packversion: 2.0.0redirects: MySuperIngot: SuperIngotMk2 # OldId: NewIdThe class is now generated as /KDataForge/Gen/MyPack.Gen_MyPack_SuperIngotMk2. Any reference
elsewhere in your own YAML (recipes, schematics, sink points) needs updating to the new path too.
redirects: only fixes resolution of old save data: it doesn’t rewrite your other documents for
you. Any save made under v1.0.0 that still has Gen_MyPack_MySuperIngot embedded resolves straight
through to Gen_MyPack_SuperIngotMk2 on load, with no warning.
A second worked copy of this exact scenario also lives in End-to-End Pack → Renaming an id later if you want to see it next to a full multi-file pack.
Renaming the pack ref
Section titled “Renaming the pack ref”Don’t. This is the one case where the honest answer is “unsupported,” not “here’s the workaround.”
ApplyRedirects takes a single PackRef and builds both the old and new class paths from it:
MakeClassPackageName(PackRef) is the same call for OldPath and NewPath. There is no code path
that builds an old path from one ref and a new path from a different ref.
Since a ref rename changes /KDataForge/Gen/<PackRef> for every single generated class in the pack
simultaneously, there’s no mechanism, redirects: or otherwise, that bridges an old ref’s package to
a new ref’s package. Every id in the pack would need its own cross-package redirect, and the feature
simply doesn’t build one.
Treat ref: as permanent once you’ve shipped a version with real players. If you genuinely need a
different display name, change name: instead. ref: is the save-persistence contract, and name:
is cosmetic.
Troubleshooting
Section titled “Troubleshooting”- Shipped an id rename, then got reports of missing items in old saves: you forgot the
redirects:entry. Add it retroactively in a follow-up version. It’s read fresh at every load, never baked into the save, so adding it late still fixes existing saves the next time they load. No save wipe needed. - Added a redirect, but old saves still show a missing class: check direction and spelling
first. The map is
OldId: NewId, not the reverse, and both sides are the rawid:value (not the display name), matched exactly.SanitizeTokenonly swaps non-alphanumeric characters for underscores, so it doesn’t fix a typo or a case mismatch. - How to verify a redirect actually took effect:
ApplyRedirectslogs one line per entry to the game log atLogseverity (visible without enablingKDF.Debug):[LogKDataForge] Redirect: /KDataForge/Gen/MyPack.Gen_MyPack_OldId -> /KDataForge/Gen/MyPack.Gen_MyPack_NewId./kdf reportshows load diagnostics (errors/warnings/tombstones) but doesn’t list applied redirects directly, so the log is the source of truth here. - Accidentally renamed a pack
ref: there’s no redirect fix (see above). The practical options are reverting therefback, or accepting that saves made before the change lose those generated objects. They’ll come back as tombstones with a warning if the oldref’s YAML still exists somewhere, otherwise they’re gone.
The generated class identity contract
is the normative persistence reference. Conditions & Packs
has the compact redirects: syntax, while End-to-End Pack
shows the same rename inside a complete multi-file pack.
