API Reference
For developers — place your resource's on-screen panels around Oxide HUD with olink.hud.AddPanel, follow its hide reasons, hold the HUD hidden, draw editor previews, use the server accent, and read the lower-level calls, events and exports.
This page is for developers who want their resource's on-screen panels to work with Oxide HUD. Server owners only need Installation, Features and Configuration.
A panel is any part of your resource's page that stays on screen while the player plays: a job timer, a stage card, a row of control hints, a notification area. With one call, olink.hud.AddPanel, Oxide HUD:
- places the panel clear of its own blocks, the minimap, the server logo and other resources' panels;
- lets the player move it, hide it while editing, or switch it off in game, in the
/hudeditor; - hides it while the HUD is hidden (pause menu, death, character select, a cutscene);
- gives it the server's accent colour.
Your resource never depends on Oxide HUD. Everything goes through the client hud namespace of o-link (1.12.0 or newer). On a server without Oxide HUD, or with an older o-link, your panel stays exactly where your page has always drawn it.
o-link HUD panels guide
The o-link HUD panels guide covers the same API with more patterns: growing stacks, panels drawn with natives, and default spots at the bottom centre.
Your First Panel
A complete resource with one panel: three files.
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
client_script 'client.lua'
ui_page 'web/index.html'
files { 'web/index.html' }
dependency 'o-link'client.lua
olink = exports['o-link']:olink()
-- The page calls this once it is listening, so it never misses the first message.
RegisterNUICallback('pageReady', function(_, cb)
cb('ok')
if not (olink.hud and olink.hud.AddPanel) then return end -- an o-link without HUD support
olink.hud.AddPanel({
id = 'my-job:timer', -- unique: start it with your resource name
label = 'My job: timer', -- shown on the panel in the /hud editor
anchor = 'top-right', -- the corner or edge the panel is pinned to
size = { w = 24, h = 8 }, -- in vh: percent of the screen height
offset = { x = 3, y = 4 }, -- where the page draws it without a HUD, in vh from the anchor
}, function(panel)
SendNUIMessage({ action = 'hudPanel', panel = panel })
end)
end)web/index.html
<style>
/* Where the panel sits without a HUD. Keep it in a stylesheet: the HUD's placement is set
inline, and when it is cleared your own position comes back. */
#timer { position: fixed; right: 3vh; top: 4vh; width: 24vh; height: 8vh; }
</style>
<div id="timer">4:32</div>
<script>
const timer = document.getElementById('timer')
window.addEventListener('message', (event) => {
if (event.data.action !== 'hudPanel') return
const panel = event.data.panel
Object.assign(timer.style, panel.style) // where the HUD put it
timer.style.display = panel.show ? '' : 'none' // switched off, or hidden with the HUD
})
fetch(`https://${GetParentResourceName()}/pageReady`, { method: 'POST', body: '{}' })
</script>That is the whole integration:
- The page loads and calls
pageReady, and Lua adds the panel. (FiveM drops NUI messages sent before a page is listening, so always add panels from a callback your page calls.) o-linkcalls your function straight away, and again every time something about the panel changes: the player drags it, switches it off, opens the editor, the HUD hides or comes back, the HUD restarts. Your function runs in your resource, soSendNUIMessagereaches your page.- The page applies
panel.styleandpanel.show.
In a Vue page the same two fields bind directly: <div v-if="active && panel.show" :style="panel.style">.
AddPanel returns true when the panel was added. When the definition is invalid it returns false and o-link prints a line in the F8 console saying what was wrong. Calling it again with the same id replaces the panel. olink.hud.RemovePanel(id) removes it, and every panel is removed when your resource stops.
What Your Function Receives
| Field | Type | Meaning |
|---|---|---|
id | string | The panel's id, so one function can serve several panels |
hosted | boolean | true while Oxide HUD places the panel. false without it: draw it where you always do |
show | boolean | false while the panel must not be drawn: the player switched it off, the editor leaves it out, or the HUD is hidden for a reason the panel follows. Draw your panel only while it is true |
preview | boolean | true while the /hud editor is open and shows this panel. Draw sample content if nothing real is up |
style | table | CSS for the panel's root element: left, right, top, bottom and opacity, as strings. Apply all of them every time. Empty strings hand the position back to your stylesheet |
theme | table or nil | The server's accent colours (The Server Accent). nil keeps your own colours |
position | table or nil | The raw placement in vh (anchor, x, y, left, top, w, h, hidden, hiddenBy), for panels drawn with natives instead of a page. nil while not hosted |
Panel Options
Only id and label are required.
| Option | Default | What it does |
|---|---|---|
id | required | 1 to 64 characters of A-Z a-z 0-9 _ - . :. Start it with your resource name, for example 'my-job:timer' |
label | required | The panel's name in the /hud editor. Pass translated text |
anchor | 'top-left' | Where the panel is pinned: top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right. Any other value makes the call fail |
size | { w = 20, h = 10 } | The panel's size in vh. A missing or non-positive width becomes 20 and height 10. Width is capped at 400 and height at 100 |
offset | none | { x, y } in vh, measured in from the anchor's edges: where your page draws the panel today. Oxide HUD starts there and moves the panel only as far as it must. Without it, the HUD finds a free spot |
overlay | false | true for a panel that only shows briefly (a prompt, a progress bar, a tutorial card): it keeps clear of the HUD, but other panels do not move for it |
essential | false | true for a panel the player must not miss (notifications, a downed card, alerts): it can be hidden while editing but never switched off in game |
shareSpot | none | A name in the same characters as id, for panels that never show at the same time, such as the stage cards of different jobs. Panels with the same shareSpot and anchor share one spot instead of stacking |
hideWith | { 'unloaded', 'external', 'pause', 'dead' } | The hide reasons this panel follows. false never hides with the HUD |
accent | true | false never sends theme. Give server owners a config switch for it |
Units. Every size and offset is in vh, percent of the screen height, so a panel keeps its place at every resolution. The screen is 100 * width / height vh wide (about 177.8 at 16:9) and 100 vh tall; 3 vh is 32 px at 1080p. If your panel is sized in pixels, convert at registration: local _, height = GetActiveScreenResolution(), then w = 300 * 100 / height.
Anchors. The anchor you register never changes, wherever the player drags the panel; only the distances change. On a centred anchor (top, bottom, left, right, center) the distance is measured from the centre line to the middle of the panel, so centre your element on it yourself, for example with transform: translateX(-50%) for top and bottom. style never sets transform, so your own transforms and transitions keep working.
Shared spots in use: job-timer (top: gig job timer cards), drug-stage (bottom: drug lab stage cards), controls-hint (bottom: rows of key prompts), activity:left and activity:right (top corners: activity cards). Use one when your panel fits it; a new name needs no change in Oxide HUD.
Hiding with the HUD
While the HUD is hidden, the reason is the first of these that applies:
| Reason | When |
|---|---|
unloaded | No character: at character select, after logout, before the first character loads |
external | A resource holds the HUD hidden with SetVisible(false): a full-screen view or a cinematic |
pause | The pause menu is open |
dead | The player is dead or downed |
phone | Oxide Phone is open |
focus | Any NUI has focus (a menu, an inventory), unless a resource holds KeepVisible |
show turns false only for the reasons listed in your panel's hideWith. Follow only the reasons that suit your panel: a downed card must not hide for dead, and a panel whose own menu takes focus must not hide for focus (the default leaves phone and focus out). Because pause comes before dead, a downed card can still hide for the pause menu.
The HUD stays on screen while the /hud editor is open, even though the editor takes NUI focus.
Hiding the HUD
olink.hud.SetVisible(false) -- hold the HUD hidden for your resource
olink.hud.SetVisible(true) -- release your holdHold it for a full-screen view that runs without NUI focus, such as a bodycam, CCTV or a camera scene, and release it on every path that closes the view. A menu with NUI focus needs nothing: the HUD already hides for focus.
olink.hud.KeepVisible(true) -- your NUI focus is not a menu: keep the HUD up
olink.hud.KeepVisible(false) -- that focus endedUse it when your resource takes NUI focus while the player keeps playing: a chat input, a remote used while driving. While any resource keeps the HUD up, NUI focus does not hide it; the pause menu, death and SetVisible(false) still do.
Both return true when Oxide HUD took the request and false without it. The HUD stays hidden while any resource holds it hidden. Each hold ends when its resource stops, and o-link remembers it while Oxide HUD is stopped and applies it again when Oxide HUD starts. With several such views in one resource, release only when the last one closes.
Previews in the Editor
While the /hud editor is open, Oxide HUD draws a labelled placeholder for your panel and sets preview = true. Draw your panel with still sample content when nothing real is up, so the player sees what they are placing; if the real panel is up, draw that instead of both.
showandpreviewalready follow the editor: the player's eye, Solo, and shared spots (the editor shows one panel of a shared spot at a time).- A panel the player switched off in game is still previewed, at the
opacitystylegives it (0.45), as its placeholder is marked Hidden in game. - While the player drags the placeholder, your function is called about ten times a second, so the preview moves with it.
- Keep the preview display only: no NUI focus, no input, no sounds, nothing sent to the server. Do not raise your page (
SetNuiZindex) while the editor is open; the editor's controls must stay on top. - Size it in vh (or in pixels scaled to the screen height) and register the same size, so it fits its placeholder at every resolution.
The Server Accent
A server owner sets one accent colour in Oxide HUD's config.lua (Config.Theme.accent, see Configuration). Your panel gets it as panel.theme:
| Field | Use |
|---|---|
accent | Where your panel uses its own accent today: bars, fills, borders, icons, glows |
light | Accent-coloured text and highlights |
dark | Gradient ends, deep fills, pressed states |
contrast | '#ffffff' or '#0f1113': text and icons drawn on a solid accent fill |
locked | true when the owner set Config.Theme.lockAccent: players may not replace the accent with colours of their own |
The four colours are lowercase '#rrggbb' strings, all worked out from the one colour the owner set:
accentis that colour, lightened only when it is too dark to read on a dark panel (until it reaches 3:1 contrast against#0f1113);lightmixesaccenttoward white, at least 30% and further until it reaches 4.5:1 against#0f1113;darkmixesaccent35% toward black;contrastis white unless white text onaccentwould fall under 3:1.
theme is nil when the owner set no accent, while Oxide HUD is stopped, without Oxide HUD, and when you pass accent = false. In every one of those cases, keep your own colours. In your page, override your own accent CSS variables with the theme's values, and remove them again when it is nil:
const VARS = { '--my-accent': 'accent', '--my-accent-text': 'light', '--my-on-accent': 'contrast' }
function setTheme(theme) {
const root = document.documentElement.style
for (const [name, key] of Object.entries(VARS)) {
if (theme && /^#[0-9a-f]{6}$/i.test(theme[key])) root.setProperty(name, theme[key])
else root.removeProperty(name)
}
}- Replace only your accent. Colours that carry meaning (good, warning and bad states, notification types) keep their colours.
- Give server owners a switch, such as
Config.UseHudAccent = true, and passaccent = Config.UseHudAccent ~= false. - If your panel lets players pick colours, hide that choice while
lockedistrue.
olink.hud.GetTheme() returns the same table (or nil) for resources that colour something other than a panel. A player's own accent from the editor's Theme tab colours only their own HUD, never other resources' panels.
How Oxide HUD Places Panels
Separately for the on-foot and in-vehicle layouts:
- Panels the player moved sit exactly where they were put. If your resource later registers the panel with a different anchor, that saved spot is ignored.
- Every other panel starts at its
offset(without one, 1 vh inside GTA's safe zone at its anchor) and moves away from its anchor's edge (down for the top and middle rows, up for the bottom row) to the first spot clear of everything it keeps clear of. A panel on thetoporbottomanchor only looks on its own half of the screen, so it never lands in the middle of the player's view; a corner or side panel can use its whole side. - When the column is full, the panel takes the spot on its own half of the screen, still clear of the HUD's own parts, that covers the least of other panels. It covers essential panels only when every spot would. If not even the HUD leaves room there (a panel taller than the space above the vehicle cluster), it stays at its
offset. A panel is never pushed onto the HUD or clamped at the far edge of the screen. - What a panel keeps clear of: the HUD's blocks and groups, each at its full size, so a panel does not move when a block hides (decor such as background panels, lines and brackets does not count); the minimap while it is shown; the server logo; the areas the owner lists in
Config.ReservedAreas; and every non-overlay panel placed before it that the player has not switched off. Things are kept 1 vh apart. - Panels against the edge. A panel at offset
y = 0on a top or bottom anchor (a card that peeks up from the bottom) keeps clear only of what the HUD draws right now, not of room kept for hidden blocks, and sits flush against what it has to move past. So it stays against the edge, or rests directly on the speedometer in a vehicle. - Order. Panels the player has not moved claim their spots in the same order every session, whatever order resources started in: essential panels first, then the panel whose
offsetasks to sit nearest its anchor's edge, then byid(a shared spot by its name). - Overlays keep clear of the HUD's own parts but not of other panels, and no panel moves for them.
- Shared spots are placed once, as a box as large as the largest member, and keep space at the size of the members that take space, so a short-lived overlay member does not push other panels further. Dragging one in the editor moves them all.
What this means for your panel: give the offset you want, nearest the edge first; mark essential only for what the player must not miss; mark anything that shows briefly as overlay; and register the size your panel draws at.
Switching between on foot and in a vehicle places every panel again, and Reset in the editor clears the player's moves for the layout being edited.
Panels the Player Hides
In the editor's Layers tab, under Other resources, the player can hide a panel (or every panel of a resource) while editing, Solo one resource, or switch a panel off in game with Show in game. Show in game is not offered for an essential panel.
With AddPanel you only need show: it is false whenever the panel must not be drawn. With the lower-level calls, a position carries hidden = true and hiddenBy = 'player' (switched off in game) or 'editor' (left out while the editor is open; this comes first while it is open). Test pos.hidden == true, since positions from a server without Oxide HUD have no hidden field.
A panel switched off in game stops taking space, so panels placed after it may move into its spot. A panel hidden only while editing keeps its space.
Without Oxide HUD
When Oxide HUD is not installed, o-link's _default provider answers the same calls:
AddPanelcalls your function once withhosted = false, emptystylevalues andshow = true, so your page keeps its own position. If Oxide HUD starts later,o-linkadds the panel to it and calls your function again.- A panel registered with an
offsetsits there (kept on screen); one without is placed inside GTA's safe zone, clear of estimated top-corner HUDs and the minimap. essentialandshareSpotare accepted and ignored.SetVisibleandKeepVisiblereturnfalse,IsVisibleis alwaystrue,GetThemereturnsnil, and noolink:client:hud:*event fires.
On an o-link older than 1.12.0, olink.hud itself is nil, so always check it (if olink.hud and olink.hud.AddPanel then). While Oxide HUD is installed but stopped, every call answers as _default, except that SetVisible and KeepVisible holds are remembered and applied when it starts.
Resources that start before o-link (a notification or progress bar resource) cannot keep a reference from load time. Look the namespace up at call time, and add your panels from your page-ready callback and again on olink:client:hud:ready:
local function hudNamespace()
if GetResourceState('o-link') ~= 'started' then return nil end
local ok, olink = pcall(function() return exports['o-link']:olink() end)
return ok and olink and olink.hud or nil
endLower-Level Calls
AddPanel is built on these. Use them only to lay out panels yourself, for example a panel drawn with natives.
local position = olink.hud.RegisterWidget(def) -- same fields as AddPanel, without hideWith and accent
olink.hud.UnregisterWidget(id) -- boolean: false if the id was not registered
olink.hud.GetWidgetPosition(id) -- position, or nil if the id is not registered
local visible, reason = olink.hud.IsVisible() -- boolean, string|nil
local layout = olink.hud.GetReservedAreas() -- what the HUD covers, see belowRegisterWidget returns a position, or false when def is invalid; Oxide HUD then prints one line in the F8 console naming the panel, the resource and what was wrong. Register at your own start and again on olink:client:hud:ready, and read IsVisible() each time, because events only report changes.
| Position field | Type | Meaning |
|---|---|---|
id | string | The panel's id |
anchor | string | The anchor you registered |
x, y | number | Offsets to draw the panel at, in vh from the anchor |
w, h | number | The panel's size in vh |
left, top | number | The panel's top-left corner, in vh from the screen's top-left corner |
moved | boolean | true when the player placed the panel in the editor |
hidden, hiddenBy | boolean, string or nil | See Panels the Player Hides |
The position RegisterWidget returns is the saved or requested spot until Oxide HUD's page has measured its own blocks; the placed one follows through olink:client:hud:layoutChanged. Always apply both.
These local client events fire on the client (AddEventHandler, not RegisterNetEvent):
| Event | Args | When |
|---|---|---|
olink:client:hud:ready | The HUD is up for the character. Register (again) here | |
olink:client:hud:layoutChanged | (positions) | Something moved or a panel was hidden or shown. positions maps every registered id to its position; read your own ids |
olink:client:hud:visibilityChanged | (visible, reason) | The HUD was hidden or shown. reason is nil while visible |
olink:client:hud:editMode | (enabled) | The /hud editor opened or closed. Positions are relayed just before it as the editor opens and just after it as the editor closes |
olink:client:hud:themeChanged | (theme) | Oxide HUD started (the theme) or stopped (nil), only when the server set an accent |
When Oxide HUD stops, it fires visibilityChanged(true, nil) if it was hidden and editMode(false) if the editor was open, so panels following it never stay hidden or in preview.
GetReservedAreas() returns, in vh from the screen's top-left corner:
{
measured = true, -- true once a HUD has measured its own layout
screen = { w, h, pixelWidth, pixelHeight },
safeZone = { x, y, w, h }, -- GTA's safe zone
areas = {
{ id, kind, x, y, w, h, resource? }, -- kind: widget, minimap, logo, reserved, panel, overlay
},
}Oxide HUD returns measured = false and no areas until its page has measured its layout, a moment after it loads; call it again after each layoutChanged. _default returns measured = false and areas of kind estimate.
Exports
Oxide HUD's client exports exist for o-link's adapter. Use olink.hud instead: it works without Oxide HUD and records the calling resource for you.
| Export | Used by olink.hud for |
|---|---|
RegisterPanel(def) | RegisterWidget and AddPanel. def.resource sets the owner; without it, the calling resource |
UnregisterPanel(id) | UnregisterWidget and RemovePanel |
GetPanelPosition(id) | GetWidgetPosition |
GetReservedAreas() | GetReservedAreas |
SetHiddenBy(owner, hidden) | SetVisible |
SetKeepVisible(owner, keep) | KeepVisible |
GetVisibility() | IsVisible. Returns { visible, reason } |
GetTheme() | GetTheme. Returns the theme table or nil |
Data Oxide HUD Reads
Oxide HUD reads all game data through o-link, so a framework or resource shows up on the HUD as soon as o-link supports it. To make your seatbelt, fuel or voice resource appear on the HUD, add an o-link adapter for it; nothing in Oxide HUD changes.
| HUD data | o-link (client) |
|---|---|
| Character loaded / unloaded | olink:client:playerReady, olink:client:playerUnload, olink:client:startCharacterSelect; olink.framework.GetName(), olink.framework.GetIsPlayerLoaded() |
| Hunger, thirst, stress | olink.needs.GetAll(), olink:client:needsChanged |
| Cash, bank | olink.money.GetBalance('cash' | 'bank'), olink:client:moneyChanged |
| Character name | olink.character.GetName() |
| Dead, downed | olink.death.IsPlayerDead(), olink.death.IsPlayerDowned(), olink:client:playerDeathStateChanged |
| Fuel | olink.fuel.GetFuel(vehicle) |
| Seatbelt | olink.seatbelt.IsOn() |
| Voice range, talking, radio | olink.voice.GetState() |
Health, armour, oxygen, stamina, vehicle, aircraft and boat data, street and area names, heading, the clock, the server ID and the equipped weapon are read from GTA natives.
Next Steps
Features
What Oxide HUD does — the default layout, blocks, data and styles, groups, templates, rules, colours, the in-game editor, vehicle types, saved layouts and share codes, your server's default HUD, auto-hiding and other resources' panels.
Troubleshooting
Fixes for common oxide-hud problems — a HUD that never shows, two HUDs, empty name and money, missing status bars, seatbelt, fuel, voice and radio, the minimap, server logo, translations, overlapping panels, resolution changes, the default layout, share codes and /hud.