Guide 10 · Developer docs

Forms and data capture

Building forms with the Forms module: how a form is put together and placed on a page, validating and branching, presenting it, where submissions are stored, and what happens after one arrives, from jobs and records to CRMs and payments.

L1–L2
Unit 03 · Branching forms

Conditional logic

Showing, hiding and requiring fields, groups and steps as the visitor answers. The browser applies the rules as the visitor types and the server applies them on submit; these patterns give the same result on both. The safe patterns, and how to keep a branching form maintainable.

Who this is for. The site builder who configures a branching form, and the theme developer whose layout has to carry its rules. Afterwards you can show, hide and require fields, groups and steps so that the browser and the server agree, and keep a large branching form maintainable.

Conditional logic is what turns a form into an application: questions that appear only when they apply, a step skipped for people it does not concern, a field that becomes compulsory because of an earlier answer. In the admin it is on the form's Conditionals tab. Each conditional is a list of conditions, all of which must be true, and a list of actions to apply when they are.

Conditionals are evaluated in two places, and the patterns on this page keep the two in agreement:

  • In the browser, by the platform's /assets/forms/js-forms-conditionals.js, live as the visitor types. This is what the visitor sees.
  • On the server, when the form is submitted, to decide which fields are required. The server applies only the visibility and requirement actions, and reads only the answers in the request it is handling.

Making the browser apply them

The script runs on every .coredna-js-form container on the page. It reads the form's conditionals from a global variable named formConditionals plus the form id, which the layout must print. That applies to page-per-step layouts too: wrap the form in the container, even though the script does not submit it.

Template code
{$rules = []}
{foreach $form->getActiveRevision()->getConditionals() as $conditional}
  {$rules[] = ['conditions' => $conditional->getConditions(), 'actions' => $conditional->getActions()]}
{/foreach}
<script nonce="{$_inlineScriptNonce}">
  window['formConditionals{$form->getId()}'] = {$rules|json_encode};
</script>
{% set rules = [] %}
{% for conditional in form.getActiveRevision().getConditionals() %}
  {% set rules = rules|merge([{ conditions: conditional.getConditions(), actions: conditional.getActions() }]) %}
{% endfor %}
<script nonce="{{ _inlineScriptNonce }}">
  window['formConditionals{{ form.getId() }}'] = {{ rules|json_encode }};
</script>

Without that variable, the script logs "Conditionals not found for form ID" to the console and every field stays visible. The script finds fields by the data-id attribute the platform prints on each element, and steps by data-step-name (the markup it looks for). Give every element an id, so the browser can target it by data-id.

Three levels: field, group, step

A visibility action shows or hides one of three things:

ActionTargetsExampleOn the server
Field VisibilityOne element, input or staticShow Intake when Course is diplomaThe field stops being required while hidden
Group VisibilityA group and everything in itShow the group Company details when Enquiry type is businessEvery field in the group stops being required while hidden
Step VisibilityA whole step, by its nameHide the step Dietary requirements when Attending dinner is uncheckedPut the step's required fields in a group: see below

They compose the way the markup nests. A hidden group hides its fields, whatever the field rules say, and a hidden step hides its groups. When a field is hidden, the browser also clears what was typed in it and removes its required attribute, so a hidden answer is never submitted.

Put a hidden step's required fields in a group, and hide the group. A required field on a step hidden by Step Visibility is still required on the server. Group visibility lifts the requirement on both sides. Step Visibility also needs the JS model; a page-per-step form shows every step in order (Multi-step forms).

The condition types

Type (admin name)StatesA concrete predicate
Value PresenceIs empty, Is filled; for a checkbox, Unchecked, CheckedPhone Is filled
String ComparisonIs equal to, Is not equal to, Contains, Not containsEnquiry type Is equal to business
Numeric ComparisonLess than, Greater than, Less than or equals, Greater than or equalsGuests Greater than 10
Date-Based ComparisonIs equal to, Is not equal to, Before, AfterEvent date Before 2026-12-01
Pattern MatchingIs regex match to, Is not regex match toEmail Is regex match to @example\.com$

A conditional's conditions are joined with AND. For OR, make two conditionals with the same actions.

The cases that look ambiguous:

  • A select, radio or checkbox list. Use String Comparison against the option's value, not its title: business, not "Business enquiry". On a list where several boxes can be ticked, Is equal to means "this box is one of those ticked".
  • A single checkbox (a consent box, "attending dinner"). Use Value Presence, Checked or Unchecked.
  • A number that can be zero. Use Numeric Comparison, not Is filled, and write "zero or more" as Greater than -1.
  • A regex. Write it without slashes or flags (@example\.com$), and for case use character classes.
  • A date. Write the value as YYYY-MM-DD. With a Date/Time field in date-and-time mode, use Before and After rather than Is equal to.

Actions beyond visibility

Action (admin name)DoesApplied by
Field RequirementsMarks a field required, or optionalThe browser and the server
Field ModificationSets a field's value; on a static element, replaces its textThe browser only. The value it sets is then submitted like any typed answer. It is not set back when the condition stops matching: add a second conditional for the opposite case (what the script undoes)
Class ManagementAdds or removes CSS classes on a fieldThe browser only

Worked example: a field that becomes required. A booking form asks Number of guests (Numeric, name guests). For groups larger than ten the venue needs Organiser's phone (Phone, name organiserPhone), and the field should say so. Leave Required unticked on organiserPhone and add one conditional:

  • Condition: Numeric Comparison, guests Greater than 10.
  • Action 1: Field Requirements, required, on organiserPhone.
  • Action 2: Class Management, add the class is-required on organiserPhone, which the theme's CSS uses to show an asterisk on the label.

At 4 guests the field is optional and the server accepts it blank. At 12 the browser adds required and the asterisk, and the server also requires a phone number. Keep guests on the same step as the phone field, or use the JS model (below).

Conditional requirements and validation

Take a course form where Intake is shown, and required, only when Course is diploma. Build a field like this in one of three ways, and keep a form's rules from conflicting with each other. Use the first unless there is a reason not to:

  1. Not required, plus Field Requirements in the showing rule. Leave Required unticked and give the conditional that shows the field a second action that makes it required. It is one rule, and it covers every case.
  2. Required, plus an explicit hide rule for the opposite case. Show when Course Is equal to diploma and Hide when Course Is not equal to diploma. It works, but it is two rules that must stay exact opposites.
  3. Required, plus an "always hide" rule first. A conditional with no conditions and one action, hide Intake, placed before the showing rule. Both sides then start with the field hidden and show it on a match. It depends on the order of the conditionals, which nobody can see from the field.

Pair a ticked Required with one of these, and make a step's fields optional with a group (above). A hidden field passes every other check too: its value is cleared when it is hidden, and the regex, length and range checks all skip an empty answer.

Conditions that look at another step

The server evaluates a condition against the answers in the request it is handling. In the JS model every step arrives in one request, so a condition can refer to any step. In the page-per-step model each request carries only its own step's answers.

So in the page-per-step model, keep each condition on the same step as the field it affects. A branch that depends on an earlier step needs the JS model.

Keeping a branching form maintainable

A forty-field form with twenty conditionals is a program, and is looked after like one. The practices that keep it editable:

  • Name fields for what they mean, consistently: enquiryType, companyName, companySize. Conditions point at fields, and field_17 makes every rule unreadable.
  • Name every conditional after its effect: "Business enquiry: company details". The name is the only documentation the admin keeps.
  • Branch on as few fields as possible. One "Enquiry type" select that drives five groups is maintainable; five fields that each drive one rule are not.
  • Hide groups, not fields. One group rule replaces a rule per field, and group hiding works on the server.
  • Keep the requirement in the same conditional as the visibility (configuration 1 above), so the two cannot drift apart.
  • Write the branches down next to the form: a short table of "if this answer, then these questions". Test every row before launch by submitting, not only by looking, since only a submit exercises the server's side of a rule.
  • Change a live form in a new revision, test it, then activate it.

The requirement has outgrown a form when the logic needs data that is not in the form: prices, stock, the visitor's account, an outside system. It has also outgrown one when the conditionals are used to calculate something rather than to show and hide. At that point a page with its own JavaScript, posting to a custom endpoint or to the form submission route, is easier to build and to change. Buying something with stock, delivery or tax belongs in a checkout, not a form.

Go deeper

Was this page helpful?

Your answer goes to the docs team.