Writing a BGS load order (plugins.txt)

SkillFiles & storage

Use when reading, editing, or generating a Bethesda Game Studio load-order file (plugins.txt / loadorder.txt) for Fallout 4, Skyrim SE/AE/VR, Fallout 76, Starfield, or when launching xEdit with a custom plugin list. Authoritative reference for the asterisk-prefix format, official masters detection, ESL handling, and the routing rules between plugins.txt editing and xEdit daemon commands.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Writing a BGS load order (plugins.txt) skill

What this skill tells your AI

The instructions your AI receives, as published by hashgraph-online/awesome-codex-plugins in plugins/BB-84C/bgs-modding-superpowers/skills/writing-bgs-load-order/SKILL.md and read by ahel’s review.

Harness status (2026-07-29): The local prototype harness was destroyed by an unattributed operation and will not be rebuilt. The historical .artifacts/mo2 example below is DEFUNCT. Testing now uses externally-configured Starfield or Fallout 4 MO2 instances; machine-specific details and the forensic record are private.

Sources for every claim in this skill: Ortham/libloadorder (the library behind LOOT), ModOrganizer2/modorganizer/src/profile.cpp, ModOrganizer2/modorganizer-basic_games/.../game_plugins.py, and xEdit's official docs Section 2.8.1.

Two file formats — know which one applies

FormatGamesFileActivation marker
Asterisk format (modern)Fallout 4, Skyrim SE/AE/VR, Fallout 4 VR, Starfieldplugins.txtleading * = active
Textfile-only (legacy)Skyrim LE, Oblivion, Fallout 3, Fallout NVplugins.txt (active only) + sibling loadorder.txt (full order)presence in plugins.txt = active

This skill covers the asterisk format in depth. If your target is Skyrim LE / Oblivion / FO3 / FNV, the rules are different — see the "Legacy format" section at the bottom.

File location

ContextPath
Vanilla install (no MO2)%LOCALAPPDATA%\<GameFolder>\plugins.txt (e.g. Fallout4, Skyrim Special Edition, Starfield)
MO2-managed<MO2_Root>\profiles\<ProfileName>\plugins.txt
MO2-managed sibling<MO2_Root>\profiles\<ProfileName>\loadorder.txt (full order including inactive; MO2 keeps both)
Agent-authored (experiments)an agent-owned artifacts path — generate your own, pass to xedit_start({ pluginsFile })

Ownership in MO2 use: MO2 writes plugins.txt whenever the user toggles plugin checkboxes in the GUI. LOOT can also write it. If you mutate it under MO2, do it while MO2 is closed OR via mobase's IPluginList API — live edits while MO2 is open get overwritten on its next save.

Asterisk format reference

# This file was automatically generated by Mod Organizer.
*Unofficial Fallout 4 Patch.esp        ← * = ACTIVE; loads first among non-vanilla
ArmorKeywords.esm                       ← no * = present-but-INACTIVE; position preserved
*HUDFramework.esm
SomeDisabledMod.esp
*MyMod.esp                              ← bottom = loads LAST = wins conflicts

Verbatim parser rules (from libloadorder/src/load_order/asterisk_based.rs):

  • Blank lines → ignored.
  • Lines starting with # (column 0) → comment, ignored.
  • Lines starting with * → plugin name follows; active.
  • Lines starting with any other character → plugin name; inactive but tracked.
  • No inline comments. # must be at column 0.
  • Encoding: Windows-1252. Plugin names with non-Win1252 characters cannot be activated until renamed.
  • Line endings: LF and CRLF both accepted on read. Prefer LF on write (libloadorder's default).
  • Order: first line = lowest load-order index = loaded first. Last line = wins conflicts.

排序判断 / Ordering judgment

Ordering is not list housekeeping. It decides which plugin record or archive payload the game actually sees when two mods touch the same thing: later wins. For loose files, remember the separate asset layer: loose files can win over BA2/BSA archive contents even when their plugin loads earlier. Use ordering to choose whole-record / whole-archive winners; use a patch when you need one final record stitched from multiple mods.

Iron law: if the desired result cannot be expressed as "one later plugin wins over one earlier plugin," stop sorting and audit/patch the conflict.

The source-mined principle comes from BB84's Vortex-era sorting lesson and the later xEdit "终极奥义": order is theoretically replaceable by a final compatibility patch, but patching every conflicting FormID in a real pack is not realistic. A sane order reduces the patch surface; it does not eliminate patching.

digraph sort_or_patch {
  rankdir=TB;
  start [label="Conflict or ordering question", shape=doublecircle];
  exists [label="Do the involved plugins actually load and have valid masters?", shape=diamond];
  fixfile [label="Fix plugins.txt/loadorder.txt validity first", shape=box];
  author [label="Does the mod author specify order or patch requirement?", shape=diamond];
  obey [label="Follow author instruction; verify with xEdit/readback", shape=box];
  winner [label="Can one plugin/archive win wholesale without losing needed data?", shape=diamond];
  sort [label="Solve by order: later winner goes after earlier loser; mirror loadorder.txt in MO2", shape=box];
  stitch [label="Need fields/data from multiple plugins in one final state?", shape=diamond];
  patch [label="Hand off to xedit-conflict-audit / xedit-automation and build or inspect a patch", shape=box];
  group [label="Is this about a per-game group template (lighting/weather/major compatibility-fix style)?", shape=diamond];
  kb [label="Query KB domain=load-order for the target game; if silent, mark [GAP]", shape=box];
  done [label="Verify winning override / loaded file chain", shape=doublecircle];

  start -> exists;
  exists -> fixfile [label="no"];
  exists -> author [label="yes"];
  fixfile -> done;
  author -> obey [label="yes"];
  author -> winner [label="no"];
  obey -> done;
  winner -> sort [label="yes"];
  winner -> stitch [label="no"];
  sort -> done;
  stitch -> patch [label="yes"];
  stitch -> group [label="not sure"];
  patch -> done;
  group -> kb [label="yes"];
  group -> patch [label="no"];
  kb -> done;
}

Judgment rules

  • Sort solves priority: which complete plugin record or BA2/BSA archive wins after a later-wins cascade.

  • Patch solves composition: which fields from multiple plugins must be preserved together in one final winning record.

  • Group ordering is a scale tool, not magic. Put mods into purpose-based groups so order can be reasoned about in batches; same-group conflicts still need a specific winner or a patch.

  • Masters and official early-loading plugins are not discretionary. Infer them from the current runtime as described below; user-managed patches generally live late because they intentionally carry final stitched decisions.

  • Prefer the later winner that expresses the pack's system rule over incidental edits from a content mod. Example principle: if one mod imposes the pack-wide equipment-slot rule and another content mod casually edits one vanilla slot, the system-rule mod usually belongs later. Verify the actual records.

  • Do not inline per-game group templates here. For lighting, weather, major compatibility-fix placement, major overhauls, or game-specific community group layouts, query the KB first:

    bgs_kb_query({ query: "load order group template lighting weather patch", games: ["<Game>"], domains: ["load-order"] })
    

    If the KB is silent, mark [GAP] instead of inventing a generic position.

LOOT / automatic sort vs manual sort

SituationDefault
Beginner / small pack, no author instruction, no known conflictUse LOOT or the manager's automatic sort as a baseline, then validate.
Author specifies order, dependency, or required compatibility patchFollow the author first; verify with xEdit/readback.
xEdit shows the automatic result chose the wrong winnerManually reorder if one winner is enough; patch if fields must be stitched.
Per-game group placement questionQuery KB load-order records; do not hardcode a cross-game template in this skill.
Recurring conflict family across many modsCreate/adjust a group rule, then patch only the records the group rule cannot express.

Red flags

ThoughtReality
"排序无所谓,xEdit能补一切."True only in principle; exhaustive FormID stitching is not realistic at pack scale. Sort first to reduce the patch surface.
"LOOT / auto-sort ran, so ordering judgment is done."Auto-sort is a baseline. It cannot know every pack-specific winner or every author-required exception.
"Same group means no conflict."Same group means conflicts should be rare. When two same-group mods touch the same record, decide winner or patch.
"Manager UI shows no conflict, so there is no conflict."Record conflicts require xEdit readback. Asset/archive conflicts require asset-precedence inspection.
"Just put every patch last and forget the rest."Patches late is correct, but a patch can only express decisions you actually audited.
"Lighting/weather/major compatibility-fix placement is universal."Those are per-game template facts. Query KB and mark [GAP] if the pack has no record.

Rationalizations

ExcuseReality
"I'll patch everything later."That is the impossible endgame. A sane order is how you avoid patching irrelevant conflicts one by one.
"Manual sorting is just vibes."Manual sorting is only legitimate when tied to a known winner, author instruction, group rule, or xEdit readback.
"The content mod should win because it adds new things."Content mods often carry incidental edits. A systemic-rule mod may be the right later winner.
"If two mods are incompatible, sorting harder will fix it."Some conflicts are ordering conflicts; some are real incompatibilities. Escalate to audit instead of looping order changes.
"Archive conflicts are invisible, so ignore them."Invisibility is why BA2/BSA precedence must be inspected rather than guessed.

If sorting does not produce the intended winning override, hand off to xedit-conflict-audit: inspect the record, name the winning override, decide whether the current winner is safe, and only then route to patch authoring.

Official / vanilla masters — infer them from the current game, do not hardcode

The engine loads each game's official plugins at fixed positions before reading the user-managed plugins.txt. The exact set depends on the target game, installed DLC, and any Creation Club / verified-content bundles.

Do not hardcode a per-game vanilla-master list in your workflow. Instead, derive the official masters from the user's actual MO2-managed runtime:

  1. Read <MO2_Root>\ModOrganizer.ini to learn gameName and gamePath.
  2. Once xEdit is ready, call xedit_call({ command: "files.list", args: {} }) and look at the earliest, auto-loaded official plugins. Treat those as the immutable official master set.
  3. If xEdit is not ready yet, inspect the MO2-managed game root (<gamePath>\Data) and known official DLC/content files there. Do not add, remove, or reorder them in your generated plugins.txt.

libloadorder's writer skips these early-loading official plugins on save; MO2 re-injects them as active at read time. So whether they appear in the file or not, they load first.

Practical rule for agent-authored plugins.txt: omit the official masters you inferred from the actual target runtime. Start your file with user-managed plugins instead.

Six operations the agent does most often

1. Enable a currently-disabled plugin

Prefix the line with *. Position unchanged.

Before:

ArmorKeywords.esm

After:

*ArmorKeywords.esm

2. Disable without removing

Strip the leading *. Position preserved (the plugin stays in the file).

Before:

*HUDFramework.esm

After:

HUDFramework.esm

3. Remove a plugin entirely

Delete the whole line. Position is dropped. (MO2 will re-append it as inactive at the end of loadorder.txt on next read — plugins.txt itself stays clean.)

4. Reorder (move X to load AFTER Y)

Cut the line, paste it after Y. Mirror the change in loadorder.txt if it exists (MO2 maintains both files; out-of-sync state confuses MO2).

5. Add a brand-new plugin

Append at the bottom with * if you want it active.

*MyExistingMod.esp
*JustInstalledMod.esp          ← new line at the end

The bottom = loads last = wins conflicts with everything above. If you don't want it to win conflicts, move it higher.

6. ESL conversion (ESP → light ESP)

Do NOT do this by editing plugins.txt. The light-plugin flag lives in the plugin's file header, not in the load-order file. Route through the xEdit daemon:

  1. xedit_call({ command: "plugin.esl.analyze", args: { file: "MyMod.esp" } }) — checks whether the plugin's FormIDs fit in the ESL FE-slot range.
  2. If verdict says yes: xedit_call({ command: "plugin.esl.apply", args: { file: "MyMod.esp" } }) — sets the light-plugin flag in the file header.

After flagging, the file extension stays .esp. The engine reads the new flag at load time and remaps the plugin into the shared FE slot.

plugins.txt does NOT change as part of ESL conversion.

Routing matrix: plugins.txt edit vs xEdit daemon command

OperationRight pathWhy
Activate / deactivate a pluginEdit plugins.txt (toggle *)No daemon verb exists for activation; the daemon manipulates plugin contents, not the activation file.
Reorder pluginsEdit plugins.txt + mirror in loadorder.txtxEdit docs 2.3 (verbatim): "Load order cannot be changed with xEdit."
Remove a plugin entryEdit plugins.txtSame reason.
Add new plugin to load orderEdit plugins.txt (append)Daemon doesn't write the activation file. MO2 will project the mod's .esp via VFS once the file references it.
Check a plugin's mastersxedit_call({command:"files.get_masters", args:{file:"..."}})Reads the plugin header.
Add a missing master to a pluginxedit_call({command:"files.add_required_masters", args:{...}})Mutates the file header.
Convert ESP → ESLplugin.esl.analyzeplugin.esl.applySets header flag; load-order file unchanged.
Clean ITM / UDRxEdit launcher with -quickAutoClean OR daemon files.clean_mastersBoth work; CLI faster for batch.
Sort the load orderExternal LOOTNeither xEdit nor MO2 sorts; LOOT does, then writes plugins.txt.
Verify a load order is validCombine: read plugins.txt, then daemon files.list to confirm each *-prefixed file exists with required masters present.Two-sided: file presence (libloadorder) + header consistency (xEdit).

The rule of thumb: plugins.txt is an activation surface owned by MO2 (or LOOT, or the agent). The xEdit daemon never touches it. The daemon's domain ends at the file header of individual plugins.

Generating a custom plugins.txt for xEdit experiments

When you want xEdit to load only a subset of mods (e.g., to isolate a conflict between two specific plugins), generate a custom plugins.txt under an agent-owned artifacts path and pass it to xedit_start:

// 1. Write the file
const customPlugins = `# Agent-generated for conflict isolation between PluginA + PluginB
*<PrimaryGameMaster>.esm
*<OptionalCommunityFix>.esp
*PluginA.esp
*PluginB.esp
`;
await writeFile("<agent-owned-artifacts-path>/foo-vs-bar/plugins.txt", customPlugins);

// 2. Start xEdit pointing at the custom file + the right Data dir
await xedit_start({
  pluginsFile: "<agent-owned-artifacts-path>/foo-vs-bar/plugins.txt",
  dataPath: "<MO2_Root>/<managed game root>/Data",
  gameMode: "<GameMode>",
});

Notes for the experimental file:

  • You MAY include the target game's primary / official masters here if you want to make the experiment self-describing, but you do not need to. xEdit auto- loads the official masters from the Data dir regardless.
  • Order matters even for a 3-plugin file. Top = loads first.
  • The file must be Windows-1252 encodable (UTF-8 works as long as you stay in ASCII).

plugins.txt vs modlist.txt — common confusion

FileWhat it listsPrefix conventionWhere
plugins.txt.esp/.esm/.esl plugin filenames* = active, no prefix = inactive<profile>/plugins.txt
modlist.txtMO2 mod folder names (under mods/)+ = enabled, - = disabled, * = foreign<profile>/modlist.txt

The * in modlist.txt means "foreign mod" (MO2-managed but externally owned), NOT "active." Mixing the two formats is the #1 way to corrupt a profile. Never copy/paste between them.

MO2 path inspection (so you know which Data dir xEdit should target)

Before launching xEdit via the agent, read MO2's config to learn the managed game path:

# <MO2_Root>\ModOrganizer.ini contains lines like:
gameName=Fallout 4
gamePath=@ByteArray(D:\\awesome-bgs-mod-master\\.artifacts\\mo2\\Stock Game\\Fallout 4)

The gamePath value (strip the @ByteArray(...) wrapper) is where the agent's xedit_start({ dataPath: ... }) should point. Specifically, the Data directory is <gamePath>\Data. Without an explicit dataPath, xEdit falls back to the Windows registry — which points at the platform install path, not necessarily the MO2-managed game root. That's the source of "wrong Data" bugs.

Validation

Three options for catching a malformed plugins.txt before launching xEdit into a broken state:

  1. xEdit one-shot smoke: launch xEdit with -quickAutoClean against the file. Non-zero exit = a master is missing or a *-prefixed plugin doesn't resolve. Cheap and authoritative.
    xEdit.exe -fo4 -D:"...\Data\" -P:"...\plugins.txt" -veryquickshowconflicts
    
  2. LOOT CLI: uses libloadorder under the hood and surfaces validation errors in its GUI / log.
  3. Manual checks: each *-prefixed plugin must exist as a file in the Data dir; no duplicate lines; under 255 active full plugins + under 4096 active light plugins (.esl + flagged-ESP).

What makes a file invalid:

  • More than 255 active full plugins (light plugins counted separately, see next bullet).
  • More than 4096 active light plugins.
  • Duplicate plugin lines.
  • A *-prefixed line whose file does not exist in the Data dir.
  • A line with a Windows-1252-unencodable filename.

Legacy format (Skyrim LE / Oblivion / FO3 / FNV)

The OLD format used by pre-asterisk games:

  • No * prefix. Presence of a plugin line in plugins.txt means it's active. Absence means it's not loaded.
  • loadorder.txt is the canonical full-order file (active + inactive). plugins.txt contains only active plugins.
  • plugins.txt uses Windows-1252 encoding; loadorder.txt uses UTF-8 without BOM.
  • Max 255 active plugins (no ESL concept in these engines).
  • CRLF line endings standard.

This format does NOT apply to FO4 / SSE / Starfield. If you're targeting one of these older games, use this section's rules; for everything else, use the asterisk format above.

See also

  • xedit-automation — hub skill for all xEdit work; routing doctrine, anti-patterns, sub-agent recipes.
  • BGS KB records under knowledge/bgs-kb/packs/core/records/ — daemon command, error-code, save-semantics, and glossary facts formerly kept in the retired xedit-automation/xedit-knowledgebase.md redirect.
  • setting-up-bgs-modding-environment — explains how to inspect MO2's ModOrganizer.ini for gamePath before launching xEdit.
  • xEdit official docs Section 2.8.1 — launcher flag reference for -P:, -D:, and friends.
  • Ortham/libloadorder documentation — the authoritative source for the asterisk format semantics.

Signals

GitHub stars
985
Forks
276
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
writing-bgs-load-order
Source
github.com/hashgraph-online/awesome-codex-plugins