The official Godot integration for prototypes hosted on Prototir. The addon connects a Godot Web export to Prototir lifecycle signals, analytics events, scores, persistent storage, and managed text generation. Its editor dock also checks project and export settings so unsupported builds are caught before upload.
- Godot 4.3 or newer
- GDScript and the Compatibility renderer
- Single-threaded Web export using the standard runtime profile
Download the addons/prototir directory from the
v0.2.0 release and copy it into your
project. Then:
- Enable Prototir SDK under Project Settings > Plugins.
- Open the Prototir dock.
- Apply safe fixes and resolve every blocking item.
- Export a non-debug Web build and ZIP the contents of the export directory.
The addon installs Prototir as an autoload. If you install source from main before the first
tag, pin the release tag once it becomes available.
Prototir.ready()
Prototir.event("level_complete", {"level": 2})
Prototir.score(1200)
var storage_request := Prototir.storage_get("difficulty")
var difficulty = await storage_request.completedManaged AI requests expose explicit success and failure signals:
var request := Prototir.ai_generate("Give the player a short quest hook.", 80)
request.completed.connect(func(text): print(text))
request.failed.connect(func(code, message): push_warning("%s: %s" % [code, message]))Call ready() after the first genuinely interactive frame. Event names are normalized to lowercase
and accept letters, numbers, _, ., :, and -. Keep payloads small and free of personal data.
The dock checks the Godot version, renderer, main scene, Web export preset, threads, GDExtension, PWA output, adaptive canvas resizing, initial focus, entry filename, and mobile texture compression. Blocking issues prevent a supported release; recommendations remain visible when a choice depends on the project. Safe fixes never enable mobile texture compression automatically because its size and quality tradeoff must be tested by the creator.
After export, the addon copies the project's root prototir.json beside index.html. If no source
manifest exists, it generates valid defaults from the project name and exact Godot version.
Run the structural validator outside Godot with:
node tools/export-validator.mjs /path/to/exportThe Prototir sandbox supplies a CSP-safe JavaScript interface. The addon retrieves that interface
with JavaScriptBridge.get_interface() and never executes inline JavaScript or requests
unsafe-eval. Editor and native play mode use in-memory storage and mock signals; managed AI
requires an explicit mock_ai_handler.
Call review_enable to give testers a floating feedback button in Web exports. They capture the
current view, drop a pin on that screenshot and write a comment.
func _ready() -> void:
Prototir.review_visibility_changed.connect(_on_review_visibility)
Prototir.review_enable("orbit-garden", "v1.4.0", "bottom-left")
func _on_review_visibility(open: bool) -> void:
get_tree().paused = openreview_enable(project, build, corner, launcher, theme) takes a stable project identifier (reviews exported from
another project are refused on import), a build recorded with the feedback, and a corner of
bottom-left (default), bottom-right, top-left, or top-right. review_disable() removes the
overlay. launcher is auto (Prototir draws the control on its own surfaces), watermark (always
show the Prototir mark and its menu) or host (draw nothing). theme is auto, light or dark.
Off Prototir the mark opens the same menu testers see in a web build - Screenshot & comment, Comments, Open on Prototir - so the experience does not change between web and native.
Connect review_visibility_changed and pause while the panel is open, otherwise the game keeps
consuming the input the tester is typing into their comment.
Screenshots come from the viewport after RenderingServer.frame_post_draw, so they match the
rendered frame. The export plugin bundles the browser runtime with the export, which is what lets
this work off Prototir; re-export with the addon enabled after upgrading.
A native export has no Prototir session, and providers like Google refuse to sign in inside an
embedded browser. So the export sends the tester to a real one: it shows a short code and a QR, the
tester approves at prototir.com/link on their desktop or phone, and the build receives a token
scoped to that one prototype.
They approve once per machine, not once per comment, and the screenshot they were writing is kept and posted the moment they come back. Testers can disconnect any build from their Prototir account settings.
Pass api_base and slug to enable it. Without them the panel saves review files instead, which
needs no account and works offline.
review_enable warns and returns on non-Web exports. On Prototir the feedback becomes an ordinary
comment on the prototype, after Prototir's own confirmation dialog. In a Web export you host
yourself the panel saves a feedback.prototir-review.json file that the tester sends you and you
reload with Import review. See the
Web SDK README for the file format and its
limits.
A Web export takes everything from the page around it: the visitor is already signed in, and the shell watches the prototype and reports for it. A download has none of that, so the addon does it itself.
You do not configure the slug. Prototir writes it into the .zip as you upload the build, into a
prototir-prototype.json beside the executable, and the addon reads it from there. The slug does
not exist until the prototype does, so there was never a value you could have put in your first
export.
Override it in Project Settings > Prototir > Prototype Slug when you need to: a build you ship
outside Prototir, or an installer Prototir cannot write into. Call Prototir.configure("your-slug")
if your game decides it at runtime. The injected slug wins over the project setting, because it
travelled with that exact download. Then:
The quickest version is one line. It shows a screen that looks like Prototir, over whatever your game is already drawing, and handles every state of the flow:
func _ready() -> void:
Prototir.ready()
if not Prototir.is_paired():
Prototir.show_pairing_screen()It draws the code large, rasterises the QR, offers Open in browser and Copy code, and closes itself once the tester approves. Use it as it is, restyle it, or ignore it entirely.
Drawing your own is still fully supported, and is the right answer as soon as your game has a look of its own. The addon hands you everything and takes no opinion:
func _ready() -> void:
Prototir.pairing_started.connect(_show_code)
if not Prototir.is_paired():
await Prototir.begin_pairing()
func _show_code(request: Dictionary) -> void:
# request.code, request.verification_url, request.qr_svg, request.prototype_title
$Code.text = request.codeGive the tester a way to act on it. A game window has no selectable text, so a printed URL on its
own leaves them retyping it off a screen: offer OS.shell_open(request.verification_url) on
desktop, and DisplayServer.clipboard_set(request.code) or the QR where a browser on this machine
helps nobody, such as a headset. request.qr_svg is SVG text, which
Image.load_svg_from_string() turns into a texture.
examples/pairing/ builds the same flow with unstyled controls, if you would rather start from
something plain than restyle the shipped screen.
ready(), event() and score() accumulate one session rather than one request each, and the
addon reports it for you every 30 seconds while the game runs, and again when the window loses
focus. You do not have to call anything. Prototir.flush_session() is there for a natural break,
such as the end of a run, if you want the numbers to land sooner.
Repeating costs nothing: the first report returns an id the rest carry, so the server updates one
row rather than counting a play per report. When the window closes there is no time left to send,
so whatever the last report missed is written to user://prototir/pending and goes out the next
time the build starts. That covers the tester playing on a train as well. Prototir.send_feedback("...") posts a comment as the tester who approved the
build; no session is needed first, because approving the pairing is the stronger signal.
Pairing works when you run from the editor, so you can build the screen without exporting every time. Reporting does not: an F5 run is not a play, and counting it would put your own testing in your own numbers.
Nothing here reaches a Web export. Prototir > Export for Prototir (Web) adds
addons/prototir/native/* to that preset's exclude filter, and the Download button excludes
addons/prototir/web/*, so each build carries only the transport it can use. You can see and change
both in Project > Export > Resources > Exclude.
Two entries under Project > Tools:
- Prototir: Export for Prototir (Web) runs the Web preflight, exports, and zips the result.
- Prototir: Export for Prototir (Download) exports for the machine you are on.
Both produce a ZIP ready to drop on the upload page, and both write prototir-build.json beside the
build. Prototir records that id from the archive, and a running build reports the same id when it
pairs; a match shows the build running is the build that was uploaded, and nothing more. Both sides
come from a file you control, so it is not verification, security or anti-cheat. It catches an old
build being run against a new upload.
Exporting for the other desktop platforms needs their export templates, so those stay in the normal Export dialog rather than behind a button that would produce a build nobody can run.
node tools/test.mjs
godot --headless --path . --script tests/run_tests.gdThe first checks structure. The second runs the pairing flow and the session recorder against fake HTTP, a fake clock and a fake delay, so a poll loop that waits ten minutes for a deadline finishes instantly and nothing touches the network.
A tagged release must additionally open without script errors in Godot 4.3+, produce a release Web export, pass the structural validator, and play in the real Prototir sandbox.