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.

OperationKindPurpose
forms / formsMain / formsTotalCount / formDetail(_id)queryForm list/detail with type, channelId, tagId, status, searchValue filters
formSubmissions(filter…) / formSubmissionDetail(_id) / formsGetContentTypesquerySubmission reads
cpForms / cpFormDetailqueryClient portal reads
formsAdd / formsEdit / formsRemove / formsToggleStatus / formsDuplicatemutationForm management
frontlineFields / frontlineFieldByCode / frontlineFieldsGetTypes / frontlineFieldsCombinedByContentTypequeryField reads
frontlineFieldsAdd / frontlineFieldsEdit / frontlineFieldsRemove / frontlineFieldsBulkAction / frontlineFieldsUpdateOrder / frontlineFieldsUpdateVisible / frontlineFieldsUpdateSystemFieldsmutationField management
formSubmissionsSave / formSubmissionsRemove / formSubmissionsEditmutationSubmission writes
widgetsLeadConnect / widgetsSaveLeadmutationWidget lead flow: connect a lead integration, then save a submission
cpWidgetsSaveLeadmutationClient 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 active survey.
  • Only targets Messenger conversations.
  • Checks channel and brand compatibility between the survey and conversation.
  • Writes a survey snapshot onto the message's extraData.survey and marks the conversation hasSurvey: true, which feeds the withSurvey inbox 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

OperationKindPurpose
surveyList / surveyDetail(_id) / surveyTotalCountqueryAdmin list/detail with searchValue, status, channelId filters
cpSurveys / cpSurveyDetail(channelId, surveyCode) / cpSurveyVotes(conversationId)queryClient portal list, detail, and existing vote selections
surveyAdd / surveyEdit / surveyRemove / surveyToggleStatusmutationSurvey management
surveySendToConversation(_id, conversationId)mutationInject the survey into a Messenger conversation
cpSurveySubmit(surveyCode, optionIds)mutationClient portal vote

There is no cpSurveyVote operation; the read is cpSurveyVotes and the write is cpSurveySubmit.

Was this helpful?