Skip to main content
Rules and workflows in New Expensify automate expense management, enforce policies, and streamline approval processes. Set up everything from simple approval chains to complex multi-level workflows with conditional rules.

Approval Workflows

Workflows define how expenses move from submission to reimbursement.

Approval Modes

New Expensify supports three approval modes:

Optional Approval

No approval required—expenses are immediately available for reimbursement.
approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL
Best for:
  • Personal workspaces
  • Small teams with high trust
  • Non-reimbursable expense tracking

Creating Approval Workflows

1

Navigate to Workflows

Go to Workspace Settings > Workflows.
2

Enable Advanced Mode

Click Enable Advanced Workflows (if not already enabled).
3

Add Workflow

Click Add Workflow and select members this workflow applies to.
4

Configure Approvers

Add approvers in order. Expenses will route through each level.
5

Set Conditions

Add amount thresholds or other conditions for routing.

Workflow Creation Code

// From src/libs/actions/Workflow.ts
function createApprovalWorkflow({
    approvalWorkflow,
    policy,
    addExpenseApprovalsTaskReport,
}: CreateApprovalWorkflowParams) {
    if (!policy) {
        return;
    }
    
    const previousEmployeeList = Object.fromEntries(
        Object.entries(policy.employeeList ?? {}).map(([key, value]) => [
            key,
            {...value, pendingAction: null},
        ])
    );
    
    const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({
        previousEmployeeList,
        approvalWorkflow,
        type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE,
    });
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.SET,
            key: ONYXKEYS.APPROVAL_WORKFLOW,
            value: null,
        },
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policy.id}`,
            value: {
                employeeList: updatedEmployees,
                approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED,
            },
        },
    ];
    
    const parameters: CreateWorkspaceApprovalParams = {
        policyID: policy.id,
        employees: JSON.stringify(Object.values(updatedEmployees)),
    };
    
    API.write(
        WRITE_COMMANDS.CREATE_WORKSPACE_APPROVAL,
        parameters,
        {optimisticData, failureData, successData}
    );
}

Workflow Structure

// Approval workflow structure from src/types/onyx/ApprovalWorkflow.ts
interface ApprovalWorkflow {
    approvers: Approver[];
    isDefault: boolean;
    members: Member[];
}

interface Approver {
    email: string;
    displayName: string;
    avatarURL?: string;
    accountID: number;
    approvalLimit?: number; // Amount threshold
    forwardsTo?: string;    // Escalation
}

interface Member {
    email: string;
    displayName: string;
    avatarURL?: string;
    accountID: number;
}
Default workflow: Applies to all members not assigned to a specific workflow. Every workspace must have a default workflow.

Setting Approvers

Configure who approves expenses:
// Set approval workflow approver from src/libs/actions/Workflow.ts
function setApprovalWorkflowApprover({
    approver,
    approverIndex,
    currentApprovalWorkflow,
    policy,
    personalDetailsByEmail,
}: SetApprovalWorkflowApproverParams) {
    const updatedApprovers = [
        ...(currentApprovalWorkflow?.approvers ?? []),
    ];
    
    if (approverIndex < updatedApprovers.length) {
        updatedApprovers[approverIndex] = approver;
    } else {
        updatedApprovers.push(approver);
    }
    
    // Calculate derived approvers
    const calculatedApprovers = calculateApprovers(
        currentApprovalWorkflow?.members ?? [],
        updatedApprovers,
        personalDetailsByEmail,
        policy,
    );
    
    Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {
        approvers: updatedApprovers,
        calculatedApprovers,
    });
}

Auto-Reporting

Automatically create and submit expense reports on a schedule:

Configuration

1

Enable Auto-Reporting

Go to Workflows and toggle Auto-Reporting.
2

Set Frequency

Choose:
  • Manual: Submit reports manually (default)
  • Immediate: Submit after each expense
  • Daily: Every business day
  • Weekly: End of week
  • Monthly: End of month
3

Configure Offset

For monthly reporting, set the day of month to submit (e.g., 25th for month-end closing).
// Set auto-reporting from src/libs/actions/Policy/Policy.ts
function setWorkspaceAutoReportingFrequency(
    policyID: string,
    frequency: ValueOf<typeof CONST.POLICY.AUTO_REPORTING_FREQUENCIES>,
) {
    const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                autoReporting: true,
                autoReportingFrequency: frequency,
                pendingFields: {
                    autoReportingFrequency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                },
            },
        },
    ];
    
    const parameters: SetWorkspaceAutoReportingFrequencyParams = {
        policyID,
        frequency,
    };
    
    API.write(
        WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_FREQUENCY,
        parameters,
        {optimisticData, successData, failureData}
    );
}

Monthly Offset

For monthly auto-reporting, configure when reports are submitted:
function setWorkspaceAutoReportingMonthlyOffset(
    policyID: string,
    autoReportingOffset: string,
) {
    const parameters: SetWorkspaceAutoReportingMonthlyOffsetParams = {
        policyID,
        autoReportingOffset,
    };
    
    API.write(
        WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET,
        parameters,
        {optimisticData, failureData}
    );
}
Best practice: Set monthly offset to 2-3 days before month-end to allow time for any last-minute expenses and approvals.

Expense Rules

Enforce policies automatically with expense rules:

Receipt Requirements

Make receipts mandatory above a certain amount:
// From src/pages/workspace/rules/RulesReceiptRequiredAmountPage.tsx
function setReceiptRequiredAmount(
    policyID: string,
    amount: number,
) {
    const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                requiresReceipt: true,
                receiptRequiredAmount: amount,
            },
        },
    ];
}
Example rule: Require receipts for all expenses over $75.

Expense Limits

Set maximum expense amounts:
Maximum amount for a single expense:
function setMaxExpenseAmount(
    policyID: string,
    maxExpenseAmount: number,
) {
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                maxExpenseAmount,
            },
        },
    ];
}

Prohibited Expenses

Block specific expense types:
function setPolicyProhibitedExpenses(
    policyID: string,
    prohibitedExpenses: ProhibitedExpenses,
) {
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                prohibitedExpenses: {
                    ...prohibitedExpenses,
                    pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                },
            },
        },
    ];
    
    const parameters: SetPolicyProhibitedExpensesParams = {
        policyID,
        prohibitedExpenses: JSON.stringify(prohibitedExpenses),
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_PROHIBITED_EXPENSES,
        parameters,
        {optimisticData, failureData}
    );
}
Common prohibitions:
  • Personal expenses
  • Alcohol (for certain policies)
  • Entertainment
  • Gifts above a threshold

Merchant Rules

Automate expense categorization based on merchant:
// From src/libs/actions/Policy/Rules.ts
function setPolicyCodingRule(
    policyID: string,
    form: MerchantRuleForm,
    policy: Policy | undefined,
    ruleID?: string,
) {
    const targetRuleID = ruleID ?? NumberUtils.rand64();
    const operator = form.matchType ?? CONST.SEARCH.SYNTAX_OPERATORS.CONTAINS;
    
    const ruleForOnyx = {
        ruleID: targetRuleID,
        filters: {
            left: 'merchant',
            operator,
            right: form.merchantToMatch,
        },
        merchant: form.merchant || null,
        category: form.category || null,
        tag: form.tag || null,
        comment: convertCommentToHTML(form.comment),
        reimbursable: form.reimbursable ?? null,
        billable: form.billable ?? null,
        pendingAction: isEditing 
            ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE 
            : CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
    };
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                rules: {
                    codingRules: {
                        [targetRuleID]: ruleForOnyx,
                    },
                },
            },
        },
    ];
    
    const parameters: SetPolicyCodingRuleParams = {
        policyID,
        ruleJSON: JSON.stringify(ruleFieldsForAPI),
        ruleID: targetRuleID,
        shouldUpdateMatchingTransactions,
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_CODING_RULE,
        parameters,
        {optimisticData, successData, failureData}
    );
}
Example rules:
  • Expenses from “Starbucks” → automatically categorize as “Meals”
  • Expenses from “United Airlines” → tag as “Travel” with “Client Meetings” tag
  • Expenses from “AWS” → categorize as “Software”, make billable
Merchant rules support:
  • Exact match: Merchant name must match exactly
  • Contains: Merchant name contains the text
  • Starts with: Merchant name starts with the text
  • Regex: Advanced pattern matching

Reimbursement Settings

Configure how expenses are reimbursed:

Reimbursement Methods

Reimburse outside the app (cash, check, etc.):
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL
// Set reimbursement method from src/libs/actions/Policy/Policy.ts
function setWorkspaceReimbursement(
    policyID: string,
    reimbursementChoice: ValueOf<typeof CONST.POLICY.REIMBURSEMENT_CHOICES>,
    reimburserEmail: string,
) {
    const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                reimbursementChoice,
                achAccount: {
                    reimburser: reimburserEmail,
                },
            },
        },
    ];
    
    const parameters: SetWorkspaceReimbursementParams = {
        policyID,
        reimbursementChoice,
        reimburserEmail,
    };
    
    API.write(
        WRITE_COMMANDS.SET_WORKSPACE_REIMBURSEMENT,
        parameters,
        {optimisticData, successData, failureData}
    );
}

Auto-Pay Rules

Automatically reimburse approved reports:
function setAutoPayReportsUnder(
    policyID: string,
    amount: number,
) {
    // Auto-pay reports under specified amount
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                autoPayReportsUnder: amount,
            },
        },
    ];
}

Workflow Page Implementation

Here’s how the workflows page works:
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
    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;
    
    const addApprovalAction = useCallback(() => {
        setApprovalWorkflow({
            ...INITIAL_APPROVAL_WORKFLOW,
            availableMembers,
            usedApproverEmails,
        });
        
        Navigation.navigate(
            ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(policyID)
        );
    }, [policy, policyID, availableMembers, usedApproverEmails]);
    
    return (
        <WorkspacePageWithSections>
            <ApprovalWorkflowSection
                approvalWorkflows={filteredApprovalWorkflows}
                onAddWorkflow={addApprovalAction}
            />
            <ExpenseReportRulesSection policy={policy} />
        </WorkspacePageWithSections>
    );
}

Best Practices

Begin with basic approval (single approver) and add advanced workflows only when needed. Complex workflows can slow down approvals.
Auto-approval limits should be high enough to be useful but low enough to maintain control. Common range: 5050-500.
Monthly auto-reporting works well for most teams. Daily is usually too frequent and creates noise.
Maintain a policy document explaining all rules and workflows. Share it during onboarding.
Audit your workflows quarterly. Remove outdated rules and optimize approval chains based on actual usage.
Create rules for your most common merchants. This saves time and improves categorization consistency.

Troubleshooting

Check if:
  • Approver has workspace access
  • Approver email is correct
  • Workflow isn’t circular (A approves for B, B approves for A)
  • Expense meets approval criteria
Verify:
  • Feature is enabled for your plan
  • Frequency is set correctly
  • User has submitted expenses in the period
  • No holds or violations blocking submission
Ensure:
  • Rule is enabled
  • Merchant name matches exactly (check for typos/spacing)
  • Rule order is correct (rules apply in sequence)
  • Categories/tags in rule are still enabled

Next Steps

Approval Workflows

Deep dive into report approval processes

Workspace Overview

Back to workspace features

Categories & Tags

Review expense organization

Integrations

Connect accounting systems

Build docs developers (and LLMs) love