Lesson 208: Construct Custom-Domain CORS Hosting Decision Receipt (2026)

Direct answer: After subdomain wasm smoke files cors_smoke_receipt_v1.json, marketing will push a custom itch domain—do not overwrite that receipt. This lesson ships cors_hosting_decision_receipt_v1.json with chosen_production_host and the split-hosting pattern (brand page on custom URL, Play on *.itch.io) before you promote HTML5 fest links.

Lesson hero for Construct custom-domain CORS hosting decision receipt

Why this matters now (July 2026 custom domains)

July 2026 press kits ship play.yourstudio.com while engineering proved green only on studio.itch.io/demo. Construct wasm and workers fail when page origin and asset origin split without a header plan—the CORS vs subdomain playbook owns strategy; Construct subdomain preflight owns S1–S6 smoke—this lesson is the course milestone that records the hosting decision on BUILD_RECEIPT (pairs help #6 CORS fix when published, guide #9 CORP/COEP). Before October fest promotion, file Lesson 222 corp_coep_fest_receipt with three-origin map and split-hosting defer path.

Beginner path (35-minute decision)

Step Action Success check
1 Confirm cors_smoke_receipt GREEN on subdomain S6 pass, MIME OK
2 Document three origins (page, wasm, API) cors_origin_map_v1.json
3 Test custom domain in DevTools Screenshot CORS lines
4 Choose hosting lane Subdomain-only or split pattern
5 Write cors_hosting_decision_receipt_v1.json promotion_allowed: true
6 Pin Play URL to subdomain in press kit No wasm on custom-only URL

Time: ~35 minutes decision pass; 70 minutes with custom-domain experiment + facilitator README update.

Developer path (gates H1–H6)

Gate Check Fail when
H1 Subdomain smoke receipt exists No cors_smoke_receipt_v1.json
H2 Origin map filed Page host ≠ wasm host undocumented
H3 Custom domain tested Marketing URL never opened in DevTools
H4 Header plan or defer custom wasm CORS errors with no mitigation
H5 DevTools CORS clean on production host Red console on chosen URL
H6 Hosting receipt committed BUILD_RECEIPT stale

Split-hosting pattern (recommended when brand wants custom URL)

Surface URL Serves
Marketing landing https://play.brand.com Static HTML, trailer embed, press copy
Playable demo https://studio.itch.io/fest-demo Construct wasm + workers
Facilitator pin itch subdomain only facilitator_url_canonical

Do not point the Play button at custom domain until custom_domain_serves_wasm: true in receipt.

cors_origin_map_v1.json

{
  "schema": "cors_origin_map_v1",
  "page_origin": "https://play.brand.com",
  "wasm_origin": "https://cdn.itch.io",
  "worker_origin": "https://cdn.itch.io",
  "api_origin": null,
  "same_origin_for_game_assets": false
}

When same_origin_for_game_assets is false, default chosen_production_host: itch_subdomain.

cors_hosting_decision_receipt_v1.json

{
  "schema": "cors_hosting_decision_receipt_v1",
  "build_id": "html5-fest-2026-rc7",
  "chosen_production_host": "itch_subdomain",
  "custom_domain_enabled": true,
  "custom_domain_serves_wasm": false,
  "split_hosting_pattern": true,
  "paired_cors_smoke_receipt": "release-evidence/html5/cors-smoke/CORS_SMOKE_RECEIPT.json",
  "gates": {
    "H1_subdomain_smoke": "pass",
    "H2_origin_map": "pass",
    "H3_custom_tested": "pass",
    "H4_defer_or_header_plan": "pass",
    "H5_devtools_clean_on_production": "pass",
    "H6_receipt": "pass"
  },
  "facilitator_url_canonical": "https://studio.itch.io/fest-demo",
  "promotion_allowed": true
}

Never set custom_domain_tested: true inside cors_smoke_receipt—keep smoke and hosting receipts separate files.

Publish gate

ALTER TABLE release_publish_gate ADD COLUMN IF NOT EXISTS
  html5_hosting_decision_blocked BOOLEAN NOT NULL DEFAULT false;

CI verify_cors_hosting_decision_v1 requires cors_hosting_decision_receipt_v1.json when html5_host column is custom or split.

Wire into BUILD_RECEIPT

Column Example
html5_host itch_subdomain or split
cors_smoke_receipt path to smoke JSON
cors_hosting_decision_receipt path to hosting JSON
facilitator_url_canonical itch subdomain URL only

Thursday row review must show both receipt paths.

Prerequisites

Common mistakes

  • Overwriting cors_smoke_receipt when custom domain fails—hosting decision is a second file.
  • Sharing custom URL in Discord while wasm only works on subdomain.
  • Fixing MIME repeatedly while origin stays split.
  • Enabling custom domain before S6 subdomain smoke passes.

Troubleshooting

Symptom Lane
Subdomain OK, custom blank Expected—use split pattern or defer custom wasm
CORS on wasm only H2 origin map + playbook
MIME octet-stream itch MIME help first
Triple-channel label red Lesson 201

Mini exercise (50 minutes)

  1. Pass subdomain smoke; archive cors_smoke_receipt.
  2. Open custom domain; capture CORS console screenshot.
  3. File origin map showing split hosts.
  4. Choose split pattern; write hosting receipt.
  5. Update press kit Play link to itch subdomain only.

Continuity

FAQ

Can we ship custom domain if marketing insists?
Only with custom_domain_serves_wasm: true and H5 green on that URL—otherwise use split pattern.

Does this replace the playbook?
No—playbook is policy; this lesson is receipt wiring in your RPG live-ops course.

Godot HTML5 too?
Same origin discipline—see Godot WASM memory playbook for a different failure family.


Subdomain smoke proves the build workshosting decision receipt proves which URL you are allowed to put in the fest press kit.