Skip to main content
Member management is central to workspace administration in New Expensify. Control who has access to your workspace, what roles they have, and how they interact with expense management workflows.

Adding Members

Invite by Email

The most common way to add team members:
1

Navigate to Members

Go to Workspace Settings > Members.
2

Click Invite

Click the Invite button.
3

Enter Emails

Type or paste email addresses (comma-separated for multiple).
4

Set Role

Choose Admin or Member role for invitees.
5

Send Invitations

Click Send. Invitations are sent immediately.

Member Invitation Code

// From src/libs/actions/Policy/Member.ts
function addMembersToWorkspace(
    invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs,
    welcomeNote: string,
    policyID: string,
) {
    const logins = Object.keys(invitedEmailsToAccountIDs);
    const accountIDs = Object.values(invitedEmailsToAccountIDs);
    
    // Build optimistic data for immediate UI update
    const optimisticMembersState: OnyxCollection<PolicyEmployee> = {};
    const successMembersState: OnyxCollection<PolicyEmployee> = {};
    const failureMembersState: OnyxCollection<PolicyEmployee> = {};
    
    logins.forEach((email) => {
        optimisticMembersState[email] = {
            role: CONST.POLICY.ROLE.USER,
            errors: {},
            pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
        };
        
        successMembersState[email] = {
            errors: null,
            pendingAction: null,
        };
        
        failureMembersState[email] = {
            errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey(
                'workspace.people.error.genericAdd'
            ),
        };
    });
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                employeeList: optimisticMembersState,
                pendingFields: {
                    invitedEmployees: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                },
            },
        },
    ];
    
    // Create policy expense chats for new members
    const membersChats = createPolicyExpenseChats(
        policyID,
        invitedEmailsToAccountIDs,
    );
    
    optimisticData.push(...membersChats.onyxOptimisticData);
    
    const parameters: AddMembersToWorkspaceParams = {
        employees: JSON.stringify(logins.map((login) => ({email: login}))),
        welcomeNote: Parser.htmlToMarkdown(welcomeNote),
        policyID,
    };
    
    API.write(
        WRITE_COMMANDS.ADD_MEMBERS_TO_WORKSPACE,
        parameters,
        {optimisticData, successData, failureData}
    );
}
When you add members, New Expensify automatically:
  1. Creates a 1-on-1 expense chat between the workspace and each member
  2. Adds them to appropriate workspace rooms (#general, etc.)
  3. Sends them an email invitation
  4. Grants access to workspace categories and tags

Bulk Import

Import multiple members from a spreadsheet:
1

Prepare Spreadsheet

Create a CSV or Excel file with columns:
  • Email (required)
  • Role (optional: “admin” or “member”)
  • Department (optional)
2

Upload

Click Import > From Spreadsheet and select your file.
3

Map Columns

Confirm which columns map to which fields.
4

Review and Import

Preview the import and click Confirm to add all members.
// Spreadsheet import handling
function updateImportSpreadsheetData(
    addedMembersLength: number,
    updatedMembersLength: number,
): OnyxData<typeof ONYXKEYS.IMPORTED_SPREADSHEET> {
    const onyxData: OnyxData<typeof ONYXKEYS.IMPORTED_SPREADSHEET> = {
        successData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.IMPORTED_SPREADSHEET,
                value: {
                    shouldFinalModalBeOpened: true,
                    importFinalModal: {
                        titleKey: 'spreadsheet.importSuccessfulTitle',
                        promptKey: 'spreadsheet.importMembersSuccessfulDescription',
                        promptKeyParams: {
                            added: addedMembersLength,
                            updated: updatedMembersLength,
                        },
                    },
                },
            },
        ],
        failureData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: ONYXKEYS.IMPORTED_SPREADSHEET,
                value: {
                    shouldFinalModalBeOpened: true,
                    importFinalModal: {
                        titleKey: 'spreadsheet.importFailedTitle',
                        promptKey: 'spreadsheet.importFailedDescription',
                    },
                },
            },
        ],
    };
    
    return onyxData;
}

Member Roles

Admin Role

Full workspace control:
role: CONST.POLICY.ROLE.ADMIN
Permissions:
  • Manage all workspace settings
  • Invite/remove members
  • Configure approval workflows
  • Set up integrations
  • Access all expenses and reports
  • Export data
  • Delete workspace

Member Role

Standard user access:
role: CONST.POLICY.ROLE.USER
Permissions:
  • Submit expenses
  • Create reports
  • View own expenses
  • Chat in workspace rooms
  • Use workspace categories/tags

Changing Roles

Update a member’s role:
// From src/libs/actions/Policy/Member.ts
function updateWorkspaceMembersRole(
    policyID: string,
    accountIDs: number[],
    role: typeof CONST.POLICY.ROLE.ADMIN | typeof CONST.POLICY.ROLE.USER,
) {
    const previousEmployeeList = {...policy?.employeeList};
    const memberRoles: WorkspaceMembersRoleData[] = [];
    
    accountIDs.forEach((accountID) => {
        const email = personalDetails?.[accountID]?.login ?? '';
        
        memberRoles.push({
            email,
            role,
        });
    });
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                employeeList: memberRoles.reduce((acc, {email, role}) => {
                    acc[email] = {
                        role,
                        pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                    };
                    return acc;
                }, {}),
            },
        },
    ];
    
    const parameters: UpdateWorkspaceMembersRoleParams = {
        policyID,
        employees: JSON.stringify(memberRoles),
    };
    
    API.write(
        WRITE_COMMANDS.UPDATE_WORKSPACE_MEMBERS_ROLE,
        parameters,
        {optimisticData, successData, failureData}
    );
}
Be careful when assigning admin roles—admins have full control including the ability to delete the workspace or remove other admins.

Removing Members

Remove members who no longer need access:
1

Select Members

Go to Members page and check the boxes next to members to remove.
2

Remove

Click Remove and confirm the action.
3

Confirmation

Members are immediately removed and lose workspace access.
// Remove members from src/libs/actions/Policy/Member.ts
function deleteMembersFromWorkspace(
    policyID: string,
    accountIDs: number[],
) {
    const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
    const workspaceChats = getWorkspaceChatsReports(policy);
    const memberEmails = accountIDs.map(
        (accountID) => allPersonalDetails?.[accountID]?.login ?? ''
    );
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                employeeList: memberEmails.reduce((acc, email) => {
                    acc[email] = {
                        pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
                    };
                    return acc;
                }, {}),
            },
        },
    ];
    
    // Remove from workspace rooms
    const announcementChatData = removeOptimisticRoomMembers(
        CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE,
        policyID,
        accountIDs,
    );
    
    optimisticData.push(...announcementChatData.optimisticData);
    
    const parameters: DeleteMembersFromWorkspaceParams = {
        policyID,
        employees: JSON.stringify(memberEmails),
    };
    
    API.write(
        WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE,
        parameters,
        {optimisticData, successData, failureData}
    );
}
When you remove a member:
  • They lose access to workspace expenses and reports
  • They’re removed from workspace chat rooms
  • Their personal 1-on-1 expense chat with the workspace is archived
  • Their historical expenses remain in the system for audit purposes

Workspace Rooms

Members are automatically added to workspace chat rooms:

Room Types

General RoomAll workspace members are automatically added:
  • Open discussion for team
  • Expense-related questions
  • General announcements
  • Collaboration space
chatType: CONST.REPORT.CHAT_TYPE.POLICY_GENERAL

Adding to Rooms

// Add members to workspace rooms
function buildRoomMembersOnyxData(
    roomType: typeof CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE | 
             typeof CONST.REPORT.CHAT_TYPE.POLICY_ADMINS,
    policyID: string,
    accountIDs: number[],
): OnyxData {
    const report = ReportUtils.getRoom(roomType, policyID);
    const reportMetadata = ReportUtils.getReportMetadata(report?.reportID);
    
    if (!report || accountIDs.length === 0) {
        return {optimisticData: [], failureData: [], successData: []};
    }
    
    const participantAccountIDs = [
        ...Object.keys(report.participants ?? {}).map(Number),
        ...accountIDs,
    ];
    
    const pendingChatMembers = ReportUtils.getPendingChatMembers(
        accountIDs,
        reportMetadata?.pendingChatMembers ?? [],
        CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD
    );
    
    const optimisticData = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`,
            value: {
                participants: ReportUtils.buildParticipantsFromAccountIDs(
                    participantAccountIDs
                ),
            },
        },
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`,
            value: {
                pendingChatMembers,
            },
        },
    ];
    
    return {optimisticData, successData, failureData};
}

Member Permissions

Default Approver

Set a default expense approver for the workspace:
// From src/libs/PolicyUtils.ts
function getDefaultApprover(
    policy: OnyxEntry<Policy>
): string | undefined {
    const defaultApprover = policy?.approver;
    const hasMultipleApprovalWorkflows = 
        Object.values(policy?.employeeList ?? {}).some(
            (employee) => employee?.submitsTo !== defaultApprover
        );
    
    return hasMultipleApprovalWorkflows ? undefined : defaultApprover;
}

Approver Roles

Check if a member is an approver:
function isApprover(policy: OnyxEntry<Policy>, employeeLogin: string) {
    if (policy?.approver === employeeLogin) {
        return true;
    }
    
    return Object.values(policy?.employeeList ?? {}).some(
        (employee) => 
            employee?.submitsTo === employeeLogin ||
            employee?.forwardsTo === employeeLogin ||
            employee?.overLimitForwardsTo === employeeLogin
    );
}

Member Activity

Track member engagement:
  • Last Active: When member last accessed workspace
  • Expenses Submitted: Total expense count
  • Reports Created: Number of reports
  • Approval Activity: Reports approved/rejected

Ownership Transfer

Transfer workspace ownership to another admin:
function requestWorkspaceOwnerChange(
    policyID: string,
    email: string,
    accountID: number,
) {
    const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
    
    const optimisticData: OnyxUpdate[] = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
            value: {
                errorFields: {
                    changeOwner: null,
                },
                isChangeOwnerSuccessful: false,
                isChangeOwnerFailed: false,
            },
        },
    ];
    
    const parameters: RequestWorkspaceOwnerChangeParams = {
        policyID,
        email,
        accountID,
    };
    
    API.write(
        WRITE_COMMANDS.REQUEST_WORKSPACE_OWNER_CHANGE,
        parameters,
        {optimisticData, successData, failureData}
    );
}
Ownership transfer:
  • Requires both parties to confirm
  • New owner must be an admin
  • Original owner becomes a regular admin
  • Cannot be undone (must transfer back)

Best Practices

Audit your member list quarterly to remove employees who have left or no longer need access.
Only grant admin roles to team members who truly need full workspace control.
When inviting members, include a welcome note explaining workspace policies and where to find help.
Keep a record of who has admin access and why. This helps during audits and ownership transitions.
Don’t just add members—provide training on expense submission, approval workflows, and workspace-specific policies.

Next Steps

Categories & Tags

Set up expense organization for your team

Approval Workflows

Configure approval chains and rules

Workspace Settings

Back to general workspace configuration

Build docs developers (and LLMs) love