Skip to content

Item Matcher ​

The matcher (matcher) adds conditions on top of item sources to decide whether the item the player just put in counts as the material this recipe wants

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

Keep the two apart: Item Source identifies which item from which plugin, and 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, making it the only 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. If the item_sources list ends up with no valid entry at all, the requirement is rejected at load time and never matches (the config precheck reports item source field must contain at least one valid source); that is not "unrestricted". Only omitting item_sources entirely while a sibling matcher exists means "match on the matcher alone, any source"; with both fields absent the entry never matches either

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 ask you to spell it 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.matcher—This 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 (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 ​

Every path in the table below 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

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; top-level matcher in gems/*.yml (the construction base is a separate field, base_item_source)
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

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. Materials that must come from storage should declare only item_sources. 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. For pure-matcher materials, declare material_id explicitly. 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, so 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