API Reference - PrismaUI_F4
Every method has its own detailed page under api/ - signature, parameters, return value,
threading, gotchas, and an example where one adds value. This page is the index and the
cross-cutting concepts that apply to more than one method.
Using an AI assistant? prisma-mcp gives it live tools that cover this entire
page - looking up any method, searching by keyword, and reading the current header - so it doesn't
have to rely on you pasting these docs in manually.
Overview
The public API is declared entirely in PrismaUI_F4_API.h. Copy that single header into your
plugin's src/ folder. You do not link against PrismaUI_F4 at compile time; the connection is made
at runtime via GetProcAddress.
#include "PrismaUI_F4_API.h"
// On kGameDataReady:
auto* api = PRISMA_UI_API::RequestPluginAPI<PRISMA_UI_API::IVPrismaUI10>();
Types
PrismaView
typedef uint64_t PrismaView;
An opaque handle that identifies one HTML view. The value 0 means "no view" / invalid. Always
check IsValid before using a handle you haven't used recently, particularly
after a game reload.
ConsoleMessageLevel
enum class ConsoleMessageLevel : uint8_t {
Log = 0,
Warning,
Error,
Debug,
Info
};
Passed to ConsoleMessageCallback. Maps directly to the JavaScript console.* level.
Callback types
// Called once when the HTML document's DOM is fully parsed and ready.
typedef void (*OnDomReadyCallback)(PrismaView view);
// Called with the string result of a JS expression evaluated via Invoke().
typedef void (*JSCallback)(const char* result);
// Called when JS code calls the registered listener function on window.
typedef void (*JSListenerCallback)(const char* argument);
// Called for every console.log/warn/error line from JS.
typedef void (*ConsoleMessageCallback)(
PrismaView view,
ConsoleMessageLevel level,
const char* message
);
Threading: OnDomReadyCallback and JSListenerCallback are both invoked on the main game
thread. See Threading below.
Interface versions
| Type | Adds |
|---|---|
IVPrismaUI1 | All core view operations |
IVPrismaUI2 | RegisterConsoleCallback |
IVPrismaUI3 | RegisterTranslations |
IVPrismaUI4 | BindUIEvent (game-thread JS listener), EnumerateViews |
IVPrismaUI5 | GetViewSRV, SetViewOffscreen, BindViewToGeometry, BindViewToScreenTexture, UnbindViewFromGeometry (on-mesh rendering) |
IVPrismaUI6 | SuppressHUDWidget, SuppressVanillaMenu, CloseVanillaMenu |
IVPrismaUI7 | SuppressVanillaMenuIf, EnableActivateChoiceFilter, SuppressActivateChoicePerk |
IVPrismaUI8 | EnumerateViewsEx (owner-tagged), GetActivateChoiceLabel, TriggerActivateChoice, GetViewHealth, SetViewOffscreenSize |
IVPrismaUI9 | Controller/keyboard button-prompt API - IsUsingGamepad, GetControllerStyle/SetControllerStyle, NoteInputDevice, GetButtonPrompt, GetGamepadButtonName. Also SetViewOwnsEscape and SetViewOffscreenBackground - see the compatibility note below. |
IVPrismaUI10 | View roles + "is another panel in the way" - SetViewRole/GetViewRole, GetFocusedView, IsAnyPanelVisible |
Always request the highest version you need. If the installed PrismaUI_F4 is older than your
requested version, RequestPluginAPI returns nullptr - handle this gracefully.
Compatibility note on
IVPrismaUI9specifically:SetViewOwnsEscapeandSetViewOffscreenBackgroundwere added to this interface after V9 first shipped, rather than as a newIVPrismaUI10. The nullptr-on-mismatch guarantee above holds at whole-version granularity, not for methods added within an already-released version - if your plugin is compiled against a header that includes these two methods but the player has an olderPrismaUI_F4.dllthat predates them, calling either one is undefined behavior (the vtable slot doesn't exist on the older build), not a clean failure. If you use either method, document that your plugin requires a currentPrismaUI_F4.dllbuild.
auto* api = PRISMA_UI_API::RequestPluginAPI<PRISMA_UI_API::IVPrismaUI10>();
if (!api) {
logger::error("PrismaUI V10 not available - update PrismaUI_F4");
return;
}
RequestPluginAPI
template <typename T>
[[nodiscard]] inline T* RequestPluginAPI();
Locates PrismaUI_F4.dll in the current process via GetModuleHandleW, calls its exported
RequestPluginAPI function, and casts the result. Returns nullptr if:
- PrismaUI_F4 is not loaded
- The loaded version does not support the requested interface
Call timing: During or after F4SE::MessagingInterface::kGameDataReady. Do not call during
F4SEPlugin_Load or F4SEPlugin_Query; F4SE may not have loaded PrismaUI_F4 yet.
Threading
JSListenerCallback functions registered via RegisterJSListener, and BindUIEvent callbacks,
both run on the main game thread, not the CEF render thread - the framework wraps every one of
them in its own F4SE::GetTaskInterface()->AddTask(...) before your code ever runs.
RE::* singleton access, InteropCall/Invoke calls, and any other game-thread work are safe to
do directly inside either kind of callback - no manual AddTask needed.
api->RegisterJSListener(view, "requestGameData", [](const char* s) {
auto* player = RE::PlayerCharacter::GetSingleton(); // safe directly here
});
Prefer BindUIEvent (V4) for new code when you want the game-thread guarantee to be part of the
API's actual contract - but existing RegisterJSListener code already gets the same guarantee and
does not need to change.
IVPrismaUI1
CreateViewInvokeInteropCallRegisterJSListenerHasFocusFocusUnfocusShowHideIsHiddenGetScrollingPixelSizeSetScrollingPixelSizeIsValidDestroySetOrderGetOrderCreateInspectorViewSetInspectorVisibilityIsInspectorVisibleSetInspectorBoundsHasAnyActiveFocus
IVPrismaUI2
Extends IVPrismaUI1.
IVPrismaUI3
Extends IVPrismaUI2.
IVPrismaUI4
Extends IVPrismaUI3.
IVPrismaUI5
Extends IVPrismaUI4. On-mesh rendering: bind a view's live texture onto in-world geometry.
IVPrismaUI6
Extends IVPrismaUI5. Vanilla UI suppression.
IVPrismaUI7
Extends IVPrismaUI6. Conditional menu suppression, and the vanilla multi-activate choice filter.
IVPrismaUI8
Extends IVPrismaUI7.
IVPrismaUI9
Extends IVPrismaUI8. Controller/keyboard button-prompt API, so every Prisma plugin gets
device-aware button prompts for free, without re-implementing gamepad/keyboard tracking per plugin.
The framework tracks whether the player is currently on keyboard/mouse or gamepad, and turns "which
button is Activate?" into a prompt token you hand to a shared shell-side renderer that draws real
button art, styled Xbox or PlayStation. Fallout 4 reads every pad as XInput (Xbox) natively -
PlayStation is purely a display re-style of the same canonical button, not different engine
behavior.
IsUsingGamepadGetControllerStyle/SetControllerStyleNoteInputDeviceGetButtonPromptGetGamepadButtonNameSetViewOwnsEscapeSetViewOffscreenBackground
Known limitation
SuppressHUDWidget and EnableActivateChoiceFilter (V6/V7) only work on Old-Gen (1.10.163) right
now. The vtable addresses they hook are hardcoded and haven't been mapped for Next-Gen/AE yet, so
on those runtimes both calls just log a warning and do nothing instead of patching a guessed
address.
IVPrismaUI10
Extends IVPrismaUI9. View roles, plus a correct answer to "is another Prisma UI in the way right
now?"
EnumerateViews (V4) reports every registered view, including always-on HUD widgets. A plugin
that opens its own menu and wants to avoid stacking on top of another UI cannot use that list
directly - "some other view exists and isn't hidden" is true the moment any HUD mod is running, so
the check never lets the menu open. V10 fixes that: declare your view's role, then ask
IsAnyPanelVisible, which only counts focused views and views explicitly declared as interactive
panels.
Unlike the two methods appended to IVPrismaUI9 after it shipped (see the compatibility note above),
these are a proper new interface - RequestPluginAPI<IVPrismaUI10>() returns nullptr cleanly on an
older PrismaUI_F4.dll, so the whole-version guarantee applies.
Typical call sequence
F4SEPlugin_Load:
F4SE::Init(a_f4se)
messaging->RegisterListener(F4SEMessageHandler)
kGameDataReady:
RequestPluginAPI<IVPrismaUI10>() -> g_api
[register key handler / event sink]
kPostLoadGame / kNewGame:
g_api->CreateView("page.html", OnDomReady) -> g_view
g_api->RegisterConsoleCallback(g_view, ...)
g_api->RegisterTranslations(g_view, "MyPlugin_F4")
OnDomReady (main game thread):
g_api->RegisterJSListener(g_view, "fnName", callback)
g_api->Invoke(g_view, "init()")
Toggle (key / event):
if opening:
g_api->Show(g_view)
g_api->Focus(g_view, pauseGame, disableFocusMenu)
if closing:
g_api->Unfocus(g_view)
g_api->Hide(g_view)
JSListenerCallback (main game thread - RE:: access is safe directly here):
g_api->InteropCall(g_view, "result", data.c_str());
Papyrus Bridge API (window.prisma)
Overview
window.prisma is automatically injected by PrismaUI_F4 into every HTML view. It provides
read-only and write access to Papyrus globals and script properties without requiring C++ code in
your plugin.
Available methods:
await prisma.getGlobal(esp, formId)- Read aTESGlobalform valueawait prisma.getProperty(esp, formId, scriptName, propertyName)- Read anAutoproperty from a Papyrus scriptawait prisma.setGlobal(esp, formId, value)- Write aTESGlobalfloat valueawait prisma.setProperty(esp, formId, scriptName, propertyName, value)- Write a float, int, or bool property to the script instance attached to the form
In every method, formId is parsed as hexadecimal, and is the local form ID only - do not
include the 2-digit plugin/file-index byte. A form at 0x00801234 in your plugin is passed as
"801234" (or "1234" with leading zeros trimmed), not the full load-order-relative form ID.
Under the hood
Writing to properties looks up the active script instance(s) attached to the form's handle in the Papyrus VM, finds the backing variable by name, and updates it directly on the game thread.
Callbacks/events limitation
Updating a property variable directly from F4SE/C++ does not run any Papyrus VM instructions (such
as a custom property Set() block in Papyrus), so the Papyrus script is not automatically notified
of the change unless it polls the property value, or another event is triggered. For workflows that
require immediate script execution when a setting changes, write to a global or property and have
the Papyrus script poll it periodically (using RegisterForSingleUpdate), or use MCM actions.
Return values
Both read methods return Promises and handle errors gracefully:
- Returns a
numberon success (including0.0, which is a valid result) - Returns
nullif the form/plugin is not loaded, the form doesn't exist, or the script/property name doesn't match - Never throws - always guard with
if (val === null)
Example
// Read a global
const val = await prisma.getGlobal('MyMod.esp', '800');
if (val !== null) {
console.log('Global value:', val);
} else {
console.log('Form not found or plugin not loaded');
}
// Read a quest property (most reliable host for properties)
const propVal = await prisma.getProperty('MyMod.esp', '801', 'MyQuestScript', 'CurrentPhase');
if (propVal !== null && propVal !== undefined) {
console.log('Quest phase:', propVal);
}
Timing
window.prisma is available immediately - no wait needed. However, getProperty calls may return
null if the Papyrus VM is not yet ready (e.g. if called before kPostLoadGame). For best results,
call property reads after the game has finished loading.
Fallout 4 VR (IVPrismaUIVR1)
Preview, not released, not yet verified in a headset. See vr-extension.md before writing anything against it.
Fallout 4 VR is served by a separate provider DLL, PrismaUI_F4VR.dll, built against CommonLibF4VR.
It is not a mode of PrismaUI_F4.dll, because VR needs a different CommonLib and a different game
executable. The base API behaves identically there.
Spatial presentation is additive, requested separately so the base V1 to V10 vtable is untouched:
auto* vr = PRISMA_UI_VR_API::RequestPluginVRAPI<PRISMA_UI_VR_API::IVPrismaUIVR1>();
if (!vr) { /* flat Fallout 4, or an older provider */ }
| Method | Purpose |
|---|---|
GetSpatialCapabilities | What this provider actually supports. Check it rather than assuming. |
SubmitSpatialUpdate | Place a view: head-locked, world billboard, or oriented world quad. |
GetSpatialState | What the renderer last applied, including appliedSequence. |
SubmitSpatialPointerUpdate | World-space controller ray. Becomes ordinary mouse events for the page. |
CancelSpatialPointer | Release a held button and clear hover. |
GetSpatialPointerState | Hit distance, UV and pixel of the last applied ray. |
CreateViewWithOptions | Create a view with a network access policy attached. |
SetNetworkAccessPolicy / GetNetworkAccessPolicy | Per-view network restriction. Read the limitation note first. |
Full reference, worked examples and the known limitations: vr-extension.md.