Skip to content

Create Custom App

0R SmartPad | How To Create Custom App

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:

text
/tabadmin

Click "Add New App" and fill in the form:

FieldDescriptionExample
App NameDisplay name on the tabletStock Market
App IDUnique identifier (no spaces)app-stocks
DescriptionShort description (optional)Real-time stock tracker
CategoryDropdown: General, Job, Services, etc.Services
ColorPick a color for the icon background(click a circle)
Icon PathPath to icon image/appicons/stocks.png
Iframe URLYour app's NUI URLhttps://cfx-nui-my-stocks/web/dist/index.html
Job FilterComma-separated jobs (empty = everyone)police, ems

Click "Create App". The app appears on all tablets immediately.

Icon tip: Place your .png icon file in 0r-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#

text
my-custom-app/
├── fxmanifest.lua
├── client.lua
├── server.lua
└── web/
    ├── src/
    │   ├── main.tsx
    │   ├── App.tsx
    │   └── hooks/
    │       └── useTabletContext.ts
    └── dist/
        └── index.html

fxmanifest.lua#

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:

text
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:

tsx
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#

FieldTypeDescription
serialstringTablet serial number (unique per device)
appIdstringApp action ID (e.g. app-stocks)
appFullIdstringFull app ID from apps.json
playerJobstringPlayer's current job name
languagestringTablet language code (en, tr, etc.)
configobjectServer 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:

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:

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:

lua
-- Set badge to 3
exports['0r-smarttab']:SetAppBadge('app-stocks', 3)

-- Clear badge
exports['0r-smarttab']:SetAppBadge('app-stocks', 0)

From server.lua:

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:

js
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:

js
window.parent.postMessage({ type: '0r-smarttab-close' }, '*')

Quick Reference#

Lua Exports#

ExportSideSignature
SendNotificationClientexports['0r-smarttab']:SendNotification(data)
SendNotificationServerexports['0r-smarttab']:SendNotification(targetSrc, data)
SetAppBadgeClientexports['0r-smarttab']:SetAppBadge(appId, count)
SetAppBadgeServerexports['0r-smarttab']:SetAppBadge(targetSrc, appId, count)
OpenTabletWithAppClientexports['0r-smarttab']:OpenTabletWithApp(appId)

Iframe Messages (JS)#

MessageDirectionPayload
0r-smarttab-contextTablet → App{ serial, appId, playerJob, language, config }
0r-smarttab-theme-changeTablet → App{ theme: 'dark' | 'light' }
0r-smarttab-visibilityTablet → App{ visible: boolean }
0r-smarttab-closeApp → Tablet(no payload)

Notification Data Fields#

FieldTypeRequiredDefault
titlestringYes—
descriptionstringYes—
appstringNo—
iconstringNo—
durationnumberNo4000