Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to create a custom webview for your FlutterFlow app

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.

What you'll learn

  • How to create a Custom Widget in FlutterFlow with the webview_flutter package
  • How to pass a URL via Component Parameter and load it in the WebView
  • How to overlay a loading spinner that hides when the page finishes loading
  • How to use JavaScriptChannel for bidirectional web-to-app communication
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner6 min read20-30 minFlutterFlow Pro+ (Custom Code required)March 2026RapidDev Engineering Team
TL;DR

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

1

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.

2

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).

custom_web_view.dart
1import 'package:webview_flutter/webview_flutter.dart';
2
3class 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;
9
10 @override
11 State<CustomWebView> createState() => _CustomWebViewState();
12}
13
14class _CustomWebViewState extends State<CustomWebView> {
15 late final WebViewController _controller;
16 bool _isLoading = true;
17
18 @override
19 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 }
31
32 @override
33 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.

3

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.

4

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.

5

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.

custom_web_view.dart (add to initState)
1_controller
2 ..addJavaScriptChannel(
3 'FlutterChannel',
4 onMessageReceived: (message) {
5 // message.message contains the string from JS
6 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

custom_web_view.dart
1import 'package:webview_flutter/webview_flutter.dart';
2
3class 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 });
12
13 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;
18
19 @override
20 State<CustomWebView> createState() => _CustomWebViewState();
21}
22
23class _CustomWebViewState extends State<CustomWebView> {
24 late final WebViewController _controller;
25 bool _isLoading = true;
26
27 @override
28 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 }
51
52 @override
53 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.

ChatGPT Prompt

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.

FlutterFlow Prompt

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.

RapidDev

Talk to an Expert

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

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

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.