JavaScript Bridge Guide
The JavaScript bridge enables bidirectional communication between your Unity C# code and JavaScript running inside the webview.
Use cases:
- Calling C# functions from web pages
- Pushing data and events from Unity to web pages
- Building hybrid apps with web-based UI and native Unity rendering
- Embedding interactive web dashboards inside your game
Enabling the Bridge
In the Inspector, enable Bridge Enabled on the LightweightBrowser component. This:
- Wraps the native webview in a
BridgedWebViewdecorator - Creates a
BridgeEventBusfor message pub/sub - Creates a
JavaScriptBridgefor structured communication - Auto-injects the
window.unityBridgeJS API on every page load
Architecture Overview
┌────────────────────┐ ┌──────────────────────┐
│ Unity C# │ │ Web Page (JS) │
│ │ │ │
│ JavaScriptBridge │◄────────►│ window.unityBridge │
│ BridgeEventBus │ │ │
│ │ JSON │ │
│ Your MonoBehaviour│ envelope │ Your JavaScript │
└────────────────────┘ └──────────────────────┘
All messages use a single JSON envelope format:
{
"id": "uuid",
"type": "command|event|call|response|error",
"name": "kebab-case-name",
"payload": "json-string-or-null",
"callbackId": "uuid-or-null"
}
JavaScript API (window.unityBridge)
The bridge is automatically injected into every page. Available after DOMContentLoaded.
Send an Event (JS → C#)
Fire-and-forget notification from JavaScript to C#.
// Simple event
window.unityBridge.send('button-clicked');
// Event with data
window.unityBridge.send('form-submitted', {
name: 'John',
email: 'john@example.com'
});
Call a C# Handler (JS → C#, with response)
Request-response pattern. Returns a Promise.
// Call a registered C# handler and await the response
const result = await window.unityBridge.call('getPlayerStats');
console.log(result); // { health: 100, score: 42 }
// With parameters
const item = await window.unityBridge.call('getInventoryItem', { slot: 3 });
Listen for Events from C# (C# → JS)
// Subscribe
window.unityBridge.on('score-updated', (data) => {
document.getElementById('score').textContent = data.score;
});
// Unsubscribe
function myHandler(data) { /* ... */ }
window.unityBridge.on('health-changed', myHandler);
window.unityBridge.off('health-changed', myHandler);
Check Bridge Availability
if (window.unityBridge) {
window.unityBridge.send('app-ready');
} else {
console.log('Unity bridge not available — running in regular browser');
}
C# API
Register a Handler
Register a function that JavaScript can call with window.unityBridge.call().
using Hexora.LightweightBrowser;
public class GameBridge : MonoBehaviour
{
[SerializeField] private LightweightBrowser _browser;
void Start()
{
_browser.JavaScript.RegisterHandler("getPlayerStats", data =>
{
var stats = new PlayerStats { health = player.Health, score = player.Score };
return JsonUtility.ToJson(stats);
});
_browser.JavaScript.RegisterHandler("buyItem", data =>
{
var request = JsonUtility.FromJson<BuyRequest>(data);
bool success = shop.Purchase(request.itemId);
return JsonUtility.ToJson(new { success });
});
}
void OnDestroy()
{
_browser.JavaScript?.UnregisterHandler("getPlayerStats");
_browser.JavaScript?.UnregisterHandler("buyItem");
}
}
Push Events to JavaScript (C# → JS)
// Simple event
_browser.JavaScript.PushEvent("game-started");
// With data
_browser.JavaScript.PushEvent("score-updated",
JsonUtility.ToJson(new { score = 42 }));
Subscribe to Events from JavaScript (JS → C# via EventBus)
void Start()
{
// Subscribe with filter — only events from JavaScript
_sub = _browser.Bridge.Subscribe(
msg => HandleJSEvent(msg),
BridgeMessageFilter.Create()
.FromJavaScript()
.EventsOnly()
.Build()
);
// Subscribe to a specific named event with typed payload
_scoreSub = _browser.Bridge.Subscribe<ScorePayload>(
"submit-score",
payload => SaveScore(payload.score, payload.playerName)
);
}
void OnDestroy()
{
_sub?.Dispose();
_scoreSub?.Dispose();
}
Execute Raw JavaScript
// Fire and forget
_browser.JavaScript.ExecuteScript("document.title = 'Hello from Unity'");
// With result callback
_browser.JavaScript.ExecuteScript(
"document.querySelectorAll('a').length",
result => Debug.Log($"Found {result} links")
);
Complete Example: Score Dashboard
Unity Side (C#)
using UnityEngine;
using Hexora.LightweightBrowser;
using Hexora.LightweightBrowser.Bridge;
public class DashboardBridge : MonoBehaviour
{
[SerializeField] private LightweightBrowser _browser;
private int _score = 0;
void Start()
{
// JS can ask for current score
_browser.JavaScript.RegisterHandler("getScore", _ =>
JsonUtility.ToJson(new ScoreData { score = _score })
);
// Listen for JS events
_browser.Bridge.Subscribe(
msg =>
{
if (msg.Envelope.Name == "player-name-set")
{
var data = JsonUtility.FromJson<NameData>(msg.Envelope.Payload);
Debug.Log($"Player name: {data.name}");
}
},
BridgeMessageFilter.Create().FromJavaScript().EventsOnly().Build()
);
}
public void AddScore(int points)
{
_score += points;
_browser.JavaScript.PushEvent("score-updated",
JsonUtility.ToJson(new ScoreData { score = _score }));
}
[System.Serializable]
struct ScoreData { public int score; }
[System.Serializable]
struct NameData { public string name; }
}
Web Side (HTML/JS)
<!DOCTYPE html>
<html>
<head><title>Score Dashboard</title></head>
<body>
<h1>Score: <span id="score">0</span></h1>
<input id="name" placeholder="Enter your name" />
<button onclick="submitName()">Submit</button>
<script>
async function init() {
if (!window.unityBridge) return;
const data = await window.unityBridge.call('getScore');
document.getElementById('score').textContent = data.score;
}
if (window.unityBridge) {
window.unityBridge.on('score-updated', (data) => {
document.getElementById('score').textContent = data.score;
});
}
function submitName() {
const name = document.getElementById('name').value;
if (window.unityBridge) {
window.unityBridge.send('player-name-set', { name });
}
}
init();
</script>
</body>
</html>
Message Flow Diagram
JS: window.unityBridge.call('getScore')
│
▼
_postToNative(envelope) ← auto-detected channel
│
▼
NativeMessageReceiver.OnNativeCallback() ← MonoPInvokeCallback
│
▼
MessageDispatcher.DispatchFromNative() ← deserializes envelope
│
├─► BridgeEventBus.Publish() ← notifies all subscribers
│
└─► JavaScriptBridge.HandleCall() ← looks up handler
│
▼
handler("getScore") returns JSON ← your C# code
│
▼
SendToWebView(responseEnvelope) ← sends response
│
▼
EvaluateJavaScript("_onMessageFromUnity(...)")
│
▼
Promise resolves in JS ← caller gets result
Tips
- Bridge adds overhead — If you don’t need JS ↔ C# communication, leave Bridge Enabled unchecked.
- JSON payloads — Use
JsonUtility.ToJson()/JsonUtility.FromJson<T>()on the C# side. - Handler names — Use kebab-case:
get-player-stats,buy-item,submit-score. - Error handling — If a C# handler throws, the bridge sends an error envelope back to JS.
- Page reloads — The bridge script is re-injected on every
PageFinishedevent. Re-register JS listeners onDOMContentLoaded. - Debug overlay — Enable during development to see all bridge traffic. Toggle with F12.