Recipes
Recipes are a way to transform a set of objects into other objects within a Minecraft world. Although Minecraft uses this system purely for item transformations, the system is built in a way that allows any kind of objects - blocks, entities, etc. - to be transformed. Almost all recipes use recipe data files; a "recipe" is assumed to be a data-driven recipe in this article unless explicitly stated otherwise.
Recipe data files are located at data/<namespace>/recipe/<path>.json. For example, the recipe minecraft:diamond_block is located at data/minecraft/recipe/diamond_block.json.
Terminology
- A recipe JSON, or recipe file, is a JSON file that is loaded and stored by the
RecipeManager. It contains info such as the recipe type, the inputs and outputs, as well as additional information (e.g. processing time). - A
Recipeholds in-code representations of all JSON fields, alongside the matching logic ("Does this input match the recipe?") and some other properties. - A
RecipeInputis a type that provides inputs to a recipe. Comes in several subclasses, e.g.CraftingInputorSingleRecipeInput(for furnaces and similar). - A recipe ingredient, or just ingredient, is a single input for a recipe (whereas the
RecipeInputgenerally represents a collection of inputs to check against a recipe's ingredients). Ingredients are a very powerful system and as such outlined in their own article. - A
PlacementInfois a definition of items the recipe contains and what indexes they should populate. If the recipe cannot be captured to some degree based on the items provided (e.g., only changing the data components), thenPlacementInfo#NOT_PLACEABLEis used. - A
SlotDisplaydefines how a single slot should display within a recipe viewer, like the recipe book. - A
RecipeDisplaydefines theSlotDisplays of a recipe to be consumed by a recipe viewer, like the recipe book. While the interface only contains methods for the result of a recipe and the workstation the recipe was conducted within, a subtype can capture information like ingredients or grid size. - The
RecipeManageris a singleton field on the server that holds all loaded recipes. - A
RecipeSerializeris basically a wrapper around aMapCodecand aStreamCodec, both used for serialization. - A
RecipeTypeis the registered type equivalent of aRecipe. It is mainly used when looking up recipes by type. As a rule of thumb, different crafting containers should use differentRecipeTypes. For example, theminecraft:craftingrecipe type covers theminecraft:crafting_shapedandminecraft:crafting_shapelessrecipe serializers, as well as the special crafting serializers. - A
RecipeBookCategoryis a group representing some recipes when viewed through a recipe book. - A recipe advancement is an advancement responsible for unlocking a recipe in the recipe book. They are not required, and generally neglected by players in favor of recipe viewer mods, however the recipe data provider generates them for you, so it's recommended to just roll with it.
- A
RecipePropertySetdefines the available list of ingredients that can be accepted by the defined input slot in a menu. - A
RecipeBuilderis used during datagen to create JSON recipes. - A recipe factory is a method reference used to create a
Recipefrom aRecipeBuilder. It can either be a reference to a constructor, or a static builder method, or a functional interface (often namedFactory) created specifically for this purpose.
JSON Specification
The contents of recipe files vary greatly depending on the selected type. Common to all recipe files are the type and neoforge:conditions properties:
{
// The recipe type. This maps to an entry in the recipe serializer registry.
"type": "minecraft:crafting_shaped",
// A list of data load conditions. Optional, NeoForge-added. See the article linked above for more information.
"neoforge:conditions": [ /*...*/ ]
}
A full list of types provided by Minecraft can be found in the Built-In Recipe Types article. Mods can also define their own recipe types.
Using Recipes
Recipes are loaded, stored and obtained via the RecipeManager class, which is in turn obtained via ServerLevel#recipeAccess or - if you don't have a ServerLevel available - ServerLifecycleHooks.getCurrentServer()#getRecipeManager. The server does not sync the recipes to the client by default, instead it only sends the RecipePropertySets for restricting inputs on menu slots. Additionally, whenever a recipe is unlocked for the recipe book, its RecipeDisplays and the corresponding RecipeDisplayEntrys are sent to the client (excluding all recipes where Recipe#isSpecial returns true) As such, recipe logic should always run on the server.
The easiest way to get a recipe is by its resource key:
RecipeManager recipes = serverLevel.recipeAccess();
// RecipeHolder<?> is a record of the resource key and the recipe itself.
Optional<RecipeHolder<?>> optional = recipes.byKey(
ResourceKey.create(Registries.RECIPE, ResourceLocation.withDefaultNamespace("diamond_block"))
);
optional.map(RecipeHolder::value).ifPresent(recipe -> {
// Do whatever you want to do with the recipe here. Be aware that the recipe may be of any type.
});
A more practically applicable method is constructing a RecipeInput and trying to get a matching recipe. In this example, we will be creating a CraftingInput containing one diamond block using CraftingInput#of. This will create a shapeless input, a shaped input would instead use CraftingInput#ofPositioned, and other inputs would use other RecipeInputs (for example, furnace recipes will generally use new SingleRecipeInput).
RecipeManager recipes = serverLevel.recipeAccess();
// Construct a RecipeInput, as required by the recipe. For example, construct a CraftingInput for a crafting recipe.
// The parameters are width, height and items, respectively.
CraftingInput input = CraftingInput.of(1, 1, List.of(new ItemStack(Items.DIAMOND_BLOCK)));
// The generic wildcard on the recipe holder should then extend CraftingRecipe.
// This allows for more type safety later on.
Optional<RecipeHolder<? extends CraftingRecipe>> optional = recipes.getRecipeFor(
// The recipe type to get the recipe for. In our case, we use the crafting type.
RecipeType.CRAFTING,
// Our recipe input.
input,
// Our level context.
serverLevel
);
// This returns the diamond block -> 9 diamonds recipe (unless a datapack changes that recipe).
optional.map(RecipeHolder::value).ifPresent(recipe -> {
// Do whatever you want here. Note that the recipe is now a CraftingRecipe instead of a Recipe<?>.
});