Skip to main content
Turn prices, quantities, percentages and other numeric inputs into values that stay in sync. FLEX v4 provides reusable calculation plans, live field bindings and server-side validation.

Bind a form

Keep calculated fields current and recalculate them before saving.

Calculate anywhere

Reuse the same rules in services, actions and form workflows.

Validated calculation plans

Declare the inputs a calculation accepts and the outputs it produces. Every input is validated before any result is returned. A failed calculation returns errors and no partial output.
validated() returns the calculated values or throws Laravel’s ValidationException. Submitted values such as total => 999999 are ignored: outputs are always calculated from the declared inputs.

Input options

Zero is a value, not an empty input. Arrays, booleans, nonnumeric strings, infinity and numeric overflow are rejected. Numeric strings submitted by form controls are accepted.

Function policies

Omit allowedFunctions to enable the supported whitelist. Supply a subset to restrict a particular plan, or [] to allow only arithmetic and comparisons. Unknown functions and undeclared input references are rejected when constructing the plan.

Live calculated fields

Bind a plan to your own FLEX fields. Configure labels, appearance, sizes, defaults and field-specific behavior on those fields as usual.
1

Fill

Calculated fields are recomputed from current inputs when the form is filled, including when editing a record.
2

Change

Inputs update live with a 250 ms debounce. Invalid input clears calculated previews until the input becomes valid.
3

Save

Input rules run on the server. During form dehydration, outputs are calculated again; a manipulated preview cannot become the saved result.
bind() owns input hydration and calculated-field hydration, validation, save hooks and disabled state. Apply it after configuring your fields. Additional input update callbacks and validation rules are preserved. Do not replace the calculated-field hooks or enable the calculated fields afterward.

Keep your own input callbacks

Use schema() when your inputs already have afterStateHydrated() callbacks. It returns a layout group that refreshes calculated previews after every input has hydrated. The input callbacks remain intact, including when outputs appear before their inputs. Prefer schema() for large calculations: it refreshes the complete group once after initialization rather than refreshing after each input. bind() remains useful when you need a flat list of fields.
If a custom update callback changes another input, request that input’s update hooks:
Normalize inputs before validation. Callbacks that change input values after validation, replace calculated-field hooks or mutate the final saved results are your application’s responsibility. For a custom persistence workflow, evaluate and validate the final input data immediately before writing it.
Save through the normal Filament form lifecycle (getState()). Directly saving raw Livewire state bypasses form validation and dehydration. In a custom save action, call the plan’s evaluate(...)->validated() before persisting results.

Repeater rows and relationships

Every row calculates its own inputs; changing, reordering or removing one row does not reuse another row’s result. The same binding can be used in a JSON repeater or a HasMany relationship repeater:
Invalid numeric inputs and runtime errors, such as division by zero, fail form validation before relationship changes are saved. Invalid previews remain empty so the form can still be displayed. Use a database transaction around the complete save when parent records, related records or application callbacks must commit together. For a custom create workflow, create the parent first, assign it to the form and then save its relationships.

Multiple and nested calculations

A flat name prefix separates independent bindings in one schema. Array keys remain the plan’s input/output names; field names include the prefix.
For a nested schema, use its statePath() and bind locally named fields inside it. The parent schema owns the nesting; do not put a dotted state path in the binding’s prefix. Each group reads its own inputs.

Money and percentages

CurrencyField stores amounts in minor units: €100.00 is 10000 cents. Keep inputs and outputs in the same unit.
Money helpers accept fractionDigits from 0 to 6. Minor-unit inputs and rounded minor-unit outputs beyond ±9,007,199,254,740,991 are rejected to prevent silent integer precision loss. Calculations use PHP floating-point arithmetic: decimal values such as 0.1 + 0.2 can carry a rounding difference. Choose explicit rounding and bounded inputs for your application; this is not an arbitrary-precision accounting engine.

Common calculations

For values submitted by a user, declare the corresponding NumericInput contracts in a plan before using these expressions. Direct evaluate() and evaluateMap() are lower-level APIs for values whose application rules you already validate.

Formula language

References use {field_name}. Names begin with a letter or underscore and contain only letters, digits and underscores. Calculation input/output names have a 128-character limit. Use decimal literals, parentheses and these operators: Comparisons return 1 or 0; zero is false and nonzero is true. Division and modulo by zero are errors. Strings, arbitrary PHP, property access and function calls outside the whitelist are not supported.

Supported functions

Argument counts are checked even inside branches that will not execute. Valid unused branches remain lazy: if(1,20,10/0) returns 20.

Dependencies, cycles and incremental updates

A plan orders outputs by dependency. A cyclic graph is rejected before evaluation.
For a trusted server-side workflow, compile a map once and reuse it. Incremental evaluation returns only affected outputs when a complete previous result is available.
Without a complete prior result, the map performs a full evaluation. Do not use client-submitted previous outputs as an incremental baseline. Use a validated full plan for authoritative saves.

Inspect expressions and handle errors

For syntax or configuration errors, direct engine methods throw InvalidArgumentException. tryEvaluate() returns null if evaluation fails. Use it for optional display values; do not replace required validation with a silent fallback. Expressions are bounded to 8192 bytes, 2048 syntax nodes and 128 levels of syntax depth. A plan accepts at most 512 inputs and 512 calculated outputs. Non-finite intermediate results are rejected. Compiled syntax is reused in a bounded cache; submitted input values and results are not stored in that cache.

Explore in the playground

Open Calculated formulas to try five business scenarios, write expressions, inspect dependencies, compare incremental updates, test malformed inputs and use real bound fields. The code examples include complete input contracts and form bindings.

Currency field

Format amounts stored in minor units.

Flex Field Groups

Configure calculated values in dynamic form schemas.
Last modified on October 16, 2026