NUI Tier 2 — Full NUI Apps
Tier 2 NUI is for complex, custom UIs: phone apps, inventory grids, maps, and HUD elements. You write the HTML/CSS/JS; Shiva provides the Lua bridge.
Structure
my-module/
├── ui/
│ ├── index.html
│ ├── src/
│ │ └── App.vue (or App.tsx, App.svelte, etc.)
│ ├── package.json
│ └── vite.config.tsSetting Up the UI
bash
cd resources/[shiva]/my-module
shiva make ui # scaffolds the ui/ directory
cd ui && npm installThe Lua ↔ JS Bridge
Lua → JS (show data)
lua
-- Lua (client side)
SendNUIMessage({
action = 'open',
data = {
items = playerItems,
},
})
SetNuiFocus(true, true)js
// JavaScript
window.addEventListener('message', (event) => {
if (event.data.action === 'open') {
store.items = event.data.data.items
isOpen.value = true
}
})JS → Lua (send actions)
js
// JavaScript: send a callback to Lua
async function closeInventory() {
await fetch(`https://my-module/close`, {
method: 'POST',
body: JSON.stringify({}),
})
}lua
-- Lua: register the callback
RegisterNUICallback('close', function(data, cb)
SetNuiFocus(false, false)
cb({ ok = true })
end)Building for Production
bash
cd ui && npm run buildThe built assets go to ui/dist/. Reference them in fxmanifest.lua:
lua
ui_page 'ui/dist/index.html'
files {
'ui/dist/**',
}Development with Hot Reload
bash
cd ui && npm run dev
# Then in game: set the NUI url to http://localhost:5173Security Checklist
- [ ] Never expose sensitive server-side data in NUI state
- [ ] Validate all
RegisterNUICallbackinputs on the server - [ ] Don't trust display values (balances, inventory counts) for authority
- [ ] Set
SetNuiFocus(false, false)on disconnect/resource stop