Guide · Updated 14 Sep 2026

Using Poggy Core in your own RedM scripts: one framework API for VORP, RSG and QBCore

Poggy Core (Free) is the free framework layer every Poggy script runs on, and it does not care whether the script calling it is ours. If you write RedM resources, for one server or for sale, you can build on it today: one set of verbs for characters, money, jobs, items, storage, notifications, menus and text inputs, and poggy_core translates them into VORP Core, RSG Core or QBCore RedM at runtime. This guide is the whole path from ensure poggy_core to a working script, with the real verb names, taken from the poggy_core README and verb table.

What it is, and why

A RedM script that talks to the framework directly is written for that framework. VORP's inventory is callback-based, RSG's is synchronous, their notification signatures differ, their job-change events differ, and RSG's AddItem drops an item on the ground when the satchel is full while QBR does no capacity check at all. poggy_core hides all of that behind one shape: every call returns ok, value, err, capacity is checked before every add, Remove never takes a player negative, and a job change is one event on every framework.

Today it has three adapters, VORP Core, RSG Core and QBCore RedM, all proven in game; the QBR adapter is written against qbr-core and qbr-inventory and passes the full self-test. A script written against poggy_core runs on whichever adapters exist, and picks up new ones when poggy_core updates, without a change on your side. On a framework poggy_core has no adapter for, it says so and refuses framework calls rather than silently no-oping.

Install

Download Poggy Core (Free) (free, from Tebex at no charge), put the poggy_core folder in your resources, and start it after your framework and before anything that uses it:

ensure oxmysql
ensure vorp_core          # or: ensure rsg-core       / ensure qbr-core
ensure vorp_inventory     # or: ensure rsg-inventory  / ensure qbr-inventory
ensure poggy_core

# lets poggy_core restart the Poggy scripts it has just updated
add_ace resource.poggy_core command.refresh allow
add_ace resource.poggy_core command.ensure allow

There is no SQL to import and every value in poggy_core/config.lua has a working default. The two add_ace lines are for poggy_core's own updater; it prints them if they are missing. lua54 'yes' is set in poggy_core, and any resource that uses it should set it too.

Your first script

Two lines in the manifest do the work: load the bridge from poggy_core as your first shared script, and declare the dependency. The bridge is one file that lives in poggy_core, so updating poggy_core updates every script's copy of it.

-- fxmanifest.lua
fx_version 'cerulean'
game 'rdr3'
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
lua54 'yes'

shared_script '@poggy_core/template/poggy.lua'
server_script 'server.lua'
client_script 'client.lua'

dependency 'poggy_core'
poggy_core_min '0.14.0'   -- the oldest core this script will accept

Then one function, Poggy(verb, payload), for everything. A shop counter that takes money, checks the satchel and hands over an item, server side:

-- server.lua
RegisterNetEvent('myshop:buy', function(item, price)
    local src = source

    local ok, canCarry = Poggy('inv.canCarry', { src = src, item = item, qty = 1 })
    if not ok or not canCarry then
        return Poggy('notify', { src = src, text = 'Your satchel is full.', kind = 'error' })
    end

    local paid = Poggy('money.remove', { src = src, amount = price, currency = 'cash', reason = 'myshop:buy' })
    if not paid then
        return Poggy('notify', { src = src, text = 'You cannot afford that.', kind = 'error' })
    end

    Poggy('inv.add', { src = src, item = item, qty = 1 })
    Poggy('notify', { src = src, text = 'Purchased.', kind = 'success' })
end)

Three things to notice. money.remove returns false, nil, 'no_funds' rather than taking the player negative, on every framework. inv.add capacity-checks first and returns false, nil, 'no_space' if the item would not fit, so the explicit inv.canCarry above is a courtesy message, not the safety net. And notify is drawn by poggy_core itself with the same RDR2 natives VORP uses, so nothing looks different and no framework notify resource is needed. kind is info, success, error or warning; notify.styled gives you the RDR2 banner styles (tip, objective, top, left, center and the rest) when position matters.

The same API is on an object if you prefer method calls: local Core = exports.poggy_core:Get() gives you Core.Money.Remove(src, 'cash', price, reason), Core.Inventory.Add(src, item, 1), Core.Notify(src, text, kind) and so on, with the same ok, err contract. Both go through the same dispatcher.

Branch on ok, and nothing else

Every verb returns ok, value, err. A core that is not ready yet, or one with no framework under it, answers false, nil, 'no_core', which looks exactly like any other failed call, so there is only one thing to handle:

local ok, char = Poggy('char.get', { src = src })
if not ok then return end
print(char.fullName, char.job, char.jobGrade, char.onDuty)   -- onDuty may be nil: the framework cannot say

The error codes are a fixed set: not_ready, no_char, unsupported, not_implemented, bad_argument, no_space, no_funds, not_found, timeout, framework_error, needs_thread, plus no_core and core_too_old from the bridge. Work that must run at startup, such as registering storage, waits with PoggyReady() first.

Menus and inputs

Since 0.14.0 poggy_core draws its own list menu and text box, so your script needs neither vorp_menu nor vorp_inputs and gets the same screens on every framework. The two verbs that wait for the player need a thread (a CreateThread, an event handler, or a callback). On the client:

-- client.lua
CreateThread(function()
    local ok, pick = Poggy('menu.open', {
        title    = 'Stable',
        subtitle = 'Pick a horse',
        items    = {
            { label = 'Arabian', value = 'horse_arabian', right = '$120', desc = 'Fast and nervous.' },
            { label = 'Shire',   value = 'horse_shire',   right = '$80',  desc = 'Strong and steady.' },
            { label = 'Mustang', value = 'horse_mustang', right = 'sold', disabled = true },
        },
    })
    if not ok then return end             -- 'closed': the player backed out
    print(pick.value, pick.index, pick.item.label)

    local okName, name = Poggy('input.text', { title = 'Name your horse', placeholder = 'Buttercup', maxLength = 24 })
    if okName then print('named ' .. name) end

    local okQty, qty = Poggy('input.text', { title = 'How many?', default = 1, numeric = true })
    if okQty then print(qty + 1) end      -- a number, not a string
end)

Poggy('menu.close', {})                   -- take down whatever is open

On the server, name the player with src and the same verbs ride poggy_core's callback transport to that client and back; the answer is the client's answer. Because the items cross the network, a value must be plain data, not a function. One menu or input at a time: opening another replaces it, and the earlier caller gets false, 'closed'. Position for every Poggy menu on the server is one setting, PoggyCoreConfig.Ui.Position.

RegisterNetEvent('mystable:browse', function()
    local src = source                    -- an event handler is a thread already
    local ok, pick = Poggy('menu.open', { src = src, title = 'Stable', items = horsesFor(src) })
    if not ok then return end
    if not Poggy('money.remove', { src = src, amount = pick.item.price }) then
        return Poggy('notify', { src = src, text = 'You cannot afford that.', kind = 'error' })
    end
    Poggy('notify', { src = src, text = 'Bought ' .. pick.item.label, kind = 'success' })
end)

Storage containers

A container is a stash in the framework's own inventory (a vorp_inventory custom inventory, an rsg-inventory stash), registered by id. Register on every boot: VORP keeps definitions in memory only, RSG persists the contents but not the label and size, and poggy_core replays your registrations if the inventory resource restarts underneath you.

-- server.lua
CreateThread(function()
    if not PoggyReady() then return end
    Poggy('storage.register', { id = 'town_safe', opts = {
        label = 'Town Safe', slots = 60, maxWeight = 200,
        jobAccess = { sheriff = 1 },      -- sheriff grade 1 and up
    } })
end)

RegisterNetEvent('mysafe:open', function()
    local src = source
    local ok, _, err = Poggy('storage.open', { src = src, id = 'town_safe' })
    if not ok then Poggy('notify', { src = src, text = 'Cannot open the safe (' .. tostring(err) .. ').', kind = 'error' }) end
end)

Ids are namespaced per script as pg_<your resource>_<id>, so two scripts cannot collide and a renamed folder does not hide what was stored; pass raw = true if you already have containers in the wild under bare ids. maxWeight is ignored on VORP (slots limit there) and jobAccess has no counterpart on RSG; pass both and the script works on every framework. storage.addItem, storage.removeItem, storage.items, storage.unregister (forget it, keep the items) and storage.delete (destroy it and the items) complete the set. On VORP, storage.addItem needs a charId.

Jobs and duty

-- server side; the same verbs work on the client without src
local ok, job = Poggy('job.get', { src = src })       -- { name, grade, label, gradeLabel, onDuty }
local isLaw   = select(2, Poggy('job.isLaw', { src = src }))
local canOpen = select(2, Poggy('job.has', { src = src, job = { 'sheriff', 'marshal' }, minGrade = 1 }))

Poggy('job.set',  { src = src, job = 'sheriff', grade = 2, persist = true })   -- one call on every framework
Poggy('job.duty', { src = src, onDuty = true })                                -- RSG yes; VORP refuses: 'unsupported'

local _, deputies = Poggy('players.onDuty', { job = 'sheriff', minGrade = 1 })  -- server ids, never a guess

Which jobs count as law or medical is the server owner's setting in poggy_core/config.lua, not something your script decides, so job.isLaw agrees with every other script on the server. onDuty is nil when the framework cannot say (VORP without vorp_police running), a boolean on RSG where duty is part of the job. job.set with persist = true also writes the framework's own job columns so resources that read the database see it at once. Job changes arrive as one event however the framework signals them; on the client, listen for poggy_core:jobChangedLocal.

Running on a server with no framework

poggy_core probes for a framework at start. When it finds none, or finds one it has no adapter for (anything other than VORP Core, RSG Core and QBCore RedM), it falls back to the standalone adapter. Standalone is honest rather than helpful: every framework verb (character, money, jobs, items, storage, permissions) refuses with unsupported, players.onDuty returns an empty list, and Core.GetFramework() reports 'standalone'. Nothing pretends to succeed.

What still works without a framework: notifications, menus and text inputs (poggy_core draws them itself), prompts, callbacks, and the SQL installer. So a script that needs none of the framework can run anywhere poggy_core runs, and a script that does can say so cleanly:

local Core = exports.poggy_core:Get()
Core.Ready(function()
    if not Core.HasAdapter() then
        print(('[myshop] poggy_core found %s but has no adapter for it; shop disabled'):format(Core.GetFramework()))
        return
    end
    if not Core.Has('money.cash') then return end   -- capabilities, not framework names
    startShop()
end)

Core.HasAdapter() is false both when there is no framework and when there is one poggy_core has no adapter for. Check it before relying on framework calls, and check Core.Has(capability) (money.gold, job.duty, storage.permissions, char.offline and the rest) rather than branching on the framework's name; Core.GetFramework() is for logging.

Database, config and updates

If your resource has a sql/install.sql, the bridge runs it through poggy_core the first time PoggyReady() passes on the server, before your first query, on MariaDB and MySQL 8. CREATE TABLE IF NOT EXISTS, ALTER TABLE ... ADD COLUMN, named indexes, INSERT IGNORE and repeatable UPDATE ... WHERE are applied only when missing, so a normal start sends no DDL; anything that cannot repeat safely goes in sql/migrations/001.sql, run once and recorded. Server owners import nothing.

poggy_core's updater only touches scripts on its own feed. Your resource is not on it, so it is never written to; the config-merge behaviour you see on Poggy scripts (added settings added, removed settings cleaned up, the owner's values kept, originals backed up) is part of that updater rather than something your script gets on its own.

Diagnostics

From the server console, or in game for admins:

poggycore           framework, version, capabilities, registered containers
poggycore test      read-only smoke test against your own character
poggycore caps      the full capability map
poggycore scripts   registered scripts: id, folder (when different), version
poggycore usables   usable items registered through poggy_core: item -> script
poggycore verbs     every verb, its side and its payload keys

The smoke test modifies nothing. Every script that loads the bridge prints one boot line saying whether poggy_core is ready and which framework was found, so a customer's console reads as one product. If your script needs a verb added in a later poggy_core, declare poggy_core_min in the manifest: against an older running core the bridge prints one red line at boot and every call returns core_too_old rather than half-working.

Adding your own adapter

One file. Copy server/adapters/standalone.lua, which is the interface specification, fill in every method it lists, declare a caps table, and add the detection probe to shared/sh_detect.lua. The dispatcher calls those method names and nothing else, so an adapter cannot silently miss one, and poggycore caps shows what you declared. server/adapters/vorp.lua is the worked example with the framework traps listed at the top; server/adapters/rsg.lua is the second, written the same way with the rsg-core line numbers each trap was read from, and server/adapters/qbr.lua is the third. That is how QBCore RedM arrived: one adapter file, no change to any script.

Where to go next

The README inside the poggy_core folder is the full reference: every verb with its payload and return value, the capability list, the per-framework notes for money, jobs, inventory and storage, and the notification styles. Poggy Skillcheck (Free) is a second free, standalone building block (one client export for a skillcheck minigame), and Poggy Util (Free) shows a whole resource written against poggy_core, config and translations open. Questions go to the Rosewood Ridge Discord; bring the boot line.

Questions this guide answers

Can I use poggy_core without buying a Poggy script?

Yes. poggy_core is free and has no dependency on any other Poggy resource. Install it, load @poggy_core/template/poggy.lua as your script's first shared script, and call Poggy(verb, payload). Nothing in it checks for a purchase.

Which frameworks does poggy_core support for my script?

VORP Core, RSG Core and QBCore RedM, each through an adapter proven on a live server; the QBR adapter passes the full self-test against qbr-core and qbr-inventory. On a server with no framework, or one poggy_core has no adapter for, Core.HasAdapter() is false and every framework verb refuses with an error code rather than pretending to work.

Does my script get poggy_core's automatic updates?

poggy_core updates itself and the Poggy scripts on its update feed. Your own resource is not on that feed, so it is never touched. What you do get for free: your sql/install.sql runs on every start with tables and columns added only when missing, and your config is merged the same way if you ship updates through the same mechanism.

What happens if the player closes a menu or the framework is not ready?

Every call returns ok, value, err. A closed menu or input returns false, nil, 'closed'; a core that is not ready or has no framework returns false, nil, 'no_core'. Branch on ok and nothing else, and nothing in your script half-works.

Scripts mentioned in this guide

Whole store →
Poggy Core — Free RedM Framework Core and Auto-Updater
Free RedM Framework Core and Auto-Updater

Poggy Core

The free foundation every Poggy script runs on: one framework API for VORP Core, RSG Core and QBCore RedM, automatic updates, config files that merge themselves and database tables created on start. Use it under your own scripts too.

PriceFree
Poggy Skillcheck — Free RedM Skillcheck Script
Free RedM Skillcheck Script

Poggy Skillcheck

A standalone, canvas-rendered circular skillcheck with a single export that waits until the player passes or fails. Hook it into lockpicking, crafting, fishing, medical or anything else.

PriceFree
Poggy Util — Free RedM Server Utilities Pack
Free RedM Server Utilities Pack

Poggy Util

Eleven optional server utilities in one resource, from area of play and duty count to unstuck, weapon jams, armor and a help menu, each with its own switch.

PriceFree

Best RedM Scripts for a New Server

Economy, law, activities, roleplay tools and admin utilities. What to run, what it costs, and where the free options are.

Read the guide →

VORP vs RSG Core vs QBCore for RedM

Script availability, structure and inventory for each framework, and what "runs on VORP, RSG and QBCore" actually means.

Read the guide →

How to Install a RedM Script

Download, resources folder, SQL, start order, config and the escrow licence check. Then the five common errors.

Read the guide →

Free RedM Scripts Worth Installing

Seven free scripts, what each one does, what it needs, and what it does not do.

Read the guide →

How to Update RedM Scripts Without Breaking Your Config

The manual routine that keeps your config safe, and the automatic route that removes the routine.

Read the guide →

RedM Server Economy: How to Balance Money, Jobs and Shops

Sources, sinks, player shops, auctions, jobs and a stipend. The model, the starting numbers and the scripts.

Read the guide →

How to Set Up Jobs on a RedM Server

Job names and grades, duty, several jobs per character, badges, and making every script agree.

Read the guide →

How to Back Up a RedM Server

The database, resources, configs, licence files and txData. How to automate it, and how to test the restore.

Read the guide →

Cfx Escrow Explained for RedM Server Owners

Which files are encrypted, what you can still change, the licence file, and the error everyone hits once.

Read the guide →

RedM Server Performance: How to Reduce Lag

Measure first, then the usual causes: hot client loops, dead entities, unindexed queries and NPC systems tuned too high.

Read the guide →
Return to Store