How to add Webflow autocomplete based on an external database

How to add Webflow autocomplete based on an external database

Learn how to build a Webflow lookup field that pulls suggestions from your external database through a Route Handler on Webflow Cloud.

How to add Webflow autocomplete based on an external database

Ismail Ajagbe
Technical Author
View author profile
Ismail Ajagbe
Technical Author
View author profile
Table of contents

Autocomplete becomes useful when the round trip stays quick; a Route Handler on Webflow Cloud puts your external database one same-origin fetch away from the input on your Webflow page.

Most lookup fields on a marketing site point at store locations, SKUs, partner names, and account IDs held in a system of record with its own API. The marketing team wants a field on a Webflow page that suggests matches as a visitor types.

The input is a Webflow element; the answers live elsewhere. A small backend service running on Webflow Cloud bridges the two.

The build is a Next.js Route Handler deployed to Webflow Cloud and mounted at a path on your site, plus a short page script that calls it on each keystroke. Because the handler is mounted on the same domain as the page, the browser request is a plain relative fetch with no CORS setup, and the database token stays in an environment variable on the Worker.

What do you need to add autocomplete from an external database in Webflow?

You need six components, including Webflow Cloud (included on the free Starter plan), or Premium or higher if the app must be mounted to a custom domain.

Prepare the site, database access, local tooling, and application project before writing the suggestion handler:

  • Webflow Cloud: Use Webflow Cloud, which is included from the free Starter site plan up.
  • A plan that can publish custom code: The suggestion handler runs on any plan, but the page script in step 6 does not. Publishing custom code, whether through a Code Embed element or the site's custom code settings, needs a paid Site plan or a Core, Growth, Agency or Freelancer Workspace.
  • Custom-domain plan: Use Premium or higher if you need to mount the application to a custom domain.
  • Webflow site: Prepare the input element and a way to load the page script after the page elements.
  • External database: Provide an HTTP query endpoint that accepts a search term and returns JSON, with a read-access token or key.
  • Local tooling: Install Node.js and npm for local development and deployed builds.
  • Next.js project: Use version 15 or higher, either in an existing project or a fresh scaffold.

With these requirements in place, the build hinges on a tested database contract, a mounted handler, and page code that calls the correct path.

6 steps to add autocomplete from an external database in Webflow Cloud

A keystroke starts in a vanilla script on the Webflow page, reaches a mounted Route Handler that proxies a constrained query, and returns normalized results for the script to paint.

The build moves from the database contract through the deployed handler and finishes with the Webflow input and results list.

1. Fix the query contract with your database

Write the exact request and response contract before any TypeScript. Autocomplete needs a prefix- or contains-match on one text column, capped to a small number of rows, and a stable identifier for each row so you can post the selected value later.

Most hosted databases expose this through a REST query endpoint or a saved query you call with a parameter; if yours needs SQL, wrap the query server-side in the endpoint so the handler never composes SQL from user input.

I write the contract as a URL and a response shape so you can test it with curl before Webflow Cloud is involved.

The handler assumes the following shape, and the marked lines in the handler are the only place it needs changing:

GET {DATABASE_QUERY_URL}?q=<term>&limit=8
Authorization: Bearer {DATABASE_API_TOKEN}

200 OK
[
  { "id": "location-1", "name": "Location One" },
  { "id": "location-2", "name": "Location Two" }
]

You now have a request you can run from a terminal that returns a JSON array of id and name pairs for a minimum-length term, which the rest of the build checks against as ground truth.

2. Scaffold the Next.js app for Webflow Cloud

Keep the default App Router configuration for this build. The platform supplies the mounted base path during deployment, so leave the config alone.

Use the current create-next-app options and a Next.js 15-or-higher package version from the directory where you keep projects:

npx create-next-app@latest webflow-autocomplete --typescript --app --use-npm
cd webflow-autocomplete
npm run dev

Accept the defaults for ESLint and the import alias; neither affects the handler. Skip Tailwind unless you plan to build UI inside the app, because the autocomplete UI in this build lives on the Webflow page.

Save the fresh project in version control once the dev server starts. When npm run dev prints its local URL and the default Next.js page loads in a browser, the scaffold is ready for the handler.

3. Write the suggestion Route Handler

A Route Handler at app/api/suggest/route.ts reads q, refuses terms below the configured minimum, calls the database with the token from the environment, and returns a normalized suggestions array.

Normalizing on the server matters because the Designer script should never know the database's column names; if the schema changes, the handler changes, and the page does not.

Leave the Next.js edge runtime target unset in this file. Webflow Cloud runs on Cloudflare Workers, an edge platform. The Next.js edge runtime target is separate, and the OpenNext Cloudflare adapter Webflow Cloud deploys through doesn't support it, so targeting that runtime ships a broken build.

Run the handler locally with your installed Next.js version. Confirm that the next/server module API and NextResponse.json produce the expected calls and response headers before production.

Paste this handler, then adjust the marked lines for your database's own API:

// app/api/suggest/route.ts
import { NextResponse } from 'next/server';

const MIN_LENGTH = 2;
const MAX_RESULTS = 8;

type Suggestion = { label: string; value: string };

export async function GET(request: Request) {
  const url = new URL(request.url);
  const q = (url.searchParams.get('q') ?? '').trim();

  if (q.length < MIN_LENGTH) {
    return NextResponse.json({ suggestions: [] });
  }

  const endpoint = process.env.DATABASE_QUERY_URL;
  const token = process.env.DATABASE_API_TOKEN;
  if (!endpoint || !token) {
    return NextResponse.json({ error: 'Database is not configured' }, { status: 500 });
  }

  // Adjust these two lines to match your database's HTTP API.
  const dbUrl = `${endpoint}?q=${encodeURIComponent(q)}&limit=${MAX_RESULTS}`;
  const dbResponse = await fetch(dbUrl, {
    headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
  });

  if (!dbResponse.ok) {
    return NextResponse.json({ error: 'Lookup failed' }, { status: 502 });
  }

  const rows = (await dbResponse.json()) as Array<{ id: string; name: string }>;
  const suggestions: Suggestion[] = rows.slice(0, MAX_RESULTS).map((row) => ({
    label: row.name,
    value: row.id,
  }));

  return NextResponse.json({ suggestions });
}

The minimum length and result cap constrain individual queries. The public route remains exposed to request volume because someone can bypass the browser debounce by calling the endpoint directly.

Before production, add authentication when the audience permits it, enforce a server-side per-client rate limit, and configure an upstream request quota or spend cap that stops excess traffic. Treat the page script and query constraints as query-shaping controls, not abuse controls.

Create a .env.local with DATABASE_QUERY_URL and DATABASE_API_TOKEN set to the tested database values, restart the dev server, and open http://localhost:3000/api/suggest?q=au. You should see a JSON object with a suggestions array of label and value pairs.

4. Create the Webflow Cloud app and choose a mount path

The mount path becomes part of every page-side request, so choose a short, stable value when you set up the Webflow Cloud project on your site.

I mount lookup services at a path such as /app, because that string is about to be hardcoded in a script on the Webflow page and is easier to keep in sync if someone later considers a more descriptive name.

Local development uses next dev with no mount path, while the deployed app answers only under the mount path. The handler is unaffected because the platform injects the path at build time, but the bring-your-own-app page states that client-side fetch calls must manually include the base path.

Code inside the Next.js app can read NEXT_PUBLIC_BASE_PATH; the page script uses the mounted endpoint at /app/api/suggest.

Confirm the selected value in the Webflow Cloud project settings:

Write the mount path down. Once the project exists and shows the mount path you chose, the site reserves a subpath for the first deploy.

5. Add the database credentials and deploy

At request time, the handler reads DATABASE_QUERY_URL and DATABASE_API_TOKEN from the Webflow Cloud environment, with the token marked as a secret. Nothing extra is needed in next.config.

The required variables cover the database connection, while the public base path applies only if you later fetch from inside the app:

Variable Secret Purpose
DATABASE_QUERY_URL No The HTTP query endpoint, without the q parameter.
DATABASE_API_TOKEN Yes The read-scoped token the handler sends as a bearer credential.
NEXT_PUBLIC_BASE_PATH No The mount path used by client-side code inside the Next.js app. The page script uses the path configured in its fetch URL.
Variable → Secret → Purpose
DATABASE_QUERY_URL
No
The HTTP query endpoint, without the q parameter.
DATABASE_API_TOKEN
Yes
The read-scoped token the handler sends as a bearer credential.
NEXT_PUBLIC_BASE_PATH
No
The mount path used by client-side code inside the Next.js app. The page script uses the path configured in its fetch URL.

Scope the database token to read-only on the one table or view the query touches. On a client site, the agency holds this credential, and a leaked read-only key on a single view is a far smaller incident than a leaked admin key.

Deploy the project and watch the build log. When it finishes, open https://your-site.webflow.io/app/api/suggest?q=au after substituting your staging domain and mount path. You should get the same JSON your local server returned.

6. Wire the input in the Webflow Designer

Two rendered IDs connect the page elements to the mounted handler. Add a Text field, or a Form block with a single input, to the page and use lookup-input as the rendered input's ID. Add an empty Div block directly beneath it and use lookup-results as its rendered ID. Leave the Div empty; the script fills it.

Test this page code in the target browsers and with the published handler before production.

Add the following as a Code Embed element on the page, or in the page's before-</body> custom code, after the page elements, replacing /app with your mount path:

<style>
  #lookup-results { position: relative; }
  .lookup-item { padding: 8px 12px; cursor: pointer; border: 1px solid #ddd; border-top: 0; background: #fff; }
  .lookup-item:hover { background: #f3f3f3; }
</style>
<script>
(function () {
  var input = document.getElementById('lookup-input');
  var list = document.getElementById('lookup-results');
  if (!input || !list) return;

  var timer = null;
  var controller = null;

  function render(items) {
    list.innerHTML = '';
    items.forEach(function (item) {
      var row = document.createElement('div');
      row.className = 'lookup-item';
      row.textContent = item.label;
      row.addEventListener('click', function () {
        input.value = item.label;
        input.dataset.selectedValue = item.value;
        list.innerHTML = '';
      });
      list.appendChild(row);
    });
  }

  input.addEventListener('input', function () {
    var q = input.value.trim();
    delete input.dataset.selectedValue;
    clearTimeout(timer);
    if (controller) controller.abort();
    if (q.length < 2) { list.innerHTML = ''; return; }

    timer = setTimeout(function () {
      controller = new AbortController();
      fetch('/app/api/suggest?q=' + encodeURIComponent(q), { signal: controller.signal })
        .then(function (r) { return r.json(); })
        .then(function (data) { render(data.suggestions || []); })
        .catch(function (err) { if (err.name !== 'AbortError') list.innerHTML = ''; });
    }, 150);
  });
})();
</script>

The debounce and the AbortController reduce the number of database queries as a visitor types; the abort also stops a slow earlier response from overwriting a newer one.

The selected row's value lands in data-selected-value on the input, which is what you read when the surrounding form submits. Editing the visible input clears that selected value so the form cannot submit an old record ID with a newly typed label.

Check the autocomplete code and its placement after the page elements:

Publish the site to staging and type enough letters to meet the configured minimum. A list of matching rows should appear under the input; clicking one should fill the input, and the browser's network tab should show requests to /app/api/suggest returning JSON.

What causes external database autocomplete in Webflow Cloud to fail?

Failures usually come from an unsupported runtime target, an incorrect Webflow Cloud mount path, missing deployed environment variables, or page code that sends excessive database requests.

Use the visible build, response, and request symptoms below to identify which layer needs attention.

The Webflow Cloud build fails with an unsupported runtime configuration

Cause: The route sets the Next.js edge runtime target even though Webflow Cloud deploys the application through the OpenNext Cloudflare adapter. That target is separate from the Cloudflare Workers platform and is not supported by the deployed adapter, so the application cannot ship successfully.

Fix: Remove the runtime target from app/api/suggest/route.ts and redeploy. The platform assigns the Workers runtime for the handler. If you also changed next.config, restore the default configuration created with the App Router scaffold.

A clean next.config and a handler with no explicit runtime target are the configuration this build expects. Watch the next build log to confirm that the Route Handler compiles, then load the mounted suggestion URL directly and verify that it returns JSON rather than a deployment or routing error.

The fetch from the Webflow page returns HTML instead of JSON

Cause: The script is calling a URL outside the app's configured mount path, so Webflow serves its not-found page. The r.json() call then throws on the HTML body, and the catch branch clears the results list.

The visible symptom is an input that never shows suggestions while the network tab shows a successful or not-found response with an HTML content type.

Fix: Use the mounted handler URL configured for the Webflow Cloud application in the page script. If you later build a suggestion component inside the Next.js app itself, read the path from NEXT_PUBLIC_BASE_PATH and prefix your fetch with it.

Confirm the change by loading the handler URL directly in a new tab on the published site. It should return JSON, and that exact mounted URL is what the script must call.

Also confirm that the staging or custom domain actually has the application mounted at the path used by the relative request.

Suggestions work under next dev, but the deployed route returns a server error

Cause: The handler cannot find DATABASE_QUERY_URL or DATABASE_API_TOKEN in the deployed environment and returns the "Database is not configured" response written into the handler.

The local dev server reads the uncommitted values in .env.local, while Webflow Cloud environment variables supply the deployed application. Missing or misnamed deployed values therefore affect production without affecting local development.

Fix: Confirm that both variables exist with the exact names the handler reads in the Webflow Cloud environment used by the app, then retest the route. No next.config change is needed.

If the server error persists, check the token itself against the database with curl, since an expired or wrongly scoped token also produces a non-success response from the database.

The handler distinguishes missing configuration from a failed upstream lookup, so inspect the response status and body to determine whether the application configuration or database authorization needs correction.

The database logs show bursts of near-identical queries for each visitor

Cause: The page-side debounce or abort is not in effect, so every keystroke reaches the database. The early return fires if the script runs before the elements exist. A second script copy or a debounce delay removed during testing can produce the same burst.

Each request also spends Worker time against the platform's per-request budget of 20 seconds of wall time and 30 seconds of CPU.

Fix: Keep one copy of the script and make sure it loads after the page elements. Retain the debounce, then verify in the network tab that requests are consolidated instead of sent for every keystroke.

Separately, enforce the server-side rate limit and upstream quota or spend cap described for the public handler, because page-side controls do not stop direct requests. Do not reach for a Cache-Control header to absorb the burst: Webflow Cloud always replaces that response header with private, no-cache, so it has no effect.

Repeat-query caching has to go through the Key Value store, whose minimum cache TTL is 60 seconds. Before promising a client a request volume, check the current Webflow Cloud limits information on developers.webflow.com for the allowance that applies to the site instead of relying on a figure from another plan.

What you can build next with an external database and Webflow

A selected ID gives the surrounding form a stable record key. The same Route Handler pattern can validate the selected ID on submit or write the submission back to the database from a second handler. The marketing team keeps designing the page in the Designer; the developer owns one small app on Webflow Cloud.

For a hosted search option, explore the Algolia integration. For a self-hosted index behind the same handler pattern, see Elasticsearch autocomplete.

The handler can also point to Webflow. Webflow's structured CMS is reachable through the Data API, so a second route can suggest CMS items alongside database rows in the same list, which is how I would handle a lookup that spans products in the CMS and inventory in a warehouse system.

Frequently asked questions

Can you add autocomplete from an external database in Webflow without Webflow Cloud?

Yes. You can use a database vendor's hosted search widget when it provides a public, read-only key that is safe to expose in the browser. You place the widget on the Webflow page and omit the backend service. You accept the vendor's markup and rate limits, and you must never expose a credential with write access.

Should you restrict the suggested route with Next.js middleware?

Yes, when the audience and authentication model permit. Apply the restriction in a layer compatible with the application's deployed runtime, then test the mounted route after deployment. Keep the route public only when anonymous visitors need suggestions, and rely on server-side rate limits and upstream quotas to control direct requests.

Can you build the suggest handler in Astro or Vite instead of Next.js?

Yes. Alongside Next.js 15 or higher, Webflow Cloud deploys Astro 6 or 7 and Vite 6.1 or higher with React, Vue, Svelte, or vanilla JavaScript. Your framework must expose a GET endpoint under the mount path that returns the same JSON suggestions array. The page script and npm package manager requirements remain unchanged.


Last Updated
September 25, 2026
Category

Related articles


verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logosoundcloud logoreddit logothe new york times logoideo logoupwork logodiscord logo
verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logosoundcloud logoreddit logothe new york times logoideo logoupwork logodiscord logo

Get started for free

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.

Get started — it’s free
Watch demo

Try Webflow for as long as you like with our free Starter plan. Purchase a paid Site plan to publish, host, and unlock additional features.