4.0 KiB
Write a plugin for Frame
There are two paths. Choose the one that matches your feature.
Frame Control extension
Use a Frame Control extension for a panel, status view, or guide that does not
need to change the headset. Put extension.json and index.html in one folder
under ~/.local/share/frame-control/extensions. The opt-in headset panel lists
the folder and opens it in a sandboxed iframe. The version 1 bridge has only
capabilities and read-only status. It cannot call Steam, change settings,
or run privileged code. See the extension API and its example.
Decky plugin
Use a Decky plugin only if you need Decky's backend or Gamepad UI integration.
Build an official-format ZIP with one top-level folder, plugin.json, and a
frontend bundle in dist/index.js. A Python main.py backend is optional.
Test on ARM64. If you ship an ELF helper, build it for AArch64 (ELF machine
183). Avoid root, sudo, Steam Deck TDP controls, fixed x86 binaries, and
paths under /etc. Frame Control's managed loader is a normal user service;
it blocks root plugins and x86 archives. Keep data under your plugin folder or
the user's home directory, and provide a removal path.
The current Frame SteamVR shell does not expose Decky's Steam Deck Quick Access
Menu. A backend Loaded log only proves backend startup. Test any frontend
page, setting, notification, route, and controller input while wearing a Frame
before describing it as working in VR. Until a QAM bridge is verified, a Frame
Control extension is the simplest route for a self-contained read-only VR panel.
Frame Control now has an experimental Framey panel host for API v2 Decky frontend bundles. It renders common settings controls in the separate SteamVR dashboard panel, but Steam library and store page patches remain unavailable. Wearer-visible use is not yet verified.
To test safely, use a separate Frame Control managed Decky install, verify the
ZIP digest from the official store, install one plugin at a time, and inspect
journalctl --user -u frame-control-decky.service. Remove only your own
plugin folder when you finish. See the current compatibility table.
Framey API v2 compatibility
Framey hosts a plugin's frontend in an isolated iframe in the Frame Control SteamVR panel. The table describes the current implementation and its tests; none of these frontend controls has passed a worn-headset check yet.
| Decky API or UI feature | Framey behavior | Verified in |
|---|---|---|
definePlugin with content, titleView |
Renders a React panel and optional title | Local browser with ProtonDB Badges; host unit test |
PanelSection, rows, buttons, toggles, dropdowns |
Local touch-sized controls | Local browser and host unit test |
Optional settings, frameyQuickActions |
Separate settings tab; up to eight actions with result text | Host unit test; these fields are Framey additions |
serverAPI.call / callable |
Calls only that plugin's Decky Python backend via the headset service | Fake Frame e2e for get/set setting; host unit test |
toaster.toast |
Visible toast inside the plugin panel; dismiss and optional onClick |
Host unit test; no Steam system notification |
routerHook.addRoute / removeRoute, Navigation.Navigate / NavigateBack |
Local routes inside this plugin panel only | Host unit test |
routerHook.addPatch, global components |
No-op; cannot patch Steam routes | Not applicable |
| Steam library/store patches, CEF tab injection | Unavailable; no matching target in the isolated panel | Frame CEF inspection |
fetchNoCors, Decky event listeners, file picker, root calls |
Unavailable | Not supported by Framey |
Keep routes local to the panel and give every operation a visible result. A
plugin that calls Navigation.Navigate('/decky/store') will not open the
Frame Control store; only paths registered by that same plugin can navigate.
Toasts stay inside the panel, so keep essential errors in the plugin UI too.