01 · Setup
Publish the website that will host the form.
- Open the correct game.Go to Games & integrations → Manage web form and copy its public installation key.
- Add the exact origin.For
http://localhost:5500/manual_testing/, enter onlyhttp://localhost:5500. Do not include a path. - Publish web form.The origin and field configuration are saved together as a new version.
02 · Load configuration
Read the currently published contract first.
The GET response contains the configuration version, standard fields, studio-defined custom fields, and the game’s published base theme. Keep the whole response until the player submits.
const response = await fetch(
`${gamedeskApi}/v1/public/web-form-installations/${installationKey}`
);
if (!response.ok) throw new Error("Support form is unavailable");
const publishedForm = await response.json();
// publishedForm.version is required when submitting.
// publishedForm.configuration contains fields, customFields, and theme.The GameDesk widget applies this theme automatically. A headless form uses your own markup and CSS, so you choose whether to apply the same baseline:
const { theme } = publishedForm.configuration;
// Headless forms own their own CSS. Apply the published baseline if you want it.
form.style.setProperty("--support-accent", theme.accent);
form.style.setProperty("--support-surface", theme.surface);
form.style.setProperty("--support-text", theme.text);
form.style.setProperty("--support-border", theme.border);
form.style.borderRadius = {
small: "8px", medium: "14px", large: "20px",
}[theme.radius];03 · Render all published fields
Use each published key as the input name.
First render every visible standard field. This includes the optional playerDisplayName when a studio enables it. The label and layout are yours to style.
for (const field of publishedForm.configuration.fields) {
if (!field.visible) continue;
const label = document.createElement("label");
label.textContent = field.label;
const control = document.createElement(
field.key === "messageBody" ? "textarea" : "input"
);
control.name = field.key;
control.required = field.required;
if (field.key === "playerEmail") control.type = "email";
label.append(control);
form.append(label);
}
// This includes playerDisplayName whenever the studio enables it.Then render custom fields. Their field.key is the stable data key that must be submitted back to GameDesk exactly.
function renderCustomFields(configuration, form) {
const container = document.querySelector("#custom-fields");
for (const field of configuration.customFields) {
if (!field.visible) continue;
const label = document.createElement("label");
label.textContent = field.label;
const control = document.createElement(
field.type === "multi_line" ? "textarea" : "input"
);
control.name = field.key; // The stable GameDesk data key.
control.required = field.required;
control.placeholder = field.placeholder;
if (field.type === "checkbox") control.type = "checkbox";
if (field.type === "select") {
const select = document.createElement("select");
select.name = field.key;
select.required = field.required;
for (const option of field.options) {
select.add(new Option(option.label, option.value));
}
label.append(select);
} else {
label.append(control);
}
if (field.helpText) label.append(" — " + field.helpText);
container.append(label);
}
}If a studio changes fields after this page loaded, the submission receives 409 web_form_configuration_outdated. Load the latest configuration and let the player review it before trying again.
04 · Submit answers
Build customFields from the same configuration.
Loop over the visible custom fields and place each answer under its exact data key. Do not send arbitrary keys or values: GameDesk checks the type, allowed select options, and required state.
const fields = new FormData(form);
const customFields = {};
for (const field of publishedForm.configuration.customFields) {
if (!field.visible) continue;
const input = form.elements.namedItem(field.key);
customFields[field.key] =
field.type === "checkbox" ? input.checked : input.value;
}
const response = await fetch(
`${gamedeskApi}/v1/public/web-form-installations/${installationKey}/submissions`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
configurationVersion: publishedForm.version,
playerEmail: fields.get("playerEmail"),
playerDisplayName: fields.get("playerDisplayName"),
subject: fields.get("subject"),
messageBody: fields.get("messageBody"),
customFields,
}),
},
);05 · Errors
What the browser should do next.
400 invalid_requestShow the player a safe validation message and retain their entered answers.404 web_form_not_availableThe key, published form, or page origin is not available. Do not expose which part failed.409 web_form_configuration_outdatedReload configuration, render the current form, and ask the player to review it.429 rate_limitedAsk the player to wait before retrying. Do not retry automatically.