Installation
Install oxide-pills — dependencies and minimum versions, supported inventories, all 59 items for QBCore, QBX and ESX, ingredients, and verifying the lab end to end.
Step-by-step setup for oxide-pills. Follow the steps in order. Most problems come from skipping one.
Before You Start
oxide-pills works on QBCore, QBX, and ESX. It talks to your framework through o-link (the bridge every Oxide resource uses), so there is nothing framework-specific to set up in the script itself. The framework-side work is making sure you run a supported inventory (below) and adding the items to it (step 4).
You need these resources installed and working first:
| Resource | Minimum Version | What it does |
|---|---|---|
ox_lib | any recent | Provides translations, callbacks, and UI helpers |
oxmysql | any recent | Connects the server to your MySQL database |
o-link | 1.9.1 | Connects pills to your framework (players, inventory, notifications, targeting, and the tool used to place stations) |
| A targeting resource | any | The "look at it and pick an option" menu every station uses. o-link supports ox_target, qb-target, sleepless_interact, tgiann-target and oxide-target. |
| A supported inventory | see below | Holds the pill items and the information stored on them |
Why o-link 1.9.1? Pill stations are placed with a placement tool that o-link added in version 1.9.0, including snapping machines onto a workbench. With an older o-link, using a station item shows "Placing stations needs an updated o-link." and nothing is placed. Version 1.9.1 puts the police alert's map blip on the lab when you don't run a dispatch resource; before it, the blip landed on whichever officer received the alert.
If you already run other Oxide resources, you have ox_lib, oxmysql and o-link already. Check that o-link is 1.9.1 or newer.
Which inventory you need
Pills store information on the item itself: which batch a pill came from, how finely a powder was ground, how worn a die set is and what it's cut for, how many tests a reagent kit has left. This is called item metadata. Your inventory must be one o-link supports, it must keep that metadata, and o-link must be able to update it on a specific slot.
| Framework | Inventory | Works with pills? |
|---|---|---|
| QBX | ox_inventory (QBX's default) | Yes |
| QBCore | qb-inventory (QBCore's default) | Yes, with the unique = true setting from step 4 |
| QBCore | ox_inventory | Yes |
| ESX | ox_inventory | Yes |
| ESX | ESX's own built-in inventory | No. o-link doesn't support it, so pills can't give or take any item. Install ox_inventory. |
| ESX | hex_4_inventory | No. It doesn't keep item metadata, so batches, die wear and reagent kits can't work. |
Other inventories:
ps-inventoryandjpr-inventory:o-linkcan't update metadata on a slot in these, so die wear, engraving a die, reagent kit tests, evidence marks and mystery powder lots don't save. Use one of the inventories in the table instead.qs-inventory,codem-inventory,core_inventory,origen_inventory,tgiann-inventoryandak47_qb_inventory:o-linkpasses metadata to them and can update it on a slot. How each one stacks items that carry different information hasn't been checked. If you use one, make two batches of pills and check they stay in separate stacks.
Installation Steps
1. Place the resource
Put the oxide-pills folder inside your server's resources folder. If you run several Oxide resources, keep them together in a folder named [oxide].
What's in the folder:
| Path | Purpose |
|---|---|
shared/config.lua | Starting values (factory defaults): ownership, distances, placement zones, item usage whitelist, police alerts |
shared/config/equipment.lua | The lab workbench and every machine: which item places it and the level it needs |
shared/config/production.lua | Every production stage, and the formula for each of the five product lines |
shared/config/profile.lua | Tablet quality tuning, and how much each level lets a player see |
shared/config/progression.lua | XP rewards, level thresholds and level-10 perks |
shared/config/scenes.lua | Stage cameras and the animations other players see |
shared/config/world.lua | Pills breaking, reagent tests, dust, taking pills, police evidence, buyers, brand reputation and the delivery run |
shared/config/models.lua | Model replacements for older game builds |
server/, client/, other shared/ files | The script itself |
web/dist/ | The pre-built interface that ships with the resource |
locales/en.json | Translation file (notifications, prompts, and menu text) |
items/ | Inventory images for every pill item, and ox_inventory_items.lua, the item list ready to paste into ox_inventory |
stream/ | Every station, machine part and tablet model |
sql/ | Database schema, for reference. Every table is created automatically on first start |
2. Set the startup order
Your server.cfg is the text file that tells your server which resources to start, and in what order. Every Oxide resource uses the same four-tier order:
# 1. Framework — everything your framework requires (qb-core, es_extended, qbx_core, etc.)
ensure <framework>
# 2. Scripts — your other resources (phone, inventory, garages, etc.)
ensure <your-other-scripts>
# 3. o-link — the bridge. Starts after your framework/scripts, before any Oxide resource.
ensure o-link
# 4. Oxide resources — keep these together, ideally in a folder named [oxide]
ensure oxide-pillsox_lib and oxmysql belong with your framework in tier 1. There's no required order inside a tier. What matters is that o-link starts after your framework and scripts, and before oxide-pills. If all your Oxide resources are in an [oxide] folder, you can write ensure [oxide] once instead of one line per resource.
3. Database: nothing to do
The resource creates its own database tables the first time it starts, so there is no SQL script to run. (sql/install.sql in the resource folder is still included if you'd rather create the tables yourself or want to see the schema. The files in sql/migrations/ are only for servers that manage their schema by hand. A fresh install doesn't need them, and neither does an update: any missing columns are added automatically when the resource starts.)
These are the tables it creates:
| Table | What it stores |
|---|---|
drugs_progression | Each character's pill level, XP, and totals made and sold |
drug_equipment | Every workbench and machine placed in the world |
pills_workbenches | The table model and number of machine slots each workbench was placed with |
pills_machine_mounts | Which workbench slot each benchtop machine sits in |
pills_machine_state | How dirty each press die is, and any tray of tablets left on a press, so nothing is lost on restart |
pills_die_wear | How worn each punch die set is, so wear follows the die and survives a restart |
pills_batches | The hidden quality numbers of every batch ever pressed |
pill_brands | Player brands, with their reputation, sales, seizures and overdoses |
pills_lots | What each lot of mystery powder really is |
pills_seizures | Pills police have logged as evidence, and where |
pills_serials | Press serial numbers (only used if you turn on the serial trail) |
oxide_settings | The live configuration you edit in game with /pills settings (see Configuration) |
It's safe to run oxide-pills alongside oxide-weed, oxide-meth, oxide-cocaine, or any other Oxide resource. drugs_progression, drug_equipment, and oxide_settings are shared on purpose (each resource keeps its own rows), and each resource only creates them if they don't exist yet.
4. Add the items to your inventory
oxide-pills needs every item below to exist in your inventory with the exact spelling shown. One typo in an item name breaks that step of the lab. None of the names clash with the items stock QBCore or QBX already ship (such as oxy, xtcbaggy or painkillers), so you don't need to rename anything.
The tables list every item so you can check nothing is missing. You don't need to type them in by hand: the ready-made files for each inventory are in Register the items below.
"Must be usable" means a player can use the item from their inventory. The script decides what happens when it's used; your inventory just has to allow it.
Workbench and machines
Using one of these starts placement: the player positions the station and confirms.
| Item | Label | Weight (g) | Must be usable |
|---|---|---|---|
pill_workbench | Lab Workbench | 8000 | Yes |
pill_crank_press | Crank Press | 6000 | Yes |
pill_grinder | Blade Grinder | 2000 | Yes |
pill_mixer | Stand Mixer | 4000 | Yes |
pill_lightbox | Light Box | 2000 | Yes |
pill_engraver | Die Engraver | 6000 | Yes |
pill_electric_press | Electric Press | 10000 | Yes |
pill_label_printer | Label Printer | 3000 | Yes |
pill_hardness_tester | Hardness Tester | 3000 | Yes |
pill_capsule_board | Capsule Filling Board | 2000 | Yes |
pill_ball_mill | Ball Mill | 6000 | Yes |
pill_blister_sealer | Blister Sealer | 8000 | Yes |
pill_reject_bin | Reject Bin | 2000 | Yes |
pill_dust_collector | Dust Collector | 8000 | Yes |
pill_drum_tumbler | Drum Tumbler | 12000 | Yes |
pill_pan_coater | Pan Coater | 12000 | Yes |
pill_rotary_press | Rotary Press | 25000 | Yes |
Hand tools and tooling
| Item | Label | Weight (g) | Must be usable |
|---|---|---|---|
pill_mortar | Mortar & Pestle | 1500 | Yes |
pill_manual | Pressing Manual | 300 | Yes |
pill_sieve | Granulating Screen | 600 | Yes |
dust_mask | Dust Mask | 100 | Yes |
reagent_kit | Reagent Test Kit | 300 | Yes |
punch_die_set | Punch & Die Set | 1200 | No |
cleaning_brush | Cleaning Brush | 200 | No |
cleaning_solvent | Cleaning Solvent | 500 | No |
Make punch_die_set and reagent_kit unstackable (one per inventory slot). Each die set tracks its own wear and shape, and each kit tracks how many tests it has left. In ox_inventory that's stack = false; in qb-inventory it's unique = true. The ready-made item files already do this. On qb-inventory more items need it, see Keeping batches apart on qb-inventory.
Ingredients
| Item | Label | Weight (g) |
|---|---|---|
party_powder | Party Powder | 100 |
pharma_bottle | Pharma Bottle | 200 |
mystery_powder | Mystery Powder | 100 |
pill_filler | Tablet Filler | 100 |
pill_binder | Tablet Binder | 100 |
pill_lubricant | Die Lubricant | 100 |
pill_dye | Tablet Dye | 100 |
granulating_fluid | Granulating Fluid | 300 |
coating_solution | Coating Solution | 400 |
empty_capsule | Empty Capsule | 5 |
None of these need to be usable.
In-between products
| Item | Label | Weight (g) |
|---|---|---|
pill_milled | Milled Powder | 100 |
pill_blend | Pressing Blend | 100 |
Neither needs to be usable.
Finished pills
All five are usable, because players can take them.
| Item | Label | Weight (g) | Must be usable |
|---|---|---|---|
party_pill | Party Pill | 20 | Yes |
bar_pill | NOVA Bar | 20 | Yes |
blue_pill | VELA 30 | 20 | Yes |
perc_pill | AXIA 10 | 20 | Yes |
molly_capsule | AURA Capsule | 20 | Yes |
Packed pills
Packing a batch turns its pills into the packed item for that line, so bags, bottles and blister cards each show their own name and icon. The count is still tablets: a bottle of 30 NOVA Bars is 30 bar_pill_bottle. All of them are usable, so players can take a pill from a pack.
| Item | Label | Weight (g) | Must be usable |
|---|---|---|---|
party_pill_bag | Bagged Party Pill | 20 | Yes |
party_pill_blister | Blister-Packed Party Pill | 20 | Yes |
bar_pill_bag | Bagged NOVA Bar | 20 | Yes |
bar_pill_bottle | Bottled NOVA Bar | 20 | Yes |
bar_pill_blister | Blister-Packed NOVA Bar | 20 | Yes |
blue_pill_bag | Bagged VELA 30 | 20 | Yes |
blue_pill_bottle | Bottled VELA 30 | 20 | Yes |
blue_pill_blister | Blister-Packed VELA 30 | 20 | Yes |
perc_pill_bag | Bagged AXIA 10 | 20 | Yes |
perc_pill_bottle | Bottled AXIA 10 | 20 | Yes |
perc_pill_blister | Blister-Packed AXIA 10 | 20 | Yes |
molly_capsule_bag | Bagged AURA Capsule | 20 | Yes |
molly_capsule_blister | Blister-Packed AURA Capsule | 20 | Yes |
Packaging and leftovers
| Item | Label | Weight (g) |
|---|---|---|
pill_baggie | Pill Baggie | 10 |
pill_bottle | Pill Bottle | 30 |
blister_sheet | Blister Sheet | 10 |
pill_rejects | Reject Tablets | 50 |
None of these need to be usable. The baggie, bottle and blister sheet are the empty packaging that gets used up when pills are packed. Packed pills are the packed pill items above.
Keeping batches apart on qb-inventory
When qb-inventory adds an item that isn't unique, it puts it on the first stack with the same name and throws away the new item's information. Without a fix, a second batch of pills would land on the first batch's stack and turn into that batch, and a second milled powder or blend would lose its grind and recipe. Players can also drag two stacks of the same item together.
The QBCore block below sets unique = true on every item that carries information: the five pills, all thirteen packed pills, pill_milled, pill_blend, pill_rejects, mystery_powder, punch_die_set and reagent_kit. A unique item still holds a count (one slot can hold 48 pills of a batch), but each batch keeps its own inventory slot, and qb-inventory won't let players merge them. If you already added the items without it, change those items to unique = true and restart the server.
ox_inventory keeps items with different information in separate stacks on its own, so two batches never merge there.
Register the items
Pick your framework. The item files ship in the resource folder, so you only paste.
Using qb-inventory (QBCore's default): add the items to qb-core/shared/items.lua. Open that file, paste the block below just above its final closing }, and save. useable = true is set on every item marked "must be usable" above, and unique = true on every item that carries information (see Keeping batches apart on qb-inventory).
-- Workbench and machines
pill_workbench = { name = 'pill_workbench', label = 'Lab Workbench', weight = 8000, type = 'item', image = 'pill_workbench.png', unique = false, useable = true, shouldClose = true, description = 'A sturdy bench that holds pill lab machines' },
pill_crank_press = { name = 'pill_crank_press', label = 'Crank Press', weight = 6000, type = 'item', image = 'pill_crank_press.png', unique = false, useable = true, shouldClose = true, description = 'A hand-cranked single-punch tablet press' },
pill_grinder = { name = 'pill_grinder', label = 'Blade Grinder', weight = 2000, type = 'item', image = 'pill_grinder.png', unique = false, useable = true, shouldClose = true, description = 'Grinds powder finer than a mortar can' },
pill_mixer = { name = 'pill_mixer', label = 'Stand Mixer', weight = 4000, type = 'item', image = 'pill_mixer.png', unique = false, useable = true, shouldClose = true, description = 'Blends a powder batch evenly' },
pill_lightbox = { name = 'pill_lightbox', label = 'Light Box', weight = 2000, type = 'item', image = 'pill_lightbox.png', unique = false, useable = true, shouldClose = true, description = 'A backlit box and loupe for inspecting tablets' },
pill_engraver = { name = 'pill_engraver', label = 'Die Engraver', weight = 6000, type = 'item', image = 'pill_engraver.png', unique = false, useable = true, shouldClose = true, description = 'Engraves custom stamps onto punch tooling' },
pill_electric_press = { name = 'pill_electric_press', label = 'Electric Press', weight = 10000, type = 'item', image = 'pill_electric_press.png', unique = false, useable = true, shouldClose = true, description = 'A motorised single-punch tablet press' },
pill_label_printer = { name = 'pill_label_printer', label = 'Label Printer', weight = 3000, type = 'item', image = 'pill_label_printer.png', unique = false, useable = true, shouldClose = true, description = 'Prints pharmacy-style bottle labels' },
pill_hardness_tester = { name = 'pill_hardness_tester', label = 'Hardness Tester', weight = 3000, type = 'item', image = 'pill_hardness_tester.png', unique = false, useable = true, shouldClose = true, description = 'Measures how much force a tablet takes to break' },
pill_capsule_board = { name = 'pill_capsule_board', label = 'Capsule Filling Board', weight = 2000, type = 'item', image = 'pill_capsule_board.png', unique = false, useable = true, shouldClose = true, description = 'Fills a tray of capsules in one pass' },
pill_ball_mill = { name = 'pill_ball_mill', label = 'Ball Mill', weight = 6000, type = 'item', image = 'pill_ball_mill.png', unique = false, useable = true, shouldClose = true, description = 'A roller jar that mills powder evenly fine' },
pill_blister_sealer = { name = 'pill_blister_sealer', label = 'Blister Sealer', weight = 8000, type = 'item', image = 'pill_blister_sealer.png', unique = false, useable = true, shouldClose = true, description = 'Heat-seals tablets into blister packs' },
pill_reject_bin = { name = 'pill_reject_bin', label = 'Reject Bin', weight = 2000, type = 'item', image = 'pill_reject_bin.png', unique = false, useable = true, shouldClose = true, description = 'Catches tablets that fail inspection' },
pill_dust_collector = { name = 'pill_dust_collector', label = 'Dust Collector', weight = 8000, type = 'item', image = 'pill_dust_collector.png', unique = false, useable = true, shouldClose = true, description = 'Pulls airborne powder away from the press' },
pill_drum_tumbler = { name = 'pill_drum_tumbler', label = 'Drum Tumbler', weight = 12000, type = 'item', image = 'pill_drum_tumbler.png', unique = false, useable = true, shouldClose = true, description = 'A rotating drum for large, even blends' },
pill_pan_coater = { name = 'pill_pan_coater', label = 'Pan Coater', weight = 12000, type = 'item', image = 'pill_pan_coater.png', unique = false, useable = true, shouldClose = true, description = 'Tumbles tablets through a coating spray' },
pill_rotary_press = { name = 'pill_rotary_press', label = 'Rotary Press', weight = 25000, type = 'item', image = 'pill_rotary_press.png', unique = false, useable = true, shouldClose = true, description = 'A multi-station rotary tablet press' },
-- Hand tools and tooling
pill_mortar = { name = 'pill_mortar', label = 'Mortar & Pestle', weight = 1500, type = 'item', image = 'pill_mortar.png', unique = false, useable = true, shouldClose = true, description = 'A heavy stone bowl for grinding powder by hand' },
pill_manual = { name = 'pill_manual', label = 'Pressing Manual', weight = 300, type = 'item', image = 'pill_manual.png', unique = false, useable = true, shouldClose = true, description = 'A dog-eared guide to formulating and pressing tablets' },
pill_sieve = { name = 'pill_sieve', label = 'Granulating Screen', weight = 600, type = 'item', image = 'pill_sieve.png', unique = false, useable = true, shouldClose = true, description = 'A mesh screen for working a wet blend into granules. Use it at a workbench' },
dust_mask = { name = 'dust_mask', label = 'Dust Mask', weight = 100, type = 'item', image = 'dust_mask.png', unique = false, useable = true, shouldClose = true, description = 'Keeps airborne powder out of your lungs' },
reagent_kit = { name = 'reagent_kit', label = 'Reagent Test Kit', weight = 300, type = 'item', image = 'reagent_kit.png', unique = true, useable = true, shouldClose = true, description = 'A few drops tell you what a powder or pill really is' },
punch_die_set = { name = 'punch_die_set', label = 'Punch & Die Set', weight = 1200, type = 'item', image = 'punch_die_set.png', unique = true, useable = false, shouldClose = true, description = 'The tooling a press strikes tablets with. Wears down with use' },
cleaning_brush = { name = 'cleaning_brush', label = 'Cleaning Brush', weight = 200, type = 'item', image = 'cleaning_brush.png', unique = false, useable = false, shouldClose = true, description = 'A stiff brush for clearing residue off a punch face' },
cleaning_solvent = { name = 'cleaning_solvent', label = 'Cleaning Solvent', weight = 500, type = 'item', image = 'cleaning_solvent.png', unique = false, useable = false, shouldClose = true, description = 'Cuts through baked-on residue a brush cannot shift' },
-- Ingredients
party_powder = { name = 'party_powder', label = 'Party Powder', weight = 100, type = 'item', image = 'party_powder.png', unique = false, useable = false, shouldClose = true, description = 'An imported active powder. Needs milling before it is any use' },
pharma_bottle = { name = 'pharma_bottle', label = 'Pharma Bottle', weight = 200, type = 'item', image = 'pharma_bottle.png', unique = false, useable = false, shouldClose = true, description = 'A stock bottle of diverted prescription tablets' },
mystery_powder = { name = 'mystery_powder', label = 'Mystery Powder', weight = 100, type = 'item', image = 'mystery_powder.png', unique = true, useable = false, shouldClose = true, description = 'An unlabelled active powder. Only a reagent test says what it is' },
pill_filler = { name = 'pill_filler', label = 'Tablet Filler', weight = 100, type = 'item', image = 'pill_filler.png', unique = false, useable = false, shouldClose = true, description = 'Inert bulking powder that brings a dose into range' },
pill_binder = { name = 'pill_binder', label = 'Tablet Binder', weight = 100, type = 'item', image = 'pill_binder.png', unique = false, useable = false, shouldClose = true, description = 'Holds a pressed tablet together' },
pill_lubricant = { name = 'pill_lubricant', label = 'Die Lubricant', weight = 100, type = 'item', image = 'pill_lubricant.png', unique = false, useable = false, shouldClose = true, description = 'Stops powder sticking to the punch face' },
pill_dye = { name = 'pill_dye', label = 'Tablet Dye', weight = 100, type = 'item', image = 'pill_dye.png', unique = false, useable = false, shouldClose = true, description = 'Colours a batch of party pills' },
granulating_fluid = { name = 'granulating_fluid', label = 'Granulating Fluid', weight = 300, type = 'item', image = 'granulating_fluid.png', unique = false, useable = false, shouldClose = true, description = 'Wets a blend so it can be worked into granules' },
coating_solution = { name = 'coating_solution', label = 'Coating Solution', weight = 400, type = 'item', image = 'coating_solution.png', unique = false, useable = false, shouldClose = true, description = 'Sprayed over tablets in a pan coater to seal and finish them' },
empty_capsule = { name = 'empty_capsule', label = 'Empty Capsule', weight = 5, type = 'item', image = 'empty_capsule.png', unique = false, useable = false, shouldClose = true, description = 'A two-piece gelatin shell, waiting to be filled' },
-- In-between products
pill_milled = { name = 'pill_milled', label = 'Milled Powder', weight = 100, type = 'item', image = 'pill_milled.png', unique = true, useable = false, shouldClose = true, description = 'Ground active, ready to formulate' },
pill_blend = { name = 'pill_blend', label = 'Pressing Blend', weight = 100, type = 'item', image = 'pill_blend.png', unique = true, useable = false, shouldClose = true, description = 'A formulated, blended powder waiting for the press' },
-- Finished pills
party_pill = { name = 'party_pill', label = 'Party Pill', weight = 20, type = 'item', image = 'party_pill.png', unique = true, useable = true, shouldClose = true, description = 'A pressed tablet. What is in it is anyone\'s guess until it is checked' },
bar_pill = { name = 'bar_pill', label = 'NOVA Bar', weight = 20, type = 'item', image = 'bar_pill.png', unique = true, useable = true, shouldClose = true, description = 'A scored white bar' },
blue_pill = { name = 'blue_pill', label = 'VELA 30', weight = 20, type = 'item', image = 'blue_pill.png', unique = true, useable = true, shouldClose = true, description = 'A small round blue tablet made to pass for the pharmacy kind' },
perc_pill = { name = 'perc_pill', label = 'AXIA 10', weight = 20, type = 'item', image = 'perc_pill.png', unique = true, useable = true, shouldClose = true, description = 'A round white tablet made to pass for the pharmacy kind' },
molly_capsule = { name = 'molly_capsule', label = 'AURA Capsule', weight = 20, type = 'item', image = 'molly_capsule.png', unique = true, useable = true, shouldClose = true, description = 'A capsule of powder. Only a test says how much of it is real' },
-- Packed pills (the count is still tablets)
party_pill_bag = { name = 'party_pill_bag', label = 'Bagged Party Pill', weight = 20, type = 'item', image = 'party_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'Party pills in a zip baggie' },
party_pill_blister = { name = 'party_pill_blister', label = 'Blister-Packed Party Pill', weight = 20, type = 'item', image = 'party_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'Party pills sealed in a blister card' },
bar_pill_bag = { name = 'bar_pill_bag', label = 'Bagged NOVA Bar', weight = 20, type = 'item', image = 'bar_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'NOVA Bars in a zip baggie' },
bar_pill_bottle = { name = 'bar_pill_bottle', label = 'Bottled NOVA Bar', weight = 20, type = 'item', image = 'bar_pill_bottle.png', unique = true, useable = true, shouldClose = true, description = 'NOVA Bars in a labelled pharmacy bottle' },
bar_pill_blister = { name = 'bar_pill_blister', label = 'Blister-Packed NOVA Bar', weight = 20, type = 'item', image = 'bar_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'NOVA Bars sealed in a blister card' },
blue_pill_bag = { name = 'blue_pill_bag', label = 'Bagged VELA 30', weight = 20, type = 'item', image = 'blue_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'VELA 30s in a zip baggie' },
blue_pill_bottle = { name = 'blue_pill_bottle', label = 'Bottled VELA 30', weight = 20, type = 'item', image = 'blue_pill_bottle.png', unique = true, useable = true, shouldClose = true, description = 'VELA 30s in a labelled pharmacy bottle' },
blue_pill_blister = { name = 'blue_pill_blister', label = 'Blister-Packed VELA 30', weight = 20, type = 'item', image = 'blue_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'VELA 30s sealed in a blister card' },
perc_pill_bag = { name = 'perc_pill_bag', label = 'Bagged AXIA 10', weight = 20, type = 'item', image = 'perc_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'AXIA 10s in a zip baggie' },
perc_pill_bottle = { name = 'perc_pill_bottle', label = 'Bottled AXIA 10', weight = 20, type = 'item', image = 'perc_pill_bottle.png', unique = true, useable = true, shouldClose = true, description = 'AXIA 10s in a labelled pharmacy bottle' },
perc_pill_blister = { name = 'perc_pill_blister', label = 'Blister-Packed AXIA 10', weight = 20, type = 'item', image = 'perc_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'AXIA 10s sealed in a blister card' },
molly_capsule_bag = { name = 'molly_capsule_bag', label = 'Bagged AURA Capsule', weight = 20, type = 'item', image = 'molly_capsule_bag.png', unique = true, useable = true, shouldClose = true, description = 'AURA Capsules in a zip baggie' },
molly_capsule_blister = { name = 'molly_capsule_blister', label = 'Blister-Packed AURA Capsule', weight = 20, type = 'item', image = 'molly_capsule_blister.png', unique = true, useable = true, shouldClose = true, description = 'AURA Capsules sealed in a blister card' },
-- Packaging and leftovers
pill_baggie = { name = 'pill_baggie', label = 'Pill Baggie', weight = 10, type = 'item', image = 'pill_baggie.png', unique = false, useable = false, shouldClose = true, description = 'A small zip bag for a dozen tablets' },
pill_bottle = { name = 'pill_bottle', label = 'Pill Bottle', weight = 30, type = 'item', image = 'pill_bottle.png', unique = false, useable = false, shouldClose = true, description = 'An amber pharmacy bottle with a child-proof cap' },
blister_sheet = { name = 'blister_sheet', label = 'Blister Sheet', weight = 10, type = 'item', image = 'blister_sheet.png', unique = false, useable = false, shouldClose = true, description = 'Formed plastic and foil backing for ten tablets' },
pill_rejects = { name = 'pill_rejects', label = 'Reject Tablets', weight = 50, type = 'item', image = 'pill_rejects.png', unique = true, useable = false, shouldClose = true, description = 'Broken and malformed tablets. Worth regrinding' },Running ox_inventory on QBCore? Skip the block above and follow the QBX tab instead: the file and the steps are the same.
QBX runs ox_inventory by default. Every item above is ready to paste in items/ox_inventory_items.lua in the resource folder.
- Open
items/ox_inventory_items.luafrom theoxide-pillsfolder in a text editor. - Copy every line between
return {and the final}. - Open
ox_inventory/data/items.luaand paste those lines just above that file's final closing}. - Save the file.
ox_inventory has no separate "usable" switch to set: pills registers the items it uses itself. ox_inventory also keeps items with different information in separate stacks on its own, so two batches never merge. The file already sets stack = false on punch_die_set and reagent_kit. If your items.lua already has an item with the same name, keep only one copy.
Check the laundering setting on QBX. The default takes black_money items, which matches a stock Qbox install. If your server uses markedbills instead, change it after the first start (see step 7).
ESX needs ox_inventory. The steps are the same as on QBX:
- Open
items/ox_inventory_items.luafrom theoxide-pillsfolder in a text editor. - Copy every line between
return {and the final}. - Open
ox_inventory/data/items.luaand paste those lines just above that file's final closing}. - Save the file.
ox_inventory has no separate "usable" switch to set: pills registers the items it uses itself, and keeps different batches in separate stacks on its own.
ESX's own built-in inventory and hex_4_inventory can't run oxide-pills. o-link doesn't support ESX's built-in inventory, so pills can't give or take any item with it, and hex_4_inventory drops item metadata. Install ox_inventory. See Which inventory you need.
Notes:
- The labels, weights and descriptions are suggestions. Change them to taste. The names must match exactly.
- Don't skip the in-between items. If
pill_milleddoesn't exist, grinding fails, and so does every step after it.
5. Copy the item images
Copy all the .png files from items/ in the resource folder into your inventory's image folder:
- qb-inventory:
qb-inventory/html/images/ - ox_inventory:
ox_inventory/web/images/
The file names already match the item names, so no renaming is needed.
When steps 4 and 5 are done, restart your server so the inventory loads the new items and images.
6. Give players a way to get ingredients
Nothing in oxide-pills sells the raw materials. Decide how players get them: a shop, a black-market dealer, a job reward, or anything else your server uses. These are the items players can't make themselves:
- Stations and tools: every item in the Workbench and machines and Hand tools and tooling tables
- Raw actives:
party_powder,pharma_bottle,mystery_powder - Additives:
pill_filler,pill_binder,pill_lubricant,pill_dye,granulating_fluid,coating_solution,empty_capsule - Packaging:
pill_baggie,pill_bottle,blister_sheet
Players make everything else (pill_milled, pill_blend, the five pills and their packed items, and pill_rejects).
7. Configure it (in-game)
The defaults work out of the box, so you can open to players right away and adjust later.
When you do want to change something, do it in game, not by editing files. As an admin, run /pills settings to open the settings menu. Almost every setting is there (stage timings, level requirements, quality tuning, packaging, pills breaking, overdoses, the delivery run, police alerts, and the in-world placement zones). Changes apply live and save automatically. See Configuration for how this works, what every setting does, and the few settings that live only in the config files.
Config files are factory defaults
The shared/config files are the resource's factory defaults: the values it starts from on first launch. After the first start, the database holds your settings, and editing those files won't change most of them. Change settings with /pills settings instead.
Worth a look first, all in the menu:
- Core → General: ownership, distances
- Core → Placement Zones: where stations may be placed
- Lab → Machines: which level each machine needs
- Players → World: pills breaking, dust, overdoses, reagent tests, police evidence
- Players → Selling: brand reputation and the pharma delivery run
- Advanced → Dispatch: police alerts (off by default)
Laundering uses your framework's dirty money. Each delivery drop can wash dirty money. The default setting (The framework default) takes markedbills items (with their worth stored on each bill) on QBCore, black_money items on QBX, and the black_money account on ESX. If your server keeps dirty money some other way, change Players → Selling → Pharma delivery run → Laundering. See Configuration.
Optional Integrations
Everything below is optional. Pills runs fine without any of it.
| Resource | What it adds | What happens without it |
|---|---|---|
oxide-drugselling (2.0.0 or newer) | Players sell pills to NPCs on the street. Each buyer judges the batch in their own way, and branded pills build their brand's reputation. | Pills can still be taken, and blister-packed pharma pills can still go on the delivery run. |
oxide-tablet | The Pill ID lookup appears as an app on the tablet. | /pills pillid opens Pill ID on its own. |
oxide-medical | An overdose is recorded as a medical condition that EMS can see and treat. | Pills plays its own overdose effect: a stumble, a blurred screen and lost health. |
A dispatch resource o-link supports (oxide-dispatch, ps-dispatch, cd_dispatch, qs_dispatch, lb-tablet and others) | Police alerts go into your dispatch system, at the station's location. | Players with the alert job get a short notification and a 30-second map blip on the lab. |
oxide-gangs | Gang territories can speed up the machines that run themselves, increase output, and raise delivery pay. Delivery drops are reported to oxide-gangs as drug sales. | Nothing changes. |
oxide-drugselling
Street sales need oxide-drugselling 2.0.0 or newer. That's the first version that knows about pills; older versions can't sell them at all.
To check yours, open oxide-drugselling/shared/config/drugs.lua in a text editor and look for a section that starts with Config.Drugs.pills. If it's there, your version is new enough: pills are sellable and priced by oxide-pills. If it isn't, update oxide-drugselling to 2.0.0 or newer.
That section sets each pill's base street price. oxide-pills then adjusts the price for every sale, based on who the buyer is, how good the batch is, how it's packed, and how well known its brand is. See Selling: Buyers and Brand Reputation.
Check That It Works
Start the server and confirm ox_lib, oxmysql, o-link, and oxide-pills all start without errors in the console.
Give yourself admin permission (see Admin), make room in your inventory, then run /pills kit in game.
- The kit is one of every station plus tools, ingredients and packaging. With the suggested weights it comes to about 139 kg, which is more than a default player inventory holds on QBCore, QBX or ESX. Anything that doesn't fit, or that isn't registered in your inventory yet, is skipped, and you see "N of M kit items could not be given (inventory full or item not registered)" instead of "Pill lab kit received".
- This check only needs these items:
pill_workbench,pill_mixer,pill_crank_press,pill_mortar,party_powder,pill_filler,pill_binder,pill_lubricant,punch_die_setand onepill_baggie(about 21 kg together). - If any of them is missing, put the items you don't need in a stash or drop them, then give yourself the missing ones with your inventory's give-item command. On both qb-inventory and ox_inventory that's
/giveitem [your server ID] [item name] [amount], for example/giveitem 1 pill_mixer 1.
Use the Lab Workbench item and place it. The workbench holds 2 machines by default, and the kit only gives you one, so for this test place just the Stand Mixer and the Crank Press on it.
Stand at the workbench and use the Mortar & Pestle item. If it asks what you're grinding (it does when you carry more than one kind of powder), pick Party Powder. Move the mouse in circles until the grind meter reaches the good zone, then press ENTER. You should receive Milled Powder.
At the mixer, choose Mix a Batch. Pick up each container and hold the left mouse button over the bowl to pour: the powder, filler, binder and a little lubricant. Press SPACE to put the bowl on, then SPACE again to start. You should receive a Pressing Blend.
At the press, choose Press Tablets. Set the fill depth and force with the arrow keys, press SPACE, then circle the mouse to turn the crank. Press ENTER to stop early if you like.
Sort the tray: hover a tablet to read it, click to throw it out, then press ENTER. You should receive Party Pills.
Aim at the workbench itself, choose Bag Tablets, and bag a dozen.
Run /pills stats and check your level and XP card appears.
If any step fails, see Troubleshooting.
Good To Know
- Each workbench holds 2 machines by default (Lab → Workbench). There are 11 benchtop machines, so a full lab needs several workbenches. The workbench itself is also where the mortar and sieve are used and where pills are bagged.
- Quality stays with the batch. A batch's hidden numbers are stored in the database against a batch ID that rides on every pill, so the quality travels with the pill when it's traded or stored.
- Pills made any other way have no batch. A pill spawned with an admin give-item command has no batch ID. It can't be inspected, packed or tested, and taking it uses it up with no effect. Use the lab to make test pills.
- This resource streams its own models. Everything in
stream/loads automatically. The workbench is a base-game table (bkr_prop_weed_table_01b). - The interface ships ready. There is nothing to build, compile, or install for it.
- Translations: the on-screen notifications, prompts, and menu text live in
locales/en.jsonand can be translated there.
Next Steps
Oxide Pills
Hands-on pill production for FiveM — mill, blend, press, sort and pack tablets by hand, with a hidden quality profile on every batch, player brands, counterfeits, a rotary press crew and a pharma delivery run.
Configuration
Every oxide-pills setting — how database-backed settings and the /pills settings menu work, which values live only in the config files, and each setting's menu location and default.