RoadPhone exposes a global window.roadphone API that allows developers to build custom applications with full access to phone data, settings, and events.
The API is available globally via window.roadphone and can be accessed from any iframe-based custom app.
window.roadphone.showNotification({ appTitle: 'My App', title: 'New Message', message: 'You have received a new message!', icon: '/public/img/Apps/light_mode/custom.webp'})
Here’s a complete example of a custom app that displays player information. This app is designed to fit perfectly within the phone’s iframe and uses vh units for proper scaling.
Copy
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>My Custom App</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; } body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', sans-serif; transition: background-color 0.3s, color 0.3s; } body.dark { background-color: #000000; color: #ffffff; } body.light { background-color: #f2f2f7; color: #1d1d1f; } .app-container { width: 100%; height: 100%; padding: 1.5vh; padding-top: 5vh; /* Space for phone status bar */ overflow-y: auto; } .header { text-align: center; padding: 2vh 0 3vh; } .header-title { font-size: 2vh; font-weight: 700; } .card { border-radius: 1.2vh; padding: 1.5vh; margin-bottom: 1vh; } body.light .card { background: #ffffff; } body.dark .card { background: rgba(255, 255, 255, 0.08); } .label { font-size: 0.9vh; color: #86868b; text-transform: uppercase; letter-spacing: 0.05vh; margin-bottom: 0.5vh; } .value { font-size: 1.4vh; font-weight: 600; } .button { width: 100%; padding: 1.2vh; border: none; border-radius: 1vh; font-size: 1.1vh; font-weight: 600; font-family: inherit; cursor: pointer; margin-top: 2vh; background: #007aff; color: #ffffff; } body.dark .button { background: #0a84ff; } </style></head><body class="light"> <div class="app-container"> <div class="header"> <div class="header-title">My Custom App</div> </div> <div class="card"> <div class="label">Player Name</div> <div class="value" id="playerName">Loading...</div> </div> <div class="card"> <div class="label">Phone Number</div> <div class="value" id="phoneNumber">Loading...</div> </div> <div class="card"> <div class="label">Job</div> <div class="value" id="job">Loading...</div> </div> <button class="button" onclick="showNotification()"> Send Test Notification </button> </div> <script> // Access the API from parent window (since we're in an iframe) const roadphone = window.parent.roadphone function init() { // Check if API is available if (!roadphone) { console.error('RoadPhone API not available') document.getElementById('playerName').textContent = 'API not available' return } // Load initial data document.getElementById('playerName').textContent = roadphone.getPlayerName() || 'Unknown' document.getElementById('phoneNumber').textContent = roadphone.getPhoneNumber() || 'Unknown' document.getElementById('job').textContent = roadphone.getJob() || 'Unemployed' // Apply initial theme applyTheme(roadphone.isDarkMode()) // Listen for dark mode changes roadphone.on('darkModeChanged', applyTheme) } function applyTheme(isDark) { document.body.classList.remove('dark', 'light') document.body.classList.add(isDark ? 'dark' : 'light') } function showNotification() { if (!roadphone) return roadphone.showNotification({ appTitle: 'My Custom App', title: 'Hello!', message: 'This is a test notification from your custom app.' }) } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init) } else { init() } </script></body></html>
Use vh units for sizing (like the rest of RoadPhone) to ensure your app scales correctly on different screen sizes.
To handle custom events from your app, register NUI callbacks in your Lua code:
Copy
-- server/custom_app.luaRegisterNUICallback('myCustomEvent', function(data, cb) local src = source print('Received custom event:', json.encode(data)) -- Do something with the data if data.action == 'doSomething' then -- Your logic here end cb('ok')end)