Google Apps Script × OpenAI helper library

A clean, sheet agnostic toolkit for calling chat/completions from Google Sheets.


Table of contents

  1. What you get
  2. Quick start
  3. Installation
  4. Configuration
  5. API helper callOpenAI
  6. Single cell wrapper runChat
  7. Batch wrappers
  8. Convenience presets
  9. In cell custom function GPT
  10. Error handling
  11. Extending
  12. Formulas

What you get

| Component | Purpose | |———–|———| | CONFIG object | Centralises constants: API URL, default sheet, property key | | callOpenAI() | Low level POST helper (JSON in, assistant reply out) | | runChat() | One shot read prompt / write reply for any three cells | | Batch helpers | Iterate vertically or horizontally and fill results | | Temperature presets | chatLowTemp, chatMedTemp, chatDeterministic | | GPT() custom function | Use =GPT(prompt) directly inside a cell | | Robust errors | HTTP status and API message surfaced in sheet | | Key security | Uses Script Properties, never hard-codes secrets |

All of the above lives in a single file openai.gs so you can push it to GitHub without leaking your key.


Quick start

  1. Open script.google.com → new project
  2. Replace the default Code.gs with openai.gs (below)
  3. Run saveApiKey() once and paste your secret key
  4. In your sheet:
    cell B1: “gpt-4o-mini
    cell A2: “Write a haiku about tequila sunsets
    Then run from the editor:
    chatLowTemp("B1","A2","A3") // A3 will get the reply

Installation

Copy openai.gs snippets below into your Apps Script project, modify as you see fit.


Configuration

/* ========= CONFIG ========= */
const CONFIG = {
  SHEET_NAME : 'Sheet1',                       // default worksheet, you can change this
  API_URL    : 'https://api.openai.com/v1/chat/completions', // default endpoint for chat completions; not for images, assistants, etc.
  PROP_KEY   : 'OPENAI_API_KEY'                // Script Property name. DO NOT ENTER YOUR ACTUAL KEY, JUST LEAVE AS IS
};

Change SHEET_NAME or override it per call. Everything else rarely changes.

Saving your key (one time)

function saveApiKey() {
  PropertiesService.getScriptProperties()
    .setProperty(CONFIG.PROP_KEY, 'sk-paste-your-key-here');
}

[!NOTE] Run that once. Your secret is encrypted in the project and never committed. Remove block once complete.


API helper callOpenAI

/**
 * Low level wrapper around chat/completions.
 *
 * @param {string} prompt        user content
 * @param {string} model         model id, e.g. "gpt-4o-mini"
 * @param {number} [temperature] 0-2 (omit for deterministic, e.g. if using a reasoning model)
 * @param {Object[]} [systemMsgs] optional system / assistant messages
 * @returns {string} assistant reply
 */
function callOpenAI(prompt, model, temperature, systemMsgs) {
  const messages = [...(systemMsgs || []), { role: 'user', content: prompt }];
  const payload  = { model, messages };
  if (temperature !== undefined) payload.temperature = temperature;

  const options  = {
    method: 'post',
    contentType: 'application/json',
    headers: { Authorization:
               'Bearer ' + PropertiesService.getScriptProperties()
                 .getProperty(CONFIG.PROP_KEY) },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  const res      = UrlFetchApp.fetch(CONFIG.API_URL, options);
  const status   = res.getResponseCode();
  const body     = JSON.parse(res.getContentText());

  if (status < 200 || status >= 300)
    throw new Error(`OpenAI ${status}: ${body.error?.message || body}`);

  return body.choices[0].message.content.trim();
}

Single cell wrapper runChat

/**
 * Pull prompt and model from two cells, push reply to a third.
 *
 * @param {string} modelCell   A1 style reference
 * @param {string} promptCell  A1 style reference
 * @param {string} outputCell  A1 style reference
 * @param {number} [temperature]
 * @param {string} [sheetName] override default sheet
 */
function runChat(modelCell, promptCell, outputCell,
                 temperature, sheetName) {
  const sheet  = SpreadsheetApp.getActive()
                  .getSheetByName(sheetName || CONFIG.SHEET_NAME);
  const model  = sheet.getRange(modelCell).getValue();
  const prompt = sheet.getRange(promptCell).getValue();

  try {
    const reply = callOpenAI(prompt, model, temperature);
    sheet.getRange(outputCell).setValue(reply);
  } catch (err) {
    sheet.getRange(outputCell).setValue(`Error: ${err.message}`);
  }
}

Batch wrappers

Vertical range → vertical answers

Fill prompts in column A (A2:A10), write results to column B (B2:B10).

function runChatColumn(promptRangeA1, outputRangeA1, model,
                       temperature, sheetName) {
  const sheet   = SpreadsheetApp.getActive()
                    .getSheetByName(sheetName || CONFIG.SHEET_NAME);
  const prompts = sheet.getRange(promptRangeA1).getValues().flat();

  const replies = prompts.map(p =>
    p ? [callOpenAI(p, model, temperature)] : ['']
  );

  sheet.getRange(outputRangeA1).offset(0, 0, replies.length, 1)
       .setValues(replies);
}

Example:

runChatColumn('A2:A10', 'B2', 'gpt-4o-mini', 0.3);

Row wise (horizontal) helper

function runChatRow(promptRangeA1, outputRangeA1, model,
                    temperature, sheetName) {
  const sheet   = SpreadsheetApp.getActive()
                    .getSheetByName(sheetName || CONFIG.SHEET_NAME);
  const prompts = sheet.getRange(promptRangeA1).getValues()[0];

  const replies = [prompts.map(p =>
    p ? callOpenAI(p, model, temperature) : ''
  )];

  sheet.getRange(outputRangeA1).offset(0, 0, 1, replies[0].length)
       .setValues(replies);
}

Convenience presets

function chatLowTemp(modelCell, promptCell, outputCell, sheetName) {
  runChat(modelCell, promptCell, outputCell, 0.2, sheetName);
}
function chatMedTemp(modelCell, promptCell, outputCell, sheetName) {
  runChat(modelCell, promptCell, outputCell, 0.4, sheetName);
}
function chatDeterministic(modelCell, promptCell, outputCell, sheetName) {
  runChat(modelCell, promptCell, outputCell, undefined, sheetName);
}

In cell custom function GPT

/**
 * Use directly in a cell: =GPT("Explain prime numbers")
 *
 * @param {string} prompt
 * @param {string} [model]        optional, default "gpt-4o-mini"
 * @param {number} [temperature]  optional
 * @return {string}
 */
function GPT(prompt, model, temperature) {
  return callOpenAI(prompt,
                    model || 'gpt-4o-mini',
                    temperature);
}

Custom functions run under the user’s quota, not trigger quota, so avoid large loops here.


Error handling

Non 2xx responses propagate the HTTP status and API message into the sheet. A typical cell output looks like:

Error: OpenAI 429: Rate limit exceeded. Try again in 20 s.

This keeps failures visible and prevents silent data corruption.


Extending

Need Hook
Retry or back off wrap UrlFetchApp.fetch inside callOpenAI
Streaming add stream:true to payload, use UrlFetchApp.fetchAll
Function calling extend payload with functions and function_call
Moderation chain a small callModeration(prompt) before callOpenAI
On edit trigger use onEdit(e) to detect prompt edits then call runChat

Absolutely—here’s a cleaner, more conceptual, and flexible section you can drop into your documentation. This version standardizes the references, clearly explains the layout, and emphasizes how to adapt for your own sheet (with less “hard-coded” feel).


Convenient Formulas for Your Sheet

Use these examples to quickly connect your Google Sheet cells to OpenAI. You can use any cells you want—these are just examples. Adjust cell references as needed.


1. Deterministic Reply (no randomness)

How to set up:

Formula:

=GPT(A2, $B$1)

How it works / How to adapt:


2. Low-Temperature (Conservative) Reply

How to set up:

Formula:

=GPT(A2, $B$1, 0.25)

How it works / How to adapt:


3. Horizontal Prompts (Replies Across Columns)

How to set up:

Formula for B2 (drag right):

=GPT(B$1, $A$2, 0.4)

How it works / How to adapt:


4. Dynamic Script Call (runChat)

How to set up:

Formula:

="runChat('" & ADDRESS(ROW(B2),COLUMN(B2),4) & "','" & ADDRESS(ROW(A2),COLUMN(A2),4) & "','" & ADDRESS(ROW(C2),COLUMN(C2),4) & "',0.2);"

How it works / How to adapt:


5. Medium-Temperature Preset (chatMedTemp)

How to set up:

Formula:

="chatMedTemp('" & ADDRESS(ROW(B2),COLUMN(B2),4) & "','" & ADDRESS(ROW(A2),COLUMN(A2),4) & "','" & ADDRESS(ROW(C2),COLUMN(C2),4) & "');"

How it works / How to adapt:


How to adapt these formulas for your sheet:


Summary: These examples are here to save you time, but they work with any cells you choose. Just swap cell references for your layout, and you’re set.