A technical look at how Owl20 bridges D&D Beyond dice rolls into Owlbear Rodeo using browser extension APIs, cross-origin messaging, and the Owlbear Rodeo extension system. Written by the developers who built it — DM UberDragon and DM Sparks.
D&D Beyond and Owlbear Rodeo run on completely different domains: dndbeyond.com and owlbear.rodeo. Browser security — specifically the same-origin policy — prevents JavaScript in one domain from directly reading data from or sending data to a different domain.
Beyond20 already solves one part of this problem: it reads dice roll data from D&D Beyond's character sheets and makes it available to VTT platforms. But Beyond20 can't natively communicate with Owlbear Rodeo's extension system, which uses an iframe-based architecture.
Owl20's job is to be the intermediary: receive data from Beyond20 on the Owlbear Rodeo page, and forward it securely across the iframe boundary into the Owl20 OBR extension running inside Owlbear Rodeo.
Owl20 has two components that work together:
A Manifest V3 browser extension installed by players (and DMs if they roll). Runs as a content script on owlbear.rodeo pages. Receives Beyond20 events and forwards them into the OBR extension iframe.
An Owlbear Rodeo extension installed by the DM. Runs inside an iframe embedded in the Owlbear Rodeo room. Receives the forwarded roll data and renders it for all participants.
Beyond20_Roll CustomEvent on the Owlbear Rodeo page (because Owlbear Rodeo is in Beyond20's custom domains list)When Beyond20 is configured to load on Owlbear Rodeo pages (via the custom domain setting), it dispatches a CustomEvent named Beyond20_Roll on the page's document object whenever a player makes a roll.
This event's detail property contains the full roll payload: the roll type, dice results, character name, ability name, modifiers, and any other context Beyond20 collected from the character sheet.
Owl20's content script registers a listener for this specific event type:
document.addEventListener("Beyond20_Roll", function(event) {
// event.detail contains the full roll data from Beyond20
forwardRollToOBRExtension(event.detail);
});
This is the standard mechanism Beyond20 uses to communicate with third-party integrations. The requirement to add https://www.owlbear.rodeo/* to Beyond20's custom domains list tells Beyond20 to inject itself — and fire these events — on Owlbear Rodeo pages.
Owlbear Rodeo loads extensions inside iframes embedded in the main room page. The challenge is that these iframes are injected dynamically — they don't exist in the initial HTML, they're added by Owlbear Rodeo's JavaScript after the page loads.
Owl20 uses a MutationObserver to watch the DOM for the appearance of the Owl20 OBR extension iframe:
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (isOwl20OBRFrame(node)) {
targetFrame = node;
observer.disconnect(); // stop watching once found
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
Once the iframe is detected, Owl20 stores a reference to it and uses that as the target for all subsequent postMessage calls. The MutationObserver also handles cases where the iframe is removed and re-added (e.g., if the DM disables and re-enables the extension mid-session) by re-establishing the reference automatically.
The core challenge of the entire system is that the Owl20 OBR extension runs in an iframe from a different origin than the Owlbear Rodeo page. Standard DOM access across cross-origin iframes is blocked by the browser's same-origin policy.
The postMessage API is the browser's approved mechanism for secure cross-origin communication between a page and an iframe. Owl20 uses it to forward the roll data:
function forwardRollToOBRExtension(rollData) {
if (targetFrame && targetFrame.contentWindow) {
targetFrame.contentWindow.postMessage(
{ type: "Beyond20_Roll", data: rollData },
"https://owl20.friendlymimic.com" // explicit target origin
);
}
}
The explicit target origin (https://owl20.friendlymimic.com) is an important security detail. By specifying the exact origin the OBR extension runs on rather than using *, Owl20 ensures the message can only be received by the legitimate Owl20 OBR extension — not by any other iframes that might be present on the page.
On the OBR extension side, the incoming message is validated by checking event.origin before processing it, providing defence-in-depth against cross-origin message spoofing.
Once the postMessage arrives inside the Owl20 OBR extension iframe, the receiving side takes over. This is the component built by DM Sparks using the official Owlbear Rodeo extension SDK.
The OBR extension receives the roll data and does three things in quick succession:
Each player's Owl20 panel receives the broadcast and renders the roll using the HTML that Beyond20 originally generated — the same styled roll cards Beyond20 shows in its own UI, including dice results, modifiers, and roll type labels. On top of that, Owl20 adds:
The panel respects Owlbear Rodeo's light and dark mode settings and updates automatically if the DM switches themes mid-session.
The OBR broadcast API routes messages peer-to-peer within the active room session — no external server, no database, no account required. When the session ends, the roll history is gone.
Owl20 was designed with a minimal footprint and no unnecessary data handling:
Owl20 does not log, store, or transmit any data to external servers. Roll data flows directly from the Beyond20 event on the page to the OBR extension iframe in memory — it never leaves the browser or touches any server under our control.
Owl20 requests only the permissions required to function:
Both the postMessage send and receive use explicit origin checks. The browser extension sends only to the known OBR extension origin, and the OBR extension rejects any messages not originating from the expected Owlbear Rodeo domain.
The complete source code for both components is available on GitHub. Anyone can audit exactly what the extensions do:
Owl20 is built on Manifest V3 (MV3), the current browser extension standard required by Chrome and Edge since 2023. This was a deliberate choice, not just a requirement:
Many extensions use background service workers for persistent processing. Owl20's design avoids this entirely. All the extension's logic — the CustomEvent listener, the MutationObserver, and the postMessage forwarder — runs as a content script injected directly into the Owlbear Rodeo page.
This means:
The same MV3 extension package works on Chrome and Edge without modification. Firefox uses its own variant of MV3 with some differences in the declarativeNetRequest API, but Owl20 doesn't use that API — so the Firefox build is functionally identical to the Chrome/Edge build with only minor manifest adjustments.
Chrome deprecated MV2 support in 2023 and began enforcing the deprecation for Chrome Web Store extensions in 2024. Building on MV3 from the start means Owl20 will continue to work without requiring a major architectural rewrite as MV2 support is fully removed across browsers.
https://www.owlbear.rodeo/* in its custom domains list.This page was written by DM UberDragon and DM Sparks — the developers of Owl20. DM UberDragon built the browser extension side with 25+ years of software engineering experience spanning full-stack web development, browser extension APIs (MV2 and MV3), and cross-origin communication architectures. DM Sparks built the Owlbear Rodeo integration that receives and displays the dice rolls.
The extension was built to solve a real problem: running D&D campaigns in Owlbear Rodeo without losing the convenience of D&D Beyond's digital character sheets and Beyond20's dice rolling. Every design decision described here — the MutationObserver approach, the explicit-origin postMessage, the content-script-only architecture — came from real experience debugging cross-origin messaging in browser extension environments.