Custom Widget
Build your own GKSPHONE home-screen widget — registration, sizes, and the iframe contract.
A widget is an HTML page from your own resource, rendered in an iframe on the phone's home screen.
Two starting points: the widget template on GitHub, or the custom-widget demo folder that ships in the download — copy it into resources, ensure it after gksphone, then add it in game from long-press home → + → Widgets.
Serve the page
Expose your files in fxmanifest.lua so the phone can load them:
files {
'ui/widget.html',
'ui/widget.css',
'ui/widget.js'
}The URL is then https://cfx-nui-<your-resource>/ui/widget.html.
Register it
exports["gksphone"]:AddCustomWidget({
id = "demo-widget",
widgetUrl = ("https://cfx-nui-%s/ui/widget.html"):format(GetCurrentResourceName()),
title = "Demo Widget",
description = "Example custom home widget",
icon = "sparkles",
size = "2x2",
show = true,
labelLangs = { en = "Demo Widget", tr = "Demo Widget", de = "Demo Widget" },
})Returns true on success, false if a required field is missing.
| Field | |
|---|---|
id | Required. Unique. Registering the same id again replaces the entry |
widgetUrl | Required. The iframe URL |
title | Gallery title. Falls back to name, then to id |
description | Gallery subtitle |
icon | Framework7 icon name for the gallery. Defaults to square_grid_2x2 |
appIcon | Image URL to use instead of the icon name. Also accepted as icons or iconUrl |
size | 1x1, 2x2, 4x2 or 4x4. Anything else silently becomes 2x2 |
show | Listed in the widget gallery. Defaults to true |
labelLangs | Title per language code |
resource | Owning resource, for auto-cleanup. Derived from widgetUrl when it is a cfx-nui URL |
Register at the right time
gksphone has to be running first, and your widget has to survive a phone restart:
local function register()
if GetResourceState("gksphone") ~= "started" then return false end
return exports["gksphone"]:AddCustomWidget({
id = "demo-widget",
widgetUrl = ("https://cfx-nui-%s/ui/widget.html"):format(GetCurrentResourceName()),
title = "Demo Widget",
}) == true
end
CreateThread(function()
for _ = 1, 60 do
if register() then return end
Wait(1000)
end
end)
AddEventHandler("onResourceStart", function(res)
if res == "gksphone" then Wait(500) register() end
end)Remove it
exports["gksphone"]:RemoveCustomWidget("demo-widget")Home-screen instances of that id are cleaned up too.
You rarely need to call this on shutdown. When a resource stops, the phone drops every widget whose widgetUrl points at that resource automatically.
Sizes
| Size | Footprint |
|---|---|
1x1 | Compact |
2x2 | Default |
4x2 | Full width, 2 rows |
4x4 | Full width, 4 rows |
Size is stored per home-screen instance when the player adds the widget.
The iframe contract
The phone posts two messages to your page:
window.addEventListener("message", (event) => {
const data = event.data
if (typeof data !== "object" || !data) return
if (data.type === "gksphone:widget:init") {
// { widgetId, size, editing } — sent once, when the iframe loads
}
if (data.type === "gksphone:widget:editing") {
// { editing } — sent when the player enters or leaves home edit mode
}
})While editing, the phone disables pointer events on the iframe, the same as built-in widgets. Use the editing flag if you want to show a hint instead of live content.
Minimal page
Fill the frame, keep the background transparent, and don't scroll:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<style>
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent; }
.widget {
width: 100%; height: 100%;
box-sizing: border-box;
border-radius: 22px;
padding: 14px;
color: #fff;
background: linear-gradient(155deg, #1a2744, #0d1526);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
</style>
</head>
<body>
<div class="widget"><strong>Demo Widget</strong><div id="meta"></div></div>
<script>
const meta = document.getElementById("meta")
window.addEventListener("message", ({ data }) => {
if (data?.type === "gksphone:widget:init") meta.textContent = data.size
})
</script>
</body>
</html>Static widgets
Config.CustomWidgets in gksphone/config/config.lua holds the registry, and you can seed it there. AddCustomWidget writes into the same list at runtime, which is the better option for a separate resource — it survives without editing the phone's own config.

