Creating a Custom App#
This guide walks you through creating a custom app that runs inside the 0r-smarttab tablet.
https://github.com/0resmon/0r-customapp-templates
Step 1: Register the App#
Open the admin panel in-game:
/tabadmin
Click "Add New App" and fill in the form:
| Field | Description | Example |
|---|---|---|
| App Name | Display name on the tablet | Stock Market |
| App ID | Unique identifier (no spaces) | app-stocks |
| Description | Short description (optional) | Real-time stock tracker |
| Category | Dropdown: General, Job, Services, etc. | Services |
| Color | Pick a color for the icon background | (click a circle) |
| Icon Path | Path to icon image | /appicons/stocks.png |
| Iframe URL | Your app's NUI URL | https://cfx-nui-my-stocks/web/dist/index.html |
| Job Filter | Comma-separated jobs (empty = everyone) | police, ems |
Click "Create App". The app appears on all tablets immediately.
Icon tip: Place your
.pngicon file in0r-smarttab/web/dist/appicons/. Use a square image (128x128 recommended).
Step 2: Set Up the Resource#
Download the 0r-customapp template from GitHub. This is a ready-to-use FiveM resource with React + TypeScript.
File Structure#
my-custom-app/
├── fxmanifest.lua
├── client.lua
├── server.lua
└── web/
├── src/
│ ├── main.tsx
│ ├── App.tsx
│ └── hooks/
│ └── useTabletContext.ts
└── dist/
└── index.html
fxmanifest.lua#
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
description 'My Custom App'
version '1.0.0'
client_scripts { 'client.lua' }
server_scripts { 'server.lua' }
ui_page 'web/dist/index.html'
files {
'web/dist/index.html',
'web/dist/**/*.*'
}
Iframe URL Format#
The URL you entered in the admin panel must point to your resource:
https://cfx-nui-my-custom-app/web/dist/index.html
Replace my-custom-app with your resource folder name.
Step 3: Receive Tablet Context#
When your app opens inside the tablet, it automatically receives context data via postMessage. Use the included hook:
import { useTabletContext } from './hooks/useTabletContext'
function App() {
const ctx = useTabletContext()
if (!ctx) return null // Not inside tablet yet
return (
<div>
<p>Serial: {ctx.serial}</p>
<p>Player Job: {ctx.playerJob}</p>
<p>Language: {ctx.language}</p>
</div>
)
}
Context Fields#
| Field | Type | Description |
|---|---|---|
serial | string | Tablet serial number (unique per device) |
appId | string | App action ID (e.g. app-stocks) |
appFullId | string | Full app ID from apps.json |
playerJob | string | Player's current job name |
language | string | Tablet language code (en, tr, etc.) |
config | object | Server config values |
Step 4: Use Tablet Features#
Custom apps can use tablet notifications and badges through Lua exports. No JavaScript needed for these — just call them from your client.lua or server.lua.
Send a Notification#
Shows a notification banner on the tablet (with sound, peek when closed, notification history).
From client.lua:
exports['0r-smarttab']:SendNotification({
title = 'Stock Market',
description = 'BTC is up 12% today!',
app = 'app-stocks', -- your app ID (for icon & peek)
duration = 5000 -- ms, default 4000
})
From server.lua:
-- Send to a specific player
exports['0r-smarttab']:SendNotification(source, {
title = 'Stock Market',
description = 'Your order has been filled.',
app = 'app-stocks',
duration = 5000
})
Set App Badge#
Shows a red badge (number) on your app's icon on the home screen.
From client.lua:
-- Set badge to 3
exports['0r-smarttab']:SetAppBadge('app-stocks', 3)
-- Clear badge
exports['0r-smarttab']:SetAppBadge('app-stocks', 0)
From server.lua:
exports['0r-smarttab']:SetAppBadge(source, 'app-stocks', 5)
Step 5: Listen for Tablet Events (Optional)#
Your app's JavaScript can listen for theme and visibility changes from the tablet:
window.addEventListener('message', (event) => {
const { type, payload } = event.data || {}
if (type === '0r-smarttab-theme-change') {
// payload.theme = 'dark' or 'light'
document.body.className = payload.theme
}
if (type === '0r-smarttab-visibility') {
// payload.visible = true (tablet opened) or false (tablet closed)
}
})
Your app can also request to close itself:
window.parent.postMessage({ type: '0r-smarttab-close' }, '*')
Quick Reference#
Lua Exports#
| Export | Side | Signature |
|---|---|---|
SendNotification | Client | exports['0r-smarttab']:SendNotification(data) |
SendNotification | Server | exports['0r-smarttab']:SendNotification(targetSrc, data) |
SetAppBadge | Client | exports['0r-smarttab']:SetAppBadge(appId, count) |
SetAppBadge | Server | exports['0r-smarttab']:SetAppBadge(targetSrc, appId, count) |
OpenTabletWithApp | Client | exports['0r-smarttab']:OpenTabletWithApp(appId) |
Iframe Messages (JS)#
| Message | Direction | Payload |
|---|---|---|
0r-smarttab-context | Tablet → App | { serial, appId, playerJob, language, config } |
0r-smarttab-theme-change | Tablet → App | { theme: 'dark' | 'light' } |
0r-smarttab-visibility | Tablet → App | { visible: boolean } |
0r-smarttab-close | App → Tablet | (no payload) |
Notification Data Fields#
| Field | Type | Required | Default |
|---|---|---|---|
title | string | Yes | — |
description | string | Yes | — |
app | string | No | — |
icon | string | No | — |
duration | number | No | 4000 |