Claude Skills Guide

Building a Chrome extension for tracking spending combines web development skills with practical finance management. This guide walks through creating a spending tracker that runs directly in your browser, storing data locally without relying on external servers.

Why Build Your Own Spending Tracker?

Most budget apps require accounts, cloud storage, and constant internet connectivity. A custom Chrome extension gives you complete control over your data. You decide where it lives, how it’s analyzed, and who has access. For developers who value privacy and customization, this approach beats commercial alternatives.

The Chrome extension platform provides several advantages: background scripts for automation, the Storage API for persistent data, and seamless integration with browser UI through popup windows and options pages.

Project Structure

A Chrome extension needs a manifest file and at least one JavaScript file. Here’s a minimal structure for your spending tracker:

spending-tracker/
├── manifest.json
├── popup.html
├── popup.js
├── background.js
└── icons/
    ├── icon16.png
    ├── icon48.png
    └── icon128.png

The manifest defines your extension’s capabilities. Version 3 (MV3) is the current standard with improved security and performance.

Manifest Configuration

Your manifest.json declares permissions and entry points:

{
  "manifest_version": 3,
  "name": "Spending Tracker",
  "version": "1.0",
  "description": "Track your spending directly in Chrome",
  "permissions": ["storage"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "48": "icons/icon48.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  }
}

The storage permission enables the chrome.storage API, which syncs data across browser sessions. For purely local storage without sync, use local storage instead.

Core Data Structure

Design your data model before writing logic. A simple transaction schema works well:

const transactionSchema = {
  id: string,          // UUID
  amount: number,      // Positive for income, negative for expenses
  category: string,   // e.g., "food", "transport", "utilities"
  description: string,
  date: string,        // ISO format: "2026-03-15"
  timestamp: number    // Unix timestamp for sorting
};

Store transactions as an array in chrome.storage:

// Save a new transaction
async function addTransaction(transaction) {
  const stored = await chrome.storage.local.get(['transactions']);
  const transactions = stored.transactions || [];
  transactions.push({
    ...transaction,
    id: crypto.randomUUID(),
    timestamp: Date.now()
  });
  await chrome.storage.local.set({ transactions });
  return transactions;
}

Building the Popup Interface

The popup provides quick entry and viewing capabilities. Create popup.html:

<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 320px; font-family: system-ui, sans-serif; padding: 16px; }
    .input-group { margin-bottom: 12px; }
    label { display: block; font-size: 12px; color: #666; margin-bottom: 4px; }
    input, select { width: 100%; padding: 8px; box-sizing: border-box; }
    button { width: 100%; padding: 10px; background: #4285f4; color: white; border: none; cursor: pointer; }
    button:hover { background: #3367d6; }
    .recent { margin-top: 16px; border-top: 1px solid #eee; padding-top: 12px; }
    .transaction { font-size: 13px; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
    .amount-positive { color: #34a853; }
    .amount-negative { color: #ea4335; }
  </style>
</head>
<body>
  <h3>Add Transaction</h3>
  <div class="input-group">
    <label>Amount</label>
    <input type="number" id="amount" step="0.01" placeholder="0.00">
  </div>
  <div class="input-group">
    <label>Category</label>
    <select id="category">
      <option value="food">Food</option>
      <option value="transport">Transport</option>
      <option value="utilities">Utilities</option>
      <option value="entertainment">Entertainment</option>
      <option value="other">Other</option>
    </select>
  </div>
  <div class="input-group">
    <label>Description</label>
    <input type="text" id="description" placeholder="What was this for?">
  </div>
  <button id="saveBtn">Save Transaction</button>
  
  <div class="recent">
    <h4>Recent Transactions</h4>
    <div id="recentList"></div>
  </div>
  
  <script src="popup.js"></script>
</body>
</html>

Connect the interface to your storage in popup.js:

document.addEventListener('DOMContentLoaded', async () => {
  const saveBtn = document.getElementById('saveBtn');
  
  saveBtn.addEventListener('click', async () => {
    const amount = parseFloat(document.getElementById('amount').value);
    const category = document.getElementById('category').value;
    const description = document.getElementById('description').value;
    
    if (!amount || !description) {
      alert('Please enter amount and description');
      return;
    }
    
    const transaction = {
      amount: -Math.abs(amount), // Negative for expenses
      category,
      description,
      date: new Date().toISOString().split('T')[0]
    };
    
    await addTransaction(transaction);
    
    // Clear form
    document.getElementById('amount').value = '';
    document.getElementById('description').value = '';
    
    // Refresh list
    loadRecentTransactions();
  });
  
  async function loadRecentTransactions() {
    const stored = await chrome.storage.local.get(['transactions']);
    const transactions = (stored.transactions || []).reverse().slice(0, 5);
    
    const list = document.getElementById('recentList');
    list.innerHTML = transactions.map(t => `
      <div class="transaction">
        <span>${t.description}</span>
        <span class="${t.amount >= 0 ? 'amount-positive' : 'amount-negative'}">
          $${Math.abs(t.amount).toFixed(2)}
        </span>
      </div>
    `).join('');
  }
  
  loadRecentTransactions();
});

async function addTransaction(transaction) {
  const stored = await chrome.storage.local.get(['transactions']);
  const transactions = stored.transactions || [];
  transactions.push({
    ...transaction,
    id: crypto.randomUUID(),
    timestamp: Date.now()
  });
  await chrome.storage.local.set({ transactions });
}

Adding Analytics with Background Scripts

Background scripts run continuously, enabling automated insights. In background.js:

chrome.storage.onChanged.addListener((changes, area) => {
  if (area === 'local' && changes.transactions) {
    const transactions = changes.transactions.newValue;
    const summary = calculateSummary(transactions);
    
    // Update badge with daily total
    const today = new Date().toISOString().split('T')[0];
    const todayTotal = transactions
      .filter(t => t.date === today)
      .reduce((sum, t) => sum + t.amount, 0);
    
    chrome.action.setBadgeText({
      text: Math.abs(todayTotal).toFixed(0)
    });
    
    chrome.action.setBadgeBackgroundColor({ color: '#ea4335' });
  }
});

function calculateSummary(transactions) {
  const byCategory = {};
  transactions.forEach(t => {
    byCategory[t.category] = (byCategory[t.category] || 0) + t.amount;
  });
  return byCategory;
}

Loading Your Extension

To test your extension in Chrome:

  1. Navigate to chrome://extensions/
  2. Enable “Developer mode” in the top right
  3. Click “Load unpacked”
  4. Select your extension folder

The extension icon appears in your toolbar. Click it to add transactions and see your spending at a glance.

Next Steps for Enhancement

Several features can elevate this foundation. Add an options page for custom categories and budget limits. Implement data export to CSV for spreadsheet analysis. Create charts using a library like Chart.js for visual spending breakdowns. Add keyboard shortcuts for rapid entry without opening the popup.

For developers seeking deeper integration, the Declarative Content API can trigger actions based on browsing behavior, though this requires careful privacy consideration.

This extension keeps your financial data in your browser, under your control. No subscriptions, no data harvesting, just functional expense tracking built exactly to your specifications.

Built by theluckystrike — More at zovo.one