Skip to main content
Firma fields can react to what a signer has already entered. A field can appear only after another field is filled in, become required only when a checkbox is ticked, or belong to a mutually-exclusive group of options. This guide covers visibility_conditions, required_conditions, and multi_group_id — the three properties that drive this behavior — along with the mistakes that most often cause them to misbehave.

The condition model

Both visibility_conditions and required_conditions accept the same shape: a ConditionSet.
The inner logic is the opposite of the outer logic. logic: "and" combines the top-level groups with AND, but the conditions inside each group are combined with OR. logic: "or" does the reverse: groups are OR’d together, conditions inside each group are AND’d. This inversion is intentional — it’s what lets you express “(A or B) and (C or D)” as two groups under an outer and, or “(A and B) or (C and D)” as two groups under an outer or. A single group with a single condition behaves the same either way.

Operators

field_id must reference another field assigned to the same recipient as the field carrying the condition. Both the signing view and the server evaluate conditions using only that recipient’s own field values — a condition that references a field belonging to a different signer or approver will never resolve to a real value (see the stale-reference gotcha below). value accepts a string or number; omit it for is_filled/is_empty.
Limits enforced server-side: at most 20 groups per condition set, at most 20 conditions per group, field_id up to 100 characters, and a string value up to 1000 characters. These exist to bound evaluation cost, not to constrain realistic use — most condition sets use one or two groups.

Visibility conditions

Set visibility_conditions on a field to control whether it’s shown to the signer at all:
A field with no visibility_conditions is always visible. A field with visibility_conditions is visible only while the condition set evaluates to true, and hidden otherwise. A hidden field is also excluded from validation — it can’t block the signer from finishing, and it isn’t rendered in the signing view.

Required conditions

Set required_conditions to make a field’s required status depend on other field values, instead of being fixed at field-creation time:
required_conditions replaces required — it doesn’t combine with it. If required_conditions is present, the field is required exactly when the conditions evaluate to true, and the static required flag is ignored entirely. Setting both required: true and required_conditions does not mean “always required, and especially required under these conditions” — the top-level required value becomes irrelevant the moment required_conditions is set. Leave required at its default (false) on any field that carries required_conditions, so the intent in your source data matches the actual behavior.

A hidden field is excluded from required validation

A field whose visibility_conditions evaluate to false is excluded from required-field validation entirely — both client-side and server-side. This means a field can have required_conditions that evaluate to true while being hidden, and the signer will not be blocked. The risk is the opposite of what you might expect: required data you intended to collect can be silently skipped if the field is hidden by its visibility conditions. The template and signing request editors surface a live warning in the field properties panel when a field’s required and visibility conditions could disagree — but the check is a conservative heuristic (it flags structurally different condition sets, not just logically incompatible ones), so review any field carrying both properties by hand. The safest pattern is to make visibility_conditions a superset of required_conditions: whenever the field must be filled in, it’s also on-screen.

multi_group_id: linking checkboxes and radio buttons

multi_group_id links multiple checkbox or radio_buttons fields into one logical group. It’s a UUID, not a label — and the two field types behave differently once grouped.
multi_group_id must be a valid UUID. The database column is a native Postgres uuid type. If you send a plain string like "group-1" through the public API’s fields array (or through anchor_tags), field validation doesn’t reject the string up front — it passes the multi_group_id value straight through to the insert, where Postgres rejects it with invalid input syntax for type uuid. That failure surfaces as a generic 500 INTERNAL error with no indication that multi_group_id was the cause. Generate a real UUID (e.g. crypto.randomUUID() in JS, uuid4() in Python) and reuse the same value across every field in the group.The template editor in the Firma dashboard doesn’t have this problem — dragging a “Linked Radio Button” onto the canvas assigns a temporary group id that the template save path converts to a real UUID for you. The signing-request editor does not perform this conversion, so radio groups created there can hit the same UUID error. The UUID requirement bites when you’re building fields through the API or through the signing-request editor.

Radio buttons: mutually exclusive by design

Fields of type radio_buttons that share a multi_group_id are a single-choice group: selecting one deselects every other field in the group, both in the signing UI and in how the group’s required status resolves. Use this when you want the signer to pick exactly one option from a fixed set — a single field per option, all sharing one multi_group_id:
Only one field in this group can end up filled. required: true on a radio group means “the signer must pick one of the options” — the requirement is satisfied as soon as any single field in the group has a value.
The API’s type enum documents radio_buttons, but radio is also accepted and normalized to radio_buttons server-side — either spelling works.

Checkboxes: grouped for “pick at least one,” never exclusive

Fields of type checkbox that share a multi_group_id do not become mutually exclusive. Each checkbox in the group is still checked or unchecked independently — checking one does not uncheck the others. Grouping checkboxes only changes how the required status is evaluated: instead of every checkbox in the group needing to be checked, the group as a whole is satisfied once at least one checkbox in it is checked.
A signer can check any combination — one, two, or all three — and the requirement is met. If you actually want mutually-exclusive single-choice options rendered as checkboxes rather than circles, there’s no server-side flag for that — build the group with radio_buttons. multi_group_id on checkbox fields is for “select any of these, but at least one,” not for exclusivity.

Combining all three

A common real-world shape: a checkbox that reveals a text field, which is itself part of a radio choice elsewhere in the document.
Here opt-out-reason is hidden and optional until opt-out-checkbox is checked, at which point it becomes both visible and required — the identical condition set on both properties keeps them in lockstep, so the field is never required while hidden.

Gotchas

The mandatory-field counter can go backward as the signer fills in the form

The signing view shows a “X of Y required fields completed” indicator. Y (the total) is computed from whichever fields are currently required — including any field whose required_conditions just became true. That means checking a box that reveals a newly-required field increases Y immediately, while X (filled count) doesn’t change until the signer fills that new field in. The visible effect is the completion percentage dropping right after the signer answers a question, which reads as the counter “not updating” when it’s actually doing the opposite: updating to reflect a form that just got longer. There’s no way to avoid this if a conditional field is going to add a genuine new requirement, but you can minimize the jump by placing the fields that reveal new requirements early in the document, so the “reveal” happens before the signer has made much progress rather than near the end.

Conditions referencing a deleted or unreachable field never fire

If a field_id inside a condition doesn’t match any field the signer can see, the evaluator treats its value as empty — the condition doesn’t error, it just resolves as if that field were blank. is_empty, not_equals, and not_contains conditions against a missing field id evaluate to true; is_filled, equals, contains, and the numeric comparisons evaluate to false. A visibility_conditions set built entirely from stale field_id references (for example, equals/is_filled checks) will just make the field permanently hidden. If a field you expected to show up never does, confirm the field_id in its conditions still matches a real field’s id on that same signing request, and that the referenced field wasn’t removed later in a template edit.

Next steps

  • Field Prefilling — the properties that control a field’s displayed value, as distinct from whether it’s shown or required
  • Sending a Signing Request — the full recipient and field creation flow these fields live inside