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:
Navigate to Members
Go to Workspace Settings > Members .
Click Invite
Click the Invite button.
Enter Emails
Type or paste email addresses (comma-separated for multiple).
Set Role
Choose Admin or Member role for invitees.
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:
Creates a 1-on-1 expense chat between the workspace and each member
Adds them to appropriate workspace rooms (#general, etc.)
Sends them an email invitation
Grants access to workspace categories and tags
Bulk Import
Import multiple members from a spreadsheet:
Prepare Spreadsheet
Create a CSV or Excel file with columns:
Email (required)
Role (optional: “admin” or “member”)
Department (optional)
Upload
Click Import > From Spreadsheet and select your file.
Map Columns
Confirm which columns map to which fields.
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:
Select Members
Go to Members page and check the boxes next to members to remove.
Remove
Click Remove and confirm the action.
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
#admins
#announce
General Room All workspace members are automatically added:
Open discussion for team
Expense-related questions
General announcements
Collaboration space
chatType : CONST . REPORT . CHAT_TYPE . POLICY_GENERAL
Admin Room Only workspace admins have access:
Private admin discussions
Sensitive policy matters
Workspace configuration planning
chatType : CONST . REPORT . CHAT_TYPE . POLICY_ADMINS
Announcement Room All members can read, only admins can post:
Important updates
Policy changes
Deadlines and reminders
chatType : CONST . REPORT . CHAT_TYPE . POLICY_ANNOUNCE
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.
Use Descriptive Welcome Notes
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