Change record status: 
Project: 
Introduced in branch: 
3.x
Introduced in version: 
3.0.0-alpha2
Description: 

Introduction

In the GraphQL schema a webform's form fields used to live directly on the Webform type:

webformById(id: "contact") {
  title
  elements { ... }
}

The problem is that a webform and a built form are not the same thing. A Webform is a configuration entity: it is fixed. A built form is an instance that depends on context: the source entity, the current user's access, whether the form is open or closed, whether a submission limit has been reached, and so on.

Because elements sat directly on Webform, the schema had no place to describe that built instance, and in particular no way to say whether the form can actually be filled in, and if not, why. When Webform decides not to render a form (closed form, missing or mismatched source entity, submission limit reached, confidential form, broken element definition, …) it returns a small render array containing only a status message
instead of the form fields. The old elements resolver walked that status message as if it were a list of fields and produced a handful of meaningless WebformElementUnexposed entries, with no clean signal that the form was unavailable and no way to read the reason or message.

To resolve this, the form fields now live on a dedicated WebformForm type, reached through a new form field on Webform. This separates the immutable configuration entity from a
context-dependent built instance (a field with arguments returning an object type, which is exactly what GraphQL is designed to express), and gives form availability a first-class, machine-readable home.

What changed

  • Removed from Webform: elements, title, sourceEntityType, sourceEntityId and sourceEntityLabel.
  • Webform now keeps only what is intrinsic to the configuration entity — id, label and settings — plus the new form field.
  • Added Webform.form(sourceEntityType: String, sourceEntityId: ID): WebformForm.
  • Added the WebformForm type, carrying the fields that moved (elements, title, sourceEntityType, sourceEntityId,
    sourceEntityLabel) plus a new unavailable field.
  • Added the WebformFormUnavailable type and the WebformMessageType and WebformFormUnavailableReason enums.

The new types look like this:

type WebformForm {
  "The form elements. Empty when the form is unavailable."
  elements: [WebformElement]
  "Set when the form cannot be filled in. NULL when the form is available."
  unavailable: WebformFormUnavailable
  title: String!
  sourceEntityType: String
  sourceEntityId: ID
  sourceEntityLabel: String
}

type WebformFormUnavailable {
  "The human-readable message Webform would display in place of the form."
  message: String!
  "The message severity."
  type: WebformMessageType
  "A machine-readable classification of why the form is unavailable."
  reason: WebformFormUnavailableReason!
}

enum WebformMessageType { STATUS INFO WARNING ERROR }

enum WebformFormUnavailableReason {
  CLOSED
  OPENING
  SOURCE_ENTITY_REQUIRED
  SOURCE_ENTITY_TYPE_MISMATCH
  OTHER
}

When the form is unavailable, elements is now empty (instead of being padded with placeholder entries) and unavailable is non-NULL. When the form is available, unavailable is NULL. The
message and type come straight from the status message Webform appends, so they are always present, translated and authoritative; reason is a best-effort classification, with everything not
explicitly classified mapped to OTHER (the real message is still provided).

The webformById query keeps its sourceEntityType / sourceEntityId arguments. The form field also accepts them; when omitted it inherits the source entity supplied to
webformById.

How to update your queries

Reading elements (and title)

Move elements and title under the new form field.

Before:

query ($id: String!) {
  webformById(id: $id) {
    title
    elements {
      __typename
      metadata { key type }
    }
  }
}

After:

query ($id: String!) {
  webformById(id: $id) {
    form {
      title
      elements {
        __typename
        metadata { key type }
      }
    }
  }
}

Source entity fields

sourceEntityType, sourceEntityId and sourceEntityLabel also moved to WebformForm. The source entity may be supplied on webformById (as before) or directly on form.

Before:

query ($id: String!) {
  webformById(id: $id, sourceEntityType: "node", sourceEntityId: "42") {
    title
    sourceEntityLabel
  }
}

After:

query ($id: String!) {
  webformById(id: $id) {
    form(sourceEntityType: "node", sourceEntityId: "42") {
      title
      sourceEntityLabel
    }
  }
}

Webform-level fields are unchanged

id, label and settings stay on Webform and need no change:

query ($id: String!) {
  webformById(id: $id) {
    id
    label
    settings { page }
  }
}

Handling unavailable forms (new)

Consumers can now detect an unavailable form and branch on the reason instead of receiving placeholder elements.

query ($id: String!) {
  webformById(id: $id) {
    form {
      elements {
        metadata { key }
      }
      unavailable {
        message
        type
        reason
      }
    }
  }
}

If unavailable is NULL, the form is available and elements contains the form fields. Otherwise elements is empty and unavailable describes why, for example reason: CLOSED with type: STATUS and the closed-form message.

Impacts: 
Module developers