Skip to main content
This guide covers everything you need to know about creating and managing expense reports in New Expensify.

Creating a New Report

Automatic Report Creation

Reports can be created automatically based on your workspace’s submission frequency settings. When enabled, expenses are automatically grouped into reports according to the configured schedule.
// From src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
const optionItems: ToggleSettingOptionRowProps[] = useMemo(() => {
    return [
        {
            title: translate('workflowsPage.submissionFrequency'),
            subtitle: translate('workflowsPage.submissionFrequencyDescription'),
            switchAccessibilityLabel: translate('workflowsPage.submissionFrequencyDescription'),
            onToggle: (isEnabled: boolean) => (policy ? setWorkspaceAutoHarvesting(policy, isEnabled) : undefined),
            subMenuItems: (
                <MenuItemWithTopDescription
                    title={getAutoReportingFrequencyDisplayNames(translate)[getCorrectedAutoReportingFrequency(policy) ?? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]}
                    titleStyle={styles.textNormalThemeText}
                    descriptionTextStyle={styles.textLabelSupportingNormal}
                    onPress={onPressAutoReportingFrequency}
                    description={translate('common.frequency')}
                    shouldShowRightIcon
                />
            ),
            isActive: (policy?.autoReporting && !hasDelayedSubmissionError) ?? false,
        },
    ];
}, [/* dependencies */]);

Manual Report Creation

When automatic submission is disabled, you can create reports manually by grouping expenses together.

Report Lifecycle

Building Optimistic Report Data

When creating a new report, New Expensify builds optimistic data for immediate UI updates:
// From src/libs/actions/Report/index.ts
function buildNewReportOptimisticData(
    policy: OnyxEntry<Policy>,
    reportID: string,
    reportActionID: string,
    ownerPersonalDetails: CurrentUserPersonalDetails,
    reportPreviewReportActionID: string,
    hasViolationsParam: boolean,
    isASAPSubmitBetaEnabled: boolean,
    betas: OnyxEntry<Beta[]>,
) {
    const {accountID, login, email} = ownerPersonalDetails;
    const timeOfCreation = DateUtils.getDBTime();
    const parentReport = getPolicyExpenseChat(accountID, policy?.id);
    const optimisticReportData = buildOptimisticEmptyReport(reportID, accountID, parentReport, reportPreviewReportActionID, policy, timeOfCreation, betas);

    const optimisticNextStep = buildOptimisticNextStep({
        report: optimisticReportData,
        predictedNextStatus: CONST.REPORT.STATUS_NUM.OPEN,
        policy,
        currentUserAccountIDParam: accountID,
        currentUserEmailParam: email ?? '',
        hasViolations: hasViolationsParam,
        isASAPSubmitBetaEnabled,
    });
    
    if (optimisticNextStep) {
        optimisticReportData.nextStep = optimisticNextStep;
    }

    return optimisticReportData;
}

Editing Report Details

Updating Report Name

You can customize the report name to make it more descriptive:
// From src/libs/actions/Report/index.ts
function updateChatName(reportID: string, oldReportName: string | undefined, reportName: string, type: typeof CONST.REPORT.CHAT_TYPE.GROUP | typeof CONST.REPORT.CHAT_TYPE.TRIP_ROOM) {
    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
            value: {
                reportName,
                pendingFields: {
                    reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                },
                errorFields: {
                    reportName: null,
                },
            },
        },
    ];

    const successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
            value: {
                pendingFields: {
                    reportName: null,
                },
            },
        },
    ];
    
    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT>> = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
            value: {
                reportName: oldReportName ?? null,
                pendingFields: {
                    reportName: null,
                },
            },
        },
    ];

    const command = type === CONST.REPORT.CHAT_TYPE.GROUP ? WRITE_COMMANDS.UPDATE_GROUP_CHAT_NAME : WRITE_COMMANDS.UPDATE_TRIP_ROOM_NAME;
    const parameters: UpdateChatNameParams = {reportName, reportID};

    API.write(command, parameters, {optimisticData, successData, failureData});
}

Updating Report Avatar

For group reports and workspace rooms, you can customize the report avatar:
// From src/libs/actions/Report/index.ts
function updateGroupChatAvatar(reportID: string, oldAvatarUrl: string | undefined, file?: File | CustomRNImageManipulatorResult) {
    const {optimisticData, successData, failureData} = buildUpdateReportAvatarOnyxData(reportID, oldAvatarUrl, file);
    const parameters: UpdateGroupChatAvatarParams = {file, reportID};
    API.write(WRITE_COMMANDS.UPDATE_GROUP_CHAT_AVATAR, parameters, {optimisticData, failureData, successData});
}

Adding Comments to Reports

Comment Functionality

Add comments to provide context or communicate with approvers:
// From src/libs/actions/Report/index.ts
/** Add a single comment to a report */
function addComment({
    report,
    notifyReportID,
    ancestors,
    text,
    timezoneParam,
    currentUserAccountID,
    shouldPlaySound,
    isInSidePanel,
    pregeneratedResponseParams,
    reportActionID,
}: AddCommentParams) {
    if (shouldPlaySound) {
        playSound(SOUNDS.DONE);
    }
    addActions({report, notifyReportID, ancestors, timezoneParam, currentUserAccountID, text, isInSidePanel, pregeneratedResponseParams, reportActionID});
}
Comments are visible to all participants on the report and create a permanent audit trail.

Report Actions

Available Actions

Depending on the report type and state, different actions are available:
  • Add expenses
  • Edit report details
  • Add comments
  • Submit for approval
  • Delete report

Report Menu Items

The Report Details page provides various menu options:
// From src/pages/ReportDetailsPage.tsx
const menuItems: ReportDetailsPageMenuItem[] = useMemo(() => {
    const items: ReportDetailsPageMenuItem[] = [];

    if (isSelfDM) {
        return [];
    }

    if (isArchivedRoom) {
        return items;
    }

    // Members option
    if (isGroupChat || (!isUserCreatedPolicyRoom && participants.length) || (isUserCreatedPolicyRoom && isPolicyEmployee)) {
        items.push({
            key: CONST.REPORT_DETAILS_MENU_ITEM.MEMBERS,
            translationKey: 'common.members',
            icon: expensifyIcons.Users,
            subtitle: activeChatMembers.length,
            isAnonymousAction: false,
            shouldShowRightIcon: true,
            action: () => {
                if (shouldOpenRoomMembersPage) {
                    Navigation.navigate(ROUTES.ROOM_MEMBERS.getRoute(report?.reportID, backTo));
                } else {
                    Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID, backTo));
                }
            },
        });
    }

    // Settings option
    if (shouldShowMenuItem) {
        items.push({
            key: CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS,
            translationKey: 'common.settings',
            icon: expensifyIcons.Gear,
            isAnonymousAction: false,
            shouldShowRightIcon: true,
            action: () => {
                Navigation.navigate(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID, backTo));
            },
        });
    }

    return items;
}, [/* dependencies */]);

Report Validation

Checking Report State

Before performing actions, validate the report state:
// From src/libs/ReportUtils.ts
function isOpenReport(report: OnyxEntry<Report>): boolean {
    return report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN;
}

function isProcessingReport(report: OnyxEntry<Report>): boolean {
    return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS_NUM.SUBMITTED;
}

function isOpenOrProcessingReport(report: OnyxEntry<Report>): boolean {
    return isOpenReport(report) || isProcessingReport(report);
}

Best Practices

Give your reports clear, descriptive names that indicate the purpose or time period (e.g., “Q1 2024 Travel Expenses” or “Client Meeting - January 2024”).
Add comments and context to your report early in the process, before submission. This helps approvers understand the expenses and speeds up the approval process.
Check your open reports regularly to ensure all expenses are properly categorized and receipts are attached.
Submit reports according to your company’s policy to ensure timely reimbursement and accurate financial reporting.

Common Issues

Report Creation FailedIf report creation fails, check:
  • Network connectivity
  • Workspace permissions
  • Policy settings
  • Required field validation
Optimistic UpdatesNew Expensify uses optimistic updates to provide instant feedback. If you see a pending indicator, the action is being synced with the server.

Reports Overview

Learn about expense reports

Approval Workflows

Understand the approval process

Report Fields

Add custom fields to reports

Build docs developers (and LLMs) love