For creators
Everything you need to build an R1 creation — the wiki, an emulator that runs your creation at the real device size, and a hardware reference.
Building with AI?
Copy these docs as context, or point your assistant at /devtools/llms.txt.
A creation is a web app (HTML/CSS/JS) that runs inside the Rabbit R1's Android WebView. RabbitOS launches your creation full-screen on the device's small portrait display. There is no native toolchain — if it runs in a browser, it can run on the R1, as long as it respects the device's size and hardware constraints.
The R1 exposes its hardware (scroll wheel, side button, camera, microphone, speaker, accelerometer) and its assistant (LLM, text-to-speech, web search) to your creation through a thin bridge of window events and global objects. The r1-create SDK wraps that bridge in an ergonomic API.
Tip: Use the Emulator tab to load your creation at the exact device size and fire hardware events at it before you ever flash it to a device.
Install the SDK:
npm install r1-create
Initialize device controls and listen for hardware:
import { deviceControls } from "r1-create";
deviceControls.init({
scrollWheelEnabled: true,
sideButtonEnabled: true,
keyboardFallback: true, // arrow keys / space on desktop for testing
});
deviceControls.on("scrollWheel", ({ direction }) => {
// direction is "up" or "down" — relative deltas, not absolute position
console.log("scroll", direction);
});
deviceControls.on("sideButton", () => {
console.log("PTT / side button pressed");
});
The SDK works on the desktop too (with the keyboard fallback), so you can develop in a normal browser and test device behavior in the Emulator tab here.
transform: rotate(90deg) — the device will not do it for you.up / down events. There is no absolute position; integrate the deltas into your own state.r1.camera.start({ facingMode }), capturePhoto(w, h), stop().startRecording() / stopRecording().r1.speaker.play(blob), playTone(freq, ms).Your creation talks to the assistant by posting messages to the host and receiving replies on a global callback.
PluginMessageHandler.postMessage(JSON.stringify(payload)) to ask the LLM, speak text, or search the web. Voice capture is toggled with CreationVoiceHandler.postMessage('start' | 'stop'), and closeWebView.postMessage('') exits the creation.window.onPluginMessage = (data) => { ... }. Assign this handler (or use the SDK's onMessage) to receive LLM answers, transcripts, and tool results.import { messaging } from "r1-create";
messaging.onMessage((data) => {
// { message, ... } from the assistant
});
messaging.sendMessage("Summarize today's weather");
Two namespaces, both auto Base64-encoded:
r1.storage.secure — encrypted storage (Android M+). Use for credentials and device IDs.r1.storage.plain — ordinary key/value storage.await r1.storage.secure.setItem("token", value);
const token = await r1.storage.secure.getItem("token");
The R1 is a low-power device (MediaTek MT6765, 4 GB RAM — see the Hardware tab). Treat it like a budget phone:
transform and opacity only; never animate layout-triggering properties during interaction.The Emulator tab loads your creation in an iframe at the exact device size and lets you fire hardware events at it. Two things can stop a creation from working in the emulator — here's how to get past each.
The emulator loads your creation in an <iframe>. If your creation's server sends X-Frame-Options: DENY/SAMEORIGIN or a CSP frame-ancestors directive that excludes this origin, the browser refuses to render it and you get a blank screen. Fixes:
X-Frame-Options (or set it to allow this origin) and set a CSP that permits it, e.g. Content-Security-Policy: frame-ancestors https://creations.boondit.site;. This is the cleanest fix and is safe — your creation already runs inside the R1's WebView, which frames it too.http://localhost:<port> while developing). Same-origin localhost also unlocks direct device-API injection (see below).X-Frame-Options by default, so they frame fine.Browser security stops the emulator from injecting the SDK globals into a cross-origin page. When your creation is served from another origin, the screen size is accurate but the scroll wheel, side button, and onPluginMessage won't fire — unless your creation opts in to the bridge.
Bypass: import the emulator bridge shim. Add this once, early in your creation (it's a no-op on a real device and outside the emulator), and the emulator can drive your creation fully even cross-origin:
// r1-emulator-bridge.js — enables full device-API simulation in the Boondit emulator
(function () {
if (window.parent === window) return; // only inside an emulator iframe
var EVENTS = ["scrollUp", "scrollDown", "sideClick", "longPressStart", "longPressEnd"];
window.addEventListener("message", function (e) {
var d = e.data;
if (!d || d.__r1emu !== true) return;
if (d.type === "event" && EVENTS.indexOf(d.event) !== -1) {
window.dispatchEvent(new CustomEvent(d.event, { detail: d.detail }));
} else if (d.type === "pluginMessage" && typeof window.onPluginMessage === "function") {
window.onPluginMessage(d.payload);
}
});
function relay(name) {
return { postMessage: function (msg) {
window.parent.postMessage({ __r1emu: true, type: "outbound", channel: name, msg: msg }, "*");
} };
}
window.PluginMessageHandler = relay("PluginMessageHandler");
window.CreationVoiceHandler = relay("CreationVoiceHandler");
window.closeWebView = relay("closeWebView");
})();
With the shim in place, the emulator relays its control-panel events (scroll, side button, PTT, mock LLM responses) to your creation, and your creation's outbound PluginMessageHandler / voice calls show up in the emulator console. Same-origin creations (localhost during dev, or anything served from this store) don't need the shim — the emulator injects the globals directly.
The full shim is always available in the Copy for AI blob and at
/devtools/llms.txt.
Open the Emulator tab to load your creation URL at 240 × 282 and dispatch scroll / side-button events at it. If you build with an AI coding assistant, use the Copy for AI button or fetch /devtools/llms.txt to give it everything on this page as context.