Skip to content

Instantly share code, notes, and snippets.

@idpaterson
Last active November 15, 2023 15:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save idpaterson/89ce0cbc6e578a9398257debd1898555 to your computer and use it in GitHub Desktop.
Save idpaterson/89ce0cbc6e578a9398257debd1898555 to your computer and use it in GitHub Desktop.
Allows exporting historic daily balances for any account(s) in Mint

Mint Daily Balance Export

🎉 The dev team at Monarch Money has created a Chrome extension that requires no setup and simplifies the export process for those who just want to grab all accounts at once. Please consider using that instead and if you can, join me in contributing to its open source code!


The Trends tool in Mint allows exporting historic balances for any combination of accounts. However, the export summarizes balances on a monthly basis and daily granularity is only available for time periods of 43 days or shorter. This tool simply gathers that data in 43 day increments to produce a CSV file of daily balances over any time period.

Export Daily History to CSV button screen recording

While perfect transaction data would allow backfilling historic balances, certain accounts like Zillow property or KBB vehicle values have irreplaceable history. Export it from Mint while you still can, even if balance history carries over to Credit Karma anything older than 3 years will be lost!

Installation

For use with a userscript browser extension like Greasemonkey (Firefox) or Tampermonkey (Chrome).

Once one of the above browser extensions is installed you can install the daily balance export userscript by clicking below:

Mandatory setup

This is the tricky part, but you only need to do it once!

  1. Go to the Trends page in Mint.
  2. Open your browser's developer tools (how?) and maximize to fill the screen.
  3. Click the Network tab.
  4. Refresh the browser to reload the Mint Trends page.
  5. Click the search icon in the top toolbar of the Networking tab.
  6. Paste the text cancelToken.throwIfRequested, there may be a few scripts containing that text.
  7. Click the result that includes "smart-money-app" in the file path. Find smart-money-app
  8. Right click anywhere in the code view panel of the Networking tab and select the "Open in Sources Panel" menu item. Open in Sources Panel
  9. Find the "Overrides" tab at the upper left of the dev tools, it may be hidden behind a >> button. overrides
  10. Click "Select folder for Overrides" and create a new folder anywhere on your computer.
  11. If prompted, allow Chrome to access the new folder.
  12. Click anywhere in the source code on the right panel.
  13. Press CtrlF (F on macOS) and search the file for cancelToken.throwIfRequested.
  14. Just below that result is a line that reads e.exports = function(e) {. Press enter (return) to add a new line above the return then paste the following code:
    if (e?.url?.endsWith('/trends')) {
        window.lastTrendsRequest = e;
    }
  1. Confirm that the code now looks like this (indentation does not matter): modified-code
  2. Press CtrlS (S) to save your changes as an override.
  3. Close the developer tools window.

Now you're all set to export daily balances!

Usage

  1. Open your browser's developer tools (how?) and minimize the window. This will apply the code overrides in mandatory setup.
  2. Go to the Trends page in Mint.
  3. Refresh the page if you do not see a "Export Daily History to CSV" button at the bottom of the page (see privacy for more details).
  4. Click "Assets" > "Over time" or "Debts" > "Over time" in the sidebar (daily export is not yet supported for any other trends).
  5. In the "From" dropdown, click to deselect "All accounts" then select the account that you want to export.
  6. In the "During" dropdown, select "All time" or any other date range.
  7. Click "Export Daily History to CSV" and wait for the automatic CSV file download.
  8. Rename the downloaded CSV file to correspond to the account you just exported.

If you see a "No trends request found" alert, please double-check that you have completed the mandatory setup.

Tips

  • Be sure to export the balance history for asset accounts like property and vehicles. Mint has historic data from Zillow and Kelley Blue Book that may not be possible to retrieve any other way.
  • Certain events like mortgage buyouts, 401k to IRA rollovers, and credit card fraud reissuance may leave you with activity spread across multiple accounts in Mint. Just select all relevant accounts in the Trends view to preserve as much history as possible. Look for gaps or double reported balances in the monthly trends before exporting, but in general the daily balances are less likely to overlap. I have one account that has amassed 7 separate entries in Mint over the years, it's great to be able to export that as one unified history!
  • "All time" is fine even if the account was closed years ago; the script will only request enough data to reach the first zero balance day.

Importing balance history to Monarch Money

Monarch Money does not backfill past account balances, so you may see some wildly inaccurate gains or losses in the last month of your net worth trends. Well I mean maybe you bought a house and a car and funded all of your investments from zero in the last few days, but... probably not. Fortunately it is easy to import past balances for any account type.

  1. Export a daily balance history CSV for a single account following the steps above.
  2. Click "Accounts" in the Monarch main menu.
  3. Click the account corresponding to your Mint import.
  4. Click "Edit" in the upper right of the account page.
  5. Select "Upload balance history" from the dropdown.
  6. Drop or select the CSV file that you just exported.
  7. Click "Add to account" and confirm the import.
  8. Click the "All" timeframe above the balance chart to confirm that the balance history was imported successfully.

If you have not yet tried Monarch Money and would like to support my work, please consider signing up with my referral link. You may also be able to use the promo code MINT50 for 50% off your first year. If you can't use both, forget my referral and use the promo code because you save $50 and I would only earn $15 from the referral! I tried Monarch Money, Simplifi, YNAB, and Fidelity Full View as Mint replacements and Monarch works best for me.

Privacy

This script is only activated upon direct access to the following pages of mint.com:

  • https://mint.intuit.com/trends

It is not activated if you start at a different page and then click through to the Trends page (see Usage step #2). This decision ensures that you can continue to use Mint normally if there is any malfunction with the script.

This script uses the Mint API to load balance history data into your web browser which then generates a local CSV file. The data never leaves your computer and there are no third party backends, analytics, or other privacy concerns introduced by using this script.

Updates

This userscript is configured to support automatic updates when changes are published. Your userscript manager may prompt you to install updates when available; refer to this gist for information about the changes in each version.

History

0.2.0 - November 2023

Disable the daily export button on unsupported trends. Currently only assets by time and debts by time are supported. Remove rare duplicate dates in exported CSV.

0.1.0 - November 2023

Avoids excessive API requests for old zeroed accounts. CSV includes only the first zero balance date if all dates thereafter are zero. The script has now been tested by exporting all of my accounts.

0.0.1 - November 2023

Initial release with daily balance export of Assets or Debts accounts.

License

MIT License

Copyright (c) 2023 Ian Paterson

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// ==UserScript==
// @name Mint Daily Account Balance Exporter
// @namespace http://idpaterson.github.io/
// @license MIT - https://tldrlegal.com/license/mit-license
// @version 0.2.1
// @description Export daily account balance CSV from Mint trends
// @author Ian Paterson
// @match https://mint.intuit.com/trends
// @icon https://blog.mint.com/wp-content/uploads/2019/04/cropped-CB_M_Pref_I_RGB.png
// @grant none
// @updateURL https://gist.githubusercontent.com/idpaterson/89ce0cbc6e578a9398257debd1898555/raw/mint-daily-balance-export.meta.js
// @downloadURL https://gist.githubusercontent.com/idpaterson/89ce0cbc6e578a9398257debd1898555/raw/mint-daily-balance-export.user.js
// ==/UserScript==
// ==UserScript==
// @name Mint Daily Account Balance Exporter
// @namespace http://idpaterson.github.io/
// @license MIT - https://tldrlegal.com/license/mit-license
// @version 0.2.1
// @description Export daily account balance CSV from Mint trends
// @author Ian Paterson
// @match https://mint.intuit.com/trends
// @icon https://blog.mint.com/wp-content/uploads/2019/04/cropped-CB_M_Pref_I_RGB.png
// @grant none
// @updateURL https://gist.githubusercontent.com/idpaterson/89ce0cbc6e578a9398257debd1898555/raw/mint-daily-balance-export.meta.js
// @downloadURL https://gist.githubusercontent.com/idpaterson/89ce0cbc6e578a9398257debd1898555/raw/mint-daily-balance-export.user.js
// ==/UserScript==
(async () => {
'use strict';
const MAX_DAILY_WINDOW = 43; // Largest date range for daily granularity trends
let progress = null; // null if not exporting, otherwise numeric
/** Waits for selector to match and returns corresponding element. */
const getElement = async (selector, parent = document) => {
return new Promise((resolve) => {
const find = () => {
const element = parent.querySelector(selector);
if (element) {
resolve(element);
} else {
setTimeout(find, 100);
}
}
find();
});
};
/** {@link Date} to `yyyy-mm-dd` date. */
const toISODate = (date) => {
const [isoDate] = date.toISOString().split('T');
return isoDate;
}
/**
* Active trend category.
*
* Possible values include `ASSETS_TIME`, `DEBTS_TIME`, `NET_INCOME`, `NET_WORTH`,
* `SPENDING_TIME`, and `INCOME_TIME`.
*/
const getTrendCategory = async () => {
const activeTrendCategory = await getElement('.trends-sidebar-report-selected-list-item a');
return activeTrendCategory.id;
};
/** Whether the extension can generate daily balances for the active trend. */
const isSupportedTrendCategory = (category) => {
return category === 'ASSETS_TIME' || category === 'DEBTS_TIME';
}
/** Parses date from metadata on the SVG chart element. */
const getDateForChartBar = (bar) => {
const [year, month, date] = bar.getAttribute('name').split('-'); // yyyy-mm-dd
// This date represents the last day in the period, noon avoids daylight savings time issues
return new Date(`${year}-${month}-${date} 12:00:00`);
}
/** Date corresponding to the first bar in the trend chart backed up to the 1st of the month. */
const getEarliestTransactionDate = async () => {
const firstBar = await getElement('.recharts-layer.recharts-bar-rectangle:first-of-type path');
const date = getDateForChartBar(firstBar);
// First of the month
date.setDate(1);
return date;
};
/** Date roughly corresponding to the last nonzero transaction based on monthly sums. */
const getLatestNonzeroTransactionDate = async () => {
const table = await getElement('[data-automation-id="trend-summary-table"]');
const tableRows = Array.from(
table.querySelectorAll('tbody tr')
);
tableRows.reverse();
// 1-based index of last nonzero row (first encountered since we iteration is reversed)
const index = tableRows.length - tableRows.findIndex(
(row) => +row.lastElementChild.innerText.replace(/[^0-9.-]/g, '') !== 0
);
let lastNonzeroBar = await getElement(`.recharts-layer.recharts-bar-rectangle:nth-of-type(${index}) path`);
const date = getDateForChartBar(lastNonzeroBar);
// Add one month since zeroed accounts often have daily data beyond the last bar in the chart
date.setDate(date.getDate() + 30);
return date;
};
/** Request daily history for the current trend and download as a CSV file. */
const exportDailyHistory = async (request, oldestDate) => {
progress = 0;
try {
const data = JSON.parse(request.data);
const trendCategory = await getTrendCategory();
const maxDate = await getLatestNonzeroTransactionDate();
let history = [];
const dateSpan = maxDate - oldestDate;
const startDate = new Date(oldestDate);
while (startDate < maxDate) {
let endDate = new Date(startDate);
endDate.setDate(endDate.getDate() + MAX_DAILY_WINDOW);
if (endDate > maxDate) {
endDate = maxDate;
}
request.data = JSON.stringify({
...data,
dateFilter: {
type: 'CUSTOM',
startDate: toISODate(startDate),
endDate: toISODate(endDate),
},
});
const response = await request.adapter(request);
const responseData = JSON.parse(response.data);
history.push(...responseData.Trend);
progress = (endDate - oldestDate) / dateSpan * 100;
updateExportProgressBar();
startDate.setDate(startDate.getDate() + MAX_DAILY_WINDOW + 1);
}
// Dedupe, in rare cases Mint will return duplicate entries
const seenDates = {};
history = history.filter(({ date }) => {
if (seenDates[date]) {
return false;
}
seenDates[date] = true;
return true;
});
let lastZeroBalanceIndex = history.length;
while (--lastZeroBalanceIndex >= 0) {
if (history[lastZeroBalanceIndex].amount !== 0) {
lastZeroBalanceIndex += 1;
break;
}
}
const firstNonzeroBalanceIndex = history.findIndex((entry) => entry.amount !== 0);
const amountMultiplier = trendCategory.startsWith('DEBTS') ? -1 : 1;
const csv = [
'Date,Balance',
...history
.slice(firstNonzeroBalanceIndex, lastZeroBalanceIndex + 1)
.map((entry) => `${entry.date},${entry.amount * amountMultiplier}`),
].join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(blob);
downloadLink.download = 'mint-daily-history.csv';
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
} catch (e) {}
progress = null;
updateExportProgressBar();
};
// Export daily balance link to add to the trends page
const exportAllLink = document.createElement('a');
exportAllLink.innerHTML = '<span style="mix-blend-mode: multiply">Export Daily History to CSV</span>';
exportAllLink.id = 'export-daily';
exportAllLink.style.borderRadius = '4px';
exportAllLink.style.marginLeft = '8px';
exportAllLink.addEventListener('click', async (event) => {
// Prevent double exports
if (progress != null) {
return;
}
const request = window.lastTrendsRequest;
if (!request) {
alert('No trends request found, please see daily history export setup instructions and ensure that dev tools are open');
} else {
progress = 0;
const startDate = await getEarliestTransactionDate();
exportDailyHistory(request, startDate);
}
event.preventDefault();
});
/** Sets the background gradient of the export daily button to indicate data pull progress. */
const updateExportProgressBar = () => {
exportAllLink.style.background = progress != null
? `linear-gradient(to right, #1b8381 ${progress}%, #f4f5f8 ${progress}%)`
: 'white';
};
/**
* Adds the daily export link after Mint's Export CSV link if not already present and updates
* its status.
*/
const addExportLink = async () => {
if (!document.getElementById('export-daily')) {
const exportLink = await getElement('[data-automation-id=export-csv]');
exportAllLink.className = exportLink.className;
updateExportProgressBar();
if (!document.getElementById('export-daily')) {
exportLink.after(exportAllLink);
}
}
const category = await getTrendCategory();
if (isSupportedTrendCategory(category)) {
exportAllLink.style.pointerEvents = 'unset';
exportAllLink.style.opacity = 1;
} else {
exportAllLink.style.pointerEvents = 'none';
exportAllLink.style.opacity = 0.25;
}
};
setInterval(addExportLink, 1000);
const heading = await getElement('#trends-main-content h1');
const observer = new MutationObserver(addExportLink);
observer.observe(heading, {
characterData: false,
attributes: false,
childList: true,
subtree: false,
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment