docusealco/docuseal · error · Submitters::SubmitValues::ValidationError

Formula infinite loop

Error message

Formula infinite loop

What it means

Raised by Submitters::SubmitValues.normalize_formula. Formula fields can reference other fields via {{uuid}} placeholders; when a referenced field itself has a preferences.formula, its formula is expanded recursively. A depth counter guards the expansion: recursion deeper than 10 levels means the formula graph is cyclic (or chained beyond 10 formula fields), so the calculation is aborted with 'Formula infinite loop'.

Solutions

  1. Open the template and remove the circular reference: a formula must not directly or indirectly reference its own field
  2. Flatten deep chains — reference plain number fields instead of stacking formula-on-formula more than 10 levels
  3. Audit every field's preferences.formula for {{uuid}} tokens that resolve to fields which themselves carry a formula
  4. If a two-way calculation is needed, split it into independent fields that read the same source fields

Example fix

# before (template field preferences)
# field A (formula): "{{B}} * 2"
# field B (formula): "{{A}} + 1"   -> cycle: A -> B -> A
# after
# field A (formula): "{{qty}} * {{price}}"
# field B (formula): "{{qty}} * {{price}} + {{tax}}"   (only non-formula references)
Defensive patterns

Strategy: validation

Validate before calling

formulas = submission.template_fields.to_h { |f| [f['uuid'], f.dig('preferences', 'formula').to_s] }
refs = formulas.transform_values { |s| s.scan(/\{\{(\w+)\}\}/).flatten }
cyclic = formulas.keys.any? do |uuid|
  stack = refs[uuid].dup
  seen = [uuid]
  until stack.empty?
    u = stack.pop
    next false if u == uuid || (seen.include?(u) && stack.empty? && u == uuid)
    break true if u == uuid
    seen << u if formulas.key?(u)
    stack.concat(refs[u]) if formulas.key?(u) && !seen.include?(u)
  end == true
end
# reject the template before any submission if cyclic

Try / catch

begin
  Submitters::SubmitValues.call(submitter, params, request)
rescue Submitters::SubmitValues::ValidationError => e
  if e.message == 'Formula infinite loop'
    Rollbar.warning("Cyclic formulas on submission #{submitter.submission_id}") if defined?(Rollbar)
  end
  raise
end

Prevention

When it happens

Trigger: A template formula field whose {{uuid}} placeholder points to another formula field that (directly or via a chain) points back — e.g. A = '{{B}} * 2' and B = '{{A}} + 1', or A = '{{A}}' — or a chain of more than 10 formula fields. Raised on every submit that computes formula values (build_formula_values).

Common situations: A template author copies a formula into a field that field references; a default_value or formula is edited to self-reference after launch, so all subsequent submissions fail; long formula-on-formula chains legitimately exceed depth 10; multi-submitter templates where merged values create an indirect cycle.

Related errors


AI-assisted analysis of docusealco/docuseal@004a22c1c8 (2026-08-21). Data as JSON: /api/errors/2879aac520144672. Report an issue: GitHub.

Appendix: source

Thrown at lib/submitters/submit_values.rb:239

          end

        values = submission_values.merge(acc.compact_blank)

        acc[field['uuid']] =
          if field['type'] == 'text'
            eval_text_formula_value(formula, values, submitter.submission)
          else
            normalized_formula = normalize_formula(formula, submitter.submission, submission_values:)

            calculate_formula_value(normalized_formula, values)
          end
      end

      computed_values.compact_blank
    end

    def normalize_formula(formula, submission, depth: 0, submission_values: nil)
      raise ValidationError, 'Formula infinite loop' if depth > 10

      formula.gsub(/{{(.*?)}}/) do |match|
        uuid = Regexp.last_match(1)

        if (nested_formula = submission.fields_uuid_index.dig(uuid, 'preferences', 'formula').presence)
          if check_field_conditions(submission_values, submission.fields_uuid_index[uuid], submission.fields_uuid_index)
            "(#{normalize_formula(nested_formula, submission, depth: depth + 1, submission_values:)})"
          else
            '0'
          end
        else
          match
        end
      end
    end

    def calculate_formula_value(_formula, _values)
      0

View on GitHub (pinned to 004a22c1c8)