JSPM

capacitor-google-sheets-zeattacker

1.0.2
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 17
  • Score
    100M100P100Q87139F
  • License MIT

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

Package Exports

  • capacitor-google-sheets-zeattacker
  • capacitor-google-sheets-zeattacker/dist/esm/index.js
  • capacitor-google-sheets-zeattacker/dist/plugin.cjs.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (capacitor-google-sheets-zeattacker) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

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.

Changelog

See CHANGELOG.md for release history.