Connect FlutterFlow to Typeform using the FlutterFlow API Call method. The fastest path is embedding a Typeform share link in a WebView Custom Widget so users fill the form in-app, while a Firebase Cloud Function proxies the Typeform Responses API and forwards new submissions to Firestore. The Personal Access Token must never ship inside the compiled app.
| Fact | Value |
|---|---|
| Tool | Typeform |
| Category | Marketing |
| Method | FlutterFlow API Call |
| Difficulty | Intermediate |
| Time required | 45 minutes |
| Last updated | July 2026 |
Why Connect FlutterFlow to Typeform?
Typeform is one of the most conversion-friendly survey tools available, and founders frequently want it inside their FlutterFlow apps for onboarding flows, NPS surveys, or lead capture forms. There are two viable paths: embed the Typeform share link in a WebView so users complete the form without ever leaving the app, or pull completed responses via the Typeform Responses API and display them in an in-app dashboard. For most non-technical founders, the embed path is the fastest win — you get a polished, branded form experience in minutes.
The Typeform API uses a Personal Access Token for authentication, and this is where FlutterFlow's client-side nature creates a critical security constraint. Unlike a server-side tool like Retool, FlutterFlow compiles your app to native iOS, Android, or web code that runs entirely on the user's device. Any key placed in a FlutterFlow API Call header is embedded in the app binary and can be extracted with basic reverse-engineering tools. The Typeform Personal Access Token is account-wide — it can read, update, and delete all your forms — so it must never leave your server.
The correct architecture is straightforward: deploy a short Firebase Cloud Function that holds the token server-side and forwards keyword queries to api.typeform.com, then point your FlutterFlow API Call at that function's URL instead of at Typeform directly. For real-time new-response alerts, configure a Typeform webhook (in Typeform's own settings) to POST completed submissions to another Cloud Function endpoint that writes them into Firestore. Your FlutterFlow app then reads Firestore in real time with no polling needed. Typeform's free plan allows up to 10 responses per month; paid plans start at around $25 per month for the Basic tier.
Integration method
FlutterFlow connects to the Typeform REST API via an API Call group, but because the Typeform Personal Access Token grants full account write access, it must never be embedded in the compiled Flutter app. Instead, you deploy a Firebase Cloud Function that holds the token and proxies requests to api.typeform.com. FlutterFlow's API Call points at your Cloud Function URL, and completed form responses are pushed into Firestore in real time via Typeform's own webhook system.
Prerequisites
- A Typeform account (free tier gives 10 responses/month; paid plan needed for higher volumes)
- A Firebase project with Cloud Functions enabled (Blaze pay-as-you-go plan required for outbound HTTP calls)
- A FlutterFlow project on a plan that supports Custom Code (for the WebView widget path)
- Your Typeform Form ID, visible in the URL when you open a form (e.g., typeform.com/to/FORM_ID or the admin URL)
- Basic familiarity with FlutterFlow's API Calls panel and Action Flow Editor
Step-by-step guide
Generate a Typeform Personal Access Token and note your Form ID
Log in to your Typeform account and navigate to your account avatar in the top-right corner, then click Account → Personal tokens. Click Generate a new token, give it a descriptive name like 'FlutterFlow App', and copy the token immediately — Typeform only shows it once. Store it somewhere safe such as a password manager or your Firebase project's Secret Manager; you will add it to an environment variable in the next step, not to FlutterFlow itself. While you are in Typeform, open the form you want to connect and look at the URL in your browser's address bar. The alphanumeric string after `/to/` or in the admin URL path is your Form ID — for example, `abc12345`. Copy this as well. You will also need the form's share link (Share → Get the link) for the WebView embed path. Keep both the token and the Form ID in your notes but remember: the token stays on the server, the Form ID can go into FlutterFlow's App Values as a constant since it is not a secret. At this point you have everything Typeform-side that you need. The Personal Access Token grants full read and write access to every form on your account, which is exactly why it cannot be placed anywhere inside the compiled Flutter app. Even if you only intend to use it for read operations, a leaked write-capable token is a serious security risk.
Pro tip: If you manage multiple Typeform workspaces, make sure you are generating the token in the correct workspace — tokens are workspace-scoped.
Expected result: You have a Typeform Personal Access Token saved securely, a Form ID copied, and the form's public share link ready.
Embed the Typeform form in a WebView Custom Widget (fastest path)
The quickest way to let users fill out a Typeform inside your FlutterFlow app is a WebView. In FlutterFlow's left navigation panel click Custom Code, then click the + Add button and select Widget. Name it TypeformEmbed. In the Dependencies field add the package `webview_flutter` (use the latest stable version listed on pub.dev — FlutterFlow resolves the version for you when you enter the package name). In the widget's Dart code area paste a simple WebView widget that loads your form's public share URL. After saving the custom widget, navigate to your page in the FlutterFlow canvas, click the + Add Widget button, search for TypeformEmbed under Custom Widgets, and drag it onto the page. Set its width to the full page width and its height to fill the remaining space. In the widget's properties panel you can expose the form URL as a parameter so you can change forms without editing code. Important platform notes: the WebView widget does not run in FlutterFlow's browser-based Test Mode (Run Mode in the top-right). You must test the embed on a real iOS or Android device, or by using FlutterFlow's Local Run feature. On the web build, the WebView renders inside an iframe, which Typeform generally supports, but you may need to ensure your Typeform plan allows iframe embedding. Once a user completes the form, Typeform's own confirmation screen appears inside the WebView — you cannot intercept the completion event directly in Flutter without additional JavaScript bridge code.
1import 'package:flutter/material.dart';2import 'package:webview_flutter/webview_flutter.dart';34class TypeformEmbed extends StatefulWidget {5 const TypeformEmbed({6 super.key,7 this.width,8 this.height,9 required this.formUrl,10 });1112 final double? width;13 final double? height;14 final String formUrl;1516 @override17 State<TypeformEmbed> createState() => _TypeformEmbedState();18}1920class _TypeformEmbedState extends State<TypeformEmbed> {21 late final WebViewController _controller;2223 @override24 void initState() {25 super.initState();26 _controller = WebViewController()27 ..setJavaScriptMode(JavaScriptMode.unrestricted)28 ..loadRequest(Uri.parse(widget.formUrl));29 }3031 @override32 Widget build(BuildContext context) {33 return SizedBox(34 width: widget.width,35 height: widget.height,36 child: WebViewWidget(controller: _controller),37 );38 }39}Pro tip: Pass the form URL as a widget parameter rather than hardcoding it — this way you can reuse the widget across multiple Typeform forms in the same app.
Expected result: The TypeformEmbed widget appears in your Custom Code library and on your page canvas, loading the Typeform share link when tested on a physical device.
Deploy a Firebase Cloud Function as a secure proxy for the Typeform Responses API
Because the Typeform Personal Access Token must not be placed in FlutterFlow, you need a thin server-side proxy. In the Firebase console, go to your project and open the Cloud Functions section. You'll write and deploy a simple Node.js function that receives a form ID from your FlutterFlow app and returns the responses from Typeform's API, injecting the secret Bearer token on the server side. In the Firebase Functions dashboard (or locally using the Firebase CLI if you prefer), create a new HTTP-triggered function. The function reads the `TYPEFORM_TOKEN` from Firebase's environment config or Secret Manager — never hardcode it in the source file. It then makes a GET request to `https://api.typeform.com/forms/{form_id}/responses` with the Authorization header set to `Bearer {token}`, and returns the JSON body to the caller. Your FlutterFlow API Call will call this function's HTTPS URL, not the Typeform API directly. Note that Typeform's Responses API returns a JSON object with an `items` array. Each item represents one submission and contains an `answers` array. Each answer has a `field` object (with a `ref` and `type`) and a value field that varies by question type — for example, `choice.label` for multiple-choice questions, `text` for short text, and `email` for email fields. This heterogeneous structure means you need to plan your JSON Path bindings carefully in the FlutterFlow response mapping step. Start by testing the Cloud Function with a direct browser or Postman call to see the actual JSON shape from your specific form before configuring paths in FlutterFlow.
1const functions = require('firebase-functions');2const admin = require('firebase-admin');3const https = require('https');45admin.initializeApp();67exports.typeformProxy = functions.https.onRequest(async (req, res) => {8 // Allow CORS for web builds9 res.set('Access-Control-Allow-Origin', '*');10 if (req.method === 'OPTIONS') {11 res.set('Access-Control-Allow-Methods', 'GET, POST');12 res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');13 return res.status(204).send('');14 }1516 const TYPEFORM_TOKEN = process.env.TYPEFORM_TOKEN17 || functions.config().typeform?.token;1819 if (!TYPEFORM_TOKEN) {20 return res.status(500).json({ error: 'Typeform token not configured' });21 }2223 const formId = req.query.form_id || req.body.form_id;24 if (!formId) {25 return res.status(400).json({ error: 'form_id is required' });26 }2728 const pageSize = req.query.page_size || '25';2930 try {31 const response = await fetch(32 `https://api.typeform.com/forms/${formId}/responses?page_size=${pageSize}`,33 {34 headers: {35 Authorization: `Bearer ${TYPEFORM_TOKEN}`,36 'Content-Type': 'application/json',37 },38 }39 );40 const data = await response.json();41 return res.status(response.status).json(data);42 } catch (err) {43 console.error('Typeform proxy error:', err);44 return res.status(500).json({ error: 'Failed to fetch from Typeform' });45 }46});Pro tip: Set the TYPEFORM_TOKEN using Firebase Secret Manager (recommended over environment config) by running `firebase functions:secrets:set TYPEFORM_TOKEN` in the Firebase CLI and then accessing it with `defineSecret` in your functions code for better security.
Expected result: Your Cloud Function is deployed and you can call its HTTPS URL with a `form_id` query parameter to receive the Typeform responses JSON without exposing the token.
Create a FlutterFlow API Call group pointing at your Cloud Function
In FlutterFlow, click API Calls in the left navigation panel and then click the + Add button and select Create API Group. Name the group TypeformProxy. In the Base URL field paste the HTTPS URL of your Cloud Function (found in the Firebase console under Functions — it looks like `https://us-central1-your-project.cloudfunctions.net/typeformProxy`). You do not need to add an Authorization header here because the token is already inside the Cloud Function — this is the security win. With the group created, click + Add inside it to create an API Call. Name it GetResponses. Set the method to GET. In the Endpoint field add nothing extra (the base URL is the full function URL) or append a path if your function handles multiple routes. Navigate to the Variables tab and add a variable named `form_id` with a default value of the Form ID you copied in Step 1. In the URL Parameters section add `form_id` mapped to `{{form_id}}` so it becomes a query parameter. Now click Response & Test. In the Test Variables section set `form_id` to your actual form ID and click Test API Call. If the Cloud Function is deployed correctly you will see the Typeform response JSON appear in the response panel. Click Generate JSON Paths — FlutterFlow will parse the structure and offer you path variables. Look for `$.total_items` (total count), `$.items` (the array of submissions), `$.items[0].answers[0].text` (example text answer), and `$.items[0].submitted_at` (submission timestamp). Select the paths you need and save the API Call. These paths will be available as variables when you bind the API response to widgets on your page.
1{2 "api_group": "TypeformProxy",3 "method": "GET",4 "base_url": "https://us-central1-YOUR_PROJECT.cloudfunctions.net/typeformProxy",5 "variables": [6 { "name": "form_id", "type": "String", "default": "YOUR_FORM_ID" },7 { "name": "page_size", "type": "String", "default": "25" }8 ],9 "url_params": {10 "form_id": "{{form_id}}",11 "page_size": "{{page_size}}"12 },13 "json_paths": [14 { "name": "totalItems", "path": "$.total_items" },15 { "name": "items", "path": "$.items" },16 { "name": "firstAnswer", "path": "$.items[0].answers[0].text" },17 { "name": "submittedAt", "path": "$.items[0].submitted_at" }18 ]19}Pro tip: Use the page_size variable to limit results to the last 25 or 50 submissions — pulling thousands of responses in one call on a mobile device will make the screen feel slow.
Expected result: The TypeformProxy API Group is configured, the test call returns real Typeform response data, and JSON Paths are generated for the fields you want to display.
Configure a Typeform webhook to push new responses into Firestore in real time
Polling the Typeform Responses API on a timer will always feel slightly stale — a user submits the form and your app takes up to 30 seconds to show the result. The proper solution is Typeform's native webhook feature, which POSTs a JSON payload to a URL of your choice within seconds of a submission. Since FlutterFlow cannot receive incoming HTTP requests (it is a client app, not a server), you point the webhook at another Firebase Cloud Function endpoint that writes the submission into Firestore. Your FlutterFlow app then reads Firestore with a real-time listener and updates instantly. In the Firebase console, add a second HTTP-triggered function named `typeformWebhook`. This function receives Typeform's POST payload, extracts the relevant fields from the `form_response` object (the submission timestamp, the answers array, any hidden fields), and writes them to a Firestore collection such as `typeform_responses/{formId}/submissions`. Add a Firestore real-time query in FlutterFlow on the matching collection — set Listen for Changes to true so the ListView updates automatically without the user refreshing. To register the webhook, go back to your Typeform account, open the form, click Connect → Webhooks, and paste your Cloud Function's URL. Enable the webhook and click Deliver a sample payload to test it. Typeform will send a test POST to your function and you should see a new document appear in Firestore within a few seconds. From that point forward, every real submission will trigger the same flow. Note that webhook delivery to the webhook endpoint is a Typeform feature available on paid plans — check your plan's webhook allowance if you are on a free account.
1exports.typeformWebhook = functions.https.onRequest(async (req, res) => {2 if (req.method !== 'POST') {3 return res.status(405).send('Method Not Allowed');4 }56 try {7 const payload = req.body;8 const formId = payload.form_response?.form_id;9 const answers = payload.form_response?.answers || [];10 const submittedAt = payload.form_response?.submitted_at;11 const responseId = payload.form_response?.token;1213 if (!formId || !responseId) {14 return res.status(400).json({ error: 'Invalid Typeform payload' });15 }1617 // Flatten answers into a simple key-value map for easy Firestore querying18 const answerMap = {};19 answers.forEach((a) => {20 const key = a.field?.ref || a.field?.id || 'unknown';21 const value = a.text || a.email || a.number ||22 a.choice?.label || a.choices?.labels?.join(', ') || '';23 answerMap[key] = value;24 });2526 await admin.firestore()27 .collection('typeform_responses')28 .doc(formId)29 .collection('submissions')30 .doc(responseId)31 .set({32 submittedAt,33 answers: answerMap,34 rawAnswers: answers,35 createdAt: admin.firestore.FieldValue.serverTimestamp(),36 });3738 return res.status(200).json({ received: true });39 } catch (err) {40 console.error('Webhook error:', err);41 return res.status(500).json({ error: 'Failed to process webhook' });42 }43});Pro tip: In FlutterFlow, set your Firestore query's Order By to `createdAt` descending so the most recent submission always appears at the top of the list.
Expected result: A new Firestore document appears within seconds of each Typeform submission, and your FlutterFlow ListView updates in real time without any manual refresh.
Common use cases
In-app onboarding survey that routes users to the right feature set
A SaaS FlutterFlow app embeds a Typeform onboarding survey as the first screen after sign-up. Typeform's webhook sends each completed submission to a Firebase Cloud Function, which writes the user's answers to Firestore and sets a user profile flag. FlutterFlow reads that flag to show a personalised dashboard based on the survey outcome.
I want new users who sign up to immediately fill out a 5-question onboarding survey inside the app. Based on their answers I want to show them a different home screen. The survey should look polished and work on both iOS and Android.
Copy this prompt to try it in FlutterFlow
Founder dashboard that shows the latest NPS responses in real time
A founder builds an internal FlutterFlow dashboard that displays NPS survey results as they come in. The Typeform webhook fires on each submission, a Cloud Function writes the score and comment to Firestore, and the FlutterFlow app listens to that Firestore collection with a real-time query, automatically updating a rolling list and average score widget.
I want a screen in my admin app that shows every NPS response from Typeform as soon as it's submitted. I need to see the score, the open-ended comment, and the timestamp. The list should update automatically without me having to refresh.
Copy this prompt to try it in FlutterFlow
Lead capture form that feeds a CRM-style contact list
A marketing FlutterFlow app uses an embedded Typeform to capture lead details — name, email, company, and budget range. Each submission is written to a Firestore contacts collection via the Typeform webhook and Cloud Function. The FlutterFlow app's admin screen lists every contact and lets the founder filter by budget range and send a follow-up email.
I want a Typeform lead capture form embedded inside my app. When someone fills it out, their name, email, and budget should automatically appear in a contacts list screen in the app. The list should be sortable and filterable.
Copy this prompt to try it in FlutterFlow
Troubleshooting
API Call returns 401 Unauthorized or the Cloud Function returns 'Typeform token not configured'
Cause: The Typeform Personal Access Token is missing or invalid in the Cloud Function's environment configuration.
Solution: In the Firebase console go to Functions → your function → Configuration and verify the TYPEFORM_TOKEN environment variable is set. If you used Secret Manager, ensure the function's service account has the Secret Accessor role. Regenerate the token in Typeform if it may have been revoked (Typeform → Account → Personal tokens).
ListView throws null errors or displays blank items when binding answers
Cause: Typeform's answers array is heterogeneous — a text question has an `answer.text` field, a choice question has `answer.choice.label`, and an email question has `answer.email`. If your JSON Path expects `.text` on a choice answer it returns null.
Solution: In your Cloud Function, flatten the answers array into a consistent key-value map before returning it to FlutterFlow. Each key is the field's `ref` from Typeform's form design, and each value is a string regardless of question type. This makes JSON Path bindings in FlutterFlow simple and null-safe.
WebView shows a blank white screen or the Typeform form does not load
Cause: The WebView widget does not run in FlutterFlow's browser-based Test Mode / Run Mode preview. The webview_flutter package requires a native platform (iOS or Android) to render.
Solution: Test the WebView embed on a physical device or using FlutterFlow's Local Run feature (connect your device via USB and click the Local Run icon in the top toolbar). Do not judge the embed by what you see in the browser preview panel — it will appear blank there even when working correctly on device.
Typeform webhook is not delivering to the Cloud Function (no Firestore documents appearing)
Cause: The webhook URL may be incorrectly registered, the Cloud Function may not be deployed, or your Typeform plan may not support webhooks.
Solution: In Typeform go to Connect → Webhooks and click 'Deliver a sample payload' to manually trigger a test POST. Check the Cloud Function's logs in the Firebase console for any errors. Ensure your Typeform plan supports webhooks — check Typeform's current plan comparison page as webhook availability varies by tier. Verify the Cloud Function URL ends with the correct function name.
Best practices
- Never place the Typeform Personal Access Token in a FlutterFlow API Call header — it will be compiled into the app binary and can be extracted from any installed APK or IPA file.
- Use Typeform's webhook-to-Firestore pattern rather than polling the Responses API on a timer — webhooks give near-instant updates and cost zero API calls on the FlutterFlow side.
- Flatten the heterogeneous answers array in your Cloud Function before sending it to FlutterFlow — this prevents null binding errors caused by different answer types having different JSON shapes.
- Store your Form ID in FlutterFlow's App Values (App Settings → App Values) as a constant rather than hardcoding it in each widget — makes it easy to swap forms across environments.
- Add CORS headers in your Cloud Function's response so that FlutterFlow web builds can call the proxy from the browser without XMLHttpRequest errors.
- Limit the `page_size` query parameter to a reasonable number (25–50) when pulling responses — loading hundreds of submissions on a mobile device degrades perceived performance.
- Test the WebView embed on a real device or Local Run before asking users to fill out the form — the browser preview panel in FlutterFlow will show a blank screen even when the widget is correctly configured.
Alternatives
Choose SurveyMonkey if you need enterprise-grade survey analytics with branching logic and have a Team or Enterprise plan, and are comfortable with OAuth-2 token management rather than a simpler Bearer token.
Choose Constant Contact if the primary goal is newsletter sign-up rather than survey collection — it handles contact list management and email marketing in one platform.
Choose SendGrid if you want to trigger transactional emails as a follow-up to form submissions rather than collecting the form data itself inside FlutterFlow.
Frequently asked questions
Can FlutterFlow receive Typeform webhook events directly?
No. FlutterFlow compiles to a client-side Flutter app running on the user's device — it has no server address that Typeform can POST to. Webhooks must be received by a Firebase Cloud Function or Supabase Edge Function. That backend endpoint then writes the data to Firestore or a database, and FlutterFlow reads that database in real time.
Is the Typeform free plan enough to test this integration?
Yes, for development and testing. Typeform's free plan allows up to 10 responses per month on one form. The WebView embed and the Responses API both work on the free plan. However, webhook delivery may require a paid plan — check Typeform's current plan comparison page. For a production app with real user traffic you will need a paid Typeform plan.
Why can't I just put the Typeform Personal Access Token directly in the FlutterFlow API Call Authorization header?
The Typeform Personal Access Token grants full read and write access to every form on your account. When you add it to a FlutterFlow API Call header, it is compiled into the Flutter app binary. Anyone who downloads your app can extract the binary and read the token using freely available tools. This is true for both iOS and Android builds. A Firebase Cloud Function proxy keeps the token server-side and out of the app entirely.
Do I need a paid Firebase plan for the Cloud Function proxy?
Yes. Firebase Cloud Functions that make outbound HTTP calls (to api.typeform.com in this case) require the Blaze (pay-as-you-go) plan. The Spark free plan restricts outbound network calls to Google services only. However, Firebase Cloud Functions have a generous free tier within the Blaze plan — the first 2 million invocations per month are free — so for most apps the cost will be negligible. Firebase recommends setting a budget alert in your project to avoid surprises.
If you'd rather skip the Cloud Function setup, what are the options?
If the proxy architecture feels like too much to manage on your own, RapidDev's team builds FlutterFlow integrations like this every week and can set up the Cloud Function, Firestore schema, and FlutterFlow bindings for you — free scoping call at rapidevelopers.com/contact. Alternatively, if you only need the embed path and don't need to read responses in-app, you can use just the WebView widget step (Step 2) without any backend work.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation