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

How to Create Custom AR/VR Experiences in FlutterFlow

FlutterFlow supports three types of AR/VR experiences through Custom Widgets: product 3D viewer (flutter_3d_controller — easiest, no native setup), AR object placement (ar_flutter_plugin — native ARKit/ARCore, requires code export and iOS/Android config), and 360-degree tour (panorama_viewer — works on all devices, no AR hardware needed). Choose based on your use case and target audience.

What you'll learn

  • How to build a 3D product viewer with touch controls using flutter_3d_controller
  • How to create AR object placement using ar_flutter_plugin with ARKit and ARCore
  • How to build a 360-degree panoramic tour viewer using the panorama_viewer package
  • How to store and serve 3D assets correctly using Firebase Storage
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced12 min read90-120 minFlutterFlow Pro+ (code export required for AR placement iOS/Android configuration)March 2026RapidDev Engineering Team
TL;DR

FlutterFlow supports three types of AR/VR experiences through Custom Widgets: product 3D viewer (flutter_3d_controller — easiest, no native setup), AR object placement (ar_flutter_plugin — native ARKit/ARCore, requires code export and iOS/Android config), and 360-degree tour (panorama_viewer — works on all devices, no AR hardware needed). Choose based on your use case and target audience.

Three AR/VR Project Types You Can Build in FlutterFlow

AR and VR in FlutterFlow are built entirely through Custom Widgets — there are no drag-and-drop AR components in the widget palette. The good news is that Flutter's package ecosystem has mature solutions for each major AR/VR use case. This guide covers three distinct project types based on real-world commercial needs. Product Viewer apps (e-commerce, real estate, furniture) need interactive 3D models that users can rotate and zoom — flutter_3d_controller is the right tool with minimal setup. AR Placement apps (interior design, retail try-on, gaming) need to anchor virtual objects to real-world surfaces — ar_flutter_plugin provides ARKit (iOS) and ARCore (Android) wrappers. 360-degree Tour apps (real estate walkthrough, travel, education) need immersive panoramic viewing — the panorama_viewer package works on all devices without special hardware.

Prerequisites

  • FlutterFlow Pro plan (Custom Widgets and code export required for AR configuration)
  • 3D model files (GLB format) hosted on Firebase Storage for the product viewer
  • 360-degree panorama images (equirectangular JPEG) hosted on Firebase Storage for the tour
  • Physical iOS or Android device for AR placement testing
  • Basic understanding of FlutterFlow Custom Widgets and the Custom Code panel

Step-by-step guide

1

Set up Firebase Storage for 3D assets

All three AR/VR project types load their assets (3D models, panorama images) from URLs at runtime rather than bundling them in the app. Open Firebase Console → Storage and create a folder named 'ar-assets'. Upload your GLB model files (for product viewer and AR placement) and equirectangular panorama JPEG files (for 360 tours) to this folder. After uploading, click each file, go to the 'Details' tab, and copy the download URL. Paste these URLs into Firestore documents in a collection like 'products' or 'locations' — your FlutterFlow pages will query this collection and pass the asset URL to the Custom Widget. Set Firebase Storage rules to allow read access for authenticated users.

Expected result: 3D model GLB files and panorama JPEG files are uploaded to Firebase Storage with download URLs stored in Firestore documents.

2

Build the Product Viewer with flutter_3d_controller

Open the Custom Code panel and add 'flutter_3d_controller: ^2.0.0' to pubspec.yaml. Tap Get Packages. Create a Custom Widget named 'ProductViewer3D' with parameters: modelUrl (String), width (double), height (double). Write the widget as a StatefulWidget that creates a Flutter3DController, returns a Flutter3DViewer with enableTouch true, and disposes the controller in dispose(). Add the widget to your product detail page and bind the modelUrl parameter to the Firestore document's model URL field. Users can then rotate, zoom, and inspect the 3D model with touch gestures. This path has no iOS/Android native configuration — it works immediately in Run Mode.

ProductViewer3D_widget.dart
1import 'package:flutter/material.dart';
2import 'package:flutter_3d_controller/flutter_3d_controller.dart';
3
4class ProductViewer3D extends StatefulWidget {
5 const ProductViewer3D({
6 super.key,
7 required this.modelUrl,
8 required this.width,
9 required this.height,
10 });
11 final String modelUrl;
12 final double width;
13 final double height;
14
15 @override
16 State<ProductViewer3D> createState() => _ProductViewer3DState();
17}
18
19class _ProductViewer3DState extends State<ProductViewer3D> {
20 late final Flutter3DController _ctrl = Flutter3DController();
21
22 @override
23 void dispose() {
24 _ctrl.dispose();
25 super.dispose();
26 }
27
28 @override
29 Widget build(BuildContext context) {
30 return SizedBox(
31 width: widget.width,
32 height: widget.height,
33 child: Flutter3DViewer(
34 controller: _ctrl,
35 src: widget.modelUrl,
36 enableTouch: true,
37 onLoad: () => debugPrint('Model loaded: ${widget.modelUrl}'),
38 onError: () => debugPrint('Model error: ${widget.modelUrl}'),
39 ),
40 );
41 }
42}

Expected result: The ProductViewer3D Custom Widget displays an interactive 3D model loaded from the Firebase Storage URL with pinch-to-zoom and rotation gestures.

3

Build the AR Placement view with ar_flutter_plugin

Add 'ar_flutter_plugin: ^0.7.3' to pubspec.yaml and tap Get Packages. Create a Custom Widget named 'ARPlacementView' with parameters: modelUrl (String), width (double), height (double). The widget uses ARView from ar_flutter_plugin — it shows the device camera feed, detects horizontal planes (floors, tables), and allows tapping to place a 3D model on a detected surface. Export your FlutterFlow project code (Pro plan required) and complete the native configuration: add NSCameraUsageDescription to ios/Runner/Info.plist, add ARKit to the Podfile, add CAMERA permission and ar.required meta-data to android/app/src/main/AndroidManifest.xml. Then build and run from Xcode/Android Studio.

ARPlacementView_widget.dart
1import 'package:flutter/material.dart';
2import 'package:ar_flutter_plugin/ar_flutter_plugin.dart';
3import 'package:ar_flutter_plugin/datatypes/config_planedetection.dart';
4import 'package:ar_flutter_plugin/datatypes/node_types.dart';
5import 'package:ar_flutter_plugin/managers/ar_location_manager.dart';
6import 'package:ar_flutter_plugin/managers/ar_session_manager.dart';
7import 'package:ar_flutter_plugin/managers/ar_object_manager.dart';
8import 'package:ar_flutter_plugin/managers/ar_anchor_manager.dart';
9import 'package:ar_flutter_plugin/models/ar_node.dart';
10import 'package:vector_math/vector_math_64.dart';
11
12class ARPlacementView extends StatefulWidget {
13 const ARPlacementView({
14 super.key,
15 required this.modelUrl,
16 required this.width,
17 required this.height,
18 });
19 final String modelUrl;
20 final double width;
21 final double height;
22
23 @override
24 State<ARPlacementView> createState() => _ARPlacementViewState();
25}
26
27class _ARPlacementViewState extends State<ARPlacementView> {
28 ARSessionManager? arSessionManager;
29 ARObjectManager? arObjectManager;
30
31 @override
32 void dispose() {
33 arSessionManager?.dispose();
34 super.dispose();
35 }
36
37 @override
38 Widget build(BuildContext context) {
39 return SizedBox(
40 width: widget.width,
41 height: widget.height,
42 child: ARView(
43 onARViewCreated: _onARViewCreated,
44 planeDetectionConfig: PlaneDetectionConfig.horizontalAndVertical,
45 ),
46 );
47 }
48
49 void _onARViewCreated(
50 ARSessionManager session,
51 ARObjectManager objects,
52 ARAnchorManager anchors,
53 ARLocationManager location,
54 ) {
55 arSessionManager = session;
56 arObjectManager = objects;
57 arSessionManager!.onInitialize(
58 showFeaturePoints: false,
59 showPlanes: true,
60 );
61 arObjectManager!.onInitialize();
62 arSessionManager!.onPlaneOrPointTap = _onPlaneTap;
63 }
64
65 Future<void> _onPlaneTap(List<ARHitTestResult> hits) async {
66 if (hits.isEmpty) return;
67 final hit = hits.first;
68 final node = ARNode(
69 type: NodeType.webGLB,
70 uri: widget.modelUrl,
71 scale: Vector3(0.2, 0.2, 0.2),
72 position: Vector3(0, 0, 0),
73 rotation: Vector4(1, 0, 0, 0),
74 );
75 await arObjectManager?.addNode(node, planeAnchor: hit);
76 }
77}

Expected result: On a physical device, the ARPlacementView shows camera feed with detected planes highlighted, and tapping a plane places the 3D model.

4

Build the 360-degree tour viewer with panorama_viewer

Add 'panorama_viewer: ^1.0.3' to pubspec.yaml and tap Get Packages. Create a Custom Widget named 'PanoramaViewer360' with parameters: imageUrl (String), width (double), height (double). The panorama_viewer package takes an equirectangular image (a 2:1 ratio panorama photo) and renders it as an interactive spherical environment — users drag to look around in all directions. The widget uses an Image.network to load the image from your Firebase Storage URL. This path requires no native configuration and works on iOS, Android, and Web. It is ideal for virtual property tours, destination previews, and educational environments.

PanoramaViewer360_widget.dart
1import 'package:flutter/material.dart';
2import 'package:panorama_viewer/panorama_viewer.dart';
3
4class PanoramaViewer360 extends StatelessWidget {
5 const PanoramaViewer360({
6 super.key,
7 required this.imageUrl,
8 required this.width,
9 required this.height,
10 });
11 final String imageUrl;
12 final double width;
13 final double height;
14
15 @override
16 Widget build(BuildContext context) {
17 return SizedBox(
18 width: widget.width,
19 height: widget.height,
20 child: Panorama(
21 child: Image.network(
22 imageUrl,
23 fit: BoxFit.cover,
24 errorBuilder: (ctx, err, stack) =>
25 const Center(child: Text('Failed to load panorama')),
26 ),
27 ),
28 );
29 }
30}

Expected result: The PanoramaViewer360 widget displays a fully interactive 360-degree photo that users can drag to explore in all directions.

5

Wire the AR widgets to Firestore data in FlutterFlow

Add the Custom Widget to a FlutterFlow page that queries Firestore for the relevant content. For a product page, add a Backend Query on the page to fetch the product document by its ID. Then drag your ProductViewer3D or ARPlacementView Custom Widget onto the page and bind the modelUrl parameter to the product document's 'modelUrl' Firestore field. Add a loading state to show a CircularProgressIndicator while the query runs. For the panorama tour, add a Repeating Group with a Backend Query on the 'locations' collection and place the PanoramaViewer360 widget inside each list item, binding imageUrl to the location's 'panoramaUrl' field.

Expected result: The AR/VR widget is bound to Firestore data, loads the correct model or panorama URL for each product or location, and shows a loading indicator while assets are fetched.

6

Test all three experience types on physical devices

Run Mode is required for all three paths. For ProductViewer3D and PanoramaViewer360, connect any recent iOS or Android phone via USB and click Run — these work on all devices. For ARPlacementView, you specifically need an ARKit-compatible iPhone (6s or later) or ARCore-compatible Android — older budget devices may not support AR. On the AR view, walk around a flat surface until the plane detection grid appears. Then tap the grid to place the model. Test scaling, rotation, and repositioning. Check the DevTools Logging tab for any ar_flutter_plugin initialization errors or model loading failures.

Expected result: All three experience types load correctly on physical devices, models render from Firebase Storage URLs, and AR plane detection and model placement work on AR-capable devices.

Complete working example

panorama_viewer_widget.dart
1// ─── Custom Widget: PanoramaViewer360 ─────────────────────────────────────────
2// Displays an interactive 360-degree panoramic photo.
3// Required parameters: imageUrl (String), width (double), height (double)
4// pubspec.yaml: panorama_viewer: ^1.0.3
5//
6// No native iOS/Android configuration needed.
7// Works on iOS, Android, and Web.
8// Use equirectangular images (2:1 aspect ratio) from Firebase Storage.
9
10import 'package:flutter/material.dart';
11import 'package:panorama_viewer/panorama_viewer.dart';
12
13class PanoramaViewer360 extends StatefulWidget {
14 const PanoramaViewer360({
15 super.key,
16 required this.imageUrl,
17 required this.width,
18 required this.height,
19 this.showSensors = true,
20 });
21
22 final String imageUrl;
23 final double width;
24 final double height;
25 final bool showSensors; // true = gyroscope-assisted panning
26
27 @override
28 State<PanoramaViewer360> createState() => _PanoramaViewer360State();
29}
30
31class _PanoramaViewer360State extends State<PanoramaViewer360> {
32 bool _loading = true;
33 bool _error = false;
34
35 @override
36 Widget build(BuildContext context) {
37 if (widget.imageUrl.isEmpty) {
38 return SizedBox(
39 width: widget.width,
40 height: widget.height,
41 child: const Center(child: Text('No panorama URL provided.')),
42 );
43 }
44
45 return SizedBox(
46 width: widget.width,
47 height: widget.height,
48 child: Stack(
49 children: [
50 if (_error)
51 const Center(
52 child: Column(
53 mainAxisSize: MainAxisSize.min,
54 children: [
55 Icon(Icons.broken_image_outlined, size: 48),
56 SizedBox(height: 8),
57 Text('Failed to load panorama'),
58 ],
59 ),
60 )
61 else
62 Panorama(
63 sensitivity: 2.0,
64 sensorControl: widget.showSensors
65 ? SensorControl.orientation
66 : SensorControl.none,
67 child: Image.network(
68 widget.imageUrl,
69 fit: BoxFit.cover,
70 loadingBuilder: (ctx, child, progress) {
71 if (progress == null) {
72 WidgetsBinding.instance.addPostFrameCallback(
73 (_) => setState(() => _loading = false));
74 return child;
75 }
76 return const SizedBox.shrink();
77 },
78 errorBuilder: (ctx, err, stack) {
79 WidgetsBinding.instance.addPostFrameCallback(
80 (_) => setState(() => _error = true));
81 return const SizedBox.shrink();
82 },
83 ),
84 ),
85 if (_loading && !_error)
86 const Center(child: CircularProgressIndicator()),
87 ],
88 ),
89 );
90 }
91}
92
93// ─── pubspec.yaml ─────────────────────────────────────────────────────────────
94// dependencies:
95// panorama_viewer: ^1.0.3

Common mistakes when creating Custom AR/VR Experiences in FlutterFlow

Why it's a problem: Hosting 3D model files as base64-encoded strings in Firestore document fields instead of Firebase Storage URLs

How to avoid: Upload 3D model files to Firebase Storage (not Firestore) and store only the string download URL in the Firestore document. Firebase Storage is designed for large binary files, has CDN caching, and has no per-file size limit on Blaze plan.

Why it's a problem: Using ar_flutter_plugin without completing native iOS and Android configuration

How to avoid: Export your FlutterFlow project (Pro plan) and follow the ar_flutter_plugin setup guide on pub.dev exactly. The Podfile, Info.plist, and AndroidManifest modifications are all documented there.

Why it's a problem: Using non-equirectangular images for the PanoramaViewer360

How to avoid: Use dedicated 360-degree camera equipment or apps (like Google Street View, Ricoh Theta) to capture equirectangular panoramas. The resulting images typically have a 2:1 aspect ratio (e.g., 4096x2048 pixels).

Why it's a problem: Setting AR model scale to Vector3(1, 1, 1) which places full-size (1 meter) models

How to avoid: Start with Vector3(0.2, 0.2, 0.2) as a default scale and add pinch-to-scale gesture handling to let users adjust. Document the expected scale of each model in your Firestore document (e.g., 'arScale': 0.15) and read it as a parameter.

Best practices

  • Start with the simplest path that meets your requirements — PanoramaViewer360 (no native config) before ar_flutter_plugin (requires code export and native setup).
  • Store AR asset metadata (model URL, default scale, thumbnail URL, supported platforms) in Firestore so you can update assets without an app update.
  • Show a loading indicator while assets download from Firebase Storage — 3D model and panorama image downloads can take several seconds on slower connections.
  • Provide a non-AR fallback (2D images or regular 3D viewer) for devices that do not support ARKit/ARCore — check availability before launching the AR session.
  • Limit AR sessions to user-initiated actions (tap 'View in AR' button) rather than launching AR automatically on page load — AR consumes significant battery and CPU.
  • Test on the oldest and least-powerful supported device in your target audience — AR performance degrades sharply on older hardware, and what runs smoothly on a flagship phone may be unusable on a budget device.
  • Compress panorama images to under 4MB and GLB models to under 5MB for acceptable loading times on mobile networks — large assets are the number one cause of poor AR experience ratings.
  • Add a brief onboarding overlay when AR launches for the first time explaining how to detect surfaces and place objects — AR UX is still unfamiliar to a significant portion of mobile users.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a FlutterFlow app for a furniture store and want to add an AR feature where users can place furniture in their room using their phone camera. I need a Flutter Custom Widget that uses ar_flutter_plugin to show the camera feed, detect horizontal planes, and let users tap to place a 3D model from a GLB URL parameter. Please write the complete StatefulWidget class including the ARView setup, onARViewCreated handler, and tap-to-place logic. The widget should accept parameters: modelUrl (String), width (double), height (double).

FlutterFlow Prompt

Write a FlutterFlow Custom Widget called 'PanoramaViewer360' using the panorama_viewer ^1.0.3 package. Parameters: imageUrl (String, required), width (double, required), height (double, required). The widget should show a loading indicator while the image loads, display an error state with an icon if loading fails, and use SensorControl.orientation for gyroscope-assisted panning. Format as a complete StatefulWidget ready to paste into the FlutterFlow Custom Code editor.

Frequently asked questions

What is the difference between the three AR/VR approaches in this guide?

Product Viewer (flutter_3d_controller) shows a 3D model that users rotate and zoom — no real-world camera integration, works on all devices. AR Placement (ar_flutter_plugin) uses the camera to detect real surfaces and places a virtual 3D model on them — requires ARKit/ARCore hardware. 360-degree Tour (panorama_viewer) wraps a spherical panorama photo into an immersive viewer — no special hardware needed.

Can I use all three AR/VR types in the same app?

Yes. Each type is a separate Custom Widget. You can have a product list page that shows the ProductViewer3D on product detail, an 'View in AR' button on that same page that opens the ARPlacementView, and a separate 'Virtual Tour' page that uses PanoramaViewer360. They are independent widgets with different package dependencies — just add all required packages to pubspec.yaml.

Do I need to pay for AR features in FlutterFlow?

The Flutter packages (ar_flutter_plugin, flutter_3d_controller, panorama_viewer) are free open-source packages. FlutterFlow Pro plan is required for Custom Widgets and code export (needed for native AR configuration). Firebase Storage costs for hosting 3D assets are minimal (free tier includes 5GB storage). There are no AR-specific per-use fees.

How do I get 3D models for product visualization?

Options include: hiring a 3D artist to create models from product photos or CAD files ($50-500 per model), using free model repositories (Google Poly archive, Sketchfab free section), using Polycam or Luma AI to scan physical objects with your iPhone, or using AI-based 3D generation tools like Meshy.ai. Always export in GLB format and compress with Draco.

Will the AR features work on the web version of my FlutterFlow app?

flutter_3d_controller works on Flutter Web. ar_flutter_plugin does NOT support Flutter Web — it requires native ARKit/ARCore. panorama_viewer works on Flutter Web. For web-based AR, use the WebAR approach: load an A-Frame or 8th Wall experience in a WebView widget.

How large should my panorama images be for good quality?

For mobile apps, 4096x2048 pixels (4K equirectangular) provides excellent quality at a manageable file size (typically 2-5MB as a JPEG). Anything smaller than 2048x1024 will look blurry when the user zooms in. For web delivery, use 8192x4096 (8K) if bandwidth allows. Compress panorama JPEGs to quality level 85 — this saves 40-50% file size with minimal visible quality loss.

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.