Files
Iris/docs/24 - Pack Mods & Snippets.md
T
Brian Neumann-Fopiano 365205ad0a d
2026-08-12 13:52:16 -04:00

15 KiB

24 - Pack Mods & Snippets

Snippets let you write a nested JSON value once and reference it by path from as many places as you like. Any pack type annotated @Snippet accepts either an inline object or a string pointing at a file under snippet/<type>/. Iris also still loads the older IrisMod schema from mods/, but nothing in the engine applies those injectors or replacers — treat that folder as dead weight.

Related: 05 - Concepts & Pack Layout.md, 10 - Studio & VSCode Schemas.md, 11 - Dimensions.md, 12 - Regions.md, 13 - Biomes.md, 14 - Generators & Noise.md, 20 - Object Placement.md, 25 - Pack Management.md.

The mental model

Most of a pack is nested objects: a decorator inside a biome, a noise style inside a generator, a palette inside a decorator. When two biomes want the same decorator you'd normally copy the JSON, and then you have two copies to keep in sync.

Snippets fix that at the deserializer level. Iris registers a Gson type adapter for every class carrying @Snippet("some-name"). When that adapter reads a field and finds a string instead of an object, it treats the string as a path, opens snippet/some-name/<path>.json, and parses the file's contents as the field's value. Nothing else changes: the biome still ends up holding a real decorator object, the engine never knows the difference, and the value is resolved once at load time rather than looked up per chunk.

Two consequences worth internalising:

  • Snippets are load-time only. Editing a snippet file does nothing until the pack reloads — Studio hotload, world reload, or a restart. There is no live indirection.
  • Snippets vanish on serialization. When Iris writes a pack back out (Studio saves, the Bukkit packager), the adapter writes the resolved object, not the string. Snippet references get inlined. See "Packaging" below.

Walkthrough: share a palette across biomes

The goal is one decorator definition placing wildflowers in several biomes, with a single file to edit. Prerequisites: a validating pack and a biome that already generates.

1. Write the snippet. The folder name must match the @Snippet value of the field you'll use it in — a decorator field wants snippet/decorator/. Save snippet/decorator/tutorial-wildflowers.json:

{
  "chance": 0.08,
  "style": {
    "style": "CLOVER_HERMITE",
    "zoom": 0.52,
    "exponent": 2.5
  },
  "slopeCondition": { "maximumSlope": 4 },
  "palette": [
    { "block": "minecraft:dandelion", "weight": 2 },
    { "block": "minecraft:poppy", "weight": 1 },
    { "block": "minecraft:air", "weight": 4 }
  ]
}

2. Reference it. In the biome, replace the inline decorator with the path. No .json suffix:

{
  "decorators": ["snippet/decorator/tutorial-wildflowers"]
}

3. Verify one call site. Validate the pack, then open Studio on a fixed seed and generate fresh chunks in that biome. Success is both flowers appearing only on slopes the snippet allows, with no "Couldn't find snippet" line in the console. If the field resolves to null, the console names the path it tried — compare it against the file on disk.

4. Add the second call site. Only once the first one works. Paste the same string into another biome's decorators.

5. Prove they're linked. Change one value inside the snippet — raise chance to 0.3 — hotload, and generate fresh chunks in both biomes. Both should get denser. Restore the value afterwards. That round trip is the actual test that you have one definition and not two.

Generate the VSCode workspace (/iris studio vscode) so schema completion offers valid snippet paths for each field. See 10 - Studio & VSCode Schemas.md.

How resolution works

  1. Iris registers a type adapter for every class annotated @Snippet("type-name").
  2. On read, if the JSON token is an object, the adapter parses it normally. Nothing snippet-specific happens.
  3. If the token is a string, the adapter treats it as a path:
    • The string must start with snippet/. If it doesn't, the field resolves to null with no error message — this is the failure mode that looks like the field was ignored.
    • The literal snippet/ prefix is stripped and replaced with snippet/<type-name>/ for the field being read. Only the prefix is rewritten; the rest of the path is kept verbatim. So writing "snippet/style/bedrock" in a decorator field becomes a lookup for snippet/decorator/style/bedrock.json, which won't exist. The rewrite is a convenience for the common snippet/<correct-type>/… case, not a search across type folders.
    • The file is read from the pack root. A missing file logs Couldn't find snippet <path> and the field resolves to null.
  4. Snippet files are parsed with the same adapters, so a snippet can reference other snippets in its own nested fields.
  5. Files may sit in subfolders under the type folder; the path in the reference is everything after snippet/<type>/, with forward slashes.

Studio schema generation exposes every snippet type as an anyOf of "object or string", and fills the string branch's enum from the files actually present under snippet/<type>/.

Disk layout

pack/
  snippet/
    decorator/
      bush.json
      dry_grass.json
      forest/
        fern.json
    style/
      bedrock.json
      deepslate.json

The folder is singular snippet/, not snippets/. Subfolder names must match the @Snippet value exactly.

Overworld usage

Dimension ore deposits reference a style snippet:

{
  "chanceStyle": "snippet/style/bedrock"
}

snippet/style/bedrock.json:

{ "style": "STATIC" }

Biome decorator lists take snippet strings as array elements, mixed freely with inline objects:

{
  "decorators": [
    "snippet/decorator/wildflowers",
    "snippet/decorator/bush",
    { "chance": 0.01, "palette": [{ "block": "minecraft:sweet_berry_bush" }] }
  ]
}

biomes/dev.json in the shipping pack uses the same pattern for a minimal decorator list.

When to use a snippet

Use one when the same value genuinely appears in more than one place and should change in lockstep: decorators shared across a climate's biomes, a noise style reused by several generators, a palette that defines a pack's stone. That's what the shipping Overworld pack uses them for.

Skip it when a value appears once. A snippet reference costs a file open and adds a place to look when something goes wrong, and it buys nothing if there's a single call site. It also makes the failure mode worse: a wrong-type or missing snippet resolves to null after logging, so a field that should have had a value silently has none. Treat pack validation and a clean console as required gates whenever you add or move snippet files.

Packaging and snippets

Because the adapter writes resolved objects rather than strings, exported packs handle snippets differently per platform:

  • Bukkit /iris studio package re-serializes the loaded object graph, so snippet references are inlined into the dimension, region, biome, and generator JSON. The export has no snippet/ folder and doesn't need one.
  • Modded /iris studio package copies the source JSON files verbatim and does not copy snippet/, so snippet references in a modded export are dangling.

See 25 - Pack Management.md for the full export contents and the gaps in both compilers.

@Snippet type names

Each value is the folder name under snippet/ and the required prefix for references to that field.

Snippet value Class
attribute-modifier IrisAttributeModifier
axis-rotation IrisAxisRotationClamp
biome-injector IrisModBiomeInjector
biome-palette IrisBiomePaletteLayer
biome-replacer IrisModBiomeReplacer
block-drops IrisBlockDrops
cave-field-module IrisCaveFieldModule
cave-profile IrisCaveProfile
color IrisColor
command IrisCommand
command-registry IrisCommandRegistry
coral IrisCoral
crystal IrisCrystal
custom-biome IrisBiomeCustom
custom-biome-particle IrisBiomeCustomParticle
custom-biome-spawn IrisBiomeCustomSpawn
decorator IrisDecorator
deposit IrisDepositGenerator
deposit-variant IrisDepositVariant
dimension-carving-entry IrisDimensionCarvingEntry
dimension-mode IrisDimensionMode
duration IrisDuration
effect IrisEffect
enchantment IrisEnchantment
entity-spawn IrisEntitySpawn
expression-function IrisExpressionFunction
expression-load IrisExpressionLoad
floating-child-biome IrisFloatingChildBiomes
formation IrisFormation
fungus IrisFungus
generator IrisNoiseGenerator
generator-layer IrisBiomeGeneratorLink
image-map IrisImageMap
loot IrisLoot
loot-registry IrisLootReference
noise-style-replacer IrisModNoiseStyleReplacer
object-block-replacer IrisObjectReplace
object-limit IrisObjectLimit
object-loot IrisObjectLoot
object-marker IrisObjectMarker
object-placement-biome-injector IrisModObjectPlacementBiomeInjector
object-placement-region-injector IrisModObjectPlacementRegionInjector
object-placer IrisObjectPlacement
object-replacer IrisModObjectReplacer
object-rotator IrisObjectRotation
object-scale IrisObjectScale
object-translator IrisObjectTranslate
object-vanilla-loot IrisObjectVanillaLoot
palette IrisMaterialPalette
position-3d IrisPosition
potion-effect IrisPotionEffect
procedural-objects IrisProceduralObjects
procedural-tree IrisProceduralTree
range IrisRange
rate IrisRate
region-replacer IrisModRegionReplacer
ruin IrisRuin
ruin-decorator IrisRuinDecorator
shaped-style IrisShapedGeneratorStyle
slope-clip IrisSlopeClip
stilt-settings IrisStiltSettings
style IrisGeneratorStyle
style-range IrisStyledRange
time-block IrisTimeBlock
tree IrisTree
tree-branches IrisTreeBranches
tree-canopy IrisTreeCanopy
tree-decorator IrisTreeDecorator
tree-layer IrisTreeLayer
tree-secondary-leaf IrisTreeSecondaryLeaf
tree-settings IrisTreeSettings
tree-size IrisTreeSize
tree-sub-branches IrisTreeSubBranches
vacuum-settings IrisVacuumSettings

Whole-file registrants — dimensions, regions, biomes, generators, loot tables, entities, spawners, markers, mods, objects, structures — are not snippet types. They already have their own folders and are referenced by key. Only nested field types appear above.

Pack mods (IrisMod) — schema only, not applied

Folder: mods/. The load key is the path under mods/ without .json. IrisData registers a loader for these files, so they parse, appear in tooling, and show up in generated schemas — but no engine path reads them. Neither world creation nor Studio hotload consumes an IrisMod. A mods/*.json file that looks correct will change nothing about the terrain you generate.

To get the same effect, edit the target dimension, region, biome, generator, or object placement directly. If you need the same edit applied to several packs, keep the edits in version control rather than expecting the mod schema to layer them at runtime.

The fields below are documented because they still appear in schema completion and because packs in the wild contain them, not because they work.

Field Type Default Intended meaning
name string "A Pack Modification" Human name, at least 2 characters
forDimension string "" Dimension load key to scope to; empty means any
overrideFluidHeight int -1..512 -1 Fluid height override; -1 leaves it alone
removeBiomes string[] [] Biome keys to strip
removeObjects string[] [] Object keys to strip
removeRegions string[] [] Region keys to strip
injectRegions string[] [] Region keys to add to the dimension
biomeInjectors IrisModBiomeInjector[] [] Add biomes to a region
biomeReplacers IrisModBiomeReplacer[] [] Swap one biome for another
objectReplacers IrisModObjectReplacer[] [] Swap object keys
biomeObjectPlacementInjectors IrisModObjectPlacementBiomeInjector[] [] Add object placements to a biome
regionObjectPlacementInjectors IrisModObjectPlacementRegionInjector[] [] Add object placements to a region
regionReplacers IrisModRegionReplacer[] [] Swap regions
blockReplacers IrisObjectReplace[] [] Block find/replace, same shape as object material replacers
styleReplacers IrisModNoiseStyleReplacer[] [] Replace NoiseStyle usages

Shapes of the nested types, all of which are also registered snippet types:

{ "region": "temperate", "inject": ["temperate/meadows"] }
{ "find": ["temperate/plains"], "replace": "temperate/lush-plains" }
{ "find": ["temperate"], "replace": "forests" }
{ "find": ["clutter/camp1"], "replace": "clutter/camp3" }
{ "biome": "temperate/plains", "place": [{ "chance": 0.01, "place": ["clutter/camp1"] }] }

IrisModObjectPlacementRegionInjector uses the field name biome even though the value is a region load key. IrisModNoiseStyleReplacer takes find (a NoiseStyle enum value), replace (a full IrisGeneratorStyle), and replaceTypeOnly (swap only the style type and keep the rest of the style's fields).

Other registered schemas with no runtime consumer

Schema registration alone doesn't prove there's a consumer. These are visible to loaders or schema generation but are not supported pack features:

Surface Status
mods/*.json (IrisMod) Parsed and registered, never applied. See above
potion-effect / IrisPotionEffect The snippet type exists, but no production field is typed as IrisPotionEffect. Use the potionEffect, potionStrength, and potionTicks* fields on IrisEffect instead
matter/ resources A loader exists for Matter binaries, but no generation or runtime path reads pack matter/ resources
IrisObjectPlacement.translateCenter Serialized and carried through placement copies, but no placement path reads the value
  • Pack validation: /iris pack validate — see 25 - Pack Management.md and 04 - Commands & Permissions.md.
  • Studio open, hotload, and VSCode schema generation: 10 - Studio & VSCode Schemas.md.