Print Expense Tracker

Record New Job

Record Advance/Payment

Account Status

Using: 📱 Local Device (Offline)

Net Balance (Due/Advance)

₹0.00

Total Cost

₹0.00

Total Paid

₹0.00

Total Pages

0

Total Jobs

0

History Log

Page 1

The definitive Print Expense Manager for cyber cafés, xerox shops, and students. Track costs, manage credits, and sync across devices instantly.

Why Use a Digital Print Tracker?

In the busy environment of a photocopy shop or a university project center, tracking every single page is difficult. Notebooks get lost, and mental math leads to financial leakage.

📉
Eliminate Math Errors Auto-calculation of (Pages × Cost) ensures you never undercharge a client.
☁️
Data Independence Your data lives on your device or your Google Drive. No reliance on expensive third-party servers.

Power User Features

Designed for the Indian market context (₹), but works globally.

Hybrid Engine
Uses IndexedDB for instant offline access and Google APIs for cloud backup. Best of both worlds.
📱
Responsive UI
An iOS 17-inspired interface that feels native on Mobile, Tablet, and Desktop browsers.
🔍
Smart Audit
Filter history by Date Range to generate daily, weekly, or monthly expense reports instantly.
🔐
Security
Includes strict URL validation and input sanitization to prevent XSS attacks and data corruption.

🚀 Cloud Sync Setup Guide

To enable syncing between your Phone and Laptop, you need to create your own "Backend" using a free Google Sheet.

Step 1: The Google Sheet

Step 2: The Script

Go to Extensions > Apps Script. Delete existing code and paste this.
IMPORTANT: Paste your Spreadsheet ID in the first line.

// ==========================================
// PRINT TRACKER BACKEND (FINAL)
// ==========================================

// 👇 PASTE YOUR SPREADSHEET ID HERE 👇
const SHEET_ID = "PASTE_YOUR_SHEET_ID_HERE"; 

function doGet(e) {
  const ss = SpreadsheetApp.openById(SHEET_ID);
  const jobsSheet = ss.getSheetByName("Jobs");
  const paySheet = ss.getSheetByName("Payments");
  
  const jobsData = getSheetData(jobsSheet);
  const payData = getSheetData(paySheet);
  
  return ContentService.createTextOutput(JSON.stringify({ 
    jobs: jobsData, 
    payments: payData 
  })).setMimeType(ContentService.MimeType.JSON);
}

function doPost(e) {
  const ss = SpreadsheetApp.openById(SHEET_ID);
  
  try {
    const data = JSON.parse(e.postData.contents);
    const action = data.action;
    
    const lock = LockService.getScriptLock();
    lock.tryLock(10000); 
    
    try {
      // CONVERT TIMESTAMP TO DATE OBJECT
      const rowDate = new Date(data.date);

      if (action === "addJob") {
        const sheet = ss.getSheetByName("Jobs");
        const id = data.id; 
        sheet.appendRow([id, data.documentName, data.pages, data.costPerPage, data.totalCost, rowDate]);
      } 
      else if (action === "addPayment") {
        const sheet = ss.getSheetByName("Payments");
        const id = data.id;
        sheet.appendRow([id, data.amount, rowDate]);
      }
      else if (action === "editJob") {
         updateRow(ss.getSheetByName("Jobs"), data.id, [data.id, data.documentName, data.pages, data.costPerPage, data.totalCost, rowDate]);
      }
      else if (action === "delete") {
        const sheetName = data.type === 'job' ? "Jobs" : "Payments";
        deleteRow(ss.getSheetByName(sheetName), data.id);
      }
      
      return ContentService.createTextOutput(JSON.stringify({status: "success"}));
      
    } catch (innerError) {
      return ContentService.createTextOutput(JSON.stringify({status: "error", message: innerError.toString()}));
    } finally {
      lock.releaseLock();
    }
    
  } catch (e) {
    return ContentService.createTextOutput(JSON.stringify({status: "fatal_error", message: e.toString()}));
  }
}

// --- HELPER FUNCTIONS ---

function getSheetData(sheet) {
  if (!sheet) return [];
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return []; 
  
  const rows = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn()).getValues();
  const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
  
  return rows.map(row => {
    let obj = {};
    headers.forEach((h, i) => {
      if (row[i] instanceof Date) {
        obj[h] = row[i].getTime();
      } else {
        obj[h] = row[i];
      }
    });
    return obj;
  });
}

function deleteRow(sheet, id) {
  const data = sheet.getDataRange().getValues();
  for (let i = data.length - 1; i >= 1; i--) {
    if (String(data[i][0]) === String(id)) {
      sheet.deleteRow(i + 1);
      return;
    }
  }
}

function updateRow(sheet, id, newValues) {
  const data = sheet.getDataRange().getValues();
  for (let i = 1; i < data.length; i++) {
    if (String(data[i][0]) === String(id)) {
      sheet.getRange(i + 1, 1, 1, newValues.length).setValues([newValues]);
      return;
    }
  }
}

Step 3: Deploy & Connect

Frequently Asked Questions

Q: Is this tool really free?

Yes. It uses your browser's storage and your Google Drive. There is no middleman and no subscription.

Q: What happens if I clear my browser history?

If you are using Local Mode, your data will be deleted. We strongly recommend setting up the Cloud Sync so your data is permanently safe in Google Sheets.

Q: Can I use it for other expenses?

Yes! While it says "Pages", you can use the quantity field for anything (e.g., hours of service, 3D printing grams) and set the cost per unit accordingly.

Q: My Cloud Data isn't loading?

Ensure your Google Deployment is set to "Anyone" and that you are not using extensions like "Video Downloaders" that block background requests.

Take Control of Your Printing Business

Start recording your jobs now and take control of your expenses! Efficiently track dues, advances, and total earnings in one streamlined dashboard.

PRINT EXPENSE TRACKER
© 2026 All Rights Reserved Designed & Developed by Soumya