AI Functions in Spreadsheet Cells with Any LLM
An AI function is a spreadsheet formula whose result comes from a model. =PROMPT("Translate ", A2, " to French") fills down a column, recalculates when A2 changes and feeds a COUNTIF, like any other formula. The model behind it is any LLM you configure: a hosted provider, a gateway in your network, or a local Ollama.
Published on October 14, 2026
Introducing
Most spreadsheet AI is a chat panel next to the grid. An AI function is the other shape: the model is a formula primitive, called from a cell with cell references as arguments, and its answer is a cell value. That gives it everything a formula has. It takes references, so the prompt reads the data in the row. It fills down, so one formula handles a column. It recalculates when its inputs change. Its result feeds other formulas, so a COUNTIF over a column of classifications is a report.
Excel ships this shape as the COPILOT function and Google Sheets as the AI function, each on the vendor's own model. In Jspreadsheet the function is =PROMPT(), evaluated by Jspreadsheet Server. The server holds the model key, caches the answer with the document and broadcasts it to every collaborator. The model is a setting on the server, so the same formulas run on a hosted provider or on a model that never leaves your network.
What an AI function does in a sheet
The prompt is text and references, joined with a space. The instruction goes in the text, the data comes from the cells, and the cell holds the answer:
| Task | Formula | Result |
|---|---|---|
| Translate | =PROMPT("Translate to French. Reply with the translation only:", B2) |
Bonjour |
| Classify | =PROMPT("Classify into one of billing, bug, feature. Reply with the category only:", C2) |
bug |
| Extract | =PROMPT("Extract the invoice number. Reply with the value only:", D2) |
INV-2041 |
| Summarize | =PROMPT("Summarize in at most 20 words:", E2) |
one sentence |
| Generate | =PROMPT("Write a one-line product description for", A2, "with these features:", B2) |
one line |
Fill the formula down and every row gets its own answer. Put =COUNTIF(F:F, "bug") under the classification column and the sheet counts bug reports. Change a ticket in column C and its classification recalculates; the count follows.
How the cell gets its value
The extension is registered in the browser and on the server, so PROMPT is a known formula on both sides. The browser returns the ... placeholder and never loads a model SDK. Jspreadsheet Server loads the document headless with the same formula engine, calls the model, stores the answer in the cell cache with setCache, and broadcasts that call to every browser with the document open. Each browser replays it and the ... becomes the answer.
The cache is persisted with the document through your adapter. When the document is loaded again, the stored answer is reused and the model is not called a second time. Editing the formula, or a cell it references, recalculates it and sends a new request.
Any LLM
The extension that ships today, @jspreadsheet/openai, speaks the OpenAI chat completions protocol. That protocol is the common interface of the hosted providers, the gateways and the local runtimes, so one extension reaches all of them. No Jspreadsheet service sits between your server and the model: a hosted endpoint receives the cell values your server sends it, a local provider receives them without leaving the network.
Hosted provider
const openai = require('@jspreadsheet/openai');
openai({
apiKey: process.env.MODEL_KEY,
onbeforesend: function(instance, request) {
request.model = 'gpt-4o-mini';
},
});
server({
// ... persistence hooks, authentication hooks and license
extensions: { api, openai },
});
The default model is gpt-4. onbeforesend runs before each request: set the model there, or prepend a system message that applies to every formula in the document.
Gateway or another provider
baseURL is forwarded to the SDK. Azure OpenAI, OpenRouter, an internal gateway, or Anthropic's OpenAI-compatible endpoint with a Claude model name all take the same three lines:
openai({
apiKey: process.env.MODEL_KEY,
baseURL: 'https://your-gateway.internal/v1',
onbeforesend: function(instance, request) {
request.model = 'whatever-you-run';
},
});
Local model
openai({
apiKey: 'ollama', // required by the SDK, ignored by Ollama
baseURL: 'http://localhost:11434/v1', // Ollama's OpenAI-compatible endpoint
onbeforesend: function(instance, request) {
request.model = 'llama3.2';
},
});
With a local provider, no request leaves the network. The formulas in the cells are the same as with a hosted provider.
Provider without the OpenAI protocol
The extension is one file, openai.js, on GitHub. One function in it, query, builds the request and reads the answer; nothing else in the file knows which model is behind it. For a provider's native SDK, or for a runtime with its own API, replace that function. The Anthropic SDK is a different require and a different query:
if (! Anthropic && typeof(require) === 'function') {
var Anthropic = require('@anthropic-ai/sdk');
}
const query = function(prompt, done) {
let request = {
model: 'claude-haiku-5-5',
max_tokens: 300,
system: 'Reply with the value only, no explanation.',
messages: [{ role: 'user', content: prompt.join(' ') }],
};
if (beforeSend) {
beforeSend(instance, request);
}
instance.messages.create(request).then(async function(result) {
await done(result.content[0].text);
});
}
Ollama's own /api/chat needs no SDK at all, and query becomes a fetch. The formulas, the cache and the broadcast do not change.
Prompts that return a value
A model left to itself answers with a paragraph, and a cell has no room for one. Two places carry the instruction. The prompt itself carries it when the rule belongs to one formula, as in the table above. The system message in onbeforesend carries it when the rule belongs to every formula in the document:
onbeforesend: function(instance, request) {
request.model = 'gpt-4o-mini';
request.messages.unshift({
role: 'system',
content: 'Answer with a single value, no explanation.'
});
}
With the rule in the system message, each cell carries only the data: =PROMPT("Classify as billing, bug or feature:", C2).
Cost and limits
- Every recalculation is a request. Fill a column down over ten thousand rows and that is ten thousand calls. Keep large batches on a local model, or write the values through the REST API from a job.
- Pick the model per task. Classification and extraction are cheap on a small model. Keep the larger ones for summaries and open questions.
- Prefer a local model for sensitive data. The formulas do not change, and nothing leaves your infrastructure.
#NAME?in the cell means the extension is not registered in the browser, or Formula Pro is missing. A cell that stays on...means the server did not answer: check the configuration call and theextensionsentry.- The model is called on every reload when the document is not persisted. The cache lives in the document, so it needs a server adapter to survive.
Next to the agent and the MCP server
An AI function answers inside a cell. The server agent is the chat shape: a request in plain language, carried out with spreadsheet tools, with quotas in your hooks. The MCP server is the third shape, for an AI client outside your application. All three work on the same live document under the same permissions, and all three keep the model key on your server.
Useful links
PROMPT formula documentation
AI with Jspreadsheet Server
Writing server extensions
The extension source on GitHub
A private alternative to ChatGPT for Excel, with a local model