Skip to content

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.

An external app is a FiveM resource that:

  1. Declares Blixt as a soft dependency in fxmanifest.lua.
  2. On server start, calls exports.blixt:RegisterExternalApp(payload) with metadata: app ID, name, icon, category, permissions, and a URL pointing at its NUI bundle.
  3. Optionally declares widgets via the widgets field of the registration payload.
  4. 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.

PackageWhat’s in it
@blixt/sdkRuntime types for external apps plus the browser-only @blixt/sdk/preview shim for standalone app previews.
@blixt/hooksReact hooks the app’s NUI uses to integrate with Blixt’s shell: navigation, theming, notifications, and tRPC client.
@blixt/typesTypeScript types for the registration payload, app props, and exported data shapes.

Both are published to npm and versioned independently from the Blixt resource itself.

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.ts
import { 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.tsx
import { 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.

  • Look at the source of apps/business in 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.