Skip to main content
Approval workflows define how expense reports are reviewed and approved in your organization. This guide explains how to configure and use approval workflows effectively.

Understanding Approval Workflows

Approval workflows determine who needs to approve expense reports before they can be reimbursed. New Expensify supports flexible workflows from simple to complex multi-level approvals.

Approval Modes

New Expensify offers several approval modes:
No approvals required. Reports are automatically approved upon submission.

Configuring Approval Workflows

Enabling Approvals

To enable approval workflows for your workspace:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
const optionItems: ToggleSettingOptionRowProps[] = useMemo(() => {
    return [
        {
            title: translate('workflowsPage.addApprovalsTitle'),
            subtitle: isSmartLimitEnabled ? translate('workspace.moreFeatures.workflows.disableApprovalPrompt') : translate('workflowsPage.addApprovalsDescription'),
            switchAccessibilityLabel: isSmartLimitEnabled ? translate('workspace.moreFeatures.workflows.disableApprovalPrompt') : translate('workflowsPage.addApprovalsDescription'),
            onToggle: (isEnabled: boolean) => {
                if (!isEnabled) {
                    showConfirmModal({
                        title: translate('workspace.bankAccount.areYouSure'),
                        prompt: translate('workflowsPage.disableApprovalPromptDescription'),
                        confirmText: translate('common.disable'),
                        cancelText: translate('common.cancel'),
                        danger: true,
                    }).then((result) => {
                        if (result.action !== ModalActions.CONFIRM) {
                            return;
                        }
                        confirmDisableApprovals();
                    });
                    return;
                }
                setWorkspaceApprovalMode(route.params.policyID, policy?.owner ?? '', isEnabled ? updateApprovalMode : CONST.POLICY.APPROVAL_MODE.OPTIONAL, {
                    reportNextSteps: allReportNextSteps,
                    transactionViolations,
                    betas,
                });
            },
            isActive: isDEWEnabled || (([CONST.POLICY.APPROVAL_MODE.BASIC, CONST.POLICY.APPROVAL_MODE.ADVANCED].some((approvalMode) => approvalMode === policy?.approvalMode) && !hasApprovalError) ?? false),
        },
    ];
}, [/* dependencies */]);

Setting Up Approval Workflows

Approval workflows can be configured with specific members and rules:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
const {approvalWorkflows, availableMembers, usedApproverEmails} = convertPolicyEmployeesToApprovalWorkflows({
    policy,
    personalDetails: personalDetails ?? {},
    localeCompare,
});

const isAdvanceApproval = (approvalWorkflows.length > 1 || (approvalWorkflows?.at(0)?.approvers ?? []).length > 1) && isControlPolicy(policy);
const updateApprovalMode = isAdvanceApproval ? CONST.POLICY.APPROVAL_MODE.ADVANCED : CONST.POLICY.APPROVAL_MODE.BASIC;

Managing Approval Workflows

Viewing Approval Workflows

The workflows page displays all configured approval workflows:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
<>
    {isDEWEnabled && (
        <View style={[styles.border, shouldUseNarrowLayout ? styles.p3 : styles.p4, styles.mt6, styles.mbn3, styles.flexRow, styles.alignItemsCenter]}>
            <Icon
                src={expensifyIcons.Info}
                fill={theme.textSupporting}
                additionalStyles={styles.popoverMenuIcon}
            />
            <View style={[styles.flex1, styles.ml3]}>
                <RenderHTML
                    html={
                        accountManagerReportID
                            ? translate('workflowsPage.customApprovalWorkflowEnabled')
                            : translate('workflowsPage.customApprovalWorkflowEnabledConciergeOnly')
                    }
                />
            </View>
        </View>
    )}
    {filteredApprovalWorkflows.length > CONST.APPROVAL_WORKFLOW_SEARCH_LIMIT && (
        <SearchBar
            label={translate('workflowsPage.findWorkflow')}
            inputValue={workflowSearchInput}
            onChangeText={setWorkflowSearchInput}
            style={[styles.mt6, {marginHorizontal: 0}]}
        />
    )}
    {searchFilteredWorkflows.map((workflow) => (
        <OfflineWithFeedback
            key={workflow.approvers.at(0)?.email}
            pendingAction={workflow.pendingAction}
        >
            <ApprovalWorkflowSection
                approvalWorkflow={workflow}
                onPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EDIT.getRoute(route.params.policyID, workflow.approvers.at(0)?.email ?? ''))}
                currency={policy?.outputCurrency}
            />
        </OfflineWithFeedback>
    ))}
</>

Adding New Workflows

Create new approval workflows for different teams or scenarios:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
const addApprovalAction = useCallback(() => {
    setApprovalWorkflow({
        ...INITIAL_APPROVAL_WORKFLOW,
        availableMembers,
        usedApproverEmails,
    });

    if (!isControlPolicy(policy)) {
        Navigation.navigate(
            ROUTES.WORKSPACE_UPGRADE.getRoute(
                route.params.policyID,
                CONST.UPGRADE_FEATURE_INTRO_MAPPING.approvals.alias,
                ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(route.params.policyID),
            ),
        );
        return;
    }

    Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(route.params.policyID));
}, [policy, route.params.policyID, availableMembers, usedApproverEmails]);
Advanced approval workflows require a Control Plan workspace. Free workspaces are limited to basic approval modes.

Workflow Features

Searching Workflows

For workspaces with many workflows, use the search functionality:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
const filterWorkflow = (workflow: ApprovalWorkflow, searchInput: string) => {
    const searchableTexts: string[] = [];

    if (workflow.isDefault) {
        searchableTexts.push(everyoneText);
    } else {
        for (const member of workflow.members) {
            searchableTexts.push(member.displayName);
            searchableTexts.push(Str.removeSMSDomain(member.displayName));
            searchableTexts.push(member.email);
            searchableTexts.push(Str.removeSMSDomain(member.email));
        }
    }

    for (const approver of workflow.approvers) {
        searchableTexts.push(approver.displayName);
        searchableTexts.push(Str.removeSMSDomain(approver.displayName));
        searchableTexts.push(approver.email);
        searchableTexts.push(Str.removeSMSDomain(approver.email));
    }

    return tokenizedSearch([workflow], searchInput, () => searchableTexts).length > 0;
};

const [workflowSearchInput, setWorkflowSearchInput, searchFilteredWorkflows] = useSearchResults(filteredApprovalWorkflows, filterWorkflow);

Default Workflows

Every workspace has a default workflow that applies when no specific workflow matches:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
const filteredApprovalWorkflows =
    policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.ADVANCED || policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.DYNAMICEXTERNAL
        ? approvalWorkflows
        : approvalWorkflows.filter((workflow) => workflow.isDefault);

Approval Process

Report Approval States

Reports move through various approval states:
  1. Submitted: Report is waiting for first approval
  2. Awaiting Approval: Report is with an approver
  3. Processing: Report is moving through approval chain
  4. Approved: All approvals complete
  5. Rejected: Report was rejected and returned to submitter

Next Steps

New Expensify automatically generates next steps to guide users:
// From src/libs/actions/Report/index.ts
const optimisticNextStep = buildOptimisticNextStep({
    report: optimisticReportData,
    predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN,
    policy,
    currentUserAccountIDParam: accountID,
    currentUserEmailParam: email ?? '',
    hasViolations: hasViolationsParam,
    isASAPSubmitBetaEnabled,
});

if (optimisticNextStep) {
    optimisticReportData.nextStep = optimisticNextStep;
}

Integration with Payments

Configuring Payment Settings

Approval workflows integrate with payment settings:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
{
    title: translate('workflowsPage.makeOrTrackPaymentsTitle'),
    subtitle: translate('workflowsPage.makeOrTrackPaymentsDescription'),
    switchAccessibilityLabel: translate('workflowsPage.makeOrTrackPaymentsDescription'),
    onToggle: (isEnabled: boolean) => {
        let newReimbursementChoice;
        if (!isEnabled) {
            newReimbursementChoice = CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO;
        } else if (!!policy?.achAccount && !isCurrencySupportedForDirectReimbursement((policy?.outputCurrency ?? '') as CurrencyType)) {
            newReimbursementChoice = CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL;
        } else {
            newReimbursementChoice = CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES;
        }

        const newReimburserEmail = policy?.achAccount?.reimburser ?? policy?.owner;
        setWorkspaceReimbursement({
            policyID: route.params.policyID,
            reimbursementChoice: newReimbursementChoice,
            reimburserEmail: newReimburserEmail ?? '',
            bankAccountID: policy?.achAccount?.bankAccountID,
            accountNumber: policy?.achAccount?.accountNumber,
            addressName: policy?.achAccount?.addressName,
            bankName: policy?.achAccount?.bankName,
            state: policy?.achAccount?.state,
        });
    },
    isEndOptionRow: true,
    isActive: policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO,
}

Authorized Payer

Specify who can approve payments for the workspace:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
const displayNameForAuthorizedPayer = useMemo(
    () => getDisplayNameOrDefault(getPersonalDetailByEmail(policy?.achAccount?.reimburser ?? ''), policy?.achAccount?.reimburser),
    [policy?.achAccount?.reimburser],
);

<MenuItemWithTopDescription
    title={displayNameForAuthorizedPayer ?? ''}
    titleStyle={styles.textNormalThemeText}
    descriptionTextStyle={styles.textLabelSupportingNormal}
    description={translate('workflowsPayerPage.payer')}
    onPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_PAYER.getRoute(route.params.policyID))}
    shouldShowRightIcon
/>

Best Practices

Start with basic approval workflows and only add complexity when needed. Complex workflows can slow down the approval process.
Make sure each workflow has clearly assigned approvers who are available and responsive.
Use approval limits to automatically escalate high-value expenses to senior approvers.
Track how long approvals take and adjust workflows to reduce bottlenecks.
Notify team members when approval workflows change so they know what to expect.

Troubleshooting

Approval StuckIf an approval is stuck:
  • Verify the approver has access to the workspace
  • Check if the approver’s account is active
  • Ensure no policy violations are blocking approval
  • Contact workspace admin if needed
Dynamic External Workflow IssuesIf you see issues with external approval integrations:
  • Verify the integration is properly configured
  • Check connection to external system
  • Review integration logs
  • Contact your account manager for assistance

Reports Overview

Learn about expense reports

Creating Reports

Create and manage reports

Workspace Settings

Configure workspace settings

Build docs developers (and LLMs) love