Orbit StudiosOrbit Studios
Orbit Studios Resourcesorbit-craftingsystem

Configuration

Configure orbit-craftingsystem shared recipes, stations, benches, blueprints, images, webhooks, and logging hooks.

Configuration

orbit-craftingsystem has two public config files. Use shared/config.lua for gameplay and recipes. Use server/config.lua for server-only logging, image paths, and Discord webhooks.

Do not mix the files

Put craftable items, benches, stations, blueprints, XP, and access rules in shared/config.lua. Put webhook URLs and logging callbacks in server/config.lua.

File Map

config.lua
config.lua

Prop

Type

Setup Workflow

Choose where the item belongs

Use a station item when the recipe should be available at a fixed world location. Use a bench non-blueprint item when the recipe should always be available on a portable bench. Use a blueprint item when players must own or install a blueprint first.

Add the inventory items first

Every final item and every required material must exist in your inventory or framework item list before testing. If the inventory does not know metalscrap, steel, or weapon_pistol, the crafting system cannot add or remove them correctly.

Add one recipe and test it

Add one recipe to shared/config.lua, restart the resource, open the station or bench, and test one craft. After that works, duplicate the same structure for the rest of your recipes.

Enable logging

Add your Discord webhook URL in server/config.lua, then keep or customize Config.Server.OnCraft and Config.Server.SuspiciousActivity.

Shared Config Shape

shared/config.lua is read in this order. Keeping the same order makes it much easier to compare your file with the shipped config when you update the resource.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config = Config or {}

Config.DeveloperMode = false
Config.CraftingWithWheel = true
Config.NuiState = function(bool)
    if not Config.DeveloperMode then return end
    print("NUI Open:", bool)
end

Config.Celebration = {
    animDict = "anim@mp_player_intcelebrationmale@air_guitar",
    animName = "air_guitar"
}

Config.BenchAnimations = {
    putting_down_bench = {
        duration = 5000,
        animDict = "amb@world_human_hammering@male@base",
        anim = "base",
        flag = 1
    },
    moving_bench = {
        duration = 1000,
        animDict = "amb@world_human_hammering@male@base",
        anim = "base",
        flag = 1
    }
}

Config.LevelValues = {
    xpGainMultiplier = 10,
    levelUpXPMultiplier = 100,
    loseMultiplier = 0.5
}

Config.MainRoutingBucket = 0
Config.Benches = {}
Config.Blueprints = {}
Config.CustomBlueprints = {}
Config.Stations = {}

The empty tables above are only an outline. In your real file, Config.Benches, Config.Blueprints, and Config.Stations contain the actual bench types, blueprint recipes, fixed stations, recipes, targets, and access groups.

Add Bench Items To Inventory

Every key inside Config.Benches.benchTypes must also exist as an inventory item. The default config includes weapon-crafting_bench and medical_bench, so those item names need to be added before players can place those benches.

Bench item names must match

If the bench type is ["weapon-crafting_bench"], the inventory item must also be weapon-crafting_bench. Bench item names cannot start with weapon_ and must include bench.

Add the bench item inside your inventory resource. For ESX servers using ox_inventory, this is usually ox_inventory/data/items.lua.

resources/[standalone]/ox_inventory/data/items.lua
["weapon-crafting_bench"] = {
    label = "Weapon Crafting Bench",
    weight = 1000,
    stack = false,
    close = true,
    description = "Places a weapon crafting bench."
},

["medical_bench"] = {
    label = "Medical Crafting Bench",
    weight = 1000,
    stack = false,
    close = true,
    description = "Places a medical crafting bench."
},

If your ESX server uses a different inventory, add the same item names using that inventory's item format. The important part is that the item name exactly matches the bench key in Config.Benches.benchTypes.

Add the bench item in QBCore's shared item file, not inside the crafting resource.

resources/[qb]/qb-core/shared/items.lua
["weapon-crafting_bench"] = {
    name = "weapon-crafting_bench",
    label = "Weapon Crafting Bench",
    weight = 1000,
    type = "item",
    image = "weapon-crafting_bench.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = "Places a weapon crafting bench."
},

["medical_bench"] = {
    name = "medical_bench",
    label = "Medical Crafting Bench",
    weight = 1000,
    type = "item",
    image = "medical_bench.png",
    unique = true,
    useable = true,
    shouldClose = true,
    combinable = nil,
    description = "Places a medical crafting bench."
},

unique = true is recommended because benches can carry metadata, especially when Config.Blueprints.store = "bench".

Add the bench item inside your inventory resource. For most Qbox/QBX servers this is the inventory item file, for example ox_inventory/data/items.lua.

resources/[standalone]/ox_inventory/data/items.lua
["weapon-crafting_bench"] = {
    label = "Weapon Crafting Bench",
    weight = 1000,
    stack = false,
    close = true,
    description = "Places a weapon crafting bench."
},

["medical_bench"] = {
    label = "Medical Crafting Bench",
    weight = 1000,
    stack = false,
    close = true,
    description = "Places a medical crafting bench."
},

If you rename or add a bench type in Config.Benches.benchTypes, add the same item name here too.

The crafting system registers each bench type as a usable item through orbit-lib. That means players use the inventory item, the resource previews the bench prop, and then the bench is placed if the player confirms the position.

Where To Add Items

Craftable items can live in three places. The recipe shape is mostly the same, but the location decides how the player accesses it.

Put fixed-location recipes inside Config.Stations[stationId].items.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Stations = {
    HideStationDistance = 50,

    ["weapon_station"] = {
        title = "Weapon Crafting Station",
        secondaryTitle = "Orbit Studios",
        blip = {
            haveBlip = true,
            sprite = 110,
            color = 1,
            scale = 0.8,
            coords = vector3(502.64, -1338.17, 29.31)
        },
        target = {
            type = "npc",
            model = "s_m_m_ammucountry",
            coords = vector3(502.64, -1338.17, 29.31),
            heading = 37.18
        },
        groups = {
            jobs = {},
            gangs = {}
        },
        items = {
            ["weapon_pistol"] = {
                type = "weapon",
                itemLevel = 1,
                baseOdds = 50,
                maxOdds = 90,
                amountToCraft = 1,
                requirements = {
                    { item = "plastic", amount = 5, removeOnSuccess = true, removeOnFail = true },
                    { item = "iron", amount = 5, removeOnSuccess = true, removeOnFail = false }
                }
            }
        }
    }
}

Leave groups.jobs and groups.gangs empty for a public station. Add job or gang names only when the station should be restricted.

Put bench recipes that do not need blueprints inside Config.Benches.benchTypes[benchItemName].nonBlueprintItems.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Benches = {
    onlyOwnerCanMove = true,
    enableBenches = true,
    enablePersistentBenches = true,
    HideBenchDistance = 50,
    benchTypes = {
        ["weapon-crafting_bench"] = {
            benchProp = "prop_tool_bench02",
            benchLabel = "Weapon Bench",
            title = "Weapon Crafting Bench",
            secondaryTitle = "Orbit Studios",
            nonBlueprintItems = {
                ["weapon_knife"] = {
                    type = "weapon",
                    itemLevel = 1,
                    baseOdds = 50,
                    maxOdds = 90,
                    amountToCraft = 1,
                    requirements = {
                        { item = "metalscrap", amount = 10, removeOnSuccess = true, removeOnFail = true },
                        { item = "steel", amount = 5, removeOnSuccess = true, removeOnFail = false }
                    }
                }
            }
        }
    }
}

The bench type key, such as weapon-crafting_bench, must match the bench inventory item name. Bench item names cannot start with weapon_ and must include bench.

Put blueprint-locked recipes inside Config.Blueprints.items.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Blueprints = {
    store = "bench",
    items = {
        ["weapon_pistol"] = {
            bench = "weapon-crafting_bench",
            type = "weapon",
            itemLevel = 1,
            baseOdds = 60,
            maxOdds = 90,
            amountToCraft = 1,
            requirements = {
                { item = "metalscrap", amount = 10, removeOnSuccess = true, removeOnFail = true },
                { item = "steel", amount = 5, removeOnSuccess = true, removeOnFail = false }
            }
        }
    }
}

store = "bench" stores learned blueprints on the bench. store = "player" stores learned blueprints on the player. The bench value must match a key in Config.Benches.benchTypes.

Use Config.CustomBlueprints when the physical inventory item has a different name from the recipe key.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Blueprints = {
    store = "bench",
    items = {
        ["weapon_pistol"] = {
            bench = "weapon-crafting_bench",
            type = "weapon",
            itemLevel = 1,
            baseOdds = 60,
            maxOdds = 90,
            requirements = {
                { item = "metalscrap", amount = 10, removeOnSuccess = true, removeOnFail = true }
            }
        }
    }
}

Config.CustomBlueprints = {
    ["weapon_pistol"] = {
        customBlueprintName = "blueprint_weapon_pistol"
    }
}

In this example, the blueprint content is still weapon_pistol, but the inventory item given to the player is blueprint_weapon_pistol.

Recipe Fields

Every recipe uses the same core fields whether it is placed in a station, bench, or blueprint list.

Prop

Type

resources/[orbit]/orbit-craftingsystem/shared/config.lua
["firstaid"] = {
    itemLevel = 2,
    baseOdds = 40,
    maxOdds = 90,
    amountToCraft = 2,
    requirements = {
        { item = "bandage", amount = 5, removeOnSuccess = true, removeOnFail = true },
        { item = "painkillers", amount = 2, removeOnSuccess = true, removeOnFail = true }
    }
}

Set removeOnSuccess and removeOnFail explicitly for each requirement while learning the config. That makes it obvious whether materials disappear on a successful craft, a failed craft, or both.

Core Shared Options

Prop

Type

Crafting Wheel

Config.CraftingWithWheel decides how strict the crafting level system is.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.CraftingWithWheel = true

When this is true, players use the success-percentage crafting wheel. itemLevel, baseOdds, and maxOdds decide how difficult the craft is and how much the chance can improve.

When this is false, crafting becomes level-gated. Players can craft items at their level or below their level, and the percentage wheel is not used.

NUI State Callback

Config.NuiState runs whenever the crafting UI opens or closes. Use it when another resource needs to know that the player is inside the crafting interface.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.NuiState = function(bool)
    if not Config.DeveloperMode then return end
    print("NUI Open:", bool)
end

bool = true means the UI opened. bool = false means it closed.

Set it to nil if you do not need this callback:

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.NuiState = nil

Celebration Animation

Config.Celebration is the animation played after a successful craft.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Celebration = {
    animDict = "anim@mp_player_intcelebrationmale@air_guitar",
    animName = "air_guitar"
}

To change the animation, replace both values with a valid GTA animation dictionary and animation name. To disable the celebration, set both values to nil.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Celebration = {
    animDict = nil,
    animName = nil
}

Bench Animations

Config.BenchAnimations is used by the orbit-lib progress integration when a player places or moves a bench.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.BenchAnimations = {
    putting_down_bench = {
        duration = 5000,
        animDict = "amb@world_human_hammering@male@base",
        anim = "base",
        flag = 1
    },
    moving_bench = {
        duration = 1000,
        animDict = "amb@world_human_hammering@male@base",
        anim = "base",
        flag = 1
    }
}

duration is in milliseconds. animDict, anim, and flag are passed to the progress animation. If you make the duration very short, bench placement will feel instant and players may not understand that the bench is being placed.

Level Values

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.LevelValues = {
    xpGainMultiplier = 10,
    levelUpXPMultiplier = 100,
    loseMultiplier = 0.5
}

loseMultiplier = 0.5 means a failed craft gives half of the XP the craft would normally give.

Bench Placement

Bench placement controls where portable benches can be placed.

Blacklist mode allows bench placement everywhere except inside listed zones.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Benches.benchPlacement = {
    enabled = true,
    mode = "blacklist",
    zones = {
        {
            vectors = {
                vector3(494.22, -1328.00, 29.00),
                vector3(494.70, -1333.96, 29.00),
                vector3(500.11, -1335.09, 29.00)
            },
            height = 10
        }
    }
}

Whitelist mode blocks bench placement everywhere except inside listed zones.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
Config.Benches.benchPlacement = {
    enabled = true,
    mode = "whitelist",
    zones = {
        {
            vectors = {
                vector3(493.93, -1335.25, 29.00)
            },
            radius = 5.0
        }
    }
}

Use vectors plus height for polygon zones. Use vectors plus radius for sphere zones. Use vectors plus size = vec3(...) for box zones.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
{
    vectors = {
        vector3(493.93, -1335.25, 29.00)
    },
    size = vec3(2, 2, 2)
}

Station Access

Access groups live inside each station. Leave both tables empty for public use.

resources/[orbit]/orbit-craftingsystem/shared/config.lua
groups = {
    jobs = {},
    gangs = {}
}

Restrict by job or gang name with a minimum grade:

resources/[orbit]/orbit-craftingsystem/shared/config.lua
groups = {
    jobs = {
        ["police"] = 0,
        ["ambulance"] = 2
    },
    gangs = {
        ["lostmc"] = 1
    }
}

["ambulance"] = 2 means the player must have the ambulance job at grade 2 or higher. Job and gang names must match the data returned by your framework through orbit-lib.

Server Config

resources/[orbit]/orbit-craftingsystem/server/config.lua is server-only. This is where inventory image URLs, Discord webhooks, and logging callbacks live.

Prop

Type

Inventory Image URLs

Use Config.Server.CustomImageURL when item images are missing in the crafting UI.

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.CustomImageURL = false

For ox_inventory, this is a common value:

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.CustomImageURL = "cfx-nui-ox_inventory/web/images/"

The image file name should match the inventory item name. For example, weapon_pistol normally needs an image named like weapon_pistol.png in the configured image directory.

Webhooks

Add your Discord webhook URL in server/config.lua. Do not put webhook URLs in shared/config.lua, because shared files can be loaded client-side.

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.Webhook = {
    url = "https://discord.com/api/webhooks/...",
    username = nil,
    avatar = nil
}

Use username and avatar only if you want to override the Discord webhook defaults:

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.Webhook = {
    url = "https://discord.com/api/webhooks/...",
    username = "Orbit Crafting Logs",
    avatar = "https://example.com/logo.png"
}

The included sender uses Discord embeds:

resources/[orbit]/orbit-craftingsystem/server/config.lua
function Config.Server.SendWebhook(embed)
    local webhook = Config.Server.Webhook
    if not webhook.url or webhook.url == "" then return end

    PerformHttpRequest(
        webhook.url,
        function() end,
        "POST",
        json.encode({
            username = webhook.username,
            avatar_url = webhook.avatar,
            embeds = { embed }
        }),
        { ["Content-Type"] = "application/json" }
    )
end

Crafting Log Webhook

Config.Server.OnCraft runs after a valid crafting attempt. It receives the player, station or bench ID, item name, crafted amount, item level, and whether the craft succeeded.

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.Colors = {
    success = "#2ecc71",
    failed = "#e74c3c",
    suspicious = "#ff0000"
}

local function HexToDecimal(hex)
    return tonumber(hex:gsub("#", ""), 16)
end

Config.Server.OnCraft = function(source, stationId, itemName, craftedAmount, itemLevel, isWin)
    local playerName = GetPlayerName(source) or "Unknown"
    local playerId = source

    local embed = {
        title = isWin and "Crafting Successful" or "Crafting Failed",
        description = isWin and "The player successfully crafted an item." or "The crafting attempt failed.",
        color = HexToDecimal(isWin and Config.Server.Colors.success or Config.Server.Colors.failed),
        author = {
            name = playerName .. " (" .. playerId .. ")"
        },
        fields = {
            {
                name = "Station or Bench",
                value = tostring(stationId),
                inline = false
            },
            {
                name = "Item",
                value = tostring(itemName),
                inline = true
            },
            {
                name = "Amount",
                value = tostring(craftedAmount),
                inline = true
            },
            {
                name = "Item Level",
                value = tostring(itemLevel),
                inline = true
            }
        },
        footer = {
            text = "Orbit Crafting System"
        },
        timestamp = os.date("!%Y-%m-%dT%H:%M:%SZ")
    }

    Config.Server.SendWebhook(embed)
end

Set Config.Server.OnCraft = nil if you want to disable normal crafting logs entirely:

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.OnCraft = nil

Suspicious Activity Webhook

Config.Server.SuspiciousActivity runs when the server detects an invalid crafting attempt. This is useful for exploit attempts, invalid items, missing requirements, or other rejected craft requests.

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.SuspiciousActivity = function(source, stationId, itemName, craftedAmount, reason, isWin)
    local playerName = GetPlayerName(source) or "Unknown"
    local playerId = source

    local embed = {
        title = "Suspicious Crafting Activity",
        description = "Potential exploit or invalid crafting attempt detected.",
        color = HexToDecimal(Config.Server.Colors.suspicious),
        author = {
            name = playerName .. " (" .. playerId .. ")"
        },
        fields = {
            {
                name = "Station or Bench",
                value = tostring(stationId),
                inline = false
            },
            {
                name = "Item",
                value = tostring(itemName),
                inline = true
            },
            {
                name = "Attempted Amount",
                value = tostring(craftedAmount),
                inline = true
            },
            {
                name = "Reason",
                value = tostring(reason),
                inline = false
            }
        },
        footer = {
            text = "Orbit Crafting System - monitor recommended"
        },
        timestamp = os.date("!%Y-%m-%dT%H:%M:%SZ")
    }

    Config.Server.SendWebhook(embed)
end

Set Config.Server.SuspiciousActivity = nil if you want to disable suspicious activity logs:

resources/[orbit]/orbit-craftingsystem/server/config.lua
Config.Server.SuspiciousActivity = nil

Webhook Testing

Add the webhook URL

Paste the Discord webhook URL into Config.Server.Webhook.url in server/config.lua.

Keep OnCraft enabled

Make sure Config.Server.OnCraft is a function, not nil.

Craft one test item

Join the server, open a station or bench, and craft an item. Discord should receive a success or failure embed.

Check the server console

If no message arrives, confirm the URL is not empty, outbound HTTP is allowed, and the config file is saved on the server.

Common Mistakes

On this page