Build your first BooApp in under 10 minutes. This guide walks you through the entire process from setup to submission.
Prerequisites
A BooApp is a mini web application that runs inside the Peqaboo mobile app. Think of it like a WeChat Mini Program or a LINE LIFF app — lightweight, instantly available, no separate installation required.
┌──────────────────────────────────────┐
│ Peqaboo Mobile App (Flutter) │
│ ┌────────────────────────────────┐ │
│ │ Native Bridge (auto-injected) │ │
│ └──────────┬─────────────────────┘ │
│ ┌──────────▼─────────────────────┐ │
│ │ WebView → your hosted URL │ │
│ │ HTML + CSS + JavaScript │ │
│ │ window.peqaboo (auto-injected)│ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
You self-host the app on any URL (Vercel, Netlify, Cloudflare
Pages, your own server). Peqaboo loads it inside a WebView and
injects the `peqaboo` global at document_start.Build with AI
Create a new directory for your BooApp with the following structure:
mkdir my-booapp && cd my-booapp
# A BooApp is just a static web app. Any stack works
# (plain HTML, Vite, Next.js static export, SvelteKit, etc.).
touch index.html booapp.jsonEvery BooApp requires a booapp.json manifest file. This describes your app's metadata, permissions, and display configuration.
Drop a booapp.json at the root of your deploy (e.g. https://my-app.vercel.app/booapp.json). When you submit your URL in the developer console, Peqaboo fetches this file and pre-fills the submission form (name, icon, permissions, category). You can also fill the form manually — the manifest is optional but recommended.
{
"appId": "my-pet-tracker",
"name": "My Pet Tracker",
"shortDescription": "Track your pet's daily activities",
"icon": "https://my-pet-tracker.vercel.app/icon.png",
"entryUrl": "https://my-pet-tracker.vercel.app",
"category": "health",
"permissions": [
"auth.requireLogin",
"pet.list",
"pet.read",
"media.takePhoto",
"location.getCurrent"
],
"orientation": "portrait",
"themeColor": "#FFFFFF",
"author": {
"name": "Developer Name",
"email": "dev@example.com"
},
"privacy": {
"dataCollection": ["user_profile", "pet_data"],
"privacyPolicyUrl": "https://example.com/privacy"
}
}Add the BooApp SDK to your HTML file. You can use the CDN or download it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>My BooApp</title>
</head>
<body>
<div id="app">
<h1>Hello, Peqaboo!</h1>
<button id="greet-btn">Greet User</button>
</div>
<script type="module" src="/app.js"></script>
<!-- The `peqaboo` global is auto-injected by the runtime
at document_start. There is no script tag to add. -->
</body>
</html>TypeScript autocomplete (optional)
@decennium/booapp-sdk from npm — types only, zero runtime. Add a triple-slash reference and thepeqaboo global lights up with full autocomplete: /// <reference types="@decennium/booapp-sdk" />Use the SDK to access native features:
// Wait for the bridge to finish bootstrapping.
await peqaboo.ready();
if (peqaboo.isInApp) {
// Running inside the Peqaboo runtime — call native APIs.
const user = await peqaboo.requireLogin();
document.getElementById('app').innerHTML = `
<h1>Hello, ${user.userName}!</h1>
<p>Welcome to My BooApp</p>
`;
}
document.getElementById('greet-btn').addEventListener('click', async () => {
const pets = await peqaboo.pet.list();
if (pets.length) {
peqaboo.device.haptic('light');
await peqaboo.device.share({
title: 'Say hi',
text: `Hi ${pets[0].petName}!`,
});
}
});Create a zip archive containing all your files. The zip must include booapp.json and your entry point at the root level.
# Vercel
vercel --prod
# Netlify
netlify deploy --prod
# Cloudflare Pages, GitHub Pages, your own server,
# or any static host that serves HTTPS.Self-host — no upload
Go to the Developer Console, fill in your app details, upload the zip package, and submit for review. Our team will review your BooApp within 2-3 business days.
Open your index.html in a browser to test the layout. SDK calls will fail outside the Peqaboo app — use try/catch blocks and mock data for development.
// Drop this into a dev-only script. The runtime auto-injects
// `peqaboo` inside the Peqaboo app — in a plain browser tab,
// `window.peqaboo` is undefined, so we shim it.
if (!window.peqaboo) {
window.peqaboo = {
ready: () => Promise.resolve(),
isInApp: false,
platform: 'web',
lang: 'en',
uid: 'test-user',
appId: 'my-booapp',
requireLogin: () => Promise.resolve({
uid: 'test-user', userName: 'Test User', userEmail: 'test@example.com',
}),
pet: {
list: () => Promise.resolve([
{ petId: 'p1', petName: 'Buddy', petBreed: 'Golden Retriever' },
]),
read: (id) => Promise.resolve({ petId: id, petName: 'Buddy' }),
update: (id, patch) => Promise.resolve({ petId: id, ...patch }),
},
device: {
share: (opts) => { console.log('share', opts); return Promise.resolve(); },
haptic: () => {},
scanCode: () => Promise.resolve({ value: 'test-qr' }),
},
on: () => () => {},
};
}Bridge not ready
await peqaboo.ready() before calling any other SDK method. The peqaboo global is injected at document_start, but its native handlers attach asynchronously — calls before ready() will reject.Permission denied
pet.list, media.takePhoto). Declare every scope you call in your submission. Calls to undeclared scopes throw PermissionDeniedError.Manifest validation failed
/booapp.json for auto-import, common rejections are: missing appId, unknown permission scope, non-HTTPS entryUrl, or icon URL that returns non-200.A complete BooApp that displays pet profile cards with share functionality.
{
"appId": "pet-profile-viewer",
"name": "Pet Profile Viewer",
"shortDescription": "View your pet's profile with beautiful cards",
"icon": "https://my-app.vercel.app/icon.png",
"entryUrl": "https://my-app.vercel.app",
"category": "tool",
"permissions": [
"auth.requireLogin",
"pet.list",
"device.share",
"device.haptic"
],
"orientation": "portrait",
"themeColor": "#F8F9FA"
}