Expense Tracking & Reimbursable Items
Track project expenses — software subscriptions, mileage, materials, contractor costs, travel — with receipt uploads. Apply per-expense markup and roll selected unbilled expenses into a draft invoice in one click. Same mental model as Time Tracking, for everything that isn't billable hours.
When to use it
- You bill clients for expenses you incur on their behalf (project costs, AWS bills, paid stock photography, sub-contractor fees, travel)
- You're already using Time Tracking for billable hours and need the matching workflow for everything else
- You want receipt photos stored alongside expense entries for tax-time reconciliation
- Your current expense workflow is "type it as a line item by hand each time and hope you remember every receipt"
Enabling
- Open Easy Invoice → Addons
- Find Expense Tracking & Reimbursable Items
- Click Activate
The Easy Invoice → Addons → Expense Tracking entry appears in the in-app sidebar. On first activation the addon creates one custom table:
{prefix}_easy_invoice_expensesSchema:
| Column | Type | Notes |
|---|---|---|
id | BIGINT PK | |
user_id | BIGINT | Who logged the entry — for sales-rep attribution. |
client_id | BIGINT | Nullable — solo / overhead costs allowed. |
project | VARCHAR(255) | Free-text label. |
category | VARCHAR(64) | software / mileage / materials / contractor / travel / meals / other. |
description | TEXT | The label that ends up as the invoice line item. |
amount | DECIMAL(12,2) | Your cost (before markup). |
currency | CHAR(3) | ISO 4217 — defaults to your site currency. |
markup | DECIMAL(5,2) | Markup % applied when converting to invoice line. |
expense_date | DATE | When the cost was incurred. |
receipt_attachment_id | BIGINT NULL | WP media-library attachment ID for the receipt. |
is_billable | TINYINT(1) | 1 = billable to client, 0 = absorbed cost. |
billed_invoice_id | BIGINT NULL | Set when expense is rolled into an invoice. |
notes | TEXT | Optional internal note. |
created_at, updated_at | TIMESTAMP |
Indexed on client_id, category, billed_invoice_id, expense_date, and the composite (is_billable, billed_invoice_id) that powers the "billable + unbilled" filter. Deactivating the addon keeps the table so you don't lose entries if you re-enable later.
Logging an expense
The page has a quick-entry form along the top with these fields:
| Field | Required | What it does |
|---|---|---|
| Date | ✓ | When the expense was incurred. Defaults to today. |
| Client | optional | Pick a client; leave blank for internal / overhead costs. |
| Project | optional | Free-text label that's prefixed onto the invoice line: [Project] Description. |
| Category | ✓ | software / mileage / materials / contractor / travel / meals / other. |
| Description | ✓ | What the expense was for — this becomes the invoice line item description. |
| Amount | ✓ | Your cost. |
| Currency | ✓ | Defaults to your site currency. |
| Markup % | optional | Per-expense markup; defaults to the global default (see Settings below). |
| Billable | toggle | Check to bill it to the client; uncheck to track as internal cost. |
| Receipt | optional | Upload via the WP media library (image or PDF). |
| Notes | optional | Internal note; not shown on the invoice. |
Click Log expense to save. The list below the form updates immediately.
Converting expenses into an invoice
The list table has a checkbox column for unbilled, billable expenses. Pick the rows you want, pick a client from the toolbar dropdown, click Create invoice from selected. The addon:
- Creates a draft
easy_invoicepost titledExpenses Invoice — {Client Name}with the next available invoice number. - Atomically claims the selected rows by linking them to the new invoice ID (UPDATE ... WHERE billed_invoice_id IS NULL — race-free even when two admins do this concurrently).
- Populates the invoice's line items: one per claimed expense, with quantity 1 and rate =
amount × (1 + markup/100). The project prefix is included in the description. - Stores source metadata on each line item (
source: expense_tracking,expense_id,category,base_amount,markup_pct) so you can trace any line item back to its receipt. - If another admin won the race and you ended up with no claimed rows, the empty draft is rolled back automatically.
Filters
The list has filters for:
- Client (any client present in the expense table)
- Category (any of the 7 categories)
- Status — Unbilled (default) / Billed / All
- Billable — Any / Billable / Internal
The summary cards above the form reflect the filter set, not just the visible page — so "Billable, unbilled" tells you exactly how much is pending invoicing across your entire history.
Settings
The addon appends one setting to Easy Invoice → Settings:
- Default Expense Markup (%) — applied to new entries unless overridden per-entry. Default
0.
Hooks
| Hook | When |
|---|---|
easy_invoice_expense_tracking_invoice_created (invoice_id, client_id, claimed_entries) | A draft invoice has been generated from claimed expenses. |
easy_invoice_audit_log('expenses_invoiced', …) | If the Team Roles & Audit Log addon is active, every conversion gets an audit-log row. |
Pairs with
- Time Tracking & Project Billing — same workflow for billable hours
- Smart Reminders & Late Fees — chase the resulting invoices automatically
- Custom Invoice & Quote Templates — design how the expense lines render on the PDF