Skip to main content
Version: 1.20.x

Ingredients

Ingredients are predicate handlers for item-based inputs which check whether a certain ItemStack meets the condition to be a valid input in a recipe. All vanilla recipes that take inputs use an Ingredient or a list of Ingredients, which is then merged into a single Ingredient.

Custom Ingredients

Custom ingredients can be specified by setting type to the name of the ingredient's serializer, with the exception of compound ingredients. When no type is specified, type defaults to the vanilla ingredient minecraft:item. Custom ingredients can also easily be used in data generation.

Forge Types

Forge provides a few additional Ingredient types for programmers to implement.

CompoundIngredient

Though they are functionally identical, Compound ingredients replaces the way one would implement a list of ingredients would in a recipe. They work as a set OR where the passed in stack must be within at least one of the supplied ingredients. This change was made to allow custom ingredients to work correctly within lists. As such, no type needs to be specified.

// For some input
[
// At least one of these ingredients must match to succeed
{
// Ingredient
},
{
// Custom ingredient
"type": "examplemod:example_ingredient"
}
]

StrictNBTIngredient

StrictNBTIngredients compare the item, damage, and the share tags (as defined by IForgeItem#getShareTag) on an ItemStack for exact equivalency. This can be used by specifying the type as forge:nbt.

// For some input
{
"type": "forge:nbt",
"item": "examplemod:example_item",
"nbt": {
// Add nbt data (must match exactly what is on the stack)
}
}

PartialNBTIngredient

PartialNBTIngredients are a looser version of StrictNBTIngredient as they compare against a single or set of items and only keys specified within the share tag (as defined by IForgeItem#getShareTag). This can be used by specifying the type as forge:partial_nbt.

// For some input
{
"type": "forge:partial_nbt",

// Either 'item' or 'items' must be specified
// If both are specified, only 'item' will be read
"item": "examplemod:example_item",
"items": [
"examplemod:example_item",
"examplemod:example_item2"
// ...
],

"nbt": {
// Checks only for equivalency on 'key1' and 'key2'
// All other keys in the stack will not be checked
"key1": "data1",
"key2": {
// Data 2
}
}
}

IntersectionIngredient

IntersectionIngredients work as a set AND where the passed in stack must match all supplied ingredients. There must be at least two ingredients supplied to this. This can be used by specifying the type as forge:intersection.

// For some input
{
"type": "forge:intersection",

// All of these ingredients must return true to succeed
"children": [
{
// Ingredient 1
},
{
// Ingredient 2
}
// ...
]
}

DifferenceIngredient

DifferenceIngredients work as a set subtraction (SUB) where the passed in stack must match the first ingredient but must not match the second ingredient. This can be used by specifying the type as forge:difference.

// For some input
{
"type": "forge:difference",
"base": {
// Ingredient the stack is in
},
"subtracted": {
// Ingredient the stack is NOT in
}
}

Creating Custom Ingredients

Custom ingredients can be created by implementing IIngredientSerializer for the created Ingredient subclass.

tip

Custom ingredients should subclass AbstractIngredient as it provides some useful abstractions for ease of implementation.

Ingredient Subclass

There are three important methods to implement for each ingredient subclass:

MethodDescription
getSerializerReturns the serializer used to read and write the ingredient.
testReturns true if the input is valid for this ingredient.
isSimpleReturns false if the ingredient matches on the stack's tag. AbstractIngredient subclasses will need to define this behavior, while Ingredient subclasses return true by default.

All other defined methods are left as an exercise to the reader to use as required for the ingredient subclass.

IIngredientSerializer

IIngredientSerializer subtypes must implement three methods:

MethodDescription
parse (JSON)Converts a JsonObject to an Ingredient.
parse (Network)Reads the network buffer to decode an Ingredient.
writeWrites an Ingredient to the network buffer.

Additionally, Ingredient subclasses should implement Ingredient#toJson for use with data generation. AbstractIngredient subclasses make #toJson an abstract method requiring the method to be implemented.

Afterwards, a static instance should be declared to hold the initialized serializer and then registered using CraftingHelper#register either during the RegisterEvent for RecipeSerializers or during FMLCommonSetupEvent. The Ingredient subclass return the static instance of the serializer in Ingredient#getSerializer.

// In some serializer class
public static final ExampleIngredientSerializer INSTANCE = new ExampleIngredientSerializer();

// In some handler class
public void registerSerializers(RegisterEvent event) {
event.register(ForgeRegistries.Keys.RECIPE_SERIALIZERS,
helper -> CraftingHelper.register(registryName, INSTANCE)
);
}

// In some ingredient subclass
@Override
public IIngredientSerializer<? extends Ingredient> getSerializer() {
return INSTANCE;
}
tip

If using FMLCommonSetupEvent to register an ingredient serializer, it must be enqueued to the synchronous work queue via FMLCommonSetupEvent#enqueueWork as CraftingHelper#register is not thread-safe.