ui_page and never touches NUI focus. The tablet owns the device, the status bar, the dock and notifications. You own what is inside your app.
This page is for developers. If you only want to send a notification, show the HUD or read a player’s group, you do not need an app. See Exports.
What you need
Your app also needs two packages from us:
@awoken/tablet-sdk for hooks, and @awoken/tablet-design-system for components that match the tablet. They are not part of the release download. Ask for them through support and reference them from your package.json as local folders:
package.json
Quick start
1
Register the app from your server
server.lua
?v= on the end matters. See Restarting does not update the UI.2
Serve the bundle from your manifest
fxmanifest.lua
ui_page.3
Configure Vite as a federation remote
web/vite.config.ts
assetsDir: ''. With Vite’s default, the bundle looks for its chunks under assets/assets/ and they fail to load.4
Write the component
web/src/MyApp.tsx
5
Build and restart
Build the web project, restart your resource, and open the tablet. Your tile is on the home screen.
Registering
RegisterApp returns ok, err. It fails when a required field is missing (id, name, remoteEntry, moduleName), when the id is already registered by another resource, or when sdkVersion does not match the tablet’s.
The tablet drops your app on its own when your resource stops, so you do not need to deregister on shutdown. DeregisterApp(id) is only for hiding an app at runtime.
The tablet’s SDK version is 1. An app on a different major is refused and its tile reads “Update”. Only raise sdkVersion when we announce a new major.
Who can see the app
Pass avisibility list to RegisterApp. Every rule must pass. Leave it out and everyone sees the app.
Rules are re-checked on player load, inventory change, and job or gang change. If something else changed, such as a flag your own script keeps, call:
Use
sdcard for any app you want locked behind a physical unlock. It works whichever way the server owner has SD cards set, so you never have to know.Talking to your server
Register an ox_lib callback and call it from React.SendAppEvent:
PushNotification.
The SDK
Button, Card, FieldRow, SectionHeader and Select, plus CSS variables such as --bg-panel, --text-primary and --awoken-primary.
Translations
Config.Locale. A missing key falls back to English, then to the key itself.
If your app crashes
Each app runs inside its own error boundary. If your component throws while rendering, the player sees a “could not load” message inside your app’s frame and the rest of the tablet keeps working. The error is logged to the F8 console. Errors inside promises and event handlers are not caught by it. Handle those yourself.Common pitfalls
Restarting my resource does not update the UI
Restarting my resource does not update the UI
The tablet caches a loaded bundle against its URL. Register the same URL twice and it keeps the one it already has.Put a token on your
remoteEntry URL that changes whenever your resource starts, as in the quick start. Version first, then the start time.The tile appears but tapping it does nothing
The tile appears but tapping it does nothing
The bundle could not be fetched. Check the
remoteEntry URL is https://cfx-nui-<your-resource>/web/dist/remoteEntry.js with the resource name matching the folder name exactly, and that your manifest’s files block includes web/dist/**/*.A chunk 404s with assets/assets in the URL
A chunk 404s with assets/assets in the URL
Set
assetsDir: '' in your Vite config.Invalid hook call
Invalid hook call
Your app loaded its own copy of React. Pin
react and react-dom to ^19.0.0 and list both in the federation shared array.The app loads but says there is no default export
The app loads but says there is no default export
The file you
expose must export default your component. Avoid named exports alongside it in that file.The player's game closes when my app opens
The player's game closes when my app opens
A vector reached the tablet, most often coordinates in a callback return. Convert them to plain
{ x, y, z } tables before returning.Dates crash the app
Dates crash the app
oxmysql returns
TIMESTAMP columns as numbers. Format them in the query so the app always receives a string: