GameDesk
← Documentation overview

Integration 01 · Available now

Headless Web Form API

Build the form entirely in your own design system. GameDesk validates the published form rules and creates the ticket; your application controls everything the player sees.

01 · Setup

Publish the website that will host the form.

  1. Open the correct game.Go to Games & integrations → Manage web form and copy its public installation key.
  2. Add the exact origin.For http://localhost:5500/manual_testing/, enter only http://localhost:5500. Do not include a path.
  3. Publish web form.The origin and field configuration are saved together as a new version.
Choose this path when you need full control.You may hard-code a stable form, but dynamically rendering the published configuration lets dashboard changes take effect without a frontend redeploy.

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.
Match the published form exactly.Send a value only for a visible field. A visible required field needs an answer. GameDesk rejects a missing required field and also rejects a value sent for a disabled field.

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.