Pulse Phone docs

Importing your existing phone data

Moving to Pulse Phone from another phone resource? Pulse Phone can bring your players' data across. It reads the old phone's tables, copies what it can into Pulse Phone, and tells you plainly what it could not.

It knows these phones:

Old phone What comes across (details in its section below)
A compatible phone_* database Nearly everything: numbers, contacts, calls, chats, photos, notes, mail, wallet, social apps and more
qb-phone Numbers, contacts, chats, photos, mail, tweets
NPWD Numbers, contacts, calls, chats, photos, notes, marketplace, Twitter
YSeries Numbers, contacts, blocked numbers, calls, chats, photos and albums, notes, mail, bank transfers, ads, voice memos
Quasar Smartphone (current, qs_phone_* tables) Numbers, contacts, blocked numbers, calls, chats, photos, notes, mail, wallet, classified ads, dark chat, dating, alarms, backups
Quasar Smartphone PRO Numbers, contacts, blocked numbers, chats (see its section), photos, mail, classified ads, backups
Quasar Smartphone (legacy) Numbers, contacts, photos, notes, mail
gcphone Numbers, contacts, calls, text messages
GKS Phone Numbers only
RoadPhone Numbers only

What holds for every phone:

  • Every player keeps their phone number. Their Pulse Phone device is keyed by the same framework identifier the old phone used (citizenid on QBCore/QBox, the ESX identifier, or a licence), so the number follows the character.
  • Your old tables are only read. Nothing is changed or deleted. (The one exception, a rename that deletes nothing, is explained in Tables that share a name with Pulse Phone.)
  • Run it as often as you like. Every imported row gets an id made from the old row's own key, so a second run only adds what is new and never creates duplicates.
  • Always dry-run first. phoneimport --dry-run counts everything and writes nothing.
  • You don't tell Pulse Phone which phone you had. It looks at your database and says what it found, for example Detected qb-phone (player_contacts, old_phone_messages, phone_gallery).
  • Nothing is guessed. If the old phone does not store something, or stores it in a way that was never published, that step says so in the report and imports nothing.

Tools:

phoneimport ... server console command: copies your existing phone data into Pulse Phone
phoneimport prepare for a compatible phone_* database only: prints the statements that move all its tables to old_phone_*

Step by step (every phone)

1. Back up the database

Take a full dump before you start, for example mysqldump -u root -p yourdb > before-pulse-phone.sql. The import never deletes anything, but a backup is always the first step.

2. Stop the old phone

Remove the old phone's ensure line from server.cfg. Leave its tables where they are; the import reads them there.

If you are coming from a compatible phone_* database, also read Coming from a compatible phone_* database below before you start Pulse Phone. It has one extra, optional step.

3. Start Pulse Phone

Add ensure phone (the resource name) to server.cfg and start the server. The console should show Pulse Phone started with a SQL backend (oxmysql, ghmattimysql or mysql-async). The import needs SQL, because your old data only exists there.

At start, Pulse Phone checks for old tables that have one of its own table names (see Tables that share a name with Pulse Phone). If it finds any, it moves them aside and prints one block in the console saying what moved and why.

4. Dry run

From the server console, or in game as a player with command.phoneimport or phone.admin:

phoneimport --dry-run

Nothing is written. The report starts with what was detected. For each step it then shows how many old rows were read and how many Pulse Phone rows would be written.

More than one phone found? If your database still has tables from two phones (for example you used qb-phone before your last phone), the import stops and lists them:

More than one phone's tables were found. Say which one to import with --from=<name>, for example:
  phoneimport --dry-run --from=compatible
  --from=compatible      a compatible phone database (61 tables under old_phone_*)
  --from=qb-phone        qb-phone (player_contacts, old_phone_messages)
Nothing was imported.

Add the --from= you want to every command. The names are compatible, qb-phone, npwd, yseries, quasar, quasar-pro, quasar-legacy, gcphone, gks and roadphone. You can import from two phones one after the other; a player's number comes from whichever ran first.

5. Import

phoneimport

The import runs in the background, 500 rows at a time, pausing between batches so the server does not hitch. It prints a line per step:

Detected qb-phone (player_contacts, old_phone_messages, phone_gallery, phone_tweets).
Importing
  devices      read    412  written    409  already      0  skipped     3  failed    0
      skipped (devices): 3 x a character with no phone number - nothing to carry over
  contacts     read   1840  written   1840  already      0  skipped     0  failed    0
  calls        read      0  written      0  already      0  skipped     0  failed    0   (not available from qb-phone: qb-phone does not save call history ...)
  messages     read    951  written  18230  already      0  skipped     2  failed    0
      skipped (messages): 2 x a message with no readable date
      note: 17511 message(s) were the second person's copy of the same chat (qb-phone stores each chat twice); imported once
  ...
Done. The source tables were not modified.

What the columns mean:

  • read: rows found in the old tables. (Some phones keep a whole chat in one row, so written can be larger than read.)
  • written: new Pulse Phone rows (in a dry run, rows that would be written).
  • already: the row came across in an earlier run.
  • skipped: an old row that could not be placed. The lines under the step say why and how many, for example a message whose chat no longer exists, a row with an empty phone number, or a message in a format that cannot be read.
  • failed: a row was malformed, or the database refused the write. Rows are written 100 per statement; if a statement is refused, its rows are retried one at a time, so a single bad row never costs its neighbours. Re-running retries anything that failed.
  • note: something you should know about that step, such as data the old phone kept twice.
  • A step the old phone has no data for says not available from <phone>: <why>.

A large table prints a progress line every 5,000 rows. If a step stops on something unexpected, the console says which step and why, and the other steps still run. Fix the cause (or leave it) and run phoneimport again; finished rows are not copied twice.

Only some parts? Use phoneimport --only=contacts,messages,photos. The steps are: devices contacts calls blocked voicemail messages photos notes mail wallet marketplace pages social darkchat dating services voicememos alarms albums playlists places dms backups crypto. devices must have run (now or in an earlier run) before the rest, because everything else hangs off a player's number.

Run it with the server empty, or before players move to Pulse Phone. A player who is online while their device is imported may have an older copy of their settings in memory, and alarms are stored in those settings.

6. Check, then keep the old tables until you are sure

Log in with a character that used the old phone and check the number, contacts, chats and photos. Keep the old tables for as long as you like. Pulse Phone never reads them outside this command, and when to drop them is your decision.

Tables that share a name with Pulse Phone

A few old phones used a table name that Pulse Phone also uses:

Table Also used by
phone_messages qb-phone, gcphone, Quasar Smartphone PRO, Quasar Smartphone (legacy)
phone_calls gcphone
phone_backups Quasar Smartphone PRO, a compatible phone_* database
phone_bills Quasar Smartphone PRO
phone_accounts, phone_news Quasar Smartphone (legacy)
phone_photos, phone_notes, phone_message_members, phone_message_reactions, phone_darkchat_members, phone_darkchat_messages, phone_music_playlists, phone_photo_albums, phone_photo_album_members a compatible phone_* database

If the old table stayed under that name, Pulse Phone could not create its own table, and that feature would not work. So every time Pulse Phone starts, before it creates its tables, it looks at each of these names:

  • It is Pulse Phone's own table: left alone. Pulse Phone never renames its own tables.
  • It has another phone's columns and none of Pulse Phone's: it is renamed (never dropped, never changed) to old_phone_<name>, for example old_phone_messages. If that name is already taken it uses old_phone_<name>_2, and so on. phoneimport reads it from there.
  • It is neither (columns Pulse Phone does not recognise): it is left alone, and the console tells you to rename it by hand. For phone_bills, phone_accounts and phone_news the old phones never published their columns, so they are only moved when that phone's other tables are in the database too; otherwise you get the same "left alone" message.

The console shows one block when something was moved:

[phone] ---- tables that share a Pulse Phone name ----
[phone] phone_messages belonged to qb-phone: it has citizenid, number, messages and none of Pulse Phone's columns (conversation, sent).
[phone]   renamed to old_phone_messages. Nothing was deleted; `phoneimport` reads it from there (docs/IMPORTING_DATA.md).
[phone] ----------------------------------------------

Nothing is printed when there is nothing to move.

Coming from a compatible phone_* database

This is the phone whose tables are named phone_phones, phone_message_channels, phone_photos, and so on. Nearly everything comes across.

Before you start Pulse Phone (optional, recommended): run the statements phoneimport prepare prints in your SQL client (HeidiSQL, phpMyAdmin or the mysql shell). It renames every table phone_x to old_phone_x, which keeps the two phones' tables clearly apart. A table your server never had just fails that one line, which is fine. If you skip this, Pulse Phone moves the ten shared names aside by itself at start, and the import finds them.

Carry your settings across: set these in Pulse Phone's config/config.lua to match your old phone before you run the import: the framework, the phone item, the language, the phone number format and the keys (INSTALL.md section 8 lists where each one is). Do this before the import: Config.Numbers.format decides how imported numbers are written. A number whose digit count matches the format is stored formatted; any other number is kept exactly as it was.

Commands:

phoneimport --dry-run
phoneimport

The import finds the tables under old_phone_*, or phone_* if you did not rename. --prefix= overrides the search.

What comes across:

Old table Pulse Phone Notes
phone_phones devices Same number and owner identifier. A number already used by a Pulse Phone device is left alone. Players are not sent through setup again.
phone_phone_contacts contacts First and last name, photo, favourite
phone_phone_calls calls (Recents) Answered, duration
phone_phone_blocked_numbers blocked numbers
phone_phone_voicemail voicemail
phone_message_channels / _members / _messages conversations, groups, messages Two-person chats become the conversation Pulse Phone uses for those two numbers. Group chats keep their name, owner and members.
phone_message_reactions tapbacks ❤️ 👍 👎 😂 ‼️ ❓; other emoji are skipped
phone_photos Photos Videos too; favourites kept
phone_notes Notes
phone_mail_messages (+ phone_logged_in_accounts) Mail A phone that was logged into a mail address keeps it as its Pulse Phone address, so mail keeps arriving there
phone_wallet_transactions Wallet history
phone_marketplace_posts Marketplace
phone_yellow_pages_posts Pages
phone_twitter_* Echo Accounts, posts, follows, likes, reposts
phone_instagram_* Vibe Accounts, posts, follows, likes, comments; stories become Glint posts
phone_tiktok_* Loop Accounts, videos, follows, likes, comments, saves
phone_darkchat_* Dark Chat Handle, channels (names normalised the way Pulse Phone does), messages
phone_tinder_* Kindred (dating) Profile (age from the date of birth), swipes. An old match is a match here; match chats become text conversations.
phone_services_* Services Company chats, with the location if one was shared
phone_voice_memos_recordings Voice Memos
phone_clock_alarms Clock alarms Merged into the device's alarm list (kept with its settings)
phone_photo_albums / _members / _photos Shared albums Owner, invited members, and the photos (as links, the way Pulse Phone albums keep them)
phone_music_playlists / _songs / _saved_playlists Music playlists Tracks that are plain video ids; saved playlists stay saved
phone_maps_locations Maps saved places
phone_twitter_messages, phone_instagram_messages, phone_tiktok_channels / _messages Echo / Vibe / Loop direct messages One thread per pair of numbers per app
phone_backups Backups A backup for the same person, holding that number's imported contacts, notes and photos
phone_crypto (+ phone_crypto_coins) Markets positions Only with --crypto=coin:SYMBOL (below)

A phone with two accounts in one social app keeps the first; the second account's posts still show under the same number.

What can't come across: old notifications, post view counts, comment likes, songs that are not plain video ids, phone PINs and Face Unlock (players set a new passcode in Settings).

Crypto holdings (optional). The old phone tracked real-world coins ("bitcoin"); Pulse Phone's Markets app simulates its own instruments (Los Santos Coin LSC, Vinewood Token VNT, and the rest in Config.Markets). They are not the same asset, so holdings only come across when you say which coin becomes which instrument:

phoneimport --only=crypto --crypto=bitcoin:LSC,ethereum:VNT
  • The value is kept, not the coin count. A holding is worth amount x the old phone's last recorded coin value (or what was invested, if no value was recorded). That value becomes a fully paid position in the mapped instrument at its current price, rounded down to the instrument's quantity step. The import never creates money.
  • Two old coins mapped to the same instrument are added together.
  • Skipped, and counted: a coin you did not map, a holding worth less than the instrument's smallest quantity, and a player who is online at the time (re-run when they are off). A player who already holds that instrument in Pulse Phone keeps their own position; it is reported as already.
  • Without --crypto=, the crypto step reports that holdings were left untouched and changes nothing.

Coming from qb-phone

Numbers come from QBCore's own players.charinfo (the phone field), keyed by citizenid.

What comes across:

  • Contacts (player_contacts): name and number.
  • Messages (phone_messages): every one-to-one chat, with shared locations and pictures. qb-phone stores each chat twice, once in each person's row; each message is imported once (the report counts the second copies in a note). The sender is converted from citizenid to number, and qb-phone's dates (which count months from 0) are read correctly.
  • Photos (phone_gallery).
  • Mail (player_mails): sender, subject, text, read or unread. Mail buttons (actions) are not carried.
  • Tweets (phone_tweets) become Echo posts, with the picture. qb-phone has no usernames, so each author's Echo account starts unregistered and they pick a handle the first time they open Echo.

What can't come across:

  • Call history: qb-phone never saved it (it lived in memory until a restart).
  • Crypto: qb-phone kept only a text history of crypto transactions, not holdings.
  • Invoices (phone_invoices): these belong to your billing script, not the phone.
  • Group chats, notes, blocked numbers and the rest: qb-phone does not have them.

Commands:

phoneimport --dry-run
phoneimport

Coming from NPWD

Numbers come from the player table NPWD used: users.phone_number (keyed by the ESX identifier) or players.phone_number (keyed by citizenid). If you configured NPWD with a different table or column, copy the numbers into one of those first.

What comes across:

  • Contacts (npwd_phone_contacts): name, number, photo.
  • Calls (npwd_calls): who called whom, answered or not, start and end.
  • Messages (npwd_messages*): one-to-one chats and group chats with their name and members, shared locations and contact cards. Messages the sender deleted are skipped and counted. NPWD records no group owner, so imported groups have none. Voice notes keep only their text.
  • Photos (npwd_phone_gallery) and notes (npwd_notes). NPWD keeps no dates for either, so they are dated the day you import.
  • Marketplace listings (npwd_marketplace_listings), with their picture. NPWD listings have no price, so they show 0 until the seller edits them.
  • Twitter becomes Echo: profiles (the profile name becomes the handle), tweets, retweets and likes.

What can't come across:

  • Dating (Match): NPWD profiles have no age, and a Pulse Phone dating profile needs one.
  • Dark chat: NPWD's has no handles, and every Pulse Phone Dark Chat message needs one.
  • Blocked numbers, voicemail and the rest: NPWD does not have them.

Commands:

phoneimport --dry-run
phoneimport

Coming from YSeries

YSeries keeps everything by the handset's IMEI. A player's number is the handset's primary SIM (yphone_sim_cards), and the handset belongs to its holder (yphone_holders, the citizenid). A player who held several handsets keeps the first one's number.

This import was built from YSeries' known table layout. If your YSeries version has different tables or columns, the dry run shows no source table for the parts it cannot find, and nothing is written for them.

What comes across:

  • Contacts (with favourites) and blocked numbers.
  • Calls (yphone_recents): both people logged each call; the two copies are paired and each call is imported once, with who answered. Calls a player removed from their Recents are skipped. Old and new call-type names are both understood.
  • Messages: one-to-one and group chats, with attachments.
  • Photos (with favourites) and albums. Photos stored inside the database as data: text, and deleted photos, are skipped and counted; Pulse Phone keeps photos as links.
  • Notes, mail, bank transfers (shown in the Wallet history of both people), YBuy ads (archived ads are skipped) and voice memos.

What can't come across: the Promo Hub, the social apps, dark chat and dating (their columns are not known well enough to map without guessing), passwords, PINs and settings.

Commands:

phoneimport --dry-run
phoneimport

Coming from Quasar Smartphone (current, qs_phone_* tables)

Numbers come from qs_phone_numbers, keyed by the owner identifier. A player who had a mail account keeps its address as their Pulse Phone address, so mail keeps arriving there.

What comes across:

  • Contacts (with favourites) and blocked numbers.
  • Calls (qs_phone_call_recents): each call once (both people's logs are paired), video or audio, and missed calls stay missed. Quasar keeps no call length; an outgoing call with no record on the other side shows as not answered.
  • Messages: one-to-one and group chats, text, images, GIFs and shared locations. Video and payment messages keep only their text; one with no text is skipped and counted.
  • Photos and videos (with favourites), notes (pinned kept; folders are not), mail (the inbox; sent, draft and deleted copies are skipped, because Pulse Phone keeps each message as its recipient's copy), wallet transfers, classified ad posts (as Pages), dark chat (handles, channels, messages), dating (profiles with a date of birth, and likes and passes), alarms and backups.

What can't come across: the social apps and chat apps (their columns were not published in enough detail), crypto wallets (real-world coins; Markets simulates its own instruments), market shop chats, and dating profiles with no date of birth (Pulse Phone needs an age).

Commands:

phoneimport --dry-run
phoneimport

Coming from Quasar Smartphone PRO

Numbers come from phone_metadata (and, for anyone not in it, your framework's own columns). A player's mail account address is kept as their Pulse Phone address.

What comes across:

  • Contacts (player_contacts: display name and photo) and blocked numbers.
  • Messages (phone_messages): the layout of the messages inside each chat was never published, so they are read carefully: a message comes across only when its sender is one of the two people in the chat and it has a real time. Anything else is skipped and counted in the report, never guessed. Both people's copies of a chat are imported once.
  • Photos and videos (phone_gallery), mail (player_mails), classified ad posts (as Pages) and backups.

What can't come across: call history (its columns were not published in enough detail), notes (they live in the phone item's metadata), WhatsApp-style chats, the social apps, dark chat and dating (kept as JSON in a shape that is not published), and crypto.

Commands:

phoneimport --dry-run
phoneimport

Coming from Quasar Smartphone (legacy)

Numbers come from your framework's own columns (users.phone_number on ESX, players on QBCore/QBox).

What comes across: contacts (and blocked numbers, where your version marked them), photos (player_gallery), notes (player_notes, dated the day you import, since no dates were kept) and mail (player_mails).

What can't come across: messages. What each column of the legacy message tables means was never published, so nothing is guessed. The rest of the legacy apps are not imported either.

Commands:

phoneimport --dry-run
phoneimport

Coming from gcphone

Numbers come from users.phone_number, keyed by the ESX identifier.

What comes across:

  • Contacts (phone_users_contacts).
  • Calls (phone_calls): gcphone keeps one log row per person; the two copies are paired and each call is imported once, answered or not. (gcphone's incoming column means the opposite of its name; this is handled.)
  • Text messages (phone_messages): gcphone stores two rows per message, one for each person; each message is imported once. A sent copy whose received copy was deleted is still imported. Unread messages stay unread.

What can't come across: the anonymous chat channels (no sender, and every Dark Chat message needs one), and Twitter (its accounts are not tied to a phone number or owner).

Commands:

phoneimport --dry-run
phoneimport

Coming from GKS Phone or RoadPhone

Their database tables are not public, so only phone numbers come across. They are read from your framework's own columns, not from the phone's tables:

  • GKS Phone: players.charinfo (phone) on QBCore/QBox; users.phone_number on ESX.
  • RoadPhone: the phone_number column on your player table (players or users), falling back to players.charinfo.

Everything else (contacts, chats, photos and the rest) cannot be carried over, and each step in the report says so.

Commands:

phoneimport --dry-run --from=gks
phoneimport --from=gks

(or --from=roadphone; the --from= is only needed if another phone's tables are found too).

Photos, avatars and voice files are copied as links, exactly as the old phone had them. If your old server stored media on a service that is not on Pulse Phone's upload allow-list, add it (Config.Media.allow).