// Gmail · Apps Script

Use negative (NOT) search operators in Gmail with Apps Script.

GmailApp.search() accepts the same query syntax as the Gmail search box, including minus-prefixed operators like -label:, -from:, and -is:starred to exclude threads from results.

I want to filter Gmail threads in Apps Script by excluding certain senders, labels, or states without manually checking each thread in a loop.

The script

copy · paste · trigger
excludeThreads.gs
Apps Script
// Find unread threads that are NOT from noreply addresses
// and NOT already labeled 'processed'
function getUnreadInboxExcluded() {
  var query = 'in:inbox is:unread -from:noreply -from:no-reply -label:processed';
  var threads = GmailApp.search(query, 0, 50);

  for (var i = 0; i < threads.length; i++) {
    var thread = threads[i];
    var subject = thread.getFirstMessageSubject();
    var from = thread.getMessages()[0].getFrom();
    Logger.log('Subject: ' + subject + ' | From: ' + from);
  }

  Logger.log('Total threads matched: ' + threads.length);
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

The minus prefix is the only negation you get

GmailApp.search() has no not() method, no exclude() helper, no second argument for exclusions. What it has is the same query parser Gmail runs in the browser. That parser recognizes a leading minus on any operator as a negation: -from:, -label:, -is:, -has:, -subject:, -in:. The minus must be flush against the operator name with no space.

So the mental model is simple: write the query you would type into the Gmail search box, paste it into GmailApp.search(), and it works. The first time I used this I spent twenty minutes looking for a dedicated API method before realizing I had already solved it in the query string.

Stacking exclusions and the 500-thread cap

Multiple minus operators are ANDed together. The query 'in:inbox is:unread -from:noreply -from:no-reply -label:processed' returns threads that are in the inbox AND unread AND not from either noreply variant AND not carrying the processed label. All conditions must hold.

GmailApp.search() caps at 500 threads per call. The second and third arguments are start (zero-indexed offset) and max (up to 500). To page through a large exclusion query, increment start by your batch size in a loop and stop when the returned array length is less than max. Forgetting the cap is how you silently miss threads on active inboxes.

Operators worth combining with exclusions in practice

The most useful combinations in automation scripts tend to be -label: (skip already-processed threads), -from: (filter out automated senders), -is:starred (exclude threads a human has flagged for manual review), and -category:promotions (keep the query out of the Promotions tab without touching tabs at the label level).

One gotcha: -label: matches the label's display name, not its ID. If a label name contains spaces, wrap it in curly braces: -label:{my label with spaces}. Without the braces Gmail parses each word as a separate token and the exclusion silently fails to match.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Does GmailApp.search support the NOT keyword like SQL?
No. Gmail's query syntax uses a leading minus (-) for negation, not the word NOT. Writing 'NOT from:example.com' will not work; write '-from:example.com' instead.
Can I exclude multiple labels in one operator?
Yes, using the OR syntax inside curly braces: -label:{processed archived}. This excludes threads carrying either label. Without braces, -label:processed archived is parsed as excluding 'processed' AND requiring the word 'archived' in the thread, which is probably not what you want.
Why does my exclusion query return fewer than expected results?
The most common cause is the 500-thread cap. GmailApp.search() returns at most 500 threads per call even if you pass a higher number. Use the offset argument and loop to page through results. A secondary cause is label name mismatches: the name is case-sensitive and must match exactly, including any spaces wrapped in curly braces.
Will -is:unread exclude threads where only some messages are read?
Gmail treats a thread as unread if any message in it is unread. So -is:unread excludes threads with at least one unread message. If you want threads where all messages are read, -is:unread is the correct operator; there is no per-message read-state filter in GmailApp.search.
// one good script a week

Get a working Apps Script snippet in your inbox, weekly.