Forms & Surveys
Capture leads with embeddable forms and run channel-scoped surveys inside Messenger conversations, including one-vote-per-user enforcement and survey-driven ticket automation.
Backend modules: backend/plugins/frontline_api/src/modules/{form,survey}. Frontend: frontend/plugins/frontline_ui/src/modules/{forms,survey} at /frontline/forms and /frontline/surveys.
Forms
Definitions and fields
A Form carries name, title, code, type, description, buttonText, numberOfPages, visibility, leadData, languageCode, departmentIds, tagIds, channelId, and integrationId. Its fields are FrontlineField records managed through the frontlineFields* operations (each with validation, regexValidation, validator, options, order, isRequired, logics, and subFields). Field validation is defined by a FieldValidator (type of PRESET/CUSTOM/NONE, presetKey, customRegex, errorMessage) backed by form/validationRegistry.ts. Preset keys: EMAIL, NUMBER, DATE_TIME, DATE, PHONE, MN_VEHICLE_REGISTRATION, POSTAL_CODE, ALPHANUMERIC. Custom regex support is guarded by length/time limits to avoid pathological patterns.
Submissions
A Submission groups one customer's answers (contentTypeId, formId, conversationId, channelId, customerId, customFieldsData, submissions); each FormSubmission row stores formFieldId, the submitted text/value, formFieldText/formFieldType, and submittedAt. Submissions attach to the customer record and can be logged onto related entities through relation.onRelationAdded. Submission export is registered as content type frontline:formSubmission.formSubmission (permission formSubmissionsExportManage).
Form queries and mutations
Schema: modules/form/graphql/schema/form.ts.
| Operation | Kind | Purpose |
|---|---|---|
forms / formsMain / formsTotalCount / formDetail(_id) | query | Form list/detail with type, channelId, tagId, status, searchValue filters |
formSubmissions(filter…) / formSubmissionDetail(_id) / formsGetContentTypes | query | Submission reads |
cpForms / cpFormDetail | query | Client portal reads |
formsAdd / formsEdit / formsRemove / formsToggleStatus / formsDuplicate | mutation | Form management |
frontlineFields / frontlineFieldByCode / frontlineFieldsGetTypes / frontlineFieldsCombinedByContentType | query | Field reads |
frontlineFieldsAdd / frontlineFieldsEdit / frontlineFieldsRemove / frontlineFieldsBulkAction / frontlineFieldsUpdateOrder / frontlineFieldsUpdateVisible / frontlineFieldsUpdateSystemFields | mutation | Field management |
formSubmissionsSave / formSubmissionsRemove / formSubmissionsEdit | mutation | Submission writes |
widgetsLeadConnect / widgetsSaveLead | mutation | Widget lead flow: connect a lead integration, then save a submission |
cpWidgetsSaveLead | mutation | Client portal lead submission |
mutation {
formsAdd(
name: "Contact us"
title: "Contact us"
type: "lead"
channelId: "chan_1"
) {
_id
code
}
}
tRPC
form.submissionsByConversation returns a conversation's form submissions.
Surveys
Survey operations are named survey* (older code called them polls: poll*, withPoll, Ticket.sourcePoll).
Definitions
A Survey carries title, question, channelId, brandId, code, options, ordered steps (each with its own question and options), allowMultiselect, durationHours, status (active or archived), sentCount, and createdUserId. Options can arm ticket automation (ticketCreationEnabled, ticketCreationThreshold, ticketPipelineId, ticketStatusId, ticketCreated, ticketId).
Sending into a conversation
surveySendToConversation(_id, conversationId) is a mutation that:
- Requires an
activesurvey. - Only targets Messenger conversations.
- Checks channel and brand compatibility between the survey and conversation.
- Writes a survey snapshot onto the message's
extraData.surveyand marks the conversationhasSurvey: true, which feeds thewithSurveyinbox filter.
Survey conversations are excluded from integrationType-scoped inbox lists unless you pass withSurvey: "true".
Client portal submission
cpSurveySubmit(surveyCode, optionIds) requires an authenticated client portal user. It finds or creates the customer, creates a Messenger conversation with a survey snapshot, validates option ids and allowMultiselect rules, and returns alreadyVoted on repeat votes. A partial unique index on SurveyVotes ({ surveyId: 1, cpUserId: 1 }, filtered to documents where cpUserId exists) enforces one vote per client-portal user per survey (definitions). After a successful vote, runSurveyTicketAutomation evaluates armed options; see Tickets.
Survey queries and mutations
| Operation | Kind | Purpose |
|---|---|---|
surveyList / surveyDetail(_id) / surveyTotalCount | query | Admin list/detail with searchValue, status, channelId filters |
cpSurveys / cpSurveyDetail(channelId, surveyCode) / cpSurveyVotes(conversationId) | query | Client portal list, detail, and existing vote selections |
surveyAdd / surveyEdit / surveyRemove / surveyToggleStatus | mutation | Survey management |
surveySendToConversation(_id, conversationId) | mutation | Inject the survey into a Messenger conversation |
cpSurveySubmit(surveyCode, optionIds) | mutation | Client portal vote |
There is no cpSurveyVote operation; the read is cpSurveyVotes and the write is cpSurveySubmit.