Skip to content

lb-phone Compatibility

The phone ships a compatibility layer so third-party scripts written against lb-phone keep working after a server switches phones, without editing them. Three surfaces are covered: exports, events, and the resource name itself.

How it works

FiveM's exports() helper is sugar over an event: registering export X binds a handler on __cfx_export_<resource>_X, and a caller's exports['<resource>']:X(...) resolves through that event. The compatibility layer registers handlers on __cfx_export_lb-phone_<Name> events directly, so other resources calling exports['lb-phone']:SendMessage(...) land on the phone's own modules and walk the same validation as the first-party exports.

  • Server half: server/compat/lbphone/ (a domain file per surface plus the event mirror).
  • Client half: client/compat/lbphone.lua.
  • Supported names map onto the equivalent behaviour. Unsupported names are registered as stubs: the first call prints one console breadcrumb naming the calling resource, then every call returns a fixed type-safe default, so no caller ever hits a hard "No such export" error.

The manifest also declares provide 'lb-phone', which satisfies other resources' hard dependency 'lb-phone' lines and GetResourceState('lb-phone') checks.

Enabling and disabling

The layer is on by default and guards itself:

cfg
set sd_phone_lbcompat "false"   # disables the whole layer

If a real resource named lb-phone exists and is started, the layer does not register and a warning explains why; if the real lb-phone starts mid-session after registration, the layer deregisters its handlers and warns. provide 'lb-phone' must be commented out of the manifest if a real, runnable lb-phone is kept on the server.

Restart caveat

FXServer caches a consumer's resolved export functions under the addressed name and clears them only when a resource with that exact name stops. Restarting the phone therefore strands consumers that already called an exports['lb-phone']:... export until those consumers restart too. Practical rule: after restarting the phone on a live server, restart the resources that integrate through lb-phone names.

Event mirror

Third-party scripts listening for lb-phone events keep working: the layer re-fires the phone's first-party events under lb-phone's names with translated payloads. Nothing fires when the layer is disabled or stood down.

Server events

lb eventFired whenFidelity notes
lb-phone:phoneNumberGeneratedA character's number is first mintedSkipped when the owner is offline (lb fires it for online players)
lb-phone:messages:messageSentA 1:1 or system SMS is sentchannelId is a synthetic 0; attachments is a JSON-encoded string per lb's contract; group sends have no lb analog
lb-phone:mail:mailSentA mail is sentFired once per recipient, matching lb's single-recipient shape
lb-phone:newCallA call starts ringingcallId is the voice channel; videoCall/hideCallerId always false
lb-phone:callAnsweredA call is answeredSame CallData shape, answered = true
lb-phone:callEndedA call endsSecond argument is the ending player's source, nil on disconnects
lb-phone:newCompanyMessageA citizen messages a companysentByEmployee always false, coords omitted, anonymous always false
lb-phone:onAddTransactionAn external Wallet row is loggedSigned amount with 'received'/'paid' derived from the sign, matching lb's source; skipped for citizens without a number
lb-phone:deletedFromGalleryA player deletes gallery mediaPositional (source, phoneNumber, link)
lb-phone:birdy:newPostA Birdy post is createdattachments JSON-encoded; verified always false
lb-phone:instapic:newPostA Photogram post is createdPhotogram stands in for InstaPic
lb-phone:pages:newPostA Pages listing is posteddescription maps from the listing body
lb-phone:marketplace:newPostA Marketplace listing is postedSame mapping as Pages

Not mirrored, with reasons: lb-phone:numberChanged (numbers never change after minting), lb-phone:factoryReset, lb-phone:toggleVerified (no verified system), lb-phone:trendy:newPost (no server-backed Trendy analog), lb-phone:darkchat:newMessage (no external DarkChat surface).

Client events

lb eventFired whenFidelity notes
lb-phone:phoneToggledThe phone opens or closes
lb-phone:setOnScreenAlongside phoneToggledThe phone has a single open state, the closest equivalent
lb-phone:toggleHudCamera mode enters (true) or exits (false)
lb-phone:jobUpdatedThe framework reports a job change{ job, grade }, matching lb's qb/esx bridges

Not mirrored: lb-phone:settingsUpdated, lb-phone:phoneDied (no battery system), lb-phone:numberChanged.

Inbound events

Events other scripts fire AT lb-phone are handled too:

lb eventBehaviour
lb-phone:usePhoneItemOpens the phone through the normal guarded path. The per-number lbPhoneNumber metadata is ignored — sd-phone's own unique-phones system (configs/uniqueandsim.lua) governs numbers and per-phone data instead
lb-phone:itemAdded / lb-phone:itemRemovedAcknowledged no-ops: phone ownership is resolved server-side on every open, nothing needs equipping

Export coverage

Every export lb-phone documents is registered, as a full mapping, a partial one, or a warn-once stub with a type-safe default. The heavily-used surfaces are full or partial: GetEquippedPhoneNumber, GetSourceFromNumber, SendMessage, SendMail, GetEmailAddress, SendNotification, NotifyEveryone, AddContact, AddTransaction, CreateCall/EndCall/IsInCall, HasPhoneItem, HasAirplaneMode, ResetSecurity, and on the client IsOpen, ToggleOpen, OpenApp, SendNotification, GetEquippedPhoneNumber, GetFlashlight, ToggleDisabled, and the custom app surface AddCustomApp/RemoveCustomApp/SendCustomAppMessage.

Cell towers map too: GetCellTowers on both sides returns the masts configured in configs/celltowers.lua, and SetServiceBars really does force the displayed bar count. Note the shape difference — lb-phone's config is a bare coordinate list, so GetCellTowers returns plain vector3 values with no ranges. sd-phone's own getCellTowers keeps them, alongside getServiceLevel and hasService.

Stubbed families (safe defaults, one console breadcrumb naming the caller): crypto, DarkChat, trays, battery, camera components, the check hooks, and lb-phone's callback wire (RegisterCallback and friends).

Scripts that query lb-phone's tables directly

The compatibility layer covers exports and events. A script that runs its own SQL against lb-phone's tables is outside it, because an export shim can intercept a function call but never a query: the query reaches the database on its own. sd-phone owns the same table names with its own schema, so an lb-shaped query fails on a column that never existed, for example Unknown column 'link' in 'field list'.

The photo gallery pickers in vehicle sale and dealership scripts are the usual case. Their lb-phone adapter selects link from phone_photos keyed by phone_number, and sd-phone keys that table by identifier:

lb-phone columnsd-phone column
phone_numbercitizenid, the owner's per-character identifier
linkurl
is_videonone, read off the URL extension
is_favouritefavorite
timestampcreated_at

Most of these scripts ship one adapter file per supported phone and pick between them in their config. Point that setting at a custom or generic adapter and read through the exports instead of SQL:

lua
-- Server side, inside the script's phone adapter.
local citizenid = exports['sd-phone']:getIdentifierByNumber(phoneNumber)
local photos = citizenid and exports['sd-phone']:getPhotosByIdentifier(citizenid, { limit = 50 }) or {}

local gallery = {}
for _, photo in ipairs(photos) do
    if not photo.isVideo then
        gallery[#gallery + 1] = photo.url
    end
end
return gallery

See getPhotos and getPhotosByIdentifier for the full entry shape.

Photos missing after a switch

On first boot the phone moves any table it finds under one of its own names but carrying a foreign shape aside, renaming it to <table>_lb, then creates its own. An old library is therefore still in phone_photos_lb rather than lost, and the built-in migrator (sdphone:migrate) imports it into the new shape.

Custom apps

Custom apps built for lb-phone run unmodified: AddCustomApp registers them for real, the app page gets the same injected globals (both capitalizations), the same componentsLoaded handshake, and the same message relay, and dependency 'lb-phone' plus GetResourceState('lb-phone') boot polls are satisfied. The full mechanism, the field table and templates are covered in the Custom Apps guide.

The complete per-export table with per-name notes ships with the resource in docs/exports.md.

TIP

lb-phone's per-player data (numbers, contacts, messages, photos, notes) can also be imported at boot by the built-in migrator: run sdphone:migrate dry from the server console for a non-destructive preview, then sdphone:migrate to import. The import is idempotent and marker-guarded, so it runs once.