Skip to content

Instantly share code, notes, and snippets.

@shotasenga
Last active June 3, 2025 13:01
Show Gist options
  • Save shotasenga/aa5e83b9406f7fcc2910cc7c43a30d4b to your computer and use it in GitHub Desktop.
Save shotasenga/aa5e83b9406f7fcc2910cc7c43a30d4b to your computer and use it in GitHub Desktop.
Export Wealthsimple transactions as CSV
/*
* DISCLAIMER:
* This script extracts sensitive financial information (transaction data) from Wealthsimple.
* Ensure that you use this script in a secure environment and handle the extracted data responsibly.
* The developer of this script is not responsible for any issues or troubles that arise from its use.
*/
(async function () {
"use strict";
const transactions = [];
for (const button of x(`//button[contains(., 'Chequing')][contains(., '$')]`)) {
const payee = button.querySelector("p").innerText;
const amount = x(`.//p[contains(., '$')]`, button).next().value.innerText;
button.click();
await nextTick();
const [date, _] = Array.from(
x(
`.//p[contains(., 'Date')]/following-sibling::*//p`,
button.parentElement.parentElement
)
).map((el) => el.innerText);
transactions.push({
payee,
amount,
date: formatDateForYNAB(date),
});
}
const csv = [];
csv.push("Date, Payee, Amount");
for (const transaction of transactions) {
csv.push(
[transaction.date, transaction.payee, transaction.amount]
.map(escapeCsvField)
.join(",")
);
}
// save as a file
const blob = new Blob([csv.join("\n")], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "transactions.csv";
a.click();
function* x(xpath, root = document) {
const xpathResult = document.evaluate(
xpath,
root,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0; i < xpathResult.snapshotLength; i++) {
yield xpathResult.snapshotItem(i);
}
}
function nextTick() {
return new Promise((resolve) => setTimeout(resolve, 0));
}
function waitUntilElementExists(xpath, callback) {
const observer = new MutationObserver(() => {
const element = x(xpath).next().value;
if (element) {
observer.disconnect();
callback(element);
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
}
function escapeCsvField(field) {
return `"${field}"`;
}
function formatDateForYNAB(str) {
// "August 19, 2024" to "2024-08-19" using RegExp
const [, month_s, day_s, year] = str.match(/(\w+) (\d+), (\d+)/);
const month = (new Date(Date.parse(`${month_s} 1, 2020`)).getMonth() + 1)
.toString()
.padStart(2, "0");
const day = day_s.padStart(2, "0");
return `${year}-${month}-${day}`;
}
})();
@shotasenga
Copy link
Author

shotasenga commented Nov 22, 2024

This is a bookmarklet version of the CSV export user script for Wealthsimple.

Instruction

  1. Create a bookmarklet with the script below
  2. Open the "Activity" page on Wealthsimple
  3. Click the bookmarklet created
javascript:(function()%7B%2F*%0A%20*%20DISCLAIMER%3A%0A%20*%20This%20script%20extracts%20sensitive%20financial%20information%20(transaction%20data)%20from%20Wealthsimple.%0A%20*%20Ensure%20that%20you%20use%20this%20script%20in%20a%20secure%20environment%20and%20handle%20the%20extracted%20data%20responsibly.%0A%20*%20The%20developer%20of%20this%20script%20is%20not%20responsible%20for%20any%20issues%20or%20troubles%20that%20arise%20from%20its%20use.%0A%20*%2F%0A(async%20function%20()%20%7B%0A%20%20%22use%20strict%22%3B%0A%0A%20%20const%20transactions%20%3D%20%5B%5D%3B%0A%0A%20%20for%20(const%20button%20of%20x(%60%2F%2Fbutton%5Bcontains(.%2C%20'Chequing')%5D%5Bcontains(.%2C%20'CAD')%5D%60))%20%7B%0A%20%20%20%20const%20payee%20%3D%20button.querySelector(%22p%22).innerText%3B%0A%20%20%20%20const%20amount%20%3D%20x(%60.%2F%2Fp%5Bcontains(.%2C%20'CAD')%5D%60%2C%20button).next().value.innerText%3B%0A%0A%20%20%20%20button.click()%3B%0A%20%20%20%20await%20nextTick()%3B%0A%0A%20%20%20%20const%20%5Bdate%2C%20_%5D%20%3D%20Array.from(%0A%20%20%20%20%20%20x(%0A%20%20%20%20%20%20%20%20%60.%2F%2Fp%5Bcontains(.%2C%20'Date')%5D%2Ffollowing-sibling%3A%3A*%2F%2Fp%60%2C%0A%20%20%20%20%20%20%20%20button.parentElement.parentElement%0A%20%20%20%20%20%20)%0A%20%20%20%20).map((el)%20%3D%3E%20el.innerText)%3B%0A%0A%20%20%20%20transactions.push(%7B%0A%20%20%20%20%20%20payee%2C%0A%20%20%20%20%20%20amount%2C%0A%20%20%20%20%20%20date%3A%20formatDateForYNAB(date)%2C%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20const%20csv%20%3D%20%5B%5D%3B%0A%20%20csv.push(%22Date%2C%20Payee%2C%20Amount%22)%3B%0A%0A%20%20for%20(const%20transaction%20of%20transactions)%20%7B%0A%20%20%20%20csv.push(%0A%20%20%20%20%20%20%5Btransaction.date%2C%20transaction.payee%2C%20transaction.amount%5D%0A%20%20%20%20%20%20%20%20.map(escapeCsvField)%0A%20%20%20%20%20%20%20%20.join(%22%2C%22)%0A%20%20%20%20)%3B%0A%20%20%7D%0A%0A%20%20%2F%2F%20save%20as%20a%20file%0A%20%20const%20blob%20%3D%20new%20Blob(%5Bcsv.join(%22%5Cn%22)%5D%2C%20%7B%20type%3A%20%22text%2Fcsv%22%20%7D)%3B%0A%20%20const%20url%20%3D%20URL.createObjectURL(blob)%3B%0A%20%20const%20a%20%3D%20document.createElement(%22a%22)%3B%0A%20%20a.href%20%3D%20url%3B%0A%20%20a.download%20%3D%20%22transactions.csv%22%3B%0A%20%20a.click()%3B%0A%0A%20%20function*%20x(xpath%2C%20root%20%3D%20document)%20%7B%0A%20%20%20%20const%20xpathResult%20%3D%20document.evaluate(%0A%20%20%20%20%20%20xpath%2C%0A%20%20%20%20%20%20root%2C%0A%20%20%20%20%20%20null%2C%0A%20%20%20%20%20%20XPathResult.ORDERED_NODE_SNAPSHOT_TYPE%2C%0A%20%20%20%20%20%20null%0A%20%20%20%20)%3B%0A%0A%20%20%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20xpathResult.snapshotLength%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20yield%20xpathResult.snapshotItem(i)%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20function%20nextTick()%20%7B%0A%20%20%20%20return%20new%20Promise((resolve)%20%3D%3E%20setTimeout(resolve%2C%200))%3B%0A%20%20%7D%0A%0A%20%20function%20waitUntilElementExists(xpath%2C%20callback)%20%7B%0A%20%20%20%20const%20observer%20%3D%20new%20MutationObserver(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20element%20%3D%20x(xpath).next().value%3B%0A%20%20%20%20%20%20if%20(element)%20%7B%0A%20%20%20%20%20%20%20%20observer.disconnect()%3B%0A%20%20%20%20%20%20%20%20callback(element)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20observer.observe(document.documentElement%2C%20%7B%0A%20%20%20%20%20%20childList%3A%20true%2C%0A%20%20%20%20%20%20subtree%3A%20true%2C%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20function%20escapeCsvField(field)%20%7B%0A%20%20%20%20return%20%60%22%24%7Bfield%7D%22%60%3B%0A%20%20%7D%0A%0A%20%20function%20formatDateForYNAB(str)%20%7B%0A%20%20%20%20%2F%2F%20%22August%2019%2C%202024%22%20to%20%222024-08-19%22%20using%20RegExp%0A%20%20%20%20const%20%5B%2C%20month_s%2C%20day_s%2C%20year%5D%20%3D%20str.match(%2F(%5Cw%2B)%20(%5Cd%2B)%2C%20(%5Cd%2B)%2F)%3B%0A%20%20%20%20const%20month%20%3D%20(new%20Date(Date.parse(%60%24%7Bmonth_s%7D%201%2C%202020%60)).getMonth()%20%2B%201)%0A%20%20%20%20%20%20.toString()%0A%20%20%20%20%20%20.padStart(2%2C%20%220%22)%3B%0A%20%20%20%20const%20day%20%3D%20day_s.padStart(2%2C%20%220%22)%3B%0A%20%20%20%20return%20%60%24%7Byear%7D-%24%7Bmonth%7D-%24%7Bday%7D%60%3B%0A%20%20%7D%0A%7D)()%3B%7D)()%3B

DISCLAIMER

  • This script extracts sensitive financial information (transaction data) from Wealthsimple.
  • Ensure that you use this script in a secure environment and handle the extracted data responsibly.
  • The developer of this script is not responsible for any issues or troubles that arise from its use.

@shotasenga
Copy link
Author

Updated the script to reflect the wording changes on Wealthsimple (from "Cache" to "Chequing")

@shotasenga
Copy link
Author

Update the script for the wording changes ('CAD' to '$')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment