Build mini apps for radex.ma
Mini apps are small web apps (HTML, CSS, JavaScript) that open instantly inside the radex.ma app, with no install. You build them here, test them in a phone simulator, pass a security review, and publish a signed package that every Radex user can open.
Quick start
- Describe itOpen the studio and tell the AI designer what you want, in Darija, French or English. Or start from the example (a spa booking app) and edit the code.
- Try itThe simulator on the right runs your app with the real Radex SDK. Switch light/dark, tap through every screen, watch the console.
- PublishPress Publish. The security audit and an AI review run; if they pass, radex.ma signs your package and it appears in the Apps tab.
The AI designer
Write what you want the way you'd explain it to a person: who it's for, the screens, the data. Then keep chatting to change it ("make the buttons bigger", "add a favorites tab", "zid search b smiya").
- It writes
manifest.json, the pages and realistic Moroccan sample data indata/*.json. - Everything it writes goes through the same security audit as your own code; blocking problems are fixed automatically once.
- It runs on Cloudflare Workers AI, with a daily free limit shared by everyone. When the limit is reached you can still edit and simulate by hand.
Project files
| File | What it is |
|---|---|
manifest.json | Name, icon, version and exactly what the app may use. Required. |
index.html | The first page (or whatever entry says). Required. |
*.js, *.css, other *.html | Your code. Keep it readable: no minified or obfuscated code. |
data/<name>.json | A JSON array your app reads with radex.query("<name>"). Declare it in collections. |
| Images, fonts | svg png jpg webp gif woff2. Everything must be inside the package. |
Every page includes the SDK, which the phone provides (never ship your own copy):
<link rel="stylesheet" href="/sdk/radex-ui.css">
<script src="/sdk/radex.js"></script>
manifest.json
{
"id": "hammam_spa", // a-z 0-9 _, 3-32 chars, can't contain "radex"
"name": "حمام و مساج", // up to 30 characters
"version": "1.0.0", // bump it for every update
"icon": "spa", // one of the icon names below
"color": "0xFFB5651D", // tile color in the Apps tab
"description": "One line people see in the Apps tab",
"entry": "index.html",
"permissions": ["payment", "storage"], // only what you use: location, payment, storage
"collections": ["centers"], // data/centers.json
"allowedDomains": [] // network hosts; empty = works offline
}
Icons: taxi, food, channels, phone, car, moto, clothes, home, laptop, pets, bag, cash, storage, bolt, key, pin, star, check, search, error, heart, time, cart, person, flag, plane, train, city, cafe, chat, shield, apps, spa, event
Network: any host in allowedDomains is shown to people before they open your app, and the phone blocks every other host. Use HTTPS hosts only, at most 5.
SDK reference
Every call returns a Promise and is checked by the phone against your manifest. A refused call rejects with permission_denied:<what>.
| Call | Returns | Needs |
|---|---|---|
radex.query(name, params) | The array in data/name.json | collections |
radex.storage.get(key) / .set(key, value) | Your app's own storage on the phone (strings, 64 KB each) | storage |
radex.location() | {lat, lng, label} | location |
radex.pay(amountMad, description) | {ok, method}, or rejects if the person cancels | payment |
radex.toast(text) | Shows a short message for 1.5 s | — |
radex.appInfo() | {id, name} | — |
radex.icon(name, size) | Inline SVG string (current text color) | — |
radex.esc(value) | HTML-escaped text. Use it for every value you put in innerHTML. | — |
radex.ui.skeleton(el, n) | Loading placeholders | — |
radex.ui.error(el, message, retry) | Error state with a retry button | — |
radex.ui.sheet(html, onClose) | Bottom sheet, returns {el, close} | — |
const E = radex.esc;
radex.ui.skeleton(list, 4);
const items = await radex.query("items");
list.innerHTML = items.map((x) => `<div class="cell">${E(x.name)}</div>`).join("");
try {
await radex.pay(total, "Order · 3 items"); // radex.ma shows its own confirmation sheet
radex.toast("Order confirmed");
} catch { radex.toast("Payment cancelled"); }
UI kit
radex-ui.css matches the radex.ma app, in light and dark, right-to-left first. Set your color with :root { --accent: #B5651D; --accent-soft: rgba(181,101,29,.12) }.
| Group | Classes |
|---|---|
| Layout | page page--bar row row--between col grow gap-8 mt-8 |
| Text | t-22 t-17 t-15 t-14 t-12 bold muted faint accent ellipsis section-title price |
| Surfaces | card card--pad card--tap cells cell cell--tap cell__icon cell__body cell__title cell__desc |
| Controls | btn btn--primary btn--secondary btn--ghost btn--danger btn--block btn--sm chips chip options option stepper search |
| Feedback | badge notice state state__icon state__title skeleton spinner |
| Other | avatar cover stars steps step bottom-bar divider fade-in |
| Theme tokens | --bg --surface --surface-2 --ink --ink-2 --ink-3 --line --accent --danger |
Design rules
- Darija first:
<html lang="ar" dir="rtl">, logical CSS (margin-inline,inset-inline-start), latin codes and @handles in<bdi>. - One clear primary action per screen; touch targets at least 44 px.
- Show loading in place (skeletons), never a blocking spinner; every error offers a retry.
- Multiple screens with hash routes (
#/details/3) so back works. - No emoji: use
radex.icon(). Respect dark mode with the theme tokens.
The simulator
It runs the phone's exact SDK files and the same rules as the phone: undeclared permissions are refused, network is limited to your domains, payments open the radex.ma sheet. The console shows every SDK call and everything that was blocked; fix those before publishing.
Security rules
Every publish runs an automatic audit, then an AI review of what the app does. Any blocking finding stops publishing, with the file and line. The phone enforces the same rules again at runtime.
| Blocked | Why |
|---|---|
eval, new Function, string timers, obfuscated code | Code must be the code that was reviewed. |
Remote scripts, import() from the web | Everything that runs must be inside the signed package. |
Iframes, window.open, WebSocket, WebRTC, sendBeacon, workers, cookies | They can move data around the network rules. |
| Password, card, bank or verification-code fields | People sign in with radex.ma and pay through radex.pay(). |
URLs not in allowedDomains | Undeclared network access. |
| Names or ids with "radex" or "official", reserved ids | Impersonation. |
| Scams, data harvesting, misleading payments (AI review) | Fake prizes, asking for ID cards, hidden charges. |
Publishing and updates
- Ownership: the first developer key to publish an id owns it. Others get
id_taken. - Updates: bump
version(1.0.0 → 1.0.1) and publish again. Phones pick it up from the catalog. - Size: up to 1.5 MB compressed, 6 MB of files, 200 files. Most apps are a few KB.
- Limits: AI designer and publishing have daily limits per visitor to keep the platform free.
Command line
Prefer your own editor? Keep your files in a folder and publish with Node 20+:
# one-time: your developer key (keep it safe; it owns your app ids)
node publish.mjs https://play.radex.ma ~/.radex-dev-key.pem ./my-app
The script (in the radex.ma repository, server/play/scripts/publish.mjs) signs radex-publish\n<id>\n<version>\n<sha256 of your files> with your Ed25519 key and calls POST /api/publish.
Package format (.rdx)
"RDX1" | version 1 | flags 0 | header length (2 bytes)
header JSON {id, version, name, size, files, sha256, dev, devSig, kid, ts, audit}
signature Ed25519 by the radex.ma platform key, over "RDX1" ‖ header
payload deflate-raw of { u16 path length, path, u32 size, bytes } for each file
API: GET /api/catalog, GET /api/apps/<id>/<version>.rdx, GET /api/platform (public keys).
What happens on the phone
- The phone downloads the package, checks the radex.ma signature and every byte, and keeps it compressed. It checks again every time the app opens.
- Your app runs on its own private origin: its storage is never shared with radex.ma or other apps.
- A strict content policy and runtime guards apply to every page; links can't leave your app.
- People can see your permissions and network hosts in the ⋯ menu at any time.
FAQ
Is it free?
Yes, during the beta.
Can my app call my own server?
Yes: add its hostname to allowedDomains. People will see it, and the phone blocks every other host.
Can I use React or Vue?
Yes, if the built files are in the package and readable. Small vanilla JavaScript apps open fastest.
I lost my developer key.
The key owns your app id, so updates need it. Publish under a new id, or contact radex.ma to recover the old one.