Skip to content

About

App templates for the sd-phone custom app system. Build phone apps with plain JS, React, Vue or Svelte; lb-phone app compatible.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

sd-phone app templates

Ready-made starting points for building custom apps on sd-phone. Pick a stack, copy a folder, ship an app.

Guide Documentation Discord

Stacks Compatibility

Custom App Guide · sd-phone · Discord


Each folder in this repository is a complete, standalone FiveM resource that registers the same small Example app on the phone: a live data card fed by fetchNui, a notes field demonstrating keyboard handling, a server push panel, and buttons for popups, context menus and notifications, with automatic light and dark theming.

The five variants are the same app written five ways, so comparing folders shows exactly what changes between stacks and nothing else.

Full documentation: the Custom App Guide covers the registration field table, the injected API surface, messaging patterns, and limits. This README covers the templates themselves.

Choosing a template

Folder Stack Build step Push command
sd-template-vanillajs Plain HTML/CSS/JS none /appmessage
sd-template-reactjs React 18 + Vite (JavaScript) npm run build /appmessage_react
sd-template-reactts React 18 + Vite (TypeScript) npm run build /appmessage_reactts
sd-template-vuejs Vue 3 + Vite npm run build /appmessage_vue
sd-template-sveltejs Svelte 5 + Vite npm run build /appmessage_svelte

Every variant ships with its UI already built (ui/dist is committed), so all five run straight from a server restart with no npm required. The TypeScript template additionally includes components.d.ts, which types the injected API surface.

Quick start

  1. Copy the variant you want into your resources folder and rename it.
  2. Set your own Identifier, Name, Description and Developer in config.lua.
  3. ensure the resource after sd-phone in your server.cfg. It then registers itself and re-registers automatically whenever sd-phone restarts.
  4. Open the phone. The app is pre-installed (DefaultApp = true); set it to false to make it an App Store download instead.
  5. Try the push command from the table above, for example /appmessage hello world, and watch the text arrive in the app.

Start order matters. Your app resource must start after sd-phone. In server.cfg, put its ensure line below sd-phone's, and never start a custom app before the phone. Starting first can leave the app unregistered until you restart it.

How a template is put together

Each variant contains the same three Lua files plus a ui/ folder:

File Role
config.lua The values copied into registration: identifier, name, description, developer, install mode
client.lua Registers the app, answers the UI's fetchNui calls, and pushes live messages with sendCustomAppMessage
fxmanifest.lua Whitelists the UI files and declares ui_page, which client.lua reads back to build the registration URL
ui/ The app webpage. Vite variants keep source in ui/src and the committed build in ui/dist

The two rules every app must follow

These two conventions are the difference between an app that works and an app that paints your screen or renders blank. Both are already wired into every variant.

1. Keep the body hidden and transparent by default

FiveM renders every resource's ui_page as a fullscreen overlay in the game at all times. Your stylesheet must hide it; the phone reveals the page when it loads inside the app frame, and the dev-mode branch reveals it in a plain browser.

html, body {
    visibility: hidden;
    background: transparent;
}

Backgrounds need the second line because of a CSS quirk: a background color on html or body is painted onto the page canvas even while the element is hidden, which floods the whole game screen with that color. Keep the body transparent and put your background color on a wrapper element instead; the templates use the #root / #app mount node.

2. Render only after componentsLoaded

In game, sd-phone injects the API globals (fetchNui, useNuiEvent, SetPopUp, GetSettings, and the rest) into your page, then posts the string componentsLoaded to your window. Rendering earlier means the globals do not exist yet.

const devMode = !window.invokeNative;

if (window.name === '' || devMode) {
    if (devMode) {
        document.body.style.visibility = 'visible';
        render();
    } else {
        window.addEventListener('message', (e) => {
            if (e.data === 'componentsLoaded') render();
        });
    }
}

window.invokeNative only exists inside FiveM, which makes it the dev-mode detector: in a plain browser the app renders immediately with mocked data, so you can develop without the game running.

Developing with live reload

  1. In the variant's ui/ folder run npm install, then npm run dev (Vite serves on http://localhost:5173).
  2. In fxmanifest.lua, swap ui_page to the commented http://localhost:5173 line and restart the resource.
  3. The phone now loads your dev server inside the app frame and edits hot-reload. Swap back and npm run build for production.

Rebuilding

The Vite variants keep package-lock.json but not node_modules; run npm install once before building. Use rem and em units in your CSS so the app stays crisp if you enable fixBlur in the registration.

lb-phone compatibility

sd-phone ships a drop-in lb-phone compatibility layer, so apps written against exports['lb-phone']:AddCustomApp register and run unmodified, including the injected globals in both capitalizations and the componentsLoaded handshake. Details in the compatibility docs.


About

App templates for the sd-phone custom app system. Build phone apps with plain JS, React, Vue or Svelte; lb-phone app compatible.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages