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,sourceEntityIdandsourceEntityLabel. Webformnow keeps only what is intrinsic to the configuration entity —id,labelandsettings— plus the newformfield.- Added
Webform.form(sourceEntityType: String, sourceEntityId: ID): WebformForm. - Added the
WebformFormtype, carrying the fields that moved (elements,title,sourceEntityType,sourceEntityId,
sourceEntityLabel) plus a newunavailablefield. - Added the
WebformFormUnavailabletype and theWebformMessageTypeandWebformFormUnavailableReasonenums.
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.