-
Notifications
You must be signed in to change notification settings - Fork 2
Modpack Guide JSON
Home › Modpack Developers › JSON Guide
This page explains how to define Collapsible Groups in JSON, including file structure, node types, and complete examples.
- 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
- 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
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 + fallbackis recommended -
enabled: whether the group is enabled -
filter: the actual rule
{ "type": "item", "id": "minecraft:barrel" }
{ "type": "fluid", "id": "minecraft:water" }
{ "type": "mekanism:chemical", "id": "mekanism:hydrogen" }{ "type": "item", "tag": "minecraft:logs" }
{ "type": "fluid", "tag": "c:water" }
{ "type": "mekanism:chemical", "tag": "mekanism:gases" }{ "block_tag": "minecraft:logs" }Notes:
-
BlockTagis item-only - it matches
BlockItementries by checking the backing block tag, not the item tag - groups that use
BlockTagare currently read-only in the player editor
{ "type": "item", "namespace": "create" }
{ "type": "fluid", "namespace": "mekanism" }
{ "type": "mekanism:chemical", "namespace": "mekanism" }{
"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.
{ "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 isgutter_middle_yellow - groups that use these nodes are currently read-only in the player editor
{
"type": "item",
"component": "apotheosis:gem",
"value": "apotheosis:core/ballast"
}Fields:
-
type: must be"item" -
component: theDataComponentTyperegistry ID innamespace:pathform -
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}"
- ResourceLocation-style: plain string, e.g.
Notes:
-
HasComponentcurrently 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
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: theDataComponentTyperegistry ID innamespace:pathform -
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
HasComponentwhen the component value is flat and directly comparable, for example aResourceLocationstring like"apotheosis:core/ballast" - Use
ComponentPathwhen the value you want to match is nested inside the component's structure, for example{"data": [{"id": "irons_spellbooks:blood_needles", ...}]}
Notes:
-
ComponentPathcurrently 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
{
"any": [
{ "type": "item", "id": "minecraft:barrel" },
{ "type": "item", "id": "minecraft:crafting_table" }
]
}{
"all": [
{ "type": "item", "tag": "minecraft:logs" },
{ "type": "item", "namespace": "minecraft" }
]
}{
"not": {
"type": "item",
"id": "minecraft:oak_planks"
}
}{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
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.
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.
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_tagwhen the source category really lives in block tags - use
item_path_starts_withwhen a mod organizes a family by registry naming - keep
namespacealongside path-based matching to stay narrow and easier to reason about - use
notwhen you only need to exclude one or two specific items
Continue reading:
Players
Modpack Developers
Mod Developers
Localization