Adding Live Search to a Flowise Chatflow

Flowise's Custom Tool node takes plain JavaScript. That's all you need to give a visual chatflow real web results.

Profile picture of Serply
Serply
A Flowise chatflow with a custom search tool node connected to an agent

Flowise is a good fit for the case where someone non-technical needs to iterate on an agent’s prompt and structure without touching a repo. Its weak spot is retrieval: the bundled search integrations are limited, and the moment you need a specific engine, a specific country, or your own result formatting, you’re out of luck.

The Custom Tool node closes that gap. It takes a JavaScript function body and a JSON schema, which is enough to wrap any HTTP API.

Creating the search tool

Tools → Create New Tool.

Name: web_search

Description — this is the text the agent reads to decide when to call it, so write it for the model, not for a docs page:

Search the live web via Google. Returns ranked results with title, URL, and a
short snippet. Use for current events, specific facts, product details, or
anything you should not answer from memory. Snippets are 1-2 sentences — use
read_page for full content.

Input Schema:

PropertyTypeDescriptionRequired
querystringKeyword-style search query, not a full sentence.yes
numnumberHow many results to return, 1–50.no

JavaScript Function:

const apiKey = $vars.SERPLY_API_KEY;
const query = $query;
const num = $num || 10;

const url = `https://api.serply.io/v1/search/q=${encodeURIComponent(query)}&num=${num}`;

try {
  const res = await fetch(url, {
    headers: { 'X-Api-Key': apiKey, 'X-Proxy-Location': 'US' },
  });

  if (res.status === 429) {
    return 'Rate limited. Wait a few seconds, then try one more specific query.';
  }
  if (!res.ok) {
    return `Search failed with HTTP ${res.status}.`;
  }

  const body = await res.json();
  const results = body.results || [];

  if (results.length === 0) {
    return `No results for "${query}". Try broader or different keywords.`;
  }

  return results
    .map((r, i) => `${i + 1}. ${r.title}\n   ${r.link}\n   ${r.description || ''}`)
    .join('\n\n');
} catch (e) {
  return `Search error: ${e.message}`;
}

Three Flowise-specific things.

Input schema properties become variables prefixed with $$query, $num. This trips people up because it isn’t a properties object you destructure.

$vars.SERPLY_API_KEY reads from Variables in the Flowise sidebar. Add it there as a static variable rather than hardcoding the key, because chatflows get exported and shared as JSON and a hardcoded key travels with them.

Return a string, always. Returning an object gets stringified in whatever way Flowise decides, and errors returned as strings let the agent recover — a thrown exception just ends the run.

The URL shape matters: the query goes in the path as /search/q=..., not as a ?q= querystring.

The news tool

Same process, different response shape.

Name: news_search

Description:

Search recent news articles. Returns headline, source, publication date, and
summary. Use when recency matters — announcements, events, ongoing stories.
Use web_search for general factual questions.

Input Schema: one required query string.

const apiKey = $vars.SERPLY_API_KEY;
const url = `https://api.serply.io/v1/news/q=${encodeURIComponent($query)}`;

try {
  const res = await fetch(url, { headers: { 'X-Api-Key': apiKey } });
  if (!res.ok) return `News search failed: HTTP ${res.status}`;

  const body = await res.json();
  const entries = (body.feed && body.feed.entries) || [];

  if (entries.length === 0) return `No recent coverage of "${$query}".`;

  return entries
    .map(e =>
      `${e.title}\n  ${e.source || 'unknown source'} — ${e.published || 'no date'}\n` +
      `  ${e.link}\n  ${e.summary || ''}`)
    .join('\n\n');
} catch (e) {
  return `News error: ${e.message}`;
}

body.feed.entries, not body.results. The news endpoint is the one response shape in this API that differs from the rest, and it’s the most common integration bug.

The page reader

Name: read_page

Description:

Fetch the full text of a web page as markdown. Use after web_search when a
snippet is not enough. Slower and more expensive than a search — read the 2-3
most promising results, not everything.

Input Schema: one required url string.

const apiKey = $vars.SERPLY_API_KEY;
const MAX = 12000;

try {
  const res = await fetch('https://api.serply.io/v1/request', {
    method: 'POST',
    headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' },
    body: JSON.stringify({ url: $url, response_type: 'markdown' }),
  });

  if (!res.ok) {
    return `Could not fetch ${$url} (HTTP ${res.status}). Try another source.`;
  }

  const text = await res.text();
  return text.length > MAX
    ? text.slice(0, MAX) + `\n\n[Truncated at ${MAX} of ${text.length} characters.]`
    : text;
} catch (e) {
  return `Fetch error: ${e.message}`;
}

res.text(), not res.json(). Markdown mode returns the content as the raw response body.

The truncation limit is not optional in Flowise. There’s no automatic context management on tool output, so a 200KB page goes straight into the prompt and either blows the context window or costs a fortune.

Wiring the chatflow

In a new chatflow:

  1. Tool Agent as the agent node
  2. ChatAnthropic (or your model of choice) into its Model input
  3. Buffer Memory into Memory
  4. All three Custom Tools into Tools

The Tool Agent’s System Message is where the retrieval policy lives:

You answer questions using live web sources.

Search before answering anything factual, current, or specific. Do not answer
those from memory.

After searching, read the 2-3 most promising results with read_page before
concluding. Snippets alone are not sufficient evidence for a factual claim.

Cite the URL for every fact you state. If sources disagree, present both.

If the search results do not answer the question, say so plainly. Do not fill
the gap from background knowledge.

That last paragraph is the one that changes behaviour most. Without an explicit permission to fail, an agent with search tools will search, find nothing useful, and answer from memory anyway — usually with a citation to whatever it did find, which is worse than no citation at all.

Debugging

Open the chat panel and expand the tool call boxes under each response. Two things are worth checking on any new chatflow.

The query the agent actually sent. Models often send full sentences when the description doesn’t explicitly say keyword-style, and sentence queries return noticeably worse results.

Whether the tool returned your “no results” string. That path is easy to miss because it looks like a successful call in the trace, and it’s the most common cause of an answer that’s fluent and unsourced.