Skip to content

Modpack Guide JSON

StarskyXIII edited this page Apr 7, 2026 · 4 revisions

Modpack Guide: JSON

HomeModpack Developers › JSON Guide

This page explains how to define Collapsible Groups in JSON, including file structure, node types, and complete examples.


When to Read This Page

  • You already decided to use data files for group maintenance
  • You want rules that are easy to review and easy to maintain over time
  • You want complex logic in a clear, stable structure

Quick Take

  • For fixed, maintainable rules, JSON should usually be your default choice
  • Nested logic and exclusion logic are usually clearer in JSON
  • If players are expected to edit groups in the GUI, avoid All, Not, Namespace, BlockTag, item_path_*, HasComponent, and nested structures

Quick Start

JSON files live at: config/collapsiblegroups/groups/*.json

Minimal example:

{
  "id": "mypack:utility_blocks",
  "name": {
    "translate": "group.collapsible_groups.mypack.utility_blocks",
    "fallback": "Utility Blocks"
  },
  "enabled": true,
  "filter": {
    "any": [
      { "type": "item", "id": "minecraft:barrel" },
      { "type": "item", "id": "minecraft:crafting_table" }
    ]
  }
}

Key fields:

  • id: unique group identifier
  • name: translate + fallback is recommended
  • enabled: whether the group is enabled
  • filter: the actual rule

Reference: Node Types

Atomic nodes

Single ID

{ "type": "item", "id": "minecraft:barrel" }
{ "type": "fluid", "id": "minecraft:water" }
{ "type": "mekanism:chemical", "id": "mekanism:hydrogen" }

Tag

{ "type": "item", "tag": "minecraft:logs" }
{ "type": "fluid", "tag": "c:water" }
{ "type": "mekanism:chemical", "tag": "mekanism:gases" }

BlockTag

{ "block_tag": "minecraft:logs" }

Notes:

  • BlockTag is item-only
  • it matches BlockItem entries by checking the backing block tag, not the item tag
  • groups that use BlockTag are currently read-only in the player editor

Namespace

{ "type": "item", "namespace": "create" }
{ "type": "fluid", "namespace": "mekanism" }
{ "type": "mekanism:chemical", "namespace": "mekanism" }

ExactStack

{
  "type": "item",
  "stack": "{\"id\":\"minecraft:potion\",\"count\":1,\"components\":{...}}"
}

Note: ExactStack currently only supports item. There is no equivalent exact-stack JSON entry point for fluid or generic types.

ItemPathStartsWith / ItemPathEndsWith

{ "item_path_starts_with": "gutter_" }
{ "item_path_ends_with": "_chair" }

Notes:

  • these are item-only filters
  • they match against the registry path, not the full namespace:path
  • for mcwroofs:gutter_middle_yellow, the path is gutter_middle_yellow
  • groups that use these nodes are currently read-only in the player editor

HasComponent

{
  "type": "item",
  "component": "apotheosis:gem",
  "value": "apotheosis:core/ballast"
}

Fields:

  • type: must be "item"
  • component: the DataComponentType registry ID in namespace:path form
  • value: the expected component value
    • ResourceLocation-style: plain string, e.g. "apotheosis:core/ballast"
    • Numeric: JSON numeric literal, e.g. "3" or "1.5"
    • Object: full JSON, e.g. "{\"level\":5}"

Notes:

  • HasComponent currently only supports item
  • The player editor does not support HasComponent, so groups that use it are read-only there
  • If the component ID does not exist or the component has no codec, the match always returns false

ComponentPath

Use ComponentPath when you need to match a specific nested field inside a component, rather than the whole component value.

The JSON format uses the same component and value fields as HasComponent, plus a path field. The presence of path is what distinguishes the two nodes.

{
  "type": "item",
  "component": "irons_spellbooks:spell_container",
  "path": "data[0].id",
  "value": "irons_spellbooks:blood_needles"
}

Fields:

  • type: must be "item"
  • component: the DataComponentType registry ID in namespace:path form
  • path: dot-separated path into the component's serialized JSON structure
  • value: the expected value at that path

Path grammar:

Pattern Meaning
field top-level key
parent.child nested key
array[n] nth element of an array
array[n].field a field inside an array element

When to use ComponentPath instead of HasComponent:

  • Use HasComponent when the component value is flat and directly comparable, for example a ResourceLocation string like "apotheosis:core/ballast"
  • Use ComponentPath when the value you want to match is nested inside the component's structure, for example {"data": [{"id": "irons_spellbooks:blood_needles", ...}]}

Notes:

  • ComponentPath currently only supports item
  • Groups that use it are read-only in the player editor
  • If the path does not resolve to a value, the match returns false

Composite nodes

OR (any)

{
  "any": [
    { "type": "item", "id": "minecraft:barrel" },
    { "type": "item", "id": "minecraft:crafting_table" }
  ]
}

AND (all)

{
  "all": [
    { "type": "item", "tag": "minecraft:logs" },
    { "type": "item", "namespace": "minecraft" }
  ]
}

NOT (not)

{
  "not": {
    "type": "item",
    "id": "minecraft:oak_planks"
  }
}

Examples

Example 1: OR group with several explicit items

{
  "id": "mypack:utility_blocks",
  "name": {
    "translate": "group.collapsible_groups.mypack.utility_blocks",
    "fallback": "Utility Blocks"
  },
  "enabled": true,
  "filter": {
    "any": [
      { "type": "item", "id": "minecraft:barrel" },
      { "type": "item", "id": "minecraft:crafting_table" },
      { "type": "item", "id": "minecraft:chest" }
    ]
  }
}

Good for small groups with clear boundaries where you want stable results.

Example 2: tag + namespace with AND

{
  "id": "mypack:vanilla_logs",
  "name": {
    "translate": "group.collapsible_groups.mypack.vanilla_logs",
    "fallback": "Vanilla Logs"
  },
  "enabled": true,
  "filter": {
    "all": [
      { "type": "item", "tag": "minecraft:logs" },
      { "type": "item", "namespace": "minecraft" }
    ]
  }
}

Useful when you want to narrow a broad shared tag back down to one namespace.

Example 2b: block tag for block-backed item groups

{
  "id": "mypack:logs_by_block_tag",
  "name": {
    "translate": "group.collapsible_groups.mypack.logs_by_block_tag",
    "fallback": "Logs by Block Tag"
  },
  "enabled": true,
  "filter": {
    "block_tag": "minecraft:logs"
  }
}

Use this when the grouping source is a block tag rather than an item tag.

Example 2c: item namespace + item path suffix

{
  "id": "mypack:macaw_chairs",
  "name": {
    "translate": "group.collapsible_groups.mypack.macaw_chairs",
    "fallback": "Macaw Chairs"
  },
  "enabled": true,
  "filter": {
    "all": [
      { "type": "item", "namespace": "mcwfurnitures" },
      { "item_path_ends_with": "_chair" }
    ]
  }
}

This is useful for mods whose item families are organized by registry naming conventions instead of tags.

Example 3: exclusion group with tag + NOT

{
  "id": "mypack:planks_except_oak",
  "name": {
    "translate": "group.collapsible_groups.mypack.planks_except_oak",
    "fallback": "Planks Except Oak"
  },
  "enabled": true,
  "filter": {
    "all": [
      { "type": "item", "tag": "minecraft:planks" },
      {
        "not": {
          "type": "item",
          "id": "minecraft:oak_planks"
        }
      }
    ]
  }
}

This is usually more stable and easier to maintain than hard-coding every non-oak member.

Example 4: nested Any(All(...), All(...))

{
  "id": "mypack:wood_family_mixed",
  "name": {
    "translate": "group.collapsible_groups.mypack.wood_family_mixed",
    "fallback": "Wood Family Mixed"
  },
  "enabled": true,
  "filter": {
    "any": [
      {
        "all": [
          { "type": "item", "tag": "minecraft:logs" },
          { "type": "item", "namespace": "minecraft" }
        ]
      },
      {
        "all": [
          { "type": "item", "tag": "minecraft:planks" },
          { "type": "item", "namespace": "create" }
        ]
      }
    ]
  }
}

This is a classic case where JSON stays easier to read than nested script chains.

Example 5: HasComponent for component-based filtering

Some mods, such as Apotheosis, use the same item ID with different Data Component values to distinguish variants. ExactStack requires every component to match. If you only want to filter on one component value, use HasComponent.

Single component value:

{
  "id": "mypack:apotheosis_ballast_gems",
  "name": {
    "translate": "group.collapsible_groups.mypack.apotheosis_ballast_gems",
    "fallback": "Ballast Gems"
  },
  "enabled": true,
  "filter": {
    "type": "item",
    "component": "apotheosis:gem",
    "value": "apotheosis:core/ballast"
  }
}

Multiple values with OR:

{
  "id": "mypack:apotheosis_ballast_or_breaker",
  "name": {
    "translate": "group.collapsible_groups.mypack.apotheosis_ballast_or_breaker",
    "fallback": "Ballast or Breaker Gems"
  },
  "enabled": true,
  "filter": {
    "any": [
      { "type": "item", "component": "apotheosis:gem", "value": "apotheosis:core/ballast" },
      { "type": "item", "component": "apotheosis:gem", "value": "apotheosis:core/breaker" }
    ]
  }
}

Note: groups that include HasComponent are read-only in the player editor.

Example 6: the difference between ExactStack and a plain item ID

If you write:

{ "type": "item", "id": "minecraft:potion" }

that refers to the whole item category for that ID.

If you write:

{
  "type": "item",
  "stack": "{\"id\":\"minecraft:potion\",\"count\":1,\"components\":{...}}"
}

that refers to one exact variant. This matters a lot for potions, enchanted books, and component-heavy items.

Example 7: mixing block tag, namespace, path, and exclusion

This kind of rule is useful when you want one group to combine a stable block-backed category with a naming-convention family, while still excluding a few unwanted entries.

{
  "id": "mypack:wood_and_roof_mix",
  "name": {
    "translate": "group.collapsible_groups.mypack.wood_and_roof_mix",
    "fallback": "Wood and Roof Mix"
  },
  "enabled": true,
  "filter": {
    "any": [
      {
        "all": [
          { "block_tag": "minecraft:logs" },
          { "type": "item", "namespace": "minecraft" }
        ]
      },
      {
        "all": [
          { "type": "item", "namespace": "mcwroofs" },
          { "item_path_starts_with": "gutter_" },
          {
            "not": {
              "type": "item",
              "id": "mcwroofs:gutter_middle_light_blue"
            }
          }
        ]
      }
    ]
  }
}

This pattern works well when one part of the group is tag-driven and another part is naming-driven:

  • use block_tag when the source category really lives in block tags
  • use item_path_starts_with when a mod organizes a family by registry naming
  • keep namespace alongside path-based matching to stay narrow and easier to reason about
  • use not when you only need to exclude one or two specific items

Continue reading:

Home


Players


Modpack Developers


Mod Developers


Localization

Clone this wiki locally