# Developer > Exports, hooks and integration points of ml_crafting Category: CRAFTING · Source: https://miciomods.it/docs/ml-crafting-developer · Last updated: 2026-07-09 ## Overview Three integration layers: server exports for progression, blueprints and bench registration; a named hook system any resource can attach to; and editable handler stubs in the `open/` folder. ## Server Exports ### Progression - `getPlayerLevel(src)`: Player's crafting level (from ml_skills when external skills are enabled) - `getPlayerCategoryLevel(src, categoryId)`: Level for one recipe category - `addXp(src, amount)`: Award crafting XP ### Blueprints - `getPlayerBlueprints(src)`: Map of blueprint id to `{ type = 'permanent' | 'item' }` - `givePlayerBlueprint(src, blueprintId)`: Permanently unlock a blueprint (alias: `unlockPermanentBlueprint`) - `removePlayerBlueprint(src, blueprintId)`: Remove a permanent unlock ### Benches - `setEntityAsBench(entity, benchTypeId)`: Register an existing entity as a usable bench. Automatically cleared when the registering resource stops - `removeEntityAsBench(entity)`: Unregister it - `setCoordAsBench(coord, benchTypeId, routingBucket?)`: Create a bench at coordinates (`coord.w` = heading). Returns `{ success, id, remove }` where `remove()` deletes it again ### Data & Queue - `getCategories()`: All recipe categories - `getRecipe(recipeId)`: One recipe with ingredients, results and blueprints - `createRecipe(data)`: Create a recipe programmatically - `getPlayerQueue(src)`: The player's queued crafts - `getQueuedCrafts(benchLocationId)`: Queue of one bench ## Hook System Hooks let another resource validate, modify or observe crafting without touching ml_crafting. Register from any server script: **your_script/server.lua** ```lua local hookId = exports.ml_crafting:registerHook('crafting:preCraft', { priority = 10, fn = function(ctx) -- return false to block -- return { success = false, message = 'msg' } to block with a reason -- return { quantity = 1 } to modify the context return true end }) exports.ml_crafting:unregisterHook('crafting:preCraft', hookId) ``` Hooks run in ascending priority. A table return without `success` is merged into the context. Hooks registered by a resource are removed automatically when that resource stops. ### Hook points - `crafting:canInteract`: Player opens a bench. ctx: `src, benchType, benchLocation` - `crafting:enter`: After access is granted, before the menu data is built. Same ctx - `crafting:getCategories`: Filter or extend the category list. ctx adds `categories` - `crafting:getRecipes`: Filter or extend a category's recipe list. ctx adds `categoryId, recipes` - `crafting:getRecipe`: Modify a single recipe before display. ctx adds `recipe` - `crafting:preCraft`: Before ingredients are taken. ctx: `src, recipe, quantity, benchType, benchLocation`. Can change `quantity` or block the craft - `crafting:postCraft`: After results are delivered. ctx adds `results`. Observe-only - `crafting:exit`: Player leaves the bench. ctx: `src, benchType, benchLocation`. Observe-only ## Open Handlers The `open/` folder is never escrowed. The shipped stubs are pre-registered into the hook system at priority 50. ### Server **open/server.lua** ```lua Handlers.CanPlayerUseBench = function(src, benchType, benchLocation) return true -- return false (optionally with a message) to block end Handlers.OnBeforeCraft = function(src, recipe, quantity) return true -- return false to block the craft end Handlers.OnAfterCraft = function(src, recipe, quantity, results) end Handlers.OnBlueprintUnlocked = function(src, blueprintId) end Handlers.OnLevelUp = function(src, category, newLevel) end Handlers.ModifyCraftResults = function(src, recipe, results) return nil -- return a modified results table to replace the output end ``` - `OnLevelUp` fires only with the internal progression (`Config.ExternalSkills.Enabled = false`) - `ModifyCraftResults` runs right before items are handed out, last chance to add, remove or retag results ### Client **open/client.lua** ```lua Handlers.CanInteractWithBench = function(benchType, entity) return true -- return false to hide the interaction end Handlers.OnCraftStarted = function(recipe) end Handlers.GetCustomAnimation = function(recipe) return nil -- or { dict = '...', clip = '...', flag = 49 } end Handlers.OnBenchEnter = function(benchType, entity) end Handlers.OnBenchExit = function(benchType, entity) end Handlers.OnCraftCompleted = function(recipe, results) end ``` ## Skill Bonuses Recipes can carry skill bonuses, set in the recipe editor. When the player has the bound ml_skills skill unlocked, the bonus applies automatically. Four bonus types are supported; values are percentages, and multiple bonuses of the same type stack additively: - `time_reduction`: Shorter craft time, capped at 90% - `extra_xp`: More XP from the craft - `extra_quantity`: Chance-scaled extra output quantity - `ingredient_reduction`: Chance to consume fewer ingredients ## Examples **Register a bench from another resource** **your_script/server.lua** ```lua local bench = exports.ml_crafting:setCoordAsBench( vector4(-261.95, -956.42, 31.22, 90.0), 'weapons_bench' ) if bench.success then print('Bench created with id ' .. bench.id) -- later: bench.remove() end ``` **Job-lock a bench type** **open/server.lua** ```lua Handlers.CanPlayerUseBench = function(src, benchType, benchLocation) if benchType.id == 'medic_bench' and not Bridge.HasJob(src, 'ambulance') then return false, 'error_not_authorized' end return true end ``` **Tax every craft through a hook** **your_script/server.lua** ```lua exports.ml_crafting:registerHook('crafting:postCraft', { priority = 10, fn = function(ctx) -- ctx.src, ctx.recipe, ctx.quantity, ctx.results TriggerEvent('myeconomy:craftTax', ctx.src, ctx.recipe.id, ctx.quantity) end }) ``` **Custom craft animation per recipe** **open/client.lua** ```lua Handlers.GetCustomAnimation = function(recipe) if recipe.category_id == 'cooking' then return { dict = 'amb@prop_human_bbq@male@base', clip = 'base', flag = 49 } end return nil end Handlers.OnCraftCompleted = function(recipe, results) end ``` **React to bench focus on the client** **open/client.lua** ```lua Handlers.OnBenchEnter = function(benchType, entity) local coords = GetEntityCoords(cache.ped) print(('Entered bench at %.1f %.1f %.1f'):format(coords.x, coords.y, coords.z)) end Handlers.OnCraftCompleted = function(recipe, results) end ```