Skip to content

Installation

Ten minutes, and most of it is waiting for the server to start. There is no SQL file to import and no second program to run — the tables build themselves the first time the resource starts.

Every step below ends with how you know it worked, so you never move on wondering.


You needWhy
oxmysql, runningEverything the phone remembers lives in your database
ESX, QBCore, QBox or fmLibDetected on its own; you do not have to say which
A FiveM server from the last couple of yearsThe phone’s web half uses the server’s built-in Node

Nothing else. No npm, no build step, no web server.


Drop mic_phone into your resources — inside a bracketed folder is tidiest:

resources/[mic]/mic_phone/

Keep the folder named mic_phone. Nothing breaks if you rename it, but the phone’s web address is built from the folder name, so anything written down about it would change too.


In server.cfg, below oxmysql and below your framework:

ensure oxmysql
ensure es_extended # or qb-core / qbx_core
ensure mic_phone

It worked when the console says, on start:

[mic_phone] web: door B listening on http://0.0.0.0:30125 · door A at /mic_phone/web/ on the game port

If you see a red line about a framework instead, start mic_phone after your framework — that is almost always what it is.


The first start reads sql/schema.sql and creates whatever is missing. You will see a run of CREATE TABLE IF NOT EXISTS mic_phone_… lines go past. That is it.

It worked when those lines appear once and never again on later starts.


Out of the box the phone needs an inventory item called phone:

-- shared/config.lua
Config.Open = {
RequireItem = true,
Item = 'phone',
...
}

Most ESX and QB setups already have that item. ox_inventory users: add it to your items list if it is not there. Just trying it out? Set RequireItem = false and skip this entirely.

It worked when you join, hold the item, and F1 opens the phone. /phone does the same, and T unfolds it into the tablet while it is open.


Copy the example and fill in what you want:

shared/server_config.example.lua → shared/server_config.lua

This file is loaded as a server script only, so nothing in it ever reaches a player. Two things live there:

Photos, videos and voice notes. Put a Fivemanage media token in ServerConfig.Media.Token (or the FIVEMANAGE_MEDIA_API_KEY convar) and the phone uploads files itself, storing only the resulting link. A Discord webhook works as a fallback.

With neither one set, FallbackToDataUrl keeps the file inside your database. That is fine for a test and heavy in production — a few seconds of voice is tens of kilobytes on every row it touches. Set a token, or turn that option off and let uploads fail loudly.

Discord webhooks. One URL per kind of event, or Default to catch everything. The one worth setting even if you skip the rest is Server: it is the only one that tells you about the server rather than about a player — the address changed, a certificate did not renew.


The phone works. Everything below is optional.


Players can carry their character’s phone on their own handset. It is already on: Settings › Linked devices on the in-game phone shows a QR, they scan it, and type the six digits it shows back into the game. That device then acts as that character.

Over plain HTTP that already gives them messages, mail, contacts, the garage, properties and the rest. What a browser will not hand to an insecure page is the good part:

Plain HTTPHTTPS
Messages, mail, garage, everything elseyesyes
Notifications with the app closednoyes
Callsnoyes
The microphone, for voice notesnoyes
Installing it as an app, with its own iconnoyes

Getting HTTPS, without installing anything

Section titled “Getting HTTPS, without installing anything”

This resource can fetch and renew its own certificate. Point a name at your server, open two ports, and fill in three lines:

-- shared/server_config.lua (server-only: this file is never sent to a client)
ServerConfig.Web = {
Url = 'https://phone.yourserver.com/',
Https = {
Enabled = true,
Names = { 'phone.yourserver.com' },
Contact = '[email protected]',
},
}
  1. A name. On a VPS that is one A record pointing at its address. No domain? duckdns.org gives you one free in a minute.
  2. Port 80 and port 443 through the firewall. 80 is only opened while a certificate is being asked for, and closed again as soon as the answer is given.

It worked when the console says:

https: asking for phone.yourserver.com
https: port 80 open for the challenge
https: challenge port closed
https: certificate in hand, good until 2027-01-31
https: listening on https://phone.yourserver.com/

If 443 is taken — some routers keep it for their own admin page — serve on another port instead: Port = 8443, forward 8443, and write Url = 'https://phone.yourserver.com:8443/'. Only the challenge has to be on 80; a certificate belongs to a name, not to a port, and everything still works.

docs/HTTPS.md covers the other situations: behind the Cfx.re proxy, behind a reverse proxy you already run, on a host that will not give you ports.


phonehttps check — asks the certificate authority’s test server whether it will talk to your machine. Run this first when no certificate arrives: it separates your firewall from this resource.

phonehttps — where the certificate stands and when it renews.

phoneduck — where the name stands, if this server keeps its own DuckDNS name pointed at itself, and when it last said so. phoneduck now sends the address it currently has and prints what DuckDNS answered, which is the way to find out that a token is wrong without waiting for the address to move.

What you seeWhat it is
The phone does not openNo phone item, or RequireItem is on and they are not carrying one
The QR points somewhere unreachableSet ServerConfig.Web.Url by hand; the automatic guess cannot see past a router that moved the port
No certificate, and the log blames the challengePort 80 is not reaching this machine. Check the forward and the firewall, in that order
Photos are huge / the database is growing fastNo media token, and FallbackToDataUrl is keeping files in the database
Calls between two players are silentVoice is auto and found no voice resource; name yours in Config.Bridge.voice

  • shared/server_config.lua is yours. Never share it or commit it anywhere public; it holds your media token and your webhook URLs.
  • Point the Server webhook somewhere you read. It is the one that says the phone’s front door has stopped working, and it is the only warning you will get before a player tells you.
  • Turn Config.Debug off if you turned it on: it prints every bridge decision to the console.
  • Security is worth ten minutes if you run a public city: what this resource assumes about the people connecting to it, and the rules it keeps so that a modified client cannot reach past its own character.