Follow these steps to build your own DIY budget tracking spreadsheet.
Lesson 1 - Make a basic budget spreadsheet
Lesson 2 - Add some more advanced features
Lesson 3 - Adding Elite features to seal the deal
Basic - Entering Monthly Budgets
Basic - Managing Accounts
Basic - Recording Transactions
Advanced - Budget Dashboard
Advanced - Monthly Income/Expense/Net Worth Graph
Advanced - Average Expense/Category Graph
Elite - Transaction Entry Form
Elite - Sort Alphabetically by Most Used
Elite - Reconciling Accounts Tool
We're going to set up a basic Google Sheet to make monthly budgets on the Budget Entry sheet, manage accounts and see your total balance on the Accts sheet and record transactions on the Transactions sheet...creative name I know.
Pull up your blank spreadsheet.
This is where you'll plan your income & expenses each month. The best way to do this is estimate income & expenses for the year, then before the start of each month, go through and make your estimates more precise.
=if(B3<>"",CONCATENATE(B3," - ",C3),"")
=if(SUM(D3:O3)>0,SUM(D3:O3),"")
=sumif($Q$3:$Q,"Income",D3:D) - sumif($Q$3:$Q,"Purchase",D3:D)
Alright, that's our first sheet. Nicely down!
This sheet holds all of your accounts, checking, savings, investment, credit card, etc. You could even put your mortgage or other assets. It'll tally everything up and show total value of your accounts.
=if(B4="","",G3)
=if(B3<>"",C3+D3+E3+F3,"")
=if((G214+G199+G184+G169+G154+G139+G124+G109+G94+G79+G64+G49+G34+G19+G4)=0,"",G214+G199+G184+G169+G154+G139+G124+G109+G94+G79+G64+G49+G34+G19+G4)
=if(count(G214,G199,G184,G169,G154,G139,G124,G109,G94,G79,G64,G49,G34,G19,G4)=0,"",count(G214,G199,G184,G169,G154,G139,G124,G109,G94,G79,G64,G49,G34,G19,G4))
And that's the Accts sheet. We're on a roll now!
Once we are done with the Complete Elite Spreadsheet, we won't need to interact with the Transactions sheet very much. But for now, this is where we'll put purchases and income transactions.
That's it for the Transaction sheet!
In lesson 2, we are going to add some visual elements to the basic budget spreadsheet that will make it 100 times more useful.
You won't use these sheets very much, but they are important to hold your income and expenses through the year.
=query('Budget Entry'!$A$3:$Q,"Select B, A Where Q = 'Income'",0)
=if(B2="","",sum(iferror(filter(Transactions!$E$2:$E, month(Transactions!$C$2:$C) = month(C$1), Transactions!$G$2:$G = $B2),0)))
=if(B2="","",sum(C2:N2))
=if(B2="","",average(C2:N2))
Wooo! You made it through the first step of Lesson 2!
This sheet tells you how your year is going financially. The graphs we're about to build display transaction data, not budget amounts. The Main graph is a two-axis graph with Income and Expenses on the left, Y-axis and Account Total Balance on the right. This is a pseudo-net worth graph.
=ArrayFormula(iferror(match(B59:B,Expenses!A:A,0)))
=Expenses!C1
=if(isblank($B59),"", sumif(Expenses!$A:$A,$B59,Expenses!C:C))
=if(isblank($B59),"", sum(C59:N59))
=if(isblank($B59),"", average(C59:N59))
=C58
=arrayformula(unique(Income!B:B))
=if(not(isblank(A46)), indirect("Income!c"&A46&":p"&A46),"")
=c45
=sum(C46:C56)
=sum(C56:C)
=C38-C39
=StartingBalance+C40
=sum(C38:N38)
=sum(C39:N39)
=sum(C40:N40)
=N41
=iferror(averageif(C38:N38, ">0", C38:N38), 0)
=iferror(averageif(C39:N39, ">0", C39:N39), 0)
=iferror(averageif(C40:N40, ">0", C40:N40), 0)
=iferror(averageif(C41:N41, ">0", C41:N41))
Wheeew! That's it for formulas and cells for this sheet, though we still need to create the graphs.
OK, let's tackle the graphs now. First up, the more complex one. This graphs your monthly income vs monthly expenses on the left, Y-axis and the ending balance of all your accounts on the right, Y-axis.
The average income and average expense charts are so much easier.
The Graphs page is a little more of a few-times-per-year type of information. Now let's build a budget dashboard that will be useful every day.
Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
=date(B1,month(B3&1),1)
=EOMONTH(C2,0)
=date(B1,month(B3&1),1)
=QUERY('Budget Entry'!$A3:$Q,"SELECT B, A, Q, "&substitute(ADDRESS(2,MATCH($B$3,'Budget Entry'!$A$2:$O$2,0),4),2,"")&" where Q = 'Purchase'",0)
=ARRAYFORMULA(if(L3:L="","",if(O3:O>0,O3:O,0)))
=sumifs(Transactions!E:E,Transactions!G:G,M3,Transactions!C:C,">="&$C$2,Transactions!C:C,"<="&$C$3)
=if(if(N3="Income",Q3-O3,O3-Q3)=0,"",if(N3="Income",Q3-O3,O3-Q3))
=query($L$3:$R,"Select L, Sum(P), sum(Q) where L is not null and (P !=0 or Q != 0) group by L order by sum(P) desc label Sum(P) 'Budgeted', Sum(Q) 'Actual' ",0)
=iferror(if(I4="","",if(I4=0,(I4-J4)/J4,(I4-J4)/I4)),"")
=sum(I4:I)
=sum(J4:J)
=I2-J2
=(I2-J2)/I2
=if(E3>0,"Budget Left","Over Budget")
That's it for Lesson 2!
Up to this point, you have a kick 'n budget spreadsheet. However, you have one that does the same thing as 90% of the ones on the internet. Let's kick it up a notch and make this thing truly spectacular!
We are going to build a form that makes it easy to input transactions. On that input form, we're going to give you the ability to list your categories and accounts by the number of times used or alphabetically. Finally, we're going to build a reconciling sheet that makes it quick and easy to reconcile your account each month.
Get excited, because we're about to dig into scripts!
This is a set-it and forget-it sheet. Once we're done here, you can drag this sheet all the way to the right and never look at it again.
We are going to query our budget entry sheet to get the a comprehensive list of sub-categories, we are going to query the transactions sheet to get a count of how many times each sub-category was used and then, we are going to put those two queries together.
Then we are going to do the same for Accounts.
=QUERY('Budget Entry'!A3:A,"select A, 1 where A is not null order by A label A 'Categories', 1 'Count'",0)
=ARRAYFORMULA(Unique({Query(Transactions!G2:G&{"",""},"select Col1, count(Col2) where Col1 != '' group by Col1 order by Col1 label Col1 'Category', count(Col2) 'Count'",0);G4:H}))
=sort(Query(D4:E, "select D, sum(E) where D != '' group by D label D 'Category', sum(E) 'Count'",0),2,false)
=QUERY(Accts!A2:A,"select A, 1 where A is not null order by A label A 'Accounts', 1 'Count'",0)
=ARRAYFORMULA(Unique({Query(Transactions!H2:H&{"",""},"select Col1, count(Col2) where Col1 != '' group by Col1 order by Col1 label Col1 'Accounts', count(Col2) 'Count'",0);P4:Q}))
=sort(Query(M4:N, "select M, sum(N) where M != '' group by M label M 'Accounts', sum(N) 'Count'",0),2,false)
Alright. That is step 1!
Here we will build a sheet that functions like an input form to make it easy to record transactions when you are out and about.
=QUERY('Budget Entry'!$A3:$Q,"SELECT sum("&substitute(ADDRESS(2,month(today())+3,4),2,"")&") where Q = 'Purchase'",0)
=QUERY(Transactions!$C2:$E,"SELECT sum(E) where D = 'Purchase' and C >= date '"&text(eomonth(today(),-1)+1,"yyyy-mm-dd")&"' and C <= date '"&text(EOMONTH(eomonth(today(),-1)+1,0),"yyyy-mm-dd")&"'",0)
=if(C15>0,"Budget Left","Over Budget")
=text(today(),"MMMM")
=B25
=C25
=C13-C14
=(C13-C14)/C13
Before we finish the last step, I'd like to say, I am proud of you for making it this far. If you are willing to put this much work in, I know you have the buy-in to stick to your budget and reach your financial goals.
If you are able and want to encourage me, my venmo is Michael-Shipe-1. Event a little bit is very encouraging ;)
We're going into the guts of Google Sheets here to create some scripts that Submit Transactions and List Accounts/Categories by use as well as alphabetically.
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Transaction Entry"); //Data entry Sheet
var datasheet = ss.getSheetByName("Transactions"); // Destination Sheet
var today = formSS.getRange("C2").getValue(); // date
// If they left the date cell blank, use today's date
if ( today === "" ){
today = Utilities.formatDate(new Date(), "GMT-5", "MM/dd/yyyy");
}
// See if the purchase checkbox is checked and set the 'purchase' variable accordingly
var purchase = formSS.getRange("C3");
if ( purchase.isChecked() ){
purchase = "Purchase";
} else {
purchase = "Income";
}
// Set a confirmation message
var msg1 = "Got it.";
// Find the last transaction ID and create a transaction ID for this entry
var lastId = datasheet.getRange(datasheet.getLastRow(),1).getValue();
var newId = lastId + 1;
// Grab the variables and input values from the sheet and hold them in an array called 'values'
var values = [[newId,
"",
today,
purchase,
formSS.getRange("C4").getValue(),
formSS.getRange("C5").getValue(),
formSS.getRange("C6").getValue(),
formSS.getRange("C7").getValue()]];
// Record the transaction data in the transaction sheet
datasheet.getRange(datasheet.getLastRow()+1, 1, 1, 8).setValues(values);
// Record the confirmation message
formSS.getRange("C9").setValue(msg1);
// Clear the form's content
formSS.getRange("C2").clearContent();
formSS.getRange("C4:C8").clearContent();
}
function onEdit(e){
const rg = e.range; // the range changed
// If the "range changed" is C8, then submit the data
if(rg.getA1Notation() === "C8" && rg.isChecked()){
submitData(); // run the submitData function
rg.uncheck(); // clear the submit checkbox
}
}
function onEdit(e){
const rg = e.range; // the range changed
// Listening for changes on Transaction Entry sheet
if(rg.getSheet().getName() === "Transaction Entry"){
// Clear confirmMsg
if(rg.getA1Notation() === "C4"){
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('\'Transaction Entry\'!C9').clearContent();
}
// If the "range changed" is C8, then submit the data
if(rg.getA1Notation() === "C8" && rg.isChecked()){
submitData(); // run the submitData function
rg.uncheck(); // clear the submit checkbox
}
}
}
function mostUsed() {
// Categories
var spreadsheet = SpreadsheetApp.getActive();
// create a new data validation in Transaction Entry C6 based off A4:A in the data sheet
spreadsheet.getRange('\'Transaction Entry\'!C6').setDataValidation(SpreadsheetApp.newDataValidation()
.setAllowInvalid(true)
.requireValueInRange(spreadsheet.getRange('data!$A$4:$A'), true)
.build());
// Accounts
var spreadsheet = SpreadsheetApp.getActive();
// create a new data validation in C7 for accounts based off the J column in Data
spreadsheet.getRange('\'Transaction Entry\'!C7').setDataValidation(SpreadsheetApp.newDataValidation()
.setAllowInvalid(true)
.requireValueInRange(spreadsheet.getRange('data!$J$4:$J'), true)
.build());
};
function alpha() {
// Categories
var spreadsheet = SpreadsheetApp.getActive();
// build the same data validations but based off different columns in data
spreadsheet.getRange('\'Transaction Entry\'!C6').setDataValidation(SpreadsheetApp.newDataValidation()
.setAllowInvalid(true)
.requireValueInRange(spreadsheet.getRange('data!$G$4:$G'), true)
.build());
// Accounts
var spreadsheet = SpreadsheetApp.getActive();
// build the same data validations but based off different columns in data
spreadsheet.getRange('\'Transaction Entry\'!C7').setDataValidation(SpreadsheetApp.newDataValidation()
.setAllowInvalid(true)
.requireValueInRange(spreadsheet.getRange('data!$P$4:$P'), true)
.build());
};
// Sort categories and accounts
if(rg.getA1Notation() === "C10" && rg.isChecked()){
alpha();
} else {
mostUsed();
}
Great job! You are nearing the finish line. One. More. Step.
This page is awesome and I'll tell you why, This page makes reconciling back accounts SO much faster. You put in your statement details into the account sheet, then pull that account up on this page and displays all of the transactions for that account for the statement dates. Not only that, but you can mark them cleared from this one screen.
=INDEX(INDIRECT("Accts!B"&MATCH(D1,Accts!A:A, 0)+1&":B"&(MATCH(D1,Accts!A:A, 0)+1)+11), MATCH(99^99,INDIRECT("Accts!B"&MATCH(D1,Accts!A:A, 0)+1&":B"&(MATCH(D1,Accts!A:A, 0)+1)+11), 1)-1)
=INDEX(INDIRECT("Accts!c"&MATCH(D1,Accts!A:A, 0)+1&":c"&(MATCH(D1,Accts!A:A, 0)+1)+11), MATCH(99^99,INDIRECT("Accts!c"&MATCH(D1,Accts!A:A, 0)+1&":c"&(MATCH(D1,Accts!A:A, 0)+1)+11), 1))
=INDEX(INDIRECT("Accts!B"&MATCH(D1,Accts!A:A, 0)+1&":B"&(MATCH(D1,Accts!A:A, 0)+1)+11), MATCH(99^99,INDIRECT("Accts!B"&MATCH(D1,Accts!A:A, 0)+1&":B"&(MATCH(D1,Accts!A:A, 0)+1)+11), 1))
=INDEX(INDIRECT("Accts!G"&MATCH(D1,Accts!A:A, 0)+1&":G"&(MATCH(D1,Accts!A:A, 0)+5)+11), MATCH(99^99,INDIRECT("Accts!G"&MATCH(D1,Accts!A:A, 0)+1&":G"&(MATCH(D1,Accts!A:A, 0)+1)+11), 1))
=iferror(sort(query({filter(Transactions!$A$2:$H, Transactions!$C$2:$C > $C$2, Transactions!$C$2:$C <= $F$2, Transactions!$H$2:$H = $D$1,Transactions!$D$2:$D = "Purchase")},"select * WHERE Col3 Is Not NULL"),3,TRUE),"No Transactions Found")
=sumif(C7:C, "Y", F7:F)
=iferror(sort(query({filter(Transactions!$A$2:$H, Transactions!$C$2:$C > $C$2, Transactions!$C$2:$C <= $F$2, Transactions!$H$2:$H = $D$1,Transactions!$D$2:$D = "Income")},"select * WHERE Col3 Is Not NULL"),3,TRUE),"No Transactions Found")
=C3-D5+N5-F3
The very last thing is to edit our script so when you check or uncheck the checkboxes, it records them cleared on the transactions sheet and gets them counted here towards Reconciliation.
function onEdit(e){
const rg = e.range;
// Listening for changes on Transaction Entry sheet
if(rg.getSheet().getName() === "Transaction Entry"){
// Clear confirmMsg
if(rg.getA1Notation() === "C4"){
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('\'Transaction Entry\'!C9').clearContent();
}
// If the "range changed" is C8, then submit the data
if(rg.getA1Notation() === "C8" && rg.isChecked()){
submitData();
rg.uncheck();
}
// If the "range changed" is C10, then submit the data
if(rg.getA1Notation() === "C10" && rg.isChecked()){
alpha();
} else {
mostUsed();
}
} else if (rg.getSheet().getName() === "Reconciling"){ // Listening for changes on Reconciling sheet
// mark transaction as cleared from reconciling sheet to transaction sheet
if(rg.getColumn() === 1 || 11){ // if the change is in column 1 or 11 (the ones with the check boxes)
var spreadsheet = SpreadsheetApp.getActive();
var row = rg.getRow();
var col = rg.getColumn();
var transId = SpreadsheetApp.getActiveSheet().getRange(row,col + 1);
var id = transId.getValue(); // get the id of the transaction that was checked
// insert 'y' in transactions sheet in the cell next to id
var ss = SpreadsheetApp.getActiveSpreadsheet();
var datasheet = ss.getSheetByName("Transactions"); // Destination Sheet
var data = datasheet.getDataRange().getValues();
// find the row with the id searching backwards through all transactions
for(var i = data.length - 1; i >= 0; i--){
if(data[i][0] == id){
var row = (i+1)
break;
}
}
// add/remove 'Y' next to that transaction
if(rg.isChecked()){
datasheet.getRange(row,2).setValue('Y');
} else {
datasheet.getRange(row,2).setValue('');
}
}
// Clear checkboxes on Reconciling sheet when account in D1 is changed
if(rg.getA1Notation() === "D1"){
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A7:A').clearContent();
spreadsheet.getRange('K7:K').clearContent();
}
}
}
That's it! You did it! Nice work!
Great job getting through this...I didn't know if anyone would take the time.
If this has been helpful for you and you want to say thanks, my venmo is Michael-Shipe-1.