A plugin for Unreal Engine that implements the Model-View-ViewModel (MVVM) design pattern using the WebBrowser (Chromium) module as the View layer.
ChromiumView allows you to create game UI using web technologies (HTML, CSS, JavaScript/TypeScript) while maintaining the benefits of Unreal Engine's MVVM pattern. It acts as a bridge between:
- Model: Your game data (managed by Unreal Engine)
- ViewModel:
UMVVMViewModelBaseclasses (from the existing ModelViewViewModel plugin) - View: HTML/JavaScript web pages rendered via Chromium
- Web-based Views: Create UI using HTML, CSS, and JavaScript
- ViewModel Binding: Automatic synchronization between ViewModel properties and JavaScript
- Bidirectional Communication: Views can send events to ViewModels and receive property updates
- TypeScript Support: Full TypeScript definitions for type-safe development
- Flexible Sizing: Views can be full-screen, use a desired size, or auto-size
- Works in Editor and Packaged Builds: Views are loaded from the project's
Viewdirectory
- Enable the plugin in your project's
.uprojectfile:
{
"Plugins": [
{
"Name": "ChromiumView",
"Enabled": true
},
{
"Name": "ModelViewViewModel",
"Enabled": true
}
]
}- Create a
Viewdirectory in your project's root folder to store HTML files.
#pragma once
#include "MVVMViewModelBase.h"
#include "MyGameViewModel.generated.h"
UCLASS(BlueprintType)
class UMyGameViewModel : public UMVVMViewModelBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, FieldNotify, Category = "Game")
FString PlayerName;
UPROPERTY(BlueprintReadWrite, FieldNotify, Category = "Game")
int32 Score;
UPROPERTY(BlueprintReadWrite, FieldNotify, Category = "Game")
float Health;
UFUNCTION(BlueprintCallable, Category = "Game")
void OnPlayerAction(const FString& Action);
};Create View/game-hud.html in your project:
<!DOCTYPE html>
<html>
<head>
<title>Game HUD</title>
<style>
body { font-family: Arial; color: white; background: transparent; }
.score { font-size: 24px; }
</style>
</head>
<body>
<div class="hud">
<div class="player-name" id="player-name">Player</div>
<div class="score">Score: <span id="score">0</span></div>
<button id="action-btn">Take Action</button>
</div>
<script>
// Wait for ChromiumView to be available
if (window.ChromiumView) {
const cv = window.ChromiumView;
// Listen for ViewModel changes
cv.onFieldChanged("PlayerName", (name) => {
document.getElementById("player-name").textContent = name;
});
cv.onFieldChanged("Score", (score) => {
document.getElementById("score").textContent = score;
});
// Send events to ViewModel
document.getElementById("action-btn").onclick = () => {
cv.sendEvent("OnPlayerAction", { action: "attack" });
};
// Notify the View is ready
cv.notifyReady();
}
</script>
</body>
</html>Blueprint:
- Create a
ChromiumViewWidget - Call
LoadView("game-hud.html") - Create your ViewModel and call
BindViewModel - Add the widget to your viewport
C++:
// Create the widget
UChromiumViewWidget* ViewWidget = CreateWidget<UChromiumViewWidget>(GetWorld());
// Load the HTML view
ViewWidget->LoadView(TEXT("game-hud.html"));
// Create and bind the ViewModel
UMyGameViewModel* ViewModel = NewObject<UMyGameViewModel>();
ViewWidget->BindViewModel(ViewModel);
// Add to viewport
ViewWidget->AddToViewport();The plugin includes TypeScript definitions for type-safe View development.
cd Engine/Plugins/ChromiumView/TypeScript
npm install
npm run buildimport { createChromiumView, ChromiumViewSizeMode } from "@unreal/chromium-view";
const cv = createChromiumView();
// Type-safe field changes
cv.onFieldChanged<number>("Score", (score) => {
console.log("New score:", score);
});
// Type-safe events
interface PlayerAction {
action: string;
target?: string;
}
cv.sendEvent<PlayerAction>("OnPlayerAction", {
action: "attack",
target: "enemy_1"
});
cv.notifyReady();| Method | Description |
|---|---|
LoadView(HtmlPath) |
Load an HTML view from the View directory |
LoadViewWithConfig(Config) |
Load a view with configuration options |
BindViewModel(ViewModel) |
Bind a ViewModel to the View |
UnbindViewModel() |
Unbind the current ViewModel |
ExecuteJavascript(Script) |
Execute JavaScript in the View |
ReloadView() |
Reload the current View |
SetSizeMode(Mode) |
Set the size mode of the View |
| Method | Description |
|---|---|
onFieldChanged(fieldName, callback) |
Listen for ViewModel property changes |
offFieldChanged(fieldName, callback) |
Remove a field change listener |
sendEvent(eventName, payload) |
Send an event to the ViewModel |
getViewModelState() |
Get the current ViewModel state |
setDesiredSize(width, height, mode) |
Set the desired size of the View |
onReady(callback) |
Register a callback for when ready |
notifyReady() |
Notify that the View is ready |
YourProject/
├── View/ # Your HTML views go here
│ ├── main-menu.html
│ ├── game-hud.html
│ └── assets/
│ ├── styles.css
│ └── app.js
└── Content/
└── ...
| Property | Type | Default | Description |
|---|---|---|---|
HtmlPath |
FString | "" | Relative path to HTML file |
InitialSizeMode |
EChromiumViewSizeMode | Auto | Initial size mode |
bSupportsTransparency |
bool | true | Enable transparency |
BackgroundColor |
FColor | White | Background color |
FrameRate |
int32 | 60 | Browser frame rate |
| Mode | Description |
|---|---|
FullScreen |
View fills the entire parent |
DesiredSize |
View uses dimensions set by setDesiredSize() |
Auto |
Parent widget controls size |
- Always call
notifyReady()after your View has finished initializing - Use TypeScript for better type safety and IDE support
- Keep Views lightweight - complex logic should be in ViewModels
- Handle initial state - call
getViewModelState()when loading - Clean up listeners when Views are destroyed
- Views are loaded from files, not packed in UFS
- JavaScript execution is async - use callbacks/promises appropriately
- Debug tools may be limited in packaged builds
- ModelViewViewModel plugin (enabled automatically)
- WebBrowser module (part of Unreal Engine)
See LICENSE.md file for details.
Most users will use the NRSL; the AGPL may be more restrictive for most common use cases. Both licenses require attribution.
Both require retaining the copyright notice: © 2026 Incanta LLC