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.
Before you start
Section titled “Before you start”| You need | Why |
|---|---|
| oxmysql, running | Everything the phone remembers lives in your database |
| ESX, QBCore, QBox or fmLib | Detected on its own; you do not have to say which |
| A FiveM server from the last couple of years | The phone’s web half uses the server’s built-in Node |
Nothing else. No npm, no build step, no web server.
1. Put the folder in place
Section titled “1. Put the folder in place”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.
2. Start it after the things it leans on
Section titled “2. Start it after the things it leans on”In server.cfg, below oxmysql and below your framework:
ensure oxmysqlensure es_extended # or qb-core / qbx_coreensure mic_phoneIt 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 portIf you see a red line about a framework instead, start mic_phone after your framework — that is almost always what it is.
3. The database — nothing to do
Section titled “3. The database — nothing to do”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.
4. Give players something to open
Section titled “4. Give players something to open”Out of the box the phone needs an inventory item called phone:
-- shared/config.luaConfig.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.
5. Server-only settings
Section titled “5. Server-only settings”Copy the example and fill in what you want:
shared/server_config.example.lua → shared/server_config.luaThis 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,
FallbackToDataUrlkeeps 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.
You are done
Section titled “You are done”The phone works. Everything below is optional.
Optional: the phone on a real phone
Section titled “Optional: the phone on a real phone”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 HTTP | HTTPS | |
|---|---|---|
| Messages, mail, garage, everything else | yes | yes |
| Notifications with the app closed | no | yes |
| Calls | no | yes |
| The microphone, for voice notes | no | yes |
| Installing it as an app, with its own icon | no | yes |
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' }, },}- A name. On a VPS that is one A record pointing at its address. No domain?
duckdns.orggives you one free in a minute. - 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.comhttps: port 80 open for the challengehttps: challenge port closedhttps: certificate in hand, good until 2027-01-31https: 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.
When something is not right
Section titled “When something is not right”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 see | What it is |
|---|---|
| The phone does not open | No phone item, or RequireItem is on and they are not carrying one |
| The QR points somewhere unreachable | Set ServerConfig.Web.Url by hand; the automatic guess cannot see past a router that moved the port |
| No certificate, and the log blames the challenge | Port 80 is not reaching this machine. Check the forward and the firewall, in that order |
| Photos are huge / the database is growing fast | No media token, and FallbackToDataUrl is keeping files in the database |
| Calls between two players are silent | Voice is auto and found no voice resource; name yours in Config.Bridge.voice |
Before you open your city
Section titled “Before you open your city”shared/server_config.luais yours. Never share it or commit it anywhere public; it holds your media token and your webhook URLs.- Point the
Serverwebhook 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.Debugoff 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.