Niagara Editing
Niagara editing uses three complementary paths: NiagaraOperationLibrary for structural operations, NodeCode for graph/property reading and writing, and ObjectOperationLibrary for detailed UObject property access.
Prerequisite: UE editor running with UCP plugin enabled.
API Overview
NiagaraOperationLibrary (structural operations)
CDO: /Script/UnrealClientProtocolEditor.Default__NiagaraOperationLibrary
UObject path parameters (System, Emitter, Script, etc.) are auto-resolved by UCP — pass the object path as a string and UCP converts it to the UObject pointer.
| Function |
Params |
Description |
AddEmitter |
System(path), SourceEmitter(path), Name |
Add emitter to system |
RemoveEmitter |
System(path), EmitterName |
Remove emitter from system |
ReorderEmitter |
System(path), EmitterName, NewIndex |
Reorder emitter |
AddUserParameter |
System(path), ParamName, TypeName, DefaultValue |
Add User parameter |
RemoveUserParameter |
System(path), ParamName |
Remove User parameter |
AddModule |
SystemOrEmitter(path), ScriptUsage, ModuleScript(path), Index |
Add module to stack |
RemoveModule |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid |
Remove module |
MoveModule |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid, NewIndex |
Reorder module |
SetModuleEnabled |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid, bEnabled |
Enable/disable module |
GetModules |
SystemOrEmitter(path), ScriptUsage |
List modules with name/guid/enabled/scriptPath |
SetModuleInputValue |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid, InputName, Value |
Set input to literal (Rapid Iteration) |
SetModuleInputBinding |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid, InputName, LinkedParamName |
Bind input to parameter |
SetModuleInputDynamicInput |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid, InputName, DynamicInputScript(path) |
Set dynamic input |
ResetModuleInput |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid, InputName |
Reset to default |
GetModuleInputs |
SystemOrEmitter(path), ScriptUsage, ModuleNodeGuid |
List inputs with name/type/mode/value |
AddRenderer |
Emitter(path), RendererClassName |
Add renderer |
RemoveRenderer |
Emitter(path), RendererIndex |
Remove renderer |
MoveRenderer |
Emitter(path), RendererIndex, NewIndex |
Reorder renderer |
AddEventHandler |
Emitter(path) |
Add event handler |
RemoveEventHandler |
Emitter(path), UsageIdString |
Remove event handler |
AddSimulationStage |
Emitter(path) |
Add simulation stage |
RemoveSimulationStage |
Emitter(path), StageIdString |
Remove simulation stage |
MoveSimulationStage |
Emitter(path), StageIdString, NewIndex |
Reorder simulation stage |
AddGraphParameter |
Script(path), ParamName, TypeName |
Add parameter to script graph |
RemoveGraphParameter |
Script(path), ParamName |
Remove parameter |
RenameGraphParameter |
Script(path), OldName, NewName |
Rename parameter |
CreateScratchPadScript |
SystemOrEmitter(path), ScriptName, Usage, ModuleUsageBitmask |
Create a ScratchPad (Local Module) script |
NodeCode (graph + properties reading/writing)
CDO: /Script/UnrealClientProtocolEditor.Default__NodeCodeEditingLibrary
| Function |
Params |
Description |
Outline |
AssetPath |
List available sections |
ReadGraph |
AssetPath, Section |
Read section content as text |
WriteGraph |
AssetPath, Section, GraphText |
Write section content back |
Section Model
System Asset (UNiagaraSystem)
| Section |
Type |
Description |
[SystemProperties] |
Properties |
Warmup, bounds, determinism, scalability, LWC, render defaults |
[Emitters] |
Properties |
Emitter list with names, enabled state, ObjectPath |
[UserParameters] |
Properties |
User. namespace exposed parameters |
[ScratchPadScripts] |
Properties |
Local Module scripts with name, Usage, ModuleUsageBitmask, ObjectPath |
[SystemSpawn] |
Graph |
System spawn stage module chain |
[SystemUpdate] |
Graph |
System update stage module chain |
Emitter Object (UNiagaraEmitter, accessed via ObjectPath from [Emitters])
| Section |
Type |
Description |
[EmitterProperties] |
Properties |
SimTarget, bLocalSpace, bounds, allocation, inheritance |
[ParticleAttributes] |
Properties (read-only) |
Compiled particle attributes list |
[Renderers] |
Properties |
Renderer list with class, ObjectPath |
[EventHandlers] |
Properties |
Event handler configs |
[SimulationStages] |
Properties |
Simulation stage list |
[EmitterSpawn] |
Graph |
Emitter spawn stage |
[EmitterUpdate] |
Graph |
Emitter update stage |
[ParticleSpawn] |
Graph |
Particle spawn stage |
[ParticleUpdate] |
Graph |
Particle update stage |
[ParticleEvent:EventId] |
Graph |
Event handler graph |
[SimulationStage:StageId] |
Graph |
Custom simulation stage graph |
Standalone Script (UNiagaraScript)
| Section |
Type |
Description |
[Module] |
Graph |
Module script graph |
[Function] |
Graph |
Function script graph |
[DynamicInput] |
Graph |
Dynamic input script graph |
ScriptUsage Values
Use these strings for ScriptUsage parameter:
SystemSpawn, SystemUpdate, EmitterSpawn, EmitterUpdate, ParticleSpawn, ParticleUpdate, ParticleEvent, SimulationStage, Module, Function, DynamicInput
Parameter Namespaces
| Namespace |
Scope |
Writable In |
System. |
System-level attributes |
System stages |
Emitter. |
Emitter-level attributes |
Emitter stages |
Particles. |
Particle attributes |
Particle stages |
Module. |
Module input parameters |
Module internal |
User. |
User-exposed parameters |
Runtime settable |
Engine. |
Engine values (DeltaTime, etc.) |
Read-only |
Local.Module. |
Module-local temporaries |
Module internal |
Transient. |
Cross-module temporaries |
Current stage |
Constants. |
Rapid iteration parameters |
Editor-set values |
Module Input Setting Methods
Modules can have their inputs set in four ways:
- Literal Value (
SetModuleInputValue) — stored as Rapid Iteration Parameter in Constants.EmitterName.ModuleName.InputName, no graph nodes
- Binding (
SetModuleInputBinding) — ParameterMapGet reads another parameter (e.g. User.MyParam, Emitter.SpawnRate)
- Dynamic Input (
SetModuleInputDynamicInput) — a DynamicInput script computes the value
- Data Interface — a NiagaraNodeInput provides a data interface object
- Default — module's built-in default, no override
Workflow
Exploring a System
// 1. Get system structure
{"object":"...NodeCodeEditingLibrary","function":"Outline","params":{"AssetPath":"/Game/FX/NS_Fountain"}}
// 2. Read emitter list
{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"/Game/FX/NS_Fountain","Section":"Emitters"}}
// 3. Deep-dive into an emitter (use ObjectPath from step 2)
{"object":"...NodeCodeEditingLibrary","function":"Outline","params":{"AssetPath":"/Game/FX/NS_Fountain.NS_Fountain:Fountain"}}
Modifying Module Stack
// Add a module
{"object":"...NiagaraOperationLibrary","function":"AddModule","params":{"EmitterPath":"...Fountain","ScriptUsage":"ParticleSpawn","ModuleAssetPath":"/Niagara/Modules/AddVelocity","Index":1}}
// Set module input to a value
{"object":"...NiagaraOperationLibrary","function":"SetModuleInputValue","params":{"EmitterPath":"...Fountain","ScriptUsage":"ParticleSpawn","ModuleGuid":"...","InputName":"Velocity","Value":"(X=0,Y=0,Z=100)"}}
// Bind module input to User parameter
{"object":"...NiagaraOperationLibrary","function":"SetModuleInputBinding","params":{"EmitterPath":"...Fountain","ScriptUsage":"ParticleSpawn","ModuleGuid":"...","InputName":"Velocity","LinkedParamName":"User.InitialVelocity"}}
Editing a Module Script's Internal Graph
ReadGraph("/Game/FX/Modules/MyModule", "Module")
→ Text representation of the node graph
WriteGraph("/Game/FX/Modules/MyModule", "Module", <modified text>)
→ Diff result
Modifying Renderer Properties
// Get renderer list
{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"...Fountain","Section":"Renderers"}}
// Read detailed renderer properties via reflection
{"object":"...ObjectOperationLibrary","function":"DescribeObject","params":{"ObjectPath":"...SpriteRenderer_0"}}
// Set renderer property
{"object":"...ObjectOperationLibrary","function":"SetObjectProperty","params":{"ObjectPath":"...SpriteRenderer_0","PropertyName":"Material","JsonValue":"\"/Game/Materials/M_Particle\""}}
Node Types in Graph Sections
| Encoded Name |
UClass |
Description |
FunctionCall:<ScriptPath> |
UNiagaraNodeFunctionCall |
Module/function call (asset module) |
FunctionCall:ScratchPad:<Name> |
UNiagaraNodeFunctionCall |
ScratchPad (Local Module) reference |
Assignment |
UNiagaraNodeAssignment |
Inline assignment |
CustomHlsl |
UNiagaraNodeCustomHlsl |
Custom HLSL code |
ParameterMapGet |
UNiagaraNodeParameterMapGet |
Read from parameter map |
ParameterMapSet |
UNiagaraNodeParameterMapSet |
Write to parameter map |
NiagaraOp |
UNiagaraNodeOp |
Math operation (OpName property) |
NiagaraNodeInput |
UNiagaraNodeInput |
Input node |
NiagaraNodeOutput |
UNiagaraNodeOutput |
Output node |
Working with ScratchPad (Local Module) Scripts
ScratchPad scripts are inline module/function/dynamic-input scripts embedded within a System or Emitter. They appear in [ScratchPadScripts] and are referenced in graphs as FunctionCall:ScratchPad:<Name>.
// 1. Read ScratchPad list from source system
{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"/Game/FX/NS_Source","Section":"ScratchPadScripts"}}
// 2. Create matching ScratchPad scripts in target system
{"object":"...NiagaraOperationLibrary","function":"CreateScratchPadScript","params":{"SystemOrEmitter":"/Game/FX/NS_Target","ScriptName":"MyModule","Usage":"Module","ModuleUsageBitmask":56}}
// 3. Read source ScratchPad internal graph (use ObjectPath from step 1)
{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"/Game/FX/NS_Source.NS_Source:MyModule","Section":"Module"}}
// 4. Write to target ScratchPad (use ObjectPath from step 2)
{"object":"...NodeCodeEditingLibrary","function":"WriteGraph","params":{"AssetPath":"/Game/FX/NS_Target.NS_Target:MyModule","Section":"Module","GraphText":"..."}}
// 5. Now write stage graphs — FunctionCall:ScratchPad:MyModule auto-resolves to target system's script
Important Notes
- GUID Preservation: When editing graphs with WriteGraph, preserve existing node
#guid values to ensure correct diff matching.
- Emitters are embedded: Emitters inside a System are copies, not references. Access them via ObjectPath from the
[Emitters] section.
- ParticleAttributes are read-only: They are derived from compiled scripts. To add/remove particle attributes, add/remove modules that write to
Particles. namespace.
- Prefer OperationLibrary for module stack changes: Use AddModule/RemoveModule/SetModuleInput instead of WriteGraph for System/Emitter stage graphs, as these APIs correctly handle Rapid Iteration parameters and Override node management.
1---2name: unreal-niagara-editing3description: Edit Niagara systems, emitters, and scripts via UCP. Use when the user asks to create/modify particle effects, manage emitters, configure modules, set module inputs, edit Niagara script graphs, or any Niagara VFX operation.4---56# Niagara Editing78Niagara editing uses three complementary paths: **NiagaraOperationLibrary** for structural operations, **NodeCode** for graph/property reading and writing, and **ObjectOperationLibrary** for detailed UObject property access.910**Prerequisite**: UE editor running with UCP plugin enabled.1112## API Overview1314### NiagaraOperationLibrary (structural operations)1516CDO: `/Script/UnrealClientProtocolEditor.Default__NiagaraOperationLibrary`1718UObject path parameters (System, Emitter, Script, etc.) are auto-resolved by UCP — pass the object path as a string and UCP converts it to the UObject pointer.1920| Function | Params | Description |21|----------|--------|-------------|22| `AddEmitter` | `System`(path), `SourceEmitter`(path), `Name` | Add emitter to system |23| `RemoveEmitter` | `System`(path), `EmitterName` | Remove emitter from system |24| `ReorderEmitter` | `System`(path), `EmitterName`, `NewIndex` | Reorder emitter |25| `AddUserParameter` | `System`(path), `ParamName`, `TypeName`, `DefaultValue` | Add User parameter |26| `RemoveUserParameter` | `System`(path), `ParamName` | Remove User parameter |27| `AddModule` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleScript`(path), `Index` | Add module to stack |28| `RemoveModule` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid` | Remove module |29| `MoveModule` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid`, `NewIndex` | Reorder module |30| `SetModuleEnabled` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid`, `bEnabled` | Enable/disable module |31| `GetModules` | `SystemOrEmitter`(path), `ScriptUsage` | List modules with name/guid/enabled/scriptPath |32| `SetModuleInputValue` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid`, `InputName`, `Value` | Set input to literal (Rapid Iteration) |33| `SetModuleInputBinding` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid`, `InputName`, `LinkedParamName` | Bind input to parameter |34| `SetModuleInputDynamicInput` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid`, `InputName`, `DynamicInputScript`(path) | Set dynamic input |35| `ResetModuleInput` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid`, `InputName` | Reset to default |36| `GetModuleInputs` | `SystemOrEmitter`(path), `ScriptUsage`, `ModuleNodeGuid` | List inputs with name/type/mode/value |37| `AddRenderer` | `Emitter`(path), `RendererClassName` | Add renderer |38| `RemoveRenderer` | `Emitter`(path), `RendererIndex` | Remove renderer |39| `MoveRenderer` | `Emitter`(path), `RendererIndex`, `NewIndex` | Reorder renderer |40| `AddEventHandler` | `Emitter`(path) | Add event handler |41| `RemoveEventHandler` | `Emitter`(path), `UsageIdString` | Remove event handler |42| `AddSimulationStage` | `Emitter`(path) | Add simulation stage |43| `RemoveSimulationStage` | `Emitter`(path), `StageIdString` | Remove simulation stage |44| `MoveSimulationStage` | `Emitter`(path), `StageIdString`, `NewIndex` | Reorder simulation stage |45| `AddGraphParameter` | `Script`(path), `ParamName`, `TypeName` | Add parameter to script graph |46| `RemoveGraphParameter` | `Script`(path), `ParamName` | Remove parameter |47| `RenameGraphParameter` | `Script`(path), `OldName`, `NewName` | Rename parameter |48| `CreateScratchPadScript` | `SystemOrEmitter`(path), `ScriptName`, `Usage`, `ModuleUsageBitmask` | Create a ScratchPad (Local Module) script |4950### NodeCode (graph + properties reading/writing)5152CDO: `/Script/UnrealClientProtocolEditor.Default__NodeCodeEditingLibrary`5354| Function | Params | Description |55|----------|--------|-------------|56| `Outline` | `AssetPath` | List available sections |57| `ReadGraph` | `AssetPath`, `Section` | Read section content as text |58| `WriteGraph` | `AssetPath`, `Section`, `GraphText` | Write section content back |5960## Section Model6162### System Asset (UNiagaraSystem)6364| Section | Type | Description |65|---------|------|-------------|66| `[SystemProperties]` | Properties | Warmup, bounds, determinism, scalability, LWC, render defaults |67| `[Emitters]` | Properties | Emitter list with names, enabled state, ObjectPath |68| `[UserParameters]` | Properties | User. namespace exposed parameters |69| `[ScratchPadScripts]` | Properties | Local Module scripts with name, Usage, ModuleUsageBitmask, ObjectPath |70| `[SystemSpawn]` | Graph | System spawn stage module chain |71| `[SystemUpdate]` | Graph | System update stage module chain |7273### Emitter Object (UNiagaraEmitter, accessed via ObjectPath from [Emitters])7475| Section | Type | Description |76|---------|------|-------------|77| `[EmitterProperties]` | Properties | SimTarget, bLocalSpace, bounds, allocation, inheritance |78| `[ParticleAttributes]` | Properties (read-only) | Compiled particle attributes list |79| `[Renderers]` | Properties | Renderer list with class, ObjectPath |80| `[EventHandlers]` | Properties | Event handler configs |81| `[SimulationStages]` | Properties | Simulation stage list |82| `[EmitterSpawn]` | Graph | Emitter spawn stage |83| `[EmitterUpdate]` | Graph | Emitter update stage |84| `[ParticleSpawn]` | Graph | Particle spawn stage |85| `[ParticleUpdate]` | Graph | Particle update stage |86| `[ParticleEvent:EventId]` | Graph | Event handler graph |87| `[SimulationStage:StageId]` | Graph | Custom simulation stage graph |8889### Standalone Script (UNiagaraScript)9091| Section | Type | Description |92|---------|------|-------------|93| `[Module]` | Graph | Module script graph |94| `[Function]` | Graph | Function script graph |95| `[DynamicInput]` | Graph | Dynamic input script graph |9697## ScriptUsage Values9899Use these strings for `ScriptUsage` parameter:100101`SystemSpawn`, `SystemUpdate`, `EmitterSpawn`, `EmitterUpdate`, `ParticleSpawn`, `ParticleUpdate`, `ParticleEvent`, `SimulationStage`, `Module`, `Function`, `DynamicInput`102103## Parameter Namespaces104105| Namespace | Scope | Writable In |106|-----------|-------|-------------|107| `System.` | System-level attributes | System stages |108| `Emitter.` | Emitter-level attributes | Emitter stages |109| `Particles.` | Particle attributes | Particle stages |110| `Module.` | Module input parameters | Module internal |111| `User.` | User-exposed parameters | Runtime settable |112| `Engine.` | Engine values (DeltaTime, etc.) | Read-only |113| `Local.Module.` | Module-local temporaries | Module internal |114| `Transient.` | Cross-module temporaries | Current stage |115| `Constants.` | Rapid iteration parameters | Editor-set values |116117## Module Input Setting Methods118119Modules can have their inputs set in four ways:1201211. **Literal Value** (`SetModuleInputValue`) — stored as Rapid Iteration Parameter in `Constants.EmitterName.ModuleName.InputName`, no graph nodes1222. **Binding** (`SetModuleInputBinding`) — ParameterMapGet reads another parameter (e.g. `User.MyParam`, `Emitter.SpawnRate`)1233. **Dynamic Input** (`SetModuleInputDynamicInput`) — a DynamicInput script computes the value1244. **Data Interface** — a NiagaraNodeInput provides a data interface object1255. **Default** — module's built-in default, no override126127## Workflow128129### Exploring a System130131```json132// 1. Get system structure133{"object":"...NodeCodeEditingLibrary","function":"Outline","params":{"AssetPath":"/Game/FX/NS_Fountain"}}134135// 2. Read emitter list136{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"/Game/FX/NS_Fountain","Section":"Emitters"}}137138// 3. Deep-dive into an emitter (use ObjectPath from step 2)139{"object":"...NodeCodeEditingLibrary","function":"Outline","params":{"AssetPath":"/Game/FX/NS_Fountain.NS_Fountain:Fountain"}}140```141142### Modifying Module Stack143144```json145// Add a module146{"object":"...NiagaraOperationLibrary","function":"AddModule","params":{"EmitterPath":"...Fountain","ScriptUsage":"ParticleSpawn","ModuleAssetPath":"/Niagara/Modules/AddVelocity","Index":1}}147148// Set module input to a value149{"object":"...NiagaraOperationLibrary","function":"SetModuleInputValue","params":{"EmitterPath":"...Fountain","ScriptUsage":"ParticleSpawn","ModuleGuid":"...","InputName":"Velocity","Value":"(X=0,Y=0,Z=100)"}}150151// Bind module input to User parameter152{"object":"...NiagaraOperationLibrary","function":"SetModuleInputBinding","params":{"EmitterPath":"...Fountain","ScriptUsage":"ParticleSpawn","ModuleGuid":"...","InputName":"Velocity","LinkedParamName":"User.InitialVelocity"}}153```154155### Editing a Module Script's Internal Graph156157```158ReadGraph("/Game/FX/Modules/MyModule", "Module")159→ Text representation of the node graph160161WriteGraph("/Game/FX/Modules/MyModule", "Module", <modified text>)162→ Diff result163```164165### Modifying Renderer Properties166167```json168// Get renderer list169{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"...Fountain","Section":"Renderers"}}170171// Read detailed renderer properties via reflection172{"object":"...ObjectOperationLibrary","function":"DescribeObject","params":{"ObjectPath":"...SpriteRenderer_0"}}173174// Set renderer property175{"object":"...ObjectOperationLibrary","function":"SetObjectProperty","params":{"ObjectPath":"...SpriteRenderer_0","PropertyName":"Material","JsonValue":"\"/Game/Materials/M_Particle\""}}176```177178## Node Types in Graph Sections179180| Encoded Name | UClass | Description |181|-------------|--------|-------------|182| `FunctionCall:<ScriptPath>` | UNiagaraNodeFunctionCall | Module/function call (asset module) |183| `FunctionCall:ScratchPad:<Name>` | UNiagaraNodeFunctionCall | ScratchPad (Local Module) reference |184| `Assignment` | UNiagaraNodeAssignment | Inline assignment |185| `CustomHlsl` | UNiagaraNodeCustomHlsl | Custom HLSL code |186| `ParameterMapGet` | UNiagaraNodeParameterMapGet | Read from parameter map |187| `ParameterMapSet` | UNiagaraNodeParameterMapSet | Write to parameter map |188| `NiagaraOp` | UNiagaraNodeOp | Math operation (OpName property) |189| `NiagaraNodeInput` | UNiagaraNodeInput | Input node |190| `NiagaraNodeOutput` | UNiagaraNodeOutput | Output node |191192### Working with ScratchPad (Local Module) Scripts193194ScratchPad scripts are inline module/function/dynamic-input scripts embedded within a System or Emitter. They appear in `[ScratchPadScripts]` and are referenced in graphs as `FunctionCall:ScratchPad:<Name>`.195196```json197// 1. Read ScratchPad list from source system198{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"/Game/FX/NS_Source","Section":"ScratchPadScripts"}}199200// 2. Create matching ScratchPad scripts in target system201{"object":"...NiagaraOperationLibrary","function":"CreateScratchPadScript","params":{"SystemOrEmitter":"/Game/FX/NS_Target","ScriptName":"MyModule","Usage":"Module","ModuleUsageBitmask":56}}202203// 3. Read source ScratchPad internal graph (use ObjectPath from step 1)204{"object":"...NodeCodeEditingLibrary","function":"ReadGraph","params":{"AssetPath":"/Game/FX/NS_Source.NS_Source:MyModule","Section":"Module"}}205206// 4. Write to target ScratchPad (use ObjectPath from step 2)207{"object":"...NodeCodeEditingLibrary","function":"WriteGraph","params":{"AssetPath":"/Game/FX/NS_Target.NS_Target:MyModule","Section":"Module","GraphText":"..."}}208209// 5. Now write stage graphs — FunctionCall:ScratchPad:MyModule auto-resolves to target system's script210```211212## Important Notes213214- **GUID Preservation**: When editing graphs with WriteGraph, preserve existing node `#guid` values to ensure correct diff matching.215- **Emitters are embedded**: Emitters inside a System are copies, not references. Access them via ObjectPath from the `[Emitters]` section.216- **ParticleAttributes are read-only**: They are derived from compiled scripts. To add/remove particle attributes, add/remove modules that write to `Particles.` namespace.217- **Prefer OperationLibrary for module stack changes**: Use AddModule/RemoveModule/SetModuleInput instead of WriteGraph for System/Emitter stage graphs, as these APIs correctly handle Rapid Iteration parameters and Override node management.