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_sourcescarries only the allowed item sources. Omit it and the item source is unrestricted.matchercarries 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.
| Tier | Purpose | Typical field | Supports matcher |
|---|---|---|---|
| T1 base item | Which vanilla item to build the custom item from | EmakiItem item.source, Gem base_item_source | No |
| T2 GUI rendering | What the interface displays (decoration, placeholders) | Item definitions in GUI templates | No |
| T3 input matching | Whether an incoming item counts as material/input | materials[].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.
| Context | Separator | Correct example |
|---|---|---|
| Item source shorthand | hyphen - | minecraft-diamond_sword |
| Component ID | colon : (namespace optional) | minecraft:enchantments / enchantments |
| Resource ID inside a component | colon : (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.
# 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: 3Matcher types
| type | Aliases | Description |
|---|---|---|
pdc_match | pdc | Match a PDC key/value. |
lore_match | lore | Match lore text. |
component | component_match | Match a Minecraft item component or a value inside it. |
variable_expr | expr, expression | Expression or PAPI condition. |
compare_target | target | Compare a PDC number against the target equipment. |
all_of | all, and | All children match. |
any_of | any, or | Any child matches. |
none_of | none, not | No child matches. |
at_least | none | At least required_count children match. |
exactly | none | Exactly 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
matcher:
type: component
component: enchantments
path: sharpness
operator: '>='
value: 5| Field | Type | Default | Description |
|---|---|---|---|
component | string | required | Component ID. The minecraft: namespace may be omitted. |
path | string | "" | Path into the component. Omit to use the whole value. |
operator | string | see below | Comparison. |
value | any | none | Expected 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
| operator | Aliases | Needs value | Description |
|---|---|---|---|
exists | present | No | Component is present. |
absent | missing | No | Component is not present. |
== | equals, = | Yes | Equal. Numbers compare numerically, booleans as booleans, everything else as text. |
!= | not_equals | Yes | Not equal. |
> | greater_than | Yes | Numeric greater than. |
>= | greater_or_equal | Yes | Numeric greater or equal. |
< | less_than | Yes | Numeric less than. |
<= | less_or_equal | Yes | Numeric less or equal. |
contains | none | Yes | Text contains. |
starts_with | none | Yes | Text prefix. |
ends_with | none | Yes | Text suffix. |
regex | pattern | Yes | Regex search (find semantics, not a full-string match). |
has_key | none | Yes | Map contains the key. Both sides are compared with the minecraft: prefix stripped. |
has_value | none | Yes | List or map values contain the value. |
size | none | Yes | List 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
| Syntax | Meaning | Example |
|---|---|---|
. | Level separator | levels.sharpness |
[n] | Array index, zero-based | floats[0] |
[*] | All elements | modifiers[*].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.
# Any attribute modifier with an amount above 5
matcher:
type: component
component: attribute_modifiers
path: modifiers[*].amount
operator: '>'
value: 5How 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 / location | Write item_sources? | Extra job it has here |
|---|---|---|
Forge materials[] | Recommended | When 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[] | Optional | id 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/*.yml | Separate field | The 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_barrel | Recommended | slot_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 storage | material_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[] | Recommended | The item-source path is also the deduction implementation. |
Strengthen materials[] | Yes | material_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.* | Optional | No extra job; purely a decision input. |
Level rules[] | Yes | Replaces the removed per-rule result_item_sources key. |
Cooking input side (tool, spatula, container, input, fuels[], …) | Optional | No 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:
item_sources:
- minecraft-diamond_sword
matcher:
type: component
component: enchantments
path: sharpness
operator: '>='
value: 1Do 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.
# 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: 0To express "does not have this component", use absent explicitly:
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:
existsrecognises vanilla default components (every tool carriesmax_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.
# Correct
matcher:
type: component
component: unbreakable
operator: exists
# Wrong: rejected at load time, never matches
matcher:
type: component
component: unbreakable
operator: '=='
value: trueFailed 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.
# Wrong: scalar
matcher: 'foo'
# Correct: mapping
matcher:
type: component
component: unbreakable
operator: existsComposition and nesting
Children of all_of, any_of, none_of, at_least, and exactly go in a matchers list.
| Field | Type | Default | Description |
|---|---|---|---|
matchers | list | [] | Child conditions. |
required_count | integer | 1 | Only used by at_least / exactly. |
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: epicComposite 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:
# 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: 3An 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:
| Type | Result when empty |
|---|---|
all_of | true (always matches) |
any_of | false (never matches) |
none_of | true (always matches) |
at_least / exactly | depends 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 item | Impact |
|---|---|
| Whether the component string includes vanilla defaults | Affects exists vs value comparison |
| Real key shape of the enchantments component | Whether a levels subkey exists and whether keys carry a namespace; affects how path is written |
Real structure of attribute_modifiers | Field names inside the modifiers array |
Real serialised form of custom_name | Plain text or a JSON text component; affects text operators |
| Field names of nested item stacks | Internal structure of bundle_contents and charged_projectiles |
| How typed NBT arrays actually appear | How 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
| Module | Config paths |
|---|---|
| Strengthen | target.filter in enhancement_recipes/*.yml; materials[].matcher; top-level matcher in recipes/*.yml (item sources there use source_patterns); stars.*.materials[].matcher |
| Gem | socket_openers.*.matcher in config.yml; top-level matcher in items/*.yml; matcher in gems/*.yml |
| Item | repair.materials[].matcher |
| Forge | blueprint_requirements[].matcher; materials[].matcher |
| Station | materials[].matcher in recipes/*.yml; top-level matcher in recipes_dismantle/*.yml |
| Cooking | ingredients[].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 |
| Level | rules[].matcher in sources/*.yml |
| Storage | behavior.deposit_filter.matcher |
| Skills | upgrade.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
matchercan 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. Astorage_unreachable_materialWARN 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
item_sources:
- minecraft-diamond_sword
matcher:
type: component
component: damage
operator: absentItem source only, no extra condition
item_sources:
- minecraft-iron_ingotExclude anything already enchanted
matcher:
type: none_of
matchers:
- type: component
component: enchantments
operator: existsName contains a marker
matcher:
type: component
component: custom_name
operator: contains
value: LegendaryAt least three distinct enchantments
matcher:
type: component
component: enchantments
path: levels
operator: size
value: 3Troubleshooting
A misconfigured matcher reports nothing at runtime; it either never matches or accepts more than you meant. Verify actively.
- Test both directions. Check that valid items pass and that invalid items are rejected. An
item_sourcesyou forgot to write leaves the item source unrestricted, which positive-only testing will not reveal. - Read the startup log. Conditions rejected at load time log a warning: a missing or unknown
type, item source conditions placed insidematcher, unknown operators, value comparisons on unit components, and non-mappingmatchervalues. - Split composites. When a composite does not match, test each child on its own to find the failing one.
- Probe with
existsfirst. When unsure of a component's shape, confirm presence withoperator: exists, then addpathone level at a time. - Enable debug. CoreLib's
debug.global_allor/corelib debug all onlogs more detail about the matching process.
Related pages
- Item Source: where every
item_sourcesfield 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_matchmatcher reads.