capacitor-google-sheets-zeattacker
v1.0.4
Published
Capacitor plugin for Google Sheets REST API (Web, iOS, Android)
Maintainers
Readme
@zeattacker/capacitor-google-sheets
Capacitor plugin for Google Sheets REST API with support for Web (PWA), iOS, and Android platforms.
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 syncPrerequisites
This plugin requires @zeattacker/capacitor-google-auth for authentication:
npm install @zeattacker/capacitor-google-authRequired 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 spreadsheetshttps://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-authorlocalStorage - Works offline if service worker caches API responses
iOS
- Uses
URLSessionfor HTTP requests - Token retrieved from GoogleAuth plugin
- All network operations run on background thread
- Responses parsed on main thread
Android
- Uses
OkHttpfor 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 installAndroid Build Errors
Sync Gradle:
cd android
./gradlew clean buildTypeScript 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
- @zeattacker/capacitor-google-auth - Google authentication (required)
Changelog
See CHANGELOG.md for release history.
