FlutterFlow is a powerful platform for rapidly building production-ready applications. It handles a wide range of common scenarios out of the box, making it an excellent choice for teams that want to move fast without sacrificing stability.
However, in real-world products, business requirements rarely stay within platform boundaries.
That’s where custom code becomes the difference between a functional app and a polished product.
In this blog, we’ll walk through a real production use case from a client project where we extended FlutterFlow using custom widgets and custom actions to deliver a branded, user-friendly experience - without breaking FlutterFlow’s workflow.
This is not a demo or experiment.This is how we solve real client problems in production.
The Business Requirement
Our client came to us with a clear requirement:
They wanted to generate a custom QR code, display it inside a branded card UI, and allow users to:
- Share the QR card as an image
- Download the QR card
- Ensure the shared or downloaded image matched the on-screen UI exactly
The exported image had to include:
- Profile image
- Background and brand colors
- QR code
- Visual brand elements
From a business perspective, this was about brand consistency and user experience, not just functionality.
The Technical Challenge
Out of the box, FlutterFlow:
- Does not support widget-level screenshot capture
- Cannot export composed UI as an image
- Does not provide native share/download for rendered widgets
So the real challenge was:
How do we extend FlutterFlow’s capabilities without breaking its low-code workflow?
This is a common scenario for teams that start with no-code or low-code tools and then hit real-world product complexity - something we frequently see in low-code / no-code development projects.
Our Technical Approach
Instead of fighting FlutterFlow, we built a solution that layers cleanly on top of it.
Our approach included:
- A Custom QR Code Widget
- A Screenshot Wrapper Widget
- Two Custom Actions:
- Share QR Card
- Download QR Card
This separation allowed:
- Designers to continue working inside FlutterFlow
- Developers to handle advanced logic with custom code
- The product to remain scalable and maintainable
Step 1: Adding Required Packages
FlutterFlow allows adding third-party packages, which unlocks advanced use cases.
For this feature, we integrated:
- A QR generation package for high-quality, customizable QR codes
- A screenshot utility package to capture widgets as images
This combination is lightweight, reliable, and suitable for production environments.
Step 2: Creating a Custom QR Code Widget
FlutterFlow’s built-in QR support is sufficient for basic use cases.But we needed:
- Embedded branding inside the QR
- Precise control over size and styling
- Reusability across multiple screens
So we created a custom QR widget using Flutter.
Why a Custom Widget?
- Reusable across the app
- Full control over branding
- Clear separation between UI and business logic
Custom QR Widget (Example)
class EmbeddedQrCode extends StatelessWidget {
final double? width;
final double? height;
final String? value;
const EmbeddedQrCode({
Key? key,
this.width,
this.height,
this.value,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return QrImageView(
data: value ?? 'Default QR',
size: 200,
gapless: false,
embeddedImage: const AssetImage('assets/images/qr_logo.png'),
embeddedImageStyle: const QrEmbeddedImageStyle(
size: Size(30, 30),
),
);
}
}
This gave us complete visual control while staying fully compatible with FlutterFlow.
Step 3: Capturing a Widget Screenshot
FlutterFlow does not natively support screenshot capture.To solve this, we wrapped the QR card UI inside a Screenshot Wrapper Widget.
Why a Wrapper Widget?
- Keeps screenshot logic isolated
- Avoids UI duplication
- Reusable for both share and download actions
Screenshot Wrapper Example
import 'package:flutter/material.dart';
import 'package:screenshot/screenshot.dart';
class ScreenshotWrapper extends StatelessWidget {
const ScreenshotWrapper({
Key? key,
required this.child,
this.width,
this.height,
}) : super(key: key);
final Widget child;
final double? width;
final double? height;
static final ScreenshotController controller = ScreenshotController();
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height,
child: Screenshot(
controller: controller,
child: child,
),
);
}
}
Why the Screenshot Wrapper Matters
This wrapper acts as a bridge between FlutterFlow’s UI hierarchy and custom logic.
Key benefits:
- Clean UI structure
- No flickering or hidden widget issues
- No modal or screen dependencies
- Consistent, pixel-perfect output
This design choice made the feature reliable in production - not fragile.
Step 4: Sharing the QR Card (Custom Action)
FlutterFlow doesn’t support sharing rendered widgets natively.
We implemented a custom action that:
- Captures the widget as an image
- Converts it into a file
- Opens the native OS share dialog
final Uint8List? image =
await ScreenshotWrapper.controller.capture(pixelRatio: 2.5);
await Share.shareXFiles([
XFile.fromData(image, mimeType: 'image/png')
]);
This worked seamlessly across iOS and Android.
Step 5: Downloading the QR Card
For downloads, we reused the same screenshot logic and returned the image as a downloadable file.
return FFUploadedFile(
bytes: bytes,
name: 'qr_card.png',
);From the user’s perspective, this felt completely native - no hacks or workarounds.
Why This Hybrid Approach Works
This solution checked all the right boxes:
- Scalable – Reusable for passes, tickets, or certificates
- Maintainable – Clean separation of concerns
- FlutterFlow-friendly – No platform-breaking hacks
- Client-ready – Fully branded, consistent visuals
- Future-proof – Extendable to PDFs, templates, or watermarks
This is the exact balance teams look for when moving from rapid prototyping to production-grade mobile apps.
Key Takeaway for Product Teams
FlutterFlow gives you speed.
Custom code gives you differentiation.
When used intentionally, custom code:
- Fills platform gaps
- Enhances user experience
- Preserves low-code velocity
- Delivers production-grade quality
This project demonstrates how no-code speed and pro-code precision can coexist.
Final Thoughts
This is how we approach every FlutterFlow project:
- Use FlutterFlow for rapid design and iteration
- Use custom code where flexibility is required
- Deliver scalable, production-ready solutions - not workarounds
When FlutterFlow doesn’t support a feature, we don’t see a limitation.We see an opportunity to extend it cleanly, safely, and future-ready.
For teams planning serious products, this hybrid approach is often the difference between shipping fast and building something that actually lasts.



