🔧 Google Apps Script - Complete Setup
Why Apps Script Mode?
Apps Script Mode unlocks full CRUD operations (Create, Update, Delete) while still bypassing the Google Cloud API:
- ✅ No Google Cloud project needed
- ✅ No API quotas or billing
- ✅ Deploy once, use forever
- ✅ 2-minute setup (copy-paste script)
Step-by-Step Setup
1. Open Apps Script Editor
- Open your Google Sheet
- Click Extensions → Apps Script
- Delete any default code
- Copy the script below
2. Copy This Complete Script
/**
SheetBase Apps Script - Full CRUD API
Provides REST API for Google Sheets with:
GET: Search and retrieve records
POST: Create, update, delete records
Author: Charles Lepoittevin (SheetBase)
Version: 2.0.0
License: MIT */
// ═══════════════════════════════════════════════════════════════════ // Configuration // ═══════════════════════════════════════════════════════════════════
const CONFIG = { SHEET_NAME: 'Sheet1', // Change to your sheet tab name MAX_RESULTS: 1000, // Maximum records to return FUZZY_THRESHOLD: 0.2, // Search sensitivity (0.0-1.0) ENABLE_LOGGING: true // Enable detailed logging };
// ═══════════════════════════════════════════════════════════════════ // GET Request Handler - Search & Retrieve // ═══════════════════════════════════════════════════════════════════
/**
Handle GET requests (search and retrieval)
Query parameters:
q: Search keywords (space-separated, OR supported)
user_id: Filter by user ID (for memory operations)
limit: Maximum results (default: 100) */ function doGet(e) { try { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
if (!sheet) { return jsonResponse({ success: false, error: Sheet "${CONFIG.SHEET_NAME}" not found }, 404); }
const data = sheet.getDataRange().getValues();
if (data.length === 0) { return jsonResponse({ success: true, data: [], count: 0 }); }
const headers = data; let records = data.slice(1).map((row, idx) => { const record = { __row: idx + 2 }; headers.forEach((header, i) => { record[header] = row[i]; }); return record; });
const params = e.parameter || {}; const query = params.q || ''; const userId = params.user_id || ''; const limit = parseInt(params.limit || '100');
if (query) { const keywords = query.toLowerCase().split(' ').filter(k => k.length > 0); records = records.filter(record => { const text = JSON.stringify(record).toLowerCase(); return keywords.some(kw => text.includes(kw)); }); }
if (userId && records.length > 0) { records = records.filter(record => { return record['user_id'] === userId || record['UserID'] === userId; }); }
if (limit > 0 && limit < records.length) { records = records.slice(0, limit); }
logInfo(GET request: Found ${records.length} records (query="${query}", user_id="${userId}"));
return jsonResponse({ success: true, data: records, count: records.length });
} catch (error) { logError('GET request failed', error); return jsonResponse({ success: false, error: error.toString() }, 500); } }
// ═══════════════════════════════════════════════════════════════════ // POST Request Handler - Create, Update, Delete // ═══════════════════════════════════════════════════════════════════
/**
Handle POST requests (CRUD operations)
Request body examples:
Create: {"operation": "create", "values": {"Product": "USB Cable", "Price": "9.99"}}
Update: {"operation": "update", "rowNumber": 5, "updates": {"Price": "29.99"}}
Delete: {"operation": "delete", "rowNumber": 5} */ function doPost(e) { try { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
if (!sheet) { return jsonResponse({ success: false, error: Sheet "${CONFIG.SHEET_NAME}" not found }, 404); }
const body = JSON.parse(e.postData.contents); const operation = body.operation;
if (!operation) { return jsonResponse({ success: false, error: 'Missing "operation" field in request body' }, 400); }
switch (operation.toLowerCase()) { case 'create': return handleCreate(sheet, body);
case 'update': return handleUpdate(sheet, body);
case 'delete': return handleDelete(sheet, body);
default: return jsonResponse({ success: false, error: Unknown operation: ${operation}. Use "create", "update", or "delete" }, 400); }
} catch (error) { logError('POST request failed', error); return jsonResponse({ success: false, error: error.toString() }, 500); } }
// ═══════════════════════════════════════════════════════════════════ // CRUD Operation Handlers // ═══════════════════════════════════════════════════════════════════
function handleCreate(sheet, body) { if (!body.values || typeof body.values !== 'object') { return jsonResponse({ success: false, error: 'Missing or invalid "values" field' }, 400); }
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues(); const newRow = headers.map(header => body.values[header] || '');
sheet.appendRow(newRow); const rowNumber = sheet.getLastRow();
logInfo(CREATE: Added row ${rowNumber} with data: ${JSON.stringify(body.values)});
return jsonResponse({ success: true, message: 'Record created successfully', rowNumber: rowNumber, data: body.values }); }
function handleUpdate(sheet, body) { if (!body.rowNumber || typeof body.rowNumber !== 'number') { return jsonResponse({ success: false, error: 'Missing or invalid "rowNumber" field' }, 400); }
if (!body.updates || typeof body.updates !== 'object') { return jsonResponse({ success: false, error: 'Missing or invalid "updates" field' }, 400); }
const rowNumber = body.rowNumber; const lastRow = sheet.getLastRow();
if (rowNumber < 2 || rowNumber > lastRow) { return jsonResponse({ success: false, error: Invalid row number: ${rowNumber}. Must be between 2 and ${lastRow} }, 400); }
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
Object.keys(body.updates).forEach(columnName => { const colIndex = headers.indexOf(columnName); if (colIndex !== -1) { sheet.getRange(rowNumber, colIndex + 1).setValue(body.updates[columnName]); } });
logInfo(UPDATE: Modified row ${rowNumber} with updates: ${JSON.stringify(body.updates)});
return jsonResponse({ success: true, message: Row ${rowNumber} updated successfully, rowNumber: rowNumber, updates: body.updates }); }
function handleDelete(sheet, body) { if (!body.rowNumber || typeof body.rowNumber !== 'number') { return jsonResponse({ success: false, error: 'Missing or invalid "rowNumber" field' }, 400); }
const rowNumber = body.rowNumber; const lastRow = sheet.getLastRow();
if (rowNumber < 2 || rowNumber > lastRow) { return jsonResponse({ success: false, error: Invalid row number: ${rowNumber}. Must be between 2 and ${lastRow} }, 400); }
sheet.deleteRow(rowNumber);
logInfo(DELETE: Removed row ${rowNumber});
return jsonResponse({ success: true, message: Row ${rowNumber} deleted successfully, rowNumber: rowNumber }); }
// ═══════════════════════════════════════════════════════════════════ // Utility Functions // ═══════════════════════════════════════════════════════════════════
function jsonResponse(data, statusCode = 200) { return ContentService .createTextOutput(JSON.stringify(data)) .setMimeType(ContentService.MimeType.JSON); }
function logInfo(message) { if (CONFIG.ENABLE_LOGGING) { console.log([SheetBase] ${message}); } }
function logError(message, error) { console.error([SheetBase ERROR] ${message}:, error); }
3. Deploy as Web App
- Click Deploy → New deployment
- Click the gear icon ⚙️ → Select Web app
- Fill in the form:
- Description:
SheetBase API v1 - Execute as: Me (your Google account)
- Who has access: Anyone ⚠️ (Important!)
- Description:
- Click Deploy
- Click Authorize access
- Select your Google account
- Click Advanced → Go to [Project Name] (unsafe)
- Click Allow
- Copy the Web App URL (looks like): https://script.google.com/macros/s/AKfycbxxxxxxxxxxxxx/exec
4. Test Your Deployment
Test GET (search): curl "https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec?q=laptop&limit=5"
Expected response: { "success": true, "data": [ {"__row": 2, "Product": "Gaming Laptop", "Price": "999"}, {"__row": 3, "Product": "Laptop Stand", "Price": "29"} ], "count": 2 }
Test POST (create): curl -X POST "https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec" -H "Content-Type: application/json" -d '{"operation": "create", "values": {"Product": "USB Cable", "Price": "9.99"}}'
5. Use in Python
from langchain_sheetbase import create_sheetbase_tools
tools = create_sheetbase_tools( mode='appsscript', apps_script_url='https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec' )
Now you have full CRUD operations!
Configuration Options
Edit the CONFIG object at the top of the script:
const CONFIG = { SHEET_NAME: 'Sheet1', // Change to your sheet tab name MAX_RESULTS: 1000, // Limit search results FUZZY_THRESHOLD: 0.2, // Search sensitivity (0.0-1.0) ENABLE_LOGGING: true // Set to false in production };
Troubleshooting
Error: "Script function not found: doGet"
- Solution: Make sure you deployed as Web app, not API executable
Error: "Authorization required"
- Solution: Redeploy with "Who has access" set to Anyone
Error: "Sheet not found"
- Solution: Update
CONFIG.SHEET_NAMEto match your sheet tab name
No data returned
- Solution: Ensure your sheet has a header row (row 1) with column names
Updating the Script
When you modify the script:
- Save changes (Ctrl+S / Cmd+S)
- Click Deploy → Manage deployments
- Click ✏️ Edit icon
- Change Version to New version
- Click Deploy
⚠️ The URL stays the same - no need to update your Python code!
Memory Schema (Optional)
If using SheetBase for conversational memory, add these columns to your sheet:
| timestamp | user_id | user_request | agent_response |
|---|---|---|---|
| 2025-10-27T20:00:00Z | user_123 | What laptops? | We have 5 gaming... |
The SheetBaseMemory class will automatically use this schema.
Metadata
Release files for langchain-sheetbase 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| langchain_sheetbase-0.1.0.tar.gz | 16.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| langchain_sheetbase-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.2 kB
Release files / langchain_sheetbase-0.1.0.tar.gz
| Download URL | langchain_sheetbase-0.1.0.tar.gz |
|---|---|
| Size | 16.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5754973c58a6dbb1e6cc267f58d2b467182f96403550279bfd1ab953ef974a68
|
|
BLAKE2b-256 checksum How to use checksums |
cc30da68141dbf9c6593f16bf0e7c30cacc793ffdbbb60157448f4f512eb9649
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.2.1 CPython/3.12.3 Linux/6.8.0-86-generic
|
Release files / langchain_sheetbase-0.1.0-py3-none-any.whl
| Download URL | langchain_sheetbase-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
183826935c26ed8b721e5cd73484cd1c0a7369f04d49872b8f93308d35020314
|
|
BLAKE2b-256 checksum How to use checksums |
d1fb4b31e34e090642de9d02a798fa989d3b83c144579405b392b644e294b10a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.2.1 CPython/3.12.3 Linux/6.8.0-86-generic
|