Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

ElasticSearch

Connect FlutterFlow to Elasticsearch using a FlutterFlow API Call that POSTs Query DSL JSON to your index's /_search endpoint. Elasticsearch credentials must never ship in the client app — route calls through a Firebase Cloud Function proxy that holds the API key. Contrast with Algolia: Elasticsearch is more powerful and self-hostable but requires you to hand-build queries, manage a cluster, and add a proxy. Advanced difficulty.

What you'll learn

  • Why Elasticsearch API keys must stay behind a proxy and cannot safely ship in the FlutterFlow client
  • How to deploy a Firebase Cloud Function proxy that forwards FlutterFlow search requests to Elasticsearch
  • How to create a FlutterFlow POST API Call to your proxy and pass a search query as a variable
  • How to parse hits.hits[]._source from the Elasticsearch response into a FlutterFlow Data Type
  • How to debounce a search TextField to avoid hammering the Elasticsearch cluster on every keystroke
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced15 min read60 minutesDatabase & BackendLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Elasticsearch using a FlutterFlow API Call that POSTs Query DSL JSON to your index's /_search endpoint. Elasticsearch credentials must never ship in the client app — route calls through a Firebase Cloud Function proxy that holds the API key. Contrast with Algolia: Elasticsearch is more powerful and self-hostable but requires you to hand-build queries, manage a cluster, and add a proxy. Advanced difficulty.

Quick facts about this guide
FactValue
ToolElasticSearch
CategoryDatabase & Backend
MethodFlutterFlow API Call
DifficultyAdvanced
Time required60 minutes
Last updatedJuly 2026

Elasticsearch in FlutterFlow — the Proxy Pattern You Cannot Skip

Elasticsearch is the dominant choice when you need full-text search with advanced scoring, geospatial queries, faceted navigation, or analytics over your own data that you host or manage. It is self-hostable for free (Apache 2.0 license for the basic distribution) or available as a managed service on Elastic Cloud. Elasticsearch Cloud endpoints look like https://deployment.es.region.aws.elastic.cloud:443, and the self-hosted default is https://host:9200.

The critical difference between Elasticsearch and Algolia for FlutterFlow builds is the security model. Algolia ships a search-only API key that is intentionally designed to be safe in the client. Elasticsearch does not — its API keys and credentials grant access to your cluster, and a key that allows searches typically also allows index modification or data exfiltration if misused. You must never embed an Elasticsearch API key in a FlutterFlow API Call header or a Dart constant, because FlutterFlow compiles to a client app and that key ships to every user's device. The required pattern is a backend proxy: a Firebase Cloud Function holds the key in environment variables, receives search requests from the app, validates them, queries Elasticsearch, and returns only the result data.

The second consideration is CORS. Elasticsearch does not send CORS headers by default, so FlutterFlow's browser-based Run/Test mode will reject direct Elasticsearch calls with an XMLHttpRequest error. Native iOS and Android builds bypass browser CORS enforcement, but even on native you are still shipping the key in the app without the proxy. The proxy solves both problems: it lives on Google's servers (no key in the client), and Cloud Functions send CORS headers by default.

Integration method

FlutterFlow API Call

FlutterFlow has no native Elasticsearch panel. You communicate with Elasticsearch via its REST API, posting Query DSL (Domain Specific Language) JSON to the `/{index}/_search` endpoint. Because the Elasticsearch API key or cluster credentials are secrets, they must be kept server-side in a Firebase Cloud Function proxy — the FlutterFlow app calls the proxy, which forwards the search to Elasticsearch and returns only the results. Native mobile builds can call Elasticsearch directly if you use a read-only key and accept the risk, but the proxy pattern is strongly recommended for any production app and is required for web builds due to CORS.

Prerequisites

  • An Elasticsearch cluster — either self-hosted or on Elastic Cloud — with at least one index containing data to search
  • An Elasticsearch API key scoped to read-only access on the specific index (created in Kibana → Security → API Keys or via the Elasticsearch API)
  • A Firebase project on the Blaze plan (required for Cloud Functions) connected to your FlutterFlow project
  • A FlutterFlow project with Firebase already configured
  • Basic familiarity with Elasticsearch Query DSL (match, bool, multi_match) — or a willingness to use the Elasticsearch documentation to build queries

Step-by-step guide

1

Create a read-only, index-scoped Elasticsearch API key

Before writing any code, create an API key in Elasticsearch that has the minimum permissions your search feature needs. In Elastic Cloud, go to Kibana → Management → Security → API Keys and click 'Create API key.' Give it a name like 'flutterflow-search-proxy'. Under 'Index privileges,' specify the index name (for example 'products') and set the privileges to 'read' only — do not grant 'write', 'manage', or cluster-level permissions. Click 'Create' and copy the encoded key (the format is `base64(id:api_key)` and the header value is `ApiKey <encoded-key>`). For self-hosted Elasticsearch, create the key via the REST API: POST `/_security/api_key` with role descriptors limiting to index read. Store this key securely — you will add it to your Cloud Function as an environment variable. Never paste it into FlutterFlow. The entire purpose of this step is to minimize blast radius if the key were ever to be exposed: a read-only, index-scoped key cannot delete data, modify mappings, or access other indexes.

create_api_key.json
1POST /_security/api_key
2{
3 "name": "flutterflow-search-proxy",
4 "role_descriptors": {
5 "search_only": {
6 "cluster": [],
7 "index": [
8 {
9 "names": ["products"],
10 "privileges": ["read"]
11 }
12 ]
13 }
14 }
15}

Pro tip: Even a read-only API key can expose sensitive data if your index contains private information. Use the proxy pattern regardless — the key should live only in Firebase, not in the app.

Expected result: Kibana shows the new API key in the API Keys list with status 'Active'. You have copied the encoded key string for use in the next step.

2

Deploy a Firebase Cloud Function proxy for Elasticsearch

In the Firebase console, go to Functions. If this is your first function, click 'Get Started' and follow the setup wizard. You will write the function in Node.js. The function accepts a POST request from FlutterFlow containing the user's search query, validates the caller is authenticated (using the Firebase ID token from the request Authorization header), builds the Elasticsearch Query DSL, calls the Elasticsearch `/_search` endpoint with your stored API key, and returns the results. Create the function as an HTTPS callable (functions.https.onCall) so Firebase Auth tokens are validated automatically. Store your Elasticsearch API key in the function's environment by going to Firebase console → Functions → your function → Environment variables and adding `ES_URL` (your cluster endpoint) and `ES_API_KEY` (the encoded key from Step 1). Deploy the function from the Firebase console's inline editor or via the Firebase CLI. The function should return results in a clean format — an array of objects from hits[].source — rather than the raw Elasticsearch response, which has nested metadata your FlutterFlow widgets do not need.

index.js
1// Firebase Cloud Function: Elasticsearch search proxy
2// Runtime: Node.js 20
3const functions = require('firebase-functions');
4const fetch = require('node-fetch');
5
6exports.searchProducts = functions.https.onCall(async (data, context) => {
7 // Reject unauthenticated callers
8 if (!context.auth) {
9 throw new functions.https.HttpsError(
10 'unauthenticated',
11 'Must be logged in to search.'
12 );
13 }
14
15 const query = data.query || '';
16 const esUrl = process.env.ES_URL; // e.g. https://my-cluster.es.us-east-1.aws.elastic.cloud:443
17 const esKey = process.env.ES_API_KEY; // ApiKey <encoded-key>
18 const indexName = 'products';
19
20 const esResponse = await fetch(`${esUrl}/${indexName}/_search`, {
21 method: 'POST',
22 headers: {
23 'Content-Type': 'application/json',
24 'Authorization': `ApiKey ${esKey}`
25 },
26 body: JSON.stringify({
27 size: 10,
28 query: {
29 multi_match: {
30 query: query,
31 fields: ['title^3', 'description'],
32 fuzziness: 'AUTO'
33 }
34 }
35 })
36 });
37
38 if (!esResponse.ok) {
39 const errText = await esResponse.text();
40 throw new functions.https.HttpsError('internal', `Elasticsearch error: ${errText}`);
41 }
42
43 const esData = await esResponse.json();
44
45 // Return only the source fields — not Elasticsearch metadata
46 const results = esData.hits.hits.map(hit => ({
47 id: hit._id,
48 ...hit._source
49 }));
50
51 return { results };
52});

Pro tip: After deploying, test the function directly in the Firebase console using the 'Test function' feature with a sample payload like `{ query: 'running shoes' }` before connecting it to FlutterFlow.

Expected result: The Firebase console shows the function deployed and active. Testing it returns a JSON object with a 'results' array of matching documents from your Elasticsearch index.

3

Create a FlutterFlow API Group and POST Call to the Cloud Function proxy

In your FlutterFlow project, click 'API Calls' in the left navigation toolbar. Click '+ Add' → 'Create API Group.' Name it 'ElasticsearchProxy.' For the base URL, use your Firebase Cloud Function callable endpoint — the format is `https://<region>-<project-id>.cloudfunctions.net`. Click 'Save.' Inside the group, click '+ Add' → 'Create API Call.' Name it 'SearchProducts.' Set method to POST. Set the endpoint to `/searchProducts` (matching the function name you deployed). Click 'Variables' and add a variable named `query` (String, required). Click 'Body' and set type to JSON with the body: `{ "data": { "query": "{{query}}" } }` — note the `data` wrapper, which is required by Firebase callable functions. Add a 'Content-Type: application/json' header. Firebase callable functions also require that you pass the user's ID token for authentication. In FlutterFlow, add a header variable named `Authorization` with the value 'Bearer ' followed by the Firebase current user's ID token (available in the variable picker under Authenticated User → ID Token). Click 'Response & Test,' enter a test query, and run the test. If the function validates auth, you may need to pass a real Firebase test token — alternatively, temporarily disable the auth check in your function for initial testing, then re-enable it. Click 'Generate JSON Paths' to extract the results array path: `$.results` (Array of Objects). Define a FlutterFlow custom Data Type matching your search result fields (title, description, id, etc.) and map the JSON path to that type.

api_call_config.json
1{
2 "api_group": "ElasticsearchProxy",
3 "base_url": "https://<region>-<project-id>.cloudfunctions.net",
4 "calls": [
5 {
6 "name": "SearchProducts",
7 "method": "POST",
8 "endpoint": "/searchProducts",
9 "headers": [
10 { "name": "Content-Type", "value": "application/json" }
11 ],
12 "variables": [
13 { "name": "query", "type": "String", "required": true }
14 ],
15 "body": {
16 "data": { "query": "{{query}}" }
17 },
18 "response_paths": [
19 { "field": "results", "path": "$.result.results", "type": "List<SearchResult>" }
20 ]
21 }
22 ]
23}

Pro tip: Firebase callable functions wrap their response in a 'result' key on the client side, so the actual JSON path to your results array is `$.result.results`, not `$.results` directly.

Expected result: The API Call test in FlutterFlow returns a 200 response with the results array. JSON Paths are generated. You can see the search results from your Elasticsearch index in the response panel.

4

Parse hits into a Data Type and bind to a ListView with debounced search

In FlutterFlow, go to App Settings → Custom Data Types and define a 'SearchResult' type with fields matching your Elasticsearch document source (for example: id String, title String, description String, price Double). Back in your API Call's Response tab, map the `$.result.results` array to a 'SearchResult' list type. Now build the search UI. Add a TextField widget for user input and a ListView below it. Click the ListView → Backend Query → '+ Add Query' → 'API Call' → select 'ElasticsearchProxy → SearchProducts.' Map the `query` variable to the TextField's value. This creates a query that re-fires whenever the variable changes. To avoid firing a search on every single keystroke — which would flood your Elasticsearch cluster and Firebase Function with requests — use FlutterFlow's debounce pattern: do not bind the search directly to the TextField's onChange. Instead, create a page variable (String, default empty) to hold the current search term. On the TextField's onChange action, use 'Update Page State' to set this variable. Then bind the ListView query to the page state variable using a slight delay via the Action Flow Editor's 'Wait' action (200-300ms) before updating the state. Inside the ListView, add your UI for each result: a Card with Text widgets bound to result.title and result.description, using the 'Item from Repeating Widget' source in the variable picker.

Pro tip: For a smoother search experience, add an empty state widget to the ListView (configured in the ListView's Properties panel) that shows 'Type to search...' when the query variable is empty, so the list is not visually empty on page load.

Expected result: Typing in the search field triggers the Cloud Function → Elasticsearch query after the debounce delay. The ListView populates with matching result cards. Clearing the field returns to the empty state.

5

Test on device and handle CORS for web builds

FlutterFlow's browser-based Run mode enforces CORS. Firebase Cloud Functions (HTTPS callable) include CORS headers automatically for calls from Firebase-authenticated clients, so calling a `functions.https.onCall` function from FlutterFlow's Run mode should work without additional CORS configuration. However, if you switch to a plain `functions.https.onRequest` function instead, you must add CORS headers manually using the `cors` npm package. Test your integration in Run mode first. If you see 'XMLHttpRequest error' or a CORS-related failure in the browser console, check that you are using `onCall` (not `onRequest`) or add the cors middleware. For native iOS/Android builds, CORS does not apply — test those with FlutterFlow's Test Mode or by downloading an APK/TestFlight build. Note: custom actions do not run in FlutterFlow's Run/Test preview — but this integration uses only API Calls, which do run in all preview modes.

Pro tip: If you need to use RapidDev's team to help with complex Elasticsearch query design (aggregations, nested queries, geo filters), a free scoping call is available at rapidevelopers.com/contact. Getting Query DSL right for advanced use cases takes experimentation that is easier to walk through with a developer.

Expected result: Searching works in FlutterFlow's Run mode without CORS errors. On a native device test, results load quickly and the debounce prevents excessive calls to the Cloud Function.

Common use cases

Real estate app with full-text property search and location filters

A FlutterFlow property-search app lets users search by neighborhood name, number of bedrooms, and price range. A Cloud Function proxy translates the app's structured inputs into an Elasticsearch bool query with match, range, and geo_distance clauses. Results flow back into a ListView showing matching properties with photos and prices.

FlutterFlow Prompt

Build a property search screen where users can type a neighborhood name and filter by price range and number of bedrooms. Results should appear as a list of property cards that update as the user types.

Copy this prompt to try it in FlutterFlow

Knowledge base app with fuzzy full-text article search

A FlutterFlow help-center app lets support agents search a large indexed knowledge base. A multi_match query across title and body fields with fuzziness enabled handles typos. The Cloud Function proxy returns only the top 10 hits' title, summary, and URL fields — the full article body never leaves the server.

FlutterFlow Prompt

Build a search screen where support agents can search articles by typing keywords. The search should be forgiving of typos and show the top 10 matching article titles and summaries.

Copy this prompt to try it in FlutterFlow

E-commerce app with product search and category facets

A FlutterFlow shopping app uses Elasticsearch's aggregations to power a search screen with live category counts (how many results in each category). The Cloud Function proxy runs a combined search + aggregation query, returning hits and facet counts in one response. FlutterFlow binds the hits to a ListView and the facet counts to a filter chip row.

FlutterFlow Prompt

Build a product search screen where users can search by name and filter by category. Show how many items exist in each category next to the category filter chips, and update the list as the user types.

Copy this prompt to try it in FlutterFlow

Troubleshooting

XMLHttpRequest error when testing the Elasticsearch API Call in FlutterFlow's browser Run mode

Cause: Elasticsearch does not send CORS headers by default. Even through a Cloud Function proxy, a misconfigured function type (onRequest without cors middleware) will fail in browser-based testing.

Solution: Use Firebase `functions.https.onCall` (callable functions) instead of `functions.https.onRequest`. Callable functions handle CORS automatically for authenticated Firebase clients. If you are already using onCall and still seeing CORS errors, verify that the function is deployed and the endpoint URL in your FlutterFlow API Group matches the actual function URL exactly.

Elasticsearch returns a 400 error with 'json_parse_exception' or 'parsing_exception'

Cause: The Query DSL JSON body sent to Elasticsearch is malformed — a missing bracket, a typo in a field name, or an invalid query structure.

Solution: Test your Query DSL directly in Kibana's Dev Tools console (or using curl) before adding it to the Cloud Function. Elasticsearch's 400 response includes a detailed 'reason' field in the JSON that pinpoints the parsing error. Fix the query in Kibana first, then paste the verified JSON into your Cloud Function.

Search ListView is empty even though documents exist in the Elasticsearch index

Cause: The JSON path used to extract results is wrong — typically because Firebase callable functions wrap the response in a 'result' key, making the actual path `$.result.results` instead of `$.results`.

Solution: In FlutterFlow's API Call → Response & Test, look at the raw response JSON. You should see a structure like `{ "result": { "results": [...] } }`. Update your JSON path to `$.result.results` to reach the array. If the array is empty but the Cloud Function returns data, check that the Elasticsearch query matches the actual field names in your index mapping.

Elasticsearch cluster gets too many requests during typing — slow responses or rate errors

Cause: The search query fires on every keystroke without debouncing, sending dozens of requests per second to the Cloud Function and Elasticsearch cluster.

Solution: Implement the debounce pattern described in Step 4 — use a page state variable and a 200-300ms wait before triggering the API Call. Alternatively, only trigger the search action when the user stops typing for 300ms or taps a Search button, depending on your UX preference.

Best practices

  • Never embed an Elasticsearch API key in a FlutterFlow API Call header — always route through a Firebase Cloud Function proxy that holds the key in environment variables.
  • Create a read-only, index-scoped API key for your proxy instead of using the elastic superuser credentials — this limits damage if the function's environment is ever compromised.
  • Always debounce search input in FlutterFlow (200-300ms minimum) before firing the Cloud Function call — a live search box without debouncing can generate hundreds of requests per minute per active user.
  • Use multi_match with fuzziness: 'AUTO' for user-facing search fields to handle typos without building complex query logic in FlutterFlow.
  • Return only the fields your FlutterFlow widgets actually display from the Cloud Function — use Elasticsearch's _source includes parameter to limit field return and reduce data transfer.
  • For web builds, use Firebase callable functions (onCall) as the proxy — they handle CORS automatically, unlike Elasticsearch's direct API which does not send CORS headers.
  • Compare Elasticsearch against Algolia before committing: if you do not need self-hosted control, real-time indexing from your own data pipeline, or advanced aggregations, Algolia offers instant search with a client-safe search key and no proxy required.

Alternatives

Frequently asked questions

Can I call Elasticsearch directly from FlutterFlow without a proxy if I use a read-only API key?

On native iOS/Android builds, you can technically make the call — browser CORS is not enforced on native. However, even a read-only key embedded in the compiled app can be extracted by a determined user, giving them unlimited search access to your index (and potentially exposing sensitive data in those documents). The proxy pattern is strongly recommended for any production app. If you accept the risk for a low-sensitivity dataset, use an index-scoped read-only key and monitor your cluster for unusual query patterns.

What is Query DSL and do I need to know it to use Elasticsearch with FlutterFlow?

Query DSL (Domain Specific Language) is Elasticsearch's JSON-based query language. You write queries like `{ 'query': { 'match': { 'title': 'search term' } } }` to describe what you want to find. You need basic familiarity with at least `match`, `multi_match`, and `bool` queries to build a working search feature. Kibana's Dev Tools console is the best place to experiment with queries interactively — test your query there first, then move it into your Cloud Function.

How is Elasticsearch different from Algolia for FlutterFlow apps?

Algolia ships a search-only API key that is intentionally safe to put in your FlutterFlow client — no proxy needed. Elasticsearch has no equivalent client-safe key, so a proxy is mandatory. Algolia is faster to set up and has built-in typo tolerance and ranking; Elasticsearch gives you more control over query logic, supports geospatial and analytics queries, and can be self-hosted for free. For most FlutterFlow apps that just need fast text search, Algolia is the lower-effort choice.

How do I keep my Elasticsearch index in sync with data in Firestore?

Use a Firebase Cloud Function triggered by Firestore writes (onCreate, onUpdate, onDelete) to push changes to your Elasticsearch index. The trigger fires whenever a document changes and the function indexes the new data into Elasticsearch using the Admin API key (stored in Function environment variables). This keeps Elasticsearch as the read-optimized search layer while Firestore remains the source of truth.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.