Auto Insert generated paycheck deduction transactions (Apps Script)

Overview

This is a helper function for jpfieber’s Paycheck Deduction Transaction Generator.
It will automatically (with a click) insert the generated transaction rows into the Transactions sheet.

Installation

It is a Google Apps Script function. Copy and paste the code into any *.gs file within ‘Apps Script’ from the Extension menu.

Setup

You need the Paycheck Deduction Transaction Generator installed, and make note of whatever you named that sheet/tab. I have a his and hers version installed.
There is a check right at the beginning of the code to make sure we are on one of the Paycheck sheets/tabs, so you should replace with whatever name(s) you use.

There are a couple of other things to check that I commented, like where to do the inserting on the Transactions sheet, because yours may be set up differently.
Default is to insert between rows 2 and 3, with data starting in column B.

Usage

Call the function paycheckDeductionTransactionAutoInsert from a custom UI menu, or a button click:

You can create a button in Google Sheets to trigger a script:

  1. Insert a drawing or an image in the sheet (Insert > Drawing or Insert > Image).
  2. Right-click the object and select Assign script.
  3. Enter the name of your function (e.g., paycheckDeductionTransactionAutoInsert).

Permissions

Is it ok for others to copy, use, and modify your workflow?
Go nuts!

The Code

Code is here on GitHub.

Also a static version:

// Add or remove sheet names here to control which sheets this script is allowed to run on.
const ALLOWED_SHEET_NAMES = [
  "Paycheck-Adam",
  "Paycheck-Eve",
  "Paycheck Deductions Generator",
];

function paycheckDeductionTransactionAutoInsert() {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const currentSheet = ss.getActiveSheet();
    const sheetName = currentSheet.getName();

    // Check if the sheet name is one of my "Paycheck Deduction Transaction Generator" sheets.
    if (!ALLOWED_SHEET_NAMES.includes(sheetName)) {
      SpreadsheetApp.getUi().alert(
        `Error: This script can only be run on Paychecks sheets.`
      );
      return; // Exit the script without doing anything
    }

    const transactionsSheet = ss.getSheetByName("Transactions");
    if (!transactionsSheet) {
      SpreadsheetApp.getUi().alert("Error: Sheet 'Transactions' does not exist.");
      return;
    }

    // Dynamically concatenate values from AQ10 and AQ12 to form the range address
    // These are the two ranges in the "Select $AT$4 Through $BO$16" section.
    const rangeStart = currentSheet.getRange("AQ10").getValue();
    const rangeEnd = currentSheet.getRange("AQ12").getValue();

    // Make a range with full colon
    const rangeAddress = `${rangeStart}:${rangeEnd}`;

    if (!rangeStart || !rangeEnd) {
      SpreadsheetApp.getUi().alert("Error: Cells AQ10 and AQ12 must not be empty.");
      return;
    }

    // Finding the "Transaction ID" column index, so we can insert unique IDs if that column exists.
    // Strip any $ signs (e.g. "$AT$4" -> "AT4") before parsing.
    // Then build a header range that is 1 row above rangeStart, spanning through rangeEnd's column.
    const startMatch = rangeStart.replace(/\$/g, "").match(/^([A-Za-z]+)(\d+)$/);
    const endMatch = rangeEnd.replace(/\$/g, "").match(/^([A-Za-z]+)(\d+)$/);
    if (!startMatch || !endMatch) {
      SpreadsheetApp.getUi().alert("Error: AQ10 and AQ12 must contain valid cell addresses (e.g. AT4 or $AT$4).");
      return;
    }
    const startCol = startMatch[1];
    const headerRow = parseInt(startMatch[2], 10) - 1;
    const endCol = endMatch[1];
    const headerRangeAddress = `${startCol}${headerRow}:${endCol}${headerRow}`;
    const headers = currentSheet.getRange(headerRangeAddress).getValues()[0];
    const txnIdColIndex = headers.findIndex(h => h === "Transaction ID");

    // Get the number of rows to insert from AQ5
    // This is the number in the "Insert nn Rows in Transactions Sheet" section.
    const rowsToInsert = currentSheet.getRange("AQ5").getValue();
    if (!Number.isInteger(rowsToInsert) || rowsToInsert < 0) {
      SpreadsheetApp.getUi().alert("Error: Cell AQ5 must contain a non-negative integer.");
      return;
    }

    try {
      // Insert rows between row 2 and 3 in the Transactions sheet
      // I have had issues with conditional formatting or arrayformulas if I try to insert at the very top.
      // Feel free to do this differently if you like to live dangerously.
      if (rowsToInsert > 0) {
        transactionsSheet.insertRowsAfter(2, rowsToInsert);
      }

      // Get the data from the specified concatenated range
      const range = currentSheet.getRange(rangeAddress);
      const data = range.getValues();

      // If a "Transaction ID" column was found in the headers, populate each row with a unique UUID.
      if (txnIdColIndex !== -1) {
        for (let i = 0; i < data.length; i++) {
          if (!data[i][txnIdColIndex]) {
            data[i][txnIdColIndex] = Utilities.getUuid();
          }
        }
      }

      // Paste the data into the Transactions sheet starting at B3
      // Your Transactions sheet may be different, but I use the first column for indicators, hence column B
      transactionsSheet
        .getRange(3, 2, data.length, data[0].length) // Row 3 and Column 2 (B3)
        .setValues(data);

    } catch (e) {
      SpreadsheetApp.getUi().alert(`Error: ${e.message}`);
    }
  }

Enjoy!

Update: 2026-MAR-05 - I added logic to add unique string (UUID) to the Transaction ID column for each row as it inserts.

This looks really cool! I thought you’d have an issue with varying numbers of deductions and columns on the Transactions sheet, but I see you addressed that. I’ll try it when my next paycheck arrives!

Cool! Yeah, you already did the work of creating dynamic number of rows to insert, and which ranges to select.

That was exactly why I thought of this, since you already did the work; I just am lazy and didn’t want to click and copy+paste.

1 Like

As luck would have it, I got paid today! Just tried your script and it worked perfectly. Much nicer than click/drag/copy/switchsheet/click/drag/insert/paste. Thanks!

1 Like

This is awesome! Thanks! Maybe edit the code for a default setup with the default name of the Paycheck Deduction Transacation Generator?

Good catch! I reinstalled in another sheet so I was sure to get the correct name.
Added “Paycheck Deductions Generator” to the logic check.

2 Likes

I’m new to Tiller, and just finding my way around.
I had installed the Paycheck Deduction Transaction Generator, and came across your helper function to automatically (with a click) insert the generated transaction rows into the Transactions sheet. This sounds like it would be great, very useful indeed. However, I get lost while trying to follow the install instructions.

  1. I created a .gs file on the Apps Scrip window – I called it “AutoInsertPDTG.gs”
  2. I am unable to create a button for a custom UI as described in the instructions … I inserted a (random) visual icon, but then right-click does not offer the option to AssignSript.

Is this something you are able to help with? Thank you.

The Tiller Helper solved this: "The missing piece is that “Assign script” only shows up for drawings/images in Google Sheets, not everything you insert.

Try this flow:

  • In your sheet, go to Insert → Drawing, create a simple shape or button, and save it onto the sheet.

  • Click the drawing (not right-click elsewhere), then use the three-dot menu or right-click on the drawing itself to find Assign script.

  • Enter the exact function name from your .gs file."

1 Like

Hey @northrop, I see you have already done another solution, but I do have a quick guide on how to do the UI menu, which is a different way than inserting a drawing. (Drawings work great though, and I have some myself for some other actions.)

2 Likes