Construct 3 Custom itch.io Domain CORS vs Subdomain Hosting - 2026 H2 Trend Playbook
Marketing bought a custom domain for the itch page. The Construct 3 HTML5 export loads on yourgame.itch.io—then shows a blank canvas on play.yourstudio.com with CORS errors for data.wasm and the worker script. Local preview works. Discord says “itch is broken.” Engineering says “we only changed DNS.”
July 2026 HTML5-first teams run itch + GX.games + Steam receipts in parallel (triple-channel help). Custom domains look professional for fest press kits—but Construct wasm and Web Workers expect same-origin asset fetches unless you control Cross-Origin headers end to end. This Trend-Jacking / News Commentary playbook is the hosting policy decision tree—not a second post-upload MIME article (CDN octet-stream) and not the planned evening subdomain smoke tutorial (step-by-step first proof).
Non-repetition note: Godot WASM memory ceiling owns runtime memory—not CORS origins. Planned help Construct CORS custom domain fix owns traceback-first fixes; this URL owns subdomain vs custom strategy + receipt.
Pair with BUILD_RECEIPT, Wednesday demo smoke, playtest isolation, and 14-free GX multi-channel tools.
Why this matters now (2026 H2)
- Custom itch domains — Studios point
CNAMEat itch for brand URLs without reading wasm origin rules. - Construct 3 HTML5 velocity — Roguelite and narrative teams ship browser demos before NW.js Steam builds (Construct save migration).
- Multi-channel facilitators — Playtest links must match
build_labelper channel—wrong host breaks receipt rows. - MIME vs CORS confusion — Teams fix
application/wasmMIME and still fail because origin differs on custom domain. - Fest October traffic — Press opens custom URLs first; subdomain proof skipped until production is on fire.
Direct answer: Prove on *`.itch.iosubdomain first**, filecors_origin_map_v1.json`, then choose custom domain only with a documented header plan—or keep marketing landing on custom and game iframe on subdomain.
The failure mode in one paragraph
Naive pattern: Export Construct HTML5 → upload to itch → enable custom domain → share https://play.brand.com in press kit. Page HTML loads from itch infrastructure but wasm/worker URLs resolve to a host that does not send Access-Control-Allow-Origin for your page origin. Browser blocks fetch; canvas stays black. Team re-uploads zip five times; MIME receipts pass; CORS still fails.
What breaks: Not Construct export quality—hosting topology. Subdomain hosting keeps game assets same-origin relative to itch’s game runner; arbitrary custom domains split origins unless configured.
Two hosting models (beginners)
| Model | URL shape | Who sets CORS? | Construct wasm typical result |
|---|---|---|---|
| itch subdomain | studio.itch.io/game |
itch (bundled) | Works when MIME OK |
| Custom domain | play.brand.com |
You + itch limits | Often breaks without header plan |
Beginner rule: Ship fest demos on subdomain until cors_hosting_decision_receipt_v1.json approves custom.
Three origins (working developers)
Origin A: HTML shell (page URL bar)
Origin B: Game assets (wasm, data, workers, media)
Origin C: Optional API (analytics, saves)
Pass condition for Construct HTML5: A and B must be compatible for fetch + WebAssembly.instantiateStreaming. itch subdomain mode usually collapses A≈B; custom domain mode often splits them.
Symptom matrix (route correctly)
| Console / Network | Likely lane | Read next |
|---|---|---|
| CORS policy blocked wasm | This playbook | C1–C6 |
instantiateStreaming MIME error |
CDN MIME | itch MIME help |
| COOP / COEP isolated | Cross-origin isolation | Compare CORP headers |
| Works incognito on itch.io only | Origin split | Subdomain proof |
| Blank after Steam switch | Wrong channel build | Store-demo mismatch |
| Godot only broken | Engine export | Godot MIME help |
Gates C1–C6 (hosting decision pass)
| Gate | Name | Pass criterion |
|---|---|---|
| C1 | Subdomain smoke | Game playable on *.itch.io with wasm_mime_receipt GREEN |
| C2 | Origin map | cors_origin_map_v1.json lists page + asset hosts |
| C3 | Custom domain need | Written reason custom URL required (press, SSL brand, etc.) |
| C4 | Header plan OR defer | CORP/COEP doc or decision to stay subdomain-only |
| C5 | DevTools proof | No CORS errors on chosen production URL |
| C6 | Receipt | cors_hosting_decision_receipt_v1.json committed |
C5 blocks public fest links when RED.
Decision tree (ASCII)
Start: Construct HTML5 fest demo ready?
|
+-- No --> Finish export; stop (not a hosting problem)
|
+-- Yes --> C1: Playable on studio.itch.io/game ?
|
+-- No --> Fix MIME/zip first (itch MIME help)
|
+-- Yes --> Need custom domain in press kit?
|
+-- No --> SHIP subdomain (done)
|
+-- Yes --> Can you set ACAO/CORP on asset host?
|
+-- No --> SPLIT: landing=custom, PLAY link=subdomain
|
+-- Yes --> Implement header plan + C4/C5 + custom
Split pattern (recommended): https://brand.com/fest-2026 marketing page embeds or links https://studio.itch.io/your-demo—players never fetch wasm from a mismatched origin.
cors_origin_map_v1.json
{
"schema": "cors_origin_map_v1",
"project": "roguelite-demo-2026",
"updated": "2026-05-25",
"lanes": [
{
"lane_id": "itch_subdomain",
"page_origin": "https://studio.itch.io",
"asset_origin": "https://studio.itch.io",
"same_origin": true,
"construct_export": "html5-zip-rc3",
"status": "production"
},
{
"lane_id": "custom_press",
"page_origin": "https://play.brand.com",
"asset_origin": "https://cdn.itch.io",
"same_origin": false,
"header_plan": "defer-wasm-to-subdomain",
"status": "marketing-only"
}
],
"wasm_files": ["data.wasm", "workermain.js"],
"notes": "Do not promote custom until C5 pass"
}
Store under release-evidence/html5/cors/.
cors_hosting_decision_receipt_v1.json
{
"schema": "cors_hosting_decision_receipt_v1",
"build_id": "html5-nextfest-2026-rc6",
"chosen_production_host": "itch_subdomain",
"custom_domain_enabled": true,
"custom_domain_serves_wasm": false,
"gates": {
"C1_subdomain_smoke": "pass",
"C2_origin_map": "pass",
"C3_custom_need_documented": "pass",
"C4_header_plan_or_defer": "pass",
"C5_devtools_no_cors": "pass",
"C6_receipt": "pass"
},
"paired_receipts": [
"release-evidence/html5/wasm_mime_receipt_v1.json",
"release-evidence/build/build_receipt_v1.json"
],
"facilitator_url_canonical": "https://studio.itch.io/your-demo",
"promotion_allowed": true
}
Wire html5_host: verified into BUILD_RECEIPT and Thursday row review.
DevTools proof protocol (15 minutes)
- Open production URL → F12 → Console — screenshot CORS lines.
- Network → filter
wasm— note Request URL host vs page host. - If hosts differ, expect CORS unless headers green in Headers tab.
- Repeat Firefox + Chrome (facilitator matrix minimum).
- Archive
devtools_cors_proof.mdwith pass/fail per browser.
curl sketch (supplement, not substitute)
curl -I "https://studio.itch.io/your-demo/data.wasm"
# Expect Content-Type: application/wasm (see MIME help)
curl -I "https://play.brand.com/"
# Page may 200 while wasm on another host still CORS-fails in browser
Browsers enforce CORS stricter than curl—never close a ticket on curl alone.
Construct 3 export notes
| Setting / artifact | Fest recommendation |
|---|---|
| Export type | HTML5 zip to itch |
| Minify scripts | On (smaller wasm fetch) |
| Worker mode | Note worker script names in origin map |
| Remote scripts | Avoid third-party origins during CORS debug |
| NW.js Steam | Separate lane—do not mix proofs |
Outbound: Construct 3 manual — HTML5 export.
Header vocabulary (when you control CDN)
| Header | Role |
|---|---|
Access-Control-Allow-Origin |
Must include page origin or * (careful with credentials) |
Cross-Origin-Resource-Policy |
cross-origin vs same-origin on wasm |
Cross-Origin-Embedder-Policy |
Isolation features—test before enabling blindly |
Cross-Origin-Opener-Policy |
Popup/worker interactions |
Honest limit: itch custom domain pages may not let you set arbitrary headers on itch-hosted wasm—treat “full custom” as marketing shell, not wasm host, unless support confirms otherwise.
Multi-channel alignment
| Channel | Hosting note |
|---|---|
| itch subdomain | Canonical wasm proof |
| GX.games | Separate origin map row |
| Steam | NW.js / standalone—not this playbook |
| itch custom | Marketing only until C5 |
triple-channel help channel_label_match fails when facilitators distribute custom URL but receipts reference subdomain build_label.
Beginner path (producer, 60 minutes)
- Ask engineering for one green URL on
*.itch.io. - Paste that URL in press kit—not custom domain.
- If brand demands custom, require split pattern in writing.
- Collect DevTools screenshot with zero CORS errors.
- Sign receipt only when
facilitator_url_canonicalmatches Discord pin.
Developer path (engineering, half day)
- Automate C1 with Playwright load + console listener.
- Block CI deploy if press JSON contains custom URL without
C5 pass. - Add
html5_hostcolumn to BUILD_RECEIPT. - Pair Wednesday smoke golden path on canonical host only.
- Document in facilitator README (snippet below).
Facilitator README snippet
## HTML5 playtest link
- Canonical: https://studio.itch.io/your-demo (wasm host)
- Marketing: https://play.brand.com (landing only—do not file bugs against wasm here)
- If canvas blank: check Console for CORS before re-uploading zip
- Tag issues: html5-cors vs html5-mime
Relationship to upcoming tutorial (#9 backlog)
| Topic | This trend playbook | Planned subdomain evening tutorial |
|---|---|---|
| Strategy | Primary | Assumes strategy chosen |
cors_smoke_receipt_v1.json |
Referenced | Hands-on steps |
| Decision tree | Full | Brief recap |
| curl MIME | Points to help | May include ffprobe-style checks |
Ship strategy first so tutorial does not debug CORS on a host you should never use.
Troubleshooting table
| Observation | First action |
|---|---|
| Custom blank, subdomain OK | Stop wasm on custom; use split |
| CORS + MIME error | Fix MIME on subdomain first |
| Intermittent blank | CDN cache—MIME help |
| GX works, itch custom fails | Separate origin maps |
| Worker failed | Include worker URL in C2 map |
| After DNS change only | Re-run C5—origins changed |
Fest week calendar
| Day | Action |
|---|---|
| Mon | C1 subdomain smoke + MIME receipt |
| Tue | Draft origin map + decision tree sign-off |
| Wed | Demo smoke on canonical host |
| Thu | Row review html5_host column |
| Fri | Freeze facilitator URL in Discord |
Engine comparison (HTML5 lanes)
| Engine | Custom domain CORS risk |
|---|---|
| Construct 3 | High (wasm + worker) |
| Godot 4.5 Web | High — see Godot wasm blog |
| Phaser | Medium (script tags) |
| Unity WebGL | High + COOP complexity |
Multi-engine studios still file one cors_origin_map_v1.json per demo SKU.
COOP / COEP / CORP (when isolation appears)
Some teams enable cross-origin isolation for threads or shared buffers—then every asset host must cooperate:
| Header | Symptom when wrong |
|---|---|
COEP: require-corp |
Wasm blocked unless CORP present |
CORP: same-origin on wasm |
Custom page cannot load asset |
COOP: same-origin |
Popups break auth flows |
Fest default: Do not enable isolation on itch HTML5 unless you already proved C5 on all hosts. Revisit after subdomain lane is boringly stable.
DNS and custom domain checklist (producer)
| Step | Owner | Pass |
|---|---|---|
| CNAME points to itch per docs | Producer | DNS propagates |
| SSL cert valid | Browser padlock | Green |
| Custom URL loads itch page shell | Manual | 200 OK |
| Game canvas plays | Engineering | C5 |
| Press PDF updated | Marketing | Canonical URL only |
Common trap: DNS correct, SSL green, canvas still black—players blame “SSL” when CORS is the real fault. Train support to ask for Console screenshot first.
Case vignettes (synthesized patterns)
Vignette A — Press kit custom, wasm on subdomain
Studio linked play.brand.com in October kit. Wasm fetched from cdn.itch.io without ACAO for play.brand.com. Fix: Press kit → studio.itch.io/demo; custom domain landing paragraph only. Time to green: 90 minutes (no re-export).
Vignette B — MIME fixed, CORS ignored
Team passed wasm_mime_receipt after CDN MIME help on subdomain, then enabled custom same day without C5. Fix: Disabled wasm on custom; kept MIME discipline on subdomain. Lesson: order gates C1 → MIME → C5.
Vignette C — Facilitator link drift
Discord pin showed custom URL; BUILD_RECEIPT facilitator_url_canonical still subdomain—playtest bugs filed against wrong host. Fix: Thursday row review added html5_host diff column.
BUILD_RECEIPT row example
{
"build_id": "html5-nextfest-2026-rc6",
"channel": "itch_public",
"html5_host": "itch_subdomain",
"facilitator_url_canonical": "https://studio.itch.io/your-demo",
"custom_domain_marketing_only": true,
"cors_hosting_decision_receipt": "release-evidence/html5/cors/cors_hosting_decision_receipt_v1.json",
"wasm_mime_receipt": "release-evidence/html5/wasm_mime_receipt_v1.json"
}
Align with GX receipt help so build_id matches across GX and itch proofs.
Browser matrix (minimum fest QA)
| Browser | Subdomain | Custom (if used) |
|---|---|---|
| Chrome desktop | Required pass | C5 if promoted |
| Firefox desktop | Required pass | C5 if promoted |
| Safari macOS | Required pass | Extra scrutiny |
| Chrome Android | Required pass | Often forgotten |
| Safari iOS | Required pass | ITP quirks |
Archive one screen recording per browser showing golden path load—not only static screenshots.
Press kit URL block (copy-paste)
Play the demo (canonical): https://studio.itch.io/your-demo
Brand site: https://play.brand.com (info + trailer; game runs on itch link above)
System: Modern Chrome/Firefox, WebGL enabled
Stops outlets from embedding the wrong origin in iframe articles.
Security and abuse notes
- Do not set
Access-Control-Allow-Origin: *on authenticated APIs sharing cookies with game host. - Treat facilitator URLs like secrets during NDA playtests—leaks cause playtest scope confusion, not security miracles.
- Custom domains increase phishing risk—publish official URL list in Discord pinned post.
When to escalate to itch support
Escalate when:
- Subdomain fails with GREEN MIME receipt (unlikely—file itch ticket with
curl -I). - Custom domain required by publisher contract and split pattern rejected.
- Headers documented but C5 still fails after 24h propagation.
Attach cors_origin_map_v1.json, devtools_cors_proof.md, and build_id—not only “CORS broken” subject lines.
Evidence folder layout
release-evidence/html5/cors/
cors_origin_map_v1.json
cors_hosting_decision_receipt_v1.json
devtools_cors_proof.md
screenshots/
chrome_subdomain_pass.png
chrome_custom_fail_or_pass.png
wasm_mime_receipt_v1.json # symlink or copy from sibling folder
Friday Block 5 should confirm the facilitator URL in Discord still matches facilitator_url_canonical—drift is a top-three October incident driver for HTML5 teams.
Post-mortem questions (after fest)
- Did we promote custom before C5?
- Did facilitators file MIME bugs when CORS was the root?
- Did GX and itch receipts disagree on
build_id? - Will tutorial #9 subdomain smoke become mandatory onboarding?
Honest answers feed the next Blog-Planner refill—Process rows stay closed; hosting depth moves into tutorials tied to receipts.
Pin the canonical itch URL in two places only: Discord welcome and BUILD_RECEIPT—third copies in Notion drafts cause October link drift.
Promotion checklist
- [ ]
cors_hosting_decision_receipt_v1.jsoncommitted - [ ]
wasm_mime_receipt_v1.jsonGREEN on subdomain - [ ] Press kit URL equals
facilitator_url_canonical - [ ] Custom domain does not serve wasm (unless C5 pass)
- [ ] BUILD_RECEIPT
html5_hostrow matches - [ ] Forward help linked when published
FAQ
Is custom domain always broken?
No—but default itch custom setups break Construct wasm unless you prove C5.
Can we iframe subdomain into custom?
Often yes for marketing; test embed policies and mobile Safari.
Does re-exporting Construct fix CORS?
Rarely—hosting topology is the fix.
Same zip on both URLs?
Same bytes, different origins—different browser results.
What about GX.games?
Add a lanes[] row—do not assume itch rules transfer.
Key takeaways
- Subdomain first — custom domain is a policy choice, not a upload toggle.
- CORS ≠ MIME — fix both, in that order, for Construct HTML5.
cors_origin_map_v1.jsondocuments truths engineering already knows.- Split pattern saves fest week when you cannot set headers.
- Planned CORS help complements this strategy URL.