Web Launcher SDK
Publishing
Package your Fluffer app as an upload-ready zip: package format, requirements, testing the build, and how to submit it today.
When your app plays well in the browser, package it and send it to us.
#1. Check your manifest
Make sure manifest.json declares exactly the capabilities your app uses, and nothing more. Players see what your app asks for, and anything undeclared fails with PERMISSION_DENIED. See Manifest.
{
"manifestVersion": 1,
"minSdkVersion": "0.1.0",
"engine": "html5",
"entry": "index.html",
"orientation": "landscape",
"capabilities": ["device.vibrate"]
}
#2. Build and package
From your app's folder:
npm run package
# ✓ packaged my-game → my-game.zip (0.03 MB)
This builds your app, validates manifest.json, and writes <slug>.zip. The slug comes from your package.json name, or you can set it yourself with --slug=my-game.
#Package format
A Fluffer package is a zip file with:
manifest.jsonat the root of the zip;- your built app (the contents of
dist/) also at the root, not inside adist/folder; - the file named by
manifest.entry(usuallyindex.html) at the path it gives, relative to the zip root.
my-game.zip
manifest.json
index.html
assets/index-3f9a1c.js
assets/index-b2e04d.css
logo.png
If you build your package by hand rather than with npm run package, follow the same layout.
#Requirements
- Self-contained. Everything your app needs at runtime is in the zip: scripts, styles, images, audio, fonts.
- Relative paths. Reference assets with relative URLs (
./logo.png, not/logo.png). Your app is served from its own address (https://<your-app>.fluffer.app/), and relative paths keep working wherever it's hosted. The templates'base: './'Vite setting handles this for you. - A valid manifest. It must pass the checks on the Manifest page.
npm run packagerefuses to package an invalid one. - Works with no toy. Your app must start and be usable when no toy is connected (
game.deviceisnull). - Works as a guest. If you use
user.profileoraccount.identity, your app must keep working when the player declines. - Respects pause. Do per-frame work in
game.onTick(notsetInterval) so your app stops when Fluffer goes to the background. - Cleans up haptics. Stop continuous patterns (
heartbeat,idle,lowHealth,burn) when the moment ends. - Audio after a gesture. Start or resume audio in the player's first tap; web views block autoplay.
- Reasonably small. Typical HTML5 apps are 50-500 KB; Three.js apps start around 150 KB gzipped plus assets. If your bundle is several megabytes, compress your assets and load optional features with dynamic
import(). - Built for phones. Full-screen, touch-first, and comfortable at phone sizes in the orientation you declared.
#3. Test the built bundle
Serve the production build and play it end to end before you send it:
npx serve dist
The mock launcher starts automatically. Check that:
- there are no errors in the console;
- haptics fire where you expect (use the mock monitor from Getting started to watch them);
- audio plays after the first tap;
- the app still works with the toy disconnected.
#4. Send it to us
Email developers@fluffer.app with:
- your
<slug>.zip; - the app's title and a one- or two-sentence description for the catalog;
- a suggested category (for example arcade, puzzle or rhythm);
- your developer or studio name;
- anything we should know to review it, such as why you need each capability.
We'll get back to you about next steps.
#Updating your app
Run npm run package again and send us the new zip. Keep the same slug so it's treated as a new version of the same app rather than a different one.
#Unity apps
The manifest accepts "engine": "unity", but Unity WebGL apps follow a different packaging path from the zip described here. If you're building with Unity, contact us at developers@fluffer.app before you package.