01 · Build
Create a static client
Bundle application code, fonts, images, audio, models, and data. Use relative paths for local files.
Documentation platform
Web documentation selected
Creator guide · Web selected
Use HTML, CSS, and JavaScript directly, or export a client-side project built with Three.js, Phaser, React, Svelte, Vue, Vite, or other browser tooling. The selector above updates setup, export, manifest, SDK, and testing guidance without leaving this page.
Currently reading
Web
Direct browser build · HTML, CSS, JavaScript, and frameworks
Your selection is reflected in the URL, saved on this device, and applied to every platform-specific section.
01 / Web setup
Selected: Web
This section changes with the platform selector. Shared packaging, publishing, page, and community guidance stays below it.
01 · Build
Bundle application code, fonts, images, audio, models, and data. Use relative paths for local files.
02 · Integrate
Load the browser SDK for readiness, analytics, scores, storage, seeded randomness, and managed AI.
03 · Package
Place index.html at the ZIP root and upload only the files a browser needs at runtime.
Good fit
This is the most direct path for interface prototypes, browser games, visualizations, interactive articles, and projects already built with JavaScript or a client-side framework.
Current boundary
Do not rebuild an existing Unity or Godot project as hand-written Web code just for Prototir. Select that engine above and follow its supported Web export profile.
Install and connect
Load the small browser SDK before your application module.
<script src="https://cdn.prototir.com/sdk/v0.2.0/prototir.js"></script>
<script type="module" src="./app.js"></script>Call ready only after the first meaningful interaction is possible.
Prototir.ready();
Prototir.event('scene_ready');Supported profile
Input and player behavior
Request pointer lock from a click or pointer action, react to pointerlockchange, and pause when lock is lost. Escape releases pointer lock and restores the cursor automatically.
const canvas = document.querySelector('canvas');
canvas.addEventListener('click', () => canvas.requestPointerLock());
document.addEventListener('pointerlockchange', () => {
const locked = document.pointerLockElement === canvas;
game.setPaused(!locked);
});
// Escape releases pointer lock and restores the cursor automatically..game-surface,
.game-surface * {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
.game-surface {
touch-action: none;
}Text selection and standard page gestures remain enabled by default. Apply this to the gameplay surface only; keep instructions, inputs, and readable app content selectable and scrollable. This styling does not require the Prototir SDK.
02 / Start
Prototir hosts finished client files, not a source repository, editor project, or development server. Every build path ends with a ZIP whose root contains an HTML entry and all files required at runtime.
Example · Web build output
my-prototype/
|-- index.html
|-- app.js
|-- styles.css
|-- assets/
| `-- cover.webp
`-- prototir.jsonWeb is selected. Changing the platform above updates this export, setup, manifest, SDK syntax, and testing guidance together.
03 / Build
A root-level prototir.json lets the upload pipeline validate the correct runtime
profile, entry, devices, permissions, thumbnail, and platform features. The Unity and Godot
SDKs generate it during export; Web creators can write it directly.
{
"entry": "index.html",
"thumbnail": "auto",
"devices": ["desktop", "mobile"],
"orientation": "any",
"runtime": { "engine": "web", "profile": "standard" },
"modules": ["three@0.170.0"],
"permissions": [],
"ai": { "mode": "disabled" }
}| field | accepted values | purpose |
|---|---|---|
runtime | web, unity, or godot · standard profile | Declares the engine and exact engine version so Prototir can validate the exported shape. |
entry | relative HTML path | Use a generated entry outside the root, which Prototir copies to the served root. |
thumbnail | auto, or a bundle-relative image path | auto captures the first available visual frame after upload,
including an intro or cutscene. A bundle-relative PNG, JPEG, or WebP travels with
the ZIP and prototir.json, bypasses runtime capture, and is the deterministic
choice when the representative scene appears later. Alternatively, upload a
Prototir-hosted custom image (up to 2 MB); it overrides the portable source until
you switch back in Studio. The last source you save wins. Use 1280×800 (16:10)
where possible. Cards center-crop it to 16:10 and 16:9. |
modules | Web builds · exact name@version specs | Injects a validated import map for browser-code projects. Unity and Godot dependencies must be included by their exporter. |
permissions | camera, microphone | Lets the player ask the visitor before granting device access. |
devices | array of desktop, mobile, xr | Declares every supported device target. Legacy desktop, mobile, and both strings remain accepted. |
xr | modes and WebXR features | Required when devices contains xr; controls explicit WebXR capability delegation. |
orientation | any, portrait, landscape | Shows the intended viewport orientation. |
ai | disabled, managed | Enables Prototir's provider-neutral managed gateway. API keys, provider names, and model ids do not belong in the manifest. |
The curated Web module catalog appears below the SDK section on this page.
Supported by every build path. Test keyboard, pointer, focus, audio, resize, and fullscreen.
Supported by every path when the experience has touch controls, responsive layout, and a tested orientation.
Use only a target guide that explicitly supports your WebXR stack. Declare xr devices, modes, and features before requesting a session.
04 / Build
Web, Unity, and Godot expose the same platform capabilities through APIs shaped for their runtime. The selector above is currently showing Web. Call Ready only after the first meaningful interaction is possible. Preview generation does not wait for this signal, so a cutscene should report readiness only when control reaches the visitor.
Prototir.ready();
Prototir.event('level_complete', { level: 2 });
Prototir.score(1200);
const random = Prototir.rng('daily-2026-08-24');| Capability | Kind | What it can do |
|---|---|---|
| Ready | function | Starts a real session after the prototype becomes interactive. |
| Event | function | Records a small, non-personal milestone. Its normalized name, sessions reached,
and total triggers appear in creator analytics. Use stable names made from
letters, numbers, _, -, ., or :. |
| Score | function | Reports the current numeric score. |
| Storage | member | Contains asynchronous get, set, and remove functions
for device-local strings scoped to the prototype and signed-in player. Each scope supports
up to 64 keys and 64 KiB of UTF-8 data per value. |
| Managed AI | member | Contains provider-neutral generate. It is available only when
managed AI is enabled. |
| Seeded RNG | function | Returns deterministic local randomness. Currently exposed by the browser SDK. |
Managed AI is a Pro-and-above feature, and it runs on your plan's daily allowance, never a visitor's. Creators and players do not supply provider keys or pick a vendor. Prototir selects the provider and model behind the tier you choose, automatically falling back to another provider if one is briefly unavailable, scans prompts and replies, and shows an AI-powered disclosure in the player shell. Prompt text leaves the device.
await Prototir.storage.set('difficulty', 'hard');
const difficulty = await Prototir.storage.get('difficulty');
await Prototir.storage.remove('difficulty');try {
const answer = await Prototir.ai.generate({
prompt: 'Give the player a short quest hook.',
maxTokens: 80
});
} catch (error) {
console.warn(error.code, error.message);
}prompt is required and limited to 32,000 characters.maxTokens is optional, must be positive, and is capped at 4,096 output tokens
or the remaining allowance.{ code, message }. Handle sign_in_required, quota_exceeded, team_fair_share_exceeded, prototype_allowance_exceeded, ai_rate_limited, ai_blocked, moderation errors, provider_error, and timeout.The rest is configured on the prototype's own page, not in code, under "AI (Prototir.ai)": mode, model tier (fast, cheap and low-latency, or quality, higher-capability and costlier), whether a visitor must sign in to use it, and an optional daily token cap per visitor on top of your own budget. Defaults (managed off, fast tier, sign-in required, your full daily budget per visitor) are chosen to be safe and cheap out of the box. Turning off sign-in lets anonymous visitors use AI too, metered per visitor by IP instead of by account. You still pay either way; there's just no account to individually rate-limit, so it's a frictionless-demo versus coarser-abuse-protection trade.
Fast and Quality each have their own included daily allowance, sized for what they actually cost to run, not one shared number. Once a day's allowance for the tier you're using is spent, AI keeps working out of any purchased AI credits (one-time top-ups you buy from the same panel). Once both are spent, managed AI pauses until the next daily reset, or more credits. A Team org's daily allowances and credits are shared across every prototype the org owns.
First action, onboarding completion, important tool use, level completion, creation/export, retry, and a clearly named abandonment point.
Use stable short names and small values. Never place names, email addresses, free-form messages, secrets, or other personal data in event payloads.
Web only / Curated modules
Add exact name@version specs to prototir.json. Prototir validates
them, injects the import map, and serves shared files from its CDN. Unity packages and Godot
addons belong in their engine projects instead.
Small Prototir-built scaffolds imported into Web code.
| module | size | needs | status | what it does |
|---|---|---|---|---|
prefab-audio@0.1.1 | 2KB | - | available | Game/app audio over raw Web Audio: load samples from your bundle, play with volume/rate/loop, zero-asset beeps. Replaces maintenance-mode audio shims. |
prefab-audio-features@0.1.1 | 2KB | mic | available | Audio-reactive features from a native AnalyserNode: rms, energy, spectral centroid + raw bins. Mic (with consent) or any AudioNode/MediaStream. |
prefab-sketch@0.1.1 | 1KB | - | available | The byte-light creative-coding loop: full-window 2D canvas, dpr scaling, resize, draw(ctx, {t, dt, mouse}) — p5 ergonomics at ~1KB. |
prefab-hand-controls@0.1.2 | 7.6MB | mediapipe-vision · camera | available | Hand landmarks → pointer/pinch events; camera + MediaPipe HandLandmarker + its model weights bundled in, no separate model pack needed. Reported pointer position is smoothed (configurable) to cut per-frame detector jitter without adding lag to pinch detection. |
prefab-voice-input@0.1.0 | - | model-whisper-tiny · mic | planned | Push-to-talk → local transcript events. |
prefab-i18n@0.1.1 | 3KB | - | available | Localized strings and voice-over from your own bundle: Intl plurals/number/date, per-locale lazy loading, audio fallback chain. |
prefab-input@0.2.0 | - | - | available | One input API for desktop and mobile: keyboard, or fully customizable virtual controls — any number of buttons/joysticks/d-pads with position, size, shape and short/long-press semantics; same axes/pressed()/onPress() reads either way. |
prefab-shader-canvas@0.1.1 | - | - | available | Full-screen fragment shader boilerplate: uniforms for time/mouse/resolution. |
prefab-map@0.1.6 | 3640.0MB on CDN, streamed | maplibre-gl | available | Embeddable vector map: markers, popups, GeoJSON overlays, light/dark/grayscale themes, click/fly-to/pan-and-zoom. Real OpenStreetMap-derived roads, water, and towns worldwide, up to zoom 10 anywhere on Earth - no external tile host or API key, no CSP change, mandatory OSM/Protomaps attribution. The basemap is served on demand over ranged CDN fetches, like a shared image or model asset, not downloaded in full by every prototype that uses it. |
Third-party browser libraries served from the Prototir CDN and cached across prototypes.
| module | size | needs | status | what it does |
|---|---|---|---|---|
three@0.185.1 | 404KB | - | available | 3D rendering — the workhorse for game/art prototypes. (r185 splits into module+core files, both platform-served; r170 stays available.) |
pixi@8.6.0 | 180KB | - | available | Fast 2D WebGL renderer. |
phaser@4.2.1 | 1.4MB | - | available | Batteries-included 2D game framework (scenes, tilemaps, arcade physics). (Phaser 4; the 3.87 API stays available as phaser@3.87.0.) |
p5@2.0.2 | 309KB | - | available | Creative coding (p5 2.x). For byte-light sketches see prefab-sketch. |
rapier3d@0.19.3 | 1.4MB | wasm | available | 3D physics (WASM). Pairs with three. (0.19 fetches its .wasm beside the module instead of embedding it; 0.14 stays available.) |
matter@0.20.0 | 26KB | - | available | Lightweight 2D physics (no WASM). |
tone@15.0.4 | 81KB | - | available | Web Audio synthesis & scheduling. |
lil-gui@0.20.0 | 14KB | - | available | Tiny tweak-panel for exposing prototype parameters. |
motion@12.42.2 | 46KB | - | available | Animation library (vanilla Motion One API). |
chart@4.4.7 | 69KB | - | available | Charts for dashboard/tool prototypes. |
d3@7.9.0 | 93KB | - | available | Data-driven documents for bespoke viz. |
maplibre-gl@6.0.0 | 138KB | - | available | Interactive vector maps (WebGL). The base for prefab-map; import it directly for custom map styling/layers beyond what the prefab exposes. |
simplex-noise@4.0.3 | 3KB | - | available | Procedural noise. |
onnxruntime-web@1.27.0 | 3.5MB | wasm | available | ONNX inference runtime (WASM + WebGPU) — the base for model packs. |
transformers@3.2.0 | 800KB | wasm | planned | Pipelines over ONNX: detection, segmentation, depth, ASR, embeddings. |
mediapipe-vision@0.10.18 | 9.8MB | camera · wasm | available | Hands / pose / face landmarks — realtime body-driven interaction. |
Weights and data for a runtime library, shared across compatible Web prototypes.
| module | size | needs | status | what it does |
|---|---|---|---|---|
model-rfdetr-nano@1.0.0 | 30.0MB | onnxruntime-web | planned | RF-DETR nano object detection weights — real-time DETR in the browser. (YOLOv8 excluded: AGPL.) |
model-whisper-tiny@1.0.0 | 40.0MB | transformers · mic | planned | Local speech-to-text — voice-controlled prototypes with zero API cost. |
model-depth-anything-small@1.0.0 | 50.0MB | transformers | planned | Monocular depth from the webcam — parallax/AR-ish effects. |
Platform features brokered by the SDK rather than import-map downloads.
| module | size | needs | status | what it does |
|---|---|---|---|---|
capability-ai@sdk | SDK | - | available | Provider-neutral managed AI generation through Prototir.ai. The sandbox has connect-src 'none'; requests hop through the shell to a metered, moderated gateway. Prototir owns credentials and routing, while the prototype owner's paid plan and AI credits fund usage; signed-in visitors never spend their own allowance. |
capability-storage@sdk | SDK | - | available | Per-prototype key-value persistence. Opaque-origin iframes have no reliable localStorage; the shell stores device-local values per prototype and isolates signed-in players on shared browsers. |
capability-permissions@sdk | SDK | - | available | Camera/microphone: declared in prototir.json, granted via iframe allow= after an explicit shell-level user consent prompt. |
05 / Build
A session is one play. Prototir counts it as real once it lasts at least three seconds and reports at least one event, and almost everything you care about is built on real sessions rather than on opens.
That threshold is also the reason a prototype can look unplayed while people are clearly playing it. A build that never calls Ready and never reports an event has no real sessions, so its analytics stay near zero, visitors cannot leave feedback on it, and it has nothing to rank with.
Starts the clock. Call it on the first frame the visitor can actually do something, not when loading finishes: a two minute cutscene before any input is not two minutes of play. Calling it twice does not restart anything.
Marks a milestone. One event is what turns an open into a real session, so report the first meaningful action early rather than only at the end of a level nobody reaches.
Reports a number. The best score of the session is the one kept, so a run that ends badly does not erase what the player achieved. Challenge leaderboards are built from scores on real sessions.
Names are lowercased and must be 1 to 64 characters of letters, numbers, _, -, . or :. Anything else is dropped rather than corrected, so Level Complete records nothing while level.complete records what you meant.
Pick names once and keep them. A name that changes between builds splits one behaviour across two rows and makes the before and after impossible to compare. Prototir keeps up to 50 distinct names per session; past that the events still count towards the total, but the per-name breakdown stops growing, which is what stops one runaway loop from becoming your entire analytics page.
Your prototype's analytics shows each event by name with two numbers: how many sessions reached it, and how many times it fired in total. The two together are the useful part. A step reached by few sessions is a wall; a step fired many times within few sessions is a retry loop. Duration buckets sit beside them as a rough drop-off curve.
A web build runs inside the shell, which watches it and reports for it. A download has no shell, so the SDK keeps its own count and sends one session for the play instead of a request per event. That session goes out when you flush it, and otherwise when the build next starts, so closing the game or being offline does not lose it.
A download also reports nothing until a tester has paired it, because until then there is nobody to attribute the play to. Running from the editor never reports: your own testing does not belong in your own numbers.
06 / Build
Not everything belongs in a browser. A prototype can offer downloadable builds for Windows, macOS, and Linux, either on its own or beside a playable web version.
The upload page offers a ZIP and a list of builds. What you actually add decides how the prototype is delivered: a ZIP alone plays in the browser, builds alone are download only, and both gives you both. Add one file per platform, each up to your plan's limit, and say which architecture it targets so nobody downloads an Intel build for an ARM Mac.
For a web prototype we take a screenshot of it running. A downloadable build never runs on our machines, so there is nothing to capture. Your cover is the only picture the prototype has, on its page and everywhere it is listed.
A web prototype runs inside our sandbox and we are accountable for containing it. A download runs on the visitor's own computer, where we have no such control, and the prototype page says so plainly rather than leaving people to assume otherwise.
We do not sign or notarize your build, and we do not broker anyone else doing it. Expect Windows SmartScreen and macOS Gatekeeper to warn people before opening an unsigned app, and expect the occasional antivirus false positive on engine builds. Signing is yours to arrange if you want those warnings gone.
prototir-build.json into the build, and Prototir records that id from the archive you upload. A running build reports
the same id when it pairs, so a mismatch tells you a tester is playing an older download than
the one on the page. That is all it does: both sides come from a file you control, so it is
not verification, not security, and not anti-cheat.Adding builds also changes how people find you. Discover asks two separate questions: what device someone is holding, and how they want a prototype to reach them. A build for Windows, macOS or Linux puts you in the second one, under How you play › Download, and again under the operating systems you actually ship. Drop a platform in a later release and you leave that filter, because it matches the build on offer now rather than one you used to have.
Already published? Adding a build later keeps what you have: the page shows the player with your downloads beside it, and removing the last build turns it back into a web prototype. Nothing here is a one-way door.
07 / Publish
Show what can be touched, clicked, typed, dragged, or controlled. Avoid an unexplained blank canvas or loader with no progress.
Fill the available player, react to resize and fullscreen changes, avoid fixed desktop-only dimensions, and honor the declared orientation.
Test keyboard, pointer, touch, focus, Escape, and visible controls using the APIs or input system of your selected runtime. Pointer lock must start from a visitor action and release cleanly.
Give visitors a restart path, useful empty states, and a clear response when an optional permission, storage call, or AI request is denied.
Compress exported assets, defer secondary content, cap rendering cost, and test the first load on a real phone and ordinary connection.
Use readable contrast, semantic or accessible controls, labels, keyboard access, reduced-motion handling, and alternatives to audio-only or color-only information.
The Web input, fullscreen, canvas, and sandbox checks are in the setup section above. Change the platform selector to replace them without opening another guide.
08 / Publish
09 / Publish
Only publish files you own or are allowed to distribute. Keep third-party license and attribution files in the bundle. If you enable source download, choose a license that actually grants the permissions you intend.
Visitors can download the source under your stated license. Platform-generated provenance is added to the downloaded manifest.
Declare the source slug or re-upload a Prototir download. The origin remains linked and conflicting lineage is rejected.
A template is a complete open-source starter prototype. Its downloads carry template provenance into later uploads.
10 / Publish
Anything a browser renders is delivered to that browser. An authorized visitor can inspect network requests and save JavaScript, models, textures, audio, video, WebAssembly, and data. Private visibility prevents anonymous access, but it cannot stop an invited viewer from capturing files they are allowed to run. This is equally true for direct Web code, Unity WebAssembly, and Godot Web exports.
The publisher can rewrite served .js files into compact, less-readable
code. This deters casual copying; it is not encryption or DRM. It does not transform
WebAssembly, engine data files, .mjs, inline code, source maps, models,
textures, or other assets. Unity and Godot already compile/package much of their
runtime output, but that output is still downloadable. The retained original ZIP
remains unchanged.
Keep production masters outside the ZIP. Export a runtime derivative: remove editor data and source maps, reduce geometry and texture resolution, use formats such as GLB with Draco or Meshopt and KTX2 where appropriate, and consider a visible or forensic watermark. Compression and renamed files add friction, but do not make client-rendered assets secret.
Rule of thumb: if disclosure would cause serious harm, do not include that file in a browser-delivered prototype. Use a reduced derivative, a watermark, or a server-side rendering approach instead.
11 / Publish
A replacement is an internal build of the same prototype. It keeps the stable URL, comments, and aggregate analytics, and does not count as another Free, Pro, or Team prototype. The current build stays live while the candidate is tested.
Keep the previous ZIP/build for instant rollback, or remove those files automatically only after the new build passes. Studio can also remove an inactive build later. Comment attribution remains, and active or event-pinned builds are protected. Publish a separate public release only when you want another listing, URL, and prototype slot.
12 / Grow
Turn on screenshot feedback and a tester can capture the moment, drop a pin on it and write a comment without leaving your prototype. It works the same whether they are playing here, on your own site, or in a downloaded build.
Prototir.review.enable({ project: 'my-prototype' }); Inside the Prototir player, Prototir adds Feedback to its own controls beside Restart and Fullscreen. Anywhere else the Prototir mark opens the same menu, so testers see one experience wherever they play.
Feedback is pinned to the captured image, because a seed and a timestamp do not reproduce a procedural scene. Add a line of context with the level, seed or build and you can find the moment again.
On Prototir it becomes an ordinary comment on your prototype, with the same moderation and wall controls. Elsewhere the tester saves a review file and sends it to you, which needs no account and works offline.
A native build shows a code and a QR; the tester approves it in a browser once per machine, and the comment they were writing posts as soon as they return. They can disconnect any build from their account settings.
Call review.capture() from a key, or review.compose({ context }) when your game notices its own failure,
and hand the tester a report already written.
Turning comments off for a prototype turns feedback off with it. In embeds the Prototir badge stays visible while feedback is on, because testers use it to reach the menu.
13 / Grow
A collection is a curated page that gathers prototypes under your own identity: a course showcase, a studio portfolio, a festival selection. It has no deadline and no winner, unlike a jam. You choose what belongs, in what order, with a note on each.
Collections start private so you can assemble one before anyone sees it. Unlisted shares it by link without putting it in discovery; public lists it on Prototir.
Collected work keeps its own page, author, comments and analytics. You can gather prototypes that are not yours, and the credit stays with whoever made them.
A private prototype in a public collection stays invisible to anyone who could not already open it. Curating cannot widen who sees a piece of work.
Removing a collection removes the page only. Every prototype it gathered stays where it is, untouched.
Upload a banner of at least 2400 by 900, plus a square mark. Prototir generates three fixed sizes from it and serves whichever fits the screen, so keep anything that matters within the middle 70 percent across and 50 percent down. The editor previews all three shapes with that safe area drawn on, so you can see what survives before saving.
Do not put your title in the image. The page draws it as real text over the art, which stays legible at every size and is readable by search engines and screen readers. The square mark covers the places the banner is not shown at all.
Jams and challenges take the same two pieces of art, at the same sizes, through the same editor, so this is worth learning once. What differs between the three is what the page is for, not how you dress it.
Create one from Dashboard › Collections. It lives
at /c/your-collection.
14 / Grow
A creation event with prototype entries. It can run online, in person, or as a hybrid, with a public venue and an optional HTTPS participation link where appropriate.
A score race on one of the host's live prototypes. The prototype reports scores through the SDK and real sessions form the leaderboard.
A jam and a challenge each take a header banner and a square mark, exactly as a collection does. Open the event and use Appearance; the sizes and the safe area are the ones described under collections above.
The page still says which of the two it is, in the header and in how it is laid out underneath: a jam is organized around entering and entries, a challenge around eligibility and the leaderboard. Art changes how a page looks, never what it is.