External apps
This page covers the high-level shape. For exports such as notifications,
mail, calendar, maps, badges, and phone/app controls, see the
Developer API. If you’re building an external
app today, the @blixt/hooks
and @blixt/types packages
are your browser/runtime contract.
External apps are FiveM resources that ship their own NUI and game scripts and register themselves with Blixt at runtime. Once registered, they appear on the home screen, in the app switcher, and in the App Store, alongside built-in apps.
The shape of an external app
Section titled “The shape of an external app”An external app is a FiveM resource that:
- Declares Blixt as a soft dependency in
fxmanifest.lua. - On server start, calls
exports.blixt:RegisterExternalApp(payload)with metadata: app ID, name, icon, category, permissions, and a URL pointing at its NUI bundle. - Optionally declares widgets via the
widgetsfield of the registration payload. - Optionally calls Blixt’s gated exports, such as read player records
or send notifications. For those calls to succeed, the resource must
be in
blixt:trusted_resources.
RegisterExternalApp itself is not gated, so unregistered resources
can still register. Gating happens at the privileged-export level.
Packages
Section titled “Packages”| Package | What’s in it |
|---|---|
@blixt/sdk | Runtime types for external apps plus the browser-only @blixt/sdk/preview shim for standalone app previews. |
@blixt/hooks | React hooks the app’s NUI uses to integrate with Blixt’s shell: navigation, theming, notifications, and tRPC client. |
@blixt/types | TypeScript types for the registration payload, app props, and exported data shapes. |
Both are published to npm and versioned independently from the Blixt resource itself.
Standalone phone preview
Section titled “Standalone phone preview”External apps can run in a normal Vite tab before they are loaded by the
FiveM phone. In dev mode, alias @blixt/sdk to the preview shim so app code
can keep importing the same SDK surface it uses in the real phone:
// apps/<product>/web/vite.config.tsimport { defineConfig } from 'vite';
export default defineConfig(({ command }) => ({ resolve: command === 'serve' ? { alias: [{ find: /^@blixt\/sdk$/, replacement: '@blixt/sdk/preview' }], } : undefined, esbuild: { jsx: 'automatic' }, server: { port: 5177, strictPort: false, },}));The app preview entry should render the app directly into a phone-sized
container. Keep browser-only data in local mocks and branch on
isBrowserMode; do not call FiveM NUI callbacks from the preview path.
// apps/<product>/web/src/preview.tsximport { createRoot } from 'react-dom/client';import App from './index';
createRoot(document.getElementById('root')!).render(<App />);<!-- apps/<product>/web/index.html --><div class="phone-frame"> <div id="root" class="phone-screen"></div></div><script type="module" src="/src/preview.tsx"></script>Use app-local CSS for the wrapper if the app is not already importing a stylesheet:
:root { color-scheme: light; --background: #f8fafc; --foreground: #0f172a; --card: #ffffff; --muted: #eef3f8; --muted-foreground: #64748b; --border: #dbe3ee;}
body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #e7edf5; color: var(--foreground); font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;}
.phone-frame { width: min(390px, 100vw); height: min(844px, 100vh); border: 1px solid #cbd5e1; border-radius: 34px; padding: 14px; background: #0f172a; box-shadow: 0 24px 80px rgba(15, 23, 42, 0.22);}
.phone-screen { height: 100%; overflow: hidden; border-radius: 24px; background: var(--background);}The preview shim provides safe browser implementations for shell hooks such as
AppContainer, useBackHandler, getActiveDevice, useWideAppLayout, and
the Gov ID helper guards. Its fetchNui and createFetchNui implementations
throw with a clear error so each app is forced to use explicit mock handlers in
browser mode.
Where to start
Section titled “Where to start”- Look at the source of
apps/businessin the Blixt repo for a reference implementation. - Use the Developer API when your resource needs phone notifications, mail, calendar, maps, badges, or phone/app controls.
- The relevant code paths in the Blixt repo are:
packages/api: tRPC routers your app’s server can call.packages/blixt-hooks(@blixt/hooks): public React hooks.packages/blixt-types(@blixt/types): public TypeScript types.
If you’re building something specific and have questions, email your support address with the use case.