Write API
The Write API allows you to create, update, and delete rows in your Google Sheets programmatically. This enables you to use SheetsJSON as a complete backend for your applications.
Note: Write API is available on Pro plans and above. Upgrade your plan to enable write operations.
Overview
| Operation | Method | Endpoint | Description |
|---|---|---|---|
| Create |
POST |
/api/sheets/:account/:sheet |
Append one or more rows |
| Update |
PUT |
/api/sheets/:account/:sheet/:row |
Update a specific row |
| Delete |
DELETE |
/api/sheets/:account/:sheet/:row |
Delete a specific row |
All write operations require authentication via API key.
Create Rows (POST)
Append one or more rows to your sheet.
POST /api/sheets/:account_slug/:sheet_slug
Request Body
You can send either a single object or an array of objects:
Single row:
{
"name": "Alice Johnson",
"email": "[email protected]",
"role": "Developer"
}
Multiple rows:
[
{"name": "Alice Johnson", "email": "[email protected]", "role": "Developer"},
{"name": "Bob Smith", "email": "[email protected]", "role": "Designer"}
]
Code Examples
Response
Success (201 Created):
{
"success": true,
"message": "2 row(s) added",
"rows_added": 2
}
Notes
- Keys in your JSON must match the column headers in your sheet (case-insensitive)
- New rows are always appended to the end of the sheet
- Missing fields will result in empty cells
- Extra fields not matching any column header will be ignored
Update Row (PUT)
Update an existing row by its row number.
PUT /api/sheets/:account_slug/:sheet_slug/:row_number
The :row_number is the 1-based row index in your sheet (row 1 is typically headers, so data starts at row 2).
Request Body
Send only the fields you want to update:
{
"price": "34.99",
"in_stock": "true"
}
Code Examples
Response
Success (200 OK):
{
"success": true,
"message": "Row 5 updated",
"row": 5
}
Notes
- Row numbers are 1-based (header row is row 1)
- You cannot update row 1 (the header row)
- Only specified fields are updated; other fields remain unchanged
- Use the GET endpoint first to find the row number you want to update
Delete Row (DELETE)
Delete a row by its row number.
DELETE /api/sheets/:account_slug/:sheet_slug/:row_number
Code Examples
Response
Success (200 OK):
{
"success": true,
"message": "Row 5 deleted",
"row": 5
}
Notes
- Row numbers are 1-based (header row is row 1)
- You cannot delete row 1 (the header row)
- After deletion, subsequent rows shift up (row 6 becomes row 5, etc.)
- This operation cannot be undone via the API
Error Responses
Write operations can return these specific errors:
| Status | Code | Description |
|---|---|---|
400 |
invalid_body |
Request body is not valid JSON or wrong format |
400 |
invalid_row_number |
Row number is not a valid positive integer > 1 |
400 |
missing_headers |
Could not read sheet headers |
401 |
api_key_required |
API key not provided |
403 |
invalid_api_key |
API key is invalid |
403 |
write_api_not_enabled |
Plan doesn’t include Write API |
404 |
account_not_found |
Account slug not found |
404 |
sheet_not_found |
Sheet slug not found |
429 |
rate_limited |
Too many requests |
502 |
google_api_error |
Error communicating with Google Sheets |
Example Error Response
{
"error": "Write API is not enabled for your plan. Please upgrade to Pro or higher.",
"code": "write_api_not_enabled"
}
Best Practices
1. Validate Data Client-Side
Always validate your data before sending it to the API to avoid unnecessary requests:
function validateProduct(product) {
if (!product.name || product.name.trim() === '') {
throw new Error('Product name is required');
}
if (product.price && isNaN(parseFloat(product.price))) {
throw new Error('Price must be a number');
}
return true;
}
2. Use Transactions for Related Updates
When updating multiple related rows, handle failures gracefully:
async function updateProducts(updates) {
const results = [];
for (const { row, data } of updates) {
try {
const response = await updateRow(row, data);
results.push({ row, success: true, response });
} catch (error) {
results.push({ row, success: false, error: error.message });
}
}
return results;
}
3. Implement Retry Logic
Handle transient errors with exponential backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
4. Cache Read Data
When you need to update specific rows, cache the row numbers from your initial read:
// Build a lookup table from your data
const rows = await fetchProducts();
const rowLookup = new Map(
rows.data.map((item, index) => [item.id, index + 2]) // +2 for header row
);
// Later, update by ID
const rowNumber = rowLookup.get('product-123');
await updateRow(rowNumber, { price: '49.99' });
Rate Limits
Write operations count against your API rate limits. See Rate Limits for details on limits by plan.
| Plan | Requests/minute | Requests/day |
|---|---|---|
| Pro | 300 | 50,000 |
| Business | 1,000 | 500,000 |
| Enterprise | Custom | Custom |
What’s Next?
- Review Error Codes for a complete error reference
- Learn about Query Parameters for reading data
- Explore Rate Limits to optimize your usage