Skip to main content
Categories and tags are the foundation of expense organization in New Expensify. They help you classify expenses for reporting, integrate with accounting systems, and ensure policy compliance across your workspace.

Categories

Categories represent the high-level classification of expenses (e.g., Travel, Meals, Office Supplies).

Creating Categories

1

Navigate to Categories

Go to Workspace Settings > Categories.
2

Add Category

Click Add Category and enter a name.
3

Configure Settings

Set category properties like GL codes, approvers, and limits.
4

Save

Click Save. The category is immediately available to workspace members.

Category Creation Code

// From src/libs/actions/Policy/Category.ts
function createPolicyCategory(
    policyID: string,
    categoryName: string,
) {
    const onyxData: OnyxData = {
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
                value: {
                    [categoryName]: {
                        name: categoryName,
                        enabled: true,
                        errors: null,
                        pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
                    },
                },
            },
        ],
        successData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
                value: {
                    [categoryName]: {
                        errors: null,
                        pendingAction: null,
                    },
                },
            },
        ],
        failureData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
                value: {
                    [categoryName]: {
                        errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey(
                            'workspace.categories.createFailureMessage'
                        ),
                    },
                },
            },
        ],
    };
    
    const parameters = {
        policyID,
        categories: JSON.stringify([{name: categoryName}]),
    };
    
    API.write(WRITE_COMMANDS.CREATE_POLICY_CATEGORY, parameters, onyxData);
}
Category names should be:
  • Clear and descriptive
  • Consistent with your accounting system (if integrated)
  • Specific enough to be useful but not so granular that users struggle to choose

Category Settings

Configure advanced category properties:
// Enable/disable categories
function setWorkspaceCategoryEnabled(
    policyID: string,
    categoriesToUpdate: Record<string, {name: string; enabled: boolean}>,
) {
    const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
    const policyCategories = allPolicyCategories[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`];
    
    const optimisticCategoryMap = Object.fromEntries(
        Object.entries(categoriesToUpdate).map(([key, categoryToUpdate]) => [
            key,
            {
                ...policyCategories?.[key],
                enabled: categoryToUpdate.enabled,
                pendingFields: {
                    enabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                },
                pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
            },
        ])
    );
    
    const onyxData = {
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
                value: optimisticCategoryMap,
            },
        ],
        // ... successData and failureData
    };
    
    API.write(WRITE_COMMANDS.SET_POLICY_CATEGORY_ENABLED, parameters, onyxData);
}
Map categories to general ledger codes for accounting integration:
function updatePolicyCategoryGLCode(
    policyID: string,
    categoryName: string,
    glCode: string,
) {
    const optimisticData = [
        {
            onyxMethod: Onyx.METHOD.MERGE,
            key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
            value: {
                [categoryName]: {
                    glCode,
                    pendingFields: {
                        glCode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
                    },
                },
            },
        },
    ];
    
    const parameters: UpdatePolicyCategoryGLCodeParams = {
        policyID,
        categoryName,
        glCode,
    };
    
    API.write(
        WRITE_COMMANDS.UPDATE_POLICY_CATEGORY_GL_CODE,
        parameters,
        {optimisticData, successData, failureData}
    );
}
Set spending limits per category:
function setPolicyCategoryMaxAmount(
    policyID: string,
    categoryName: string,
    maxAmount: number,
) {
    const parameters: SetPolicyCategoryMaxAmountParams = {
        policyID,
        categoryName,
        maxAmount,
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_CATEGORY_MAX_AMOUNT,
        parameters,
        {optimisticData, failureData}
    );
}
Mandate receipts for specific categories:
function setPolicyCategoryReceiptsRequired(
    policyID: string,
    categoryName: string,
    receiptsRequired: boolean,
) {
    const parameters: SetPolicyCategoryReceiptsRequiredParams = {
        policyID,
        categoryName,
        receiptsRequired,
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_CATEGORY_RECEIPTS_REQUIRED,
        parameters,
        {optimisticData, failureData}
    );
}
Assign specific approvers for categories:
function setPolicyCategoryApprover(
    policyID: string,
    categoryName: string,
    approver: string,
) {
    const parameters: SetPolicyCategoryApproverParams = {
        policyID,
        categoryName,
        approver,
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_CATEGORY_APPROVER,
        parameters,
        {optimisticData, failureData}
    );
}

Importing Categories

Bulk import from accounting systems or spreadsheets:
Automatically import categories when connecting QuickBooks, Xero, etc.:
  1. Connect your accounting integration
  2. Navigate to Workspace Settings > Accounting > Import
  3. Select Import Categories
  4. Map imported categories to existing ones (if needed)
  5. Click Confirm Import
Categories sync automatically based on your integration settings.

Tags

Tags provide additional expense classification beyond categories, often used for projects, departments, or cost centers.

Tag Structure

New Expensify supports multi-level tag hierarchies:
// Tag structure from src/types/onyx/Policy.ts
interface PolicyTags {
    [tagListName: string]: {
        name: string;
        orderWeight?: number;
        tags: {
            [tagName: string]: PolicyTag;
        };
    };
}

interface PolicyTag {
    name: string;
    enabled: boolean;
    accountID?: number;
    glCode?: string;
    pendingAction?: PendingAction;
}
Example hierarchy:
  • Tag List 1: Department
    • Engineering
    • Marketing
    • Sales
  • Tag List 2: Project
    • Project Alpha
    • Project Beta
    • Operations

Creating Tags

1

Navigate to Tags

Go to Workspace Settings > Tags.
2

Create Tag List

If needed, create a tag list (e.g., “Department”, “Project”).
3

Add Tags

Within each list, add individual tags.
4

Configure

Set tag properties like GL codes and enable/disable status.
// Create tag from src/libs/actions/Policy/Tag.ts
function createPolicyTag(
    policyID: string,
    tagName: string,
) {
    const policyTag = PolicyUtils.getTagLists(policyTags)?.at(0);
    const newTagName = PolicyUtils.escapeTagName(tagName);
    
    const tagListsOptimisticData = {
        [policyTag.name]: {
            tags: {
                [newTagName]: {
                    name: newTagName,
                    enabled: true,
                    errors: null,
                    pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
                },
            },
        },
    };
    
    const onyxData: OnyxData = {
        optimisticData: [
            {
                onyxMethod: Onyx.METHOD.MERGE,
                key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
                value: tagListsOptimisticData,
            },
        ],
        // ... successData and failureData
    };
    
    const parameters = {
        policyID,
        tags: JSON.stringify([{name: newTagName}]),
    };
    
    API.write(WRITE_COMMANDS.CREATE_POLICY_TAG, parameters, onyxData);
}

Multi-Level Tags

Create hierarchical tag structures:
// Rename tag list from src/libs/actions/Policy/Tag.ts
function renamePolicyTagList(
    policyID: string,
    oldTagListName: string,
    newTagListName: string,
) {
    const parameters: RenamePolicyTagListParams = {
        policyID,
        oldName: oldTagListName,
        newName: newTagListName,
    };
    
    API.write(
        WRITE_COMMANDS.RENAME_POLICY_TAG_LIST,
        parameters,
        {optimisticData, successData, failureData}
    );
}
Tag levels example:
  1. Level 1 (Department): Required, single-select
  2. Level 2 (Project): Optional, single-select
  3. Level 3 (Cost Center): Optional, multi-select
Each level can have different requirements and selection modes.

Tag Settings

Required Tags

Make tags mandatory for expense submission
function setPolicyTagsRequired(
    policyID: string,
    tagName: string,
    required: boolean,
) {
    const parameters: SetPolicyTagsRequired = {
        policyID,
        tagName,
        required,
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_TAGS_REQUIRED,
        parameters,
        {optimisticData, failureData}
    );
}

Tag Approvers

Assign approvers by tag
function setPolicyTagApprover(
    policyID: string,
    tagName: string,
    approver: string,
) {
    const parameters: SetPolicyTagApproverParams = {
        policyID,
        tag: tagName,
        approver,
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_TAG_APPROVER,
        parameters,
        {optimisticData, failureData}
    );
}

GL Code Mapping

Map tags to accounting codes
function updatePolicyTagGLCode(
    policyID: string,
    tagName: string,
    glCode: string,
) {
    const parameters: UpdatePolicyTagGLCodeParams = {
        policyID,
        tagName,
        glCode,
    };
    
    API.write(
        WRITE_COMMANDS.UPDATE_POLICY_TAG_GL_CODE,
        parameters,
        {optimisticData, failureData}
    );
}

Enable/Disable

Control tag availability
function setPolicyTagsEnabled(
    policyID: string,
    tags: Record<string, boolean>,
) {
    const parameters: SetPolicyTagsEnabled = {
        policyID,
        tags: JSON.stringify(tags),
    };
    
    API.write(
        WRITE_COMMANDS.SET_POLICY_TAGS_ENABLED,
        parameters,
        {optimisticData, failureData}
    );
}

Recently Used Tags

New Expensify remembers recently used tags for quick selection:
// Build recently used tags from src/libs/actions/Policy/Tag.ts
function buildOptimisticPolicyRecentlyUsedTags({
    policyTags,
    policyRecentlyUsedTags,
    transactionTags,
}: BuildOptimisticPolicyRecentlyUsedTagsProps): RecentlyUsedTags {
    if (!transactionTags) {
        return {};
    }
    
    const policyTagKeys = PolicyUtils.getSortedTagKeys(policyTags);
    const newOptimisticPolicyRecentlyUsedTags: RecentlyUsedTags = {};
    
    for (const [index, tag] of getTagArrayFromName(transactionTags).entries()) {
        if (!tag) {
            continue;
        }
        
        const tagListKey = policyTagKeys.at(index) ?? '';
        newOptimisticPolicyRecentlyUsedTags[tagListKey] = [
            ...new Set([tag, ...(policyRecentlyUsedTags?.[tagListKey] ?? [])])
        ];
    }
    
    return newOptimisticPolicyRecentlyUsedTags;
}

Using Categories and Tags

On Expenses

When submitting an expense:
  1. Select Category: Choose from workspace categories
  2. Add Tags: Select required and optional tags
  3. Validation: System checks for required fields and policy compliance
  4. Submit: Expense includes category and tag data

In Reports

Categories and tags enable powerful reporting:
  • By Category: Total spending per category
  • By Tag: Department, project, or cost center breakdowns
  • Combined: Multi-dimensional analysis (e.g., Travel expenses by Department)
  • Trend Analysis: Spending patterns over time

Export to Accounting

Categories and tags map to your accounting system:
  • Categories → Chart of Accounts
  • Tags → Classes, Departments, or Locations
  • GL Codes → Account numbers

Best Practices

Start with 5-10 core categories. Add more only when necessary. Too many categories confuses users and reduces compliance.
Match your categories to your chart of accounts before adding expenses. This makes export and reconciliation much easier.
“Client Meals” is better than “Meals”. “Software Subscriptions” is clearer than “IT”.
Most important tags first (usually Department), then project-specific tags. This guides users through selection.
Don’t delete old categories/tags—disable them. This preserves historical data while preventing new usage.
Review categories and tags quarterly. Consolidate similar items and remove duplicates.

Next Steps

Rules & Workflows

Set up approval workflows and policy rules

Accounting Integrations

Connect to QuickBooks, Xero, or NetSuite

Member Management

Back to member configuration

Build docs developers (and LLMs) love