You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] Workspace - No error shown when saving a report field with an invalid formula #104067
When you save a workspace report field whose default value has an invalid formula, the save fails silently. The new field disappears from the list and no error appears.
Reproducible in production
Yes
Action Performed
Open Workspace settings > Report fields.
Add a report field.
Enter an invalid formula in Default value, such as {field:DoesNotExist}.
Save the report field.
Expected Result
The save fails and the report fields page shows an error explaining that the formula is invalid.
Actual Result
The save fails, but no error is shown. The newly added report field disappears from the list.
Cause
The server correctly rejects the invalid formula. The App then fails to show that rejection:
The report field form doesn't check whether a {field:...} reference points to an existing field, so the request is sent to the server.
When the server rejects it, the App rolls back the optimistic field and stores the error on the policy's errorFields.fieldList.
Nothing on the report fields page reads or displays that error, so the user gets no feedback.
Suggested fix
Show the stored errorFields.fieldList error on the report fields page, so server rejections are visible and dismissible.
Optionally, validate {field:...} references in the form before saving, so this case is caught client-side.
Updated 2026-10-22: Updated based on feedback from shubham1206agra. The backend now returns the specific error keyed to the failed field, so this version reads that error and shows it on the field's own row.
Please re-state the problem that we are trying to solve in this issue.
When you save a report field whose default value has an invalid formula, such as {field:DoesNotExist}, the save fails. The new field disappears and no error appears.
What is the root cause of that problem?
The backend now returns the error in this shape. The App already uses this shape for the report title error:
errorFields.fieldList.<fieldKey>.defaultValue.<microtime>: "Formula field {field:DoesNotExist} not recognized"
The App doesn't use it:
On failure, createReportField removes the field from policy.fieldList, so its row disappears. It also writes only a generic error to errorFields[fieldKey].
In WorkspaceFieldsSection, each row's OfflineWithFeedback gets only pendingAction. Nothing reads errorFields.fieldList[fieldKey] or errorFields[fieldKey].
What changes do you think we should make in order to solve the problem?
Show the error on the failed field's row, as Categories and Tags do.
Keep the failed row. In createReportField's failureData, keep the policy field with pendingAction: ADD instead of setting it to null. Keep removing it from each report's fieldList, as the code does today. Keep the generic errorFields[fieldKey] error as a fallback.
Show the error on the row. In WorkspaceFieldsSection, give each row:
errors: the backend error, read with getLatestErrorField({errorFields: policy?.errorFields?.fieldList?.[fieldKey] ?? {}}, 'defaultValue'). If that is empty, use getLatestErrorField(policy, fieldKey).
onClose: call a new clearReportFieldErrors(policy, fieldID).
Add clearReportFieldErrors in ReportField.ts:
Clear errorFields.fieldList[fieldKey] and errorFields[fieldKey].
If the field's pendingAction is ADD, remove it from fieldList. This mirrors clearCategoryErrors.
Keep failed fields off reports.getAvailableReportFields merges every policy field into unsettled reports. Skip policy fields that have pendingAction: ADD and are not on the report already. Optimistic adds are written to each report's fieldList, so they still appear.
Clear errors narrowly. In createReportField and updateReportFieldInitialValue, the optimistic and success data set errorFields: null, which wipes every policy error, including the title error. Clear only {[fieldKey]: null, fieldList: {[fieldKey]: null}}.
updateReportFieldInitialValue already keeps the row and reverts the value on failure. After steps 2 and 3, its server error also appears on the row.
What alternative solutions did you explore? (Optional)
Show failures at the top of the section. Keep removing the field, and show any errorFields.fieldList key that has no matching field. This avoids step 4, but the field's name is lost, so the user can't tell which field failed.
Validate in the form. Flag unknown {field:X} references in CreateFieldsPage and FieldsInitialValuePage before saving. This is optional now, because the server returns a clear message.
Order of updates: the response onyxData is applied before failureData (OnyxUpdates.ts:74-99). That's why the generic fallback stays on a separate key: on the same key, it would have a newer timestamp and hide the specific message.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/104067#issuecomment-6189162199) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-]Workspace - No error shown when saving a report field with an invalid formula[/-][+][$175] Workspace - No error shown when saving a report field with an invalid formula[/+]on Oct 15, 2026
Please re-state the problem that we are trying to solve in this issue.
When a workspace report field is saved with an invalid formula as its default value (for example {field:DoesNotExist}), the save fails silently. The new field disappears from the list and no error is shown.
What is the root cause of that problem?
Two things combine here:
validateForm in CreateReportFieldsPage checks length, circular references and unsupported {report:...} parts, but not whether a {field:...} reference points to an existing field. The invalid value passes the form and is sent to the server.
When the server rejects it, the failureData in createReportField sets fieldList[fieldKey] to null, which removes the row, and writes the error to errorFields[fieldKey]. The row in WorkspaceReportsPage is rendered in an OfflineWithFeedback with only pendingAction (no errors or onClose), and nothing else on the page reads that error. The row disappears and the error is never displayed.
What changes do you think we should make in order to solve the problem?
Keep the failed row on the page with its error, the same way categories already do it.
In createReportField's failureData, keep the optimistic field with pendingAction: add and set errors on it (generic failure message), instead of setting it to null.
In WorkspaceReportsPage, pass the row's errors and an onClose handler to its OfflineWithFeedback.
Add a clearReportFieldErrors(policyID, fieldKey) action that mirrors clearCategoryErrors. If the field's pendingAction is add, remove it from fieldList. Otherwise, only clear its errors.
The user then sees the failed field with a red error, and clicking the X removes it. This covers every server-side rejection, not just formulas.
What alternative solutions did you explore? (Optional)
Validate the formula in the form before the API call. In validateForm, parse the default value with the existing parse from libs/Formula and show an inline error on the default value input for common invalid cases:
{field:X} where no field named X exists in policy.fieldList
Please re-state the problem that we are trying to solve in this issue.
In Workspace Settings > More features > Report fields, when creating a new report field with an invalid formula default value (e.g. {field:DoesNotExist}), saving the field fails silently. The newly created report field disappears completely from the list and no error message is displayed to the user.
What is the root cause of that problem?
Destructive Failure Data in Action:
In src/libs/actions/Policy/ReportField.ts (createReportField, lines 256–268), failureData deletes the optimistic field by setting fieldList: {[fieldKey]: null} while simultaneously writing the failure message to policy.errorFields[fieldKey].
List Rendering Disconnect in WorkspaceFieldsSection.tsx:
In src/pages/workspace/fields/WorkspaceFieldsSection.tsx (lines 136–181), menu items are mapped strictly from Object.values(policy.fieldList). Because failureData deleted the field from fieldList, no row exists in the DOM. Furthermore, WorkspaceFieldsSection.tsx never reads policy.errorFields for field keys, nor does it pass errors or an onClose callback to OfflineWithFeedback for field items. Consequently, the error is orphaned in Onyx and the field silently disappears.
What changes do you think we should make in order to solve the problem?
In src/libs/actions/Policy/ReportField.ts:
Update createReportField's failureData to retain the field in fieldList with errors attached instead of unmounting it to null, following Expensify's standard pattern (mirrored in Categories and Tags):
In src/pages/workspace/fields/WorkspaceFieldsSection.tsx:
In getMenuItems, pass errors={field.errors} and an onClose handler to OfflineWithFeedback for each report field row:
When dismissed via onClose, call a helper clearReportField(policyID, fieldKey). If field.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, it removes the field from fieldList ([fieldKey]: null); otherwise, it clears errors: null.
In src/libs/actions/Policy/ReportField.ts:
Export clearReportField(policyID: string, fieldKey: string) to cleanly dismiss the error and unlink failed optimistic additions from Onyx.
What alternative solutions did you explore? (Optional)
Displaying orphaned errorFields in a banner at the top of WorkspaceFieldsSection. However, keeping the failed item in-line with OfflineWithFeedback (and a red brick road indicator) preserves context for which field failed and provides a consistent UX matching Workspace Categories and Tags.
Please re-state the problem that we are trying to solve in this issue.
In Workspace Settings > Report fields, when adding or editing a report field with a default value formula containing a reference to a non-existent field (e.g. {field:DoesNotExist}), saving fails silently. The newly created report field disappears completely from the list and no error message is displayed to the user.
What is the root cause of that problem?
There are two interrelated root causes spanning client validation and failure handling:
Client-side formula validation ignores {field:...} tokens:
In src/pages/workspace/fields/CreateFieldsPage.tsx (validateForm), formula validation only checks for circular references (hasCircularReferences) and unsupported {report:...} parts (getUnsupportedReportFieldFormulaParts).
In src/libs/WorkspaceReportFieldUtils.ts (getUnsupportedReportFieldFormulaParts), only tokens of type FORMULA_PART_TYPES.REPORT are checked (if (part.type !== FORMULA_PART_TYPES.REPORT) continue;). Tokens of type FORMULA_PART_TYPES.FIELD are ignored. Therefore, references to non-existent fields like {field:DoesNotExist} pass client-side validation as valid and are dispatched to the server via createReportField.
Destructive failureData & Missing Section Error Display:
When the server rejects the invalid formula, the App applies failureData in src/libs/actions/Policy/ReportField.ts (createReportField):
Setting fieldList[fieldKey]: null removes the optimistic report field completely from policy.fieldList. Meanwhile, src/pages/workspace/fields/WorkspaceFieldsSection.tsx only renders items that exist in policy.fieldList (Object.values(fieldList).filter(...)) and its renderItem only passes pendingAction={item.pendingAction} to OfflineWithFeedback. It never reads or displays policy.errorFields.
Consequently, when the server rejects the request, the item is deleted from the UI with zero error message rendered.
What changes do you think we should make in order to solve the problem?
1. Validate {field:...} references on the client (WorkspaceReportFieldUtils.ts & CreateFieldsPage.tsx):
In src/libs/WorkspaceReportFieldUtils.ts, add a helper getInvalidReportFieldFormulaParts(initialValue?: string, fieldList?: Record<string, PolicyReportField>, expectedTarget?: ValueOf<typeof CONST.REPORT_FIELD_TARGETS>):
Parse the formula using Formula.parse(initialValue).
For parts of type FORMULA_PART_TYPES.FIELD: extract the referenced field name from part.fieldPath[0].
Verify whether the referenced field exists in the available fields for this policy and target (using getReportFieldsForTarget(fieldList, expectedTarget) and checking by name or fieldID).
If referenced field is missing, return the invalid token(s).
This catches the error immediately on form submission and displays an inline error under the Default Value field without sending an invalid request to the server.
2. Display server failure errors in WorkspaceFieldsSection.tsx:
In src/pages/workspace/fields/WorkspaceFieldsSection.tsx, wrap the list or section with an OfflineWithFeedback or render a dismissible error banner bound to policy?.errorFields for report fields:
And add clearReportFieldError in ReportField.ts to clear policy.errorFields[fieldKey].
What alternative solutions did you explore?
Only relying on server error display without client validation would create unnecessary network roundtrips and optimistic UI flickering (field appearing and disappearing). Validating on the client side provides immediate UX feedback while adding section error display ensures robustness for any other server-side rejection.
Please re-state the problem that we are trying to solve in this issue.
When creating a workspace report field with an invalid formula default value (such as {field:DoesNotExist}), the form submits the request to the server without validating {field:...} references. When the server rejects the request, the optimistic field is removed from the list and no error message is shown on screen.
What is the root cause of that problem?
Client-Side Formula Reference Validation Gap:
In src/pages/workspace/fields/CreateFieldsPage.tsx (validateForm), formula validation calls getUnsupportedReportFieldFormulaParts(formInitialValue). In src/libs/WorkspaceReportFieldUtils.ts, getUnsupportedReportFieldFormulaParts only checks tokens of type FORMULA_PART_TYPES.REPORT ({report:...}) and skips FORMULA_PART_TYPES.FIELD ({field:...}). As a result, references to non-existent fields like {field:DoesNotExist} bypass form validation and are sent to the server.
Unrendered Failure Error on Report Fields Section:
When createReportField in src/libs/actions/Policy/ReportField.ts fails on the server, failureData removes the optimistic field (fieldList[fieldKey]: null) and writes the error to policy.errorFields[fieldKey]. Because the field is removed from policy.fieldList, WorkspaceFieldsSection.tsx no longer renders a row for it, and WorkspaceFieldsSection.tsx never passes errors or onCloseError to ToggleSettingOptionRow. The field disappears silently while leaving an orphaned error key on policy.errorFields.
What changes do you think we should make in order to solve the problem?
Validate {field:...} References in src/libs/WorkspaceReportFieldUtils.ts:
Update the dynamic require type for FORMULA_PART_TYPES in getUnsupportedReportFieldFormulaParts to {REPORT: string; FIELD: string} and add an optional second parameter availableFields?: Record<string, PolicyReportField>.
When part.type === FORMULA_PART_TYPES.FIELD and availableFields !== undefined, extract const [fieldName] = part.fieldPath ?? [] and normalize with const normalizedReferencedName = fieldName?.toLowerCase() (matching the case-insensitive lookup without trimming in Formula.ts:468 and ReportUtils.ts:14962). If !normalizedReferencedName or no field in Object.values(availableFields) has field.name.toLowerCase() === normalizedReferencedName, push part.definition to unsupported.
Enforce Field Reference Validation in CreateFieldsPage.tsx and FieldsInitialValuePage.tsx:
In CreateFieldsPage.tsx (validateForm), inside the existing (type === CONST.REPORT_FIELD_TYPES.TEXT || type === CONST.REPORT_FIELD_TYPES.FORMULA) && !!formInitialValue && !errors[INPUT_IDS.INITIAL_VALUE] guard, compute const targetFields = getReportFieldsForTarget(policy?.fieldList, fieldTarget) and pass targetFields into getUnsupportedReportFieldFormulaParts(formInitialValue, targetFields).
Apply the same check in FieldsInitialValuePage.tsx (validateForm) behind !errors[INPUT_IDS.INITIAL_VALUE] when reportField?.type is TEXT or FORMULA and !!formInitialValue, passing getReportFieldsForTarget(policy?.fieldList, expectedTarget) into getUnsupportedReportFieldFormulaParts(formInitialValue, targetFields) and setting errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.unsupportedFormulaValueError', unsupportedFormulaParts.join(', ')). Because getReportFieldsForTarget returns {} when policy?.fieldList is empty or undefined, {field:DoesNotExist} is rejected even on workspaces with zero existing fields, while keeping circular reference errors higher priority.
Scope and Render Section Failure Errors in ReportField.ts and WorkspaceFieldsSection.tsx:
In optimisticData and successData, clear errorFields: {[sectionErrorKey]: null} instead of wiping the entire errorFields object (preserving unrelated policy toggle or report title errors).
In failureData, set fieldList: {[fieldKey]: null} and write the error exclusively to errorFields: {[sectionErrorKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.reportFields.genericFailureMessage')} (omitting [fieldKey] so dismissing the banner leaves no orphaned red-brick-road badge on the workspace).
In src/pages/workspace/fields/WorkspaceFieldsSection.tsx, derive const sectionErrorKey = titleKey === 'workspace.common.invoiceFields' ? 'invoiceFieldsError' : 'reportFieldsError' and pass errors={policy?.errorFields?.[sectionErrorKey] ?? undefined} and onCloseError={() => clearPolicyErrorField(policyID, sectionErrorKey)} to ToggleSettingOptionRow.
What alternative solutions did you explore? (Optional)
I considered scanning policy.errorFields for keys starting with expensify_ that are missing from policy.fieldList. However, because WorkspaceFieldsSection renders both Expense Report Fields and Invoice Fields, and failureData deletes fieldList[fieldKey] (removing field.target), prefix scanning cannot distinguish whether a failed creation belonged to Expense Report Fields or Invoice Fields. I also considered keeping rejected fields inside policy.fieldList with pendingAction: ADD, but retaining server-rejected entries in policy.fieldList exposes invalid fields to downstream pickers and formula resolvers. Dedicated reportFieldsError and invoiceFieldsError keys isolate both sections cleanly.
Test Plan
Preconditions:
Log into an account with admin access to a Control workspace with Report Fields enabled.
Manual Verification:
Navigate to Workspace settings > Report fields > Add report field, set Type to Formula, enter {field:DoesNotExist} as the Default value, and click Save. Verify an inline error ("Formula field {field:DoesNotExist} not recognized") blocks submission without sending a network request.
Create a text field named Department, then create a formula field with {field:department} and verify case-insensitive matching succeeds. Edit its initial value to {field:Ghost} and verify inline validation blocks saving.
Simulate a server failure on CreatePolicyReportField and verify the error banner appears on the Report Fields section header, does not bleed into Invoice Fields, and clears cleanly with no residual workspace RBR badge when dismissed.
Regression Verification:
Verify circular reference formulas still display the circular reference error first.
Verify existing report field creation, initial value editing, deletion, and report title settings (errorFields.fieldList) continue working without regressions, and run npm run test tests/unit/WorkspaceReportFieldUtilsTest.ts.
Done, dukenv0307. I created an internal issue for the backend change and assigned it to you.
It asks the backend to return report field save errors keyed to the specific field, not the generic errorFields.fieldList key. The App can then show the error on the failed field's row.
The App reads it with getLatestErrorField({errorFields: policy?.errorFields?.fieldList?.[key] ?? {}}, 'defaultValue'): WorkspaceReportsPage.tsx:67-68.
A report field can use the same read with its own fieldKey. To clear it, merge errorFields.fieldList[fieldKey] = null, as clearPolicyTitleFieldError does.
On the App side, createReportField's failureData still writes a generic error to errorFields[fieldKey]. It also removes the field from fieldList, so the field's row disappears (ReportField.ts:256-268). The App fix should read the new path. It should also keep the failed row, or show the error elsewhere on the page.
I updated my proposal. It now uses the new backend error at errorFields.fieldList[fieldKey].defaultValue.
Keep the failed row: On failure, the new field stays in the list with pendingAction: ADD instead of being removed.
Show the error on the row: The row shows the server message, such as "Formula field {field:DoesNotExist} not recognized." If the server sends no message, the row shows the generic error.
Dismiss: Dismissing the error clears it and removes the failed field, as Categories do.
Keep failed fields off reports: Failed fields don't show on unsettled reports while they wait to be dismissed.
Clear errors narrowly: Saving clears only that field's errors, not every policy error.
@shubham1206agra Thank you for the clarification in your comment above!
I tested MelvinBot’s approach locally and noticed a few clear issues:
The dismiss button does not work: When you click the (X) to close the error banner, the error stays on the screen, and the red dot next to the workspace never goes away.
The.dismiss.button.does.not.work.webm
The field completely disappears: When saving fails, the field vanishes from the list, and a disconnected error message is dumped at the top of the page.
The.field.completely.disappears.webm
Generic error message: It only shows a generic failure message instead of telling the user what actually went wrong with the formula.
Too many file changes: Melvin modifies 4 files with extra duplicate validation code, making it harder to review.
How My Solution Fixes This (Standard Expensify Design)
Minimal & Clean: Touches only 2 files instead of 4, keeping the fix small and easy to review.
The field stays visible: Just like Categories and Tags, the failed field remains in the list with a red dot so the user can see what failed.
Shows the real error: It displays the exact message from the server (e.g., "Formula field {field:DoesNotExist} not recognized").
Working dismiss button: Clicking (X) cleanly closes the error, removes the failed field, and clears the red dot immediately.
A field that fails to save stays in the list, with the server error on its row.
Dismissing the error removes the failed field.
Failed fields stay off reports.
Saving clears only that field's errors, so the report title error is kept.
Typecheck, lint, spell check, and the related unit tests pass. The web browser test was blocked because the test session showed a blank page, so please verify the flow manually.
Activity
MelvinBot commentedon Oct 15, 2026
Proposal
Updated 2026-10-22: Updated based on feedback from
shubham1206agra. The backend now returns the specific error keyed to the failed field, so this version reads that error and shows it on the field's own row.Please re-state the problem that we are trying to solve in this issue.
When you save a report field whose default value has an invalid formula, such as
{field:DoesNotExist}, the save fails. The new field disappears and no error appears.What is the root cause of that problem?
The backend now returns the error in this shape. The App already uses this shape for the report title error:
The App doesn't use it:
createReportFieldremoves the field frompolicy.fieldList, so its row disappears. It also writes only a generic error toerrorFields[fieldKey].WorkspaceFieldsSection, each row'sOfflineWithFeedbackgets onlypendingAction. Nothing readserrorFields.fieldList[fieldKey]orerrorFields[fieldKey].What changes do you think we should make in order to solve the problem?
Show the error on the failed field's row, as Categories and Tags do.
createReportField'sfailureData, keep the policy field withpendingAction: ADDinstead of setting it tonull. Keep removing it from each report'sfieldList, as the code does today. Keep the genericerrorFields[fieldKey]error as a fallback.WorkspaceFieldsSection, give each row:errors: the backend error, read withgetLatestErrorField({errorFields: policy?.errorFields?.fieldList?.[fieldKey] ?? {}}, 'defaultValue'). If that is empty, usegetLatestErrorField(policy, fieldKey).onClose: call a newclearReportFieldErrors(policy, fieldID).clearReportFieldErrorsinReportField.ts:errorFields.fieldList[fieldKey]anderrorFields[fieldKey].pendingActionisADD, remove it fromfieldList. This mirrorsclearCategoryErrors.getAvailableReportFieldsmerges every policy field into unsettled reports. Skip policy fields that havependingAction: ADDand are not on the report already. Optimistic adds are written to each report'sfieldList, so they still appear.createReportFieldandupdateReportFieldInitialValue, the optimistic and success data seterrorFields: null, which wipes every policy error, including the title error. Clear only{[fieldKey]: null, fieldList: {[fieldKey]: null}}.updateReportFieldInitialValuealready keeps the row and reverts the value on failure. After steps 2 and 3, its server error also appears on the row.What alternative solutions did you explore? (Optional)
errorFields.fieldListkey that has no matching field. This avoids step 4, but the field's name is lost, so the user can't tell which field failed.{field:X}references inCreateFieldsPageandFieldsInitialValuePagebefore saving. This is optional now, because the server returns a clear message.Code references
onyxDatais applied beforefailureData(OnyxUpdates.ts:74-99). That's why the generic fallback stays on a separate key: on the same key, it would have a newer timestamp and hide the specific message.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/104067#issuecomment-6189162199)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
melvin-bot commentedon Oct 15, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @shubham1206agra (
External)[-]Workspace - No error shown when saving a report field with an invalid formula[/-][+][$175] Workspace - No error shown when saving a report field with an invalid formula[/+]melvin-bot commentedon Oct 15, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022110667854459323263
ashu75575 commentedon Oct 15, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a workspace report field is saved with an invalid formula as its default value (for example
{field:DoesNotExist}), the save fails silently. The new field disappears from the list and no error is shown.What is the root cause of that problem?
Two things combine here:
validateForminCreateReportFieldsPagechecks length, circular references and unsupported{report:...}parts, but not whether a{field:...}reference points to an existing field. The invalid value passes the form and is sent to the server.failureDataincreateReportFieldsetsfieldList[fieldKey]tonull, which removes the row, and writes the error toerrorFields[fieldKey]. The row inWorkspaceReportsPageis rendered in anOfflineWithFeedbackwith onlypendingAction(noerrorsoronClose), and nothing else on the page reads that error. The row disappears and the error is never displayed.What changes do you think we should make in order to solve the problem?
Keep the failed row on the page with its error, the same way categories already do it.
createReportField'sfailureData, keep the optimistic field withpendingAction: addand seterrorson it (generic failure message), instead of setting it tonull.WorkspaceReportsPage, pass the row'serrorsand anonClosehandler to itsOfflineWithFeedback.clearReportFieldErrors(policyID, fieldKey)action that mirrorsclearCategoryErrors. If the field'spendingActionisadd, remove it fromfieldList. Otherwise, only clear itserrors.The user then sees the failed field with a red error, and clicking the X removes it. This covers every server-side rejection, not just formulas.
What alternative solutions did you explore? (Optional)
Validate the formula in the form before the API call. In
validateForm, parse the default value with the existingparsefromlibs/Formulaand show an inline error on the default value input for common invalid cases:{field:X}where no field named X exists inpolicy.fieldList{field:}with an empty field name{without a matching}camesenin commentedon Oct 15, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
In Workspace Settings > More features > Report fields, when creating a new report field with an invalid formula default value (e.g.
{field:DoesNotExist}), saving the field fails silently. The newly created report field disappears completely from the list and no error message is displayed to the user.What is the root cause of that problem?
In
src/libs/actions/Policy/ReportField.ts(createReportField, lines 256–268),failureDatadeletes the optimistic field by settingfieldList: {[fieldKey]: null}while simultaneously writing the failure message topolicy.errorFields[fieldKey].WorkspaceFieldsSection.tsx:In
src/pages/workspace/fields/WorkspaceFieldsSection.tsx(lines 136–181), menu items are mapped strictly fromObject.values(policy.fieldList). BecausefailureDatadeleted the field fromfieldList, no row exists in the DOM. Furthermore,WorkspaceFieldsSection.tsxnever readspolicy.errorFieldsfor field keys, nor does it passerrorsor anonClosecallback toOfflineWithFeedbackfor field items. Consequently, the error is orphaned in Onyx and the field silently disappears.What changes do you think we should make in order to solve the problem?
In
src/libs/actions/Policy/ReportField.ts:Update
createReportField'sfailureDatato retain the field infieldListwitherrorsattached instead of unmounting it tonull, following Expensify's standard pattern (mirrored in Categories and Tags):In
src/pages/workspace/fields/WorkspaceFieldsSection.tsx:In
getMenuItems, passerrors={field.errors}and anonClosehandler toOfflineWithFeedbackfor each report field row:onClose, call a helperclearReportField(policyID, fieldKey). Iffield.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, it removes the field fromfieldList([fieldKey]: null); otherwise, it clearserrors: null.In
src/libs/actions/Policy/ReportField.ts:Export
clearReportField(policyID: string, fieldKey: string)to cleanly dismiss the error and unlink failed optimistic additions from Onyx.What alternative solutions did you explore? (Optional)
Displaying orphaned
errorFieldsin a banner at the top ofWorkspaceFieldsSection. However, keeping the failed item in-line withOfflineWithFeedback(and a red brick road indicator) preserves context for which field failed and provides a consistent UX matching Workspace Categories and Tags.Contributor details
Your Expensify account email: camesenin@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0170d6b02e9440e604
melvin-bot commentedon Oct 15, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
janiya2 commentedon Oct 15, 2026
Contributor details
Your Expensify account email: janithad2@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/janithad
Proposal
Please re-state the problem that we are trying to solve in this issue.
In Workspace Settings > Report fields, when adding or editing a report field with a default value formula containing a reference to a non-existent field (e.g.
{field:DoesNotExist}), saving fails silently. The newly created report field disappears completely from the list and no error message is displayed to the user.What is the root cause of that problem?
There are two interrelated root causes spanning client validation and failure handling:
Client-side formula validation ignores
{field:...}tokens:In
src/pages/workspace/fields/CreateFieldsPage.tsx(validateForm), formula validation only checks for circular references (hasCircularReferences) and unsupported{report:...}parts (getUnsupportedReportFieldFormulaParts).In
src/libs/WorkspaceReportFieldUtils.ts(getUnsupportedReportFieldFormulaParts), only tokens of typeFORMULA_PART_TYPES.REPORTare checked (if (part.type !== FORMULA_PART_TYPES.REPORT) continue;). Tokens of typeFORMULA_PART_TYPES.FIELDare ignored. Therefore, references to non-existent fields like{field:DoesNotExist}pass client-side validation as valid and are dispatched to the server viacreateReportField.Destructive
failureData& Missing Section Error Display:When the server rejects the invalid formula, the App applies
failureDatainsrc/libs/actions/Policy/ReportField.ts(createReportField):Setting
fieldList[fieldKey]: nullremoves the optimistic report field completely frompolicy.fieldList. Meanwhile,src/pages/workspace/fields/WorkspaceFieldsSection.tsxonly renders items that exist inpolicy.fieldList(Object.values(fieldList).filter(...)) and itsrenderItemonly passespendingAction={item.pendingAction}toOfflineWithFeedback. It never reads or displayspolicy.errorFields.Consequently, when the server rejects the request, the item is deleted from the UI with zero error message rendered.
What changes do you think we should make in order to solve the problem?
1. Validate
{field:...}references on the client (WorkspaceReportFieldUtils.ts&CreateFieldsPage.tsx):In
src/libs/WorkspaceReportFieldUtils.ts, add a helpergetInvalidReportFieldFormulaParts(initialValue?: string, fieldList?: Record<string, PolicyReportField>, expectedTarget?: ValueOf<typeof CONST.REPORT_FIELD_TARGETS>):Formula.parse(initialValue).FORMULA_PART_TYPES.FIELD: extract the referenced field name frompart.fieldPath[0].getReportFieldsForTarget(fieldList, expectedTarget)and checking bynameorfieldID).In
CreateFieldsPage.tsx->validateForm:This catches the error immediately on form submission and displays an inline error under the Default Value field without sending an invalid request to the server.
2. Display server failure errors in
WorkspaceFieldsSection.tsx:In
src/pages/workspace/fields/WorkspaceFieldsSection.tsx, wrap the list or section with anOfflineWithFeedbackor render a dismissible error banner bound topolicy?.errorFieldsfor report fields:And add
clearReportFieldErrorinReportField.tsto clearpolicy.errorFields[fieldKey].What alternative solutions did you explore?
Only relying on server error display without client validation would create unnecessary network roundtrips and optimistic UI flickering (field appearing and disappearing). Validating on the client side provides immediate UX feedback while adding section error display ensures robustness for any other server-side rejection.
melvin-bot commentedon Oct 15, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
taieddie commentedon Oct 15, 2026
Contributor details
Your Expensify account email: taieddie8@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~01e51b3f9ff738cf94
Proposal
Please re-state the problem that we are trying to solve in this issue.
When creating a workspace report field with an invalid formula default value (such as
{field:DoesNotExist}), the form submits the request to the server without validating{field:...}references. When the server rejects the request, the optimistic field is removed from the list and no error message is shown on screen.What is the root cause of that problem?
Client-Side Formula Reference Validation Gap:
In
src/pages/workspace/fields/CreateFieldsPage.tsx(validateForm), formula validation callsgetUnsupportedReportFieldFormulaParts(formInitialValue). Insrc/libs/WorkspaceReportFieldUtils.ts,getUnsupportedReportFieldFormulaPartsonly checks tokens of typeFORMULA_PART_TYPES.REPORT({report:...}) and skipsFORMULA_PART_TYPES.FIELD({field:...}). As a result, references to non-existent fields like{field:DoesNotExist}bypass form validation and are sent to the server.Unrendered Failure Error on Report Fields Section:
When
createReportFieldinsrc/libs/actions/Policy/ReportField.tsfails on the server,failureDataremoves the optimistic field (fieldList[fieldKey]: null) and writes the error topolicy.errorFields[fieldKey]. Because the field is removed frompolicy.fieldList,WorkspaceFieldsSection.tsxno longer renders a row for it, andWorkspaceFieldsSection.tsxnever passeserrorsoronCloseErrortoToggleSettingOptionRow. The field disappears silently while leaving an orphaned error key onpolicy.errorFields.What changes do you think we should make in order to solve the problem?
Validate
{field:...}References insrc/libs/WorkspaceReportFieldUtils.ts:FORMULA_PART_TYPESingetUnsupportedReportFieldFormulaPartsto{REPORT: string; FIELD: string}and add an optional second parameteravailableFields?: Record<string, PolicyReportField>.part.type === FORMULA_PART_TYPES.FIELDandavailableFields !== undefined, extractconst [fieldName] = part.fieldPath ?? []and normalize withconst normalizedReferencedName = fieldName?.toLowerCase()(matching the case-insensitive lookup without trimming inFormula.ts:468andReportUtils.ts:14962). If!normalizedReferencedNameor no field inObject.values(availableFields)hasfield.name.toLowerCase() === normalizedReferencedName, pushpart.definitiontounsupported.Enforce Field Reference Validation in
CreateFieldsPage.tsxandFieldsInitialValuePage.tsx:CreateFieldsPage.tsx(validateForm), inside the existing(type === CONST.REPORT_FIELD_TYPES.TEXT || type === CONST.REPORT_FIELD_TYPES.FORMULA) && !!formInitialValue && !errors[INPUT_IDS.INITIAL_VALUE]guard, computeconst targetFields = getReportFieldsForTarget(policy?.fieldList, fieldTarget)and passtargetFieldsintogetUnsupportedReportFieldFormulaParts(formInitialValue, targetFields).FieldsInitialValuePage.tsx(validateForm) behind!errors[INPUT_IDS.INITIAL_VALUE]whenreportField?.typeisTEXTorFORMULAand!!formInitialValue, passinggetReportFieldsForTarget(policy?.fieldList, expectedTarget)intogetUnsupportedReportFieldFormulaParts(formInitialValue, targetFields)and settingerrors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.unsupportedFormulaValueError', unsupportedFormulaParts.join(', ')). BecausegetReportFieldsForTargetreturns{}whenpolicy?.fieldListis empty or undefined,{field:DoesNotExist}is rejected even on workspaces with zero existing fields, while keeping circular reference errors higher priority.Scope and Render Section Failure Errors in
ReportField.tsandWorkspaceFieldsSection.tsx:src/libs/actions/Policy/ReportField.ts(createReportField), defineconst sectionErrorKey = isInvoiceField ? 'invoiceFieldsError' : 'reportFieldsError'.optimisticDataandsuccessData, clearerrorFields: {[sectionErrorKey]: null}instead of wiping the entireerrorFieldsobject (preserving unrelated policy toggle or report title errors).failureData, setfieldList: {[fieldKey]: null}and write the error exclusively toerrorFields: {[sectionErrorKey]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.reportFields.genericFailureMessage')}(omitting[fieldKey]so dismissing the banner leaves no orphaned red-brick-road badge on the workspace).src/pages/workspace/fields/WorkspaceFieldsSection.tsx, deriveconst sectionErrorKey = titleKey === 'workspace.common.invoiceFields' ? 'invoiceFieldsError' : 'reportFieldsError'and passerrors={policy?.errorFields?.[sectionErrorKey] ?? undefined}andonCloseError={() => clearPolicyErrorField(policyID, sectionErrorKey)}toToggleSettingOptionRow.What alternative solutions did you explore? (Optional)
I considered scanning
policy.errorFieldsfor keys starting withexpensify_that are missing frompolicy.fieldList. However, becauseWorkspaceFieldsSectionrenders both Expense Report Fields and Invoice Fields, andfailureDatadeletesfieldList[fieldKey](removingfield.target), prefix scanning cannot distinguish whether a failed creation belonged to Expense Report Fields or Invoice Fields. I also considered keeping rejected fields insidepolicy.fieldListwithpendingAction: ADD, but retaining server-rejected entries inpolicy.fieldListexposes invalid fields to downstream pickers and formula resolvers. DedicatedreportFieldsErrorandinvoiceFieldsErrorkeys isolate both sections cleanly.Test Plan
Preconditions:
Manual Verification:
{field:DoesNotExist}as the Default value, and click Save. Verify an inline error ("Formula field {field:DoesNotExist} not recognized") blocks submission without sending a network request.Department, then create a formula field with{field:department}and verify case-insensitive matching succeeds. Edit its initial value to{field:Ghost}and verify inline validation blocks saving.CreatePolicyReportFieldand verify the error banner appears on the Report Fields section header, does not bleed into Invoice Fields, and clears cleanly with no residual workspace RBR badge when dismissed.Regression Verification:
errorFields.fieldList) continue working without regressions, and runnpm run test tests/unit/WorkspaceReportFieldUtilsTest.ts.melvin-bot commentedon Oct 15, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
12 remaining items
dukenv0307 commentedon Oct 20, 2026
@MelvinBot create an issue in E/E then assign me
MelvinBot commentedon Oct 20, 2026
Done,
dukenv0307. I created an internal issue for the backend change and assigned it to you.It asks the backend to return report field save errors keyed to the specific field, not the generic
errorFields.fieldListkey. The App can then show the error on the failed field's row.view run
dukenv0307 commentedon Oct 22, 2026
BE PR is deployed to staging
shubham1206agra commentedon Oct 22, 2026
@dukenv0307 Maybe you put the error too deep. @MelvinBot Can you check and confirm this?
{ "code": 666, "jsonCode": 666, "type": "Expensify\\Libs\\Error\\ExpError", "UUID": "1A68E442-5F40-440B-9A37-405C0DFC9471", "message": "Formula field {field:DoesNotExist} not recognized", "title": "", "data": { "field_id_LO": [ "Formula field {field:DoesNotExist} not recognized" ], "onyxData": [ { "onyxMethod": "merge", "key": "policy_5B0B81C5BF426725", "value": { "errorFields": { "fieldList": { "expensify_field_id_LO": { "defaultValue": { "1792680689003780": "Formula field {field:DoesNotExist} not recognized" } } } } } } ] }, "htmlMessage": "", "onyxData": [ { "onyxMethod": "merge", "key": "policy_5B0B81C5BF426725", "value": { "errorFields": { "fieldList": { "expensify_field_id_LO": { "defaultValue": { "1792680689003780": "Formula field {field:DoesNotExist} not recognized" } } } } } } ], "requestID": "a4e952006ced8af8-DEL" }MelvinBot commentedon Oct 22, 2026
No, the error isn't nested too deep. The new response uses the same shape the App already uses for the report title field error:
errorFields.fieldList.<fieldKey>.defaultValue.<microtime>The key also matches what the App generates. A field named "lo" gets
expensify_field_id_LOfromgenerateFieldIDandgetReportFieldKey.Evidence and App-side follow-up
getLatestErrorField({errorFields: policy?.errorFields?.fieldList?.[key] ?? {}}, 'defaultValue'): WorkspaceReportsPage.tsx:67-68.fieldKey. To clear it, mergeerrorFields.fieldList[fieldKey] = null, asclearPolicyTitleFieldErrordoes.On the App side,
createReportField'sfailureDatastill writes a generic error toerrorFields[fieldKey]. It also removes the field fromfieldList, so the field's row disappears (ReportField.ts:256-268). The App fix should read the new path. It should also keep the failed row, or show the error elsewhere on the page.view run
shubham1206agra commentedon Oct 22, 2026
@MelvinBot Update the proposal
MelvinBot commentedon Oct 22, 2026
I updated my proposal. It now uses the new backend error at
errorFields.fieldList[fieldKey].defaultValue.pendingAction: ADDinstead of being removed.view run
emkhalid commentedon Oct 22, 2026
@shubham1206agra Thank you for the clarification in your comment above!
I tested MelvinBot’s approach locally and noticed a few clear issues:
(X)to close the error banner, the error stays on the screen, and the red dot next to the workspace never goes away.The.dismiss.button.does.not.work.webm
The.field.completely.disappears.webm
How My Solution Fixes This (Standard Expensify Design)
Minimal & Clean: Touches only 2 files instead of 4, keeping the fix small and easy to review.
The field stays visible: Just like Categories and Tags, the failed field remains in the list with a red dot so the user can see what failed.
Shows the real error: It displays the exact message from the server (e.g., "Formula field {field:DoesNotExist} not recognized").
Working dismiss button: Clicking
(X)cleanly closes the error, removes the failed field, and clears the red dot immediately.Fix demo
fixDemo104067.webm
Would you like me to share the full proposal ?
shubham1206agra commentedon Oct 25, 2026
@MelvinBot Implement #104067 (comment)
MelvinBot commentedon Oct 25, 2026
🤖 Draft PR created: Show report field save errors on the field's row
Please mark it as "Ready for review" or ask me to do that for you when it is ready for review.
It implements the proposal:
Typecheck, lint, spell check, and the related unit tests pass. The web browser test was blocked because the test session showed a blank page, so please verify the flow manually.
view run · no recording available