Kartik Scripts

Tablet Apps (External UI)

Other resources can put their own UI inside the MDT tablet, so players carry one tablet instead of several. A registered app shows up in the MDT navigation like any other tab.

An app opens in one of two ways:

  • Embedded (recommended): your resource's own NUI page is shown inside the MDT screen, under the MDT navigation. The MDT keeps the tablet prop, the animation and NUI focus.
  • Hand-off: the MDT hides and your resource shows its own NUI. The tablet prop stays in the player's hand.

All exports on this page are client-side. Keep your own item or command as a fallback for servers that do not run kartik-mdt.


RegisterTabletApp

exports['kartik-mdt']:RegisterTabletApp(app)
FieldTypeDescription
idstringUnique app id. Your resource name is a good choice.
labelstringName shown in the MDT navigation.
iconstring?An icon from the custom page icon set (Shield, Notebook, FolderOpen, ...) or an image URL.
ordernumber?Sort order among apps. Default 100.
uistring?Embedded apps only: your NUI page, for example https://cfx-nui-my-resource/web/index.html. The MDT adds ?embedded=1 to it. Leave it out for a hand-off app.
openfunction(ctx)Called when the player opens the app. ctx.embedded is true for embedded apps, ctx.fromTablet for hand-off apps. In both cases skip your own prop and animation, and do not call SetNuiFocus when embedded.
closefunction?Called when the MDT closes while your app is open.
canOpenfunction?Return false to hide the app for this player, for example a job check.

Returns true when the app was registered.

Apps are removed automatically when your resource stops. Register on start, and again on kartik-mdt:tablet:ready in case the MDT starts after your resource:

local function registerApp()
    if GetResourceState('kartik-mdt') ~= 'started' then return end
    exports['kartik-mdt']:RegisterTabletApp({
        id = GetCurrentResourceName(),
        label = 'Evidence',
        icon = 'FolderOpen',
        ui = ('https://cfx-nui-%s/web/index.html'):format(GetCurrentResourceName()),
        open = function(ctx) OpenMyTablet(ctx) end,
        close = function() CloseMyTablet() end,
    })
end

CreateThread(registerApp)
AddEventHandler('kartik-mdt:tablet:ready', registerApp)

Embedded apps

SendNUIMessage from your resource only reaches your own NUI frame, not your page inside the MDT. While embedded:

  • Send messages to your page with SendTabletAppMessage(id, payload). payload arrives as a normal message event, the same shape as SendNUIMessage.

  • Messages are queued until your page is ready. Once your message listener is attached, post:

    window.parent.postMessage({ tabletAppReady: true }, '*');
  • fetch('https://<your-resource>/<callback>') from your page still reaches your own RegisterNUICallbacks.

  • Inside the MDT invokeNative may be missing, so check for ?embedded=1 in the URL instead of treating the page as a normal browser.

  • Do not call SetNuiFocus, the MDT owns focus. To hide the tablet for a moment (a camera view, a screenshot), call SetTabletSuspended(true), then SetTabletSuspended(false) to bring it back.

  • When your page closes itself (for example on ESC), call CloseMDT() to put the tablet away.

exports['kartik-mdt']:SendTabletAppMessage('my-resource', { action = 'refresh', data = records })
exports['kartik-mdt']:SetTabletSuspended(true)
exports['kartik-mdt']:CloseMDT()

Hand-off apps

  • ReturnToTablet(): for your Home button. Hide your UI first, then call it. The MDT reopens with the tablet still in hand.
  • CloseTabletApp(): your UI closed outright (ESC). The tablet prop is put away.
  • GetActiveTabletApp(): returns the id of the hand-off app currently showing, or nil.

UnregisterTabletApp

exports['kartik-mdt']:UnregisterTabletApp(id)

Removes an app. If it is open, it is closed.


Opening the MDT on your app

openMDTPage(path) opens the MDT (if it is closed) straight on a page. Use it with your app's id to open your app directly, for example from an item:

exports['kartik-mdt']:openMDTPage('/tablet-app?id=my-resource')

On this page