Skip to content
Expedify
5 min

Web Search

Six fields, and two behaviours that are not in any of them: you do not choose the search engine, and a query that finds nothing is quietly replaced with a shorter one.

The scraper reads a page you already knew about. This node is for when you do not — a company you have never heard of, a name on an inbound enquiry, whether anything happened to an account this month.

Its configuration is the smallest in this module. What makes it worth a lesson is everything the configuration does not tell you.

The fields

query

What it holds
Search query (e.g., 'Shashank Sharma Expedify CEO', 'Python async tutorial')

num_results

What it holds
Number of results to return (1-20) Defaults to 5.

search_type

What it holds
Type of search: web (general) or news (recent articles) One of: web · news Defaults to web.

region

What it holds
Region code for localized results (e.g., 'us-en', 'in-en', 'wt-wt' for worldwide) Defaults to wt-wt.

safe_search

What it holds
Safe search filtering level One of: off · moderate · strict Defaults to moderate.

time_range

What it holds
Filter results by time period One of: all · day · week · month · year Defaults to all.

The result count is capped at twenty however you set it. Ask for a hundred and you get twenty. That is a sensible limit — search results past the first page are rarely what you wanted — but it is silent, so a workflow written expecting fifty is quietly working with less.

time_range is the field people forget and then wish they had used. Searching a company name across all time returns whatever ranks best, which is usually their homepage and a directory listing. Restricting it to the last month returns what actually happened recently, which is almost always the question.

search_type of news narrows to news sources; web is everything. For monitoring an account, news plus a month is the pairing that works.

You do not choose the search engine

There is no provider field, and the reason is that the choice is made on the server rather than in the node. It tries a paid search API if one is configured for the deployment, then a search library if it is installed, and failing both it falls back to scraping a search page directly.

The practical consequence: the same workflow returns different results on different environments. A query that works well on production may return thin results on a development machine that has neither the API key nor the library — and nothing in the node says which path it took. If results look unexpectedly poor, check the environment before you rewrite the query.

A worked example

A question in chat becomes a recent-news search, and the reply is built from what came back — with the empty case handled explicitly, because it is common.

Search, then branch on whether it found anything

The condition on count is the part worth copying.

ResultsYesNo

Scroll for all 5 steps →

The reply reaches into the results with an indexed path — {{websearch_1.output.results[0].title}} — rather than looping over them. That is deliberate: as the Text Response lesson showed, loop and conditional directives are stripped before a reply node runs, so a template can substitute values but cannot iterate. If you need one line per result, build the list upstream.

The condition exists because empty is a normal outcome, not a failure. A search that finds nothing returns successfully with a count of zero. Without that branch, the reply would be a heading followed by nothing, which reads to the user as a broken product rather than an honest “I could not find anything”.

region is set to India-English here. It changes which results rank, not which are legal to return, and the default of worldwide is rarely the best answer for a business that operates in one market.

The behaviour that will catch you

A query that finds nothing is retried with its first three words. If your search returns no results and the query is longer than two words, the node silently searches again using only the beginning of it and returns those results as the answer. So “Northwind Logistics funding round 2026” finds nothing, becomes “Northwind Logistics funding”, and returns whatever that matches — which may be another company entirely.

Nothing marks the substitution. The count is non-zero, the results look plausible, and the workflow proceeds as though the original question was answered. It is a well-meant feature — a slightly-too-specific query returning something is usually better than nothing — and it is dangerous the moment the result feeds a decision rather than a human's eyes.

Two defences. Keep queries short enough that the fallback would be the same query anyway — three or four words. And when the result matters, put the search terms in the reply so whoever reads it can see what was actually asked.

What comes back

{{alias.output.results}}

What you get
The results, each with a title, a URL and a snippet.

{{alias.output.count}}

What you get
How many. The field to branch on, every time.

{{alias.output.top_urls}}

What you get
Just the links — the natural input to a Web Scraper for a second pass.

{{alias.output.query}}

What you get
The query as it went out. Worth logging, given the substitution above.

The pairing this node exists for is search then scrape: find candidate pages here, hand top_urls to the scraper, and read the ones that matter. Search gives you snippets; the scraper gives you the page.

What breaks

Snippets are not facts. A search result's snippet is an extract chosen by a ranking algorithm to look relevant to your query. Feeding snippets to a model and asking it to summarise produces something that reads authoritative and is assembled from fragments of pages nobody opened. If the answer matters, scrape the page.

Results are not stable. Run the same query twice and the order can change, and so can the set. A workflow that assumes results[0] is the right one is making a bet that gets settled differently each run.

Try it

  1. Search a company you know well with time_range on all, then on month. The second list is the one you would actually want.
  2. Set num_results to 50 and read {{alias.output.count}}. It will be twenty at most.
  3. Now write a deliberately over-specific query — five or six words including a year — about something obscure. Compare the results with a search for just its first three words. If they match, you have watched the substitution happen.
  4. Add a condition on {{alias.output.count}} and give the zero branch a real reply. Every search-driven workflow needs this and most do not have it.
  5. Feed {{alias.output.top_urls}} into a Web Scraper and read the first page properly. That is the pattern this node is half of.

Next: Document Parser — the same problem when the thing you need to read arrived as a file rather than a page.

Related lessons