Build a sentiment-scoring function without AI
Rate text positive or negative with a tiny built-in lexicon — no API key, no quota.
Published Jul 23, 2025
Northwind surveys its clients every quarter, and the free-text answers are where the honest feedback lives. Reading every one for tone is slow, and calling an AI model for a quick gut-check feels like overkill — it means an API key, a quota, and a network round trip for each cell.
For a fast first pass, a lexicon does the job. This custom function scores a
piece of text by counting positive and negative words from a small built-in
list. It runs entirely inside the sheet — no key, no quota, no internet — so
you can fill a whole column of survey answers with =SENTIMENT(...) and sort
the spreadsheet by tone in seconds.
What you’ll need
- A Google Sheet with free-text answers to score — Northwind keeps survey responses with the open-text column in column A.
- Nothing else. A custom function lives in the bound script and needs no API key, no enabled services, and no setup beyond pasting the code.
The script
// Words that count as positive sentiment.
const POS = [
'great', 'love', 'excellent', 'happy', 'amazing', 'perfect',
'helpful', 'smooth', 'quick', 'fast', 'clear', 'recommend',
];
// Words that count as negative sentiment.
const NEG = [
'bad', 'hate', 'slow', 'broken', 'confusing', 'disappointing',
'poor', 'awful', 'rude', 'unclear', 'frustrating', 'late',
];
/**
* Scores a piece of text by sentiment. Each positive word adds 1,
* each negative word subtracts 1. The result is a single number:
* above zero is positive, below zero is negative, zero is neutral.
*
* @param {string} text The text to score.
* @return {number} The net sentiment score.
* @customfunction
*/
function SENTIMENT(text) {
// An empty cell is neutral — score it zero.
if (!text) return 0;
// Lower-case and split on any non-word character to get plain words.
const words = String(text).toLowerCase().split(/\W+/);
// Walk every word, adding for positives and subtracting for negatives.
let score = 0;
for (const w of words) {
if (POS.includes(w)) score += 1;
if (NEG.includes(w)) score -= 1;
}
return score;
}
How it works
SENTIMENTtakes one cell of text. The@customfunctiontag in the JSDoc block is what makes it callable from a spreadsheet formula.- If the cell is empty, it returns
0straight away — a blank answer is neutral. - It lower-cases the text and splits it on
\W+, any run of non-word characters, which breaks the sentence into a clean array of words and strips punctuation. - It walks every word. Each word found in the
POSlist adds 1 to the score; each word in theNEGlist subtracts 1. - It returns the net total. A positive number leans positive, a negative number leans negative, and zero is neutral or mixed.
Use it
Paste the formula next to a column of answers and fill it down:
=SENTIMENT(A2)
Example run
Drop the function beside a column of survey answers and the tone falls out as a number you can sort on:
| Answer (A) | =SENTIMENT(A) |
|---|---|
| “The team was helpful and the rollout was smooth.” | 2 |
| ”Setup was confusing and support felt slow.” | -2 |
| ”It works.” | 0 |
| ”Great product, but the onboarding was frustrating.” | 0 |
Note the last row: one positive word and one negative word cancel out to zero, which is exactly what a mixed answer should score.
Watch out for
- It counts words, it does not read meaning. “Not great” scores
+1because the lexicon never sees the “not”. Negation, sarcasm, and context are all invisible to it. - The lexicon is tiny and English-only. Answers that avoid these exact words
score
0even when the tone is obvious. ExtendPOSandNEGwith the vocabulary your clients actually use. - Only whole words match. “loved” and “loving” will not match “love” — add the word forms you care about, or switch the check to a stem-based comparison.
- Custom functions are recalculated by the sheet and cannot call services that need authorisation, but a pure-lexicon function like this has no such limit.
- For nuance — negation, sarcasm, mixed sentiment — a lexicon will not get you there. Use Score sentiment in open-text feedback, which sends each answer to Claude.
Related
Parse messy mixed-format dates
Normalise inconsistently formatted strings into real date values with a single formula.
Updated Aug 2, 2025
Scrape a web table into cells with one formula
Pull HTML tables into Sheets as a custom function — no IMPORTHTML quirks.
Updated Jul 30, 2025
Mask sensitive columns for shareable copies
Redact PII with a custom function so you can share a copy of the sheet without exposing names, emails, or numbers.
Updated Jul 26, 2025
Build a unit-conversion function library
Convert between any units with one custom formula — kg/lb, km/mi, °C/°F, and the rest.
Updated Jul 19, 2025
Create a readability-scoring function
Rate text columns for reading difficulty with a Flesch reading-ease score in one formula.
Updated Jul 16, 2025