Skip to content

Item Matcher

A matcher (matcher) is CoreLib's shared input predicate. It answers one question: does the item the player just put in count as the material this recipe wants?

Item Source identifies which item from which plugin. A matcher adds conditions on top: enchantment level, name contents, damage taken, durability. Either can be used alone or combined.

Every module that supports matchers uses this same syntax. This page is the authoritative reference; module pages list only their own paths and module-specific limits.

item_sources and matcher are siblings, ANDed

At a decision site (anywhere that judges "does this one item qualify"), the verdict is item_sources AND matcher. They are two parallel top-level fields, never nested inside one another:

  • item_sources carries only the allowed item sources. Omit it and the item source is unrestricted.
  • matcher carries only non-item-source conditions — component, PDC, lore, variable. Omit it and no extra condition applies.
  • Omit both and the entry never matches.

Item source conditions are forbidden inside matcher. The four type values item_source, item_sources, source, and sources are rejected at load time with a warning, and such a matcher never matches. The same ban applies to the matchers child lists of all_of / any_of / none_of / at_least / exactly.

A matcher that omits type is likewise rejected with a warning — it no longer falls back to item-source matching. So is an unknown type.

Legacy configs still load

Old key spellings are still read at load time as compatibility fallbacks, but they are not the recommended form. Cooking's converter handles the old flat matcher keys and keeps fermentation-barrel input identity keys compatible; Forge's converter handles legacy material and blueprint source entries; Strengthen's converter handles the old match block. These conversions are configuration-file migrations only, not live-server or persisted-save migrations. Use dry-run first and keep the generated backup before applying.

Only input-matching fields support matcher

Fields that look like item sources fall into three categories. Only the third supports matcher.

TierPurposeTypical fieldSupports matcher
T1 base itemWhich vanilla item to build the custom item fromEmakiItem item.source, Gem base_item_sourceNo
T2 GUI renderingWhat the interface displays (decoration, placeholders)Item definitions in GUI templatesNo
T3 input matchingWhether an incoming item counts as material/inputmaterials[].item_sources, ingredients[]Yes

DANGER

Never put a matcher on a T1 field. item.source is the mould used to build the item, not a predicate. A matcher there stops the item from being created.

T1 and T3 often live in the same file and look alike. In EmakiItem, item.source is T1 (do not change it) while repair.materials[].matcher is T3 (safe to add). The test is: is this field creating an item, or recognising one?

Item sources use hyphens, component IDs use colons

This is the most common configuration mistake. The two forms are not interchangeable.

ContextSeparatorCorrect example
Item source shorthandhyphen -minecraft-diamond_sword
Component IDcolon : (namespace optional)minecraft:enchantments / enchantments
Resource ID inside a componentcolon : (namespace optional)minecraft:sharpness / sharpness

DANGER

An item source written as minecraft:diamond_sword is silently discarded as invalid, not reported as an error: vanilla identifiers may not contain a colon. Once every entry in item_sources is dropped this way, the list ends up empty and the item source is treated as unrestricted.

Component IDs are the reverse: minecraft-enchantments is read as a nonexistent namespace and never resolves.

yaml
# Correct
item_sources:
  - minecraft-diamond_sword           # item source: hyphen
matcher:
  type: component
  component: minecraft:enchantments   # component ID: colon
  path: minecraft:sharpness           # resource ID: colon
  operator: '>='
  value: 3

Matcher types

typeAliasesDescription
pdc_matchpdcMatch a PDC key/value.
lore_matchloreMatch lore text.
componentcomponent_matchMatch a Minecraft item component or a value inside it.
variable_exprexpr, expressionExpression or PAPI condition.
compare_targettargetCompare a PDC number against the target equipment.
all_ofall, andAll children match.
any_ofany, orAny child matches.
none_ofnone, notNo child matches.
at_leastnoneAt least required_count children match.
exactlynoneExactly required_count children match.

Type names are case-insensitive. type is required: a matcher without it is rejected at load time and never matches. item_source, item_sources, source, and sources are rejected the same way — those conditions belong in the sibling item_sources field.

type: component

yaml
matcher:
  type: component
  component: enchantments
  path: sharpness
  operator: '>='
  value: 5
FieldTypeDefaultDescription
componentstringrequiredComponent ID. The minecraft: namespace may be omitted.
pathstring""Path into the component. Omit to use the whole value.
operatorstringsee belowComparison.
valueanynoneExpected value. Required for every operator except exists / absent.

When operator is omitted it defaults to exists if no value is given, and == if one is.

component is normalised: lowercased, spaces to underscores, minecraft: added when no namespace is present. So custom_name, Custom_Name, and minecraft:custom_name are equivalent.

Operators

operatorAliasesNeeds valueDescription
existspresentNoComponent is present.
absentmissingNoComponent is not present.
==equals, =YesEqual. Numbers compare numerically, booleans as booleans, everything else as text.
!=not_equalsYesNot equal.
>greater_thanYesNumeric greater than.
>=greater_or_equalYesNumeric greater or equal.
<less_thanYesNumeric less than.
<=less_or_equalYesNumeric less or equal.
containsnoneYesText contains.
starts_withnoneYesText prefix.
ends_withnoneYesText suffix.
regexpatternYesRegex search (find semantics, not a full-string match).
has_keynoneYesMap contains the key. Both sides are compared with the minecraft: prefix stripped.
has_valuenoneYesList or map values contain the value.
sizenoneYesList length / map entry count / string length equals the number.

Numeric comparisons parse both sides as numbers and fail if either side is not numeric. >, >=, <, and <= never fall back to lexicographic comparison.

Path syntax

SyntaxMeaningExample
.Level separatorlevels.sharpness
[n]Array index, zero-basedfloats[0]
[*]All elementsmodifiers[*].amount
"..."Quote keys containing special characters"minecraft:sharpness"

[*] expands every element of a list or every value of a map; a match on any one of them counts as a hit.

Resource ID keys resolve the namespace in both directions: path: sharpness finds the real key minecraft:sharpness, and path: minecraft:sharpness finds a real key of sharpness.

yaml
# Any attribute modifier with an amount above 5
matcher:
  type: component
  component: attribute_modifiers
  path: modifiers[*].amount
  operator: '>'
  value: 5

How matcher relates to item_sources

Both fields take part in the decision, and both must hold. Beyond deciding, item_sources also carries a few non-decision jobs, which is why some positions still want it spelled out even when a matcher would suffice.

Output nodes use scalar item_source

Cooking, Forge, and Station output nodes now use scalar item_source as the canonical field. It accepts exactly one source value, cannot be a list, and cannot appear together with item_sources. The loaders still accept a single legacy item_sources entry with a warning; multiple sources or a matcher on an output node are rejected. Outputs construct items and are not matcher decision sites.

Module / locationWrite item_sources?Extra job it has here
Forge materials[]RecommendedWhen no explicit identity is present, sources help derive the material identity. material_id is the selection/lookup identity, count_key is the quantity aggregation and consumption identity, and audit_id is the finished-item PDC audit/refresh identity. They may be declared independently.
Forge blueprint_requirements[]Optionalid groups acceptable forms for counting and feeds the by-source reverse index/API view. item_sources + matcher remain decision fields.
Gem top level in gems/*.ymlSeparate fieldThe construction base is the scalar base_item_source, not a matching position. Recognition uses the sibling item_sources + matcher pair. The old top-level item_sources is only a construction fallback.
Cooking inputs[] in recipes/fermentation_barrelRecommendedslot_id is the persisted slot identity; count_key is the quantity aggregation/consumption identity. Both must be stable and slot IDs must be unique within a recipe.
Station materials[]Yes, when the material may come from storagematerial_id identifies the material; requirement_id and count_key are retained in allocation/consumption records. Storage counts by item source only, so matcher-bearing materials use backpack stack allocation.
Skills materials[], Item repair.materials[]RecommendedThe item-source path is also the deduction implementation.
Strengthen materials[]Yesmaterial_id identifies the stage rule and selection; count_key is the aggregation identity. The matcher is evaluated together with the source list.
Gem socket_openers.*OptionalNo extra job; purely a decision input.
Level rules[]YesReplaces the removed per-rule result_item_sources key.
Cooking input side (tool, spatula, container, input, fuels[], …)OptionalNo extra job; purely a decision input. Each role holds its own item_sources + matcher pair.
Storage deposit_filter.matcherThis is a filter, not a decision site: a hit on entries or on matcher counts as a hit. Unchanged.

slot_groups, stats_any and source_patterns are separate top-level fields that also AND with item_sources and matcher (all must hold).

To express "this kind of item AND some component condition"

Write the two conditions as sibling fields. The item source goes in item_sources, the component condition in matcher, and both must hold:

yaml
item_sources:
  - minecraft-diamond_sword
matcher:
  type: component
  component: enchantments
  path: sharpness
  operator: '>='
  value: 1

Do not wrap them into one all_of with a type: item_source child — that child is rejected at load time and the whole matcher then never matches.

Key semantics

Read this section before writing your first matcher.

Missing is not zero

When a component is absent, every value comparison fails. A missing component is never treated as 0, "", or an empty list.

yaml
# Does NOT match "a sword with no enchantments".
# It matches only "has an enchantments component whose sharpness is 0".
matcher:
  type: component
  component: enchantments
  path: sharpness
  operator: '=='
  value: 0

To express "does not have this component", use absent explicitly:

yaml
matcher:
  type: component
  component: enchantments
  operator: absent

!= likewise never succeeds through absence. To express "either missing or not equal to X", list both cases under any_of.

exists and value comparison read different data

exists / absent ask whether the component is present; value comparisons read the component's data. The two look at different sets:

  • exists recognises vanilla default components (every tool carries max_damage, for example).
  • Value comparisons only see components explicitly set on the item.

So on an untouched vanilla diamond sword, exists on max_damage succeeds, but a numeric comparison against max_damage does not match. Use value comparisons to filter on values a server owner actually wrote; use exists to filter on whether the item has that kind of property at all.

WARNING

The runtime behaviour of this rule has not been verified on a real Paper server. See Unverified items.

Unit components support only exists / absent

unbreakable, glider, and intangible_projectile carry no value; they are just flags. A value comparison against them is rejected at load time with a warning, and that condition never matches.

yaml
# Correct
matcher:
  type: component
  component: unbreakable
  operator: exists

# Wrong: rejected at load time, never matches
matcher:
  type: component
  component: unbreakable
  operator: '=='
  value: true

Failed path evaluation returns false, never throws

A malformed path does not spam errors or abort config loading — the condition simply never becomes true. There is no direct feedback, so verify with logs and live testing.

An invalid regex returns false

The regex pattern comes from the server owner. If it fails to compile, a warning is logged and the condition evaluates to false without affecting other conditions.

A misspelled or missing type never matches

If type names something that does not exist, or is missing entirely, the matcher is rejected at load time with a warning and never matches. The old "unknown type always passes" behaviour is gone, and so is the old default of item_source.

WARNING

A rejected matcher fails closed, so the symptom is "this recipe never accepts anything". Read the startup log for Matcher rejected at load time lines before hunting elsewhere.

A non-mapping config never matches

matcher must be a mapping. A scalar logs a warning at load time and the condition never matches.

yaml
# Wrong: scalar
matcher: 'foo'

# Correct: mapping
matcher:
  type: component
  component: unbreakable
  operator: exists

Composition and nesting

Children of all_of, any_of, none_of, at_least, and exactly go in a matchers list.

FieldTypeDefaultDescription
matcherslist[]Child conditions.
required_countinteger1Only used by at_least / exactly.
yaml
matcher:
  type: at_least
  required_count: 2
  matchers:
    - type: component
      component: enchantments
      path: sharpness
      operator: '>='
      value: 5
    - type: component
      component: unbreakable
      operator: exists
    - type: component
      component: rarity
      operator: '=='
      value: epic

Composite types nest to any depth, and nested entries use exactly the same syntax as the top level. Item source conditions stay outside, in the sibling item_sources:

yaml
# A diamond or netherite sword, undamaged, with Sharpness 3 or higher
item_sources:
  - minecraft-diamond_sword
  - minecraft-netherite_sword
matcher:
  type: all_of
  matchers:
    - type: none_of
      matchers:
        - type: component
          component: damage
          operator: '>'
          value: 0
    - type: component
      component: enchantments
      path: sharpness
      operator: '>='
      value: 3

An item_sources list is an any-of: the item qualifies when it matches any one entry.

Empty list behaviour

An empty matchers list does not behave the same across types. Avoid leaving one empty:

TypeResult when empty
all_oftrue (always matches)
any_offalse (never matches)
none_oftrue (always matches)
at_least / exactlydepends on required_count; true when required_count: 0

Unverified items

The six items below concern runtime shapes on a real Paper server. They were not verified against a live server this cycle, only against local JVM parsing and evaluation. Test configurations that depend on these details on a staging server before going to production.

Unverified itemImpact
Whether the component string includes vanilla defaultsAffects exists vs value comparison
Real key shape of the enchantments componentWhether a levels subkey exists and whether keys carry a namespace; affects how path is written
Real structure of attribute_modifiersField names inside the modifiers array
Real serialised form of custom_namePlain text or a JSON text component; affects text operators
Field names of nested item stacksInternal structure of bundle_contents and charged_projectiles
How typed NBT arrays actually appearHow forms such as [I;...] are presented in components

The syntax of every path example on this page (levels.sharpness, modifiers[*].amount, and so on) is settled, but the exact key names depend on the shapes above and may need adjusting against a live server.

NOTE

Confirmed by local JVM test runs: SNBT numeric suffix stripping (3.0f compares as 3.0, 5b as 5), typed array parsing, path evaluation including [*] wildcards and two-way resource ID namespacing, and config parsing for all five composite types at arbitrary nesting depth. This is not a claim of Paper or Folia live-server success. Actual component serialisation, thread timing, and compatibility of old YAML or persisted state must be tested on the target server separately.

Where each module accepts a matcher

ModuleConfig paths
Strengthentarget.filter in enhancement_recipes/*.yml; materials[].matcher; top-level matcher in recipes/*.yml (item sources there use source_patterns); stars.*.materials[].matcher
Gemsocket_openers.*.matcher in config.yml; top-level matcher in items/*.yml; matcher in gems/*.yml
Itemrepair.materials[].matcher
Forgeblueprint_requirements[].matcher; materials[].matcher
Stationmaterials[].matcher in recipes/*.yml; top-level matcher in recipes_dismantle/*.yml
Cookingingredients[].matcher, inputs[].matcher, input.matcher, container.matcher, stations.chopping_board.tool.matcher, stations.wok.spatula.matcher, stations.juicer.container.matcher, stations.oven.fuels[].matcher, stations.steamer.fuels[].matcher, stations.steamer.moisture_rules[].input.matcher, nutrition.food_sources[].matcher
Levelrules[].matcher in sources/*.yml
Storagebehavior.deposit_filter.matcher
Skillsupgrade.levels.<n>.materials[].matcher

Every path above pairs with a sibling item_sources at the same level, and the two are ANDed. Strengthen's top-level recipe matching is the one exception: it expresses item sources through source_patterns (regex) instead.

Two module-specific limits:

  • Station: any material that declares a matcher can be supplied from the backpack only — storage counts stock by item source and cannot see a real item's components, so it is skipped for that material. A storage_unreachable_material WARN is logged at load time. See Station recipe definitions.
  • Forge: materials are first-match in declaration order, so a broad condition declared earlier shadows a narrower one declared later. See Forge materials.

Cooking's block side (workstation block detection) and output side (output.item_sources) do not support matchers.

Examples

Undamaged diamond sword only

yaml
item_sources:
  - minecraft-diamond_sword
matcher:
  type: component
  component: damage
  operator: absent

Item source only, no extra condition

yaml
item_sources:
  - minecraft-iron_ingot

Exclude anything already enchanted

yaml
matcher:
  type: none_of
  matchers:
    - type: component
      component: enchantments
      operator: exists

Name contains a marker

yaml
matcher:
  type: component
  component: custom_name
  operator: contains
  value: Legendary

At least three distinct enchantments

yaml
matcher:
  type: component
  component: enchantments
  path: levels
  operator: size
  value: 3

Troubleshooting

A misconfigured matcher reports nothing at runtime; it either never matches or accepts more than you meant. Verify actively.

  1. Test both directions. Check that valid items pass and that invalid items are rejected. An item_sources you forgot to write leaves the item source unrestricted, which positive-only testing will not reveal.
  2. Read the startup log. Conditions rejected at load time log a warning: a missing or unknown type, item source conditions placed inside matcher, unknown operators, value comparisons on unit components, and non-mapping matcher values.
  3. Split composites. When a composite does not match, test each child on its own to find the failing one.
  4. Probe with exists first. When unsure of a component's shape, confirm presence with operator: exists, then add path one level at a time.
  5. Enable debug. CoreLib's debug.global_all or /corelib debug all on logs more detail about the matching process.
  • Item Source: where every item_sources field gets its values.
  • Condition: decides whether an operation may proceed, a separate mechanism from a matcher's "does this item count".
  • PDC: the data the pdc_match matcher reads.