Displaying Tags on Mint Transactions
I’ve used Mint.com to categorize and measure my spending for years. It is an amazing tool, but the user interface for tagging transactions has always had a frustrating limitation. After adding a tag to a transaction, you don’t see that tag anywhere on the page. I wrote a user script to display them:
How do I install it?
Follow the (very short) installation instructions on GitHub.
Why is this necessary?
On Mint, a transaction must belong to one category (for example, Vacation or Restaurants). At the same time, a transaction may belong to any number of tags (for example, I can add the Taiwan trip tag to an expense in the Vacation category, or add Work lunch and Client X tags to an expense in the Restaurants category). The way I use Mint, categories and tags are both important to gaining a complete understanding of where my money goes. Unfortunately, in the transactions list (above), only categories are displayed.
The only time you ever see what tags you have applied to a transaction is when you are editing it:
I’m baffled that the tags feature has such a glaring omission in functionality… so I wrote a user script to fix it.
How does it work?
A user script is a piece of JavaScript code which your browser includes on certain web pages. In this case, I’ve written code which alters the way that the Mint.com transactions page works. I want to see what data my browser is requesting from Mint’s servers, find the transaction tags within that data, store a temporary copy of them, and insert the tags into the page.
Find XHR responses containing transaction data
The first step is to use my browser’s developer tools to watch the traffic on the transactions page. When the page loads, the Mint app makes many requests: images, JavaScript code, and data. I’m interested only in what data is being sent, so I filter the requests to show only XHR:
I notice that the Mint app makes a request to getJsonData.xevent
on every page load. To investigate, I try loading different transactions into the page by clicking “Next” at the bottom of the transactions list, or by performing a transaction search. Each time, another request is made to that URL. Filtering the requests to show only those for this URL, and inspecting the content of the responses, confirms that the list of transactions is included in this data. Digging deeper into the structure of the data, I can find how the “labels” for each transaction are represented:
Overwrite the browser’s XHR code to intercept the data
The above AJAX requests are made every time the transactions page loads. JavaScript allows a developer to overwrite the base AJAX request function which is used by all other code on a page (including Mint’s own code) to obtain data from the server. I can replace that function with my own code, then look for completed requests made to the getJsonData.xevent
endpoint. Then my code can store its own copy of the data before the Mint app even sees it.
I overwrite the XMLHttpRequest.prototype.open
method to run my code first, and then call the browser’s original version of the method. This allows me to inspect every request:
React to changes in the content of the transactions table
Next, I need to use the data stored by the code above to add the tags to the transactions table. I can’t simply add them as soon as the data arrives, because the Mint app needs a chance to process that data and populate the table with it. A hacky way to do this would be to use setTimeout
or setInterval
to check the transactions table every few seconds to see if it’s changed, and add/remove tags as necessary. But there’s a better way: the MutationObserver API. This is a new-ish browser API, almost tailor-made for the use case of third-party code watching for changes to the DOM.
React to the user editing a transaction
It turns out that I can’t respond only to changes to the transactions table (triggered by navigating between pages of transactions or a search). I also need to update the table after the user edits a transaction. For this, the code must intercept XHR requests, so that it can spy on POSTs to the edit transaction endpoint (just as it spied on XHR responses for the transaction list above).
It doesn’t take much code
The final code is a bit more nuanced than just the above. For example, Mint makes a separate request for a mapping from tag ID to tag name, which also must be cached. When Mint updates the transactions table, it actually triggers multiple DOM mutation events for each row (one each for date, merchant, price, etc.), so the code must be smart enough to update each row only once. Also, observing the DOM must be paused while the user script itself mutates the DOM by adding tags.
Nonetheless, the full code to add this functionality is fewer than 200 lines of readable, whitespace-heavy JavaScript. Adding a browser extension like this requires trust, so read through the code yourself on GitHub!