FlutterFlow has no built-in WebView widget, so you need a Custom Widget using the webview_flutter package. This tutorial shows you how to create the Custom Widget with WebViewController, pass a URL via Component Parameter, enable JavaScript, add a loading spinner overlay, and handle navigation callbacks — all in about 60 lines of Dart.
Embedding a WebView in FlutterFlow
FlutterFlow does not include a built-in WebView widget. To embed external web pages, payment forms, or web-based tools inside your app, you need to create a Custom Widget using the webview_flutter package from pub.dev. This tutorial walks you through the full setup: adding the dependency, writing the Dart code, passing configuration via Component Parameters, and handling common issues like loading states and JavaScript communication.
Prerequisites
- FlutterFlow Pro plan or higher (Custom Code required)
- A FlutterFlow project open in the builder
- The URL you want to embed (must be HTTPS for iOS)
- Basic understanding of what a Custom Widget is in FlutterFlow
Step-by-step guide
Add the webview_flutter dependency to your project
Add the webview_flutter dependency to your project
In FlutterFlow, go to Custom Code in the left Navigation Menu. Click the Pubspec Dependencies tab. Add webview_flutter with version ^4.4.0. Click Save. This makes the package available for import in any Custom Widget or Custom Action. FlutterFlow will download the package on next compile.
Expected result: The webview_flutter package appears in your Pubspec Dependencies list without errors.
Create the Custom Widget with WebView controller
Create the Custom Widget with WebView controller
Go to Custom Code → Custom Widgets → click + Add. Name it CustomWebView. Set width parameter to double.infinity and height to 500 (or pass via parameter). In the code editor, import webview_flutter. Create a StatefulWidget. In initState(), initialize the WebViewController: set JavaScriptMode.unrestricted, set the NavigationDelegate to filter URLs (optional), and call loadRequest(Uri.parse(widget.url)). In build(), return WebViewWidget(controller: _controller).
1import 'package:webview_flutter/webview_flutter.dart';23class CustomWebView extends StatefulWidget {4 const CustomWebView({super.key, this.width, this.height, required this.url, this.onPageFinished});5 final double? width;6 final double? height;7 final String url;8 final Future Function(String url)? onPageFinished;910 @override11 State<CustomWebView> createState() => _CustomWebViewState();12}1314class _CustomWebViewState extends State<CustomWebView> {15 late final WebViewController _controller;16 bool _isLoading = true;1718 @override19 void initState() {20 super.initState();21 _controller = WebViewController()22 ..setJavaScriptMode(JavaScriptMode.unrestricted)23 ..setNavigationDelegate(NavigationDelegate(24 onPageFinished: (url) {25 setState(() => _isLoading = false);26 widget.onPageFinished?.call(url);27 },28 ))29 ..loadRequest(Uri.parse(widget.url));30 }3132 @override33 Widget build(BuildContext context) {34 return SizedBox(35 width: widget.width,36 height: widget.height ?? 500,37 child: Stack(38 children: [39 WebViewWidget(controller: _controller),40 if (_isLoading)41 const Center(child: CircularProgressIndicator()),42 ],43 ),44 );45 }46}Expected result: The Custom Widget compiles without errors. You see CustomWebView in your widget palette.
Add Component Parameters for URL and callbacks
Add Component Parameters for URL and callbacks
In the Custom Widget editor, ensure you have these parameters defined: url (String, required) — the web page URL to load, and onPageFinished (Action, optional) — callback triggered when page finishes loading. Click Compile Code to verify. The url parameter lets you reuse this widget across pages with different URLs by passing the value from the parent.
Expected result: Parameters show in the Properties Panel when you place the widget on a page.
Place the widget on a page and pass the URL
Place the widget on a page and pass the URL
Navigate to the page where you want the WebView. Drag your CustomWebView widget from the Widget Palette (under Custom Widgets). In the Properties Panel, set the url parameter — either hardcode a URL string or bind it to a Page State variable or Route Parameter for dynamic URLs. Set width to infinity (fills parent) and height to the desired pixel value. Wrap in an Expanded widget if it is inside a Column and should fill remaining space.
Expected result: In Run mode, the page loads the specified URL inside the embedded WebView with a loading spinner.
Add JavaScript communication for web-to-app data passing
Add JavaScript communication for web-to-app data passing
To receive data from the web page (e.g., a payment confirmation or form result), add a JavaScriptChannel to the controller in initState(). The channel creates a global JavaScript object that the web page can call: window.FlutterChannel.postMessage('data'). In the handler, parse the message and update Page State or trigger an Action Parameter callback.
1_controller2 ..addJavaScriptChannel(3 'FlutterChannel',4 onMessageReceived: (message) {5 // message.message contains the string from JS6 widget.onDataReceived?.call(message.message);7 },8 );Expected result: Web page can send data to your FlutterFlow app via the JavaScript channel.
Complete working example
1import 'package:webview_flutter/webview_flutter.dart';23class CustomWebView extends StatefulWidget {4 const CustomWebView({5 super.key,6 this.width,7 this.height,8 required this.url,9 this.onPageFinished,10 this.onDataReceived,11 });1213 final double? width;14 final double? height;15 final String url;16 final Future Function(String url)? onPageFinished;17 final Future Function(String data)? onDataReceived;1819 @override20 State<CustomWebView> createState() => _CustomWebViewState();21}2223class _CustomWebViewState extends State<CustomWebView> {24 late final WebViewController _controller;25 bool _isLoading = true;2627 @override28 void initState() {29 super.initState();30 _controller = WebViewController()31 ..setJavaScriptMode(JavaScriptMode.unrestricted)32 ..addJavaScriptChannel(33 'FlutterChannel',34 onMessageReceived: (message) {35 widget.onDataReceived?.call(message.message);36 },37 )38 ..setNavigationDelegate(NavigationDelegate(39 onPageStarted: (_) => setState(() => _isLoading = true),40 onPageFinished: (url) {41 setState(() => _isLoading = false);42 widget.onPageFinished?.call(url);43 },44 onWebResourceError: (error) {45 setState(() => _isLoading = false);46 debugPrint('WebView error: ${error.description}');47 },48 ))49 ..loadRequest(Uri.parse(widget.url));50 }5152 @override53 Widget build(BuildContext context) {54 return SizedBox(55 width: widget.width ?? double.infinity,56 height: widget.height ?? 500,57 child: Stack(58 children: [59 WebViewWidget(controller: _controller),60 if (_isLoading)61 const Center(child: CircularProgressIndicator()),62 ],63 ),64 );65 }66}Common mistakes when creating a custom webview for your FlutterFlow app
Why it's a problem: Using http:// URLs on iOS
How to avoid: Always use https:// URLs. If you must load HTTP after code export, add an ATS exception in ios/Runner/Info.plist — but this is not recommended for production.
Why it's a problem: Not setting a fixed height on the WebView container
How to avoid: Always set an explicit height on the Custom Widget or wrap it in Expanded when inside a Column so it takes the remaining available space.
Why it's a problem: Forgetting to enable JavaScript
How to avoid: Set JavaScriptMode.unrestricted in the WebViewController setup. Only disable JS if loading purely static content for security.
Best practices
- Always pass the URL as a Component Parameter — never hardcode URLs in the Dart code
- Show a loading indicator overlay while the page loads using the onPageStarted/onPageFinished callbacks
- Use NavigationDelegate to block navigation to external domains if the WebView should stay on one site
- Handle onWebResourceError to show a user-friendly error message instead of a blank screen
- Test on both iOS and Android — WebView behavior differs between platforms (iOS uses WKWebView, Android uses Chrome)
- For payment forms, prefer Stripe Checkout redirect over embedding Stripe Elements in a WebView
- Dispose the WebView when not needed to free memory — avoid keeping it alive in a background tab
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to embed a WebView in my FlutterFlow app using a Custom Widget. Write the full Dart code for a StatefulWidget that takes a URL parameter, shows a loading spinner while loading, enables JavaScript, and has a JavaScriptChannel called FlutterChannel for receiving messages from the web page.
Create a page with a header text, a full-width container below it, and a button to navigate back. I'll add a Custom Widget for the WebView inside the container manually.
Frequently asked questions
Can I use FlutterFlow's built-in widgets instead of Custom Code for WebView?
No. FlutterFlow does not have a built-in WebView widget as of March 2026. You must create a Custom Widget using the webview_flutter package, which requires a Pro plan or higher.
Does the WebView work on web deployments?
The webview_flutter package only works on iOS and Android. For web, use an HtmlElementView with an iframe instead — this requires a separate Custom Widget for web platform. Or use Launch URL to open in a new browser tab.
How do I pass data FROM my app TO the web page?
Use controller.runJavaScript('functionName("data")') to call a JavaScript function on the loaded page. The web page must define that function to handle the incoming data.
Can the WebView access the device camera or location?
WebView has limited access to device features. Camera and file upload work on Android via onShowFileChooser but require additional configuration on iOS. Location works if the web page requests it and the app has location permissions.
Why is my WebView showing a blank white screen?
Common causes: HTTP URL blocked by iOS ATS (use HTTPS), JavaScript disabled (enable unrestricted mode), URL returns an error (check in browser first), or WebView has zero height (set explicit height).
Can RapidDev help with complex WebView integrations?
Yes. If you need OAuth flows inside WebView, deep JavaScript bridging, or platform-specific WebView configurations, RapidDev's team can build Custom Widgets that handle these advanced scenarios.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation