Skip to content

Transmutation System Overview

The transmutation system is the core feature of Alchemy, providing powerful and flexible item data conversion through JSON-configured rules.

Alchemy intercepts Minecraft’s item deserialization process using a mixin on the ItemStack codec. This means conversions happen automatically when:

  • Players join the server (inventory loading)
  • Chunks are loaded (container blocks)
  • Items are dropped or picked up
  • Any item data is read from NBT

When an item is being deserialized:

  1. Rules are evaluated in priority order (highest first)
  2. Each rule’s condition is checked against the item
  3. The first matching rule is applied
  4. If a rule matches, no other rules are checked

Once a rule matches:

  1. The rule’s operations are applied in sequence
  2. Each operation receives the output of the previous operation
  3. The final result replaces the original item

A transmutation configuration file has this structure:

{
"conversions": [
{
"id": "unique_identifier",
"priority": 100,
"condition": { /* condition object */ },
"operations": [ /* array of operations */ ]
}
]
}
FieldTypeRequiredDescription
conversionsArrayYesList of conversion rules
FieldTypeRequiredDefaultDescription
idStringYes-Unique identifier for this rule
priorityIntegerNo0Priority for rule evaluation (higher = checked first)
conditionObjectYes-Condition that determines if this rule applies
operationsArrayYes-List of operations to apply when condition matches

Understanding the execution order is crucial:

Item Loading
Load All Rules from config/alchemy/transmutations/*.json
Sort Rules by Priority (highest → lowest)
For Each Item Being Deserialized:
Check Rule 1 Condition → No Match
Check Rule 2 Condition → Match! ✓
Apply Rule 2 Operations in Sequence:
Operation 1 → Operation 2 → Operation 3
Return Transformed Item (skip remaining rules)

Priority determines the order in which rules are evaluated:

  • Higher numbers = checked first
  • Same priority = order is undefined (use different priorities for predictable behavior)
  • Default priority = 0
1000+ : Critical conversions that must run first
100-999 : High priority conversions
1-99 : Normal conversions
0 : Default priority
-1 to -99 : Low priority conversions
-100+ : Catch-all or fallback conversions

Paths use dot-notation to navigate item data structures:

id # Item ID
components # Components container
components.minecraft:custom_data # Custom data component
components.minecraft:custom_data.my_key # Nested custom data
components.custom:my_component # Custom namespace component

Important Notes:

  • Always include namespace prefixes (e.g., minecraft:custom_data, not custom_data)
  • Paths are case-sensitive
  • Missing paths result in null values (safe to check with component_exists)

Here’s a complete example showing how a rule works:

{
"conversions": [
{
"id": "legacy_magic_items",
"priority": 150,
"condition": {
"type": "all",
"all": [
{
"type": "component_exists",
"path": "components.minecraft:custom_data.legacy_item"
},
{
"type": "component_equals",
"path": "components.minecraft:custom_data.legacy_item.type",
"value": "magic"
}
]
},
"operations": [
{
"type": "set_item_id_from_component",
"source": "components.minecraft:custom_data.legacy_item.new_id"
},
{
"type": "set_component",
"component": "custom:magic_data",
"value": {
"type": "map",
"map": {
"power": {
"type": "from_source",
"fromPath": "components.minecraft:custom_data.legacy_item.power"
},
"element": {
"type": "from_source",
"fromPath": "components.minecraft:custom_data.legacy_item.element"
},
"version": {
"type": "number",
"number": 2
}
}
}
},
{
"type": "remove_custom_data_key",
"path": "legacy_item"
}
]
}
]
}

What This Does:

  1. Condition: Matches items that have legacy_item data with type: "magic"
  2. Operation 1: Changes the item ID based on the new_id field
  3. Operation 2: Creates a new custom:magic_data component with restructured data
  4. Operation 3: Removes the old legacy_item data
"id": "oraxen_to_custom_weapons" // Good
"id": "conversion_1" // Bad
// Specific conversions should run before generic ones
{
"id": "specific_diamond_sword",
"priority": 100,
"condition": { /* ... */ }
}
{
"id": "generic_swords",
"priority": 50,
"condition": { /* ... */ }
}

Always test your conditions don’t accidentally match unintended items:

// Good: Specific condition
{
"type": "all",
"all": [
{"type": "component_exists", "path": "..."},
{"type": "component_equals", "path": "...", "value": "..."}
]
}
// Risky: May match too broadly
{
"type": "component_exists",
"path": "components.minecraft:custom_data"
}

Always remove old data to prevent duplicate conversions:

{
"operations": [
{"type": "set_item_id", "itemId": "new:item"},
{"type": "remove_component", "path": "old_marker"} // Clean up!
]
}