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

Cloudflare

Connect FlutterFlow to Cloudflare by calling a Cloudflare Worker HTTPS endpoint from a FlutterFlow API Call group. The Worker is your secure backend — it holds API keys, handles R2 uploads via presigned URLs, and exposes D1 SQLite data as REST routes. There is no direct Flutter SDK for R2 or D1; the Worker proxy is mandatory. DNS and custom domain setup happens entirely in the Cloudflare dashboard.

What you'll learn

  • How to deploy a Cloudflare Worker as a backend proxy for your FlutterFlow app
  • How to upload files to Cloudflare R2 using Worker-generated presigned URLs
  • How to expose Cloudflare D1 data to FlutterFlow as REST endpoints
  • Why all Cloudflare secrets must live in Worker variables, not in FlutterFlow
  • How to point your custom domain's DNS at your published FlutterFlow web app using Cloudflare
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read40 minutesDatabase & BackendLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Cloudflare by calling a Cloudflare Worker HTTPS endpoint from a FlutterFlow API Call group. The Worker is your secure backend — it holds API keys, handles R2 uploads via presigned URLs, and exposes D1 SQLite data as REST routes. There is no direct Flutter SDK for R2 or D1; the Worker proxy is mandatory. DNS and custom domain setup happens entirely in the Cloudflare dashboard.

Quick facts about this guide
FactValue
ToolCloudflare
CategoryDatabase & Backend
MethodFlutterFlow API Call
DifficultyIntermediate
Time required40 minutes
Last updatedJuly 2026

Using Cloudflare Workers as the Backend for Your FlutterFlow App

When founders talk about 'connecting FlutterFlow to Cloudflare,' they typically have one of two goals: using Cloudflare as a backend (Workers + R2 + D1) or using Cloudflare as DNS/CDN for their published FlutterFlow web app. Both are valid — and this guide covers the backend use case in depth, with a note on DNS at the end.

A Cloudflare Worker is a serverless JavaScript function that runs at Cloudflare's global edge network — meaning it responds fast regardless of where your users are. For a FlutterFlow app, the Worker serves as your secure proxy: it holds secrets that FlutterFlow cannot safely store (API keys, R2 credentials, database credentials), exposes clean REST endpoints that your API Calls can reach, and returns the JSON that FlutterFlow widgets display. R2 object storage (Cloudflare's zero-egress alternative to AWS S3) and D1 (a serverless SQLite database) are only accessible through Worker bindings — there are no client SDKs for Flutter.

On pricing, Cloudflare's free tier is genuinely generous for a bootstrapped app: 100,000 Worker requests per day, 5 GB of D1 storage, and 10 GB of R2 storage — all at no cost. The paid Workers plan starts at $5 per month for 10 million requests. R2 charges $0.015 per GB per month for storage and has zero egress fees, which makes it significantly cheaper than S3 or Azure Blob for file-heavy apps. Check Cloudflare's pricing page for current limits before going to production.

Integration method

FlutterFlow API Call

FlutterFlow communicates with Cloudflare through a Worker you deploy as a lightweight HTTPS backend. The Worker holds all secrets (R2 credentials, third-party API keys) via Wrangler environment variables, exposes routes for D1 CRUD operations, and generates presigned PUT URLs for R2 uploads. FlutterFlow adds the Worker URL as an API Call group and calls each route as a standard REST request. DNS and SSL for your published FlutterFlow web app are configured separately in the Cloudflare dashboard.

Prerequisites

  • A FlutterFlow project created and open in the browser
  • A Cloudflare account (free tier works — sign up at cloudflare.com)
  • A deployed Cloudflare Worker with at least one HTTP route (can be created in the Cloudflare dashboard without any CLI)
  • Basic understanding of FlutterFlow's API Calls panel
  • Your Worker URL (e.g., https://my-worker.your-subdomain.workers.dev)

Step-by-step guide

1

Deploy a Cloudflare Worker using the dashboard editor

You do not need the Wrangler CLI to create a Worker — the Cloudflare dashboard has a browser-based editor that non-technical founders can use. Log into your Cloudflare account at dash.cloudflare.com, click Workers & Pages in the left sidebar, then click Create application → Create Worker. Give it a name (e.g., 'flutterflow-backend') and click Deploy — Cloudflare generates a starter Worker at https://flutterflow-backend.your-subdomain.workers.dev instantly. Click Edit code to open the browser editor. Replace the starter code with your application logic: define routes for GET /items, POST /items, GET /upload-url, etc. Use the request.url and request.method properties to dispatch to the correct handler. All secrets (R2 credentials, third-party API keys) must be added as environment variables through Settings → Variables and Secrets — not hardcoded in the Worker source code. Worker secrets are encrypted at rest and injected at runtime as env.SECRET_NAME. For D1 databases, go back to Workers & Pages → D1, create a new database, then bind it to your Worker via the Worker's Settings → Bindings → D1 Database → Add binding. The binding name (e.g., 'DB') becomes available as env.DB inside your Worker. Run SQL migrations using the D1 console in the dashboard to create your tables before testing.

worker.js
1// Cloudflare Worker — example with D1 and R2 routes
2export default {
3 async fetch(request, env) {
4 const url = new URL(request.url);
5
6 // CORS headers for FlutterFlow web preview
7 const corsHeaders = {
8 'Access-Control-Allow-Origin': '*',
9 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
10 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
11 };
12
13 if (request.method === 'OPTIONS') {
14 return new Response(null, { headers: corsHeaders });
15 }
16
17 if (url.pathname === '/items' && request.method === 'GET') {
18 const { results } = await env.DB.prepare('SELECT * FROM items ORDER BY created_at DESC LIMIT 50').all();
19 return Response.json({ items: results }, { headers: corsHeaders });
20 }
21
22 if (url.pathname === '/items' && request.method === 'POST') {
23 const body = await request.json();
24 await env.DB.prepare('INSERT INTO items (name, value) VALUES (?, ?)').bind(body.name, body.value).run();
25 return Response.json({ success: true }, { headers: corsHeaders });
26 }
27
28 return new Response('Not found', { status: 404 });
29 }
30};

Pro tip: Add the CORS preflight handler (OPTIONS method) from day one — FlutterFlow's web preview sends preflight requests before every API call, and the Worker must respond with 200 to them.

Expected result: Your Worker is live at https://flutterflow-backend.your-subdomain.workers.dev. Visiting /items in a browser returns a JSON response from your D1 database.

2

Store secrets as Worker environment variables

Any API key, database credential, or private token your Worker needs must be stored in Worker environment variables — never in the Worker source code or in FlutterFlow. In the Cloudflare dashboard, navigate to Workers & Pages → your Worker → Settings → Variables and Secrets. Click Add variable for non-sensitive configuration values (like a public API base URL) and Add secret for sensitive values (like a third-party API key or an R2 access key ID). Secrets are encrypted and not visible after saving — even Cloudflare employees cannot read them. Inside your Worker code, access these values through the env parameter: env.MY_API_KEY, env.R2_BUCKET (the R2 binding), env.DB (the D1 binding). When you make a request from FlutterFlow to your Worker, the Worker reads these values at runtime and uses them to call downstream services. This means FlutterFlow never needs to know your third-party API keys — it only needs the Worker URL, which is public by design. For R2, add an R2 bucket binding rather than using access keys: in the Worker's Settings → Bindings → R2 Bucket → Add binding. Name it 'BUCKET' and select your R2 bucket. The Worker then accesses R2 via env.BUCKET.put(), env.BUCKET.get(), etc. — no credentials needed in code because the binding handles authentication automatically.

Pro tip: After adding secrets in the Cloudflare dashboard, redeploy the Worker (click Save and deploy in the editor) for the new secrets to take effect. Cloudflare does not hot-reload secrets without a redeploy.

Expected result: Your Worker's Settings → Variables page shows your secrets listed (values hidden). The Worker source code references them as env.VARIABLE_NAME with no hardcoded strings.

3

Add the Worker URL as a FlutterFlow API Call group

Now configure FlutterFlow to talk to your Worker. In the FlutterFlow editor, click API Calls in the left navigation panel, then click + Add → Create API Group. Name it 'Cloudflare Worker' and set the Base URL to your Worker URL: https://flutterflow-backend.your-subdomain.workers.dev (or your custom domain if you've configured one). Under Headers, add 'Content-Type: application/json'. If you've implemented JWT-based authentication in your Worker, add an 'Authorization' header with the value 'Bearer {{userToken}}' — FlutterFlow will substitute the actual token at runtime from a page variable. Inside the API Group, click + Add API Call for each route your Worker exposes. For the GetItems call: Method = GET, Path = /items. For the CreateItem call: Method = POST, Path = /items, and in the Body tab add the JSON fields your Worker expects (e.g., { "name": "{{itemName}}", "value": "{{itemValue}}" }). Click the Variables tab on each call to define the variables that FlutterFlow will substitute at runtime (itemName, itemValue, etc.). Switch to the Response & Test tab on GetItems, paste a sample response from your Worker (e.g., {"items": [{"id": 1, "name": "Test", "value": "ABC"}]}), and click Generate JSON Paths. FlutterFlow will automatically extract $.items as the list path and $.items[0].name, $.items[0].value as field paths. These paths are what you'll use when binding the API Call to a Data Type and ListView.

typescript
1// Sample API Call config
2{
3 "group_name": "Cloudflare Worker",
4 "base_url": "https://flutterflow-backend.your-subdomain.workers.dev",
5 "headers": {
6 "Content-Type": "application/json",
7 "Authorization": "Bearer {{userToken}}"
8 },
9 "calls": [
10 { "name": "GetItems", "method": "GET", "path": "/items" },
11 { "name": "CreateItem", "method": "POST", "path": "/items",
12 "body": { "name": "{{itemName}}", "value": "{{itemValue}}" } },
13 { "name": "GetUploadUrl", "method": "GET", "path": "/upload-url?filename={{filename}}" }
14 ]
15}

Pro tip: Test every API Call in the Response & Test tab before building widgets. Paste your actual Worker URL and real variable values — this catches CORS issues and JSON path mismatches early.

Expected result: The API Calls panel shows your Cloudflare Worker group with all routes listed. Each call returns a 200 response with the expected JSON in the test tab.

4

Wire an R2 file upload via Worker-signed presigned URL

Uploading to Cloudflare R2 from FlutterFlow follows the same two-step pattern as Blob Storage and S3: first get a presigned URL from your Worker, then PUT the file bytes directly to R2. Add a Worker route that generates a presigned R2 PUT URL using the R2 bucket binding. The Worker calls env.BUCKET.createMultipartUpload() or uses R2's S3-compatible API to generate a signed URL with a short expiry, then returns it as JSON. In FlutterFlow, build the upload flow in the Action Flow Editor. Start with an 'Upload Photo/Video' action to get the file into a local state variable, then add an API Call action to GetUploadUrl (passing the filename as a variable), storing the returned presigned URL in a page state variable. Finally, add a Custom Action (Dart) that performs an http.put() to the presigned URL with the file bytes from the local variable. The presigned URL is time-limited and scope-limited — it is safe to use in Dart. After a successful upload, call a third Worker route to record the file's R2 URL in your D1 database so you can display it in the app later. For displaying R2 files, your Worker can return the R2 public URL (if your bucket has public access enabled) or generate a short-lived signed read URL. For most apps, enabling public read access on the R2 bucket is the simplest approach — files are still only writable through the Worker.

worker.js
1// Worker route: generate R2 presigned upload URL
2import { AwsClient } from 'aws4fetch'; // R2 is S3-compatible
3
4async function handleUploadUrl(request, env) {
5 const url = new URL(request.url);
6 const filename = url.searchParams.get('filename');
7 const accountId = env.CF_ACCOUNT_ID;
8 const accessKeyId = env.R2_ACCESS_KEY_ID;
9 const secretAccessKey = env.R2_SECRET_ACCESS_KEY;
10 const bucketName = env.R2_BUCKET_NAME;
11
12 const client = new AwsClient({ accessKeyId, secretAccessKey });
13 const r2Url = `https://${accountId}.r2.cloudflarestorage.com/${bucketName}/${filename}`;
14 const presigned = await client.sign(new Request(r2Url, { method: 'PUT' }), {
15 aws: { signQuery: true },
16 datetime: new Date().toISOString().replace(/[:-]|\..+/g, ''),
17 expiresIn: 600 // 10 minutes
18 });
19
20 return Response.json({ uploadUrl: presigned.url });
21}

Pro tip: Enable R2 bucket public access for read-only operations so FlutterFlow Image widgets can load files directly from R2 URLs without going through the Worker on every request.

Expected result: Tapping the upload button generates a presigned URL via the Worker, the file uploads to R2, and the file appears in the Cloudflare dashboard under R2 → your bucket.

5

Configure Cloudflare DNS for your published FlutterFlow web app

If you want users to reach your FlutterFlow web app at a custom domain (e.g., app.yourcompany.com) and you use Cloudflare for DNS, the setup is entirely in the Cloudflare dashboard — no changes in FlutterFlow are needed for this step. First, publish your FlutterFlow app: click the Publish icon (top-right) → Publish → wait for the build to complete. FlutterFlow gives you a URL like https://yourapp.flutterflow.app. FlutterFlow custom domains require a paid FlutterFlow plan; you configure the custom domain in FlutterFlow's Settings → Custom Domain section by adding a CNAME record. Cloudflare is where you create that CNAME record: go to your Cloudflare account → select your domain → DNS → Records → + Add record. Set Type = CNAME, Name = app (for app.yourcompany.com), Target = the value FlutterFlow instructs you to use. Enable the orange cloud (Cloudflare proxy) to get SSL and CDN benefits automatically. After DNS propagation (usually under 5 minutes with Cloudflare), your app is live at your custom domain. This DNS configuration is completely separate from your Worker — the Worker is your backend, the DNS record points to FlutterFlow's hosting.

Pro tip: If you have Cloudflare's SSL/TLS mode set to 'Full (Strict)' and your FlutterFlow published app has a valid SSL certificate (it does, by default), the CNAME + proxy setup will work without any extra configuration.

Expected result: Your FlutterFlow web app loads at your custom domain (e.g., app.yourcompany.com) with Cloudflare's CDN and SSL active. DNS propagation is typically near-instant when Cloudflare is your nameserver.

Common use cases

Portfolio app with photo gallery stored in Cloudflare R2

A FlutterFlow app where creative professionals upload high-resolution photos. FlutterFlow calls a Worker endpoint to get a presigned PUT URL, uploads the file directly to R2, and then calls a second Worker route to save the file metadata in D1. A GridView widget fetches and displays the gallery from D1.

FlutterFlow Prompt

Build a portfolio app where users can upload photos and organize them into galleries. Photos are stored in Cloudflare R2 and the gallery metadata (title, description, file URL) is stored in a D1 database.

Copy this prompt to try it in FlutterFlow

Internal tools app with D1-backed data and Worker business logic

A company's internal Flutter app for logging field visits, submitting forms, and viewing team reports. All data lives in a D1 SQLite database, accessed through Worker routes that enforce authentication via a JWT checked server-side. FlutterFlow's API Calls hit the Worker's /visits, /forms, and /reports endpoints.

FlutterFlow Prompt

Create an internal operations app where field technicians can log site visits, upload photos, and view team activity reports — all backed by a Cloudflare D1 database and secured through a Cloudflare Worker.

Copy this prompt to try it in FlutterFlow

API aggregation app using Worker as a multi-service proxy

A FlutterFlow app that pulls data from multiple third-party APIs (weather, maps, financial data) through a single Cloudflare Worker endpoint. The Worker holds all the third-party API keys securely and returns a unified JSON response, so FlutterFlow only needs one API Call group.

FlutterFlow Prompt

Build a dashboard app that combines real estate data, weather conditions, and neighborhood statistics into a single view — using a Cloudflare Worker to proxy and aggregate from multiple APIs.

Copy this prompt to try it in FlutterFlow

Troubleshooting

XMLHttpRequest error or CORS error in FlutterFlow web preview when calling Worker

Cause: The Worker does not include CORS headers, so the browser blocks the response. FlutterFlow's web preview runs at app.flutterflow.io, which is a different origin than your Worker.

Solution: Add CORS headers to every response your Worker sends, and handle the OPTIONS preflight request. In your Worker, include 'Access-Control-Allow-Origin': '*' (or specify https://app.flutterflow.io and your published domain). Add an OPTIONS handler that returns 200 with the CORS headers. See the example Worker code in Step 1.

typescript
1// Add to every Worker response:
2const corsHeaders = {
3 'Access-Control-Allow-Origin': '*',
4 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
5 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
6};
7if (request.method === 'OPTIONS') return new Response(null, { headers: corsHeaders });

Worker returns 200 but the response takes 2–3 seconds and sometimes times out

Cause: The Worker is hitting the free tier's 10ms CPU time limit on a CPU-intensive operation (e.g., complex JSON aggregation, encryption). The request itself may have completed, but CPU-heavy synchronous work causes a timeout error.

Solution: Move heavy computation to async operations where possible. For Workers on the free plan, the 10ms CPU limit applies per request — paid Workers plans raise this significantly. Break large D1 queries into smaller batches. If the timeout is from a downstream API call (not CPU), the Worker has no built-in timeout limit on fetch() — the downstream service is slow.

D1 data does not appear in FlutterFlow widgets despite the Worker returning 200

Cause: The JSON path in FlutterFlow does not match the response structure. D1 queries return results under a 'results' key by default; if your Worker wraps them differently (e.g., 'items'), the path must match.

Solution: Open the API Call → Response & Test tab → paste the actual Worker response → click Generate JSON Paths. Verify the list path matches your Worker's response envelope (e.g., $.items or $.results). Update the Data Type mapping to use the correct paths. Check that the Data Type field names match exactly — FlutterFlow path matching is case-sensitive.

R2 upload fails with 403 Forbidden even with a presigned URL

Cause: The presigned URL has expired (they are short-lived by design), or the PUT request is missing the correct Content-Type header that was included when the URL was signed.

Solution: Ensure the upload happens within the presigned URL's expiry window (request a new one if the user took more than 10 minutes to select a file). In the Dart custom action that performs the PUT, include the same Content-Type header that was used when signing the URL (typically 'application/octet-stream' or the file's MIME type).

Best practices

  • Never put Cloudflare API tokens, R2 access keys, or D1 credentials in FlutterFlow — store all secrets as Worker environment variables (encrypted secrets, not plain variables).
  • Always handle the OPTIONS preflight in your Worker from day one — FlutterFlow's web preview sends preflight requests before every API call.
  • Use R2 bucket bindings in your Worker instead of S3-compatible access keys where possible — bindings are zero-credential and simpler to configure.
  • Set presigned R2 upload URLs to expire in 10–15 minutes — short-lived URLs minimize the impact if a URL is intercepted.
  • Monitor your Worker's request count in the Cloudflare dashboard before going to production — the free tier's 100k requests/day can be exhausted quickly by a viral app.
  • For D1, use parameterized queries (env.DB.prepare().bind()) rather than string concatenation — this prevents SQL injection even though D1 is accessed through a Worker.
  • Enable R2 public read access for static assets (images, videos) so FlutterFlow Image widgets load content directly from R2 without routing through the Worker on every view.
  • If you need Cloudflare Turnstile (bot protection) on a FlutterFlow web build, implement the token verification in the Worker and have FlutterFlow pass the Turnstile token as a request parameter — contact RapidDev if the widget integration is proving complex.

Alternatives

Frequently asked questions

Do I need the Wrangler CLI to deploy a Cloudflare Worker for FlutterFlow?

No. Cloudflare's browser-based dashboard editor lets you write, test, and deploy Workers entirely in the browser — no terminal or CLI required. The dashboard editor is perfect for non-technical founders. The Wrangler CLI is optional and mainly useful for version-controlled deployments with a local development workflow.

Can FlutterFlow access D1 or R2 directly without a Worker?

No. Cloudflare D1 and R2 do not have client-side SDKs for Flutter. They are only accessible through Worker bindings (server-side JavaScript). The Worker acts as the required middleware — it queries D1 and returns JSON to FlutterFlow, or generates presigned R2 URLs for file operations. This is by design and aligns with the security model: D1 and R2 credentials never reach the client.

Will my FlutterFlow app work on iOS and Android if it uses a Cloudflare Worker backend?

Yes. Native iOS and Android builds do not enforce CORS (the OS makes HTTP calls directly, not through a browser), so they can call Worker endpoints without any special CORS configuration. However, CORS headers are still required for FlutterFlow's web preview and for your published web app, so it is best practice to include them in every Worker response regardless.

How do I secure my Worker endpoint so only my FlutterFlow app can call it?

The simplest approach is to generate a shared secret token and include it in the Worker as an environment secret, then pass it as a custom header (e.g., 'X-App-Token') from FlutterFlow's API Call headers. The Worker validates this header before processing any request. For apps with user authentication, have FlutterFlow pass the user's Firebase or Supabase auth token, and validate it in the Worker.

Does Cloudflare have a free tier that is actually usable for a real app?

Yes — Cloudflare's free tier is genuinely useful for small to medium apps. You get 100,000 Worker requests per day, 5 GB of D1 storage, and 10 GB of R2 storage at no cost. The paid Workers plan ($5/month) increases this to 10 million requests per month. Most early-stage apps stay within the free tier for months. Check Cloudflare's current pricing page for the latest limits before you go to production.

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.