Context
WebhookModel currently builds its payload via a top-level pagesToQuestions(page, state) function in server/plugins/engine/models/submission/WebhookModel.ts. Each page type that needs different serialization (e.g. repeating sections (which I am currenlty working on and can link the downstream PR for)) forces a branch, either inside pagesToQuestions itself, or one level up via a type-check on the page object:
isRepeatingFieldPageController(page)
? repeatingPageToQuestion(page, state)
: pagesToQuestions(page, state);
This pattern doesn't scale: every new page-type variant adds another branch to shared code.
Proposal
Add a toWebhookQuestions(state) method to PageControllerBase. The default implementation is the current pagesToQuestions body. Subclasses with different state shapes override it.
// PageControllerBase.ts
toWebhookQuestions(state: FormSubmissionState): WebhookQuestion[] {
// port current existing logic
}
// WebhookModel.ts
questions = relevantPages.flatMap((page) => page.toWebhookQuestions(state));
Returning an array (rather than a single question) future-proofs the signature for controllers that legitimately emit N blocks per page (e.g. per-entry serialization).
Why this is worth doing
WebhookModel stops needing to know page types. It iterates pages and asks each to serialize itself. Adding a new variant means one new override on the variant's controller — no diff against shared code.
- Serialization lives next to state shape. Today, how a controller writes state is in one file and how that state is read for the webhook is in another. Co-locating them removes a known source of drift (e.g.
sectionKey not being known to the serializer).
- Future serializers benefit. Any CSV / JSON export / audit feature that needs "ask each page how to represent its state" can use the same hook.
Scope
(small only three files require changing)
PageControllerBase: add the method with the existing pagesToQuestions` body.
WebhookModel: swithch map to call page.toWebhookQuestions(state), this will require no added switch logic
- Any controller that needs custom serialization: add an override (initially zero or one).
Backwards compatibility
For non-repeating pages, the default implementation produces a single-element array containing exactly the object pagesToQuestions returns today, so the emitted payload is byte-identical. A snapshot test against an existing form should confirm.
Context
WebhookModelcurrently builds its payload via a top-levelpagesToQuestions(page, state)function inserver/plugins/engine/models/submission/WebhookModel.ts. Each page type that needs different serialization (e.g. repeating sections (which I am currenlty working on and can link the downstream PR for)) forces a branch, either insidepagesToQuestionsitself, or one level up via a type-check on the page object:This pattern doesn't scale: every new page-type variant adds another branch to shared code.
Proposal
Add a
toWebhookQuestions(state)method toPageControllerBase. The default implementation is the currentpagesToQuestionsbody. Subclasses with different state shapes override it.Returning an array (rather than a single question) future-proofs the signature for controllers that legitimately emit N blocks per page (e.g. per-entry serialization).
Why this is worth doing
WebhookModelstops needing to know page types. It iterates pages and asks each to serialize itself. Adding a new variant means one new override on the variant's controller — no diff against shared code.sectionKeynot being known to the serializer).Scope
(small only three files require changing)
PageControllerBase: add the method with the existingpagesToQuestions` body.WebhookModel: swithch map to callpage.toWebhookQuestions(state), this will require no added switch logicBackwards compatibility
For non-repeating pages, the default implementation produces a single-element array containing exactly the object
pagesToQuestionsreturns today, so the emitted payload is byte-identical. A snapshot test against an existing form should confirm.