Bujeti category management product interface

At a glance

RoleSenior Product Designer, end to end
DeliverablesCategory model · three creation paths · rules engine · category insights · bulk editing
UsersFinance teams and admins at African businesses
ContextBujeti · spend management · cards, budgets and approvals

The problem

Bujeti already had categories. They were labels attached to expenses, and the default set that shipped with every account could not be tracked at all. A finance lead could tag a transaction “Travel” and then have no way to ask what Travel had cost this month, who was spending it, or whether it was over.

So the work happened somewhere else. Teams exported transactions, re-categorised them in a spreadsheet at month end, and made decisions from that copy. The categorisation inside the product was not wrong so much as inert, and the data quality problem that showed up in reporting was really an incentive problem: nobody categorises carefully when the category does nothing.

How I reframed the brief

I was handed a request for finer granularity, which reads as a request for deeper folders. Granularity was not going to fix anything on its own. A category nobody trusts is a category nobody fills in accurately, at any depth.

Research and approach

I ran sessions with finance users and internal stakeholders, then studied how other platforms model categories: accounting tools, spend management products, and the chart-of-accounts structures these teams already keep outside the product.

Two things came back that shaped everything after:

01 · The taxonomy already existed

Finance teams were not waiting for us to give them categories. They had a structure already, in a spreadsheet or an accounting package, and any product that made them retype it was going to lose.

02 · Accuracy is not an effort problem

Miscategorised spend was not carelessness. People categorise well when something depends on it, and they categorise badly when the field is decoration.

The decision: a category is a control, not a label

I redefined the category from a tag into an object that owns things. A category now carries a spend limit, an approval rule, a set of categorisation rules, its sub-categories, and every transaction that has landed against it. That single change is why it moved in the navigation and now sits under Compliance, beside approval rules and policies, rather than filed under expenses.

The list view carries the argument. It leads with Amount spent and Available, so the first thing a category tells you is how much room is left in it, not what it is called.

What it cost

A category that owns limits and rules is a heavier object to create, and setup got slower for a small team who only wanted five labels. It also put category management behind an admin permission, which means a spender can no longer invent a category mid-expense. I took that trade because uncontrolled category creation is the thing that destroys the reporting the feature exists to produce.

Categories list leading with amount spent and available, with expandable sub-category rows

Three ways in, because setup is not one situation

Making the category heavier made the first run at it harder, and research had already told me the taxonomy usually exists somewhere else. So creation is not one flow. It is three, each aimed at a different starting state.

01 · One at a time, for a system already running

The everyday case. Someone needs one more category, or one sub-category under an existing parent, and should not have to open a bulk tool to get it.

Creating a single category and sub-category
02 · A table, for building the structure from scratch

An editable grid with Add a category and Add a sub category as inline row actions, sub-rows indented under their parent, and a running count at the bottom so the person can see the shape of what they have built. Limit is a column, so the control is set at the moment the category is created rather than as a second pass nobody comes back for.

Bulk creation table, empty state and first entriesBulk creation with sub-categories nested under a parent and a running countCompleted category list after bulk creation
03 · CSV import, for the taxonomy that already exists

The import maps the customer’s column headers onto Bujeti’s fields, with their own sample data shown beside each mapping so they can confirm the match rather than trust it. For anyone without a file ready there is a pre-formatted template to download, which quietly teaches the required shape instead of rejecting a bad upload later.

CSV import with column mapping against sample data and a downloadable templateCSV import review list and confirmation

The limit, and the buffer under it

A hard limit fails in a specific way: it stops a legitimate purchase at the worst possible moment, usually with someone standing at a counter. No limit fails the other way, quietly, and only shows up in the month-end report.

So the limit is stated with an explicit buffer beside it, and the progress bar shows three zones rather than two: what has been spent, what remains inside the limit, and the declared overage the business has already agreed to absorb. The category is still controlled, and going slightly over is a visible, sanctioned state instead of a failure.

Everything the category owns is on this page as a tab: transactions, sub-categories, approval rules and categorisation rules. Insights sit under the limit, cutting the same spend by top sub-category, by trend and by person.

Category detail page with limit plus buffer, three-zone progress bar, insights, and tabs for transactions, sub-categories, approval rules and categorisation rules

Rules are what actually moved data quality

Research said accuracy would not improve by asking people to try harder, so the system had to do the categorising. A categorisation rule matches on transaction description or narration and assigns a category automatically, with any or all of its conditions required, and each condition using an explicit operator rather than a hidden fuzzy match.

I kept the rule readable on purpose. Someone in finance has to be able to look at a rule six months later and understand why a transaction landed where it did, which is also what makes a wrong rule fixable rather than mysterious. Rules run alongside the manual path, they never replace it, and the category’s transaction list is where a bad assignment gets caught.

Create categorisation rule with any or all condition matching and a target category

Where the category meets the money

The transactions tab lists what has actually hit the category: counterparty, amount, the person who spent it, the account, status, and whether a receipt is attached. The receipt column carries an add affordance where the file is missing, which turns a reporting gap into a one-click fix at the row where it happened. Past transactions can be pulled into a category as well as future ones, so adopting the feature does not mean starting the history over.

Category transactions tab showing counterparty, amount, spender, account, status and receipt state

Editing has to work at the same scale as creating

A structure imported from a CSV is a structure that will need correcting in bulk, so selection and multi-edit mirror the creation table rather than forcing a per-row modal. The single edit stays small and focused for the one-field change, which is the far more common case.

Category and sub-category tables with multi-select editingSingle category edit modal

How I worked

  • Translated PRDs into a category model, taking the request for deeper hierarchy and returning a definition of what a category owns
  • Planned and ran feature testing with users and engineers, validating the creation paths before any of them were built out
  • Partnered with customer success and marketing on launch, since a feature that changes where something lives in the navigation needs telling, not discovering
  • Drove roadmap prioritisation by arguing the rules engine up the order, because without automation the accuracy target was not reachable

Outcomes

50%+

improvement in expense data quality, which is the number the rules engine was built to move

36%

feature adoption inside the first quarter, on a feature that requires setup before it returns anything

Reflection

The useful lesson here was about reading a request. I was asked for granularity, and granularity was a real need, but shipping only that would have produced a deeper version of a field people already ignored. The work got good when I went after why the field was ignored instead.

The second lesson was that data quality is a systems problem wearing a behaviour problem’s clothes. Every intervention that worked here removed a human decision or gave one a consequence. None of them asked anyone to be more diligent.

What I would do differently

36% adoption in a quarter is decent for a feature with a setup cost, and I still do not know what stopped the other 64%. I never instrumented which creation path people chose, or how many started setup and abandoned it. If CSV import was carrying adoption, that is where the next investment goes. If nobody found it, that is a different and cheaper problem. I built three paths and shipped without the means to learn which one earned its keep.

BUJETIAccounts payable inside Bujeti: supplier invoices captured in seconds, approved before they're paid.
BUJETIRevamping the onboarding experience