Help setting up Tags

Hello everyone,

I’m new to Tiller Money and would appreciate some advice on how to install the tag feature and the tags report. My categories are fine, and I like using them for broader categorization. However, I occasionally need tags for more granular tracking, such as spending on a particular trip or a specific set of purchases related to events.

Tags are a central part of how I manage information, and I’m hoping to implement them easily with Tiller Money. I found some instructions for tags and noticed a good tag solution in the Tiller Money feeds, but the instructions seem a bit dated and don’t apply to the current version of Google Sheets.

Any guidance or updated instructions would be greatly appreciated!

Thank you!

Welcome. I’m a big tag user myself. Are you looking at these instructions? I think those still work and will get you up-and-running.

Thank you for the reply. I had not seen these instructions. What I was seeing were the template links for Tag Report and Tag Manager.

This appears to have what I need as far as adding them to transactions and categories, so thanks so much for that.

I am still lost on how to get to the tags report, though. Below are the instructions that I am finding linked to the doc you shared with me, there are other links too but they appear to be broken. These instruction don’t seem current for the latest setup of sheets. I have the Tiller Community Solutions but I don’t see an option for Analysis or any tags drop down. Perhaps I am missing something.

How to Get the Tags Report

  1. Install or launch the [Tiller Community Solutions add-on]
  2. Open the add-on and choose "Analysis” from the tags dropdown on the Explore tab
  3. Click on Tags Report
  4. Choose “Add to spreadsheet.”

Got it. When you open the Tiller Community Solutions sidebar, just enter Tags in the search bar. Tags Report should be the first thing that pops up in the search returns. Click on that and then “Add to spreadsheet.” That should do it, I think (and hope).

How do I open the Tiller Community Solutions sidebar?

If you look under the Extensions menu in Google Sheets, you should see Tiller Community Solutions there. From that submenu, you can open it.

Hi there! It doesn’t show up for me. Is the tag report template no longer available?

Hello,

Have a look at these 2 sheets

And this one

Thank you! And my bad, now I finally understand how to navigate the Tiller Community Solutions sidebar.

By the way, with some vibe coding and code review, I made a small Apps Script for editing category and tag names while keeping transaction dropdown data in sync / valid. This is extremely useful for myself and I think others might benefit from it. For renaming category however there’s already a community tool so I’d recommend you use that first. If you’re like me you want it for tag too, then continue to the following.

What the script does and assumes:

  • A tab (i.e., sheet) named “Transactions“
    • Contains header of “Tags“ and “Category“. Which column doesn’t matter - it just have to exist (but should only have exactly one column named as Tags and Category).
  • A tab named “Tags“ AND a tab named “Categories“. For both, the script uses A2:A (entire column A, less the 1st row because that’s header) as a list for source of dropdown options.
  • In sheet “Transactions“, you have data validation set up for Category and Tags column. Both configured as Criteria = Dropdown (from a range) . It’s up to you to “Allow multiple selections” or not. The script supports either way. If multiple selection, do make sure not to have leading or trailing spaces.
  • :police_car_light: DO NOT include comma in your tag or category name, for obvious reason.

Then there you go, each time you finish editing your list in sheet Tags and Categories (e.g. when you finish editing a cell and hit enter), wait for a few seconds and you shall see a toast notification at the right bottom notifying you the operation succeed:

In google sheet menu bar, select “Extensions > Apps Script”, then paste the following in Code.gs file. Click save (no need to deploy or anything). Now navigate back to your sheet, go to Tags, try edit a tag (or the Categories sheet). You should see the toast, and then go check your Transaction sheet to see the results.

I recommend test with a new tag/category and small scope of data so that it doesn’t get mess up with your data in case some error happen.



/**
 * Helper function to find a column index by its header name.
 * 
 * @param {GoogleAppsScript.Spreadsheet.Sheet} sheet The sheet object to search.
 * @param {string} headerName The text of the header to find.
 * @param {number} headerRow The row number where headers are located (e.g., 1).
 * @returns {number} The 1-based column index, or -1 if not found.
 */
function getColumnByHeaderName(sheet, headerName, headerRow) {
  const lastColumn = sheet.getLastColumn();
  // Get all header values in the specified row (e.g., A1:R1)
  const headers = sheet.getRange(headerRow, 1, 1, lastColumn).getValues()[0];
  
  // Find the index of the header name (+1 because arrays are 0-indexed, but Sheets columns are 1-based)
  for (let i = 0; i < headers.length; i++) {
    // Note: toString().trim() is used for robust comparison against the header string
    if (headers[i].toString().trim() === headerName.trim()) {
      return i + 1; // Return the 1-based column index
    }
  }
  return -1;
}

/**
 * Automatically updates all transaction tags/categories when a master list item is renamed.
 * This function is triggered automatically on any manual cell edit.
 *
 * @param {GoogleAppsScript.Events.SheetsOnEdit} e The event object.
 */
function onEdit(e) {
  // --- CONFIGURATION ---
  
  // Maps the master list sheet name to the target column header in the transaction sheet.
  const SOURCE_TO_TARGET_MAP = {
    // If master sheet 'Tags' is edited, update transaction column 'Tags'
    'Tags': 'Tags',
    // If master sheet 'Categories' is edited, update transaction column 'Category'
    'Categories': 'Category' 
  };

  // Column where the master list items (Tags/Categories) are located in their respective sheets
  const MASTER_LIST_COLUMN_INDEX = 1; 

  // Name of the sheet with your transaction data (where the 'Tags' and 'Category' columns are)
  const TRANSACTION_SHEET_NAME = 'Transactions';
  
  // Row number where the headers are located in the transaction sheet
  const HEADER_ROW = 1;
  
  // Set to true if tags/categories should be matched with case-sensitivity
  const IS_CASE_SENSITIVE = false;
  // ---------------------

  const range = e.range;
  const sourceSheet = range.getSheet();
  const sourceSheetName = sourceSheet.getName();
  
  // 1. Check if the edited sheet is one of the configured master lists
  const targetHeader = SOURCE_TO_TARGET_MAP[sourceSheetName];
  if (!targetHeader) {
    return;
  }
  
  // 2. Check if the edited column is the master list column and it is not the header row
  if (range.getColumn() === MASTER_LIST_COLUMN_INDEX && range.getRow() > HEADER_ROW) {
    
    // The event object 'e' automatically provides the old and new value. Trim them.
    const newName = e.value.toString().trim();
    const oldName = e.oldValue.toString().trim();

    // A check to ensure both old and new values are present and different
    if (!oldName || !newName || oldName === newName) {
      return;
    }

    if (oldName.includes(',') || newName.includes(',')) {
      e.source.toast(`🚨 Error: item contains illegal comma, please remove them in your ${sourceSheetName}`, true, 5);
      return;
    }
    
    // Get the transaction sheet
    const transactionSheet = e.source.getSheetByName(TRANSACTION_SHEET_NAME);
    if (!transactionSheet) {
      e.source.toast(`🚨 Error: Transaction sheet not found: ${TRANSACTION_SHEET_NAME}`, true, 5);
      return;
    }

    // DYNAMICALLY FIND THE TARGET COLUMN INDEX in the transaction sheet
    const targetColIndex = getColumnByHeaderName(
      transactionSheet, 
      targetHeader, 
      HEADER_ROW
    );

    if (targetColIndex === -1) {
      e.source.toast(`🚨 Error: Column header '${targetHeader}' not found in sheet '${TRANSACTION_SHEET_NAME}'.`, true, 5);
      return;
    }

    // Determine the range to apply the updates
    const lastRow = transactionSheet.getLastRow();
    const dataStartRow = HEADER_ROW + 1;
    
    if (lastRow < dataStartRow) {
      e.source.toast("No transaction data found to update.", true, 5);
      return;
    }

    const numRows = lastRow - HEADER_ROW;
    const transactionRange = transactionSheet.getRange(dataStartRow, targetColIndex, numRows, 1);
    
    // Get all tag/category values as a 2D array
    const values = transactionRange.getValues();

    // 3. Process the array to update names while preserving comma-separated structure
    const updatedValues = values.map(row => {
        const cellValue = row[0] ? row[0].toString().trim() : '';
        if (!cellValue) {
          return ['']; // Return empty for empty cells
        }
        
        // Split the string into an array of individual items, handling various space/comma separators
        const itemsArray = cellValue.split(/\s*,\s*/).map(item => item.trim());
        
        // Map over the items and replace the old item with the new one if it's an exact match
        const newItemsArray = itemsArray.map(item => {
            // Determine comparison values based on IS_CASE_SENSITIVE setting
            const compareItem = IS_CASE_SENSITIVE ? item : item.toLowerCase();
            const compareOldName = IS_CASE_SENSITIVE ? oldName : oldName.toLowerCase();

            if (compareItem === compareOldName) {
                return newName;
            }
            return item;
        });
        
        // Join the items back into a comma-separated string
        // Filter out any empty strings and enforce a clean list format
        const newCellValue = newItemsArray
            .filter(item => item) 
            .join(', ');
            
        return [newCellValue];
    });

    // Write the updated values back to the sheet
    transactionRange.setValues(updatedValues);

    e.source.toast(`✨ ${sourceSheetName} Renamed: '${oldName}' updated to '${newName}' across all transactions in column '${targetHeader}'.`, true, 10);
  }
}

Thanks for the script, I’ll give it a look.

I would still look at the last community solutions I posted, the one by @rhowell .

It adds functionality over the simpler tags report and I prefer it.