npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

capacitor-google-sheets-zeattacker

v1.0.4

Published

Capacitor plugin for Google Sheets REST API (Web, iOS, Android)

Readme

@zeattacker/capacitor-google-sheets

Capacitor plugin for Google Sheets REST API with support for Web (PWA), iOS, and Android platforms.

npm version License: MIT

Features

  • Cross-platform - Works on Web, iOS, and Android
  • Type-safe - Full TypeScript support with detailed type definitions
  • REST API - Uses Google Sheets REST API (no gapi library needed)
  • Offline-compatible - Uses native fetch/URLSession/OkHttp
  • Integrated - Works seamlessly with @zeattacker/capacitor-google-auth
  • Complete - All major Sheets operations supported

Installation

npm install @zeattacker/capacitor-google-sheets
npx cap sync

Prerequisites

This plugin requires @zeattacker/capacitor-google-auth for authentication:

npm install @zeattacker/capacitor-google-auth

Required Scopes

Add the Google Sheets scope when initializing GoogleAuth:

import { GoogleAuth } from '@zeattacker/capacitor-google-auth';

await GoogleAuth.initialize({
  clientId: 'YOUR_CLIENT_ID',
  scopes: [
    'profile',
    'email',
    'https://www.googleapis.com/auth/spreadsheets', // Full access
    // OR
    'https://www.googleapis.com/auth/drive.file'    // App-created files only
  ]
});

Scope options:

  • https://www.googleapis.com/auth/spreadsheets - Read and write access to all spreadsheets
  • https://www.googleapis.com/auth/drive.file - Access only to files created by this app (recommended for privacy)

Quick Start

import { GoogleAuth } from '@zeattacker/capacitor-google-auth';
import { GoogleSheets } from '@zeattacker/capacitor-google-sheets';

// 1. Sign in with Google
await GoogleAuth.signIn();

// 2. Create a new spreadsheet
const spreadsheet = await GoogleSheets.createSpreadsheet({
  title: 'My Finance Data',
  sheets: [
    { title: 'Transactions', frozenRowCount: 1 },
    { title: 'Categories', frozenRowCount: 1 }
  ]
});

console.log('Created spreadsheet:', spreadsheet.spreadsheetUrl);

// 3. Write headers
await GoogleSheets.updateRange({
  spreadsheetId: spreadsheet.spreadsheetId,
  range: 'Transactions!A1:D1',
  values: [['Date', 'Description', 'Amount', 'Category']]
});

// 4. Append data
await GoogleSheets.appendRows({
  spreadsheetId: spreadsheet.spreadsheetId,
  range: 'Transactions!A:D',
  values: [
    ['2025-01-01', 'Coffee', '5.50', 'Food'],
    ['2025-01-02', 'Salary', '5000.00', 'Income']
  ]
});

// 5. Read data back
const data = await GoogleSheets.readRange({
  spreadsheetId: spreadsheet.spreadsheetId,
  range: 'Transactions!A:D'
});

console.log('Read data:', data.values);

API Reference

createSpreadsheet()

Create a new Google Spreadsheet.

const result = await GoogleSheets.createSpreadsheet({
  title: 'My Spreadsheet',
  sheets: [
    {
      title: 'Sheet1',
      frozenRowCount: 1,      // Optional: freeze header row
      frozenColumnCount: 0    // Optional: freeze columns
    }
  ]
});

// Returns: SpreadsheetResult
// {
//   spreadsheetId: string,
//   title: string,
//   spreadsheetUrl: string,
//   sheets: Array<{ sheetId, title, index, ... }>
// }

getSpreadsheet()

Get spreadsheet metadata.

const info = await GoogleSheets.getSpreadsheet({
  spreadsheetId: 'abc123',
  includeGridData: false  // Optional: include cell values
});

readRange()

Read values from a range.

const data = await GoogleSheets.readRange({
  spreadsheetId: 'abc123',
  range: 'Sheet1!A1:D10',
  valueRenderOption: 'FORMATTED_VALUE',    // Optional
  dateTimeRenderOption: 'SERIAL_NUMBER'    // Optional
});

// Returns: ValuesResult
// {
//   range: string,
//   values: (string | number | boolean)[][],
//   rowCount: number,
//   columnCount: number
// }

Range formats:

  • 'Sheet1!A1:D10' - Specific range
  • 'Sheet1!A:D' - Entire columns A through D
  • 'Sheet1' - Entire sheet

updateRange()

Update values in a range.

const result = await GoogleSheets.updateRange({
  spreadsheetId: 'abc123',
  range: 'Sheet1!A1:B2',
  values: [
    ['Header1', 'Header2'],
    ['Value1', 'Value2']
  ],
  valueInputOption: 'USER_ENTERED'  // Optional: 'RAW' or 'USER_ENTERED'
});

// Returns: UpdateResult
// {
//   updatedRange: string,
//   updatedRows: number,
//   updatedColumns: number,
//   updatedCells: number
// }

appendRows()

Append rows to the end of a sheet.

const result = await GoogleSheets.appendRows({
  spreadsheetId: 'abc123',
  range: 'Sheet1!A:D',  // Or just 'Sheet1'
  values: [
    ['Row1Col1', 'Row1Col2', 'Row1Col3', 'Row1Col4'],
    ['Row2Col1', 'Row2Col2', 'Row2Col3', 'Row2Col4']
  ],
  valueInputOption: 'USER_ENTERED',      // Optional
  insertDataOption: 'INSERT_ROWS'        // Optional: 'OVERWRITE' or 'INSERT_ROWS'
});

clearRange()

Clear values in a range.

await GoogleSheets.clearRange({
  spreadsheetId: 'abc123',
  range: 'Sheet1!A2:Z100'  // Clear data rows, keep headers
});

batchUpdate()

Update multiple ranges at once.

const result = await GoogleSheets.batchUpdate({
  spreadsheetId: 'abc123',
  data: [
    {
      range: 'Sheet1!A1:B1',
      values: [['Header1', 'Header2']]
    },
    {
      range: 'Sheet2!A1:C1',
      values: [['Col1', 'Col2', 'Col3']]
    }
  ],
  valueInputOption: 'USER_ENTERED'  // Optional
});

// Returns: BatchUpdateResult
// {
//   totalUpdatedCells: number,
//   totalUpdatedRows: number,
//   totalUpdatedColumns: number,
//   totalUpdatedSheets: number
// }

validateSheets()

Check if required sheets exist in a spreadsheet.

const validation = await GoogleSheets.validateSheets({
  spreadsheetId: 'abc123',
  requiredSheets: ['Transactions', 'Categories', 'Assets']
});

if (!validation.valid) {
  console.log('Missing sheets:', validation.missing);
  // Missing sheets: ['Assets']
}

// Returns: ValidationResult
// {
//   valid: boolean,
//   missing: string[],
//   existing: string[]
// }

Common Patterns

Initialize Headers on New Spreadsheet

const spreadsheet = await GoogleSheets.createSpreadsheet({
  title: 'Finance Tracker',
  sheets: [
    { title: 'Transactions', frozenRowCount: 1 },
    { title: 'Categories', frozenRowCount: 1 }
  ]
});

await GoogleSheets.batchUpdate({
  spreadsheetId: spreadsheet.spreadsheetId,
  data: [
    {
      range: 'Transactions!A1:E1',
      values: [['Date', 'Description', 'Amount', 'Category', 'Notes']]
    },
    {
      range: 'Categories!A1:B1',
      values: [['Name', 'Type']]
    }
  ]
});

Find or Create Spreadsheet

async function getOrCreateSpreadsheet(): Promise<string> {
  // Check localStorage for existing ID
  const storedId = localStorage.getItem('my_spreadsheet_id');

  if (storedId) {
    try {
      // Validate it still exists
      await GoogleSheets.getSpreadsheet({ spreadsheetId: storedId });
      return storedId;
    } catch (error) {
      // Spreadsheet no longer accessible, create new
      localStorage.removeItem('my_spreadsheet_id');
    }
  }

  // Create new spreadsheet
  const result = await GoogleSheets.createSpreadsheet({
    title: 'My App Data'
  });

  localStorage.setItem('my_spreadsheet_id', result.spreadsheetId);
  return result.spreadsheetId;
}

Update Metadata Row

async function updateMetadata(spreadsheetId: string, key: string, value: string) {
  // Read existing metadata
  const data = await GoogleSheets.readRange({
    spreadsheetId,
    range: 'Metadata!A:C'
  });

  // Find row with matching key
  const rowIndex = data.values.findIndex(row => row[0] === key);
  const timestamp = new Date().toISOString();

  if (rowIndex >= 0) {
    // Update existing row
    await GoogleSheets.updateRange({
      spreadsheetId,
      range: `Metadata!A${rowIndex + 1}:C${rowIndex + 1}`,
      values: [[key, value, timestamp]]
    });
  } else {
    // Append new row
    await GoogleSheets.appendRows({
      spreadsheetId,
      range: 'Metadata!A:C',
      values: [[key, value, timestamp]]
    });
  }
}

Sync Local Data to Sheets

async function syncToSheets(spreadsheetId: string, transactions: any[]) {
  // Clear existing data (keep headers)
  await GoogleSheets.clearRange({
    spreadsheetId,
    range: 'Transactions!A2:Z'
  });

  // Convert objects to 2D array
  const values = transactions.map(t => [
    t.date,
    t.description,
    t.amount,
    t.category,
    t.notes
  ]);

  // Append all rows
  await GoogleSheets.appendRows({
    spreadsheetId,
    range: 'Transactions!A:E',
    values
  });

  console.log(`Synced ${transactions.length} transactions`);
}

Platform-Specific Notes

Web (PWA)

  • Uses fetch() API
  • Token retrieved from @zeattacker/capacitor-google-auth or localStorage
  • Works offline if service worker caches API responses

iOS

  • Uses URLSession for HTTP requests
  • Token retrieved from GoogleAuth plugin
  • All network operations run on background thread
  • Responses parsed on main thread

Android

  • Uses OkHttp for HTTP requests
  • Token retrieved from GoogleAuth plugin
  • Network operations run on ExecutorService
  • Automatic JSON parsing with Gson

Error Handling

try {
  const data = await GoogleSheets.readRange({
    spreadsheetId: 'abc123',
    range: 'Sheet1!A:D'
  });
} catch (error) {
  if (error.message.includes('401')) {
    // Token expired - refresh auth
    await GoogleAuth.refresh();
  } else if (error.message.includes('403')) {
    // Insufficient permissions - wrong scope
    console.error('Missing spreadsheets scope');
  } else if (error.message.includes('404')) {
    // Spreadsheet not found
    console.error('Spreadsheet does not exist');
  } else {
    console.error('Unknown error:', error);
  }
}

Common error codes:

  • 401 - Unauthorized (token expired or invalid)
  • 403 - Forbidden (insufficient permissions/wrong scope)
  • 404 - Not found (spreadsheet or sheet doesn't exist)
  • 429 - Rate limit exceeded (too many requests)

Troubleshooting

"Not authenticated" Error

Make sure you've signed in with GoogleAuth first:

await GoogleAuth.signIn();

"Insufficient permissions" Error

Add the Sheets scope to your GoogleAuth initialization:

await GoogleAuth.initialize({
  scopes: ['https://www.googleapis.com/auth/spreadsheets']
});

iOS Build Errors

Run pod install:

cd ios/App
pod install

Android Build Errors

Sync Gradle:

cd android
./gradlew clean build

TypeScript Types

Full TypeScript support with detailed interfaces:

import type {
  GoogleSheetsPlugin,
  SpreadsheetResult,
  ValuesResult,
  UpdateResult,
  BatchUpdateResult,
  ValidationResult,
  CellValue
} from '@zeattacker/capacitor-google-sheets';

Migration from Direct API Calls

If you're currently using the Google Sheets REST API directly:

Before:

const token = localStorage.getItem('google_access_token');
const response = await fetch(
  `https://sheets.googleapis.com/v4/spreadsheets/${id}/values/${range}`,
  {
    headers: { Authorization: `Bearer ${token}` }
  }
);
const data = await response.json();

After:

const data = await GoogleSheets.readRange({
  spreadsheetId: id,
  range: range
});

License

MIT

Author

ZeAttacker

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

Related Plugins

Changelog

See CHANGELOG.md for release history.