Skip to main content

LangChain integration for Google Sheets as database with conversational memory and RAG retriever

Project description


๐Ÿ”ง 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

  1. Open your Google Sheet
  2. Click Extensions โ†’ Apps Script
  3. Delete any default code
  4. 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

  1. Click Deploy โ†’ New deployment
  2. Click the gear icon โš™๏ธ โ†’ Select Web app
  3. Fill in the form:
    • Description: SheetBase API v1
    • Execute as: Me (your Google account)
    • Who has access: Anyone โš ๏ธ (Important!)
  4. Click Deploy
  5. Click Authorize access
    • Select your Google account
    • Click Advanced โ†’ Go to [Project Name] (unsafe)
    • Click Allow
  6. 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_NAME to 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:

  1. Save changes (Ctrl+S / Cmd+S)
  2. Click Deploy โ†’ Manage deployments
  3. Click โœ๏ธ Edit icon
  4. Change Version to New version
  5. 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langchain_sheetbase-0.1.0.tar.gz (16.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langchain_sheetbase-0.1.0-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file langchain_sheetbase-0.1.0.tar.gz.

File metadata

  • Download URL: langchain_sheetbase-0.1.0.tar.gz
  • Upload date:
  • Size: 16.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.12.3 Linux/6.8.0-86-generic

File hashes

Hashes for langchain_sheetbase-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5754973c58a6dbb1e6cc267f58d2b467182f96403550279bfd1ab953ef974a68
MD5 ad6fa6ac1b9352f733b4c18b8a482204
BLAKE2b-256 cc30da68141dbf9c6593f16bf0e7c30cacc793ffdbbb60157448f4f512eb9649

See more details on using hashes here.

File details

Details for the file langchain_sheetbase-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: langchain_sheetbase-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.12.3 Linux/6.8.0-86-generic

File hashes

Hashes for langchain_sheetbase-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 183826935c26ed8b721e5cd73484cd1c0a7369f04d49872b8f93308d35020314
MD5 c9484954685c8c0d1430564dfb2bc548
BLAKE2b-256 d1fb4b31e34e090642de9d02a798fa989d3b83c144579405b392b644e294b10a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page