# FlutterFlow Documentation > Learn how to build mobile, web and desktop apps incredibly fast — without sacrificing on app quality or features ## search - [Search the documentation](/search.md) ## tags - [Tags](/tags.md) - [One doc tagged with "Accessibility"](/tags/accessibility.md) - [6 docs tagged with "Action"](/tags/action.md) - [One doc tagged with "Action Blocks"](/tags/action-blocks.md) - [2 docs tagged with "Action Flow Editor"](/tags/action-flow-editor.md) - [13 docs tagged with "Actions"](/tags/actions.md) - [One doc tagged with "AdBanner"](/tags/ad-banner.md) - [One doc tagged with "AdMob"](/tags/ad-mob.md) - [One doc tagged with "Add Item"](/tags/add-item.md) - [10 docs tagged with "AI"](/tags/ai.md) - [One doc tagged with "AI Agent"](/tags/ai-agent.md) - [4 docs tagged with "Alerts & Notifications"](/tags/alerts-notifications.md) - [2 docs tagged with "Algolia"](/tags/algolia.md) - [One doc tagged with "Android"](/tags/android.md) - [4 docs tagged with "Animations"](/tags/animations.md) - [One doc tagged with "Anonymous Login"](/tags/anonymous-login.md) - [One doc tagged with "APIs"](/tags/ap-is.md) - [2 docs tagged with "API"](/tags/api.md) - [One doc tagged with "API Call"](/tags/api-call.md) - [2 docs tagged with "API Keys"](/tags/api-keys.md) - [One doc tagged with "App Builder"](/tags/app-builder.md) - [One doc tagged with "App Check"](/tags/app-check.md) - [2 docs tagged with "App Events"](/tags/app-events.md) - [One doc tagged with "App State"](/tags/app-state.md) - [3 docs tagged with "Apple App Store"](/tags/apple-app-store.md) - [One doc tagged with "Apple Authentication"](/tags/apple-authentication.md) - [One doc tagged with "Apple Login"](/tags/apple-login.md) - [One doc tagged with "Assets"](/tags/assets.md) - [2 docs tagged with "Auth Actions"](/tags/auth-actions.md) - [18 docs tagged with "Authentication"](/tags/authentication.md) - [2 docs tagged with "Automated Tests"](/tags/automated-tests.md) - [One doc tagged with "Backend"](/tags/backend.md) - [14 docs tagged with "Backend Logic"](/tags/backend-logic.md) - [10 docs tagged with "Backend Query"](/tags/backend-query.md) - [17 docs tagged with "Base Elements"](/tags/base-elements.md) - [One doc tagged with "Best Practices"](/tags/best-practices.md) - [One doc tagged with "Branching"](/tags/branching.md) - [One doc tagged with "Building Layout"](/tags/building-layout.md) - [One doc tagged with "Canvas"](/tags/canvas.md) - [4 docs tagged with "Chat"](/tags/chat.md) - [One doc tagged with "Child Widget"](/tags/child-widget.md) - [One doc tagged with "Claude Code"](/tags/claude-code.md) - [2 docs tagged with "Clear Delete Data"](/tags/clear-delete-data.md) - [5 docs tagged with "CLI"](/tags/cli.md) - [6 docs tagged with "Cloud Firestore"](/tags/cloud-firestore.md) - [One doc tagged with "Cloud Functions"](/tags/cloud-functions.md) - [2 docs tagged with "Cloud Storage"](/tags/cloud-storage.md) - [One doc tagged with "Code File"](/tags/code-file.md) - [One doc tagged with "Codex"](/tags/codex.md) - [5 docs tagged with "Collaboration"](/tags/collaboration.md) - [One doc tagged with "Collections"](/tags/collections.md) - [9 docs tagged with "Components"](/tags/components.md) - [24 docs tagged with "Concepts"](/tags/concepts.md) - [One doc tagged with "ConditionalBuilder"](/tags/conditional-builder.md) - [One doc tagged with "Conditional Logic"](/tags/conditional-logic.md) - [One doc tagged with "Configuration Files"](/tags/configuration-files.md) - [One doc tagged with "Constants"](/tags/constants.md) - [One doc tagged with "Content Manager"](/tags/content-manager.md) - [16 docs tagged with "Control Flow"](/tags/control-flow.md) - [One doc tagged with "Conversational UI"](/tags/conversational-ui.md) - [One doc tagged with "Crashlytics"](/tags/crashlytics.md) - [One doc tagged with "Creator Hub"](/tags/creator-hub.md) - [7 docs tagged with "Creators Hub"](/tags/creators-hub.md) - [2 docs tagged with "Custom Actions"](/tags/custom-actions.md) - [3 docs tagged with "Custom Authentication"](/tags/custom-authentication.md) - [9 docs tagged with "Custom Code"](/tags/custom-code.md) - [One doc tagged with "Custom Data Types"](/tags/custom-data-types.md) - [One doc tagged with "Custom Functions"](/tags/custom-functions.md) - [One doc tagged with "Custom Widgets"](/tags/custom-widgets.md) - [2 docs tagged with "Customizations"](/tags/customizations.md) - [7 docs tagged with "Data Representation"](/tags/data-representation.md) - [One doc tagged with "Data Types"](/tags/data-types.md) - [9 docs tagged with "Database"](/tags/database.md) - [One doc tagged with "Deep Linking"](/tags/deep-linking.md) - [6 docs tagged with "Deployment"](/tags/deployment.md) - [12 docs tagged with "Design"](/tags/design.md) - [One doc tagged with "Design System"](/tags/design-system.md) - [One doc tagged with "designer"](/tags/designer.md) - [One doc tagged with "Desktop App"](/tags/desktop-app.md) - [2 docs tagged with "Dev Environments"](/tags/dev-environments.md) - [One doc tagged with "Document"](/tags/document.md) - [2 docs tagged with "Download Data"](/tags/download-data.md) - [One doc tagged with "Dropdown"](/tags/dropdown.md) - [One doc tagged with "Dynamic Linking"](/tags/dynamic-linking.md) - [One doc tagged with "ElevenLabs"](/tags/eleven-labs.md) - [One doc tagged with "Email Authentication"](/tags/email-authentication.md) - [One doc tagged with "Email Login"](/tags/email-login.md) - [2 docs tagged with "Enterprise"](/tags/enterprise.md) - [One doc tagged with "Enums"](/tags/enums.md) - [One doc tagged with "Error Handling"](/tags/error-handling.md) - [2 docs tagged with "Export"](/tags/export.md) - [One doc tagged with "Facebook Login"](/tags/facebook-login.md) - [16 docs tagged with "Firebase"](/tags/firebase.md) - [2 docs tagged with "Firebase Storage"](/tags/firebase-storage.md) - [6 docs tagged with "Firestore"](/tags/firestore.md) - [One doc tagged with "Firestore Search"](/tags/firestore-search.md) - [One doc tagged with "Flex"](/tags/flex.md) - [One doc tagged with "Flutter"](/tags/flutter.md) - [52 docs tagged with "FlutterFlow"](/tags/flutter-flow.md) - [One doc tagged with "FlutterFlow CLI"](/tags/flutter-flow-cli.md) - [11 docs tagged with "FlutterFlow Designer"](/tags/flutter-flow-designer.md) - [One doc tagged with "flutterflow"](/tags/flutterflow.md) - [7 docs tagged with "Form"](/tags/form.md) - [10 docs tagged with "Form Elements"](/tags/form-elements.md) - [One doc tagged with "Forms"](/tags/forms.md) - [2 docs tagged with "Functions"](/tags/functions.md) - [2 docs tagged with "Gemini"](/tags/gemini.md) - [2 docs tagged with "Generated Code"](/tags/generated-code.md) - [One doc tagged with "Getting Started"](/tags/getting-started.md) - [2 docs tagged with "GitHub"](/tags/git-hub.md) - [One doc tagged with "GitHub Login"](/tags/git-hub-login.md) - [One doc tagged with "Global Properties"](/tags/global-properties.md) - [One doc tagged with "Google Analytics"](/tags/google-analytics.md) - [One doc tagged with "Google Authentication"](/tags/google-authentication.md) - [One doc tagged with "Google Cloud"](/tags/google-cloud.md) - [4 docs tagged with "Google Maps"](/tags/google-maps.md) - [One doc tagged with "Google OAuth"](/tags/google-o-auth.md) - [3 docs tagged with "Google Play Store"](/tags/google-play-store.md) - [One doc tagged with "Guidelines"](/tags/guidelines.md) - [One doc tagged with "Hero Animations"](/tags/hero-animations.md) - [One doc tagged with "Hot Reload"](/tags/hot-reload.md) - [One doc tagged with "iOS"](/tags/i-os.md) - [One doc tagged with "Implicit Animations"](/tags/implicit-animations.md) - [2 docs tagged with "Import"](/tags/import.md) - [2 docs tagged with "Initial Setup"](/tags/initial-setup.md) - [19 docs tagged with "Integration"](/tags/integration.md) - [One doc tagged with "Integrations"](/tags/integrations.md) - [One doc tagged with "Internationalization"](/tags/internationalization.md) - [One doc tagged with "Interstitial Ad"](/tags/interstitial-ad.md) - [One doc tagged with "Issue"](/tags/issue.md) - [One doc tagged with "Iterate"](/tags/iterate.md) - [One doc tagged with "JWT"](/tags/jwt.md) - [One doc tagged with "Launch URL"](/tags/launch-url.md) - [12 docs tagged with "Layout Elements"](/tags/layout-elements.md) - [One doc tagged with "Libraries"](/tags/libraries.md) - [One doc tagged with "Library"](/tags/library.md) - [One doc tagged with "Local Run"](/tags/local-run.md) - [One doc tagged with "Local Search"](/tags/local-search.md) - [One doc tagged with "Local Storage"](/tags/local-storage.md) - [One doc tagged with "Localization"](/tags/localization.md) - [One doc tagged with "Loops"](/tags/loops.md) - [One doc tagged with "Lottie Animation"](/tags/lottie-animation.md) - [One doc tagged with "Maps"](/tags/maps.md) - [11 docs tagged with "MarketPlace"](/tags/market-place.md) - [One doc tagged with "Marketplace"](/tags/marketplace.md) - [2 docs tagged with "MCP"](/tags/mcp.md) - [5 docs tagged with "Media Files"](/tags/media-files.md) - [One doc tagged with "Multiple Languages"](/tags/multiple-languages.md) - [One doc tagged with "MuxBroadcast"](/tags/mux-broadcast.md) - [One doc tagged with "My Teams"](/tags/my-teams.md) - [9 docs tagged with "Navigation"](/tags/navigation.md) - [2 docs tagged with "Notifications"](/tags/notifications.md) - [One doc tagged with "OpenAI"](/tags/open-ai.md) - [One doc tagged with "Organization"](/tags/organization.md) - [One doc tagged with "Page Navigation"](/tags/page-navigation.md) - [One doc tagged with "Page Transition Animations"](/tags/page-transition-animations.md) - [One doc tagged with "PageView"](/tags/page-view.md) - [One doc tagged with "Passing Data"](/tags/passing-data.md) - [4 docs tagged with "Payments"](/tags/payments.md) - [One doc tagged with "Performance Monitoring"](/tags/performance-monitoring.md) - [One doc tagged with "Periodic Action"](/tags/periodic-action.md) - [One doc tagged with "Permissions"](/tags/permissions.md) - [One doc tagged with "Phone Login"](/tags/phone-login.md) - [One doc tagged with "PinCode"](/tags/pin-code.md) - [One doc tagged with "Place Picker"](/tags/place-picker.md) - [One doc tagged with "PowerPoint"](/tags/power-point.md) - [One doc tagged with "Pre-checks"](/tags/pre-checks.md) - [One doc tagged with "Presentation"](/tags/presentation.md) - [One doc tagged with "Project"](/tags/project.md) - [6 docs tagged with "Project Management"](/tags/project-management.md) - [4 docs tagged with "Projects"](/tags/projects.md) - [One doc tagged with "Prompting"](/tags/prompting.md) - [One doc tagged with "Publishing"](/tags/publishing.md) - [One doc tagged with "Purchase Item"](/tags/purchase-item.md) - [One doc tagged with "Query"](/tags/query.md) - [One doc tagged with "Query Collection"](/tags/query-collection.md) - [2 docs tagged with "Quickstart"](/tags/quickstart.md) - [One doc tagged with "RadioButton"](/tags/radio-button.md) - [One doc tagged with "RatingBar"](/tags/rating-bar.md) - [One doc tagged with "Razorpay"](/tags/razorpay.md) - [One doc tagged with "Real-Time Database"](/tags/real-time-database.md) - [One doc tagged with "Refactor"](/tags/refactor.md) - [One doc tagged with "Reference"](/tags/reference.md) - [One doc tagged with "Refresh"](/tags/refresh.md) - [One doc tagged with "Refund Policy"](/tags/refund-policy.md) - [One doc tagged with "Remote Config"](/tags/remote-config.md) - [One doc tagged with "Resource Hierarchy"](/tags/resource-hierarchy.md) - [One doc tagged with "Responsive Layout"](/tags/responsive-layout.md) - [One doc tagged with "RevenueCat"](/tags/revenue-cat.md) - [One doc tagged with "Review Dispute"](/tags/review-dispute.md) - [One doc tagged with "Rive Animation"](/tags/rive-animation.md) - [One doc tagged with "Rules"](/tags/rules.md) - [One doc tagged with "Run"](/tags/run.md) - [One doc tagged with "Search"](/tags/search.md) - [2 docs tagged with "Security"](/tags/security.md) - [One doc tagged with "Serverless"](/tags/serverless.md) - [3 docs tagged with "Setup"](/tags/setup.md) - [One doc tagged with "Share Action"](/tags/share-action.md) - [One doc tagged with "Simple Search"](/tags/simple-search.md) - [One doc tagged with "Slider"](/tags/slider.md) - [One doc tagged with "Slides"](/tags/slides.md) - [One doc tagged with "SOAP APIs"](/tags/soap-ap-is.md) - [One doc tagged with "Special Page Navigations"](/tags/special-page-navigations.md) - [2 docs tagged with "SQLite"](/tags/sq-lite.md) - [2 docs tagged with "State Management"](/tags/state-management.md) - [One doc tagged with "Storage Rules"](/tags/storage-rules.md) - [One doc tagged with "Storyboard"](/tags/storyboard.md) - [One doc tagged with "Streaming APIs"](/tags/streaming-ap-is.md) - [One doc tagged with "Stripe"](/tags/stripe.md) - [One doc tagged with "Style Guide"](/tags/style-guide.md) - [One doc tagged with "Subcollections"](/tags/subcollections.md) - [One doc tagged with "Submit Feedback"](/tags/submit-feedback.md) - [8 docs tagged with "Supabase"](/tags/supabase.md) - [One doc tagged with "TabBar"](/tags/tab-bar.md) - [6 docs tagged with "Testing"](/tags/testing.md) - [One doc tagged with "TextField"](/tags/text-field.md) - [3 docs tagged with "Time-Based Logic"](/tags/time-based-logic.md) - [One doc tagged with "Timer Widget"](/tags/timer-widget.md) - [One doc tagged with "Tokens"](/tags/tokens.md) - [One doc tagged with "Toolbar"](/tags/toolbar.md) - [One doc tagged with "Tools"](/tags/tools.md) - [One doc tagged with "Triggers"](/tags/triggers.md) - [3 docs tagged with "Troubleshooting"](/tags/troubleshooting.md) - [9 docs tagged with "UI"](/tags/ui.md) - [One doc tagged with "UI/UX"](/tags/ui-ux.md) - [2 docs tagged with "Upload Data"](/tags/upload-data.md) - [One doc tagged with "User Navigation"](/tags/user-navigation.md) - [One doc tagged with "Validation"](/tags/validation.md) - [2 docs tagged with "Variables"](/tags/variables.md) - [2 docs tagged with "Versioning"](/tags/versioning.md) - [One doc tagged with "Wait Action"](/tags/wait-action.md) - [One doc tagged with "Web Publishing"](/tags/web-publishing.md) - [One doc tagged with "WebView"](/tags/web-view.md) - [10 docs tagged with "Widget"](/tags/widget.md) - [One doc tagged with "Widget Animations"](/tags/widget-animations.md) - [One doc tagged with "Widget Palette"](/tags/widget-palette.md) - [One doc tagged with "Widget Tree"](/tags/widget-tree.md) - [8 docs tagged with "Widgets"](/tags/widgets.md) - [One doc tagged with "Workspace"](/tags/workspace.md) - [One doc tagged with "Wrap"](/tags/wrap.md) ## accounts-billing - [Account Management](/accounts-billing/account-management.md): This section contains information on changing your password, verifying your email, and deleting your account. - [Manage Custom Domains](/accounts-billing/manage-custom-domains.md): All paid plans include one free custom domain, with the option to purchase more if needed. - [Payments & Billing](/accounts-billing/payments-billing.md): This section contains information on the payment methods we accept and how to change your payment method. - [Plan Comparison](/accounts-billing/plan-comparison.md): Compare FlutterFlow plans and features to find the right plan for your needs - [Plans & Pricing](/accounts-billing/plan-pricing.md): For our most up-to-date information, please visit FlutterFlow pricing. - [Privacy And Terms Of Service](/accounts-billing/privacy-terms-of-service.md): How do I request the deletion of my personal data? - [Referral Program](/accounts-billing/referral-program.md): With the retirement of the Pro plan, the existing referral program has been discontinued. Any active referral discounts will end at your next renewal. However, referral credits you’ve already earned will remain in your account and can be redeemed for free months on the new Growth plan. - [Refunds](/accounts-billing/subscriptions/refunds.md): If you're not happy with your FlutterFlow subscription, you can cancel at any time. - [Subscriptions](/accounts-billing/subscriptions/subscriptions.md): This section provides information on free trials, plan changes, and other subscription-related questions. ## before-you-begin - [App Development](/before-you-begin/app-architecture.md): Before you jump in and start using FlutterFlow, it's helpful to have an idea of how app development works more broadly. - [Create an account](/before-you-begin/setup-flutterflow.md): Ensure you meet system requirements and grasp technical concepts for smooth building in FlutterFlow. ## best-practices - [Best Practices: Secure API Keys](/best-practices/secure-api-keys.md): Learn best practices for securing API keys in your FlutterFlow app, including key restrictions, geographical restrictions, IP address binding, and service-specific limitations. ## collaboration - [Branching](/collaboration/branching.md): Learn how branching in FlutterFlow allows you to add new features without disrupting your current progress. Understand the workflow of creating and merging branches, resolving conflicts, and the difference between merging and rebasing, with practical examples and tips. - [Saving and Versioning](/collaboration/saving-versioning.md): Learn about versioning in your FlutterFlow. ## concepts - [Accessibility](/concepts/accessibility.md): Learn how to make your app accessible to everyone. - [Integrating Native SDKs Using Method Channels](/concepts/advanced/method-channels.md): Learn how to integrate third-party native SDKs into your FlutterFlow project using Method Channels. This guide walks through setting up channels, writing native code, and connecting it back to FlutterFlow. - [AI Agent](/concepts/ai-agent.md): Use AI Agent from the FlutterFlow desktop app to set up supported AI agent CLI tools, connect them to your project, and build with natural-language prompts. - [Alert Dialog](/concepts/alerts/alert-dialog.md): The action allows you to alert the user of important situations that require acknowledgment in the form of a pop-up or custom-designed dialog. With this feature, you can choose to display a pre-built pop-up or create a custom design that suits your specific requirements. - [Dismiss Custom Dialog](/concepts/alerts/dismiss-custom-dialog.md): With this action, you can easily close the custom dialog, providing a convenient way for users to dismiss it. This functionality is handy when you want to give users the option to close the dialog from any widget within it, like a close button. - [Haptic Feedback](/concepts/alerts/haptic-feedback.md): Using this action, you can vibrate the user's device. Typically this is used to draw users' attention to the action they have performed. For example, vibrating the user's device on setting the alarm. - [Animations](/concepts/animations.md): Learn the basics of animations in FlutterFlow. - [Hero Animation](/concepts/animations/hero-animations.md): Learn how to add Hero Animations in your FlutterFlow app. - [Implicit Animations](/concepts/animations/implicit.md): Learn how to add implicit animations in FlutterFlow. - [Lottie Animation](/concepts/animations/lottie-animation.md): Learn how to add Lottie animation in your FlutterFlow app. - [Page Transition Animations](/concepts/animations/page-transition.md): Learn how to add page transition animations in your FlutterFlow app. - [Rive Animation](/concepts/animations/rive-animation.md): Learn how to add Rive animation in your FlutterFlow app. - [Shaders](/concepts/animations/shaders.md): Learn how to add visual effects using Shaders in your FlutterFlow app. - [Widget Animations](/concepts/animations/widget-animations.md): Learn how to add widget animations in FlutterFlow. - [App Events Integrations](/concepts/app-event-integration.md): Feed local app events into GenUI so the conversation can react to live app state and time-sensitive signals. - [App Events](/concepts/app-events.md): Learn how to use App Events in FlutterFlow. - [Component Catalog](/concepts/component-catalog.md): Configure the FlutterFlow components that GenUI is allowed to render inside the chat surface. - [Custom Code](/concepts/custom-code.md): Learn how to write and integrate custom code in your FlutterFlow app to add custom functionalities. - [Cloud Functions](/concepts/custom-code/cloud-functions.md): Learn how to use Cloud Functions in your FlutterFlow app for serverless backend functionality. - [Code File](/concepts/custom-code/code-file.md): Learn how to create and use custom classes and enums in FlutterFlow. - [Common Code Examples](/concepts/custom-code/common-examples.md): Learn about the common custom code examples and use it directly in your project. - [Configuration Files](/concepts/custom-code/configuration-files.md): Learn how to modify platform-specific files for Android and iOS to extend your app's capabilities. - [Custom Actions](/concepts/custom-code/custom-actions.md): Learn how to create and use custom actions in your FlutterFlow app to enhance functionality. - [Custom Functions](/concepts/custom-code/custom-functions.md): Learn how to create and use custom functions in your FlutterFlow app to add custom functionalities. - [Custom Widgets](/concepts/custom-code/custom-widgets.md): Learn how to create and use custom widgets in your FlutterFlow app to enhance its user interface. - [FlutterFlow Visual Studio Extension](/concepts/custom-code/vscode-extension.md): Learn how to leverage the Visual Studio Code Extension to write custom code. - [Design System](/concepts/design-system.md): Discover how to create a consistent UI/UX across your app with a design system in FlutterFlow. - [File Handling](/concepts/file-handling.md): Learn how to handle media files in FlutterFlow. - [Clear or Delete Media](/concepts/file-handling/clear-delete-media.md): Learn how to add clear and delete file actions into your FlutterFlow app. - [Displaying Media](/concepts/file-handling/displaying-media.md): Learn how to display media in FlutterFlow. - [Download File](/concepts/file-handling/download-file.md): Learn how to add download file action into your FlutterFlow app. - [Uploading Files](/concepts/file-handling/uploading-files.md): Learn how to upload media in FlutterFlow. - [GenUI Chat](/concepts/genui-chat.md): Add a conversational AI surface to your FlutterFlow app that can render catalog components, call action blocks as tools, and react to local app events. - [Building Layout](/concepts/layouts.md): Learn how to build layout in your FlutterFlow app. - [ConditionalBuilder](/concepts/layouts/conditional-builder.md): Learn how to display different widgets based on certain conditions in your FlutterFlow app. - [Flex](/concepts/layouts/flex.md): Learn how to add the Flex widget in your FlutterFlow app. - [Responsive Layout](/concepts/layouts/responsive.md): Learn how to create responsive layout in your FlutterFlow app. - [Wrap](/concepts/layouts/wrap.md): Learn how to add the Wrap widget in your FlutterFlow app. - [Localization](/concepts/localization.md): Learn how to make your app work for different languages. - [Bottom Sheet](/concepts/navigation/bottom-sheet.md): A Bottom Sheet is an alternative to a menu or a dialog. It opens from bottom to top and can be dismissed by swiping it from top to bottom. When it opens, it prevents the user from interacting with the rest of the app. - [Deep & Dynamic Linking](/concepts/navigation/deep-dynamic-linking.md): Learn how to implement deep and dynamic linking in your FlutterFlow app. - [Generate Current Page Link](/concepts/navigation/generate-current-page-link.md): Learn how to generate the current page link in your FlutterFlow app. - [Launch URL [Action]](/concepts/navigation/launch-url.md): Learn how to use the Launch URL Action in FlutterFlow to open URLs with supporting apps. - [Overview](/concepts/navigation/overview.md): Learn how to add navigation in FlutterFlow. - [Page Navigation](/concepts/navigation/page-navigation.md): Learn how to navigate between pages in FlutterFlow. - [PageView](/concepts/navigation/pageview.md): Learn how to use the PageView widget for creating swipeable pages, perfect for creating onboarding screens or multi-step forms. - [Passing Data between Pages](/concepts/navigation/passing-data.md): Learn how to pass data between pages in FlutterFlow. - [Share [Action]](/concepts/navigation/share-action.md): Learn how to use the Share Action in your FlutterFlow app to share content. - [Overview](/concepts/navigation/special-page-navigations.md): Learn how to add Special Page Navigations in FlutterFlow. - [TabBar](/concepts/navigation/tabbar.md): Learn how to use the TabBar widget in FlutterFlow to create a horizontal row of tabs for navigating different content views in your app. - [WebView](/concepts/navigation/webview.md): Learn how to use the WebView widget in FlutterFlow to display website content directly within your app. - [Notifications](/concepts/notifications.md): Learn how to add notifications in FlutterFlow. - [OneSignal](/concepts/notifications/one-signal.md): Integrating OneSignal lets you send emails and SMS (text messages) to your users. This can help you - [Push Notifications](/concepts/notifications/push-notifications.md): Push Notifications let you deliver time-sensitive, real-time messages to users even when the app isn’t active. These notifications rely on Firebase Cloud Messaging (FCM) behind the scenes, which routes messages to both Android and iOS devices. When integrated correctly, you can use push notifications to: - [State Management](/concepts/state-management.md): An overview of state management & state variables in FlutterFlow. - [Widget State](/concepts/state-management/widget-state.md): Widget state refers to the data or information that a widget holds, which can change over time and affect the widget's appearance or behavior. In FlutterFlow, the state is particularly important for form widgets, such as text fields, checkboxes, and radio buttons, as it allows these widgets to respond to user interactions. - [Tools Configuration](/concepts/tools.md): Expose Action Blocks to GenUI so the model can fetch data, run workflows, and use real results in its responses. ## deployment - [Apple App Store Deployment](/deployment/apple-app-store-deployment.md): Learn how to seamlessly deploy your apps to the Apple App Store using FlutterFlow. - [Deploy for Development Environments](/deployment/deploy-for-environments.md): Learn how to deploy your apps for development environments. - [Deploy from GitHub](/deployment/deploy-from-github.md): Learn how to deploy your apps directly from GitHub branch. - [Google Play Store Deployment](/deployment/google-playstore-deployment.md): Learn how to seamlessly deploy your apps to the Google Play Store using FlutterFlow. - [Pre-checks Before Publishing](/deployment/pre-checks-before-publishing.md): Ensure your app is ready for launch with this detailed guide on essential pre-publishing checks. - [Web Publishing](/deployment/web-publishing.md): Discover how to effortlessly publish your applications on the web with FlutterFlow. This guide covers everything from enabling web support to deploying your app and adding custom domains. ## designer - [Craft your intent on the canvas. Bring the cost of design iteration to zero.](/designer.md): Discover FlutterFlow Designer—the fastest way to design apps. Explore its key features, understand how it works, and start designing your first app with ease. - [Collaboration](/designer/collaboration.md): Work on the same design together — in real time, with comments and shared access. - [Components](/designer/components.md): Learn how to create reusable UI components, add variants and toggles, and manage dynamic behavior using parameters and expressions in FlutterFlow Designer. - [Export](/designer/export.md): Export your FlutterFlow Designer designs as PNGs, agent-ready prompts, or directly into a FlutterFlow project. - [Import from FlutterFlow](/designer/import.md): Bring existing FlutterFlow screens into Designer to enhance layouts, explore new styles, and refine the user experience faster. - [Integrations](/designer/integrations.md): Connect FlutterFlow Designer with AI agents and developer tools to generate, edit, and update designs using natural language from your preferred environment. - [Iterate](/designer/iterate.md): Refine and improve your generated screens visually, with AI prompts, or by editing the global theme. - [Prompting](/designer/prompting.md): Generate your first app design from a prompt. Explore styles, attach reference images, and create a complete storyboard from a single description. - [Quickstart](/designer/quickstart.md): Get started with designing your first app quickly. - [Slides](/designer/slides.md): Turn a FlutterFlow Designer project into a presentation deck. Design 16:9 slides, present with presenter view, and import or export PowerPoint files. - [Workspace](/designer/workspace.md): Learn about FlutterFlow Designer's workspace that provide a complete design environment with specialized tools. ## exporting - [Push to GitHub Repo](/exporting/push-to-github.md): Learn how to connect your FlutterFlow project to a GitHub repository and manage custom code. ## flutterflow-cli - [FlutterFlow CLI](/flutterflow-cli.md): Learn how to download and manage your FlutterFlow projects locally using the FlutterFlow CLI. - [Build with AI Agents](/flutterflow-cli/build.md): Create and edit FlutterFlow projects from your terminal using your preferred AI coding agent. - [Claude Code Plugin](/flutterflow-cli/claude-code.md): Use the FlutterFlow plugin in the Claude Code terminal or desktop app to build and edit FlutterFlow apps with AI, including automatic CLI install, secure API key setup, and a guided build workflow. - [Build with Codex](/flutterflow-cli/codex.md): Use the FlutterFlow plugin in Codex CLI or the ChatGPT desktop app to create, edit, and export FlutterFlow projects with natural-language prompts. - [Exporting Projects](/flutterflow-cli/exporting.md): Learn how to download and manage your FlutterFlow projects locally using the FlutterFlow CLI. ## flutterflow-ui - [App Builder](/flutterflow-ui/builder.md): Explore the App Builder in FlutterFlow, featuring a comprehensive interface with four main sections-Navigation Menu, Toolbar, Canvas, and Properties Panel. - [Canvas](/flutterflow-ui/canvas.md): Dive into the versatile Canvas in FlutterFlow, where you can effortlessly design and preview your app’s interface. - [Dashboard](/flutterflow-ui/dashboard.md): Explore the dashboard in FlutterFlow, a centralized location for managing projects and your account. - [My Teams](/flutterflow-ui/my-teams.md): On the My Teams page, you can manage billing for your team, edit projects simultaneously, and share code, design systems, APIs, and assets. This makes collaboration between team members much easier and helps keep everyone on the same page. Even if you don't have team members, you can still use this page to share resources between your own projects and keep your development process organized. - [Resource Hierarchy Overview](/flutterflow-ui/resource-hierarchy.md): Explore the Resource Hierarchy Overview to understand the correlation between traditional Flutter app components and their equivalents in FlutterFlow. - [Storyboard](/flutterflow-ui/storyboard.md): Master the Storyboard view in FlutterFlow to visualize your app’s design and user navigation. The Storyboard allows you to see screens and interactions, ensuring a seamless user experience. - [Toolbar](/flutterflow-ui/toolbar.md): Learn how to use the FlutterFlow Toolbar to access project management, version control, help, testing, and development tools. - [Widget Palette](/flutterflow-ui/widget-palette.md): Explore the Widget Palette in FlutterFlow to access a wide range of UI elements. This feature offers an intuitive interface for dragging and dropping Flutter widgets onto your canvas. ## generated-code - [Generated Code: Components](/generated-code/component-model.md): Similar to a Page, when creating a component in FlutterFlow, it automatically generates two files: a Widget class and a Model class. - [DataTypeStruct class](/generated-code/custom-data-types.md): This guide uses example of the generated code of the EcommerceFlow demo app. To view the generated code directly, check out the Github repository. - [FFAppState](/generated-code/ff-app-state.md): This guide uses example of the generated code of the EcommerceFlow demo app. To view the generated code directly, check out the Github repository. - [FlutterFlow Model](/generated-code/flutterflow-model.md): The FlutterFlowModel class is an abstract class used in FlutterFlow to provide a unified and extensible structure for managing state and behavior of widgets (both pages and components). It encapsulates initialization, state management, and disposal logic, making it easier to handle the lifecycle of widgets and their models. - [Generated Code: Pages](/generated-code/page-model.md): When you create a new Page in FlutterFlow, it automatically generates two files: a Widget class and a Model class. So if the name of the page you created is called ProductListPage, FlutterFlow generation backend will automatically create ProductListPageWidget class and ProductListPageModel class. - [Directory Structure](/generated-code/project-structure.md): This guide uses example of the generated code of the EcommerceFlow demo app. To view the generated code directly, check out the Github repository. - [FlutterFlow State Management](/generated-code/state-management.md): Learn about the state management used in FlutterFlow's generated code. ## integrations - [AdMob](/integrations/ads/admob.md): Learn how to add AdMob in your FlutterFlow app. - [AI Agents](/integrations/ai-agents.md): Learn how to add AI Agents for chat, image generation, video generation, text-to-speech, and speech-to-text in your FlutterFlow app. - [Authentication Methods Overview](/integrations/authentication-methods.md): Authentication enables users to create accounts and log into your app, establishing a secure, - [Overview](/integrations/authentication-types.md): Learn about integrating various authentication services like Firebase, Supabase, and Custom Authentication in FlutterFlow. - [Custom Authentication](/integrations/authentication/custom-authentication.md): Learn how to add custom authentication in your FlutterFlow app. - [Anonymous Login](/integrations/authentication/firebase/anonymous-login.md): Learn how to implement anonymous login in your FlutterFlow app. - [Apple Login](/integrations/authentication/firebase/apple.md): Learn how to add Apple login in your FlutterFlow app. - [Common Auth Actions](/integrations/authentication/firebase/auth-actions.md): Learn how to add Firebase Authentication actions in your FlutterFlow app. - [Email Login using Firebase](/integrations/authentication/firebase/email-login.md): Learn how to add Email Login in your FlutterFlow app. - [Facebook Login](/integrations/authentication/firebase/facebook.md): Learn how to add Facebook login in your FlutterFlow app. - [GitHub Login](/integrations/authentication/firebase/github.md): Learn how to add GitHub authentication in your FlutterFlow app. - [Google Login](/integrations/authentication/firebase/google-oauth-login.md): Learn how to add Google OAuth login in your FlutterFlow app. - [Enabling Firebase Auth in FlutterFlow](/integrations/authentication/firebase/initial-setup.md): Learn how to perform the initial setup for Firebase authentication in your FlutterFlow app. - [JWT Token Authentication](/integrations/authentication/firebase/jwt-auth.md): Learn how to implement JWT authentication in your FlutterFlow app. - [Phone Login](/integrations/authentication/firebase/phone.md): Learn how to add phone login in your FlutterFlow app. - [Authentication: Generated Code](/integrations/authentication/generated-code.md): Learn about the generated code behind enabling authentication in FlutterFlow. - [Apple Login](/integrations/authentication/supabase/apple.md): Learn how to integrate Apple Login of Supabase Auth into your FlutterFlow app. - [Authentication Actions](/integrations/authentication/supabase/auth-actions.md): Learn how to add Supabase Authentication actions in your FlutterFlow app. - [Email Authentication](/integrations/authentication/supabase/email.md): Learn how to integrate Email Login of Supabase Auth into your FlutterFlow app. - [Google Login](/integrations/authentication/supabase/google.md): Learn how to integrate Google Login of Supabase Auth into your FlutterFlow app. - [Initial Setup](/integrations/authentication/supabase/initial-setup.md): Learn how to perform the initial setup for Supabase Authentication in your FlutterFlow app. - [Tokens: Types and Lifespans](/integrations/authentication/tokens.md): Learn about the types and lifespans of tokens in custom authentication. - [Creating Collections](/integrations/database/cloud-firestore/creating-collections.md): Learn how to create collections in Firestore for your FlutterFlow app, including organizing documents within collections. - [Creating Subcollections](/integrations/database/cloud-firestore/creating-subcollections.md): Learn how to create subcollections in Firestore for your FlutterFlow app, including organizing documents within subcollections. - [Firestore Actions](/integrations/database/cloud-firestore/firestore-actions.md): Learn about Firestore actions in your FlutterFlow app, including how to perform various database operations. - [Firestore Content Manager](/integrations/database/cloud-firestore/firestore-content-manager.md): Learn how to use the Firestore Content Manager in your FlutterFlow app to manage Firestore data efficiently. - [Firestore Rules](/integrations/database/cloud-firestore/firestore-rules.md): Learn how to deploy Firestore rules in your FlutterFlow app to manage data access and security. - [Cloud Firestore](/integrations/database/cloud-firestore/getting-started.md): Learn how to get started with Cloud Firestore in your FlutterFlow app to manage your app's data. - [Refresh Database Request [Action]](/integrations/database/refresh-db-request.md): Learn how to use the Refresh DB Request action in your FlutterFlow app to refresh your database content. - [SQLite](/integrations/database/sqlite.md): Learn how to quickly get started with SQLite in your FlutterFlow app for local data storage. - [Supabase Database Actions](/integrations/database/supabase/database-actions.md): Learn about Supabase Database actions in your FlutterFlow app, including how to perform various database operations. - [Import from FF Designer](/integrations/designer/import-from-ff-designer.md): Learn how to export screens from FF Designer and import them into FlutterFlow. - [Firebase Storage Library](/integrations/firebase-storage/storage-library.md): The Firebase Storage Library provides access to the files in Cloud Storage through the Firebase SDK beyond what FlutterFlow's built-in support provides. - [Storage Rules](/integrations/firebase-storage/storage-rules.md): Learn how to deploy storage rules in your FlutterFlow app to manage and secure your Firebase storage. - [App Check](/integrations/firebase/app-check.md): Learn how to integrate Firebase App Check in your FlutterFlow app. - [Connect to Firebase](/integrations/firebase/connect-to-firebase.md): Learn how to integrate Firebase with your FlutterFlow app to add user authentication, cloud storage, real-time databases, and more. - [Firebase Crashlytics](/integrations/firebase/crashlytics.md): Learn how to integrate Firebase Crashlytics in your FlutterFlow app. - [Performance Monitoring](/integrations/firebase/performance-monitoring.md): Learn how to integrate Firebase Performance Monitoring in your FlutterFlow app. - [Remote Config](/integrations/firebase/remote-config.md): Learn how to integrate Firebase Remote Config in your FlutterFlow app. - [Gemini](/integrations/gemini.md): Learn how to get started with the Gemini action in your FlutterFlow app to generate text, process text-and-image inputs, and count tokens. - [Google Analytics](/integrations/google-analytics.md): Learn how to setup Google Analytics in FluterFlow - [Maps & Places APIs](/integrations/google-maps/generate-maps-keys.md): Learn how to generate and use Maps keys for Google Maps integration in your FlutterFlow app. - [Google Maps Widget](/integrations/google-maps/google-maps-widget.md): Learn how to add and configure the Google Maps widget in your FlutterFlow app. - [Move Map Center [Action]](/integrations/google-maps/move-map-center-action.md): Learn how to use the Move Map Center action in your FlutterFlow app to adjust the center of the Google Map. - [Place Picker Widget](/integrations/google-maps/place-picker-widget.md): Learn how to add and configure the Place Picker widget in your FlutterFlow app. - [Static Map Widget](/integrations/mapbox/staticmap-widget.md): Learn how to add and configure the StaticMap (Mapbox) widget in your FlutterFlow app. - [Launch Map](/integrations/maps/launch-map.md): Learn how to open Map app installed on your device from your FlutterFlow app. - [Mux Livestream](/integrations/mux.md): Learn how to get started with MuxBroadcast in your FlutterFlow app for live video broadcasting. - [Braintree](/integrations/payments/braintree.md): Learn how to integrate Braintree payments in your FlutterFlow app. - [RazorPay](/integrations/payments/razorpay.md): Learn how to integrate Razorpay in your FlutterFlow app. - [RevenueCat](/integrations/payments/revenuecat.md): Learn how to integrate RevenueCat payments in your FlutterFlow app. - [Stripe](/integrations/payments/stripe.md): Learn how to integrate Stripe in your FlutterFlow app. - [Algolia](/integrations/search/algolia-search.md): Learn how to implement algolia search functionality in your FlutterFlow app. - [Simple Search](/integrations/search/simple-search.md): Learn how to implement simple search functionality in your FlutterFlow app to search local data on a device. - [Supabase Setup](/integrations/supabase/setup.md): Learn how to set up Supabase in your FlutterFlow app for database and authentication functionalities. ## marketplace - [Adding & Purchasing Items](/marketplace/adding-purchasing-item.md): Learn how to add and purchase FlutterFlow marketplace items. - [Creators Hub](/marketplace/creators-hub.md): This section is designed to provide you with all the necessary information to contribute effectively and responsibly to Marketplace. - [Copyright (DMCA) Process](/marketplace/creators-hub/copyright-dmca-process.md): Understand the copyright (DMCA) process on FlutterFlow Marketplace. - [Creator FAQs](/marketplace/creators-hub/creator-faqs.md): Learn about creator's FAQs in FlutterFlow Marketplace. - [Legal Guidelines for Creators](/marketplace/creators-hub/legal-guidelines-for-creators.md): Understand the legal guidlines for creating marketplace items. - [Navigating External Licenses](/marketplace/creators-hub/navigating-external-licenses.md): Understand the key concepts that will assist you in creating unique and compliant content for FlutterFlow Marketplace. - [FlutterFlow Marketplace Review Dispute Guidelines](/marketplace/creators-hub/review-dispute-guidelines.md): Learn about FlutterFlow Marketplace review dispute process and when reviews may be removed or modified. - [Item Submission Criteria](/marketplace/creators-hub/submission-criteria.md): Learn about marketplace item submission criteria. - [Submitting Item for Review](/marketplace/creators-hub/submit-item-for-review.md): Learn how to submit an item to the FlutterFlow Marketplace. - [Refund Policy](/marketplace/refund-policy.md): Learn more about the refund policy of FlutterFlow marketplace. - [Submitting Feedback for Items](/marketplace/submit-feedback.md): Learn more about the submitting feedback on FlutterFlow marketplace items. ## misc - [Additional Resources To Get Help](/misc/additional-resources.md): FlutterFlow community forum - [Application & Data Ownership](/misc/application-data-ownership.md): Intellectual Property - [Customer Support Policy](/misc/customer-support-policy.md): We love connecting with our users and supporting you as you build your application! However, there are a few things that fall outside the scope of our support team. To avoid confusion, we've created this document to outline our Customer Support Policy. - [Enterprise](/misc/enterprise.md): Learn how to use FlutterFlow for Enterprise. - [Hire FlutterFlow Developer](/misc/hire-flutterflow-developer.md): Learn how to hire a FlutterFlow Developer. - [Security](/misc/security.md): At FlutterFlow, we consider security to be our utmost priority. We understand the importance of safeguarding your data and ensuring a secure environment for our users. Below, we provide an overview of our security measures to give you confidence in the safety of your information. - [Submit Bug Reports](/misc/submit-bug-report.md): Learn how to submit bug report. ## quickstart - [Quickstart Guide](/quickstart.md): Build your first interactive FlutterFlow app by creating a layout, customizing its style, managing state, and testing the result. ## resources - [Create & Test API Call](/resources/backend-logic/create-test-api.md): In this guide, you'll learn how to create and test API calls in FlutterFlow. Integrating API calls allows your app to interact with external services, bringing in real-time data and functionality that enhances your app's capabilities. - [API Calls](/resources/backend-logic/rest-api.md): Learn the basics of making API calls in your backend logic. - [SOAP APIs](/resources/backend-logic/soap-api.md): Learn how to use SOAP APIs in your backend logic with FlutterFlow. - [Streaming APIs](/resources/backend-logic/streaming-api.md): Learn how to use streaming APIs in your backend logic with FlutterFlow. - [Backend Query](/resources/backend-query.md): Learn about backend queries in your FlutterFlow app, including how to set up and manage queries. - [Algolia Search Query](/resources/backend-query/algolia-search-query.md): Learn how to perform an Algolia search query in your FlutterFlow app. - [API Call Query](/resources/backend-query/api-call-query.md): Learn how to perform an API call query in your FlutterFlow app. - [Document from Reference](/resources/backend-query/document-from-reference.md): Learn how to retrieve a document from a reference in your FlutterFlow app. - [Query Collection / Table](/resources/backend-query/query-collection.md): Learn how to query a collection in your FlutterFlow app. - [SQLite Query](/resources/backend-query/sqlite-query.md): Learn how to perform SQLite queries in your FlutterFlow app. - [Control Flow Concepts](/resources/control-flow-concepts.md): Understand and implement control flow in your FlutterFlow app to manage the execution of statements, instructions, and function calls under various conditions. - [Control Flow & Logic](/resources/control-flow-overview.md): Control flow in programming refers to the order in which individual statements, instructions, or - [Overview](/resources/data-representation.md): Explore the essentials of data representation in app development, focusing on the use of variables in FlutterFlow. - [App State](/resources/data-representation/app-state.md): Learn how to effectively utilize App State Variables in FlutterFlow to maintain and manage global application states across all pages and components. - [Constants](/resources/data-representation/constants.md): Explore the importance of using Constants in FlutterFlow to define unchanging values throughout your application. - [Custom Data Types](/resources/data-representation/custom-data-types.md): Learn how to create and utilize custom data types in FlutterFlow to handle complex data structures that predefined types can't cover. - [Data Types](/resources/data-representation/data-types.md): Dive into the diverse range of data types supported by FlutterFlow, from basic primitives like integers and strings to complex composite types and built-in functionalities tailored for app development. - [Enums](/resources/data-representation/enums.md): Learn how Enums can enhance the management of application states, product types, and process statuses by providing a robust method to handle predefined sets of values. - [Global Properties](/resources/data-representation/global-properties.md): Discover the role of Global Properties in FlutterFlow, which provide universal access across all pages of your app to facilitate common tasks and enhance functionality. - [Variable](/resources/data-representation/variables.md): Variables - [Forms Overview](/resources/forms.md): Learn how to work with Forms in FlutterFlow app. - [Checkbox](/resources/forms/checkbox.md): Learn how to add Checkbox, CheckboxGroup, and CheckboxListTile widget in your FlutterFlow app. - [ChoiceChips](/resources/forms/choice-chips.md): Learn how to add ChoiceChips in your FlutterFlow app. - [Dropdown](/resources/forms/dropdown.md): Learn how to add Dropdown widget in your FlutterFlow app. - [Form Triggers](/resources/forms/form-triggers.md): Learn how to use Form Triggers in FlutterFlow to create dynamic, interactive user experiences by responding to user input on widgets like dropdowns, sliders, toggles, and text fields. - [Form Validation](/resources/forms/form-validation.md): Learn how to add Form Validation widget in your FlutterFlow app. - [RadioButton](/resources/forms/radiobutton.md): Learn how to add RadioButton widget in your FlutterFlow app. - [Reset Form Field [Action]](/resources/forms/reset-form-field.md): Learn how to add Reset Form Field action in your FlutterFlow app. - [Set Form Field [Action]](/resources/forms/set-form-field.md): Learn how to add Set Form Field action in your FlutterFlow app. - [Switch Widgets](/resources/forms/switch.md): Learn how to add Switch and SwitchListTile widget in your FlutterFlow app. - [TextField](/resources/forms/textfield.md): Learn how to add TextField widget in your FlutterFlow app. - [Action Blocks](/resources/functions/action-blocks.md): Learn how to use Action Blocks in your FlutterFlow app to and create reusable actions. - [Actions](/resources/functions/action-flow-editor.md): Learn how to use the Action Flow Editor in your FlutterFlow app to manage and streamline your backend logic. - [Action Triggers](/resources/functions/action-triggers.md): Explore the action triggers available in FlutterFlow. - [Conditional Logic](/resources/functions/conditional-logic.md): Learn how to implement conditional logic in your FlutterFlow app to control the flow of actions or generate properties based on certain conditions. - [Loops](/resources/functions/loops.md): Learn how to implement loops in your FlutterFlow app to iterate over data and perform repeated actions. - [Utility Functions](/resources/functions/utility.md): Learn about the built-in utility functions available in FlutterFlow to enhance your app's UI logic. - [Utility Actions](/resources/functions/utility-actions.md): Learn about the built-in utility Actions available in FlutterFlow to enhance your app's UI logic. - [What is a Project?](/resources/projects.md): Understand what constitutes a project in FlutterFlow and how to manage them effectively. - [Collaborate on Projects](/resources/projects/collaboration.md): Learn how to collaborate effectively on projects in FlutterFlow, including best practices for teamwork and project management. - [Create, Find, and Organize Projects](/resources/projects/how-to-create-find-organize-projects.md): Learn how to create, find, and organize projects in FlutterFlow to streamline your app development process. - [Run and Test Projects](/resources/projects/how-to-run-test-projects.md): Learn how to run and test projects in FlutterFlow to ensure your app functions correctly and meets your requirements. - [Libraries](/resources/projects/libraries.md): Learn how to share and reuse entire FlutterFlow projects using libraries. - [Refactor Project](/resources/projects/refactor-project.md): Learn how to refactor your project in FlutterFlow. - [Pinning Projects to Stable FlutterFlow Versions](/resources/projects/settings/flutterflow-version-management.md): Learn how to manage the FlutterFlow version used for your project. - [General Settings](/resources/projects/settings/general-settings.md): Learn how to configure general settings for your FlutterFlow app. - [Project API](/resources/projects/settings/project-apis.md): The FlutterFlow Project APIs allow you to programmatically read, write, and validate YAML configuration files through REST endpoints. Using these APIs, you can automate project management tasks, integrate continuous integration and delivery (CI/CD) workflows, and apply bulk configuration updates without manual interactions with the FlutterFlow user interface. - [Project Setup](/resources/projects/settings/project-setup.md): Learn how to setup your project in FlutterFlow. - [Naming Variables & Functions](/resources/style-guide.md): Naming conventions for FlutterFlow, including guidelines for widgets, components, state variables, constants, and more. - [Periodic Action](/resources/time-based-logic/periodic-action.md): Learn how to use the Periodic Action in your FlutterFlow app to perform actions at regular intervals. - [Timer [Widget]](/resources/time-based-logic/timer-widget.md): Learn how to use the Timer Widget in your FlutterFlow app to manage timed events and actions. - [Wait [Action]](/resources/time-based-logic/wait-action.md): Learn how to use the Wait Action in your FlutterFlow app to pause actions for a specified duration. - [Components](/resources/ui/components.md): Components in FlutterFlow are reusable widgets. You design a widget once and can reuse it throughout your app - [Action Parameters (Callbacks)](/resources/ui/components/callbacks.md): Learn how to add action parameters or callbacks to custom components. - [Child Widget](/resources/ui/components/child-widget.md): Learn how to use Child Widget to add flexible, customizable content inside components. - [Component Actions & Lifecycle](/resources/ui/components/component-lifecycle.md): In FlutterFlow, understanding the component lifecycle is crucial for managing state and optimizing your - [Components](/resources/ui/components/creating-components.md): Components are reusable widgets you create to meet the specific needs of your app. This approach ensures consistency, saves - [Using Components](/resources/ui/components/using-components.md): Components in FlutterFlow can be added to the widget tree of a page or another component. They help streamline - [Widget Builder Parameters](/resources/ui/components/widget-builder.md): Sometimes, you want to create a component that offers some consistent design, while also allowing for customization. This is where passing widget builders as parameters becomes valuable. - [UI Building Blocks](/resources/ui/overview.md): When designing user interfaces in FlutterFlow, understanding the fundamental building - [Introduction to Pages](/resources/ui/pages.md): In FlutterFlow, a Page represents a single screen in your app. Under-the-hood pages use a Scaffold, a foundational widget from Flutter that provides a structured layout for a screen within your app. The Scaffold offers essential elements like the AppBar and Body, allowing you to easily build screens. - [Page Lifecycle](/resources/ui/pages/page-lifecycle.md): In FlutterFlow and Flutter, understanding the page lifecycle, or the stages a page goes - [Properties Panel](/resources/ui/pages/properties.md): In FlutterFlow, the Properties panel on the right helps you set up and manage your pages. It opens when you select the root element in the Widget Tree (on the left). - [Page Elements](/resources/ui/pages/scaffold.md): Page elements in FlutterFlow are the key elements that define the structure and functionality of each page in your app. Understanding these elements is crucial for building intuitive and effective user interfaces. From navigational elements like the AppBar and Drawer to interactive components like Floating Action Buttons (FABs), each element plays a specific role in shaping the user experience. - [Introduction to Widgets](/resources/ui/widgets.md): Introduction to Widgets - [Basic Widgets](/resources/ui/widgets/basic-widgets.md): FlutterFlow offers a range of basic widgets that are the building blocks of a Page or Component. In this guide, we'll cover five fundamental widgets: Container, Text, Icon, Button, and Image. Understanding these widgets is crucial for building any FlutterFlow app. - [AspectRatio](/resources/ui/widgets/built-in-widgets/aspect-ratio.md): Learn how to add an AspectRatio widget in your FlutterFlow app. - [Badge](/resources/ui/widgets/built-in-widgets/badge.md): The Badget widget indicates the number of items that need your attention. Typically it's a medium-sized dot that floats over other widgets such as IconButton. - [Barcode](/resources/ui/widgets/built-in-widgets/barcode.md): The Barcode widget is used to embed the information inside the series of lines and patterns. The data inside the barcode can be easily retried with a scanner machine, an app like Google Lens (Android), Apple Camera (iOS), or your own app created using FlutterFlow. - [Blur](/resources/ui/widgets/built-in-widgets/blur.md): Learn how to add Blur widget in your FlutterFlow app. - [Calendar](/resources/ui/widgets/built-in-widgets/calendar.md): Learn how to add Calendar widget in your FlutterFlow project. - [Card](/resources/ui/widgets/built-in-widgets/card.md): The Card widget is used to represent some related information in a box with rounded corners and a slight shadow for a 3D effect. For example, you can use a Card widget to show a Business card, restaurant information, movie details, etc. - [Carousel](/resources/ui/widgets/built-in-widgets/carousel.md): Learn how to add Carousel widget in your FlutterFlow project. - [Bar Chart](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md): Learn how to add Bar Chart widget in your FlutterFlow project. - [Chart](/resources/ui/widgets/built-in-widgets/chart/chart.md): Learn how to add Chart widget in your FlutterFlow project. - [Line Chart](/resources/ui/widgets/built-in-widgets/chart/line-chart.md): Learn how to add Line Chart widget in your FlutterFlow project. - [Pie Chart](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md): Learn how to add Pie Chart widget in your FlutterFlow project. - [CountController](/resources/ui/widgets/built-in-widgets/count-controller.md): Learn how to add CountController in your FlutterFlow app. - [CreditCardForm](/resources/ui/widgets/built-in-widgets/credit-card-form.md): Learn how to add CreditCardForm in your FlutterFlow app. - [DataTable (Paginated)](/resources/ui/widgets/built-in-widgets/datatable.md): Learn how to add DataTable widget in your FlutterFlow project. - [Dividers](/resources/ui/widgets/built-in-widgets/dividers.md): Add a thin horizontal or vertical line, with padding on either side. Customize the color, width - [Draggable + DragTarget](/resources/ui/widgets/built-in-widgets/draggable.md): The Draggable widget is used to make a widget that can be dragged and dropped to a different location within the app. It allows users to interact with the app by moving an item using touch gestures or a mouse. The DragTarget widget is used in conjunction with the Draggable widget to specify where a dragged item can be dropped. It creates a region that can accept the data carried by the Draggable widget. - [Expandable](/resources/ui/widgets/built-in-widgets/expandable.md): An Expandable widget is a user interface component used to show or hide content dynamically. It consists of a header that can be tapped to reveal or collapse additional content. This functionality is particularly useful in interfaces where space is at a premium, such as in mobile applications or complex forms, enabling users to access information on demand without overwhelming the screen with too much content all at once. - [FlippableCard](/resources/ui/widgets/built-in-widgets/flippable-card.md): Learn how to add Flippable Card widget in your FlutterFlow app. - [Markdown](/resources/ui/widgets/built-in-widgets/markdown.md): The Markdown widget is used to input and display text using Markdown syntax. It allows you to format text easily, without the complexity of a full-fledged WYSIWYG (What You See Is What You Get) editor or the need to write HTML code. - [MediaDisplay](/resources/ui/widgets/built-in-widgets/media-display.md): Learn how to add MediaDisplay widget in your FlutterFlow app. - [MouseRegion](/resources/ui/widgets/built-in-widgets/mouse-region.md): The MouseRegion widget lets you know whenever the mouse pointer enters or exits from a widget. You could use it to build a user experience (UX), such as animating buttons when a user hovers over them and revealing or hiding menu items when a user hovers over the menu icon. - [PinCode](/resources/ui/widgets/built-in-widgets/pincode.md): Learn how to add the PinCode widget in your FlutterFlow app. - [ProgressBar](/resources/ui/widgets/built-in-widgets/progressbar.md): Learn how to add ProgressBar widget in your FlutterFlow project. - [RatingBar](/resources/ui/widgets/built-in-widgets/ratingbar.md): Learn how to add RatingBar in your FlutterFlow app. - [Signature](/resources/ui/widgets/built-in-widgets/signature.md): Learn how to add Signature widget in your FlutterFlow app. - [Slider](/resources/ui/widgets/built-in-widgets/slider.md): Learn how to add Slider in your FlutterFlow app. - [Spacer](/resources/ui/widgets/built-in-widgets/spacer.md): The Spacer widget is used to insert a flexible empty - [StickyHeader](/resources/ui/widgets/built-in-widgets/sticky-header.md): The StickyHeader widget is a special type of widget that allows the top part of a scrollable list to "stick" or remain visible at the top of a viewport while the rest of the content can be scrolled. As users scroll down, the sticky header remains fixed at the top, providing consistent context or navigation cues. - [SwipeableStack](/resources/ui/widgets/built-in-widgets/swipeable-stack.md): Learn how to add SwipeableStack widget in your FlutterFlow project. - [Tooltip](/resources/ui/widgets/built-in-widgets/tooltip.md): The Tooltip widget provides additional information or visual cues of a widget in a small popup box. It appears when the user taps or long-presses the widget or hovers over it. It's typically used to provide an explanation about the function of a widget. - [Transform](/resources/ui/widgets/built-in-widgets/transform.md): The Transform widget applies graphic transformations such as skew (or tilt), rotate, scale, and translate (or slide) to its child widget. You could use this widget in combination with animations to build visually engaging apps. - [Button](/resources/ui/widgets/button.md): The Button widget is a fundamental component in user interface design, utilized extensively across web and mobile applications. It serves as a primary means of user interaction, allowing users to execute actions or commands within an application. Buttons are essential for: - [Composing Widgets](/resources/ui/widgets/composing-widgets.md): In FlutterFlow, creating a complex user interface often involves combining simpler widgets into more intricate layouts. While atomic widgets like Text, Button, Image, and Icon form the building blocks of your UI, you’ll use molecular widgets like Row, Column, and Stack to arrange these atomic widgets into a structured layout. - [Generate Dynamic Children](/resources/ui/widgets/composing-widgets/generate-dynamic-children.md): Widgets capable of handling multiple child widgets have an additional functionality called - [Lists & Grids](/resources/ui/widgets/composing-widgets/list-grid.md): In FlutterFlow, ListView and GridView are versatile widgets designed for displaying lists and grids - [Rows, Column & Stack](/resources/ui/widgets/composing-widgets/rows-column-stack.md): In Flutter, Rows, Columns, and Stacks are fundamental layout widgets that - [Container](/resources/ui/widgets/container.md): A Container is a highly versatile widget that functions much like a multi-purpose box in your app's - [Icons](/resources/ui/widgets/icons.md): Icons are integral elements in user interfaces, providing visual cues that enhance user interaction and aesthetic appeal. They communicate action, represent functionality, and improve navigation efficiency within applications. - [Image](/resources/ui/widgets/image.md): Images are a fundamental part of modern user interfaces, enhancing visual appeal and user - [Properties Panel](/resources/ui/widgets/properties.md): In FlutterFlow, the Properties Panel on the right helps you configure and manage your widgets. It opens when you click on a widget or component in the Widget Tree. - [Text](/resources/ui/widgets/text.md): Text is a fundamental element in any user interface, used to convey information and interact - [Common Widget Properties](/resources/ui/widgets/widget-commonalities.md): Learn how to control common widget properties in FlutterFlow ## roadmap - [Roadmap](/roadmap.md): This roadmap guides you through the key layers of app development: the UI Layer, Logic Layer, and Data Layer. Understanding these layers is essential for creating apps that are visually appealing, functionally robust, and secure. ## testing - [Automated Tests](/testing/automated-tests.md): Discover how to effectively utilize automated testing in FlutterFlow to ensure your app performs as intended. - [Development Environments](/testing/dev-environments.md): Learn how to create and leverage development environments in FlutterFlow. - [Local Run](/testing/local-run.md): Local Run downloads the code locally and gives you the option to use Flutter's Hot Reload to see your changes instantly on a device. - [Run your App](/testing/run-your-app.md): Discover the essentials of running and testing your FlutterFlow app with this comprehensive guide. - [Test Pilot](/testing/test-pilot.md): Learn how to create and run AI-powered QA tests for your FlutterFlow app using Test Pilot. ## troubleshooting - [API Charset and Encoding Fix Guide](/troubleshooting/api/api-charset-and-encoding-fix-guide.md): When working with API calls in FlutterFlow, you might encounter issues where the response returns with strange characters, incorrect formatting, or unreadable content. These problems are often caused by improper charset or encoding settings either in the API request or the server response. - [Client-Server Errors During the API Call](/troubleshooting/api/client-server-errors-during-the-api-call.md): When calling an API in FlutterFlow, you may run into client-server errors. These typically come as status codes that indicate what went wrong, either on your end (the client) or on the server you're requesting data from. - [Securing Your API Keys in Private API Calls](/troubleshooting/api/securing-your-api-keys-in-private-api-calls.md): Ensuring the security of API keys is a critical aspect of building and maintaining a safe and reliable application. In the realm of private API calls, it's especially important to make sure your API keys are not exposed. This article aims to provide a best-practices guide on where to place your API keys to increase security in a FlutterFlow environment.​ - [Custom Domain Connection Error](/troubleshooting/apple-store-deployment-issues/custom-domain-connection-error.md): If you encounter the error shown below after clicking Connect, follow these steps to resolve it: - [Custom Domain Connection Issues](/troubleshooting/apple-store-deployment-issues/custom-domain-connection-issues.md): This article provides solutions for common problems encountered when connecting custom domains. - [Web Publishing FAQs](/troubleshooting/apple-store-deployment-issues/web-publishing-faqs.md): This article provides answers to frequently asked questions related to web publishing. - [Codemagic Install Pods Failure](/troubleshooting/apple-store-deployment/codemagic-install-pods-failure.md): During Codemagic deployment, errors may occur at the Install Pods step due to iOS dependency conflicts, unstable code branches, or pod version mismatches. This guide outlines steps to identify and resolve these issues effectively. - [Codemagic Signing Certificate Limit](/troubleshooting/apple-store-deployment/codemagic-signing-certificate-limit.md): During iOS deployment, Codemagic attempts to create distribution certificates in your Apple Developer Account. If the maximum number of certificates has already been reached, the build will fail with a certificate creation error. - [Download dSYM File from App Store Connect](/troubleshooting/apple-store-deployment/download-dsym-file-from-app-store-connect.md): To download the dSYM file from the App Store Connect Developer Console, follow these steps. - [ImageNotification Development Team Error](/troubleshooting/apple-store-deployment/imagenotification-development-team-error.md): This error occurs when the ImageNotification entitlement is missing in your Apple Developer account. To resolve it, create a new Identifier for ImageNotification in your Apple Developer account. - [iOS Deployment Authentication Error](/troubleshooting/apple-store-deployment/ios-deployment-authentication-error.md): During iOS deployment using Codemagic, an authentication credentials error can occur due to misconfigured or expired API tokens for App Store deployment. - [App Starts from HomePage in Run Mode](/troubleshooting/authentication/app-starts-from-homepage-in-run-mode.md): If your app always redirects to the HomePage in Run Mode, even after a previous login, it's likely caused by retained authentication state or cached session data in your browser. - [Check Firebase Login Method](/troubleshooting/authentication/check-firebase-login-method.md): Understanding which authentication method a user has used can be useful for several reasons. For example, it can be leveraged for analytics, user support, and to customize the user's experience based on their login method. This method, however, is specific to Firebase Authentication.​ - [Deleting Firebase Users and Related Data](/troubleshooting/authentication/deleting-firebase-users-and-related-data.md): Understanding the Delete Action - [Fix Google Sign-In Issues](/troubleshooting/authentication/fix-google-sign-in-issues.md): If Google Sign-In isn’t working after exporting your FlutterFlow app, follow these steps based on how you’re deploying your app. - [Permission Denied: Code 403](/troubleshooting/authentication/permission-denied-code-403.md): This error typically occurs when your application or service account does not have the required permissions to access a resource in Google Cloud or Firebase. - [SafetyNet Phone Sign-In Issue on Android Devices](/troubleshooting/authentication/safetynet-phone-sign-in-issue-on-android-devices.md): If you're experiencing issues with Firebase Phone Authentication on Android devices, especially when using emulators or testing in release mode, this guide will help you identify and resolve common problems. - [Sign in With Apple (for Web)](/troubleshooting/authentication/sign-in-with-apple-for-web.md): To enable Sign in with Apple on the web, you must complete additional steps in both your Apple Developer Account and Firebase Console. These steps allow Apple to identify your website and authorize the use of Apple login on web platforms. - [Troubleshooting Custom Authentication](/troubleshooting/authentication/troubleshooting-authentication.md): - Ensure you have a custom server with login and sign-up endpoints that return a JWT token upon success. - [ListView Gray Box and Red Screen Errors](/troubleshooting/backend/listview-gray-box-and-red-screen-errors.md): When loading a list of items from the database, you might encounter a gray box or red error screen. This article explains the possible causes and how to resolve them. - [Fix ListView Only Returning One Item](/troubleshooting/backend/listview-returning-only-one-item.md): If your ListView is only showing one item, this guide will walk you through the common reasons and how to resolve the issue. - [Resolving Firebase Configuration Issues](/troubleshooting/backend/resolving-firebase-configuration-issues.md): If you're experiencing backend errors, failed schema validation, or data sync issues, this guide will help you verify and fix your Firebase setup in FlutterFlow. - [Update Document Action Fails During Backend Call](/troubleshooting/backend/update-document-action-fails-during-backend-call.md): When performing the Update Document action, you may encounter a situation where the loading indicator appears but then stops without completing the action. This indicates that the update was unsuccessful. If the update succeeds, the next steps in your action flow, such as displaying an alert dialog, should execute automatically. - [Fix Cloud Functions Deployment](/troubleshooting/cloud-functions/fix-cloud-functions-deployment.md): - You must have a Firebase project connected to FlutterFlow. - [Custom Actions Errors](/troubleshooting/custom-actions/custom-actions-errors.md): - A basic understanding of how custom actions work. - [Testing Custom Actions using Debug Console](/troubleshooting/custom-actions/testing-custom-actions-using-debug-console.md): Sometimes, the compiler does not show any errors in the custom action, but the custom action still won't work as expected. This might be due to the code logic or the implementation. In order to test the implementation and the flow, you can use the debug console to test the custom action in different scenarios. - [Codemagic Deployment Error Identification](/troubleshooting/deployment/codemagic-deployment-error-identification.md): Follow the steps below to identify your codemagic error: - [CodeMagic Deployment Tips](/troubleshooting/deployment/codemagic-deployment-tips.md): Here are some tips to avoid Deployment issues: - [Deployment Issues with Stripe Integration](/troubleshooting/deployment/deployment-issues-with-stripe-integration.md): Integrating Stripe in your FlutterFlow project can help you accept payments efficiently. However, some common deployment issues may arise. This article outlines key steps and best practices to ensure a smooth Stripe integration and deployment experience. - [Fixing Razorpay Deployment](/troubleshooting/deployment/fixing-razorpay-deployment.md): Razorpay is a major payment processor in India. Integrating Razorpay can allow users to make payments using their app. This article outlines some common scenarios and troubleshooting instructions for Razorpay deployment issues. - [Fixing Stripe Deployment & Payment Errors](/troubleshooting/deployment/fixing-stripe-deployment-and-payment-errors.md): Integrating Stripe for payment processing in FlutterFlow can significantly simplify monetization. However, developers may encounter issues during deployment or while managing transactions. This guide outlines common deployment and payment issues—and how to fix them—to help ensure a seamless Stripe integration experience in FlutterFlow apps. - [Resolve Errors in Downloaded Code](/troubleshooting/deployment/resolve-errors-in-downloaded-code.md): When you download your project from FlutterFlow and run it locally in your IDE, you may encounter errors due to Flutter version mismatches. This guide outlines how to resolve these issues by ensuring your local Flutter version matches the version supported by FlutterFlow. - [Run Mode: Build Failure](/troubleshooting/deployment/run-mode-build-failure.md): Encountering a "Run mode: Build failed" error can be frustrating when you're eager to see your app in action. This error typically signifies a project issue that prevents a successful build. Addressing these errors promptly ensures your app's functionality and performance. - [Enterprise](/troubleshooting/enterprise.md): A guide to troubleshoot FlutterFlow enterprise projects. - [Client Access to Firestore Expired](/troubleshooting/firebase/client-access-to-firestore-expired.md): You may receive an email from Firebase with the subject: - [Configuring CORS for Firebase Storage](/troubleshooting/firebase/configuring-cors-for-firebase-storage.md): When you deploy your web app to a custom domain, the domain and the Firebase Storage bucket are hosted on different servers. This means that the browser will block requests to the Firebase Storage bucket from your web app, because the origins (the domains and ports) of the two servers are different. - [Content Manager Firestore Error](/troubleshooting/firebase/content-manager-firestore-error.md): You may see the following error message when accessing the FlutterFlow Content Management System (CMS): - [Firebase Android Config File Missing](/troubleshooting/firebase/firebase-android-config-file-missing.md): You may see the following warning in FlutterFlow, as shown in the image below: - [Firebase Storage Limits in FlutterFlow](/troubleshooting/firebase/firebase-storage-limits-in-flutterflow.md): Managing Firebase Storage properly is essential for controlling your app's file storage and associated costs in FlutterFlow. This article summarizes the current limits and best practices following Firebase’s September 2024 changes. - [Get the Sum of Firebase Document or API Values](/troubleshooting/firebase/get-the-sum-of-firebase-document-or-api-values.md): Sometimes you need to display a total, such as a subtotal or count based on data fetched from Firebase or an API. This guide walks you through the steps to calculate and display that sum in FlutterFlow. - [Missing Firebase Storage in FlutterFlow Settings](/troubleshooting/firebase/missing-firebase-storage-in-flutterflow-settings.md): When setting up Firebase Storage in your FlutterFlow project, you may notice that the Firebase Storage option is missing from the Firebase Settings tab. - [Resolving Firestore Index Deployment Issues](/troubleshooting/firebase/resolving-firestore-index-deployment-issues.md): If your Firestore indexes are not being deployed as expected, follow these troubleshooting steps to resolve the issue and ensure your app performs correctly. - [Unable to Validate Firestore Schema](/troubleshooting/firebase/unable-to-validate-firestore-schema.md): When trying to validate your Firestore Schema, you may encounter the error as seen in the image below: - [Updating Firestore Security Rules](/troubleshooting/firebase/updating-firestore-security-rules.md): Most backend issues are generated by the misconfiguration of the Firestore Security Rules. These backend issues may include Grey Screen errors, Infinite Loading screen, Firestore record creating error, Data mismatch errors, etc. - [Initialize GitHub Repository](/troubleshooting/github/initialize-github-repository.md): When pushing code to GitHub, the following error may occur: - [Repository Head Deployment Failure](/troubleshooting/github/repository-head-deployment-failure.md): This error may occur when deploying your FlutterFlow app to GitHub using Codemagic. The message Failed to set the repository head indicates a problem with repository access, configuration, or connectivity. - [AdMob Ads Not Displaying in Google Play Testing](/troubleshooting/google-play-store-deployment/admob-ads-not-displaying-in-google-play-testing.md): If your AdMob ads are not showing during Open Testing via the Google Play Store, the issue is often tied to AdMob configuration, app permissions, or settings in the Google Play Console. Follow the steps below to ensure ads display correctly. - [Declare Advertising ID for Android 13+ in Play Console](/troubleshooting/google-play-store-deployment/declare-advertising-id-android-13-play-console.md): If your app targets Android 13 (API 33) or higher, Google Play requires that you declare whether your app uses the Advertising ID. Failing to do so will result in an upload error when submitting artifacts to the Play Console. - [Error Running Pod Install](/troubleshooting/google-play-store-deployment/error-running-pod-install.md): This article addresses the common Error Running Pod Install issue, which typically occurs due to misconfiguration of Flutter or CocoaPods on macOS devices. - [Fix Flutter Launcher Icons Package Error](/troubleshooting/google-play-store-deployment/fix-launcher-icons-package-error.md): This article describes how to resolve the flutter_launcher_icons package error that may occur during app build or deployment. - [Google Play Draft Release Error](/troubleshooting/google-play-store-deployment/google-play-draft-release-error.md): When uploading an app to Google Play, you may encounter the following error: - [Google Play Failed to Upload Artefacts](/troubleshooting/google-play-store-deployment/google-play-failed-to-upload-artefacts-package.md): - Ensure your app’s Package Name in FlutterFlow matches the package name in Google Play Console. - [Google Play Store Debug Signing Error](/troubleshooting/google-play-store-deployment/google-play-store-debug-signing-error.md): When uploading your Android App Bundle (AAB) or APK to Google Play, you might encounter this error: - [Launcher Icon Missing After Upload](/troubleshooting/google-play-store-deployment/launcher-icon-missing-after-upload.md): Custom app launcher icons may fail to appear after being added in the project settings due to missing icon generation steps. - [Migrate to Play Integrity API From SafetyNet Attestation](/troubleshooting/google-play-store-deployment/migrate-to-play-integrity-api-from-safetynet-attestation.md): Google is deprecating the SafetyNet Attestation API, replacing it with the Play Integrity API. This article explains the migration steps needed to maintain app security and compliance with Google Play requirements. - [Signed in Debug Mode Error](/troubleshooting/google-play-store-deployment/signed-in-debug-mode-error.md): - Generated an APK or Android App Bundle via FlutterFlow → Build → Android. - [Version Solving Failed Due to Incompatible Package](/troubleshooting/google-play-store-deployment/version-solving-failed-due-to-incompatible-package.md): A version solving failed error may occur when running flutter pub get if package versions in the project conflict with FlutterFlow's supported Flutter version. - [FCM Token Generation Troubleshooting](/troubleshooting/notifications/fcm-token-generation-troubleshooting.md): When a user does not have an fcmtoken sub-collection in their Firestore document, push notifications cannot be delivered to their device. This guide outlines the possible causes and solutions for resolving missing fcmtoken sub-collections in FlutterFlow apps. - [Firebase Push Notification Troubleshooting](/troubleshooting/notifications/firebase-push-notification-troubleshooting.md): Push notifications are essential for keeping users informed through timely alerts and updates. However, several common configuration issues can prevent push notifications from working as expected in FlutterFlow projects. This guide outlines potential causes and solutions. - [Firebase Push Notifications on Web](/troubleshooting/notifications/firebase-push-notifications-on-web.md): FlutterFlow currently does not support sending Firebase push notifications on web apps natively. However, Firebase itself supports this capability. This guide outlines alternative approaches to enable Firebase push notifications on web projects built with FlutterFlow. - [Fix Insufficient Permissions for Push Notifications](/troubleshooting/notifications/fix-insufficient-permissions-push-notifications.md): If you encounter an "Insufficient Permissions" error when deploying push notifications from FlutterFlow to Firebase, it usually means the firebase@flutterflow.io service account does not have the necessary permissions in your Firebase project. This guide will walk you through how to resolve this issue. - [Fix Push Notifications Sent to Zero Devices](/troubleshooting/notifications/fix-push-notifications-sent-to-zero-devices.md): Push notifications allow apps to send updates, alerts, and messages directly to users. In some cases, after triggering a push notification, FlutterFlow displays the following message: - [Black Screen During Preview](/troubleshooting/test-mode/black-screen-during-run-mode.md): If your app screen appears blank during Run Mode, follow these steps to resolve the issue: - [Firestore Permission Error in Run Mode](/troubleshooting/test-mode/firestore-permission-error-run-mode.md): When previewing your app in Run Mode, you may encounter the following error message: - [Gray Screen in Run Mode](/troubleshooting/test-mode/gray-screen-run-mode.md): Seeing a gray screen in Run Mode usually points to a configuration issue in your Firebase or project settings. Follow these steps to diagnose and resolve the issue. - [Loading Spinner in Run Mode](/troubleshooting/test-mode/loading-spinner-run-mode.md): A persistent loading spinner in FlutterFlow's Run Mode usually indicates an issue with your Firestore rules configuration. Updating your rules can resolve this issue. - [Local Build ProviderInstaller Error](/troubleshooting/test-mode/local-build-providerinstaller-error.md): This error commonly occurs when building Flutter apps on Android emulators. It is related to the ProviderInstaller service and can typically be resolved through basic cleanup and Flutter version upgrades. - [Slow Loading in Test Mode](/troubleshooting/test-mode/slow-test-mode-load.md): If Test Mode takes several minutes to load or fails entirely, the issue may stem from your browser, network, or project configuration. This guide walks you through the most common causes and how to resolve them. - [Test API Calls](/troubleshooting/test-mode/test-api-calls.md): Verifying an API response before integrating it into your app helps prevent runtime issues and ensures your data is structured correctly. This guide walks you through testing an API directly within FlutterFlow. - [Fix Google Translate Errors](/troubleshooting/translations/fix-google-translate-errors.md): FlutterFlow integrates with Google Translate to help localize your app automatically. This guide outlines how to identify and resolve common issues with the translation integration. - [Custom Widget Errors](/troubleshooting/widget/custom-widget-errors.md): This article demonstrates common errors and issues that may occur when creating a Custom Widget in FlutterFlow, along with steps to resolve them. In this example, an Animated Text Widget is used. - [Emoji Size on iOS Devices](/troubleshooting/widget/emoji-size-on-ios-devices.md): On iOS devices, emojis can appear oversized when rendered inside text widgets, disrupting the intended design and layout. This guide explains how to maintain consistent emoji sizing across all devices using container constraints and auto-sizing configuration. - [Infinite Scroll Pagination in ListView](/troubleshooting/widget/infinite-scroll-pagination-in-listview.md): If a ListView with Infinite Scroll enabled loads all items at once instead of paginating, the issue is typically related to layout configuration. This guide outlines how to correctly structure the widget for proper pagination behavior. - [Rive Animation Loading Errors](/troubleshooting/widget/rive-animation-loading-errors.md): Rive animations may fail to render when the source file is incorrectly linked. This guide outlines how to provide a valid .riv file URL for successful animation loading. - [Scroll To Action on Page Load](/troubleshooting/widget/scroll-to-action-on-page-load.md): When a Scroll To Action fails to trigger during a page load, it is often because the scrollable widget has not fully rendered at the time the action executes. This guide outlines how to ensure the scroll action works reliably during page load. - [Store Custom Widget Output Using App State](/troubleshooting/widget/store-custom-widget-output-using-app-state.md): To use the output from a custom widget elsewhere in your project, you can store its value in an app state variable. FlutterFlow does not directly support retrieving data from custom widgets, so this method provides an effective workaround. --- # Full Documentation Content [Skip to main content](#__docusaurus_skipToContent_fallback) [![FlutterFlow Docs](/logos/logoMark_outlinePrimary_transparent.svg)![FlutterFlow Docs](/logos/logoMark_outlinePrimary_transparent.svg)](/) [**FlutterFlow Docs**](/)[Marketplace](/marketplace)[Troubleshooting](/troubleshooting)[Designer](/designer.md) [GitHub](https://github.com/FlutterFlow/flutterflow-documentation) Search # Search the documentation Type your search here [](https://www.algolia.com/) Docs * [Tutorial](/search.md) Community * [Community Forum](https://community.flutterflow.io) * [Twitter](https://twitter.com/flutterflow) More * [Blog](https://blog.flutterflow.io) * [GitHub](https://github.com/FlutterFlow/flutterflow-documentation) Copyright © 2026 FlutterFlow. Built with Docusaurus. --- ## A[​](/tags.md#A "Direct link to A") * [Accessibility1](/tags/accessibility.md) * [Action6](/tags/action.md) * [Action Blocks1](/tags/action-blocks.md) * [Action Flow Editor2](/tags/action-flow-editor.md) * [Actions13](/tags/actions.md) * [AdBanner1](/tags/ad-banner.md) * [Add Item1](/tags/add-item.md) * [AdMob1](/tags/ad-mob.md) * [AI10](/tags/ai.md) * [AI Agent1](/tags/ai-agent.md) * [Alerts & Notifications4](/tags/alerts-notifications.md) * [Algolia2](/tags/algolia.md) * [Android1](/tags/android.md) * [Animations4](/tags/animations.md) * [Anonymous Login1](/tags/anonymous-login.md) * [API2](/tags/api.md) * [API Call1](/tags/api-call.md) * [API Keys2](/tags/api-keys.md) * [APIs1](/tags/ap-is.md) * [App Builder1](/tags/app-builder.md) * [App Check1](/tags/app-check.md) * [App Events2](/tags/app-events.md) * [App State1](/tags/app-state.md) * [Apple App Store3](/tags/apple-app-store.md) * [Apple Authentication1](/tags/apple-authentication.md) * [Apple Login1](/tags/apple-login.md) * [Assets1](/tags/assets.md) * [Auth Actions2](/tags/auth-actions.md) * [Authentication18](/tags/authentication.md) * [Automated Tests2](/tags/automated-tests.md) *** --- ## [Accessibility](/concepts/accessibility.md) Learn how to make your app accessible to everyone. --- ## [Launch Map](/integrations/maps/launch-map.md) Learn how to open Map app installed on your device from your FlutterFlow app. --- ## [Action Blocks](/resources/functions/action-blocks.md) Learn how to use Action Blocks in your FlutterFlow app to and create reusable actions. --- ## [Action Triggers](/resources/functions/action-triggers.md) Explore the action triggers available in FlutterFlow. --- ## [Action Parameters (Callbacks)](/resources/ui/components/callbacks.md) Learn how to add action parameters or callbacks to custom components. --- ## [AdMob](/integrations/ads/admob.md) Learn how to add AdMob in your FlutterFlow app. --- ## [AdMob](/integrations/ads/admob.md) Learn how to add AdMob in your FlutterFlow app. --- ## [Adding & Purchasing Items](/marketplace/adding-purchasing-item.md) Learn how to add and purchase FlutterFlow marketplace items. --- ## [AI Agent](/concepts/ai-agent.md) Use AI Agent from the FlutterFlow desktop app to set up supported AI agent CLI tools, connect them to your project, and build with natural-language prompts. --- ## [AI Agent](/concepts/ai-agent.md) Use AI Agent from the FlutterFlow desktop app to set up supported AI agent CLI tools, connect them to your project, and build with natural-language prompts. --- ## [Alert Dialog](/concepts/alerts/alert-dialog.md) The action allows you to alert the user of important situations that require acknowledgment in the form of a pop-up or custom-designed dialog. With this feature, you can choose to display a pre-built pop-up or create a custom design that suits your specific requirements. --- ## [Algolia Search](/integrations/search/algolia-search.md) Learn how to implement algolia search functionality in your FlutterFlow app. --- ## [Google Play Store Deployment](/deployment/google-playstore-deployment.md) Learn how to seamlessly deploy your apps to the Google Play Store using FlutterFlow. --- ## [Animations](/concepts/animations.md) Learn the basics of animations in FlutterFlow. --- ## [Anonymous Login](/integrations/authentication/firebase/anonymous-login.md) Learn how to implement anonymous login in your FlutterFlow app. --- ## [Project APIs](/resources/projects/settings/project-apis.md) The FlutterFlow Project APIs allow you to programmatically read, write, and validate YAML configuration files through REST endpoints. Using these APIs, you can automate project management tasks, integrate continuous integration and delivery (CI/CD) workflows, and apply bulk configuration updates without manual interactions with the FlutterFlow user interface. --- ## [Algolia Search Query](/resources/backend-query/algolia-search-query.md) Learn how to perform an Algolia search query in your FlutterFlow app. --- ## [API Call Query](/resources/backend-query/api-call-query.md) Learn how to perform an API call query in your FlutterFlow app. --- ## [Generate Maps Keys](/integrations/google-maps/generate-maps-keys.md) Learn how to generate and use Maps keys for Google Maps integration in your FlutterFlow app. --- ## [App Builder](/flutterflow-ui/builder.md) Explore the App Builder in FlutterFlow, featuring a comprehensive interface with four main sections-Navigation Menu, Toolbar, Canvas, and Properties Panel. --- ## [App Check](/integrations/firebase/app-check.md) Learn how to integrate Firebase App Check in your FlutterFlow app. --- ## [App Event Integration](/concepts/app-event-integration.md) Feed local app events into GenUI so the conversation can react to live app state and time-sensitive signals. --- ## [App State](/resources/data-representation/app-state.md) Learn how to effectively utilize App State Variables in FlutterFlow to maintain and manage global application states across all pages and components. --- ## [Apple App Store Deployment](/deployment/apple-app-store-deployment.md) Learn how to seamlessly deploy your apps to the Apple App Store using FlutterFlow. --- ## [Apple Login](/integrations/authentication/supabase/apple.md) Learn how to integrate Apple Login of Supabase Auth into your FlutterFlow app. --- ## [Apple Login](/integrations/authentication/firebase/apple.md) Learn how to add Apple login in your FlutterFlow app. --- ## [General Settings](/resources/projects/settings/general-settings.md) Learn how to configure general settings for your FlutterFlow app. --- ## [Common Auth Actions](/integrations/authentication/firebase/auth-actions.md) Learn how to add Firebase Authentication actions in your FlutterFlow app. --- ## [Anonymous Login](/integrations/authentication/firebase/anonymous-login.md) Learn how to implement anonymous login in your FlutterFlow app. --- ## [Automated Tests](/testing/automated-tests.md) Discover how to effectively utilize automated testing in FlutterFlow to ensure your app performs as intended. --- ## [Development Environments](/testing/dev-environments.md) Learn how to create and leverage development environments in FlutterFlow. --- ## [Action Blocks](/resources/functions/action-blocks.md) Learn how to use Action Blocks in your FlutterFlow app to and create reusable actions. --- ## [Action Blocks](/resources/functions/action-blocks.md) Learn how to use Action Blocks in your FlutterFlow app to and create reusable actions. --- ## [AspectRatio](/resources/ui/widgets/built-in-widgets/aspect-ratio.md) Learn how to add an AspectRatio widget in your FlutterFlow app. --- ## [Secure API Keys](/best-practices/secure-api-keys.md) Learn best practices for securing API keys in your FlutterFlow app, including key restrictions, geographical restrictions, IP address binding, and service-specific limitations. --- ## [Branching](/collaboration/branching.md) Learn how branching in FlutterFlow allows you to add new features without disrupting your current progress. Understand the workflow of creating and merging branches, resolving conflicts, and the difference between merging and rebasing, with practical examples and tips. --- ## [Building Layout](/concepts/layouts.md) Learn how to build layout in your FlutterFlow app. --- ## [Canvas](/flutterflow-ui/canvas.md) Dive into the versatile Canvas in FlutterFlow, where you can effortlessly design and preview your app’s interface. --- ## [App Event Integration](/concepts/app-event-integration.md) Feed local app events into GenUI so the conversation can react to live app state and time-sensitive signals. --- ## [Child Widget](/resources/ui/components/child-widget.md) Learn how to use Child Widget to add flexible, customizable content inside components. --- ## [Claude Code Plugin](/flutterflow-cli/claude-code.md) Use the FlutterFlow plugin in the Claude Code terminal or desktop app to build and edit FlutterFlow apps with AI, including automatic CLI install, secure API key setup, and a guided build workflow. --- ## [Clear or Delete Media](/concepts/file-handling/clear-delete-media.md) Learn how to add clear and delete file actions into your FlutterFlow app. --- ## [Build with AI Agents](/flutterflow-cli/build.md) Create and edit FlutterFlow projects from your terminal using your preferred AI coding agent. --- ## [Creating Collections](/integrations/database/cloud-firestore/creating-collections.md) Learn how to create collections in Firestore for your FlutterFlow app, including organizing documents within collections. --- ## [Cloud Functions](/concepts/custom-code/cloud-functions.md) Learn how to use Cloud Functions in your FlutterFlow app for serverless backend functionality. --- ## [Connect to Firebase](/integrations/firebase/connect-to-firebase.md) Learn how to integrate Firebase with your FlutterFlow app to add user authentication, cloud storage, real-time databases, and more. --- ## [Code File](/concepts/custom-code/code-file.md) Learn how to create and use custom classes and enums in FlutterFlow. --- ## [Build with Codex](/flutterflow-cli/codex.md) Use the FlutterFlow plugin in Codex CLI or the ChatGPT desktop app to create, edit, and export FlutterFlow projects with natural-language prompts. --- ## [Branching](/collaboration/branching.md) Learn how branching in FlutterFlow allows you to add new features without disrupting your current progress. Understand the workflow of creating and merging branches, resolving conflicts, and the difference between merging and rebasing, with practical examples and tips. --- ## [Creating Collections](/integrations/database/cloud-firestore/creating-collections.md) Learn how to create collections in Firestore for your FlutterFlow app, including organizing documents within collections. --- ## [Action Parameters (Callbacks)](/resources/ui/components/callbacks.md) Learn how to add action parameters or callbacks to custom components. --- ## [Animations](/concepts/animations.md) Learn the basics of animations in FlutterFlow. --- ## [ConditionalBuilder](/concepts/layouts/conditional-builder.md) Learn how to display different widgets based on certain conditions in your FlutterFlow app. --- ## [Conditional Logic](/resources/functions/conditional-logic.md) Learn how to implement conditional logic in your FlutterFlow app to control the flow of actions or generate properties based on certain conditions. --- ## [Configuration Files](/concepts/custom-code/configuration-files.md) Learn how to modify platform-specific files for Android and iOS to extend your app's capabilities. --- ## [Constants](/resources/data-representation/constants.md) Explore the importance of using Constants in FlutterFlow to define unchanging values throughout your application. --- ## [Firestore Content Manager](/integrations/database/cloud-firestore/firestore-content-manager.md) Learn how to use the Firestore Content Manager in your FlutterFlow app to manage Firestore data efficiently. --- ## [Action Blocks](/resources/functions/action-blocks.md) Learn how to use Action Blocks in your FlutterFlow app to and create reusable actions. --- ## [GenUI Chat](/concepts/genui-chat.md) Add a conversational AI surface to your FlutterFlow app that can render catalog components, call action blocks as tools, and react to local app events. --- ## [Crashlytics](/integrations/firebase/crashlytics.md) Learn how to integrate Firebase Crashlytics in your FlutterFlow app. --- ## [FlutterFlow Marketplace](/marketplace) Discover how to explore, purchase, and contribute to the FlutterFlow Marketplace, including guidelines for submissions and handling copyrights. --- ## [Copyright (DMCA) Process](/marketplace/creators-hub/copyright-dmca-process.md) Understand the copyright (DMCA) process on FlutterFlow Marketplace. --- ## [Common Examples](/concepts/custom-code/common-examples.md) Learn about the common custom code examples and use it directly in your project. --- ## [Custom Authentication](/integrations/authentication/custom-authentication.md) Learn how to add custom authentication in your FlutterFlow app. --- ## [Code File](/concepts/custom-code/code-file.md) Learn how to create and use custom classes and enums in FlutterFlow. --- ## [Custom Data Types](/resources/data-representation/custom-data-types.md) Learn how to create and utilize custom data types in FlutterFlow to handle complex data structures that predefined types can't cover. --- ## [Custom Functions](/concepts/custom-code/custom-functions.md) Learn how to create and use custom functions in your FlutterFlow app to add custom functionalities. --- ## [Custom Widgets](/concepts/custom-code/custom-widgets.md) Learn how to create and use custom widgets in your FlutterFlow app to enhance its user interface. --- ## [Visual Studio Code Extension](/concepts/custom-code/vscode-extension.md) Learn how to leverage the Visual Studio Code Extension to write custom code. --- ## [App State](/resources/data-representation/app-state.md) Learn how to effectively utilize App State Variables in FlutterFlow to maintain and manage global application states across all pages and components. --- ## [Data Types](/resources/data-representation/data-types.md) Dive into the diverse range of data types supported by FlutterFlow, from basic primitives like integers and strings to complex composite types and built-in functionalities tailored for app development. --- ## [Creating Collections](/integrations/database/cloud-firestore/creating-collections.md) Learn how to create collections in Firestore for your FlutterFlow app, including organizing documents within collections. --- ## [Deep & Dynamic Linking](/concepts/navigation/deep-dynamic-linking.md) Learn how to implement deep and dynamic linking in your FlutterFlow app. --- ## [Apple App Store Deployment](/deployment/apple-app-store-deployment.md) Learn how to seamlessly deploy your apps to the Apple App Store using FlutterFlow. --- ## [App Builder](/flutterflow-ui/builder.md) Explore the App Builder in FlutterFlow, featuring a comprehensive interface with four main sections-Navigation Menu, Toolbar, Canvas, and Properties Panel. --- ## [Design System](/concepts/design-system.md) Discover how to create a consistent UI/UX across your app with a design system in FlutterFlow. --- ## [Import from FF Designer](/integrations/designer/import-from-ff-designer.md) Learn how to export screens from FF Designer and import them into FlutterFlow. --- ## [AI Agent](/concepts/ai-agent.md) Use AI Agent from the FlutterFlow desktop app to set up supported AI agent CLI tools, connect them to your project, and build with natural-language prompts. --- ## [Deploy for Development Environments](/deployment/deploy-for-environments.md) Learn how to deploy your apps for development environments. --- ## [Document From Reference](/resources/backend-query/document-from-reference.md) Learn how to retrieve a document from a reference in your FlutterFlow app. --- ## [Download File](/concepts/file-handling/download-file.md) Learn how to add download file action into your FlutterFlow app. --- ## [Dropdown](/resources/forms/dropdown.md) Learn how to add Dropdown widget in your FlutterFlow app. --- ## [Deep & Dynamic Linking](/concepts/navigation/deep-dynamic-linking.md) Learn how to implement deep and dynamic linking in your FlutterFlow app. --- ## [AI Agents](/integrations/ai-agents.md) Learn how to add AI Agents for chat, image generation, video generation, text-to-speech, and speech-to-text in your FlutterFlow app. --- ## [Email Authentication](/integrations/authentication/supabase/email.md) Learn how to integrate Email Login of Supabase Auth into your FlutterFlow app. --- ## [Email Login](/integrations/authentication/firebase/email-login.md) Learn how to add Email Login in your FlutterFlow app. --- ## [Enterprise](/misc/enterprise.md) Learn how to use FlutterFlow for Enterprise. --- ## [Enums](/resources/data-representation/enums.md) Learn how Enums can enhance the management of application states, product types, and process statuses by providing a robust method to handle predefined sets of values. --- ## [Form Validation](/resources/forms/form-validation.md) Learn how to add Form Validation widget in your FlutterFlow app. --- ## [Export](/designer/export.md) Export your FlutterFlow Designer designs as PNGs, agent-ready prompts, or directly into a FlutterFlow project. --- ## [Facebook Login](/integrations/authentication/firebase/facebook.md) Learn how to add Facebook login in your FlutterFlow app. --- ## [Anonymous Login](/integrations/authentication/firebase/anonymous-login.md) Learn how to implement anonymous login in your FlutterFlow app. --- ## [Deploy Storage Rules](/integrations/firebase-storage/storage-rules.md) Learn how to deploy storage rules in your FlutterFlow app to manage and secure your Firebase storage. --- ## [Creating Collections](/integrations/database/cloud-firestore/creating-collections.md) Learn how to create collections in Firestore for your FlutterFlow app, including organizing documents within collections. --- ## [Algolia Search](/integrations/search/algolia-search.md) Learn how to implement algolia search functionality in your FlutterFlow app. --- ## [Flex](/concepts/layouts/flex.md) Learn how to add the Flex widget in your FlutterFlow app. --- ## [Integrating Native SDKs Using Method Channels](/concepts/advanced/method-channels.md) Learn how to integrate third-party native SDKs into your FlutterFlow project using Method Channels. This guide walks through setting up channels, writing native code, and connecting it back to FlutterFlow. --- ## [Algolia Search Query](/resources/backend-query/algolia-search-query.md) Learn how to perform an Algolia search query in your FlutterFlow app. --- ## [AI Agent](/concepts/ai-agent.md) Use AI Agent from the FlutterFlow desktop app to set up supported AI agent CLI tools, connect them to your project, and build with natural-language prompts. --- ## [Collaboration](/designer/collaboration.md) Work on the same design together — in real time, with comments and shared access. --- ## [Import from FF Designer](/integrations/designer/import-from-ff-designer.md) Learn how to export screens from FF Designer and import them into FlutterFlow. --- ## [Dropdown](/resources/forms/dropdown.md) Learn how to add Dropdown widget in your FlutterFlow app. --- ## [Checkbox](/resources/forms/checkbox.md) Learn how to add Checkbox, CheckboxGroup, and CheckboxListTile widget in your FlutterFlow app. --- ## [Forms Overview](/resources/forms.md) Learn how to work with Forms in FlutterFlow app. --- ## [Utility Actions](/resources/functions/utility-actions.md) Learn about the built-in utility Actions available in FlutterFlow to enhance your app's UI logic. --- ## [AI Agents](/integrations/ai-agents.md) Learn how to add AI Agents for chat, image generation, video generation, text-to-speech, and speech-to-text in your FlutterFlow app. --- ## [FlutterFlow State Management](/generated-code/state-management.md) Learn about the state management used in FlutterFlow's generated code. --- ## [Getting Started](/integrations/database/cloud-firestore/getting-started.md) Learn how to get started with Cloud Firestore in your FlutterFlow app to manage your app's data. --- ## [Deploy from GitHub](/deployment/deploy-from-github.md) Learn how to deploy your apps directly from GitHub branch. --- ## [GitHub Login](/integrations/authentication/firebase/github.md) Learn how to add GitHub authentication in your FlutterFlow app. --- ## [Global Properties](/resources/data-representation/global-properties.md) Discover the role of Global Properties in FlutterFlow, which provide universal access across all pages of your app to facilitate common tasks and enhance functionality. --- ## [Google Analytics](/integrations/google-analytics.md) Learn how to setup Google Analytics in FluterFlow --- ## [Google Login](/integrations/authentication/supabase/google.md) Learn how to integrate Google Login of Supabase Auth into your FlutterFlow app. --- ## [Secure API Keys](/best-practices/secure-api-keys.md) Learn best practices for securing API keys in your FlutterFlow app, including key restrictions, geographical restrictions, IP address binding, and service-specific limitations. --- ## [Generate Maps Keys](/integrations/google-maps/generate-maps-keys.md) Learn how to generate and use Maps keys for Google Maps integration in your FlutterFlow app. --- ## [Google OAuth Login](/integrations/authentication/firebase/google-oauth-login.md) Learn how to add Google OAuth login in your FlutterFlow app. --- ## [Deploy for Development Environments](/deployment/deploy-for-environments.md) Learn how to deploy your apps for development environments. --- ## [Review Dispute Guidelines](/marketplace/creators-hub/review-dispute-guidelines.md) Learn about FlutterFlow Marketplace review dispute process and when reviews may be removed or modified. --- ## [Hero Animations](/concepts/animations/hero-animations.md) Learn how to add Hero Animations in your FlutterFlow app. --- ## [Local Run](/testing/local-run.md) Local Run downloads the code locally and gives you the option to use Flutter's Hot Reload to see your changes instantly on a device. --- ## [Apple App Store Deployment](/deployment/apple-app-store-deployment.md) Learn how to seamlessly deploy your apps to the Apple App Store using FlutterFlow. --- ## [Implicit Animations](/concepts/animations/implicit.md) Learn how to add implicit animations in FlutterFlow. --- ## [Import from FF Designer](/integrations/designer/import-from-ff-designer.md) Learn how to export screens from FF Designer and import them into FlutterFlow. --- ## [Initial Setup](/integrations/authentication/firebase/initial-setup.md) Learn how to perform the initial setup for Firebase authentication in your FlutterFlow app. --- ## [AI Agents](/integrations/ai-agents.md) Learn how to add AI Agents for chat, image generation, video generation, text-to-speech, and speech-to-text in your FlutterFlow app. --- ## [Integrations](/designer/integrations.md) Connect FlutterFlow Designer with AI agents and developer tools to generate, edit, and update designs using natural language from your preferred environment. --- ## [Localization](/concepts/localization.md) Learn how to make your app work for different languages. --- ## [AdMob](/integrations/ads/admob.md) Learn how to add AdMob in your FlutterFlow app. --- ## [Submit Bug Reports](/misc/submit-bug-report.md) Learn how to submit bug report. --- ## [Iterate](/designer/iterate.md) Refine and improve your generated screens visually, with AI prompts, or by editing the global theme. --- ## [JWT Token](/integrations/authentication/firebase/jwt-auth.md) Learn how to implement JWT authentication in your FlutterFlow app. --- ## [Launch URL \[Action\]](/concepts/navigation/launch-url.md) Learn how to use the Launch URL Action in FlutterFlow to open URLs with supporting apps. --- ## [Card](/resources/ui/widgets/built-in-widgets/card.md) The Card widget is used to represent some related information in a box with rounded corners and a slight shadow for a 3D effect. For example, you can use a Card widget to show a Business card, restaurant information, movie details, etc. --- ## [Libraries](/resources/projects/libraries.md) Learn how to share and reuse entire FlutterFlow projects using libraries. --- ## [Firebase Storage Library](/integrations/firebase-storage/storage-library.md) The Firebase Storage Library provides access to the files in Cloud Storage through the Firebase SDK beyond what FlutterFlow's built-in support provides. --- ## [Local Run](/testing/local-run.md) Local Run downloads the code locally and gives you the option to use Flutter's Hot Reload to see your changes instantly on a device. --- ## [Simple Search](/integrations/search/simple-search.md) Learn how to implement simple search functionality in your FlutterFlow app to search local data on a device. --- ## [SQLite Quickstart](/integrations/database/sqlite.md) Learn how to quickly get started with SQLite in your FlutterFlow app for local data storage. --- ## [Localization](/concepts/localization.md) Learn how to make your app work for different languages. --- ## [Loops](/resources/functions/loops.md) Learn how to implement loops in your FlutterFlow app to iterate over data and perform repeated actions. --- ## [Lottie Animation](/concepts/animations/lottie-animation.md) Learn how to add Lottie animation in your FlutterFlow app. --- ## [Launch Map](/integrations/maps/launch-map.md) Learn how to open Map app installed on your device from your FlutterFlow app. --- ## [Adding & Purchasing Items](/marketplace/adding-purchasing-item.md) Learn how to add and purchase FlutterFlow marketplace items. --- ## [FlutterFlow Marketplace](/marketplace) Discover how to explore, purchase, and contribute to the FlutterFlow Marketplace, including guidelines for submissions and handling copyrights. --- ## [Build with AI Agents](/flutterflow-cli/build.md) Create and edit FlutterFlow projects from your terminal using your preferred AI coding agent. --- ## [Clear or Delete Media](/concepts/file-handling/clear-delete-media.md) Learn how to add clear and delete file actions into your FlutterFlow app. --- ## [Project Setup](/resources/projects/settings/project-setup.md) Learn how to setup your project in FlutterFlow. --- ## [Mux Livestream](/integrations/mux.md) Learn how to get started with MuxBroadcast in your FlutterFlow app for live video broadcasting. --- ## [My Teams](/flutterflow-ui/my-teams.md) On the My Teams page, you can manage billing for your team, edit projects simultaneously, and share code, design systems, APIs, and assets. This makes collaboration between team members much easier and helps keep everyone on the same page. Even if you don't have team members, you can still use this page to share resources between your own projects and keep your development process organized. --- ## [Bottom Sheet](/concepts/navigation/bottom-sheet.md) A Bottom Sheet is an alternative to a menu or a dialog. It opens from bottom to top and can be dismissed by swiping it from top to bottom. When it opens, it prevents the user from interacting with the rest of the app. --- ## [Notifications](/concepts/notifications.md) Learn how to add notifications in FlutterFlow. --- ## [AI Agents](/integrations/ai-agents.md) Learn how to add AI Agents for chat, image generation, video generation, text-to-speech, and speech-to-text in your FlutterFlow app. --- ## [Create, Find, and Organize Projects](/resources/projects/how-to-create-find-organize-projects.md) Learn how to create, find, and organize projects in FlutterFlow to streamline your app development process. --- ## [Page Navigation](/concepts/navigation/page-navigation.md) Learn how to navigate between pages in FlutterFlow. --- ## [Page Transition Animations](/concepts/animations/page-transition.md) Learn how to add page transition animations in your FlutterFlow app. --- ## [PageView](/concepts/navigation/pageview.md) Learn how to use the PageView widget for creating swipeable pages, perfect for creating onboarding screens or multi-step forms. --- ## [Passing Data](/concepts/navigation/passing-data.md) Learn how to pass data between pages in FlutterFlow. --- ## [Braintree](/integrations/payments/braintree.md) Learn how to integrate Braintree payments in your FlutterFlow app. --- ## [Performance Monitoring](/integrations/firebase/performance-monitoring.md) Learn how to integrate Firebase Performance Monitoring in your FlutterFlow app. --- ## [Periodic \[Action\]](/resources/time-based-logic/periodic-action.md) Learn how to use the Periodic Action in your FlutterFlow app to perform actions at regular intervals. --- ## [Project Setup](/resources/projects/settings/project-setup.md) Learn how to setup your project in FlutterFlow. --- ## [Phone Login](/integrations/authentication/firebase/phone.md) Learn how to add phone login in your FlutterFlow app. --- ## [PinCode](/resources/ui/widgets/built-in-widgets/pincode.md) Learn how to add the PinCode widget in your FlutterFlow app. --- ## [Place Picker Widget](/integrations/google-maps/place-picker-widget.md) Learn how to add and configure the Place Picker widget in your FlutterFlow app. --- ## [Slides](/designer/slides.md) Turn a FlutterFlow Designer project into a presentation deck. Design 16:9 slides, present with presenter view, and import or export PowerPoint files. --- ## [Pre-checks Before Publishing](/deployment/pre-checks-before-publishing.md) Ensure your app is ready for launch with this detailed guide on essential pre-publishing checks. --- ## [Slides](/designer/slides.md) Turn a FlutterFlow Designer project into a presentation deck. Design 16:9 slides, present with presenter view, and import or export PowerPoint files. --- ## [General Settings](/resources/projects/settings/general-settings.md) Learn how to configure general settings for your FlutterFlow app. --- ## [Collaborate on Projects](/resources/projects/collaboration.md) Learn how to collaborate effectively on projects in FlutterFlow, including best practices for teamwork and project management. --- ## [Create, Find, and Organize Projects](/resources/projects/how-to-create-find-organize-projects.md) Learn how to create, find, and organize projects in FlutterFlow to streamline your app development process. --- ## [Prompting](/designer/prompting.md) Generate your first app design from a prompt. Explore styles, attach reference images, and create a complete storyboard from a single description. --- ## [Pre-checks Before Publishing](/deployment/pre-checks-before-publishing.md) Ensure your app is ready for launch with this detailed guide on essential pre-publishing checks. --- ## [Adding & Purchasing Items](/marketplace/adding-purchasing-item.md) Learn how to add and purchase FlutterFlow marketplace items. --- ## [SQLite Query](/resources/backend-query/sqlite-query.md) Learn how to perform SQLite queries in your FlutterFlow app. --- ## [Query Collection / Table](/resources/backend-query/query-collection.md) Learn how to query a collection in your FlutterFlow app. --- ## [Quickstart](/designer/quickstart.md) Get started with designing your first app quickly. --- ## [RadioButton](/resources/forms/radiobutton.md) Learn how to add RadioButton widget in your FlutterFlow app. --- ## [RatingBar](/resources/ui/widgets/built-in-widgets/ratingbar.md) Learn how to add RatingBar in your FlutterFlow app. --- ## [Razorpay](/integrations/payments/razorpay.md) Learn how to integrate Razorpay in your FlutterFlow app. --- ## [Connect to Firebase](/integrations/firebase/connect-to-firebase.md) Learn how to integrate Firebase with your FlutterFlow app to add user authentication, cloud storage, real-time databases, and more. --- ## [Refactor Project](/resources/projects/refactor-project.md) Learn how to refactor your project in FlutterFlow. --- ## [Document From Reference](/resources/backend-query/document-from-reference.md) Learn how to retrieve a document from a reference in your FlutterFlow app. --- ## [Refresh DB Request Action](/integrations/database/refresh-db-request.md) Learn how to use the Refresh DB Request action in your FlutterFlow app to refresh your database content. --- ## [Refund Policy](/marketplace/refund-policy.md) Learn more about the refund policy of FlutterFlow marketplace. --- ## [Remote Config](/integrations/firebase/remote-config.md) Learn how to integrate Firebase Remote Config in your FlutterFlow app. --- ## [Resource Hierarchy Overview](/flutterflow-ui/resource-hierarchy.md) Explore the Resource Hierarchy Overview to understand the correlation between traditional Flutter app components and their equivalents in FlutterFlow. --- ## [Responsive Layout](/concepts/layouts/responsive.md) Learn how to create responsive layout in your FlutterFlow app. --- ## [RevenueCat](/integrations/payments/revenuecat.md) Learn how to integrate RevenueCat payments in your FlutterFlow app. --- ## [Review Dispute Guidelines](/marketplace/creators-hub/review-dispute-guidelines.md) Learn about FlutterFlow Marketplace review dispute process and when reviews may be removed or modified. --- ## [Rive Animation](/concepts/animations/rive-animation.md) Learn how to add Rive animation in your FlutterFlow app. --- ## [Deploy Firestore Rules](/integrations/database/cloud-firestore/firestore-rules.md) Learn how to deploy Firestore rules in your FlutterFlow app to manage data access and security. --- ## [Run your App](/testing/run-your-app.md) Discover the essentials of running and testing your FlutterFlow app with this comprehensive guide. --- ## [Algolia Search Query](/resources/backend-query/algolia-search-query.md) Learn how to perform an Algolia search query in your FlutterFlow app. --- ## [Deploy Storage Rules](/integrations/firebase-storage/storage-rules.md) Learn how to deploy storage rules in your FlutterFlow app to manage and secure your Firebase storage. --- ## [Cloud Functions](/concepts/custom-code/cloud-functions.md) Learn how to use Cloud Functions in your FlutterFlow app for serverless backend functionality. --- ## [Mux Livestream](/integrations/mux.md) Learn how to get started with MuxBroadcast in your FlutterFlow app for live video broadcasting. --- ## [Share \[Action\]](/concepts/navigation/share-action.md) Learn how to use the Share Action in your FlutterFlow app to share content. --- ## [Simple Search](/integrations/search/simple-search.md) Learn how to implement simple search functionality in your FlutterFlow app to search local data on a device. --- ## [Slider](/resources/ui/widgets/built-in-widgets/slider.md) Learn how to add Slider in your FlutterFlow app. --- ## [Slides](/designer/slides.md) Turn a FlutterFlow Designer project into a presentation deck. Design 16:9 slides, present with presenter view, and import or export PowerPoint files. --- ## [SOAP APIs](/resources/backend-logic/soap-api.md) Learn how to use SOAP APIs in your backend logic with FlutterFlow. --- ## [Overview](/concepts/navigation/special-page-navigations.md) Learn how to add Special Page Navigations in FlutterFlow. --- ## [SQLite Query](/resources/backend-query/sqlite-query.md) Learn how to perform SQLite queries in your FlutterFlow app. --- ## [App Events](/concepts/app-events.md) Learn how to use App Events in FlutterFlow. --- ## [Deploy Storage Rules](/integrations/firebase-storage/storage-rules.md) Learn how to deploy storage rules in your FlutterFlow app to manage and secure your Firebase storage. --- ## [Storyboard](/flutterflow-ui/storyboard.md) Master the Storyboard view in FlutterFlow to visualize your app’s design and user navigation. The Storyboard allows you to see screens and interactions, ensuring a seamless user experience. --- ## [Streaming APIs](/resources/backend-logic/streaming-api.md) Learn how to use streaming APIs in your backend logic with FlutterFlow. --- ## [Stripe](/integrations/payments/stripe.md) Learn how to integrate Stripe in your FlutterFlow app. --- ## [Naming Variables & Functions](/resources/style-guide.md) Naming conventions for FlutterFlow, including guidelines for widgets, components, state variables, constants, and more. --- ## [Creating Subcollections](/integrations/database/cloud-firestore/creating-subcollections.md) Learn how to create subcollections in Firestore for your FlutterFlow app, including organizing documents within subcollections. --- ## [Submit Feedback](/marketplace/submit-feedback.md) Learn more about the submitting feedback on FlutterFlow marketplace items. --- ## [Apple Login](/integrations/authentication/supabase/apple.md) Learn how to integrate Apple Login of Supabase Auth into your FlutterFlow app. --- ## [TabBar](/concepts/navigation/tabbar.md) Learn how to use the TabBar widget in FlutterFlow to create a horizontal row of tabs for navigating different content views in your app. --- ## [Automated Tests](/testing/automated-tests.md) Discover how to effectively utilize automated testing in FlutterFlow to ensure your app performs as intended. --- ## [TextField](/resources/forms/textfield.md) Learn how to add TextField widget in your FlutterFlow app. --- ## [Periodic \[Action\]](/resources/time-based-logic/periodic-action.md) Learn how to use the Periodic Action in your FlutterFlow app to perform actions at regular intervals. --- ## [Timer \[Widget\]](/resources/time-based-logic/timer-widget.md) Learn how to use the Timer Widget in your FlutterFlow app to manage timed events and actions. --- ## [Tokens](/integrations/authentication/tokens.md) Learn about the types and lifespans of tokens in custom authentication. --- ## [Toolbar](/flutterflow-ui/toolbar.md) Learn how to use the FlutterFlow Toolbar to access project management, version control, help, testing, and development tools. --- ## [Toolbar](/flutterflow-ui/toolbar.md) Learn how to use the FlutterFlow Toolbar to access project management, version control, help, testing, and development tools. --- ## [Form Triggers](/resources/forms/form-triggers.md) Learn how to use Form Triggers in FlutterFlow to create dynamic, interactive user experiences by responding to user input on widgets like dropdowns, sliders, toggles, and text fields. --- ## [Detecting Issues](/troubleshooting) A guide to troubleshoot or debug issues that occur within FlutterFlow project. --- ## [App Builder](/flutterflow-ui/builder.md) Explore the App Builder in FlutterFlow, featuring a comprehensive interface with four main sections-Navigation Menu, Toolbar, Canvas, and Properties Panel. --- ## [Design System](/concepts/design-system.md) Discover how to create a consistent UI/UX across your app with a design system in FlutterFlow. --- ## [File Handling](/concepts/file-handling.md) Learn how to handle media files in FlutterFlow. --- ## [Storyboard](/flutterflow-ui/storyboard.md) Master the Storyboard view in FlutterFlow to visualize your app’s design and user navigation. The Storyboard allows you to see screens and interactions, ensuring a seamless user experience. --- ## [Form Validation](/resources/forms/form-validation.md) Learn how to add Form Validation widget in your FlutterFlow app. --- ## [Naming Variables & Functions](/resources/style-guide.md) Naming conventions for FlutterFlow, including guidelines for widgets, components, state variables, constants, and more. --- ## [Pin to FlutterFlow Version](/resources/projects/settings/flutterflow-version-management.md) Learn how to manage the FlutterFlow version used for your project. --- ## [Wait \[Action\]](/resources/time-based-logic/wait-action.md) Learn how to use the Wait Action in your FlutterFlow app to pause actions for a specified duration. --- ## [Web Publishing](/deployment/web-publishing.md) Discover how to effortlessly publish your applications on the web with FlutterFlow. This guide covers everything from enabling web support to deploying your app and adding custom domains. --- ## [WebView](/concepts/navigation/webview.md) Learn how to use the WebView widget in FlutterFlow to display website content directly within your app. --- ## [Displaying Media](/concepts/file-handling/displaying-media.md) Learn how to display media in FlutterFlow. --- ## [Widget Animations](/concepts/animations/widget-animations.md) Learn how to add widget animations in FlutterFlow. --- ## [Widget Palette](/flutterflow-ui/widget-palette.md) Explore the Widget Palette in FlutterFlow to access a wide range of UI elements. This feature offers an intuitive interface for dragging and dropping Flutter widgets onto your canvas. --- ## [Overview](/resources/ui/overview.md) When designing user interfaces in FlutterFlow, understanding the fundamental building --- ## [Checkbox](/resources/forms/checkbox.md) Learn how to add Checkbox, CheckboxGroup, and CheckboxListTile widget in your FlutterFlow app. --- ## [Workspace](/designer/workspace.md) Learn about FlutterFlow Designer's workspace that provide a complete design environment with specialized tools. --- ## [Wrap](/concepts/layouts/wrap.md) Learn how to add the Wrap widget in your FlutterFlow app. --- # Account Management This section contains information on changing your password, verifying your email, and deleting your account. ### I can't log in to my account / I forgot my login info.[​](/accounts-billing/account-management.md#i-cant-log-in-to-my-account--i-forgot-my-login-info "Direct link to I can't log in to my account / I forgot my login info.") To reset your account password: 1. From `flutterflow.io` select Login in the top right corner. 2. At the bottom of the page, select **Reset Password**. 3. You will receive an email with a link to reset your password. 4. Click the reset link and enter your new password. If you can’t remember your username or are experiencing any other issues, please reach out to us at `support@flutterflow.io` ### How do I change my password?[​](/accounts-billing/account-management.md#how-do-i-change-my-password "Direct link to How do I change my password?") To change your password, please use the following steps: 1. Navigate to your [account page in FlutterFlow](https://app.flutterflow.io/account). 2. Under Personal Info, select Reset Password. 3. You will receive an email with a link to reset your password. 4. Click the reset link and enter your new password. ### How do I check if my account is verified?[​](/accounts-billing/account-management.md#how-do-i-check-if-my-account-is-verified "Direct link to How do I check if my account is verified?") To check if you have verified your account: 1. Navigate to your [account page in FlutterFlow](https://app.flutterflow.io/account). 2. If you have a green checkmark next to your email, your account is verified. ![check-account-verification.png](/assets/images/check-account-verification-6e149bec567f3535f1e7cd83630e6c91.png) ### I didn't get the email to verify my account, how do I resend the verification email?[​](/accounts-billing/account-management.md#i-didnt-get-the-email-to-verify-my-account-how-do-i-resend-the-verification-email "Direct link to I didn't get the email to verify my account, how do I resend the verification email?") If you did not receive a verification email, please follow these steps: 1. Navigate to your [account page in FlutterFlow](https://app.flutterflow.io/account). 2. Check that your email address is correct. If your email is incorrect, please reach out to `support@flutterflow.io` to correct this. 3. From the **Profile Information** section, select **Verify Email**. You should receive a new confirmation email. If you do not receive the verification email, please contact us at . ![email-verification.png](/assets/images/email-verification-d0cf7266c2f94c0dd089788be25f97a8.png) ### How do I delete my account?[​](/accounts-billing/account-management.md#how-do-i-delete-my-account "Direct link to How do I delete my account?") To delete your FlutterFlow account, please follow these steps: 1. Log in to your FlutterFlow account and select **Account** from the top right. 2. Scroll down to the **My Plan** section and select **Delete Account** (bottom right corner) danger This step can not be undone. We will not be able to recover your projects. ### How do I change or update my email address?[​](/accounts-billing/account-management.md#how-do-i-change-or-update-my-email-address "Direct link to How do I change or update my email address?") To change your login email in FlutterFlow: 1. Log into your FlutterFlow account. 2. Go to the dashboard and select your account tile (showing your name and email). 3. Click on **Update Email**. 4. Enter your current email and password. 5. Input your new email and click **Confirm & Log Out**. 6. Verify the new email via the link sent to it. 7. Now, you need to create a new password for your new email address. To do so, click on the **Forgot Password** on the login page and enter your new email address. 8. You'll receive the password reset link at your new email address. Click the link and reset the password. Now, you are ready to log in with your new email address and password. ![update-email.png](/assets/images/update-email-bf385749e89a9ee1251f7766e3d028a3.png) ### How do I generate an API Token?[​](/accounts-billing/account-management.md#how-do-i-generate-an-api-token "Direct link to How do I generate an API Token?") An API token is required to use the [CLI](/flutterflow-cli.md) and the [Visual Studio Code Extension](/concepts/custom-code/vscode-extension.md) . To create an API token tied to your account: 1. Navigate to your [account page in FlutterFlow](https://app.flutterflow.io/account). 2. Near the bottom of the page, click **Create Token** --- # Manage Custom Domains All paid plans include one free custom domain, with the option to purchase more if needed. ### How do I purchase additional custom domains?[​](/accounts-billing/manage-custom-domains.md#how-do-i-purchase-additional-custom-domains "Direct link to How do I purchase additional custom domains?") To purchase domains, paid users can go to their [**account**](https://app.flutterflow.io/account) page, find the **Custom Domains** section, and click the **Add Domains** button. ![add-domain](/assets/images/add-domain-84a700e31e777337a9020b211d5d11e7.avif) The **Team** owner can purchase domains from the **My Team** page. Under the **Custom Domains** section, click **Add Domains** to add one for the team. ![add-domain-team](/assets/images/add-domain-team-67be7960a4784781dd35a82b46d31fd1.avif) note Note that purchasing a domain is not possible during the trial period. If you're interested in obtaining a domain, please reach out to our support team for further assistance. ### How do I remove custom domains?[​](/accounts-billing/manage-custom-domains.md#how-do-i-remove-custom-domains "Direct link to How do I remove custom domains?") To remove the custom domain, paid users can go to their [**account**](https://app.flutterflow.io/account) page, find the **Custom Domains** section, and click **Remove Domains** to remove the existing custom domain. The **Team** owner can remove domain from the **My Team** page. In the **Custom Domains** section, click **Remove Domains**. ![remove-domain-team](/assets/images/remove-domain-team-0e5a27ab84bb66dbba1da439fda3369c.avif) --- # Payments & Billing This section contains information on the payment methods we accept and how to change your payment method. ## Invoices[​](/accounts-billing/payments-billing.md#invoices "Direct link to Invoices") #### Can I Add A Tax ID (e.g. VAT) to my invoice?[​](/accounts-billing/payments-billing.md#can-i-add-a-tax-id-eg-vat-to-my-invoice "Direct link to Can I Add A Tax ID (e.g. VAT) to my invoice?") If you need to include VAT in your invoices, please reach out to our support team at , and we’ll be happy to assist you with the process. ## Payment Methods[​](/accounts-billing/payments-billing.md#payment-methods "Direct link to Payment Methods") ### What payment methods do you accept?[​](/accounts-billing/payments-billing.md#what-payment-methods-do-you-accept "Direct link to What payment methods do you accept?") We currently accept Visa, Mastercard, American Express, and JCB. ### Can I use a gift card in addition to my credit card?[​](/accounts-billing/payments-billing.md#can-i-use-a-gift-card-in-addition-to-my-credit-card "Direct link to Can I use a gift card in addition to my credit card?") At this time we are unable to process Gift Card payments. ### My payment failed, how can I change to a different credit card?[​](/accounts-billing/payments-billing.md#my-payment-failed-how-can-i-change-to-a-different-credit-card "Direct link to My payment failed, how can I change to a different credit card?") Failed subscription payments happen from time to time. These steps will help you troubleshoot the issue and update your payment method. info The most common causes for failed payments are insufficient funds, payment blocked by your credit card provider, or an expired card. If your payment fails, please reach out to your credit card provider for more details on why the payment failed. You can use these steps to update your payment method on an open invoice (where your credit card has not been charged), you can change your payment method using these steps: 1. Head to the [My Account Page](https://app.flutterflow.io/account) 2. Select **Manage Billing** 3. Scroll to **Invoice History** 4. Locate the invoice that failed (should be at the top) and click the icon ![img\_17.png](/assets/images/img_17-91a070fe48a576c3e4425fe28ae995e7.png) 5. Enter your updated payment information Once your updated transaction is successfully completed, your system access will be restored. ### I used the wrong credit card, can I change it?[​](/accounts-billing/payments-billing.md#i-used-the-wrong-credit-card-can-i-change-it "Direct link to I used the wrong credit card, can I change it?") Once your subscription has been purchased, we unfortunately are unable to change your payment method for this month. You can change your default payment method for next month's purchase using these steps: 1. After logging into your FlutterFlow, select [“Account”](https://app.flutterflow.io/account) from the top right. 2. In the **My Plan** section, select **Manage Billing.** 3. Scroll down to the **Payment Methods** section. 4. Select **+ New Payment Method,** enter your payment details, and then select **Add.** 5. Remove your old payment method by selecting the three dots to the right of your payment and then selecting **Delete.** note You can change the default payment method by selecting the three dots next to the payment method and then selecting **Make Default.** --- # Plan Comparison Choose the plan that fits your development needs and team size. ← Scroll horizontally to see all plans → Currency: USDINR Billing: MonthlyAnnual | Plan | Free
$0
per month | Basic
$39
per month | Growth
1st seat: $80, 2nd seat: $55
per month | Business
1st seat: $150, Seats 2-5: $85 each\*
per month | Enterprise
Custom
pricing | | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------- | | **Core Platform Features** | | | | | | | Visual Development Environment
Drag & drop builder for creating apps visually | ✅ | ✅ | ✅ | ✅ | ✅ | | 1K+ Prebuilt Templates
Ready-to-use app templates and components | ✅ | ✅ | ✅ | ✅ | ✅ | | Project Count
Number of projects you can create | 2 projects | Unlimited | Unlimited | Unlimited | Custom | | AI Generation
AI-powered assistance for building and coding | 5 requests/lifetime | 50 requests/mo | 200 requests/mo | 500 requests/mo | Custom | | **Data & Integrations** | | | | | | | Firebase Integration
Connect to Firestore, Firebase Auth, and more | ✅ | ✅ | ✅ | ✅ | ✅ | | Supabase Integration
Connect to Supabase for database and auth | ✅ | ✅ | ✅ | ✅ | ✅ | | AI Agents
Create AI agents with OpenAI, Anthropic, and Google | 0 | 1 | Unlimited | Unlimited | Unlimited | | API Endpoints
Connect to external APIs and services | 2 | Unlimited | Unlimited | Unlimited | Custom | | Swagger/OpenAPI Imports
Import API specifications automatically | ❌ | ✅ | ✅ | ✅ | ✅ | | Development Environments
Separate databases and configuration values for testing and production | 1 (default only) | 1 (default only) | Up to 1 additional (+default) | Up to 2 additional (+default) | Custom | | **Development Features** | | | | | | | Code Extensibility
Add custom code to extend functionality | ✅ | ✅ | ✅ | ✅ | ✅ | | Live Debugging
Test your app in the browser and hot reload | ✅ | ✅ | ✅ | ✅ | ✅ | | Test Mode Session Expiration
How long a Test Mode session remains active before expiring | 20 minutes | No expiration | No expiration | No expiration | No expiration | | Visual Logic Builder
Create app logic with a visual editor | ✅ | ✅ | ✅ | ✅ | ✅ | | State Management
Manage app data and user interface states | ✅ | ✅ | ✅ | ✅ | ✅ | | Custom Code Expressions
Write custom expressions and logic | ❌ | ✅ | ✅ | ✅ | ✅ | | One-Click Localization (i18n)
Automatically translate your app using Google Translate API | ❌ | ❌ | ✅ | ✅ | ✅ | | Push to GitHub
Push your project code to GitHub | ❌ | ❌ | ✅ | ✅ | ✅ | | VS Code Extension
Sync custom code files back and forth between FlutterFlow and VS Code | ❌ | ❌ | ✅ | ✅ | ✅ | | Automated Testing
Run automated tests on your applications | ❌ | ❌ | 1 test per project | Up to 3 tests per project | Unlimited tests | | Test Pilot
AI-powered QA testing with additional credits | ❌ | ✅ | ✅ | ✅ | ✅ | | Custom Classes
Bring custom Dart classes into your app | ❌ | ✅ | ✅ | ✅ | ✅ | | YAML Editing
Refactor your project with by editing the YAML representation | ❌ | ✅ | ✅ | ✅ | ✅ | | Project API
Programmatic access to project data | ❌ | ✅ | ✅ | ✅ | ✅ | | MCP Server (Experimental)
Model Context Protocol server integration | ❌ | ✅ | ✅ | ✅ | ✅ | | Cloud Functions
Write and deploy Firebase Cloud Functions directly from FlutterFlow | ✅ | ✅ | ✅ | ✅ | ✅ | | CLI
Command-line interface for downloading code and project management | ❌ | ✅ | ✅ | ✅ | ✅ | | Configuration File Snippets
Directly modify Info.plist, main.dart, Android manifest, and other config files | ❌ | ✅ | ✅ | ✅ | ✅ | | Local Run Desktop Emulator
Run code locally with automatic environment setup | ❌ | ✅ | ✅ | ✅ | ✅ | | **Design Features** | | | | | | | Design Systems
Consistent color schemes, typographic, icons, and more | ✅ | ✅ | ✅ | ✅ | ✅ | | Animations & Haptic Touch
Add animations and haptic feedback to your app | ✅ | ✅ | ✅ | ✅ | ✅ | | Custom Fonts & Icons
Upload and use custom fonts and icons | ✅ | ✅ | ✅ | ✅ | ✅ | | Custom Typography Presets
Create reusable text styles and presets | ❌ | ❌ | ❌ | ✅ | ✅ | | Screenshot Generator
Generate app screenshots automatically for App Store review | ✅ | ✅ | ✅ | ✅ | ✅ | | Figma Theme Import
Import color and typography themes from Figma | ✅ | ✅ | ✅ | ✅ | ✅ | | Figma Frame Import
AI-powered import of Figma frames to FlutterFlow | ❌ | ❌ | ❌ | 100 requests/mo | Custom | | **Advanced App Features** | | | | | | | Push Notifications
Send notifications to app users | ❌ | ✅ | ✅ | ✅ | ✅ | | Payments Integration
Integrate Stripe and other payment providers | ❌ | ✅ | ✅ | ✅ | ✅ | | Ads Integration
Monetize your app with advertisements | ❌ | ✅ | ✅ | ✅ | ✅ | | Third-Party Package Imports
Add pub.dev packages and GitHub dependencies | ❌ | ✅ | ✅ | ✅ | ✅ | | Debug Panel
Advanced debugging tools and console | ❌ | ✅ | ✅ | ✅ | ✅ | | **Collaboration Features** | | | | | | | Number of Editors
Team members who can edit projects | 1 | 1 | Up to 2 | Up to 5\* | Custom | | Single Project Collaborator Add-Ons
Allow non-team members to collaborate on a single project | None | None | Up to 4 collaborators available for purchase | Up to 10 collaborators available for purchase | N/A | | Real-Time Collaboration
Work together on projects simultaneously | ❌ | ❌ | ✅ | ✅ | ✅ | | Project Commenting
Add comments and feedback to projects | ❌ | ✅ | ✅ | ✅ | ✅ | | Manual Commits
Make explicit named commits to the current branch for version control | ✅ | ✅ | ✅ | ✅ | ✅ | | Number of Branches
Create and manage multiple project branches (all plans include main branch) | 1 (main only) | 1 (main only) | Up to 2 open branches (+main) | Up to 5 open branches (+main) | Custom | | Automated Snapshot Backups
Automatic project backups and version control | Up to 1 hour prior | Up to 1 day prior | Up to 3 days prior | Up to 7 days prior | Custom | | Activity Logging
Track project changes and user activity | ❌ | ❌ | ❌ | ❌ | ✅ | | Project Level Access Control
Manage permissions for individual projects | Manage view-only collaborators only | Manage view-only collaborators only | ✅ | ✅ | ✅ | | Centralized Billing
Manage team billing from one account | ❌ | ❌ | ✅ | ✅ | ✅ | | **Library Features** | | | | | | | Library Imports
Add FlutterFlow libraries to your projects | ✅ | ✅ | ✅ | ✅ | ✅ | | Library Publishing
Publish your projects as reusable libraries | ❌ | ✅ | ✅ | ✅ | ✅ | | **Deployment** | | | | | | | Web Deployment
Deploy your app as a web application | ✅ | ✅ | ✅ | ✅ | ✅ | | Free Subdomains
Deploy your web app to FlutterFlow subdomains | Up to 2 | Up to 20 | Up to 20 | Up to 20 | Unlimited | | Custom Domains
Deploy to your own custom domain | ❌ | Connect 1 domain for free, additional connections will be charged | Connect 1 domain for free, additional connections will be charged | Connect 1 domain for free, additional connections will be charged | Custom | | Custom Web Favicon
Set custom favicon for web publishing | ❌ | ✅ | ✅ | ✅ | ✅ | | FlutterFlow Watermark Removal
Remove FlutterFlow branding from published apps | ❌ | ✅ | ✅ | ✅ | ✅ | | Code Download
Download your project's source code | ❌ | ✅ | ✅ | ✅ | ✅ | | APK Download
Download Android APK files | ❌ | ✅ | ✅ | ✅ | ✅ | | One-Click Apple & Google Store Deployment
Deploy directly to app stores with one click | ❌ | ✅ | ✅ | ✅ | ✅ | | **Support** | | | | | | | Account and Billing Support
Help with account management and billing questions | ✅ | ✅ | ✅ | ✅ | ✅ | | Community Support
Access to FlutterFlow community forums | ✅ | ✅ | ✅ | ✅ | ✅ | | Email Support
Get help via email from our support team | ❌ | ✅ | ✅ | ✅ | ✅ | | In-App Support
Chat support directly within FlutterFlow | ❌ | ❌ | ✅ | ✅ | ✅ | | Dedicated Live Support
Direct access to dedicated support specialists | ❌ | ❌ | ❌ | ❌ | ✅ | ## Business Plan Extensions[​](/accounts-billing/plan-comparison.md#business-plan-extensions "Direct link to Business Plan Extensions") ### \*Agencies Expansion[​](/accounts-billing/plan-comparison.md#agencies-expansion "Direct link to *Agencies Expansion") Includes all Business features, plus the ability to add up to 7 additional seats (12 total per team) at **$85/seat/month** (USD) or **₹2,850/seat/month** (INR) and collaborate with up to 20 other paid users at the project level via Single Project Collaborator Passes. Must be approved as an Expert Agency via [Contra](https://contra.com/opportunity/rWlmk2Yv-become-a-flutter-flow-agency). ### Localized Pricing[​](/accounts-billing/plan-comparison.md#localized-pricing "Direct link to Localized Pricing") INR pricing reflects localized rates adjusted for local purchasing power, providing the same features and plan structures as USD pricing. All plans include the same comprehensive feature set regardless of currency. --- # Plans & Pricing info For our most up-to-date information, please visit **[FlutterFlow pricing](https://flutterflow.io/pricing)**. Regional discounts are available, please **[log in to FlutterFlow](https://app.flutterflow.io/)** to see the pricing for your region. ## Pricing Update \[June 2025][​](/accounts-billing/plan-pricing.md#pricing-update-june-2025 "Direct link to Pricing Update \[June 2025]") FlutterFlow has evolved significantly, from a visual builder to a complete development environment with features like code export, GitHub integration, branching, AI agents, and app deployment tools. As the platform has matured, so have the ways people use it. To better reflect how teams build and scale today, we're introducing updated pricing plans. These updates will help us continue improving the platform, supporting your workflows, and delivering the advanced features needed for building production-ready apps. ### What's Changing?[​](/accounts-billing/plan-pricing.md#whats-changing "Direct link to What's Changing?") As part of broader improvements to the platform, FlutterFlow is updating its pricing and packaging model effective **August 18, 2025**. The update introduces new plan tiers aligned with team size, simplifies billing, and ensures better alignment between user needs and platform capabilities. **Key Changes** * We're retiring our legacy plans: **Standard**, **Pro**, and **Teams** and introducing a new, simplified lineup: **Free**, **Basic**, **Growth**, **Business**. Our Enterprise offering will continue as is, providing advanced features and support for larger teams. * If you're already using FlutterFlow, you'll be automatically moved to the new plan that best fits your current team size. No action needed on your part. * That said, feature access will look a little different. Some users will gain powerful new capabilities, while others might see a few features move to higher tiers. To understand how each new plan compares, **[view the detailed plan comparison](/accounts-billing/plan-comparison.md).** ## FAQS[​](/accounts-billing/plan-pricing.md#faqs "Direct link to FAQS") ### General / Timeline[​](/accounts-billing/plan-pricing.md#general--timeline "Direct link to General / Timeline") Who is affected by this pricing change? All current Free, Standard, Pro, and Teams plan users will move to the new structure. Enterprise customers on custom contracts are not affected by these changes. When does the new pricing take effect for me? * For new users, the pricing and packaging will apply immediately on August 18, 2025. After this date, no legacy plans (Standard, Pro, Teams) can be purchased or updated. * For existing Free, Standard, Pro, and Teams plan users, billing and feature access will remain unchanged during a **30-day transition period** where you will have the ability to select a new plan. On September 18, 2025, your account will be moved to one of the new plans if no action is taken. * **Important exception:** If you're currently on a Teams plan, you will no longer be able to use your team features on personal projects starting August 18, 2025. To maintain existing Teams plan feature access on those projects, you must either: * Move your personal projects into your Team, or * Convert your current Teams plan to a new Growth or Business plan and purchase a separate Basic plan for your personal work. * **Note:** All plan updates will take effect at 12:00 AM local time on the specified effective date. ![Pricing Update Timeline - 2025](/assets/images/pricing-timeline-2025-light-8cba34a0d150e869d9848ff396cf9721.png)![Pricing Update Timeline - 2025](/assets/images/pricing-timeline-2025-dark-462f7c30b4684ef64f55daca68e988ff.png) Why is FlutterFlow updating its pricing? When we launched FlutterFlow, we had one goal: make it radically easier to build beautiful, powerful digital products. Four years later, we’re a full development platform that goes from idea to app store. We now have collaboration features, AI tools, lots of integrations, branching, development environments, and more, built in. Now our plans are evolving to reflect that growth. We’ve introduced new features across every tier and restructured our plans to better align with the way people build today and how their needs change as they move from MVP to scaling production apps. ### Plans[​](/accounts-billing/plan-pricing.md#plans "Direct link to Plans") How do the new plans differ from the current plans? The new plans introduce pricing by team size, differentiation between team and personal projects, and more structured feature access to support different types of users and teams as they grow. Key changes include: * New plan tiers based on team size, with clearer limits of number of developers that can work together. * Collaboration primarily at the team level to support scalable workflows and controls, with a new option to enable single-project collaboration as an add-on. * Updated feature access, with certain advanced features now only available in higher tiers. * Plan-based Support levels, with availability varying by plan. * Revised pricing structure, with updated USD and INR rates. For a detailed comparison of the current and new plans, including feature breakdown and pricing, please see the **[Detailed Plan Changes](/accounts-billing/plan-comparison.md)** table above. What is the difference between team projects and personal projects? [**Team + Restricted Team Projects:**](https://docs.flutterflow.io/resources/projects/collaboration/) * Give you access to all the features of your Growth or Business plan * Can be shared with your whole team, or restricted to specific members * The team owner always has access to every project in the team **Personal projects:** * Belong only to you; team owners cannot access them * Only include the features available in your personal plan * Note: If you want to use Growth or Business features by yourself, you can create a team of one To update the collaboration type of a project, go to the Collaboration tab in your project settings and choose from one of the following options: Team Project, Personal Project, or Restricted Team Project Which plan is best suited for different types of users? The tiers are designed as a general guide to help highlight which plans tend to work best for different types of use cases, but we know that every user’s needs are different and you’re always welcome to choose the one that works best for you. That said, here’s how we generally recommend thinking about the tiers based on common usage patterns: * **Free:** App builders learning and prototyping. * **Basic:** Independent builders shipping production-ready apps. * **Growth:** Solo developers or small teams needing advanced functionality. * **Business:** Established teams (3–5 users) ready for advanced development workflows. * **Enterprise:** Larger teams needing advanced security, governance, and collaboration features. How do I know which plan I will be moved to if I do not make a selection before September 18, 2025? If you do not make a selection during the election period (August 18, 2025 - September 17, 2025), your new plan will be automatically determined based on your current **team size**. For example: * Users on the Free plan will remain in the Free plan, but with new feature restrictions. * Solo users in Standard will move to the **Basic** plan. * Pro plan users and Teams of 2 will move to the **Growth** plan. * Teams of 3-5 will move to the **Business** plan. * Teams with 6+ users will move to the Business plan and retain their current seat count as of September 18, 2025 for up to 12 months. During this period, no additional seats can be added. After 12 months, you will need to upgrade to an Enterprise plan to continue building with more than 5 team seats. * * We highly encourage you to begin evaluating your team’s resourcing and expansion needs early, as this plan will not support usage growth beyond the feature limits of the Business tier. Early planning and engaging with our sales team can help ensure a smooth migration, avoid disruption, and prevent any risk of project or data access issues at the 12-month cut-off. To start the conversation, please reach out to [](mailto:sales@flutterflow.io) to explore the best solution package for your team. Expert Agencies (approved via [**Contra**](https://contra.com/opportunity/rWlmk2Yv-become-a-flutter-flow-agency)) will move to the **Business** plan with Agencies Expansion included. We'll notify you directly in the app and by email before the September 18, 2025 migration, so you'll have a chance to review or adjust your plan if needed. If you're unsure, contact us and we'll help you confirm your new plan Are there any benefits to electing into a new plan vs. being auto-migrated? Yes! By proactively choosing to move to any of the new plans with annual billing during the election period (before September 18, 2025), you will receive **20% off your first year**. Can I stay on my old plan? No. All existing plans will be retired on September 18, 2025, and users will be automatically transitioned to the new plans based on their current team size. This helps us simplify billing, improve feature alignment, and deliver a more consistent experience across all teams. If you’re currently a paying user and would prefer not to be part of the migration to one of the new paid plans, you have two paths: * **Continue building on the Free plan**: you can downgrade your plan to Free, where you will be able to view, edit, and run any 2 existing projects of your choosing inside the editor, but paid‑tier features, deployments, and team seats will be disabled until you upgrade. * **Export your code**: download the full Flutter source and assets for each project before September 18, 2025 and continue building locally to retain full ownership of your codebase. If you’d like to review your options or adjust your usage ahead of time, our support team is here to help. You will receive an email confirming the plan your account will move to, but can also confirm by logging into your account after August 18, 2025 to see how your team maps to the new tiers. What's included in the Enterprise plan? The Enterprise plan is built for organizations that need advanced security, scale, and white-glove support while managing production-grade apps across teams. In addition to all features available in lower tiers, Enterprise includes: * Controlled FlutterFlow upgrades through version pinning * Unlimited access to automated snapshot backups for project history and rollback * Single Sign-On (SSO) and Activity Logging for secure, centralized access * Unlimited development environments to mirror staging, QA, and production workflows * Advanced accessibility features to meet regulatory requirements * No automatic right for FlutterFlow to use your logo * Live and dedicated technical support, plus access to custom engineering solutions when needed To learn more or explore a custom Enterprise solution for your team, please reach out to [](mailto:sales@flutterflow.io) – we'd be happy to walk you through options that match your scale and needs. ### Feature Access[​](/accounts-billing/plan-pricing.md#feature-access "Direct link to Feature Access") Will my existing FlutterFlow apps stop working? 1. No, your current apps will continue to function and remain deployed, though access to certain features may change depending on your new plan tier starting September 18, 2025.
2. If you elect into a new plan during the election period before September 18, 2025, those feature changes will take effect as soon as your new plan becomes active.

What happens to features I already use but are not part of my new plan? * Access to features will be updated according to your new plan beginning September 18, 2025. If you're currently using a feature that is moving to a higher tier, there are two possible outcomes: * **Build-time features** (like activity logging, automated testing, or Figma Frame imports) will no longer be accessible. You’ll see an upgrade prompt if you attempt to use them. * **Run-time features** (like API endpoints, branching, GitHub integration, or dev environments) will be grandfathered and continue to work as-is, but you won’t be able to create additional instances beyond what you already have. For example: * If you are currently building on a Free plan with 3 API endpoints, you can continue editing them, but won’t be able to add a 4th without upgrading to a paid plan. * If you’ve used branching or added multiple development environments and currently exceed your new plan limits, those remain active but you’ll be prompted to upgrade if you try to add more. * This approach ensures existing work isn’t disrupted, while still aligning future access with your selected plan. ### Free Users[​](/accounts-billing/plan-pricing.md#free-users "Direct link to Free Users") I am a Free user and I will lose access to technical support. Where else can I look to for help when building? * Starting August 18, 2025 for new users and September 18, 2025 for existing users, 1:1 support will no longer be included with the Free plan. However, we offer a collection of self-serve resources to help you continue building with confidence: * Our Help Center at [docs.flutterflow.io](http://docs.flutterflow.io) offers a free collection of step-by-step guides on how to build, get started, and make the most of FlutterFlow’s features. * We are also launching new troubleshooting guides to help you resolve common issues and workflows. * Plus, a new AI-powered assistant will help you quickly find answers and relevant resources within the Help Center. * You can turn to our [Community Forum](https://community.flutterflow.io/) to ask questions, share learnings, and get help from other FlutterFlow builders. * We also offer free educational content via our [YouTube channel](https://www.youtube.com/@flutterflow) to support your learning and skill development. * These resources are designed to help all users succeed without needing to rely on 1:1 technical support. * Our Support team will still be available at [](mailto:support@flutterflow.io) to all users to assist with billing or account-related issues. I am a Free user and have more than 2 projects – what will happen to them? * Starting September 18, 2025, all personal Free plan projects will be archived until you actively select two to keep editable. This selection is permanent and cannot be changed afterwards. All other projects will be archived – they'll still appear on your dashboard, and published apps will remain live, but you won't be able to open, edit, or publish updates unless you upgrade. * **Marketplace exception:** Existing Free plan projects published to the Marketplace prior to August 18, 2025 will not count toward your 2-project limit. If a project is later removed from Marketplace and you exceed the limit, it will be automatically archived. * For users on a team-based plan but not on a personal paid plan, this 2-project selection requirement only applies to your personal projects. You will still be able to edit any projects that belong to your team. * We've set this policy to ensure everyone can explore FlutterFlow for free while keeping heavy usage sustainable. We won't remove any of your existing projects. They're safe and accessible whenever you decide to upgrade. Can I still deploy my existing FlutterFlow apps if I stay on the Free Plan with >2 projects? Any existing projects already live will remain deployed, even if you have more than 2 projects currently deployed. However, on the new Free plan, you’ll be limited to editing and publishing updates to at most 2 active projects. All other projects will remain deployed, but you won’t be able to make changes or redeploy them unless you upgrade to a paid plan. What if I delete one of my 2 active projects? If you delete one of your active projects, we’ll automatically unarchive your most recently edited archived project to replace it. If you only had 2 projects total and delete one, you’ll be able to create a new project instead. ### Teams (Growth/Business)[​](/accounts-billing/plan-pricing.md#teams-growthbusiness "Direct link to Teams (Growth/Business)") What happens to projects with multiple collaborators? Starting September 18, 2025, all project collaboration must occur within a team (Growth or Business). This means: * **Team projects.** Everyone on your team keeps full edit access. Any project collaborator who is not a paid seat on your team will be switched to view-only access at the project level until they’re added as a paid team member. * **Projects not associated with a team.** The project owner keeps full edit access and all other project collaborators become view-only members on that project. To keep editing together, move the project into a team and invite those collaborators as team members. * **Solo projects:** If you are the only editor, nothing changes. You retain full edit access. Note: If you choose to migrate to a new paid plan before September 18, 2025, any collaborators not on your team will immediately move to view-only access at the time of conversion. I’m currently on a Teams plan and want to change the number of users. Can I still do this on or after August 18, 2025? No. As a part of the existing Teams plan retirement, team size will be locked on August 18, 2025. To adjust your team size after that date, please transition to one of the new plans (Growth or Business). I am currently on a Teams plan with 6+ users and do not want to migrate to the Enterprise plan – how do I stay on the Business tier and how will I be charged? * Teams with more than 5 users who do not wish to move yet to an Enterprise contract can continue on the Business tier under a transitional pricing structure. These teams will be billed at the standard Business tier seat pricing and then $85/seat/month for each additional seat over 5. Pricing will be based on the number of users in the team as of September 18, 2025 and billed on a monthly basis. - This option allows larger retail teams to continue operating under the Business feature set without immediate contract negotiation, but will be available only to existing 6+ seat teams for 12 months from September 18, 2025 through September 18, 2026 to ensure continuity without immediate contract negotiation. * Note: Your seat count will be locked based on your team size as of September 18, 2025. You may reduce seats later, but will not be able to add more or expand beyond the feature set and usage limits of the current Business tier (except for any run-time features already in use that are grandfathered). However, if you would like to maintain a single account, collaboration across all of your team members, enterprise level features and support, please reach out to [](mailto:sales@flutterflow.io). Can I belong to multiple teams? How will that be billed? Yes, starting August 18, 2025, users will be able to belong to multiple teams in FlutterFlow in the new plans – this is a new capability as part of our updated team and collaboration structure. Each team is treated as a separate billing entity, with its own plan, users, and usage limits. If you are added as an editor on more than one team, you will count toward the seat total on each of those teams, and each team will manage your seat and billing as part of their own subscription. You will not be billed individually – all billing remains centralized at the team level. Note: you can also be added as a view-only collaborator on projects that are a part of different teams. View-only collaborators do not count toward any seat limits or billing. Can I share projects across multiple teams? * No, projects cannot be shared across multiple teams. Each project belongs to at most one team, and access is managed within that team’s structure. * If you want someone from another team to collaborate on a project, they must be invited into your team as an editor or granted access using a Single Project Collaborator Pass. What if I want to continue collaborating with project collaborators who are not on a Teams plan with me? * With the new pricing model, collaboration is only supported within shared Teams plans. This means that to work together on a project, all collaborators must be part of the same Growth, Business, or Enterprise team. However, users can now be members of multiple teams at the same time, which allows you to create separate teams for different projects, depending on who you need to collaborate with. * Project-level collaboration (where individuals outside your team could be added to specific projects) is being phased out to simplify permissions, ensure security, and support shared billing. * If you would like to continue collaborating: * You can invite others to join your team (additional seats may require an upgrade depending on your plan). * Or, they can create a new team and invite you, depending on who should own billing and project access. * **New:** If you're on a Growth or Business plan, you may also purchase Single Project Collaborator passes, which allows you to grant another paid user access to a single project without adding them to your full team. Each pass is $15/month and can be reassigned to different collaborators or projects as needed. You can purchase up to 4 (Growth) or up to 10 (Business). This collaborator must also be on a paid plan to be eligible to be a single project collaborator. * This change ensures that every project has clear ownership, consistent permissions, and a scalable path for team-based collaboration. I build apps for clients (agency/freelancer). What plan should I choose? * We will now offer multiple plan options to support agencies of all sizes – whether you’re a solo freelancer, a fast-growing studio, or an established consultancy. We believe the best path depends on your team size and how you prefer to work with your clients: * Solo freelancers or small agencies (1-5 developers) * We recommend the Business plan, which supports up to 5 team members with advanced features like branching and access control. * Agencies with more than 5 developers: * If your client plans to manage the code: * We recommend encouraging your client to purchase their own Business or Enterprise plan and then invite your agency developers to join, either as [Team members or as collaborators](https://docs.flutterflow.io/resources/projects/collaboration/) (with a collaborator pass). To learn more about our Enterprise offering, they can reach out to [](mailto:sales@flutterflow.io). * If you intend to maintain the code on behalf of your client: * You may qualify for our new Agencies Expansion package, coming out with the Business plan and available to all verified FlutterFlow Expert Agencies. * As part of the add-on, you can: * Continue to purchase additional seats beyond the 5 included in Business at $85/seat/month * And, continue to invite additional paid users to specific projects without requiring them to be team members via Single Project Collaborator Passes. * To become eligible now, you can apply to be an Expert Agency on our [Contra](https://contra.com/opportunity/rWlmk2Yv-become-a-flutter-flow-agency) page. Existing Expert Agencies listed on Contra will be pre-approved to select the Agencies Expansion package starting August 18, 2025. You can transfer ownership of projects between yourself and your client. To transfer a project to a client, simply add them as a collaborator using a pass, transfer ownership, and then remove them and the pass if no longer needed after the handoff. ### Billing[​](/accounts-billing/plan-pricing.md#billing "Direct link to Billing") How will my billing cycle be affected when I move to the new plan? To ensure a smooth transition, billing changes will align with your existing billing cycle: * You will stay on your current pricing until your next billing renewal (monthly or annual). For example: * * If your monthly billing date is September 3, 2025, your features will switch to the new plan on September 18, 2025 (or earlier if you elect to switch), but new pricing will apply starting your next billing cycle on October 3, 2025. * If you’re on an annual plan, your price won’t change until your next annual renewal. After that, the new pricing will apply for the following 12 months. You will have the option to upgrade early to the new pricing plan if you choose, with any remaining credit from your current plan applied toward the new plan. If you are currently on an annual plan and choose to cancel your subscription during the transition period (August 18, 2025 - September 17, 2025), you will be eligible for a pro-rated refund. This is to account for any features you may have prepaid for under your current plan that will no longer be available once the new plans take effect. If you have questions about your billing, please contact support at [](mailto:support@flutterflow.io) Will there still be a discount for paying annually on the new plans? Yes, we will continue offering a meaningful discount on all new plans when billed annually instead of monthly – typically around 25%. This discount remains available regardless of your location or currency and reflects 12 months of service at a reduced monthly rate. *Note: Annual billing is not available for Business teams with greater than 5 seats on transitional pricing, as this is intended to support existing users during their migration period.* How will prices change with respect to country discounts? Localized pricing will continue where applicable. If you’re in a supported region, your billing will reflect adjusted rates at existing discounts based on your location. What if I am billed in INR right now? If your account is billed in INR, your pricing will follow our localized rates: * **Basic Plan:** ₹1,300 INR per seat per month. * **Growth Plan:** ₹2,650 INR for the first seat, and ₹1,850 INR for the second seat per month. * **Business Plan:** ₹5,100 INR for the first seat, and ₹2,850 INR each for seats 2–5 per month. * Agencies Expansion: Each additional seat beyond 5 at ₹2,850 INR/month. All INR pricing reflects the same features and plan structures as USD pricing, with adjustments for local purchasing power. ### Miscellaneous[​](/accounts-billing/plan-pricing.md#miscellaneous "Direct link to Miscellaneous") I have special access. How will this change impact me? * If you currently have Special Access (such as through a community program, academic use, or other exception), your FlutterFlow experience will remain unchanged. You will continue to have the same benefits provided under your existing Special Access status, which is separate from the new plan structure. * **Note:** * Users with Special Access can collaborate with an unlimited number of users, but those collaborators must also have either Special Access or be on a paid teams (Growth or Business) plan. * Special Access may be granted at either the individual or team level. If only the individual has Special Access, they will not have full feature access when working on team projects unless the team also has Special Access. Will the referral program still exist with the new plans, now that the Pro plan is going away? * With the retirement of the Pro plan, our current referral program will also be sunset. This means any active referral discounts will end at your next renewal. However, any earned referral credits will remain in your account and can be redeemed for equivalent free months of the new Growth plan. * We’re actively exploring what a future referral or incentive program could look like under the new pricing model, with the goal of better supporting and rewarding our community as we grow. Are there any changes to DreamFlow plans as well? Dreamflow is a separate product and Dreamflow plans are not affected by this plan update. --- # Privacy And Terms Of Service ### How do I request the deletion of my personal data?[​](/accounts-billing/privacy-terms-of-service.md#how-do-i-request-the-deletion-of-my-personal-data "Direct link to How do I request the deletion of my personal data?") To request deletion of your personal data, please reach out to our support team at ### How do I request a copy of my personal data?[​](/accounts-billing/privacy-terms-of-service.md#how-do-i-request-a-copy-of-my-personal-data "Direct link to How do I request a copy of my personal data?") To request deletion of your personal data, please reach out to our support team at . ### How do I unsubscribe from email communications / marketing emails?[​](/accounts-billing/privacy-terms-of-service.md#how-do-i-unsubscribe-from-email-communications--marketing-emails "Direct link to How do I unsubscribe from email communications / marketing emails?") To unsubscribe from FlutterFlow emails, please click the “Unsubscribe” link in the footer of our emails. ### Where can I view your Privacy Policy?[​](/accounts-billing/privacy-terms-of-service.md#where-can-i-view-your-privacy-policy "Direct link to Where can I view your Privacy Policy?") You can review the most recent version of our Privacy Policy [on the website](https://www.flutterflow.io/privacy). ### Where can I view your Terms of Service (ToS)?[​](/accounts-billing/privacy-terms-of-service.md#where-can-i-view-your-terms-of-service-tos "Direct link to Where can I view your Terms of Service (ToS)?") You can review the most recent version of our Terms of Service [linked on the website](https://www.flutterflow.io/tos). --- # Referral Program Discontinued With the retirement of the Pro plan, the existing referral program has been discontinued. Any active referral discounts will end at your next renewal. However, referral credits you’ve already earned will remain in your account and can be redeemed for free months on the new Growth plan. We are also exploring new referral and incentive programs to better support and reward our community under the updated pricing model. --- # Refunds If you're not happy with your FlutterFlow subscription, you can [cancel at any time](/accounts-billing/subscriptions/subscriptions.md#cancel-my-plan). However, there are no refunds for cancellation. In the event that the Company suspends or terminates your Account or these Terms, you understand and agree that you shall receive no refund, whether for any unused time on a subscription, any license or subscription fees for any portion of the Service, any content or data associated with your User Account, or for anything else. --- # Subscriptions This section provides information on free trials, plan changes, and other subscription-related questions. ## Free Trials[​](/accounts-billing/subscriptions/subscriptions.md#free-trials "Direct link to Free Trials") The first paid plan you purchase will come with a free 14-day trial. For 14 days, you will have access to the features of the plan you selected before you are charged. If you can cancel your subscription during this 14-day trial, you will not be charged. info The 14-day trial applies only to your first paid plan. Any later plan (Basic, Growth, or Business) won’t include a trial, even if the first plan is still in trial. ### How do I start a free trial?[​](/accounts-billing/subscriptions/subscriptions.md#how-do-i-start-a-free-trial "Direct link to How do I start a free trial?") To start a free trial, please follow these steps: 1. Navigate to [app.flutterflow.io](http://app.flutterflow.io/) 2. Click the “Create Account” text and enter your name, email address, and password. Then press the “Create Account” button to create your account. 3. Validate your email address by clicking on the link in the message sent to the email address you provided. 4. To start trialing on a **Basic** plan, click on your profile picture in the bottom left corner, then click “Upgrade Plan.” Select “Start Free Trial” and fill out and submit the form with your payment information. 5. To instead start trialing on a **Growth** or **Business** plan, click “My Team”, and create a team. Then press the “Subscribe” button, select your desired plan and number of seats, click on “Start Free Trial”, and fill out and submit the form with your payment information. ### What happens at the end of the trial period?[​](/accounts-billing/subscriptions/subscriptions.md#what-happens-at-the-end-of-the-trial-period "Direct link to What happens at the end of the trial period?") At the end of your trial period, your payment method will be charged. You can cancel at any time during the trial period. ## Upgrade Plan[​](/accounts-billing/subscriptions/subscriptions.md#upgrade-plan "Direct link to Upgrade Plan") ### How do I upgrade my plan?[​](/accounts-billing/subscriptions/subscriptions.md#how-do-i-upgrade-my-plan "Direct link to How do I upgrade my plan?") If you would like to upgrade from a Basic plan, follow the steps to purchase a new Growth or Business plan, and then cancel your existing Basic plan if you no longer want it. To upgrade a team directly from a Growth plan to a Business plan, please follow these steps: 1. Click on “My Team” and select the team that has the Growth plan you wish to upgrade. 2. Click on “Upgrade to Business Plan” near the top right corner. 3. View the invoice and confirm that you are willing to be charged this amount for the upgrade. ### How do I check what plan I am subscribed to?[​](/accounts-billing/subscriptions/subscriptions.md#how-do-i-check-what-plan-i-am-subscribed-to "Direct link to How do I check what plan I am subscribed to?") To view your plan details, go to the [**FlutterFlow Account Page**](https://app.flutterflow.io/account) and select **Manage Billing.** The **Current Plan** section will show which plan(s) you are subscribed to. ## Downgrade Plan[​](/accounts-billing/subscriptions/subscriptions.md#downgrade-plan "Direct link to Downgrade Plan") ### How to downgrade?[​](/accounts-billing/subscriptions/subscriptions.md#how-to-downgrade "Direct link to How to downgrade?") If you wish to downgrade from Growth to Basic or from Business to Growth or Basic, you should cancel your existing plan and then sign up for the new one after it expires. ### What happens when I downgrade to the free plan? Will my projects be deleted?[​](/accounts-billing/subscriptions/subscriptions.md#what-happens-when-i-downgrade-to-the-free-plan-will-my-projects-be-deleted "Direct link to What happens when I downgrade to the free plan? Will my projects be deleted?") On the free plan, you will be restricted to two projects. Any other projects won't be deleted, but will be archived and made accessible if you return to any paid plan. ## Cancel My Plan[​](/accounts-billing/subscriptions/subscriptions.md#cancel-my-plan "Direct link to Cancel My Plan") You can cancel your plan at any time. You will have access to the paid features until your next billing cycle date. Please follow these steps to cancel your account: 1. Log in to FlutterFlow and click on your profile picture to go to the Account page. 2. Find the plan you want to cancel, and select **Cancel Plan**. 3. Complete the Cancellation Survey and select **Cancel Subscription.** warning Your FlutterFlow account can have multiple team plans and a personal plan at the same time; you must cancel each plan manually. Canceling one plan does not automatically cancel any other active plan. ## Other Subscription Questions[​](/accounts-billing/subscriptions/subscriptions.md#other-subscription-questions "Direct link to Other Subscription Questions") ### When will my plan renew / When will I be charged?[​](/accounts-billing/subscriptions/subscriptions.md#when-will-my-plan-renew--when-will-i-be-charged "Direct link to When will my plan renew / When will I be charged?") You can view the next billing cycle date in the "My Plan" section of the [Flutterflow Account Page](https://app.flutterflow.io/account). ![renew](/assets/images/renew-b7de713dccd5a228d37fb4534fd1cf72.png) The next billing cycle date for this plan is September 12, 2025. ### Do subscriptions renew automatically?[​](/accounts-billing/subscriptions/subscriptions.md#do-subscriptions-renew-automatically "Direct link to Do subscriptions renew automatically?") Yes, our subscriptions renew automatically to avoid disrupting your app development. Monthly subscriptions renew on the same day each month (typically the day you subscribed). ### Can I pause my subscription?[​](/accounts-billing/subscriptions/subscriptions.md#can-i-pause-my-subscription "Direct link to Can I pause my subscription?") We do not currently offer the option to pause your subscription. ### Can I transfer my subscription to another user?[​](/accounts-billing/subscriptions/subscriptions.md#can-i-transfer-my-subscription-to-another-user "Direct link to Can I transfer my subscription to another user?") We are unable to transfer a paid FlutterFlow subscription to another FlutterFlow account. ### If I have a paid plan, will project collaborators be able to use paid features?[​](/accounts-billing/subscriptions/subscriptions.md#if-i-have-a-paid-plan-will-project-collaborators-be-able-to-use-paid-features "Direct link to If I have a paid plan, will project collaborators be able to use paid features?") No. Having a paid plan yourself does not give your project collaborators access to paid features. Starting **September 17, 2025**, all collaboration must happen within a **Growth, Business, or Enterprise plan**, and every collaborator must have a **paid seat** in that team to have full edit access. Anyone not on your team will be switched to **view-only** until added as a paid team member. ### If I upgrade from the Growth Plan to the Business Plan in the middle of my billing cycle, will I be charged for both plans?[​](/accounts-billing/subscriptions/subscriptions.md#if-i-upgrade-from-the-growth-plan-to-the-business-plan-in-the-middle-of-my-billing-cycle-will-i-be-charged-for-both-plans "Direct link to If I upgrade from the Growth Plan to the Business Plan in the middle of my billing cycle, will I be charged for both plans?") Upgrades are automatic, so the system will count the remaining days from the Growth plan and reduce it from the Business Plan price. For example, if you paid $80 for Growth and you have 15 days remaining in the billing cycle, then on upgrading to Business (let's say priced at $150), you will eventually pay $(150-40) = $110. info FlutterFlow provides different pricing options depending on your region. To see the exact prices for your area, visit the [**Plans & Pricing**](/accounts-billing/plan-pricing.md) page. --- # App Development Before you jump in and start using FlutterFlow, it's helpful to have an idea of how app development works more broadly. Traditionally, developing an app required writing a lot of code. You can think of code as a set of instructions for the computer, or device, executing the code. The codebase is usually divided up into two pieces: instructions for the frontend, and instructions for the backend. # Frontend vs Backend Frontend development deals with creating the parts of an application that users interact with directly. This includes: * Defining the visual pieces of your app, like text or buttons * Figuring out how these pieces should be laid out on the screen * Setting up logic for how your app should react to retrieved data and user interactions Backend usually refers to more complex logic and data storage. This includes: * Setting up a database that is capable of storing, sending and retrieving data * Leveraging off-the-shelf services, like authentication providers or payment platforms * Defining business logic, either by writing code or using a low-code tool The interaction between frontend and backend often occurs through APIs (Application Programming Interfaces). In most cases, the backend exposes endpoints for the frontend to send requests to. The backend handles the request, and sends some data back in response - which the frontend can use to change its visual appearance. # Where does the code execute? Backend code runs on a server, which could be located in a data center or hosted on a cloud platform like AWS, Google Cloud, or Azure. The server is responsible for handling requests, processing data, and sending responses back to the frontend. Frontend code runs on the user's device. This could be a web browser for web applications or the operating system for mobile applications. The frontend code is responsible for displaying the user interface and handling user interactions. # Frontend architecture When it comes to developing the frontend of your application, there are several key architectural patterns and best practices to consider. These include: * **Component-Based Architecture:** Breaking down the UI into reusable components, each responsible for a specific part of the interface. This makes the code more modular and easier to maintain. * **State Management:** Managing the state of the application, which includes the data displayed in the UI and the user's interactions. * **Responsive Design:** Ensuring that your application looks and works well on different screen sizes and orientations. This involves using flexible layouts and scalable assets. * **Performance Optimization:** Making sure your app runs smoothly by optimizing rendering, minimizing the number of network requests, and reducing the size of your assets. By understanding these concepts and implementing best practices, you can create robust and user-friendly applications with FlutterFlow. --- # Create an account Create your free account to get started with FlutterFlow. After you've set up your account, you'll be able to create as many projects as you like. You can [**sign up**](https://app.flutterflow.io/create-account) via Apple, Google, or Github. ## System Requirements[​](/before-you-begin/setup-flutterflow.md#system-requirements "Direct link to System Requirements") The FlutterFlow application can be accessed from your browser or installed as a desktop app. ### General recommendations:[​](/before-you-begin/setup-flutterflow.md#general-recommendations "Direct link to General recommendations:") * Use a screen that is at least **1280 x 1024** ### Browser recommendations:[​](/before-you-begin/setup-flutterflow.md#browser-recommendations "Direct link to Browser recommendations:") * FlutterFlow works best on **Google Chrome** * We recommend keeping your browser up-to-date, specifically within the latest two versions * You should allow pop-up and redirects and ClipBoard from *app.flutterflow\.io*. ### Desktop recommendations:[​](/before-you-begin/setup-flutterflow.md#desktop-recommendations "Direct link to Desktop recommendations:") * **macOS**: While FlutterFlow should work on 10.13 or higher, we recommend using 13 or higher * **Windows**: While FlutterFlow should work on 7 or higher, we recommend using 10 or higher info Some Windows users may experience a crash. To fix this, install the [**Microsoft Visual C++ 2015–2022 Redistributable (both x64 and x86)**](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170) from the official Microsoft site. For details on target platform system requirements, please see the [Flutter documentation](https://docs.flutter.dev/reference/supported-platforms). ### Desktop vs Web:[​](/before-you-begin/setup-flutterflow.md#desktop-vs-web "Direct link to Desktop vs Web:") We recommend using the desktop application for improved performance and access to features like [**local run**](/testing/local-run.md). However, our desktop applications are currently in a preview phase, which may result in some instability. --- # Best Practices: Secure API Keys Google Cloud API key restriction is essential for managing access and enhancing security when working with Google Cloud services. This overview explains how to effectively restrict API keys, allowing developers to control how and where their keys can be used. Developers can set geographical restrictions, bind keys to specific IP addresses, or limit usage to particular services. These measures ensure that API keys are secured, helping to protect projects and maintain optimal functionality. To minimize potential damage from compromised API keys: * **Add restrictions to your API key:** By setting restrictions, you can limit how an API key can be used, thus reducing the impact if it becomes compromised. * **Delete unnecessary API keys:** Remove any API keys that are no longer required to reduce exposure to attacks. * **Rotate your API keys periodically:** Regularly create new API keys, delete the old ones, and update your applications to use the new keys. This practice helps maintain security and limit the lifespan of any single key. ## Add restrictions to your API key[​](/best-practices/secure-api-keys.md#add-restrictions-to-your-api-key "Direct link to Add restrictions to your API key") API keys are unrestricted by default. Unrestricted keys are insecure because they can be used by anyone, from anywhere. You can add either [application restrictions](https://cloud.google.com/docs/authentication/api-keys?#adding-application-restrictions) or [API restrictions](https://cloud.google.com/docs/authentication/api-keys?#api_key_restrictions) to enhance security. In the following example, we will use the **Map API keys** and restrict them to specific platforms using their unique identifiers. At this stage, you should already have API keys created, but they are currently unrestricted. If they are not yet created, you can follow the integration process for any of the Google Cloud services we support in FlutterFlow, or for Maps, [you can go here.](/integrations/google-maps/generate-maps-keys.md) All your created API keys should be available on the [Cloud Credentials Page](https://console.cloud.google.com/apis/credentials). (Ensure you are logged into the correct Google account and are in the right Google Cloud project.) Follow the steps below to enable the iOS key exclusively for iOS apps with a unique package name: [Restrict API Keys](https://demo.arcade.software/givOcppDSZHXzWJDloWj?embed\&show_copy_link=true) Now your iOS API Key will only work when accessed from your app with the given unique identifier. You can also restrict the API keys by **HTTP referrers** or **IP addresses**. Here's a quick overview from the official docs: ![app-restriction.png](/assets/images/app-restriction-85dca210a3d64c0162faf32140c4ffa0.png) Learn More Learn more about **securing API keys for all platforms and restricting API usage** by visiting the official [**Google Cloud Docs**](https://cloud.google.com/docs/authentication/api-keys?#securing). --- # Branching Branching creates a separate copy of your work, so you can add new features without disrupting your current progress. It enables multiple developers or teams to work simultaneously on different features without interfering with each other. Suppose you have an eCommerce app and you want to add a new feature, such as a product recommendation system. Instead of incorporating it directly into your existing `main` branch and potentially causing problems, you can create a branch to work on this new feature in isolation. Once it's complete, you can integrate it back into the `main` branch. info While all users can access the branching menu and create commits, only **Growth** plan and above support creating new branches. warning Creating a branch here doesn't create one on GitHub. Branches stay and are managed solely within FlutterFlow. You can also learn more about [**managing custom code on GitHub**](/exporting/push-to-github.md#manage-custom-code-on-github). ## Branching Overview[​](/collaboration/branching.md#branching-overview "Direct link to Branching Overview") Before you create and merge a branch, it is essential to understand the general workflow. Here's what it looks like: ![branching](/assets/images/branching-overview-bbc4d99782390c8234732aaed3f94c1e.avif) First, create a new branch from the `main` branch. After making your changes in a new branch and finalizing the feature, merge this new branch back into the `main` branch. If there are any conflicts, you must resolve them first. note It’s important to understand what merging actually means. Merging does not perform a "union" of data between branches. Instead, Git merge reconciles differences (diffs) between the branches. When you merge, Git compares the changes made in the new branch with the main branch and applies these changes directly. For instance, if a branch is created and all existing data is deleted before new content is added, Git interprets this as a replacement. When the branch is merged back into the main branch, those deletions will also be applied removing the original data. This behaviour can be surprising to those expecting Git to automatically preserve all content from both branches. Learn more about [**Merging**](/collaboration/branching.md#merging). To avoid accidental data loss, ensure that your branch workflow involves incremental and intentional changes rather than deleting and replacing all existing content unless that's specifically your goal. ## Creating a New Branch[​](/collaboration/branching.md#creating-a-new-branch "Direct link to Creating a New Branch") To create a new branch from the current branch, simply go to the **Branching Options** button next to current branch in the **Branching menu.** [Sharing a Project with a User](https://demo.arcade.software/5n61rPZR7WuWxs0lTFkE?embed\&show_copy_link=true) tip You can create a new branch from any existing branch, however it's most common to create new branches from `main` ## Commits[​](/collaboration/branching.md#commits "Direct link to Commits") A commit is essentially a saved snapshot of your project at a particular point in time. When you make changes to your project (such as adding new widgets, modifying actions, or configuring integrations), you can create a commit to save these changes. Each commit stores a record of what has been modified and serves as a version history for your branch, making it easy to see what has changed and roll back to previous versions if needed. ### Create Commits[​](/collaboration/branching.md#create-commits "Direct link to Create Commits") To create a commit, follow these steps: Best Practices for Commits * **Commit Frequently:** Save your work often to ensure that changes are tracked, and you have a detailed version history. You can use the keyboard shortcut (cmd + enter) for faster iteration! * **Use Clear Messages:** Always provide meaningful commit messages that explain what was done. * **Test Before Committing:** Ensure that the project works as expected before committing significant changes. ### View Commit Changes[​](/collaboration/branching.md#view-commit-changes "Direct link to View Commit Changes") Once the commit is created, you can see the list of all commits under the **Branch History** section. Here, each commit is displayed with a timestamp, the user who made the changes, and a commit message. You can also search and filter through commits by specific users and date range. To see the commit changes, simply click on the commit. You’ll then land on a **Commit View** page where you can: * **Review Changed Files**: In the left panel, files that have been modified are marked with a gray dot, making it easy to spot which parts of your project have updates. * **Compare Before and After**: The center pane provides a side-by-side diff of the YAML for each changed file. Lines highlighted in red indicate removed or altered content, while lines in green show newly added or updated content. * **See Commit Statistics**: At the top of the page, you’ll see a quick summary of how many files were changed and the total lines added (+) or removed (-). [Viewing Commits](https://demo.arcade.software/RwImFTtbmT0hkxj1RtuF?embed\&show_copy_link=true) ### Commit Options[​](/collaboration/branching.md#commit-options "Direct link to Commit Options") The options provided for each commit are as follows: * **View Commit:** This option lets you view the details of a particular commit. * **Restore Branch to Commit:** This option allows you to revert your branch to a previous commit. It creates a new commit that resets the branch to the state of the selected commit. This is particularly useful if a recent commit introduced issues, and you need to return to a stable point in the project's history. * **Copy Commit ID:** Every commit is assigned a unique ID. This option allows you to copy the commit ID, which can be useful for referencing specific commits in collaboration with team members. ### Commits vs. Snapshots and Versions[​](/collaboration/branching.md#commits-vs-snapshots-and-versions "Direct link to Commits vs. Snapshots and Versions") FlutterFlow offers multiple ways to save the state of your project at specific points in time. * **Snapshots** are automatically created as you edit your project. Think of them as automatic backups that you can revert to whenever needed. * **Versions and commits**, on the other hand, are manually created checkpoints. While both serve a similar purpose, commits offer more flexibility by allowing you to view the specific changes made in each commit. If you're using a plan that supports branching, it's recommended to use commits for better tracking and version control. You can learn more about [snapshots and versions here](/collaboration/saving-versioning.md). ## Merging[​](/collaboration/branching.md#merging "Direct link to Merging") Merging allows you to push the changes you've made in one branch into another. For example, you may want to push your changes from a feature branch, or a branch where you are developing a new feature, back into the `main` branch once it's ready to be deployed to your users. Say your feature branch has two commits: `Commit 1` and `Commit 3` (which are your changes), and `Commit 2` (made by a colleague in the main branch). The merge would look like the below image: ![after-merging](/assets/images/after-merging-bb3a6676d00f240174f0d6d2c7ec13c5.png) You can also merge changes from the parent branch, into the current branch. For example, say you want to pull the latest commits on `main` into your feature branch. This merge would look like the below image: ![after-merging-2](/assets/images/after-merging-2-0e2dd391925885e88d24733e9c17265a.png) During a merge, Git compares the changes made in both branches, if the changes don't overlap or conflict then the branches are automatically combined. If there are conflicts (for example, both branches modified the same widget property) you'll need to resolve these before the merge can be completed. Few things to note here * At the moment, FlutterFlow only supports merging into the parent branch, or the branch that the current branch was created from. * Only the user who initiated the merge can access both the branches during an ongoing merge. * Merges result in a merge [commit](/collaboration/branching.md#commits), which means you can undo a merge by restoring the branch to a prior commit * If you leave the project during the merge and come back, the progress you have made on the merge will be preserved. Merging in FlutterFlow uses Git under the hood to calculate differences between project files. Each project is backed by a repository of YAML files (except for custom code, which appears as Dart files). These YAML files map directly to various project properties, and Git calculates differences among these files to identify merge conflicts. Future Plans * **Hover-Based Documentation**: Display helpful tooltips for YAML fields (scheduled before production release). * **Inline YAML Errors**: Show errors directly in the file for quicker fixes (scheduled before production release). * **Simplified YAML**: Make YAML files and errors more user-friendly and understandable. * **Enhanced Visual Diff Tools**: Provide more intuitive views for comparing changes. * **User Experience Improvements**: Continuously refine merging workflows and UI elements. * **Performance Optimizations**: Improve speed when initiating merges. ### Initiating a Merge[​](/collaboration/branching.md#initiating-a-merge "Direct link to Initiating a Merge") To initiate the Merge, navigate to **Toolbar >** select **Branching > Branching options >** select **Merge**. When performing a merge in FlutterFlow, you’ll see a screen with multiple panels and info sections. Here are the details of it. ![merging-window](/assets/images/merging-window-5801e73a0c51551eab1cbad7aed64101.avif) **Top Panel** * **Branch Information**: At the top of the merge interface, you’ll see exactly which branches are being merged. You have two options for merging directions: * **Parent → Child**: Pulls changes down from the parent into the child branch, often used to keep a feature branch in sync with the parent branch. ![parent-child](data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAEHkAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAgAAAAB2AAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAEIFtZGF0EgAKChghv/1YIEBA0IAy6CBMBALcyCQVHYobGOaKC0IqP6O5xtKbgYA2Wq9F41pDRlkCgPz01TnI5LGNsvy777nzBDcuimNihtJBxSG7Nik2hJJYbY+lx9mqt9kO9H2Hivo8OAw0/Pl1j3m5m7Bwt6rs8vEX/9PL4Rv6KTiT1I/iTPmJSShILXqVJiGoNZnDQDLRrMJZ+kiY4k+lX+5IqvDT5Yr0Ixd08598iGd+liyswiYCWJ70tOQM47cfkoieyKMNCBuGJT5X175e7Qwcan7Q9/evAA4Bpkq+Y8ZPAvbo4vI5FHqMNFxwlXfEGc051m1Kfuv8IOMlnkEpTxQSygJIicbuKZj7YefTPIKF5wyVzS3Y141lBKZI/aGK+lV7111OcOC/M2kHmsga+ujtekOJNLU3Gm8VLgf4liUnlX1Syawu/pQFe7fhIKrcrFGGl/gj41oaM9cA6DMNdcv/9Vlc9VmJGTU2oBqdEkEhU6mH5T8HWi8m82fM4jbNteudfVSJ4wfLyimmPcV64o571+y3XPWfvnWGRcifzvK9IPKaD4Q/XJNezi5o2KRuDwEohT26l6i89HLKhth3fTxOMfoSuKxyRVmaYgVuZCcO9/4x3YS2vnsnupP05wzI1wJduTJrhNR/bhRC6DUTSOb43h5SUWXbJv1ihtLRqNw22tnjNJXrTKYWzMWqT9b+nvUSW5Au3Iqywlhq4tczT5B/c+5FN/dyDoXX7WYp08UKBXBzJItF/dROcvQNdL/kQo35IKwdn/7t4u8NjzvUOWEflcKd29hjQs5d0iQo5tg88tTGYSHWuTH6nW1iV1D9Sqw+atVJ6LNhl66VNgaKET2CQpfjt3FOCnkolLO51gjXiF0zkyJveKm0OSTdmb/DztrLzC0Fv90ctk+UVmyQBNq74mYnIjTLpw7PoDsB/G8v27D8WSJCuoO64WdWli1AFO4MkZBuaxRrsc+Xa07s3s0xl4YFKzZcEKSnT3bRYzbbbLOIgof5N4wFee3J0WOZtvDfaiUrFOZV5T8HaEMrDxkKFkHspKbDH5rX3rc3UjzPiUJzpix/pfwLsV3cFGPfllYZsF6XSeTmwjILqZF9ohDQRSydWXRYfHURKHiJTuJmQ9EzRolBZJpzxMoOBs/eqwM8qkOmgkLV2xm8pnkQjJqxKu4V3pkX5A6CUfvUcSeD4taba/CRr6yb0gYF1iQxhNKyM5gWdJ+F+ZG+2H8zukFGmwlJG1+zctJD6iBNxYgYWBUKvKOK+4rREhmZHru2ia6GGWYqjdMjE18CmZTguy81mddnb3uC9ECIMUFLflV3NdfjJvogny2ya1neziLXiJ1jfE3Ffd7AzGGvKtTpbkK4lvpoR10o9FcODpfjJ41aU9wQ8pH6xV2ckWrFqjCIOtyRBCLokVLspZQNNE/3sScfeQWBL/8L7NqBDb0tUysq5JqbTGBeBRCiWiLI2OyOAKjv9l5aCoBd+7/6CB2U6vhkdpdlSGvJFNUnbKKih0E2ENqWgHb1V/Uk8LmEplT21yUxy7DntPFx4+0LDQZojJJWATfMNHJXPW5owCAzJD9Zf2giQ6s74QUFccaO1edbxhGDu1m7pP/+5tjkseaYxsmRPg6bvQS6Ekbbph3kpI7ac0feYJ6wd8FmiqqMQOzhd6/a+/URb/GMUhtju0bmN+eHOlbdWNN8VyOQnT9x7g7C0an2RY4Rwx39yey3oBCc97XIyGgIRDo20Y2yrU/UZHB8s03fHTJ1Xms1LWSo7nTDO2IewRG00d0kgqjXMLflkP3NbFyUPCtDL4VbSZXi06SQffDESjHqGRotiZgvffVqHZANWFBK/HcZ7w6VCnpjtEeNOFVkSpaWOo5p1n+kcHhyxlWT6CdNm+iq3iaozl3V7xiTtL8jJvnjd6SEyBMlUYst+65x7VRisrrZpiam7gLsA5V74uO+AOPFIRGOppD+eNzNekfPd8U5jOMEQkzlD5XRoLsP9bwvbg7JcjYLk8MFs+fItUbqMo08bpFgxWkbMHlbfkJDkFx9Sqd2GT+UHmALfASYwCN/mAxStt7vFYbajTAyFrrF64JuuPIFhuf0382Er6cujZkmYvbFJPtkh6zWy8s7/qbmxZs2ZDnKh4OSJtFVC3JMIj/Tl5UDePuD8riNRPgZ7hJCQ96Z19sAs8biun6GWILGWKhKUS3OF/ziDmociLGlbhiiGaTreFclN5oDI1sBtKDGTo5EAwhY/iT612/RoIrlzX3Uul7Bk0UfsX5wUpiwxuDBd1xWhO7GUKCWFTOMPXtYWxrE7AkukYn/NFc5HSr0XQuTGg+BKys+RVh7c2ZDwIeptsAf+RzfWyQhT88snZ7T3BzUwqmrx28WX1bGWS+aCWhwIdi8PsuB7ghgOXwuKBjo12RTkX+svzIO2iRaZ+fxIEghXVbiMr0JcRciFvCXapDHCzX9jIqwREugB3FzvZkO7UnhllVRz06YMXzKbRO0/t/1ZEaM2BINZPi5DSD5sbXtJPTf8ppWzeQ53ll2+x9ZAHshuEyG5Y4i/TNjFYdQ/b5Vf/////5yxL3+QOiQNVcaHANY4xEHUzLVvbanwzUg7orhWSAB2uevML4Jrja6SqrjxKSke+57Dj1b3yM2ZlXsjyQLpWU16UxJLkYTZ0NEqKvp8r7ruGVqcAAmkIs1ARRkwASpMx41uJsQ6S7VUM5Vj90C/y67CsOfvy2pT9ybp61bpD+qXWXOnEHIQ2NmPuwcj1O05+g3CZYK1KOnngbM8fjwpKLeYk119iUNjMBwQHOJjUnbjp9XTug4HjBST1aStJRLvCkywL6qcvpiwTRXZXoY8fmlTtzziXT6eYi8DG1bMGGITp4iWHyjx8DK15AH9Q5Oqj+C6/Altf9jzU4pqs1VjnvvHAMvkXqVX7OEmP6CGG75hK83JrEOcymbmzCLxye3c21s8yw20/GLGZZ06Wy46Qbye1LlYaxEDDTaiAijtAriQyABM3AZxp4PiF5CS81OnqmOV0VN47CU2ox5ru4iaEHNF6iuuFBcUshfgSHyYkKdMUhCePs1uhuY5I8VsjxUx68JXN1ZkElym4jdOM5mo/EPKKql/UC1/JjS12Xq0aYGlihkqFqBB9ZffWuQjAmmqPaMW4dYa6UK7fmb92ifFnH3FqokhzctRdTuFjcj/jVKHxt/xdjWf7GUGrWXGeJvgU/g1VP0JYuBjl9ZyidsHzJ0JgTn48ceeMSJlXkn9YgBPMI5t2TcjCpu7AdGdXbv+cqw7qxY5N5BeqNtmng7dMRmlOZ/11LGu8XfUblu1XeKlsxV4UP76NlmYqZN58zsu463NoeTag8Epi558jLUeUxNh3xj9MGeGO4L92YMB1sMFZ2kGYQuM/rLMnYrSXcoDxAOHhVbOG9tcESQ1LmV1P1L7S30JVZehbF6rr9cyX0qpBEctHfau/s4sOJvjRMGUfrR2JrG4jZaSf2ZhdS2SYukSmRILXmP+vAT9j0DuGd0MQVNYj+D5bbp3rHp6CdBe8rmmz4GS5GsQ5HsJvFedg2w9X/SZI9OhVvIlJWs1U8K2LuR+CzFWaWxXZZtAqpiryabU4LxjbXrtHL4enuycB3DVTttbBUyJ1KupKFSC3bKm9Ft3c77b/RMm8c8PUfNToPVlpypxVFRDW3h4UXs4iw3i189JNeO7ffn1//opvNbfRZ7RpMbaZiKg3w5HZg6jpYrqM9zp8+Aqlfc8Av6+y82gHzzcTKRybvlJAMr0Runhowjtv2XGXJHKr4BxT56MbZuvcaG/PNQeYgDcLn71zTqqhT6RLkYP7EinvjnFmf1F0dJ6CHOciijJ5vL8tVClU+l+BMtbhwFgAWyEhbUH9TO0TOXjethrMkuxCo0eUHB6mju6MDye8XF8SO9OpqJh6nDHtZAnRiYHKocjZQmvFuoeFuY7GA5hPtuGyvE0PAuAQr+jhFQUN87wbBSeQXK/lYVosLe2JQKszxkEvg631fvTd46/SmJ4VtoCUOVgVz7ueBWDHEryDUIDGZpZP+Ce3/3p0LcYhgIJ33R1CLSCW0rXEwcnNz+4kylHhV2q3REcqkxstg5gVSNtEZT1KHZfkyN2kGiiZ4v2mA/hMdZTED/w/2f9rPNPneK0+usTP2N5BsAMZ5io5ZfZV4Sl5KtOikJT6ATDh8SjV/dgqKl9zwGv1/Oh40a2edU4Dbk5mqMoFJnzljVKZ1q5tgclDlHB5emkanmUjVdZ0nel/XZNnzchTTqbACQ1tv0R4Y+t1CFleuL9IEyJtjLE4G8/ZSAg84pffwC9qIho9h4pyJdOpLly16vcsQxEtIltHYvs6TUkEuiXXkluN9VVHWsk2hJblDbvBpB7zTts8f90bjlsj7NNY0oqWBms/VGfmXxPW7PwDc+6zMMIqnI4HeuNjXg7fxoVrMbuT545MlTc8cJeuUBhmalw2710dCIijy7rGh6NXI/nTEgNCavM4g1p8fM2xLmmlkgqOAKr8MOVR0o38Ctb5Hv2u8sUcAHRyflrd/d8NZ8eCHz3gXOT3ybYFFO7oys7XuoPtEQtsLbiD02tgl2Gx+qBaQg9Al5v62daTwKnPyFjQWHVXWIyLZYYppaVHGGiOLedqnbez8cd0Gh8t9qp3zH2wqzKwtYTuTB2X6ylSbA+UOGwQFzeF31pU54My+4eHN+SO23iSequoTEVKG9nuZHR+wh9eAHbZUJczp/Bu8N4RWERfR5ZxT4D5HfKW0Xrs4CSJy0eKNYfPMTJ1ars0PqQgt4IZL4CxkB7su8/EgqCDa41wh5qbuPfoToZ1NrlYg86A/wZ40SB5FHzXVEW184cl8yV4eiCOj/NV0OGe1Bp3x0F9ja7+bn0O0ydW1L+wyVHfMnzp8PpSpSF9ir5eQmuTCqXXUHSky9IAEJVAyvA6w2lF1EEuWipUkInqztch/aqFTEG07KNh7e5ETUege6UQmWXHLsjfLfptz8DI+vxfnn/H8Mvr6SAJyNCyJB/Ti78c+mQFHDT9wHeLB5yHDgv4kynKvYKpP1JNtBEcc48iJSzjS8OzPASEBluQbmeYECgV3yS76DQVtO4aKGsY4g8vld2+uq8U9VflM+bZ2NJSzUfqaPYxBlU41kAYxKUcu9iax5V1SF43qbdLwn63nTjDa27Sd+vC5LEHEUSV47hdcoWoqcMVasJb+JJp0BrEYcpfnN2jFEy0BRRFZma7Feg4Ee4CGp+t8R7coHs9Ockx3MQpXej4JfJkb7xyCnO0tnc2n00DyARwrUWv9ol2LR6U23bEholMCKkrk7YOV4pIqOw9FhithBn0tdC+ccRh/+7Z7hhnImM1BaPArfpmFNGa9RZFqffJiKRQq3EKPvi1yKe/hZIQtb2UyByWpdzRIMpYIRvcMti9R93937aFFhXW/xl2abiw+g4dYBt03TX+VoOwjDXbbH35BUgXHWx1lsPfg3n6D/tL0IPGtLs/Dzriufg3BW8w1x1n4upYPuMYoZl+l7qEnUZ5UV3VXx5Y0eojl6Xj/BDGefCLUpolzyp+HaDzdOFuR+h12EbLG9zkF4Xl7Oyz/T2TI0hk4ALPYRHq6Z4lwpw1fR7dnaBfdntdK7rz1uzc5WO75k7UEj7aMrrhtkwiA=) * **Child → Parent**: Pushes features (or other changes) from the child branch back up to the parent, commonly done once a feature is ready to go into the parent branch. ![child-parent](data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAADuUAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAgYAAAB2AAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAADu1tZGF0EgAKChgloF6sECAgaEAy1B1MCALcadegjvSXVVMhTnMfpIeMu7tj9vlTGPplB7nhRDgszQovRNQr2g80Cg7A4WrfgQ2ncpyzvI2bUGLK5gVJtErBYSKHPfY4n/ZSljcE+dWCXX5KtsSjibBuFHiYM6XTk9NWLKMP9xF0dR96wvjLmCRuZrBtigURHjPp1q7qZ0Hrzo8ThD9i7jExozEKvRKlBNDKVU0OknMKrzDNDQ2HrIocwIVKK2xm3WciEPw3zGfLnsYKyV+vm0CGETpPWONJ0k25VBGXzwtTCNs5ppapxFwKFmQguD1hlM5+AlbIND6r1jlAB2Ty7giTPxuLpwKR0peGus4pQgpWfhQyCSv60CfuFNNZWAMQw5PE9aO6lPbFAymGkeoCmOKsotpo5glfaPqu0Y7gnl7UM1ewFki8aXWzQ/buNR9uREYZTz7YLPQeUK7BkQI1atf7BbMZvb0Il98v5qCjiq1SkMLLDlorlhl4j8YnbGUoBml99iGt0WwFnsZfNtejtwjWBmmVA7xIh7NCV/5NPONhCHsHUdt5znGb0qrSaOyhlIWIn5xHKa4e6bKNXwvrEHpAEyJUVMgOR9S/9CWDsZ22chF6Q3fmifBK6ZJf/zp/UGROwJcTb5CkIxpF9iSOdfzfYYVbwZ6c3yXMVVnPMDxZLstEP+k6sSpkOH4qElzAKxQ8uO95Noakoz1RlQGw8+uqo3uJV56FzHDDo9k+gBbZdHYsA7DI1fPV6bTWUt84fsKENVN0HAWn71FJxi5dRVPvNnTcrAKcFjyePFSh6EszlO7pqG/FyMqTrRzvzomJEP5e4ra4MRKNCJ8jZZJWt/kFBTxCjIbO02j6dBDvAWbl8ByNuAYWt5/GSrqU99kt1U60/wrSC+VGvdnDVB80W1dkgRjh38Az2l8B/It+TzWXPyL04xSCValn0qneUrD8TggPjZxy0KD3WCVbtgBD8GUNxxEN0Fz6QQ/o/6ug2oG1Tw7rC5vMfJH3ivhJHy676Xhanr60VtgMCk8ZLTSOsbhN+YJCQk7FnylciY+8Sw5EOuXb0jiYOpCVjnd3kO0GM98SvZXuG57DHlHJMnyZlG8q86IsjawZFsa9Dgs7Na9NQqRZ4DjEnl0igS8YykhtDdjFQdpRp1TPTpT2PEzpV2ZqqCfFkwA82TPPpmhdndv4o5AqDLOc/5qtIybvMGKLZjCZjSM/fCXJXcv9K3p21WKcce+seDH5+JLDoYzbR+F7fVtbxQ8nOan2XMgk5ZOP/40IHChEf4UqoFGQR/KZul/YpKFDJ0sI6q7EIKKmh6z9Gz8FhreB4IwVOtGJnFt9ykpez3z9j/bV01Qsp+kSsn+uqzSvZJnj8jZlgFt82J2YAyr1QGMGXoaAAerf7SWhBQYDYiT+SlV1fdq4wT8CQzsMo45jE8Sbm+xr6B10F+AeJuh78okUHr/aopcR45G+QIMYb7saZqbzCq5zh1beDFhPzCL8eniVpspCYsi3NnSCF0bXibmn9Pt0BIGSkEvg9SLDlrpMh0Ogblpg1gS9rn98AFI/oyhbKf2o6oh45tXM3QtGjY6A0CVm8SL6nveMp5KUv+n3rq7NbDPe0dl4aTRNQYRlyqDCD82n5bDOdVvVgh7p961Y1w703fMIBWvSvqRQ+7jx/hVIFDJx8iEjPrij8gO3MtcQvIoy6UxTlWIhb8/DwisgIcQGeclGm+DKgK4leqymsi7tKh1ghKe679xjIBafjxyqoYqalg9C0H6QolslyAjYBYZ95onE9hgJsuC9GH1/kUSGxG8US3HGN3uXDkZB4JDT2iLhS8Ay4+W/grvzPIqUpTZwbWjoT/QS9x9fCtUGihV/lfm/t43GaF/qSiP8sARn//ngpdL+5vair0GFY4OTPwZqmj6UlVz+7aoidaPAmOF3jLJbO53xHTd267Wz0/uODKnf3bsupnmSGc4RwA8k15jy99wxwGJacqTuz77F12ELjm68XTHqzweT+/Y3kpp6BHEN5i1S3rzJ3PaMycfcFIB7TTfDDe2zOrldpLbG4Gh22xrXpc32LNFtMEwUkqLd2uOx2ODr3nsBqo/e/uh3o7nBcN6Zzh2zt8KJQd3mtfipsvM1HqJZmQBUhpWBou31Iqw9+BD2T2amqB2Eu9pbQ9tUNPyRTp441FKJow6tu7/ni0MADmSd9MAZTvGeZ7S8cI8Sa9e5agWUO3Qd/AYaJIqRQUbT1a/ZOWwW7o/bF5iIFNWIWvD+djAs59se89h7Lth6oH2oFL8z9KK6KF9LF14GxHE7anNu5pURyeMFBBKtPrYECbOQMKm+YjXN88LWjlZGtr0IzonBkYSiw9pBxUvZeRov1EhrbPgftwQLi5JfqCUQ1XEBDc8+TW2UJ41zygaGppqJK32CDAt48EBsywuRR9wUDa8GFN+4d6BDeMhaRLBJKvJg+wsbzbNooUdUIj+0RjxcMEtjrza+JJ62IY5YxRyhtnSg6iJcJDH5RNDlLSZpNzSjG3T1a8ItpSycfDH5pFWT5hhgOI8DOAkcO0prDdhwjaA1CpPZWT9jVFrKhPIlkXLuWcyXHLh9PHo9tmeKBRFu8rzGTj9FtmxwoZwE+U6Fte//b3LqPqnkbJLVSke7hQN9RfI+/RII7QGM9u0HFR8Hq5GxASkM05jzJL5UJzshpTVINjcAeOGl1G37vxmcQ6J+L8TVGG3btLjimqw2yHN7U7qSPeP1zNDMJit9JyrZx/qWcHYK0ePqgJ9KrHbv8khwo7J9ZmgQv7oAfEiSu4EQz3YVkrN1o7D3P2yy4j/tGycQsFQCCV5gIf9xLVc0ziDkgvKRMqfNRe0pgL3I8utB9qZX0QvsaM/oN71TC522/S7TVPrucL1o8aczn7wQsc7SKj6vZwtY18pAG3XEhmn1daMTvVTh+0Xgxpg7xm+OJ4NLB3fiVa41gU3Z8IwtYugsySJxBcI1LTKkwFXnwz+jQrePxw3A9B+Z1x6ofDg4Keu1l6d7FmUL7GaFb5tTx+XlqA7ghYiIty2NR/poZBWvPzGkTDLMex5bALk7xpCo2GfuKf+3Bf6wViPDSYEI5xYxJ+QyEoos8OJSoefhmsripW5On/4J0CwVovALwHTaaYjAH5V9BboMltr+UJHD0HOPGfxHMNZLJIal3KQoIHT4/kWDUSe3dOdPvjmOShSMbExtwoiv94mInz6N05GRTPhCAGUopsIB7WAknJXHneVGy4KD+HhiSIdep2r/KevHvGxtlaY/EsQaxm4Y2aW+hjBQlbIf6wxrgKywZjU0IyX2K0LG8ySCkDZv/J91LDblIMgN29Xi7NGP6v6Riw+arpI6j2fwjdYYSPEq08fLNNhaRTLZxBQG8ABlHWmm7ELepsbWbHBk5D1+vdn+fN4GWgVGCvZM8r/Mekgk5IfEhRISLdl8U0No+HDu6HTQ8I7RlhJPuyeovYSTWeNk2cjgu1NfutdPNMxgyGcK7itWS10HnM6yYqtdEQMz+2Y+uCoNo6063NeL26jBxD5OPl1b6QMHD/DqnIHJ2EexFfAnjuX2zejv9iH4PJmBwNhDkiPTEqtQfCHwlSLF3x1AdXzgZsNELbOtKU/+GEtPbCXygvwaPj3Z9quxOsOQRO6XcTTpYuTRUzydAzZL/Ipa3mT0TzSZY3s6ux2eAiD6jkMdw2SSpd+nGLiyL8ERvEOoZV/8E954Rrit8LR5HNoNs+BqQ2KOt6QZfbQnIDwGOM6v7QZI+502FoV94Dfq0U/HYzYHIuCkO/b330xPKcszm0TfLVWeavkXeDPhsZ+GuMRGdhP2Gj+CPfi9Jnjb8WB3B9J0ImCjauA75qQlThS+z0J+WU9yLI2SWXTEIRoypDr7iT0Kp4RzLl3SLMWgekEinWk5/hW1lB/6Sv/D2mEAOJyLC1XCLGGN4nbkuqBXBEWlOs1ETqV6ETl4vyIt7e+7q8EwAX2kZ6FwqvvssJiJNEbAhXmGEbTgQYlqJpLIC1TMWgskN8HnQNHlm6gxlEK8gbADkWmGQ6e9HWDXz684y0usBslUkObarNoeVag6zDGMs/yGn4e8B7EKPSPLTkXz7mKFWjN25oa83T0i2Ocv+vb1IHDvdv4+t+CGCRbuyjspn+Y+uhplprPUexF/mwO65eu+obk8m5HnBzoz5xqfOJZPlkc+IA/c+2TQ6dsH62dKqy9i6J9xPL9hcTSdfAkaYhr3QT0FIblh77YTsjxXSl9+ucQR7elnd+W2Zx0fPQQdHMd8L7S0TGoNmLt4BytoCGgRs6gU6qPV6wYTe5QShsKW27iQmh1N+BdECj30M0d6Svl4eacl3kOnaknVZQqs/977nCVqSuvRW0NitrzdN1u3m1oqH/tntwmmyXtgacRaw44BfyJ42wAF2bg3lVbRVtfPcrqPOjBybZe+yxHUXQ4tgNM3hIw661AOYcg97H5YVUtUy0tZoUE+ZD6SgX80cRCXjHDeWNo4JJdHMLoSpJ/TFgCko4AVviykXwEygmTzH7fVFk5AqBF4VKNdNCBTtnK7Z78rA0qDB6vxuSXRBXSckf2/DSsfv/Qh6K8AK7jEhi/rsQSIlemEtxRmleEB6T1XcPWhZBxHDNwGHML219/ecjFLmDWBNPyJpTCzQGVYooFfIa02QJzYRlnS7Hy/WXE2btQLPnbh/p7w6yTWb3wMCpwqK5rzbzqzCK9P/OPldHuMq4kINklxwIc3N+05VQr8rfiiOBueOP0Gyt+CsmWwEc5RnmXO/RHonKmBmUy6KJeyUCbKMqcZ3sW4eY2m8yneeJr6PSFffff5OEqwhj88ir4mmW9afLr9fDaxxNynU3e2lxLwRJUBFSd4XMb+YpZAnhQt091jBMj7nLJ3k3/hMn+Ia8djkBSUxNCjkSZwz9SV+7Zrsdxsx58K1Z8ZWHbzgpnQYNrzYeSEw2L0xZvTpPPRGg1qVoAFFttRImNBx6H2xxdy2x3zjUholvHRqFKV3bCOCBIsZQkjkgwpPQJjfZmh/xIA3ZCSBxIF51D77vqvoKvizViKw0QcgyLiHk/WtHkiV0/To2dKwkgIw09OBNgiQ6lqS5YesAzhAgzA) * **YAML Validation Errors**: These occur when the resulting data is not in a “FlutterFlow-friendly” format—whether that’s due to manual edits or merges that generate incompatible YAML. For example, imagine you have two pages in your project, and each branch independently deletes a different one. After merging, there are zero pages left. Even though no lines of code are edited or directly have a conflict, this results in a YAML Validation Error. Clicking on these errors should redirect you to the specific file. Invalid lines will be underlined in red within the file, and, you cannot complete the merge while YAML errors exist. ![yaml-validation-error](/assets/images/yaml-validation-error-191b32eac7c334bdb481a34ece669a4d.avif) * **Project Errors**: Project errors occur when the result of a merge creates a problem in your project. For example, this might happen if the merge results in two data types having the same name. These errors need to be resolved to ensure your project works as expected. You have several options to deal with project errors: * **Fix Errors During the Merge**: This approach ensures that the merged project is error-free right from the start. Here’s how you can do it: * **Edit the YAML files:** Update the project YAML files (in the Right Lower Panel) to fix issues, such as renaming a data type that causes a conflict. * **Edit the Project Directly while Merging:** While still in the merge process, open the project, make the necessary changes (like renaming the conflicting data type), and then continue. * **Fix Errors After the Merge**: If you prefer, you can complete the merge first and address the errors later. For example, finish the merge process as it is. After merging, go back to the project and resolve any issues. * **Cancel**: Abandons the merge process and discards any conflict resolutions you’ve already applied during this merge session. * **Merge**: Finalizes the merge once all merge conflicts and YAML validation errors are cleared. Project errors can remain if you choose to resolve them later. * **Bulk Accept Changes**: Accessible via the **arrow** next to **Merge** button. This option lets you accept all changes from one branch at once—handy if you already know which branch’s changes take precedence. ![bulk-accept](/assets/images/bulk-accept-dd67d3857d7e5b5ff873c4e53f05cddb.avif) **Left Panel** The left-hand side panel displays all the project files in YAML format. YAML (Yet Another Markup Language) files use a simple, human-readable format to define configuration data. They are particularly useful during a merge because they allow you to directly review, understand, and resolve any changes or conflicts in your project’s file. * **Filter Files:** You can use filters to narrow down the list of YAML files based on specific criteria: * **All Files (Unchanged Files)**: Shows every YAML file in the project that has no changes. * **Files with Changes**: Displays only files where a change has been made on either branch. * **Files with Conflicts**: Shows only files that have merge conflicts, where the changes in one branch directly contradict the changes in the other. info * A **change** refers to any update, addition, or deletion made in one of the branches. For example, modifying a field name or changing the properties of a widget. ![change](/assets/images/change-7047276948eae556fafbf757de879206.avif) * A **conflict** occurs when the same part of a file has been changed in both branches, making it unclear which version to keep. For instance, if one branch changes the color of the Container to blue and the other changes it to red, this creates a conflict. ![conflict](/assets/images/conflict-e8856491422d4b03fa8dc2bffdf9efda.avif) * **Search File:** If you’re looking for a particular file, you can use the search bar to locate it quickly. This is especially useful in larger projects with many files. Clicking on a file in the panel opens it in the editor, allowing you to view, edit, and resolve issues directly. **Right Upper Panel** The Upper Right Panel offers a quick, side-by-side comparison of file changes from both branches, along with easy one-click accept buttons and previews. This panel makes it simple to decide which changes to keep or discard. info The edits are highlighted using green and red (Git) color coding: * **Green** indicates lines or values **added** (or unique) in one branch. * **Red** indicates lines or values **removed** (or replaced) by that branch. - **Accept Change Button**: Quickly accept changes from one branch if you know it has the correct edits. - **Eye (Preview) Icon**: Open or view the file in the FlutterFlow builder to see how the changes look. For example, you can preview a theme color change visually rather than just reading its name in the file. **Right Lower Panel** The **Lower Panel** displays the final merged files after Git applies its merging logic. It gives you a chance to manually inspect and edit the outcome—whether or not a conflict occurs. Git attempts to combine changes from both branches automatically. If Git can’t reconcile certain lines, it flags a **merge conflict** in the file. Conflicts appear with special markers like `<<<<<<<`, `=======`, and `>>>>>>>`. * `<<<<<<<`: Marks the beginning of other branch’s changes * `=======`: Separates your current branch’s changes from the other branch’s changes. * `>>>>>>>`: Marks the end of the conflict, indicating your current branch’s changes. tip You might decide to keep certain lines from `<<<<<<<` (from the other branch) or `>>>>>>>` (from your branch) or combine them manually. You can modify files or edit the project directly from the lower panel at any time—even if there’s no conflict. After editing, click **Save Changes** to confirm your changes. A red reset button appears if you want to undo your changes and restore the file to its initial state before you began editing. For more information, check out the video below. ### Resolve Merge Conflicts[​](/collaboration/branching.md#resolve-merge-conflicts "Direct link to Resolve Merge Conflicts") A merge conflict occurs when multiple team members make changes to the same part of the project. For example, imagine two developers, Alice and Bob, are working on the same FlutterFlow project and both decide to update the same button widget. | **Developer** | **Branch Name** | **Changes** | | ------------- | --------------- | ------------------------------------------ | | Alice | `feature-alice` | - Changes the button text to "Submit Form" | | | | - Changes the button color to blue | | Bob | `feature-bob` | - Changes the button text to "Send" | | | | - Changes the button color to green | When Alice's changes are merged into the main project first, her updates will be integrated without any issues. However, when Bob tries to merge his changes afterward, a merge conflict will occur because the changes to the button text and color have already been modified by Alice. When you initiate a merge using Git, the system attempts to automatically reconcile your project files. Any conflicts that cannot be automatically resolved are flagged for your attention. You can review each file with merge conflicts and choose to: * Accept all changes from one branch. ![accept-all-from-one-branch](/assets/images/accept-all-from-one-branch-dc23fea9853ea19f885cea8a473716eb.avif) * Pick specific changes from any branch. ![accept-specific-change-from-file](/assets/images/accept-specific-change-from-file-3123c05847bd438e8ce69ad336f7bc5c.avif) * Manually edit the YAML files. **Note that** it’s essential to correct any YAML validation errors that arise from manual edits. Finally, complete the merge by clicking **Merge**. tip * If you merged a child branch into its parent and are confident everything looks correct, you may delete the child branch. * If you find any issues after the merge, you can revert the branch to an earlier commit. However, be aware that any changes made after that commit will be lost. ## Branch-level Permissions[​](/collaboration/branching.md#branch-level-permissions "Direct link to Branch-level Permissions") In your project, you have the ability to assign specific roles such as **Editors** and **Mergers** to project members for each branch. To configure these permissions, navigate to **Settings & Integrations > Project Setup > Collaboration > Branch-Level Access**. ![branch-permission](/assets/images/branch-permission-9bf4148afe3cc265ffdb3ee474c17766.png) * **Editors** assigned to a branch have the authority to make direct modifications to the project while working within that branch. * **Mergers** on the other hand, are only allowed to merge other branches into that branch. This is especially useful for protected branches where you don't want any users to make direct modifications. Instead, users should only merge other branches into that branch. ## Closing Branch[​](/collaboration/branching.md#closing-branch "Direct link to Closing Branch") Closing a branch is a common practice after the branch has served its purpose, typically once its changes have been merged into another branch (like the `main` or `development` branch). By regularly closing inactive or merged branches, you help maintain a clean, efficient, and well-organized project. When to Close a Branch * **After a Merge:** Once the branch’s changes have been merged into the `main` branch (or another target branch), it’s safe to close the branch. This often happens after a feature is complete or a bug is fixed. * **Unused Branch:** If a branch is no longer needed (e.g., a feature was abandoned or changes were made in another branch), it’s a good idea to close it. Here’s how you can close a branch: Best Practices * **Review before deletion:** Before closing a branch, ensure that all necessary changes have been merged or no longer need to be kept. * **Coordinate with your team:** If you’re working in a team, ensure that no one is actively using the branch before you close it, to avoid disrupting ongoing work. Once a branch is closed, it will no longer appear in the list of active branches. However, you can restore a closed branch within **30 days** of its closure. ### Restore Branch[​](/collaboration/branching.md#restore-branch "Direct link to Restore Branch") To restore a branch, open the **Branch Filter** menu and enable **Show Closed Branches**. Search for or select the branch you want to restore, and it will open in a new browser tab. Then, within the closed branch, open the **Branching Options** menu and select **Restore Branch** to reactivate it. ## FAQs[​](/collaboration/branching.md#faqs "Direct link to FAQs") How YAML files are helpful during a merge? YAML files play a key role in managing and resolving conflicts during the merge process because: * YAML files hold important configuration data, such as settings, resource definitions, and project properties. During a merge, changes in these files reflect modifications to the structure or behavior of the project. * The simple and hierarchical nature of YAML makes it easy to spot changes or conflicts, even in complex files. * YAML files allow you to manually edit and resolve conflicts during the merge process. * Since YAML files are text-based, they are version-controlled effectively, enabling multiple team members to make changes and merge their work. Why didn’t all my changes appear after merging two branches? Merging in Git is not like copying everything from one branch into another. It’s more like combining changes from two versions of a document based on a common starting point. Let’s say you and your friend both made changes to the same project: * You both started with the same original version (this is called the common ancestor). * You made your changes in `Branch A`. * Your friend made changes in `Branch B`. When you merge `Branch B` into `Branch A`, Git compares: * What changed in `Branch A` since the common starting point. * What changed in `Branch B` since the common starting point. If both of you changed different parts, Git can merge them easily. But if you both changed the same part in different ways, Git won’t know which one to keep, that's called a conflict, and you'll need to resolve it manually. ![git-merging-behavior](/assets/images/git-merging-behavior-086d608f27fae6843bcd054db6a244b0.avif) Here are a few other things to know: * **No conflicts ≠ no changes**: “No conflicts” doesn’t mean “no changes” and it definitely doesn’t mean the project is error-free. * **Project errors are not bugs**: Project errors let you know that you are making mistakes when merging data. Even if changes are successfully merged, project errors indicate areas you should double-check to ensure everything merged as expected. * If a change was previously accepted or rejected during a merge, it won’t appear as a diff the next time you merge the same branches. That’s expected behavior. For example, you merge `Branch B` into `Branch A`, and `change C` (which exists in `Branch B`) gets copied over to `Branch A`. Later, you decide to undo `change C` directly on `Branch A`. Now, if you merge `Branch B` into `Branch A` again, Git will not re-flag `change C` as a difference. This is because Git considers it already merged and no longer a diff. **Best Practice:** Keep your branch histories short and simple. After each merge, delete the merged branch to avoid unnecessary complexity. For example, if you merge `Branch B` into `Branch A`, and later want to undo or revise those changes, don’t go back and modify `Branch B`. Instead, create a new branch (e.g., `Branch C`) from `Branch A` to make your updates. This approach prevents intertwining branch histories, avoids confusing merge behavior, and ensures clean, trackable diffs. Keeping branches focused and temporary makes merging more predictable and manageable. --- # Saving and Versioning In this section, we discuss the important concepts of saving and versioning in your project. Understanding how to use versions, snapshots and commits can be crucial in preventing loss of work and maintaining progress. ## Versions[​](/collaboration/saving-versioning.md#versions "Direct link to Versions") Project Versions are now deprecated You can no longer create new versions in FlutterFlow. However, any previously created versions will remain accessible. Moving forward, we recommend using [**Commits**](/collaboration/saving-versioning.md#commits), which provides a more robust way to track changes and manage your project history. ### Restoring a version[​](/collaboration/saving-versioning.md#restoring-a-version "Direct link to Restoring a version") Restoring the previous version will preserve the current version, then load the changes from the version you're restoring. Before restoration, you may want to view the changes in the previous version. To do this, select the **Peek** option, which opens the previous version in a new tab. ![restore-version](/assets/images/restore-version-801604fdeba89500669a9e66822f191e.avif) ## Commits[​](/collaboration/saving-versioning.md#commits "Direct link to Commits") Commits are similar to versions in that you can save the state of your project at a point in time. Commits are saved to a specific branch's history. With commits you can view the specific changes made in that commit and restore a branch to the state of a specific commit. For more details see this page on [Branching and Commits](/collaboration/branching.md#commits). ## Snapshots[​](/collaboration/saving-versioning.md#snapshots "Direct link to Snapshots") Snapshots are automatic saves of your project's state as you build it. They allow you to **Peek** or **Revert** to a previous state of the project if needed. ![snapshots](/assets/images/snapshots-4bb79f7b55a02f7d11d2dfe7001a8da2.avif) info * Users on the **Free** plan can access automated snapshot backups from **up to 1 hour prior**. * The **Basic** plan allows access to backups from **up to 1 day prior**. * The **Growth** plan provides access to backups from **up to 3 days prior**. * The **Business** plan extends this to **up to 7 days prior**. * For **Enterprise** users, snapshot retention is **customized**. --- # Accessibility Accessibility is about making your app usable for everyone, including individuals with visual, auditory, cognitive, or motor impairments. Ensuring your app is accessible not only benefits users with disabilities but also improves the overall user experience and usability of the app for everyone. Here are some examples of how accessibility can help users with disabilities: * **Screen Readers for Visually Impaired Users**: Screen readers like **TalkBack** (Android) and **VoiceOver** (iOS) help visually impaired users navigate and understand the app by reading aloud on-screen content. * **Large Touch Targets for Motor Impairments**: Large touch targets make it easier for users with motor impairments to interact with buttons and other UI elements. * **Color Contrast for Visual Impairments**: High-contrast color schemes ensure text and interactive elements are easily readable for users with visual impairments. * **Keyboard Navigation for Physical Impairments**: Users unable to use touchscreens can navigate the app effectively with keyboard controls. * **Haptic Feedback**: Tactile responses help users with visual or motor impairments understand when interactions are successful. In FlutterFlow, you can enhance the accessibility of your app by incorporating various accessibility features, such as semantic labels, keyboard navigation, haptic feedback, responsive fonts, and proper color contrast. Here are some key accessibility features you can use: ## Semantic Label[​](/concepts/accessibility.md#semantic-label "Direct link to Semantic Label") **Semantic Labels** enhance your app’s accessibility and SEO by providing meaningful context about widgets for screen readers and search engines. These descriptions are especially helpful for users relying on assistive technologies. For example, in an e-commerce app, you can add a semantic label to an '*Add to Bag*' button with a message like '*Add the selected item to cart*', which helps users better understand the button's action. To add a semantic label for any widget, select the widget, move to the properties panel (right side), tap the document icon inside the **Accessibility & Semantic Label** section, add the message, and click **Save**. tip You can also dynamically set semantic labels using variables or expressions. This allows the label to change based on the app context, so screen readers announce exactly what’s on the screen instead of generic terms like "image" or "button." For example, a product image can read out the product name (e.g., "Red Running Shoes" pulled from Firestore) instead of just saying "image." ### Advanced Semantic Settings (Enterprise Only)[​](/concepts/accessibility.md#advanced-semantic-settings-enterprise-only "Direct link to Advanced Semantic Settings (Enterprise Only)") These settings help make your app more accessible by giving you better control over how screen readers interpret and describe your UI. info These settings are only available to **Enterprise** users. Here’s what each option does: * **Is Container**: Indicates the widget acts as a grouping for other semantic widgets. * **Is Image**: Tells screen readers the widget represents an image. * **Is Button**: Declares that the widget behaves like a button. * **Is Header**: Identifies a widget as a heading for better navigation. * **Explicit Child Nodes**: Forces semantics to include all child nodes, even if normally ignored. * **Exclude Semantics**: Prevents screen readers from announcing this widget. * **Is Live Region**: Tells assistive tech that the widget’s content may change dynamically and should be re-announced. * **Hint Text**: Provides an additional hint for users (e.g., "Double tap to open"). * **Tooltip Text**: Provides descriptive text about the widget to screen readers, giving extra context beyond the primary label. * **Ordinal Sort Key**: Controls the order in which widgets are accessed by screen readers. tip You can add a semantic label for every widget in your app that has an action trigger `OnTap` or `onLongPress`, by enabling the **Add Warning for Semantic Widgets**. By doing so, you'll get a warning if any widget has an action but doesn't have a semantic label added yet. You can click on the warning item to directly navigate to that widget. ![add-warning-for-semantic-widgets.avif](/assets/images/add-warning-for-semantic-widgets-5d0dc639482abc4bace4a41d4cd01da2.avif) After you add semantic labels, enable **TalkBack** on Android or **VoiceOver** on iOS to test how screen readers interact with your app. These screen readers will help you verify that all UI elements are read clearly, descriptions are meaningful, and users can navigate logically without getting lost. Learn more about [enabling screen reader on your device](https://docs.flutter.dev/ui/accessibility-and-internationalization/accessibility#screen-readers). ## Semantic Announce \[Action][​](/concepts/accessibility.md#semantic-announce-action "Direct link to Semantic Announce \[Action]") The **Semantic Announce** action lets you notify screen reader users about important UI changes or provide contextual updates. It sends a request to the device’s accessibility service (TalkBack/VoiceOver) to speak the text out loud. It significantly improves accessibility by allowing screen reader users to receive timely and meaningful feedback. This is especially helpful when visual feedback might be missed or unavailable. possible use cases * **Form Submission**: After a user submits a form, you can trigger a screen reader announcement like "Your form has been submitted successfully," giving immediate feedback without requiring visual cues. * **Dynamic Content Updates**: When new content is added or changed on the screen—like loading new chat messages or refreshing a feed—you can announce messages like "3 new messages loaded" to ensure screen reader users are aware of the update. * **Error or Validation Messages**: If a user enters invalid input, you can announce helpful validation feedback like "Please enter a valid email address". The Semantic Announce action allows you to trigger screen reader announcements with the following settings: * **Announcement Text**: The message you want the screen reader to speak aloud (e.g., "Item added to favorites"). * **Is Text Right to Left**: Set this to True for right-to-left languages like Arabic or Hebrew. It defaults to False, which is appropriate for left-to-right languages like English. ![semantic-announcement.avif](/assets/images/semantic-announcement-12a2c9ab81b23b565dca5b1d97b520e3.avif) Best Practices * Long announcements can overwhelm the user. Aim for a concise phrase like "Search complete — 3 results." * Too many announcements can confuse or irritate the user. Only announce critical or timely changes that aren’t otherwise discoverable. * Use the correct language direction of the message. If your app supports multiple locales, dynamic direction binding can help. * Screen reader behavior can vary across Android (TalkBack) and iOS (VoiceOver). Test thoroughly on real hardware to confirm the experience. ## Focus Configuration[​](/concepts/accessibility.md#focus-configuration "Direct link to Focus Configuration") **Focus Configuration** helps improve keyboard and remote-control navigation in your app—especially important for web, desktop, TV, and kiosk apps. It controls how users move through widgets using the `Tab` key or other navigation inputs (like arrow keys or D-pad on TV or remote). You can control the Focus Configuration using the following properties: * **Wrap in Focus Traversal Group**: It places a widget (and all its children) in a dedicated group so focus cycles within that region before moving on. For example, if you have a login form with two fields: Email and Password, enabling this option ensures that pressing `Tab` will cycle only between them (and not jump to unrelated parts of the screen). * **Focus Traversal Order**: This sets the exact sequence in which widgets receive focus using numeric values (e.g., 1, 2, etc.). For example, In a sign‑up form, set `Name = 1`, `Email = 2`, and `Password = 3` so pressing `Tab` moves logically down the form rather than following the raw widget tree. * **Show Border on Focus**: Enabling this toggle highlights the widget with a visible border when it receives focus, making navigation clearer. Once enabled, you can customize the border’s appearance using **Border Width**, **Border Color**, and **Border Radius** to match your design. warning While you can assign a value for the **Focus Traversal Order** of any widget, it won’t take effect unless you enable **Wrap in Focus Traversal Group** on the current widget or one of its parent widgets. The **Focus Traversal Group** defines a context or scope for focus traversal, and **Focus Traversal Order** only applies within that group. Without it, there's no defined order for the traversal logic to follow. ## Update Text Scaling Factor \[Action][​](/concepts/accessibility.md#update-text-scaling-factor-action "Direct link to Update Text Scaling Factor \[Action]") The **Update Text Scaling Factor** action in FlutterFlow allows you to dynamically adjust the text size across your app during runtime. This is particularly useful for improving accessibility by letting users control the size of the text without having to manually change system settings. Imagine you have a "+" and "-" button on a page to help users adjust text size. When the user taps the "+" button, the text scaling factor increases by 1, making the text larger. Tapping the "-" button decreases the text scaling factor by 1, making the text smaller. Additionally, a Reset button can be provided to return the text scaling back to its default value. info This action works in conjunction with the [**Display Settings**](/resources/projects/settings/general-settings.md#display-settings) configured at the project level, such as **Min Text Scaling Factor** and **Max Text Scaling Factor**. When configuring the Update Text Scaling Factor action, you can choose from three update types: * **Set Value**: Directly assigns the text scaling factor to a specific value. * **Increment/Decrement**: Adjusts the current scaling factor by a specified amount. A positive value increases scaling, and a negative value decreases it. * **Reset**: Restores the text scaling factor to the project's default setting. ![text-scaling-action](/assets/images/text-scaling-action-8c7c4afe7cb0c3575599aecae82eb60e.avif) ## Keyboard Navigation[​](/concepts/accessibility.md#keyboard-navigation "Direct link to Keyboard Navigation") You can use the [On Shortcut Press](/resources/ui/pages/page-lifecycle.md#on-shortcut-press-action-trigger) action trigger to bind keyboard shortcuts to specific actions. This makes it easier for users with disabilities to navigate your app, especially in web and desktop environments. It enhances accessibility by allowing users to interact without relying solely on a mouse or touchscreen, making the experience more inclusive and efficient. ## Haptic Feedback[​](/concepts/accessibility.md#haptic-feedback "Direct link to Haptic Feedback") Using [Haptic Feedback](/concepts/alerts/haptic-feedback.md), you can vibrate the user's device, which is particularly helpful for users with visual or cognitive impairments. It provides a tactile response to indicate that an action has been completed. For example, vibrating the user's device when successfully submitting a form. ## Responsive Fonts[​](/concepts/accessibility.md#responsive-fonts "Direct link to Responsive Fonts") When developing an app, it's important to consider the different platforms on which it will run. Text may appear smaller on devices with higher screen resolution, such as tablets, web, or desktops, which can negatively impact accessibility for users with visual impairments. [Adding responsive text](/concepts/design-system.md#adding-responsive-text-styles) that adjusts font size based on the platform helps make content more readable, improving accessibility for users who need larger or more legible text. ## Color Contrast[​](/concepts/accessibility.md#color-contrast "Direct link to Color Contrast") Use sufficient color contrast to make text and interactive elements readable for users with visual impairments or color blindness. This helps ensure that content is easily distinguishable, even for users with limited vision. Learn more about using various ways to [add colors](/concepts/design-system.md#colors) in your FlutterFlow app. tip You can use tools like [**WCAG Contrast Checker**](https://webaim.org/resources/contrastchecker/) to validate the color contrast ratio. ## Best Practices[​](/concepts/accessibility.md#best-practices "Direct link to Best Practices") * Accessibility should be considered from the start of the design and development process, not added as an afterthought. * While adding [semantic labels](/concepts/accessibility.md#semantic-label): * Avoid ambiguous labels like "Click here" or "Press this"; instead, use descriptive phrases such as "Submit form" or "Navigate to settings." * Instead of just showing an icon, add semantic labels like "Back button" or "Search button" to provide context for screen readers. * Ensure that interactive widgets have a minimum touch target size of 48x48 logical pixels. This helps users with motor impairments easily interact with buttons, switches, and other components. * Always test your app with screen readers enabled to verify that it behaves as expected. * Don't use color as the only means to convey important information. Include text, icons, or patterns to supplement color, making the content accessible to colorblind users. * Verify your app's UI under high contrast or larger text sizes to ensure it remains readable and usable. * Use simple gestures like taps and double-taps instead of multi-finger swipes or long presses. Provide alternate ways to perform actions, such as using a button in addition to a swipe gesture. * Perform usability tests with individuals with disabilities. Real user feedback is invaluable for identifying issues that might not be caught during standard testing. --- # Integrating Native SDKs Using Method Channels Flutter lets you build one app that runs on mobile, web, desktop, and embedded experiences from a single codebase. You write your app logic in Dart once, which is then compiled natively for the target platform. This is a big advantage for teams that want to reduce duplication between Android and iOS apps while maintaining great performance and flexibility. For native developers accustomed to Kotlin, Java, Swift, or Objective-C, Flutter provides access to platform-specific functionality. The bridge between Dart and native code is called a **MethodChannel**. You can think of it as a two-way door: Dart can ask Android or iOS to run some code, and the platform can send results back. The two sides communicate using messages, rather than shared memory, which makes the system simple and secure. With MethodChannels, you can: * Call Android and iOS APIs that aren’t built into Flutter. * Use advanced third‑party SDKs (e.g., barcode scanners, Bluetooth libraries, custom UI components). * Perform operations that need native performance or device-specific access. * Return data from the native side into Flutter with low latency. **Why this matters for native engineers** | What you need | How MethodChannel helps | | ------------------------ | ----------------------------------------------------------------------------------------------- | | Reach every platform API | You can call any Android or iOS API from Flutter using familiar native code. | | Keep the app smooth | Messages are encoded in binary and handled asynchronously, so the UI remains responsive. | | Keep the key code native | You can keep performance-sensitive or secure logic in Kotlin/Swift while building UI in Dart. | | Bridge advanced SDKs | Integrate with native libraries that do not have Flutter support, without waiting for a plugin. | You’re not limited by what Flutter provides out of the box. MethodChannels lets you plug in your native knowledge exactly where needed, so you don’t lose years of platform experience when moving to Flutter. ## What is a MethodChannel?[​](/concepts/advanced/method-channels.md#what-is-a-methodchannel "Direct link to What is a MethodChannel?") A **[MethodChannel](https://docs.flutter.dev/platform-integration/platform-channels)** or **Platform Channels** is Flutter’s core mechanism for integrating platform-specific functionality. It allows Dart code to send messages to, and receive responses from, the host platform’s native code - Android (written in Kotlin or Java) or iOS (written in Swift or Objective-C). This enables your Flutter app to access device features and third-party native libraries that are outside the scope of the Flutter framework or its plugin ecosystem. Here is an example of MethodChannel. ``` class _BatteryLevelScreenState extends State { // Define the MethodChannel with a unique name. This name must match the one used on the native side. static const platform = MethodChannel('com.example.battery'); // Variable to hold the battery level. String _batteryLevel = 'Unknown battery level.'; // Method to invoke the native method to get the battery level. Future _getBatteryLevel() async { String batteryLevel; try { // Invoke the method on the native side. final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result%.'; } on PlatformException catch (e) { // Handle exception if the native code fails. batteryLevel = "Failed to get battery level: '${e.message}'."; } // Update the UI with the retrieved battery level. setState(() { _batteryLevel = batteryLevel; }); } ``` MethodChannels operate over a named channel using a message-passing model. You define a unique **channel name**, such as `'com.example/device'`, and both the Flutter and native sides agree to use it. On the Dart side, you call a method using `invokeMethod()`, sending an optional payload. The platform side sets up a listener (known as a method call handler) that waits for these invocations, runs native logic, and returns a result. This message flow is asynchronous and decoupled: * Dart code doesn’t block while the native code runs; it returns a `Future` that resolves when the result is ready. * Native code must explicitly return a result using either `success`, `error`, or `notImplemented`, ensuring consistent feedback. ### Key Concepts[​](/concepts/advanced/method-channels.md#key-concepts "Direct link to Key Concepts") * **Channel Name**: A unique identifier string that both Flutter and native code must use. Example: `'com.example/platform'`. Naming collisions should be avoided by namespacing based on your app or organization. * **Method Invocation**: Flutter calls `invokeMethod('methodName', arguments)`. The method name is a simple string. Arguments can be null or any value supported by Flutter’s `StandardMessageCodec` (bool, int, double, string, List, Map). * **Method Handler**: Native code uses a handler (e.g., `setMethodCallHandler` on Android) to listen for calls and run logic when the specified method name is matched. * **Result Callback**: The native handler must return a result via `result.success(...)`, `result.error(...)`, or `result.notImplemented()`. These responses are passed back to Dart, completing the `Future`. ### Example Message Flow[​](/concepts/advanced/method-channels.md#example-message-flow "Direct link to Example Message Flow") ![method-channels.avif](/assets/images/method-channels-ec81d7359e3e6892c652e1b5905c3bee.avif) This design ensures clear separation between platform and UI logic, and it keeps the UI thread non-blocking for both Dart and native sides. It also makes the communication extensible—you can define as many methods as you need over a single channel or use multiple channels for modular organization. ### When to Use a MethodChannel[​](/concepts/advanced/method-channels.md#when-to-use-a-methodchannel "Direct link to When to Use a MethodChannel") MethodChannel is most appropriate when: * You need to use Android/iOS APIs not available in Flutter or plugins (e.g., access to specific hardware sensors, native storage APIs). * You need to integrate a proprietary or vendor SDK (e.g., analytics, payment, OCR) written for the platform. * You need to launch a platform-native UI (e.g., a full-screen scanner or a native file picker). * You’re bridging a legacy native feature into a Flutter app or gradually migrating a native app to Flutter. ### What MethodChannels Are Not[​](/concepts/advanced/method-channels.md#what-methodchannels-are-not "Direct link to What MethodChannels Are Not") * They are not **shared memory** - All data is copied through serialization, not shared by reference. Only standard types are supported (primitives, lists, maps, typed data). Large data transfers require full serialization/deserialization. * They are **not synchronous** - Calls return Futures immediately without blocking. Results arrive asynchronously via the event loop. Platform errors surface as PlatformExceptions when the Future completes. * They are **not opinionated** - You define the API contract (method names, arguments, types) on both sides. There's no compile-time validation across the boundary - mismatches fail at runtime. Document your contract and validate inputs since type safety isn't enforced. By understanding these characteristics, you can create robust, maintainable bridges between Dart and native code. You can write minimal, purpose-driven native handlers and keep the rest of your app in Flutter, achieving both deep platform access and cross-platform speed. ## Real-World Use Cases for MethodChannels[​](/concepts/advanced/method-channels.md#real-world-use-cases-for-methodchannels "Direct link to Real-World Use Cases for MethodChannels") While Flutter plugins cover many common platform integrations, there are frequent scenarios where you require direct access to native SDKs or platform-specific APIs. MethodChannels offer a direct path for these integrations without waiting for third-party plugin support. Ultimately, method channel integration is essentially plugin development - you're writing the same native bridge packaged for your app instead of as a public package. Once complete, it can be imported into FlutterFlow. The following examples show when building your own native integration is more practical than waiting for or wrestling with existing plugins. The following examples outline situations where MethodChannels are suitable. ### Accessing Device Hardware Not Exposed by Plugins[​](/concepts/advanced/method-channels.md#accessing-device-hardware-not-exposed-by-plugins "Direct link to Accessing Device Hardware Not Exposed by Plugins") **Example:** Retrieve mobile network signal strength, advanced battery metrics, or thermal status. * Low-level APIs like Android's `TelephonyManager` or iOS's `CoreTelephony` are rarely exposed through Flutter plugins. * These require direct permission management and native invocation. * With MethodChannels, you can call only what you need, without waiting for a plugin update or writing one from scratch. **Benefit:** Access hardware-level telemetry or diagnostics crucial for field-service apps, testing tools, or enterprise reporting. ### Integrating Proprietary SDKs or Vendor Libraries[​](/concepts/advanced/method-channels.md#integrating-proprietary-sdks-or-vendor-libraries "Direct link to Integrating Proprietary SDKs or Vendor Libraries") **Example:** Use a third-party identity verification SDK, document scanner, or encrypted storage SDK. * Many vendors distribute Android/iOS SDKs only and have no Flutter wrappers. * A minimal native wrapper and MethodChannel interface let you expose only the needed functionality. * Native SDK updates remain decoupled from Flutter UI changes. **Benefit:** Unlocks core business features (KYC, biometrics, payments) without dependency on plugin authors or external wrappers. ### Embedding Native UI Views Temporarily[​](/concepts/advanced/method-channels.md#embedding-native-ui-views-temporarily "Direct link to Embedding Native UI Views Temporarily") **Example:** Show a native PDF viewer, a camera UI from a vendor SDK, or an AR interface. * `PlatformView` allows embedding native UI, but it requires more setup and introduces performance tradeoffs. * If the native UI is temporary or full-screen, you can invoke it via MethodChannel and return control to Flutter afterward. **Benefit:** Delivers platform-native experiences where needed while preserving Flutter’s rendering pipeline elsewhere. ### Background Tasks and Event-Driven Native APIs[​](/concepts/advanced/method-channels.md#background-tasks-and-event-driven-native-apis "Direct link to Background Tasks and Event-Driven Native APIs") **Example:** Respond to geofencing events, push token refresh, or Bluetooth device state changes. * These use cases originate in native services or background tasks. * You can queue or debounce events on the native side and send them to Flutter via MethodChannel when the app is active. * For continuous updates, use `EventChannel`, as MethodChannel is ideal for transactional or one-off data transfers. **Benefit:** Achieves OS-level integration (e.g., location, power, Bluetooth) without polling or Dart-side complexity. ### Secure Device Data Retrieval[​](/concepts/advanced/method-channels.md#secure-device-data-retrieval "Direct link to Secure Device Data Retrieval") **Example:** Fetch IMEI, MAC address, device fingerprint, or system identifiers. * These APIs often require special entitlements and native-side permission prompts. * Native logic can validate permissions, sanitize data, and decide what’s safe to return. **Benefit:** Ensures security-sensitive operations remain native-controlled, supporting enterprise, regulated, or BYOD environments. ## Implementing a MethodChannel[​](/concepts/advanced/method-channels.md#implementing-a-methodchannel "Direct link to Implementing a MethodChannel") This section walks through the complete implementation of a MethodChannel, showing how to define the channel in Flutter (Dart), connect it to native platform code, and properly exchange messages, arguments, and results. For native developers used to Android or iOS, this breakdown will show how to bridge Dart and native code in a way that is robust, testable, and production-ready. ### 1. Dart Side (Flutter)[​](/concepts/advanced/method-channels.md#1-dart-side-flutter "Direct link to 1. Dart Side (Flutter)") In Flutter, you use the `MethodChannel` class from the `services` package to create a communication path. The Dart side always initiates the call, and the native side responds. **Define and Use a Channel:** ``` import 'package:flutter/services.dart'; const platform = MethodChannel('com.example/device'); ``` * The channel name `'com.example/device'` must match **exactly** with the one used on the native side. * Channel names should follow a reverse-domain convention to avoid collisions. **Sending a Method Call:** ``` Future getBatteryLevel() async { try { final int result = await platform.invokeMethod('getBatteryLevel'); return 'Battery level: $result%'; } on PlatformException catch (e) { return 'Failed to get battery level: ${e.message}'; } } ``` * `invokeMethod` sends a string method name and optional arguments to native code. * The result comes back asynchronously via a `Future`. * Always wrap the call in a `try-catch` block to handle `PlatformException`, which may occur if: * The native method throws an error * The method is not implemented * Data serialization fails **Notes:** * You can pass arguments to `invokeMethod()` as the second parameter (e.g., a `Map`). * The result can be any JSON-compatible Dart type: `int`, `String`, `bool`, `double`, `List`, or `Map`. ### 2. Android Side (Kotlin)[​](/concepts/advanced/method-channels.md#2-android-side-kotlin "Direct link to 2. Android Side (Kotlin)") The Android side handles Dart calls using a `MethodChannel` registered in `MainActivity`. This handler runs on the **main thread** by default, so long-running work should be offloaded to a background thread. **Setting Up the Channel:** ``` import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import android.os.BatteryManager import android.content.Context ``` **Handling the Method Call:** ``` class MainActivity: FlutterActivity() { private val CHANNEL = "com.example/device" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "getBatteryLevel" -> { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } else -> result.notImplemented() } } } private fun getBatteryLevel(): Int { val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) } } ``` **Notes:** * Always return a result using one of the following: * `result.success(data)` — returns data to Dart * `result.error(code, message, details)` — throws `PlatformException` in Dart * `result.notImplemented()` — throws `MissingPluginException` in Dart * Do **not** call `result` multiple times. Flutter expects a one-time, one-result reply per method call. * If your native call involves I/O, network, or anything that blocks, use a background thread: ``` Thread(Runnable { val resultData = longRunningOperation() runOnUiThread { result.success(resultData) } }).start() ``` ### 3. iOS Side (Swift)[​](/concepts/advanced/method-channels.md#3-ios-side-swift "Direct link to 3. iOS Side (Swift)") In iOS, the platform channel is handled via `FlutterMethodChannel` in `AppDelegate.swift`. Similar to Android, the method call handler runs on the **main thread** by default. **Setting Up the Channel:** ``` import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel(name: "com.example/device", binaryMessenger: controller.binaryMessenger) ``` **Handling the Method Call:** ``` batteryChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in if call.method == "getBatteryLevel" { UIDevice.current.isBatteryMonitoringEnabled = true let level = UIDevice.current.batteryLevel if level >= 0 { result(Int(level * 100)) } else { result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available.", details: nil)) } } else { result(FlutterMethodNotImplemented) } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ``` **Notes:** * Always return exactly one response per method call. * Use `FlutterError` to send detailed error info to Dart. * If needed, use `DispatchQueue.global().async` to run long tasks in the background, then return via `DispatchQueue.main.async`. ### Best Practices on MethodChannels[​](/concepts/advanced/method-channels.md#best-practices-on-methodchannels "Direct link to Best Practices on MethodChannels") To implement **MethodChannels** successfully: * **Use consistent channel and method names** between Dart and native code. * **Use standard types** for data exchange (prefer `String`, `int`, `bool`, `List`, `Map`). * **Always handle errors** clearly on both sides. * **Offload long-running native logic** to background threads. * **Keep the native side minimal and testable**, separating SDK logic from channel code where appropriate. By following these steps and patterns, you’ll be able to bridge Flutter with native code cleanly—supporting deep platform integrations while maintaining a smooth UI and maintainable codebase. ## Integrating MethodChannels in FlutterFlow[​](/concepts/advanced/method-channels.md#integrating-methodchannels-in-flutterflow "Direct link to Integrating MethodChannels in FlutterFlow") FlutterFlow is a visual development platform that generates complete Flutter applications. While it supports writing custom Dart code through **Custom Actions** and **Custom Functions**, it does not allow direct editing of platform-native code (Kotlin, Swift) through its web UI. This introduces some important considerations when integrating Flutter’s `MethodChannel` API for platform-specific functionality. **Step 1: Create a Custom Flutter Plugin** **1.1 Initialize the Plugin** Use the Flutter CLI to create a new plugin: ``` flutter create --template=plugin --platforms=android,ios my_custom_plugin ``` This command sets up a plugin project with the necessary structure for both Android and iOS platforms. **1.2 Implement Platform-Specific Code** Within the generated plugin, navigate to the platform-specific directories (`android` and `ios`) to implement the desired functionality. For example, to retrieve the device's battery level: * **Android (Kotlin):** Modify `MyCustomPlugin.kt` to access the battery information using Android's `BatteryManager`. * **iOS (Swift):** Update `MyCustomPlugin.swift` to utilize `UIDevice` for battery level retrieval. **1.3 Publish to GitHub** After implementing and testing your plugin: 1. Initialize a Git repository in your plugin directory. 2. Commit your changes. 3. Push the repository to GitHub. Ensure your `pubspec.yaml` is correctly configured, and consider tagging releases for versioning. **Step 2: Add the Plugin as a Dependency in FlutterFlow** To integrate your custom plugin into a FlutterFlow project: 1. Navigate to **Custom Code > Custom Actions** or **Custom Widgets** in FlutterFlow. 2. Create a new Custom Action or Widget. 3. In the **Settings** panel on the right, scroll to **Dependencies**. 4. Add your plugin using the Git URL: ``` my_custom_plugin: git: url: https://github.com/yourusername/my_custom_plugin.git ``` 5. In the code editor, import your plugin: ``` import 'package:my_custom_plugin/my_custom_plugin.dart';` ``` 6. Implement the desired functionality using the plugin's API. For detailed guidance, refer to FlutterFlow's documentation on [using unpublished or private packages](https://docs.flutterflow.io/concepts/custom-code/#using-unpublished-or-private-packages). **Step 3: Utilize the Plugin via Custom Actions in FlutterFlow** With the plugin integrated, you can now create Custom Actions to leverage its functionality: 1. Define a new Custom Action in FlutterFlow. 2. In the code editor, implement the action using your plugin. For example: ``` Future getBatteryLevel() async { final batteryLevel = await MyCustomPlugin.getBatteryLevel(); return batteryLevel; } ``` 3. Compile the custom code to ensure there are no errors. 4. Use this Custom Action within your FlutterFlow project's action flows, just like any built-in action. This approach allows you to encapsulate complex logic within reusable actions, enhancing modularity and maintainability. ### Managing Private Repositories[​](/concepts/advanced/method-channels.md#managing-private-repositories "Direct link to Managing Private Repositories") If your plugin repository is private, FlutterFlow needs access to it. As per FlutterFlow's documentation, you may need to provide authentication credentials or use SSH keys. Refer to the [FlutterFlow documentation](/concepts/custom-code.md#using-unpublished-or-private-packages) for detailed instructions on integrating private packages. ## Common Pitfalls and Debugging[​](/concepts/advanced/method-channels.md#common-pitfalls-and-debugging "Direct link to Common Pitfalls and Debugging") MethodChannels are powerful but require careful implementation. When the Dart and native sides are not aligned, or error handling is overlooked, it often leads to runtime issues or silent failures. This section outlines the most common problems developers face with MethodChannels, especially in projects generated by tools like FlutterFlow, and provides actionable solutions to help you debug effectively and write resilient platform-channel integrations. ### MissingPluginException[​](/concepts/advanced/method-channels.md#missingpluginexception "Direct link to MissingPluginException") **Symptom:** Flutter throws a `MissingPluginException`, typically saying the plugin or method is not implemented. **What it means:** Flutter tried to invoke a method on the MethodChannel, but the native side did not recognize the channel or method name. **Common causes:** * Dart `MethodChannel` name does not match the native channel name. * Native code handler (`setMethodCallHandler`) was never set up or was incorrectly placed. * Custom native code was overwritten when re-downloading a FlutterFlow project without preserving changes. * The method was invoked before the Flutter engine or the channel was fully initialized. * Hot reload only updates Dart code, but not with native channel implementations. **How to fix:** * Confirm that the channel name is **identical** in Dart and native code (case-sensitive). * On Android, ensure the channel is registered inside `configureFlutterEngine()`. * On iOS, set up the `FlutterMethodChannel` inside `didFinishLaunchingWithOptions()`. * Log the available channels/methods to confirm registration during app startup. * Native channel implementations require a full restart because the platform-specific code must be recompiled and relinked. ### Incorrect Argument or Result Types[​](/concepts/advanced/method-channels.md#incorrect-argument-or-result-types "Direct link to Incorrect Argument or Result Types") **Symptom:** App crashes with type casting errors or returns `null` unexpectedly. **What it means:** The data passed between Dart and native does not match expected formats. **Common causes:** * Dart sends an argument as a Map but native expects a String, or vice versa. * Native code returns a platform object that can't be serialized by Flutter. * The return value is not compatible with `StandardMessageCodec`. **How to fix:** * Only use standard types: `int`, `double`, `String`, `bool`, `List`, or `Map` with JSON-safe contents. * On Dart side, specify the expected return type with generics: `invokeMethod(...)`. * On native side, validate input types before using them. Consider using try/catch or safe casting. * Avoid sending complex objects like native SDK responses directly—convert to a simple dictionary or string. ### No Response or App Hangs[​](/concepts/advanced/method-channels.md#no-response-or-app-hangs "Direct link to No Response or App Hangs") **Symptom:** The Dart call to `invokeMethod()` never returns, or the UI freezes. **What it means:** The native side didn’t complete the method call correctly, or a long-running task is blocking the UI thread. **Common causes:** * Native method handler fails to call `result.success`, `result.error`, or `result.notImplemented`. * The method call handler throws an exception that prevents the response from being sent. * Heavy logic (e.g., file I/O, network calls) is blocking the main thread. **How to fix:** * Always call one—and only one—of the result callbacks. * Wrap native code in try/catch blocks to catch and report any exceptions. * Offload slow operations to a background thread or coroutine (Kotlin) or dispatch queue (Swift). * Use Dart timeouts or loading indicators to keep the UI responsive while waiting. ### Calling `result` Multiple Times[​](/concepts/advanced/method-channels.md#calling-result-multiple-times "Direct link to calling-result-multiple-times") **Symptom:** The app crashes with a runtime error like "Reply already submitted" or shows inconsistent results. **What it means:** The native code responded more than once for the same method call. **Common causes:** * Both success and error branches are executed due to logic errors. * Async operations or callbacks race to return multiple responses. * A timeout, retry, or exception causes unintended second calls. **How to fix:** * Track whether a response has been sent using a flag (e.g., `var responded = false`). * Use return statements or guards to prevent multiple result calls. * Structure async callbacks carefully to ensure only one callback path runs. ### Debugging Tips by Platform[​](/concepts/advanced/method-channels.md#debugging-tips-by-platform "Direct link to Debugging Tips by Platform") **Flutter/Dart:** * Use `print()` or `debugPrint()` to log method calls and results. * Always wrap `invokeMethod` in `try/catch` and log exceptions. * Add logs before and after `invokeMethod()` to verify flow. * Use Flutter DevTools to inspect console logs and application state. **Android (Kotlin/Java):** * Use `Log.d("MethodChannel", "Received: ${call.method}")` inside the handler. * Use `adb logcat | grep flutter` to filter platform logs. * Ensure `configureFlutterEngine()` is actually called—older project setups may require manual configuration. * Use breakpoints in Android Studio for step-by-step inspection. **iOS (Swift/Objective-C):** * Use `print()` or `NSLog()` to trace handler execution. * Watch the Xcode console for startup logs or channel registration issues. * Ensure you're calling `result(...)` correctly and only once. * Check if the `AppDelegate` is properly casting `window?.rootViewController` to `FlutterViewController`. By understanding and anticipating these pitfalls, developers can avoid common errors that derail Flutter-to-native communication. MethodChannels are extremely reliable when implemented correctly, and with structured debugging, most issues can be diagnosed and resolved quickly—even in FlutterFlow-generated apps where visibility into the build system may be limited. ## Performance and Architecture Best Practices[​](/concepts/advanced/method-channels.md#performance-and-architecture-best-practices "Direct link to Performance and Architecture Best Practices") Integrating native functionality through MethodChannels can bring significant value to your app - but only if it’s done with performance and maintainability in mind. Below are the five most important best practices engineers should apply in real-world production apps, along with deeper insights into why each one matters. Important Context for FlutterFlow Users FlutterFlow generates clean Dart code and supports Custom Actions for inserting Dart logic, but it does not currently support inline native (Kotlin/Swift) editing. ### Don’t Block the Main Thread[​](/concepts/advanced/method-channels.md#dont-block-the-main-thread "Direct link to Don’t Block the Main Thread") * By default, all MethodChannel calls are handled on the **main UI thread**, which is also responsible for rendering the app. * Native operations like database access, file I/O, Bluetooth scanning, or network requests **must** be moved off the main thread. * Use background threads (e.g., `Executors` or `coroutines` on Android, `DispatchQueue.global()` on iOS) to perform long-running tasks. * Return results on the main thread using `runOnUiThread` (Android) or `DispatchQueue.main.async` (iOS). Blocking the UI thread for even a few milliseconds can cause dropped frames, janky animations, and a visibly unresponsive app, especially on mid-range devices. tip While UI interactions and workflows look smooth inside FlutterFlow, once you export and test the app on a real device, slow operations in Kotlin or Swift can still freeze the app. Always delegate those tasks to background threads before calling back into Dart. ### Keep MethodChannel Code Minimal[​](/concepts/advanced/method-channels.md#keep-methodchannel-code-minimal "Direct link to Keep MethodChannel Code Minimal") * Your MethodChannel handler should act like a **controller**, not a service. It should delegate execution to well-structured, modular native components. * This keeps the interface between Dart and native thin and easy to maintain. * For example, `getBatteryLevel` in Kotlin should just delegate to `BatteryService().getLevel()`. * This separation helps native teams evolve platform code independently of Flutter UI updates. Clean separation of concerns leads to better test coverage, easier onboarding, and avoids hard-to-debug cross-layer bugs. ### Use Only JSON-Compatible Data[​](/concepts/advanced/method-channels.md#use-only-json-compatible-data "Direct link to Use Only JSON-Compatible Data") * The Flutter engine uses `StandardMessageCodec` for MethodChannel communication. * It supports only a limited set of Dart-native types: `int`, `double`, `bool`, `String`, `List`, `Map`, and `null`. * Any native types (e.g., `Bitmap`, `Bundle`, `NSData`, `UIColor`) must be converted to a JSON-friendly structure first. * If data is complex (e.g., a barcode result or device info), serialize it to a flat Map or a JSON string before sending it across. Type mismatches across the bridge don’t fail at compile time—they crash at runtime. Keeping your types simple prevents hard-to-diagnose issues. tip When using Dart Custom Actions that invoke MethodChannels, ensure the return values can be used in FlutterFlow bindings. Only supported types (like `String` or `int`) can be stored in App State or used in conditions or widgets. ### Validate and Sanitize Dart Inputs[​](/concepts/advanced/method-channels.md#validate-and-sanitize-dart-inputs "Direct link to Validate and Sanitize Dart Inputs") * Treat incoming Dart method calls like external API requests. Assume they can be malformed. * Use pattern matching (switch/case or `when`) to route and verify each method call. * Validate presence and type of arguments before using them. For example: ``` val timeout = call.argument("timeout") ?: return result.error("INVALID", "Missing timeout", null) ``` * Defensive coding helps avoid unexpected behavior, native crashes, or incorrect hardware usage. Dart developers might call your method incorrectly. Native code must fail safely and visibly. tip Custom Actions in FlutterFlow can include parameters from the UI, but if the parameter isn’t set or passed correctly in a workflow, the Dart code will still execute. Validate these inputs natively before use. ### Log Clearly on Both Sides[​](/concepts/advanced/method-channels.md#log-clearly-on-both-sides "Direct link to Log Clearly on Both Sides") * Add logging on both Dart and native layers for every MethodChannel call: * What method was called? * What were the arguments? * What was returned, and how long did it take? * Use structured logs (`Log.d("MethodChannel", "method=... args=... result=...")` on Android, `NSLog` or `print()` on iOS). * Align log timestamps across layers to help trace issues during debugging sessions. When something goes wrong in production, good logs make the difference between a 10-minute fix and a multi-day investigation. tip Use `debugPrint()` inside Dart Custom Actions to log output alongside platform logs. In test builds, these logs help verify whether native results are arriving as expected. ## Summary & Guidance[​](/concepts/advanced/method-channels.md#summary--guidance "Direct link to Summary & Guidance") MethodChannels are a foundational tool for extending the power of your app beyond what plugins alone can provide. They allow direct access to platform-native APIs and SDKs, enabling teams to solve tough integration challenges and deliver production-grade features with full control. But like any system boundary, MethodChannels require disciplined design. Misuse can lead to fragile bridges, performance bottlenecks, and increased maintenance overhead. When implemented thoughtfully, MethodChannels provide: * A clean interface between Dart and native layers * Strategic reuse of platform-optimized SDKs and APIs * A clear path to ship advanced features without waiting on plugin ecosystems * A sustainable integration model that scales with your team and product Flutter will continue to evolve with innovative solutions for platform interoperability in the future through two key tools: FFIgen and JNIgen. FFIgen automates the creation of Objective-C and Swift API bindings, while JNIgen handles Java and Kotlin API connections, making native code integration more streamlined and maintainable across platforms. --- # AI Agent AI Agent lets you work with AI coding agents directly from the FlutterFlow desktop app. Instead of manually opening a terminal, navigating to your project, and configuring command-line tools yourself, FlutterFlow checks the required setup and helps prepare the tools needed to work with your project. After you choose an agent, FlutterFlow launches it with the correct project context. Depending on your selected agent, FlutterFlow may also install or verify the required CLI, initialize or reuse a local FlutterFlow AI workspace, register the FlutterFlow MCP server, and guide the agent through authentication. Once connected, you can describe the change you want, review the agent's plan, and apply updates to your project. The agent works with your FlutterFlow project through the [FlutterFlow CLI](/flutterflow-cli.md) and the same agent workflow described in [Build with AI Agents](/flutterflow-cli/build.md). Desktop app only AI Agent is available only in the FlutterFlow desktop app. It is not available in browser-based FlutterFlow. To use AI Agent, download the [**FlutterFlow desktop app**](https://flutterflow.io/desktop). Different from in-app AI Agents This page is about using external AI coding agents to build and edit your FlutterFlow project. If you want to create AI-powered chat, text-to-speech, speech-to-text, image generation, or video generation experiences inside your app, see [**AI Agents**](/integrations/ai-agents.md). AI Agent is useful for project editing tasks such as: * Creating or updating pages and components * Adjusting widget properties, layout, styling, and responsiveness * Wiring actions, action blocks, app state, and app events * Updating theme values, design tokens, and navigation * Reviewing project structure and finding issues * Applying focused changes to a specific widget with Copy AI Selector For more details about what agents can and cannot edit, see [Agent Edit Scope](/flutterflow-cli/build.md#agent-edit-scope). ## Prerequisites[​](/concepts/ai-agent.md#prerequisites "Direct link to Prerequisites") Before using AI Agent, make sure you have: * The [FlutterFlow desktop app](https://flutterflow.io/desktop) installed and signed in. * An account with the provider of your selected agent, such as a ChatGPT account for **Codex** or an Anthropic account for **Claude**. ## Set Up AI Agent[​](/concepts/ai-agent.md#set-up-ai-agent "Direct link to Set Up AI Agent") When you open AI Agent for the first time, FlutterFlow checks whether the required tools are ready for the selected agent. info You do not need to download a separate AI agent desktop app before getting started. The setup panel checks the required CLI tools for the selected agent and lets you install missing tools from the AI Agent setup panel. To set up AI Agent: 1. Open your project in the **FlutterFlow desktop app**. 2. Select the **brain icon** in the toolbar. 3. In the **Set up your AI agent** panel, choose **Claude** or **Codex**. 4. Review the setup checklist. FlutterFlow checks the selected agent's CLI, Flutter SDK, FlutterFlow CLI, local workspace, and sign-in status. 5. If any required CLI tools are missing, click **Install required tools**. 6. After installing tools or signing in, click **Re-check** to refresh the setup status. 7. When prompted, sign in with the account for the selected agent, such as your Anthropic account for Claude or your ChatGPT account for Codex. The selected agent runs on your own account and follows that account's usage and limits. The coding CLI runs locally on your Mac and sends prompts, relevant project context, and model responses through your selected agent provider. Data handling and retention follow your provider account's policies and data controls. FlutterFlow does not receive or store your conversations. [Set up AI Agent](https://demo.arcade.software/iB9pUVMMLggNDnJmL0Hl?embed\&show_copy_link=true) ## Use AI Agent[​](/concepts/ai-agent.md#use-ai-agent "Direct link to Use AI Agent") After setup is complete: 1. Describe what you want the agent to do, such as "Create a profile settings page," "Fix the login button action," or "Update this card to match the new design." 2. Review the agent's proposed changes before applying them. 3. Verify the result visually. tip For targeted widget updates, right-click the widget in the builder and select [**Copy AI Selector**](/flutterflow-cli/build.md#copy-ai-selector). Paste the selector into your prompt so the agent can locate the exact widget you want to update. [Use AI Agent](https://demo.arcade.software/jJPRPlsUJoFKIj0nuQT4?embed\&show_copy_link=true) ## Settings[​](/concepts/ai-agent.md#settings "Direct link to Settings") Select the **Settings** icon in the AI Agent panel to manage how FlutterFlow connects to local agents, stores workspaces, and opens sessions. * **Workspace location:** Shows where FlutterFlow creates local MCP workspaces on your Mac and displays the current workspace for the open project. * **Claude:** Lets you sign in to Claude and review the permissions that control which tools Claude can run without asking each time. You can open the Claude permissions file at `~/.claude/settings.json`. * **Codex:** Shows your signed-in ChatGPT account, plan, and current usage limits as reported by the Codex CLI. You can also log out from here. * **Codex permissions:** Lets you review the permissions that control which tools Codex can run without asking each time. You can open the Codex permissions file at `~/.codex/config.toml`. * **Terminal:** Selects which terminal app FlutterFlow uses when you choose **Open in terminal** to resume an agent session. * **Show conversation recap:** Turns the conversation recap on or off. When enabled, FlutterFlow shows a one-line recap above the prompt after a few exchanges in a thread. * **Troubleshooting:** Lets you download diagnostic logs for AI Agent setup and conversations, including tool installs, workspace initialization, CLI launches, and errors. Conversations and personal data are not included in these logs. * **Privacy:** Confirms that the coding CLI runs locally and that conversations are handled according to your selected agent provider's policies and data controls. FlutterFlow does not receive or store your conversations. tip If the setup status does not update after installing tools or signing in, click **Re-check**. If it still does not update, restart the FlutterFlow desktop app and open the AI Agent panel again. ## Best Practices[​](/concepts/ai-agent.md#best-practices "Direct link to Best Practices") * Be specific about the outcome you want, not just the widget you want changed. * Use **Copy AI Selector** when multiple widgets look similar or when the target widget is deeply nested. * Ask the agent to inspect the current project before making broad changes. * Review changes in the visual builder before continuing with additional prompts. * Keep a [Live Session](/flutterflow-cli/build.md#live-sessions) running only while you are actively using it. --- # Alert Dialog The action allows you to alert the user of important situations that require acknowledgment in the form of a pop-up or custom-designed dialog. With this feature, you can choose to display a pre-built pop-up or create a custom design that suits your specific requirements. ### Types of Alert Dialog[​](/concepts/alerts/alert-dialog.md#types-of-alert-dialog "Direct link to Types of Alert Dialog") We allow you to define two types of Alert Dialog Actions: * **Informational Dialog:** To show some information the user should be aware of before interacting with the app. Contains only a single action button. * **Confirm Dialog:** This dialog can contain two action buttons. It can trigger the subsequent action based on whether a user confirms the action. It can also be used before performing any non-revertable user action, for example, before deleting a user account. * **Custom Dialog**: This is a fully customizable dialog that you can create using [components](/resources/ui/components.md). ### Adding Informational Dialog \[Action][​](/concepts/alerts/alert-dialog.md#adding-informational-dialog-action "Direct link to Adding Informational Dialog \[Action]") Follow the steps below to add this type of action to any widget: 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Alert Dialog** (under *Alerts/Notifications*) action. 4. Set the **Alert Dialog Type** to **Informational Dialog**. 5. Provide the **Title** and **Message** for the dialog. Note: You can also set it from a variable; for example, a combined text with a value from a variable. 6. Also, enter a **Dismiss Text** that will be shown on the action button. ### Adding Confirm Dialog \[Action][​](/concepts/alerts/alert-dialog.md#adding-confirm-dialog-action "Direct link to Adding Confirm Dialog \[Action]") Follow the steps below to add this type of action to any widget: 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Click on the **+ Add Action**. 2. On the right side, search and select **Alert Dialog**. 3. Set the **Alert Dialog Type** to **Confirm Dialog**. 4. Provide the **Title** and **Message** for the dialog. Note: You can also set it from a variable; for example, a combined text with a value from a variable. 5. Now, enter a **Dismiss Text** (shown on the action button that will cancel the Action) and a **Confirm Text** (shown on the action button that will trigger the Action that you will define in the next step). 6. Now, click on the **+** button and select **Add Conditional**. 7. On the right side (**Set Condition for Action**), set the **Source** to **Confirm Dialog Response**. 1. Under the **TRUE** section, add an action that will be triggered if a user gives confirmation. 2. Under the **FALSE** section, add an action that will be triggered if a user cancels this dialog. 3. Click **Close**. ### Adding Custom Dialog \[Action][​](/concepts/alerts/alert-dialog.md#adding-custom-dialog-action "Direct link to Adding Custom Dialog \[Action]") Before you add this action, ensure you [create a component](/resources/ui/components/creating-components.md) that you want to display as a custom dialog. Now follow the steps below to add this type of action to any widget: 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Alert Dialog** (under *Alerts/Notifications*) action. 4. Set the **Alert Dialog Type** to **Custom Dialog** and **Select Component**. 5. It is recommended to set the appropriate **Width** and **Height** for the custom dialog. 6. Optionally, you can set the **Background** and **Barrier Color** for this dialog. ![Setting background color and barrier color](/assets/images/custom-dialog-f68560d78150e1f20ecaed3aab6b928f.avif) 7. By default, this type of action blocks the following action (if any) from triggering while this action is in progress, meaning the dialog is present on the screen. However, in some cases, you might want to allow the next action (after this) to execute, for example, making an API call immediately after showing the custom loading dialog. To do so, enable **Non Blocking** option. 8. By default, **Non Dismissble** option closes the dialog when you click outside of it. To disable this behavior, enable this option. 9) By default, the custom dialog appears in the center of the screen. However, you can use the **Dialog Alignment** property to decide where to position the dialog on the screen. ![Align custom dialog](/assets/images/align-custom-dialog-f591108c3294aec8394816920e839921.avif) 10) To position the dialog around the widget that opened it, enable the **Align with the Target Widget**, and then align using the **Target Alignment** property. **Tip**: If dialog goes out of the screen, enable **Avoid Overflow**. --- # Dismiss Custom Dialog With this action, you can easily close the [custom dialog](/concepts/alerts/alert-dialog.md#adding-custom-dialog-action), providing a convenient way for users to dismiss it. This functionality is handy when you want to give users the option to close the dialog from any widget within it, like a close button. ## Adding Dismiss Custom Dialog \[Action][​](/concepts/alerts/dismiss-custom-dialog.md#adding-dismiss-custom-dialog-action "Direct link to Adding Dismiss Custom Dialog \[Action]") Follow the steps below to add this type of action to any widget: 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Dismiss Custom Dialog** (under *Alerts/Notifications*) action. 4. You can set a default value to be sent when the user closes the custom dialog. You can do so by enabling the **Has Value** option. For instance, if the dialog provides a list of colors and the user closes it without selecting any color, you can set a default color value of "Black" to be sent as the default selection. ![Adding Dismiss Custom Dialog action](/assets/images/adding-dismiss-custom-dialog-action-84538fcd13850dd52da4c5c24f4a1aac.png) --- # Haptic Feedback Using this action, you can vibrate the user's device. Typically this is used to draw users' attention to the action they have performed. For example, vibrating the user's device on setting the alarm. ## Types of Haptic Feedback[​](/concepts/alerts/haptic-feedback.md#types-of-haptic-feedback "Direct link to Types of Haptic Feedback") Depending on the action a user has performed (e.g., bookmark an item, on-off flashlight), you can set the different vibration intensity and duration types. Here are the types of haptic feedback: 1. **Light**: This creates a very low-intensity vibration similar to pressing a virtual on-screen key. 2. **Medium**: This creates a medium-intensity vibration similar to pressing a key on a keyboard. 3. **Heavy**: This creates a high-intensity vibration similar to clicking an item. 4. **Selection Click**: This vibrates the device when selection changes through discrete values. Similar to changing hours and minutes on the clock app. 5. **Vibrate**: This creates a vibration for a short duration. warning * The *Light*, *Medium*, *Heavy*, and *Selection Click*, these types of haptic feedback only work on iOS version 10 and above. * The *Selection Click* type only works on Android API levels 23 and above. ## Adding Haptic Feedback \[Action][​](/concepts/alerts/haptic-feedback.md#adding-haptic-feedback-action "Direct link to Adding Haptic Feedback \[Action]") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Haptic Feedback** (under *Alerts/Notifications*) action. 4. Set the **Feedback Type** among the **Light**, **Medium**, **Heavy**, **Selection Click**, and **Vibrate**. --- # Animations Enhancing your app with animations significantly improves the user experience, making it more engaging and intuitive. In FlutterFlow, you have several options to add animations to your app: * [**Widget Animations**](/concepts/animations/widget-animations.md): Add animation effects to an entire widget. * [**Implicit Animations**](/concepts/animations/implicit.md): Animate changes in specific widget properties, such as the height of a Container. * [**Hero Animations**](/concepts/animations/hero-animations.md): Animate a widget that transitions smoothly between screens, also known as shared element transitions. * [**Page Transition Animations**](/concepts/animations/page-transition.md): Specify transitions between pages within your app. * **Import Animations**: Import animations you've created using other tools such [lottiefiles](/concepts/animations/lottie-animation.md) and [Rive](/concepts/animations/rive-animation.md). * [**Shaders**](/concepts/animations/shaders.md): Add GPU-powered visual effects like animated backgrounds, distortions, and interactive touch-based visuals to enhance your UI. To learn more about animations in FlutterFlow, check out this video: [YouTube video player](https://www.youtube.com/embed/-quxi_t0eWU?si=GdZBMFcuEZEyFplB) --- # Hero Animation "Hero" is a widget that gracefully transitions from one screen to another. For instance, on a product listing page, clicking on a product's image triggers a smooth animation where the image flies to a new screen, revealing detailed information about the product. ## Creating Hero Animation[​](/concepts/animations/hero-animations.md#creating-hero-animation "Direct link to Creating Hero Animation") Let's how to create hero animation with an example that looks like the one below: ![hero-animation-image-widget.gif](/assets/images/hero-animation-image-widget-5338d8ec61a3451fc894306e233fe913.gif) info Building Hero Animation requires you to have at least two pages that share the same image. The steps to build such an example are as follows: 1. On the first page, select the image, head over to the properties panel, enable **Use Hero Animation**, and **Add Hero Tag**. 2. On the second page, select the image, head over to the properties panel, enable **Use Hero Animation**, and select the **Hero Tag** that you created on the first page component. 3. Add [navigation action](/concepts/navigation/page-navigation.md#navigate-to-action) from page 1 to page 2. ## Hero Animation on Component[​](/concepts/animations/hero-animations.md#hero-animation-on-component "Direct link to Hero Animation on Component") You can also add hero animation on a custom component. Let's see how to build an example that looks like the one below: Before you begin, * Make sure you have a component added to both the first and second pages. * For a smoother and more appealing hero animation effect, ensure that the components on both pages have a somewhat similar appearance. This enhances the overall visual impact of the animation. The steps to add hero animation on a component are as follows: 1. On the first page, select a component, head over to the properties panel, enable **Use Hero Animation**, and **Add Hero Tag**. 2. On the second page, select a component, head over to the properties panel, enable **Use Hero Animation**, and select the **Hero Tag** that you created on the first page component. 3. Add [navigation action](/concepts/navigation/page-navigation.md#navigate-to-action) from page 1 to page 2. ## FAQs[​](/concepts/animations/hero-animations.md#faqs "Direct link to FAQs") Why is the Hero animation not working when navigating forward? Works only backward This is because the image on the second page does not exist on the very first frame. Hero animation will only work when the image is loaded from an asset or from the network (*if the path is pre-specified*). If you're pulling the image from a Firestore document, it might not be ready in time for the animation to take place. To fix this issue, you can avoid loading an image directly from Firestore. Instead, you can pass the image URL (which would have already been retrieved from the Firestore) from the previous page to the second page. And then use that URL to load the image. See how to [pass data](/concepts/navigation/passing-data.md) from one page to another. --- # Implicit Animations In Implicit Animation, the widget automatically animates to a new property's value when they are updated. For example, the container widget animates whenever you change its size and colors. info Implicit Animation is recommended only when you want to run the animation once (after the properties are changed). Here are some examples of how it looks when you update the widget properties with and without Implicit Animation. | | Without Implicit Animation | With Implicit Animation | | ------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | | **Container** | ![Without Implicit Animation](/assets/images/without-implicit-animation-e6592770149d9db9e86d1e9b0b0d4d93.gif) | ![With Implicit Animation](/assets/images/with-implicit-animation-124cc1e1c70135504c7be7675ad78ef9.gif) | | **Text** | ![Without Implicit Animation](/assets/images/without-implicit-animation-text-b58f28715f01ad899d063ee8d1cbb9c9.gif) | ![Wit Implicit Animation](/assets/images/with-implicit-animation-text-6b252df96edd2f6806e62c49904fea2d.gif) | Here's an example of how you add the Implicit Animation on Container widget: --- # Lottie Animation The LottieAnimation widget allows you to display [Lottie files](https://lottiefiles.com/featured) from uploaded assets or the URL link. Lottie files are high quality (they do not pixelate), smaller than GIF, and easy to add to any platform. For example, you could use the LottieAnimation widget to show a nicely animated loading indicator to provide a great user experience to the users. ## Adding LottieAnimation[​](/concepts/animations/lottie-animation.md#adding-lottieanimation "Direct link to Adding LottieAnimation") Showing Lottie files in a LottieAnimation widget comprises the following steps: 1. [Getting Lottie files](/concepts/animations/lottie-animation.md#1-getting-lottie-files) 2. [Adding LottieAnimation widget](/concepts/animations/lottie-animation.md#2-adding-lottieanimation-widget) 3. [Changing animation source](/concepts/animations/lottie-animation.md#3-changing-animation-source) ### 1. Getting Lottie files[​](/concepts/animations/lottie-animation.md#1-getting-lottie-files "Direct link to 1. Getting Lottie files") The LottieAnimation requires the Lottie file to be added to display the animation on the screen. You can get the Lottie files from its [official collection](https://lottiefiles.com/featured) in two ways. #### 1.1 Downloading the Lottie JSON file[​](/concepts/animations/lottie-animation.md#11-downloading-the-lottie-json-file "Direct link to 1.1 Downloading the Lottie JSON file") The Lottie JSON file is required when you want to play the animation from the file uploaded to your project. To download the Lottie JSON file: 1. Open and search for the required animation. 2. Select the animation you would like to add. This will open a new popup. 3. Click on the **Download** button and select **Lottie JSON**. #### 1.2 Copying Lottie animation URL[​](/concepts/animations/lottie-animation.md#12-copying-lottie-animation-url "Direct link to 1.2 Copying Lottie animation URL") The Lottie animation URL is required when you want to play the animation from the file hosted at . To copy the animation URL: 1. Open and search for the required animation. 2. Select the animation you would like to add. This will open a new popup. 3. Find the **Lottie Animation URL** (bottom right of the playing animation) and copy it. info The Lottie animation URL is only visible when you are logged in. ### 2. Adding LottieAnimation widget[​](/concepts/animations/lottie-animation.md#2-adding-lottieanimation-widget "Direct link to 2. Adding LottieAnimation widget") To add LottieAnimation widget to your project: 1. Drag the **LottieAnimation** widget from the **Base Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. Move to the properties panel (on the right side of your screen) and scroll down to the **Lottie Animation** section. 3. Find the **Path** property and enter the **URL** (see how to get it [1.2](/concepts/animations/lottie-animation.md#12-copying-lottie-animation-url)) for the new Lottie file. 4. By default, the animation will play as soon as the page loads. To disable this and play animation on a button click or any other event, uncheck the **Auto Animate** checkbox. ### 3. Changing animation source[​](/concepts/animations/lottie-animation.md#3-changing-animation-source "Direct link to 3. Changing animation source") By default, the widget's animation source is set to network. However, you can change this to use a Lottie file uploaded directly to your app. Here's how you can change the animation source: 1. Select the **LottieAnimation** widget from the widget tree or the canvas area. 2. Move to the property panel (on the right side of your screen) and scroll down to the **Lottie Animation** section. 3. Find the **Animation Source** dropdown and select . 4. Now, find the **Asset Animation** property, click the **Upload LottieAnimation** button, select the Lottie file and upload it. ## Customizing[​](/concepts/animations/lottie-animation.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Changing animation type[​](/concepts/animations/lottie-animation.md#changing-animation-type "Direct link to Changing animation type") You can control how the animation is played, whether it should play only once, in a loop, or in a boomerang fashion (play back and forth). To control the animation type: 1. Select the **LottieAnimation** widget from the widget tree or the canvas area. 2. Move to the property panel (on the right side of your screen) and scroll down to the **Lottie Animation** section. 3. Find the **Animation Type** dropdown and select among the **Once**, **Loop**, and **Boomerang**. * Open * Loop * Bomerang ### Change frame rate[​](/concepts/animations/lottie-animation.md#change-frame-rate "Direct link to Change frame rate") By default, animations are played at the frame rate specified when they are exported from After Effects, usually at 10 or 30 FPS. Modern phones, however, can support higher refresh rates, such as 60 or 120 FPS. If you're not satisfied with how the animation looks at these default settings, you can adjust its frame rate to a smoother 60 FPS for better quality. To do so, move to the **properties panel** > **Lottie Animation** > enter the value in the **Frame Rate** field. ### Changing the box fit[​](/concepts/animations/lottie-animation.md#changing-the-box-fit "Direct link to Changing the box fit") Changing the Box Fit value allows you to control how the Lottie file animation should display inside the LottieAnimation widget. Various options under the Box Fit property help you scale (grow or shrink in size) the Lottie file animation inside the LottieAnimation widget. To change the Box Fit value: 1. Select the **LottieAnimation** widget from the widget tree or the canvas area. 2. Move to the property panel (on the right side of your screen) and scroll down to the **Lottie Animation** section. 3. Find the **Box Fit** dropdown, try changing the value among the **Fill**, **Contain**, **Cover**, **Fit Width**, **Fit Height**, **None**, and **Scale Down**. ## Start/pause animation on button press[​](/concepts/animations/lottie-animation.md#startpause-animation-on-button-press "Direct link to Start/pause animation on button press") You probably want to start or pause the animation when something happens in your app. For example, after saving the form, while data is loading, searching, etc. You can do this by triggering the Lottie Animation action. ### Adding Lottie Animation \[Action][​](/concepts/animations/lottie-animation.md#adding-lottie-animation-action "Direct link to Adding Lottie Animation \[Action]") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. On the right side, search and select **Lottie Animation**. 5. **Choose Lottie Animation** from the dropdown. 6. Enable **Allow Play/Pause** if you want to start and pause the animation while the animation is running. **Note**: You can only access this setting if the **Auto Animate** property of the LottieAnimation widget is unchecked. **Note** that this option is only available if you have set the [animation type](/concepts/animations/lottie-animation.md#changing-animation-type) to either Loop or Boomerang. --- # Page Transition Animations The animation that plays while transitioning from one page of the app to another is known as a page transition. In FlutterFlow, you can customize this animation to enhance the user experience. You can choose from any of the following transition animations: info Here, the transitions are recorded with the duration set to 1000ms to make the animation clearly visible. But inside the app, it's recommended to keep the duration between 200-400ms. | Transition Type | Description | Example | | --------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Instant | Transition with no animation, switching pages immediately. | ![Instant](/assets/images/instant-page-transitions-d5095a438999fcd33a1b56cac8ade15f.gif) | | Fade In | Gradually fades the new page into view. | ![Fade In](/assets/images/fade-page-transitions-5be1eead82c6344067a70a9f457eb6fb.gif) | | Slide Up | Slides the new page up from the bottom. | ![Slide Up](/assets/images/slide-up-page-transition-786f1cc3a74cbcc6d84f262442985430.gif) | | Slide Down | Slides the new page down from the top. | ![Slide Down](/assets/images/slide-down-page-transition-3d3e86f4d15106129d0484cb9c7f8214.gif) | | Slide Left | Slides the new page in from the right. | ![Slide Left](/assets/images/slide-left-page-transition-94ae7fb43d636201d54b16a38d6638cf.gif) | | Slide Right | Slides the new page in from the left. | ![Slide Right](/assets/images/slide-right-page-transition-aee5a5a584ef005c7c434cefd4a4cd21.gif) | | Scale | Scales the new page in from a smaller size to full screen. | ![Scale](/assets/images/scale-page-transitions-e146d5c73ecde74e57c87c2cf7ecabb7.gif) | ## Animate single navigate transition[​](/concepts/animations/page-transition.md#animate-single-navigate-transition "Direct link to Animate single navigate transition") To set a transition animation for a single navigate action, first, ensure that you have added a [**Navigate To**](/concepts/navigation/page-navigation.md#navigate-to-action) action and then select an animation from the **Transition Type** dropdown. By default, the animations use 300 milliseconds as the duration for which it plays but you can change it by specifying a value inside the **Duration** (ms) field. ![single-navigate-transition-animation.avif](/assets/images/single-navigate-transition-animation-5d5b9d9bda8bb8177893dc3f32794c03.avif) ## Change global navigate transition[​](/concepts/animations/page-transition.md#change-global-navigate-transition "Direct link to Change global navigate transition") To change the default transition animation of your entire app, follow the steps below: --- # Rive Animation [Rive](https://rive.app/) is a real-time interactive design and animation tool. Using the **RiveAnimation** widget you can easily import your Rive assets to FlutterFlow and use them inside your app. ## Designing Animation[​](/concepts/animations/rive-animation.md#designing-animation "Direct link to Designing Animation") You can create an animation from scratch by using [Rive Editor](https://editor.rive.app/). 1. Click **+ New File**. 2. Specify the **Artboard dimensions** (Width and Height). 3. Click **Create**. Use the Rive [design tools](https://help.rive.app/editor/fundamentals/shapes-and-paths) or import image files to start designing your animation. Once your design is ready you can use the [Timeline](https://help.rive.app/editor/animate-mode/timeline) and use keying to easily animate your design. info You should have at least one [**Artboard**](https://help.rive.app/editor/fundamentals/artboards) inside your Rive file but you can add an infinite amount of Artboards. After you have completed designing your animation, you can either download it as an asset (having `.riv` extension) or you can share it with others by publishing it to the Rive community. To download the Rive file, click the **Export icon** (top-left corner of the Rive toolbar), and select **Download -> For newest runtime**. To publish the file to the community, click the **Export icon** (top-left corner of the Rive toolbar), and select **Publish to Community**. Give a **title** and **description** to your animation and click **Publish to Community**. warning For using a Rive animation file inside FlutterFlow, you should either download or publish the file to the community. Instead of creating an animation from scratch, you can also use any Rive asset shared in the [Community](https://rive.app/community/). ## Adding RiveAnimation widget[​](/concepts/animations/rive-animation.md#adding-riveanimation-widget "Direct link to Adding RiveAnimation widget") Follow the steps below to use a Rive animation: 1. Drag and drop the **RiveAnimation** widget onto the canvas. 2. Select the **Animation Source** as either ***Network*** or ***Asset***. 3. If you have selected ***Network***, enter the **Path** (download URL) \*\*\*\*of the animation. Get the path by navigating to the Rive animation published in the community, right-click on the **Download** button and copy the link address. 4. If you have selected ***Asset***, 5. Choose an **Artboard** from the dropdown list. 6. Select the **Animations** that you want to use (these are imported from the Rive asset). After selecting one or more animation(s), you can use the **Preview Animations** button to play it. 7. The **Animation Type** is selected as ***Once*** by default. If the selected animations contain a loop or boomerang, you will have an option to select ***Continuous***. On choosing this option, if the animation contains a loop it will play continuously. 8. By default, the **Auto Animate** checkbox remains checked, which means that the animation will play as soon as the page loads. But if you want to use an Action to trigger the animation, uncheck this. 9. Specify the **Width** and **Height** of the RiveAnimation widget, and select a **Box Fit** type. 10. (Optional) If you plan to use an Action to trigger the animation, you can give an appropriate **Name** to this *RiveAnimation* widget for it to be easily identifiable. ## Control animation using action[​](/concepts/animations/rive-animation.md#control-animation-using-action "Direct link to Control animation using action") To trigger a RiveAnimation to start playing using an Action, you can use the **Rive** **Animation Action**. ### Adding Rive Animation \[Action][​](/concepts/animations/rive-animation.md#adding-rive-animation-action "Direct link to Adding Rive Animation \[Action]") Follow the steps below to define an action to start the animation: 1. Select the **widget** (eg., `Button`) on which you want to define the action. 2. Select **Actions** from the Properties Panel. 3. Click **+ Add Action** button. 4. Choose a gesture from the dropdown among **On Tap, On Double Tap,** or **On Long Press**. 5. Select the **Action Type** as ***Animation**.* 6. Set **Choose Animation Type** to ***Rive Animation***. 7. Under **Choose Rive Animation**, select the `RiveAnimation` widget (If you have given your `RiveAnimation` widget a name, that will be displayed here). info You should have the **Auto Animate** unchecked inside the properties of `RiveAnimation` widget to take advantage of this action. --- # Shaders Shaders let you add rich visual effects to your app, such as animated gradients, ripple distortions, dissolve transitions, and interactive touch effects. Instead of using static images or simple color backgrounds, shaders generate visuals in real time using the device’s graphics processor (GPU). This makes it possible to create smooth animations and procedural textures that feel dynamic and alive. ## Shader Widgets[​](/concepts/animations/shaders.md#shader-widgets "Direct link to Shader Widgets") FlutterFlow provides two shader widgets, each designed for a different purpose. Choose the one that best matches how you want to apply the visual effect in your UI. ### ShaderFill[​](/concepts/animations/shaders.md#shaderfill "Direct link to ShaderFill") The **ShaderFill** widget creates a standalone shader effect that fills a rectangular area. It does not contain any child widgets and works as its own visual element in the UI. This makes it ideal for decorative effects such as animated gradients, procedural textures, or dynamic backgrounds. You can control its size directly using the width and height properties. For example, you can use the **ShaderFill** widget to create a visually engaging animated gradient background for an onboarding or welcome screen. ### ShaderWrapper[​](/concepts/animations/shaders.md#shaderwrapper "Direct link to ShaderWrapper") The **ShaderWrapper** widget applies a shader effect on top of an existing widget. Instead of rendering a standalone visual, it wraps a child widget and modifies how it appears on screen. This is useful when you want to add effects like ripples, burn transitions, or dissolve animations to elements such as images, containers, or other UI components. info The **ShaderWrapper** widget automatically takes the size of the child widget it contains. For example, instead of abruptly removing a UI element, wrap it with a **Shader Wrapper** to apply a visual effect that gradually fades or distorts it, helping users understand that it’s being removed. note Internally, it uses the [**material\_palette**](https://github.com/FlutterFlow/material_palette) package, developed by the FlutterFlow team, to power the shader-based visual effects. ## Shader Mode[​](/concepts/animations/shaders.md#shader-mode "Direct link to Shader Mode") Every shader widget includes a **Shader Mode** setting that lets you choose how the shader is defined and applied. You can either use ready-made effects or bring your own custom shader. * **Preset:** Select from a library of built-in shader effects. Each preset includes adjustable parameters such as colors, speed, intensity, and more, allowing you to easily customize the look and behavior directly from the properties panel. * **Custom:** Upload your own `.frag` (fragment shader) file to create fully custom effects. You can define and control inputs using uniform values, which are exposed as sliders in FlutterFlow. Custom shaders appear as a checkerboard placeholder in the builder, but render with full visuals in Test or Run mode. ## Preset[​](/concepts/animations/shaders.md#preset "Direct link to Preset") Presets are ready-to-use shader effects that you can quickly apply and customize without writing any code. tip You can explore and try out all available [**presets here**](https://flutterflow.github.io/material_palette/). ### ShaderFill Preset[​](/concepts/animations/shaders.md#shaderfill-preset "Direct link to ShaderFill Preset") The following presets are available on the ShaderFill Widget: #### Gradient Presets[​](/concepts/animations/shaders.md#gradient-presets "Direct link to Gradient Presets") Gradient presets combine color transitions with procedural noise to create rich, animated visuals. Each gradient type is available in both **linear** and **radial** variants, and all share a common set of customizable property groups for fine-tuning the look and motion via the properties panel. | Gradient Type | Description | Example | | ----------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------- | | **Gritty Gradient** | A rough, grainy gradient with a textured, stippled feel | ![gritty-gradient](/assets/images/gritty-gradient-14c58eb938728597cc1b26d3723c8b02.png) | | **Perlin Gradient** | Smooth, natural-looking noise blended into a gradient | ![sf1](/assets/images/sf1-9fb7639b827d7a94f77a25d0ccf220c9.gif) | | **Simplex Gradient** | Similar to Perlin, but sharper and more structured | ![sf2](/assets/images/sf2-966a7fc4582c95e032b5070be468e9f3.gif) | | **FBM Gradient** | Layered noise that creates soft, cloud-like detail | ![sf3](/assets/images/sf3-bef67b165968a47d13a52289e95a04f8.gif) | | **Turbulence Gradient** | A more chaotic, high-energy version of FBM | ![sf4](/assets/images/sf4-d441c1c9bcf9d12e5f9a80da37383eff.gif) | | **Voronoi Gradient** | Distinct cell-like patterns based on geometric regions | ![sf5](/assets/images/sf5-043116bd83cf98c7a7391b138b92c429.gif) | | **Voronoise Gradient** | A hybrid of cell structures and smooth noise | ![sf6-6](/assets/images/sf6-6-8f9a2f584995cc69b038e31f2a336e76.png) | #### Marble Smear Preset[​](/concepts/animations/shaders.md#marble-smear-preset "Direct link to Marble Smear Preset") A procedural marble texture that reacts to user input. When **Interactive** is enabled, users can drag or touch the surface to smear and distort the marble pattern in real time, creating a fluid, organic visual effect. This is ideal for playful backgrounds, creative demos, or experiences where you want users to directly interact with the visuals. ![marble-smear](/assets/images/marble-smear-58e759fbd05ec8a25a0f62a145c1e2ed.png) ### ShaderWrapper Preset[​](/concepts/animations/shaders.md#shaderwrapper-preset "Direct link to ShaderWrapper Preset") The following presets are available on the ShaderWrapper Widget: #### Ripple / Clickable Ripple[​](/concepts/animations/shaders.md#ripple--clickable-ripple "Direct link to Ripple / Clickable Ripple") Creates a wave-like distortion on the child widget, similar to water ripples. The standard ripple animates continuously, while the clickable version triggers ripples from the user’s tap location, adding responsive visual feedback to interactions. ![sw1](/assets/images/sw1-86591ece0c3e2d5d8c3c12574f4b6f88.gif) #### Burn / Radial Burn / Tappable Burn[​](/concepts/animations/shaders.md#burn--radial-burn--tappable-burn "Direct link to Burn / Radial Burn / Tappable Burn") A dramatic dissolve effect that makes the widget appear to burn away. It can progress in a direction, radiate from a center point, or originate from user taps, with glowing edges that resemble fire. ![sw11](/assets/images/sw11-4e9f73a298631ce32f6add6b5ebcc6bf.gif) #### Smoke / Radial Smoke / Tappable Smoke[​](/concepts/animations/shaders.md#smoke--radial-smoke--tappable-smoke "Direct link to Smoke / Radial Smoke / Tappable Smoke") A softer version of the burn effect, where the widget fades away like drifting smoke. It supports directional, radial, and tap-based variations for smooth and subtle transitions. ![sw3](/assets/images/sw3-a45497d51d0969943029318403127c32.gif) #### Pixel Dissolve / Radial Pixel Dissolve / Tappable Pixel Dissolve[​](/concepts/animations/shaders.md#pixel-dissolve--radial-pixel-dissolve--tappable-pixel-dissolve "Direct link to Pixel Dissolve / Radial Pixel Dissolve / Tappable Pixel Dissolve") Breaks the widget into pixel blocks that scatter and disappear. This effect works for directional, radial, or tap-based dissolves, making it ideal for stylized removal or transition animations. ![sw4](/assets/images/sw4-04badc7eec01957b703015c3d13b5ff8.gif) #### Tappable Slurp[​](/concepts/animations/shaders.md#tappable-slurp "Direct link to Tappable Slurp") A playful distortion effect that pulls the widget toward tap points, like a whirlpool. Each interaction creates a dynamic suction effect, adding a fun and interactive feel to the UI. ## Implicit Animated[​](/concepts/animations/shaders.md#implicit-animated "Direct link to Implicit Animated") When [**Implicit Animated**](/concepts/animations/implicit.md) is enabled, changes to shader parameters (such as colors or slider values) animate smoothly instead of updating instantly. This is especially helpful when parameters are driven by app state, allowing for seamless transitions like gradually shifting gradient colors or intensities. ## Time Animation Behavior[​](/concepts/animations/shaders.md#time-animation-behavior "Direct link to Time Animation Behavior") Time Animation Behavior controls how a shader animates over time. It defines whether the animation runs automatically, is controlled manually, or follows a custom timeline. ### Continuous (default)[​](/concepts/animations/shaders.md#continuous-default "Direct link to Continuous (default)") The shader animates automatically in a smooth, endless loop with no setup required. This is ideal for ambient effects like animated backgrounds, gradients, or subtle motion that should always be running. tip You can have the **Time Animation Behavior** set to **Continuous** while the widget is [**Implicit Animated**](/concepts/animations/shaders.md#implicit-animated). This allows the animation to run continuously while still enabling you to control specific parameters when needed. ### Implicit[​](/concepts/animations/shaders.md#implicit "Direct link to Implicit") You control the shader’s animation manually using a **Time** slider \[0–10]. This is useful when you want to connect the animation to app state or user interaction, such as syncing it with scroll position, triggering it through actions, or freezing the effect at a specific point in time. ### Explicit[​](/concepts/animations/shaders.md#explicit "Direct link to Explicit") Provides full control over the animation timeline. You can define how the animation plays by configuring properties like duration, delay, easing curve, looping, and direction. This mode is useful for choreographed animations that need to start, stop, or respond to events using a **Shader Animation** action. ## Interactive Mode[​](/concepts/animations/shaders.md#interactive-mode "Direct link to Interactive Mode") Some shader presets support touch and tap interactions, allowing users to directly influence the visual effect. When **Interactive** is enabled, users can tap or drag on the shader to trigger dynamic responses such as ripples, burn marks, distortions, or smearing effects, making the UI feel more engaging and responsive. ![st](/assets/images/st-b09341f4cb0600c7d72c019af3908752.gif) **The following presets are Interactive:** * **Fill:** Marble Smear (drag to smear) * **Wrap:** Clickable Ripple, Tappable Burn, Tappable Smoke, Tappable Pixel Dissolve, Tappable Slurp ### Persist Taps[​](/concepts/animations/shaders.md#persist-taps "Direct link to Persist Taps") Available for tappable wrap presets. When enabled, the effects created by taps remain visible even after the user lifts their finger. When disabled, the effects gradually fade away, creating a more temporary interaction. ### Tap Animation[​](/concepts/animations/shaders.md#tap-animation "Direct link to Tap Animation") For interactive wrap presets, you can control how each tap effect animates. This is separate from the main time animation and lets you define properties like curve, duration, delay, and playback behavior for each interaction, giving you finer control over how tap responses feel. ## Cache[​](/concepts/animations/shaders.md#cache "Direct link to Cache") The **Cache** option improves performance by storing the shader’s rendered output. When enabled, the shader is rendered once and reused until its parameters change. This is enabled by default for ShaderFill. tip Disable caching if your shader needs to update continuously, such as in animations or real-time interactive effects that change every frame. ## Custom Shaders[​](/concepts/animations/shaders.md#custom-shaders "Direct link to Custom Shaders") Custom Shaders allow you to create fully custom visual effects by uploading your own `.frag` (fragment shader) file. This gives you complete control over how pixels are rendered. Here’s how to add a custom shader: 1. Create a Flutter-compatible `.frag` (fragment shader) file. You can generate it using ChatGPT or Claude by describing the effect you want. You can also start from an [existing shader](https://github.com/FlutterFlow/material_palette/blob/main/lib/shaders/perlin_gradient.frag) and modify it. Example Prompt: ``` Create a Flutter-compatible GLSL .frag shader for a soft animated onboarding background using Flutter runtime effect format. {describe your effect here} Return complete shader code and a downloadable .frag file. ``` 2. Upload the `.frag` file using the **Shader Asset** picker in FlutterFlow. 3. After uploading, use **Add Uniform** to define input values for your shader. Each uniform is a slider value from 0 to 10. In the builder, custom shaders appear as a checkerboard placeholder. To view the actual rendered effect, run or test your app. ### Adding Uniforms[​](/concepts/animations/shaders.md#adding-uniforms "Direct link to Adding Uniforms") Uniforms are simply input parameters that you pass to a custom shader. In FlutterFlow, they appear as sliders in the UI, similar to how you adjust settings (like speed, colors, or intensity) in preset shaders. #### Order is everything[​](/concepts/animations/shaders.md#order-is-everything "Direct link to Order is everything") Uniforms are not matched by name. They are passed strictly in the order they are declared in the shader. This means the first uniform you declare receives the first value from the UI, the second uniform receives the next values, and so on. Example: ``` uniform float speed; // 1st uniform vec4 color; // 2nd ``` In the UI, you must provide values in this exact sequence: * Uniform 1 → `speed` * Uniform 2 → `color` (since `vec4` = 4 float values) ![uniform](/assets/images/uniform-3138516513b9e782b90be69f028a5c08.avif) warning If the order does not match, the shader will receive incorrect values, which can result in broken visuals or unexpected behavior. #### Everything becomes floats[​](/concepts/animations/shaders.md#everything-becomes-floats "Direct link to Everything becomes floats") When you have the following uniform in shader file: ``` uniform vec4 color; ``` FlutterFlow treats it as: ``` float r float g float b float a ``` #### Default uniforms[​](/concepts/animations/shaders.md#default-uniforms "Direct link to Default uniforms") Even if you don’t write them, FlutterFlow **always passes these first**: ``` uniform vec2 uSize; uniform float uTime; ``` So your shader MUST assume these exist at the top. Meaning your file should start like: ``` uniform vec2 uSize; uniform float uTime; uniform float speed; uniform vec4 color; ``` ## Use Shadertoy Shaders[​](/concepts/animations/shaders.md#use-shadertoy-shaders "Direct link to Use Shadertoy Shaders") [Shadertoy](https://www.shadertoy.com/) hosts thousands of community-made GLSL fragment shaders such as animated backgrounds, glowing effects, liquid simulations, and more. Flutter supports custom fragment shaders through its `FragmentProgram` API, but Shadertoy shaders can't be dropped in directly: they use a different entry point, different uniform names, and several built-ins that Flutter doesn't recognize. The [Shadertoy to Flutter skill](https://github.com/FlutterFlow/shadertoy_to_flutter_skill) helps convert Shadertoy GLSL into Flutter-compatible `.frag` shaders. It rewrites the shader structure, maps Shadertoy uniforms to Flutter uniforms, handles texture/audio channels where possible, and produces a `.frag` file that can be uploaded into your FlutterFlow project. **Step 1: Download Skill** The skill teaches AI Agents how to convert Shadertoy shaders accurately and safely for Flutter. The skill used for this workflow is: **`shadertoy-to-flutter`**. Download it from the [GitHub repo](https://github.com/FlutterFlow/shadertoy_to_flutter_skill). The skill contains the following files: * `SKILL.md`: Main instruction file containing the shader conversion workflow and rules. * `references/flutter_glsl_constraints.md`: Flutter GLSL limitations, unsupported features, uniform rules, and texture handling. * `references/uniform_mapping.md`: Maps Shadertoy uniforms to Flutter equivalents (e.g. `iTime → uTime`). * `references/templates.md`: Example fill/wrap shader templates and sample conversions. * `references/noise_library.md`: Noise/hash helper functions for replacing Shadertoy noise textures. * `scripts/package-skill.sh`: Packages the skill into a distributable zip file. **Step 2: Install Skill** You can use this skill with AI agents such as Claude, Codex, or another AI assistant that can read `SKILL.md` and follow its instructions. **Install in Claude** 1. Open the **Claude Desktop app**. 2. Go to **Customize**. Select **Skills** from the left sidebar. 3. Click the **+** button at the top of the Skills panel. Choose **Upload a skill**. 4. Upload the Shadertoy skill as a .zip file or skill folder. The uploaded skill must include:`SKILL.md`. It can also include supporting folders such as: `references/` and `scripts/` **Install in Codex** 1. Open the **Codex Desktop app**. 2. In the message box, type: `/sk` 3. Select **Skill Installer** from the skill suggestions list. 4. Ask Codex to install the Shadertoy skill directly from GitHub: ``` Install this skill https://github.com/FlutterFlow/shadertoy_to_flutter_skill ``` Codex will run the Skill Installer and install the skill into your local Codex folder. After installation finishes, restart Codex. **Step 3: Using Skill** You can use the skill with either a Shadertoy URL or a local .glsl file. **Option A: Convert a Shadertoy URL** In the prompt, provide the Shadertoy URL and ask to convert into `.frag` file, for example: ``` [invoke shadertoy-to-flutter skill] convert this Shadertoy shader into a Flutter .frag file: [shadertoy-url] ``` ![convert-via-url.avif](/assets/images/convert-via-url-5e57bb85748151d9d67f8664a100b608.avif) **Option B: Convert a Local .glsl File** Open the Shadertoy shader you want to use, copy the shader code, and save it as a `.glsl` file. Then attach the file and use a prompt such as: ``` [invoke shadertoy-to-flutter skill] convert this file into a Flutter .frag file ``` tip You can also paste the shader code directly into the prompt, for example: ``` Use the /shadertoy-to-flutter skill and convert this shader to a Flutter .frag file: [paste shader code] ``` **Step 4: Upload .frag File to FlutterFlow Project** Upload the `.frag` file generated in the previous step to the **Shader Asset** picker in FlutterFlow and run your app. If required, also [add Uniform](/concepts/animations/shaders.md#adding-uniforms) to define input values for your shader. ### Best Practices[​](/concepts/animations/shaders.md#best-practices "Direct link to Best Practices") * Keep the generated .frag file unchanged unless you know GLSL well. * Always check the uniform order before wiring values in FlutterFlow or Dart. * Prefer fill shaders when possible because they are easier to use. * Use wrap shaders only when the shader needs an image, scene, or app UI texture. * Avoid adding extra uniforms unless you really need user control. --- # Widget Animations Widget animations allow you to add animation effects at the widget level. To add an animation to a widget, you'll need to go to the property panel for the widget and select the animations tab. ![animation-properties.png](/assets/images/animations_overview-2-90c80022663c27f43c9eb79279fd7980.png) Animation Overview ## Animation effects & properties[​](/concepts/animations/widget-animations.md#animation-effects--properties "Direct link to Animation effects & properties") FlutterFlow supports a variety of animation effects and properties for widget animations. Most animations have core properties you can edit, like the `Duration`, which specifies how long the animation should run for, and the `Delay`, which specifies what delay the animation should have before it starts to run. In addition, there are animation-specific properties that usually have both a start and end value, which are mentioned in the table below. | Effect | Description | Example | Effect-Specific properties | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Fade** | Makes the widget gradually appear or disappear. It's widely used for smooth introductions of elements on the screen and to focus user attention by fading in or out content or UI elements. | ![Alt text for your GIF](/assets/images/fade-74dce8bfb19c813981a82249c782d390.gif) | `Opacity`: the starting or ending visibility of the widget, where 0 is fully transparent and 1 is fully visible | | **Slide** | Changes the widget's position on the screen. Typically used to introduce widget in a dynamic, visually engaging way, like sliding in menus, pages, or notifications. FlutterFlow supports both vertical and horizontal slide. | ![Alt text for your GIF](/assets/images/slide-ce4b66cbd168766f4786839abc936f8a.gif) | `Position`: where 0 specifies the widget's current position, -100 specifies 100px to the left (horizontal) or down (vertical), and 100 specifies 100px to the right (horizontal) or up (vertical).

*To make the widget come and go off the screen, make the start and/or final position greater than the width of the device.* | | **Scale** | Changes the size of the widget. Often used to draw attention to UI components, like magnifying buttons on hover or animating dialog boxes to appear from a central point. | ![Alt text for your GIF](/assets/images/scale-1fcac2418d1530633f792113f21f7095.gif) | `Scale`: the starting or ending multiple to scale the widget horizontally (X) or vertically (Y), where 1 represents the current size of the widget. | | **Rotate** | Turns the widget clockwise or anticlockwise. It's often used for simple effects like spinning a loading icon. | ![Alt text for your GIF](/assets/images/rotate-f295f7fff35fc0ff396d44f4a3321e54.gif) | `Turns`: specifies the number of 360 degree rotations. | | **Shake** | Creates the shake effect on a widget. Often used to draw attention to an element or indicate an error, like when a user enters incorrect information in a form field. | ![Alt text for your GIF](/assets/images/shake-ed533b417c3500650ab1a974cee66660.gif) | `Frequency`: Number of shakes per second

`Offset`: Shake distance, a higher value intensifies and a negative value shakes the opposite direction

`Rotation Angle`: Angle of the shake | | **Blur** | Creates a focus or un-focus effect on a widget | ![Alt text for your GIF](/assets/images/blur-95946cd8bab69eab32988f2de676776e.gif) | `Radius (X or Y)`: Size of the blur.

*To create an unfocus effect, `Final Radius` should be greater than `Initial Radius`. To create a focus effect, `Initial Radius` should be greater than `Final Radius`*. | | **Saturate** | Used to enhance visual appeal by making colors more vibrant for focused content or creating a muted effect for background elements. | ![Alt text for your GIF](/assets/images/saturate-cfa9a65e2af5bfcf6dbabcc7aebc3816.gif) | `Strength`: 0 indicates fully desaturated, 100 represents normal saturation and >100 represents the percent saturation | | **Tilt** | Creates a transforming effect (3D perspective) on your widget. Typically used to add a subtle interactive element to UI components, like buttons or cards, indicating user interaction or focus. | ![Alt text for your GIF](/assets/images/tilt-be815bd2b18bc241870c8a16babee6ee.gif) | `Tilt`: The angle at which the widget is viewed. | | **Flip** | Flip animation rotates an element around its horizontal or vertical axis, creating a mirror effect. It's often used for flipping cards in a UI to reveal hidden information. | ![Alt text for your GIF](/assets/images/flip-6a4be57f468f0bd7ea93258399327160.gif) | `Flip`: The angle at which the widget is viewed. | | **Shimmer** | Creates a "shiny" effect moving across the screen, often used to signify that data or content is in the process of loading or being fetched.

**Note** that this animation doesn't run on the Test mode. | ![Alt text for your GIF](/assets/images/shimmer-87e3ba79e5e0600d87c12d14317add7b.gif) | `Color`: The color of the "shiny" line or gradient that sweeps of the widget. A common practice is to use a slightly lighter shade than the content.

`Angle`: Determines the direction of the shimmer effect across the content. 0 degrees for horizontal and 90 for vertical. | | **Tint** | Adds a color overlay effect to your content. | ![Alt text for your GIF](/assets/images/tint-d01264b6437187cfa1332ee13230308d.gif) | `Color`: Color of the overlay.

`Strength`: Intensity of the tint. | ## Animation curves[​](/concepts/animations/widget-animations.md#animation-curves "Direct link to Animation curves") When applying an animation, you'll also be able to specify the curve. An animation curve is essentially a mathematical formula used to interpolate values over time. Changing the animation curve allows you to control the speed and style of the animation. ![Alt text for your GIF](/assets/images/animation_curves-0b845c61e6c51d21c95c697e777436aa.gif)

FlutterFlow supports a variety of animation curves: | Curve | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Ease In** | Starts the animation slowly and then accelerates towards the end. It's useful for creating an effect where the motion begins gently and speeds up. | | **Ease In Out** | Starts the animation slowly, accelerates in the middle, and then decelerates towards the end. It's ideal for creating smooth, natural-looking animations that don't have abrupt starts or stops. | | **Ease Out** | Begins the animation quickly and then slows down towards the end. It gives the effect of a rapid start that gently comes to a stop. | | **Bounce** | Adds a bouncing effect at the end of the animation. The animated object overshoots its final position and then bounces back, mimicking the physical behavior of a bouncing ball. | | **Elastic** | Creates an elastic effect where the animation overshoots its target value and oscillates before settling. It's useful for animations that need a springy, elastic feel. | | **Linear** | Progresses at a constant speed throughout the animation. It provides a uniform transition from start to end, with no acceleration or deceleration. | ## Animation on Page Load[​](/concepts/animations/widget-animations.md#animation-on-page-load "Direct link to Animation on Page Load") There are many cases when you might want to trigger an animation when a page or (in the case of a delayed load) widget is loaded onto the screen. Consider an eCommerce use case, where a backend query is used to retrieve a list of trending products. There may be some delay between when the page is first loaded and when the actual results are displayed. To improve the user experience we can add some animations to let users know when content is loading. ![A widget that first shows a container with a shimmer effect, then fades in a widget displaying the product details](/assets/images/shimmerAnimationFinal-dc5ef2f80204dce1c5a7102ca23f1a69.gif) To create an experience like this, you need to add a shimmer animation to a widget, and display that widget conditionally (i.e. when the query is loading). Here's how you do it: ## Animation on Action Trigger[​](/concepts/animations/widget-animations.md#animation-on-action-trigger "Direct link to Animation on Action Trigger") Beyond triggering widget animations on load, you can trigger an animation to occur as part of an action. For example, say you want a like button to be animated when a user clicks it. Here's how you do it: note You can give a name to the widget that you want to animate using the action, this will make it easier to find in the action menu. ## Applying multiple animations[​](/concepts/animations/widget-animations.md#applying-multiple-animations "Direct link to Applying multiple animations") You can apply multiple animations to a single widget. By default, when you add multiple animations, they are executed in a series (one after another) creating staggered animation. However, you can define to run all animations at the same time. ### Run multiple animations simultaneously[​](/concepts/animations/widget-animations.md#run-multiple-animations-simultaneously "Direct link to Run multiple animations simultaneously") If you want to run multiple animations together for the same amount of time (e.g., slide and scale widget at the same time), enable the **Apply same duration & delay** while adding animation. ### Create staggered animation[​](/concepts/animations/widget-animations.md#create-staggered-animation "Direct link to Create staggered animation") A staggered animation is multiple animations executed subsequently. Adding staggered animations can help you create a stunning visual effect. To create staggered animation, ensure you **disable** the **Apply same duration & delay** option and keep adding animations. The delay property will be auto adjusted based on the duration of all previously added animations. tip For manually controlling the staggered animation, set the delay for your new animation based on the total duration of all previously added animations. For instance, if the first two animations each last 1000ms (1 second), the delay for the third animation should be 2000ms (2 seconds). This ensures the third animation begins only after the completion of the first two, each lasting 1 second. Here's an example of creating a staggered animation: ## Setting animation values from variables[​](/concepts/animations/widget-animations.md#setting-animation-values-from-variables "Direct link to Setting animation values from variables") You can set animation values dynamically using the variables of your app. This flexibility allows you to create more sophisticated animations. Let's see an example of creating a beautiful animation where a list of items is sliding in from left to right. Here's how it looks: ![Setting animation values from variables](/assets/images/set-animations-from-variable-b784248823cbf3da590d2ca5a5bb8922.gif) If you notice carefully, the items appear in a staggered fashion. This can be achieved by setting the delay value of each item based on its position (index) in the list. Here's how exactly you do it: Select the item in the list and add the Slide animation. In the Delay property, open the variable menu and add a [inline function](/resources/functions/utility.md#inline-function-code-expressions) to calculate the delay value based on the item's index. For this example, we use the formula `[index] * 100`, where `index` represents the position of the item, and `100` is the delay in milliseconds. This means the first item will slide in after 100 ms, the second after 200 ms, and so on, creating a staggered animation effect. --- # App Events Integrations App Event Integration lets GenUI listen to FlutterFlow **LOCAL** app events and turn them into conversation context. This is how GenUI becomes aware of things the user did not explicitly type: * Cart changes * Workflow completion * Alerts * Navigation context * Device or sensor updates GenUI automatically listens for matching local events and converts them into hidden context messages for the conversation. ## Two Integration Modes[​](/concepts/app-event-integration.md#two-integration-modes "Direct link to Two Integration Modes") * **Context Injection**: Use `auto_respond: false` when the event should enrich future replies without interrupting the user immediately. In this mode, the event message is added to a pending queue, which is then flushed before the next user message is sent, allowing the model to use these queued messages as hidden context during the next inference. * **Proactive Response**: Use `auto_respond: true` when the event should trigger an immediate GenUI response. In this mode, the event message is sent directly into the conversation as an InternalMessage, inference starts right away, and the model may respond with text, UI, both, or nothing visible depending on the prompt and context. ## Message Construction[​](/concepts/app-event-integration.md#message-construction "Direct link to Message Construction") You can either enter a custom message directly in the **Message Template** field or bind it to a variable for dynamic content. If the event includes payload data, GenUI automatically appends it. For example, entering “Your order status is:” and triggering the event which includes event data such as `pending` or `in transit` will result in messages such as “Your order status is pending.” ## Pending Context Queue[​](/concepts/app-event-integration.md#pending-context-queue "Direct link to Pending Context Queue") For `auto_respond: false`, GenUI stores pending event messages in memory until the user sends the next message. The queue has a maximum size of 50, and if it overflows, the oldest messages are dropped first. Before the next user request is sent, these messages are injected directly into the conversation history as InternalMessages, allowing the model to use them as context without triggering additional model calls. ## Best Practices[​](/concepts/app-event-integration.md#best-practices "Direct link to Best Practices") #### Use context injection for ambient state[​](/concepts/app-event-integration.md#use-context-injection-for-ambient-state "Direct link to Use context injection for ambient state") For example: * Updated cart contents * Current page context * Background sync results These make future replies smarter without causing unsolicited responses. #### Use proactive response for time-sensitive events[​](/concepts/app-event-integration.md#use-proactive-response-for-time-sensitive-events "Direct link to Use proactive response for time-sensitive events") For example: * Threshold alerts * Task completion * Failed jobs * Incoming high-priority updates These are the moments where an immediate assistant response is justified. #### Keep event data structured[​](/concepts/app-event-integration.md#keep-event-data-structured "Direct link to Keep event data structured") If an event carries payload data, use a stable, well-designed data type. The generated message ends up calling `toMap()`, so clearer payload structure produces clearer AI context. #### Do not flood the queue[​](/concepts/app-event-integration.md#do-not-flood-the-queue "Direct link to Do not flood the queue") If a background signal can fire rapidly, consider batching it before triggering the event. The queue has a hard cap of 50 messages. ## Examples[​](/concepts/app-event-integration.md#examples "Direct link to Examples") #### Cart awareness without interruption[​](/concepts/app-event-integration.md#cart-awareness-without-interruption "Direct link to Cart awareness without interruption") Use a `CartUpdated` local event with `auto_respond: false`. Each cart update quietly enriches the pending context so the next time the user asks, "What's in my cart?" the model already has the latest state. #### Immediate alerting[​](/concepts/app-event-integration.md#immediate-alerting "Direct link to Immediate alerting") Use a `TemperatureAlert` local event with `auto_respond: true`. When the event fires, GenUI immediately triggers inference and the model can warn the user and render a supporting UI component if the catalog contains one. --- # App Events **App Events** allow different parts of your app to communicate without being directly connected. Instead of tightly coupling pages and components together, you can trigger an event in one place and handle it somewhere else. This helps keep your app more modular, easier to maintain, and simpler to scale as new features are added. In many apps, making `Page A` react to something that happened on `Page B` often requires passing data through navigation parameters, updating app state, or building complex callback chains. As your app grows, this approach can quickly become difficult to manage. App Events provide a cleaner pattern. Any part of your app can broadcast a named event (optionally with data), and any other part of the app can listen for that event and respond accordingly. The sender and receiver do not need to know about each other, which keeps your architecture loosely coupled. For example, imagine a user adds a product to the cart from a product detail sheet. Instead of manually updating every place that shows cart information, the app can trigger a **CartUpdated** event. The cart badge, mini cart, or product list page can listen for this event and refresh itself automatically. The component that added the item doesn’t need to know which parts of the app will update. It simply announces that the cart has changed. ![app-event.avif](/assets/images/app-event-56ecf95098e14853bdedb5dbe084ef8a.avif) ## Key Concepts[​](/concepts/app-events.md#key-concepts "Direct link to Key Concepts") ### Events[​](/concepts/app-events.md#events "Direct link to Events") An **Event** is a named signal that indicates something happened in your app. For example: * `Internet Connection Changed` : The device’s network connectivity status changed (e.g., went offline or came back online) * `Cart Updated` : An item was added to or removed from the cart You can also pass relevant details along with an event. For example, a `Cart Updated` event might include information about the specific product that was added or removed. This data can be defined using a **FlutterFlow [DataType](/resources/data-representation/data-types.md)** to ensure the event carries structured and consistent information. ### Event Handlers[​](/concepts/app-events.md#event-handlers "Direct link to Event Handlers") Event handlers define **what should happen when an event occurs**. When an event is triggered, the handler runs an **Action Block** that performs the required logic. ### Global vs. Local Events[​](/concepts/app-events.md#global-vslocal-events "Direct link to Global vs. Local Events") App Events can be scoped as **Global** or **Local**, which determines **where the event is handled and who can respond to it**. Global events are handled at the app level, while Local events are handled by specific pages or components that choose to listen for them. Choosing the right scope helps keep your app architecture clean and prevents unnecessary coupling between parts of the UI. | | Global | Local | | --------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | **Where it's handled** | At the app level | On specific pages or components that explicitly subscribe to the event | | **Number of handlers** | Exactly one (the assigned Action Block) | Many — any page or component can add a handler | | **Subscription management** | Automatic — always active | Manual — handlers are added and cancelled using actions | | **Best for** | App-wide concerns such as analytics, logging, authentication state, or global notifications | Page or component reactions such as refreshing lists, updating widgets, or syncing UI elements | | **Processing** | Sequential queue (events processed one at a time) | Broadcast stream (all subscribers notified immediately) | ### Actions[​](/concepts/app-events.md#actions "Direct link to Actions") You can **trigger and respond to App Events** using the following actions: * **Trigger App Event:** Fires an event. This action can be used anywhere actions are supported, such as on button taps, page load triggers, or inside action flows. * **Add Local App Event Handler:** Starts listening for a local event on the current page or component and runs the assigned **Action Block** when the event is triggered. * **Cancel Local App Event Handler:** Stops listening for a local event on the current page or component when you no longer want it to respond to that event. ## Using App Events[​](/concepts/app-events.md#using-app-events "Direct link to Using App Events") Follow the steps below to use App Events in your app: ### 1. Create an App Event[​](/concepts/app-events.md#1-create-an-app-event "Direct link to 1. Create an App Event") 1. Open the **App Events** page from the left sidebar. 2. Click the **+** button to create a new event. 3. Enter a name of the event. 4. Configure the event settings: * **Description** *(optional):* Add a short explanation of when and why this event fires. This description appears as a comment in the generated Dart code. * **Scope:** Choose **Global** or **Local** depending on where the event should be handled. * **Include Event Data:** Enable this if the event needs to pass additional information when it fires. * **Data Type:** If event data is enabled, select the **DataType** that defines the structure of the event payload. * **Nullable:** Specify whether the event data can be `null`. 5. **If the scope is Global**, assign a handler **Action Block**. This Action Block runs automatically whenever the event is triggered. If the event includes data, the Action Block must have a parameter matching the event's Data Type. ### 2. Trigger the Event[​](/concepts/app-events.md#2-trigger-the-event "Direct link to 2. Trigger the Event") 1. Open the **Action Flow Editor** on the widget or page where the event should be triggered. 2. Add a new action as **Trigger App Event** (under the **App Events** group). 3. Configure the action: * **Event to Trigger:** Select the app event you created. * **App Event Data:** If the event includes data, provide the values to pass with the event. * **Wait for Completion (Global events only):** If enabled (default), the event queue waits until the handler Action Block completes before processing the next queued event. Disable it for fire-and-forget behavior. This option is not shown for local events. * **Debug ID** *(optional):* Add a label to help identify this trigger during debugging. ### 3. Handle the Event[​](/concepts/app-events.md#3-handle-the-event "Direct link to 3. Handle the Event") #### For Global Events[​](/concepts/app-events.md#for-global-events "Direct link to For Global Events") No additional setup is required. The Action Block you created in [Step 1](/concepts/app-events.md#1-create-an-app-event) is called automatically whenever the event fires, from anywhere in the app. #### For Local Events[​](/concepts/app-events.md#for-local-events "Direct link to For Local Events") 1. On the page or component that should respond to the event, open the **Action Flow Editor** (commonly under **On Page Load** or **On Component Load**). 2. Add a new action as **Add Local App Event Handler**. 3. Configure the handler as per the following: * **Local App Event to Handle:** Select the event you want this page or component to listen for. * **Handler Action Block:** Choose the Action Block that should run when the event is triggered. If the event includes data, the Action Block must have a parameter matching the event's Data Type. *(Optional)* If you want to stop listening later (for example, after a certain condition is met or a toggle is switched off), add a **Cancel Local App Event Handler** action and select the same event. tip Local event subscriptions are automatically cleaned up when the page or component is disposed (removed from the widget tree). You only need to manually cancel if you want to stop listening *before* the page closes. ## Examples[​](/concepts/app-events.md#examples "Direct link to Examples") Let’s look at a couple of examples to understand how **App Events** can be useful in real-world scenarios. ### Internet Connectivity Status (Global Event)[​](/concepts/app-events.md#internet-connectivity-status-global-event "Direct link to Internet Connectivity Status (Global Event)") Internet connectivity affects the **entire app**, not just a single page. Instead of handling connectivity changes separately on every screen, you can trigger a **global event** whenever the device goes offline or reconnects and handle the response from one centralized place. When the app detects that the device has gone **offline** or come **back online**, the global event handler can react accordingly. For example, it can: * Show a **“No Internet Connection”** banner or snackbar when the device goes offline * Hide the banner when the connection is restored * Pause or resume background sync or network-dependent actions You can also include additional event data to provide more context about the connectivity state, such as: * `isConnected` → `true` / `false` * `connectionType` → `wifi` / `mobile` / `none` ![global-event.avif](/assets/images/global-event-1e6a594f6137c21bc3bd5f0dad1b4c2f.avif) Here’s the complete setup: 1. Create a **DataType** called `ConnectivityStatus` with the following fields: * `isConnected` (Boolean) * `connectionType` (String) → `wifi`, `mobile`, or `none` 2. Create a **Global App Event** called `Internet Connection Changed` with the following configurations: * Scope: **Global** * Include Event Data: **On** * Data Type: `ConnectivityStatus` 3. Create an **Action Block** called `handleConnectivityChange` that: * Checks the `isConnected` value * Shows a **“No Internet Connection”** banner when `false` * Optionally displays the current `connectionType` when connected * Hides the banner when the connection is restored 4. **Trigger the event** whenever connectivity changes: * In a connectivity listener or custom action → **Trigger App Event** * Pass `isConnected: true` with `connectionType: wifi` or `mobile` when connected * Pass `isConnected: false` with `connectionType: none` when offline The app responds consistently to connectivity changes from anywhere. All network status handling is centralized in a single Action Block, making the behavior easy to maintain and extend. ### Multi-Tab Dashboard Sync (Local Event)[​](/concepts/app-events.md#multi-tab-dashboard-sync-local-event "Direct link to Multi-Tab Dashboard Sync (Local Event)") In many apps, a dashboard contains **multiple tabs showing related data**. When information is edited in one tab, the other tabs should update to reflect the latest state. Instead of directly wiring the tabs together, you can trigger a **local event** so that each tab can react independently. When a change happens, the tabs listening for the event can react in different ways, such as: * Refreshing backend queries to fetch the latest data * Updating summary widgets or charts * Reloading lists or tables displayed in other tabs Because this is a **local event**, only the pages or components that subscribe to it will respond. ![local-event.avif](/assets/images/local-event-28abb7116637070ff0dec8f563c9303e.avif) Here’s the complete setup: 1. Create a **Local App Event** called `Dashboard Data Changed` with the following configurations: * Scope: **Local** * Include Event Data: **Off** 2. On each **dashboard tab component** (typically on **On Component Load**): * Add **Add Local App Event Handler** * Set the App Event to `DashboardDataChanged` 3. Create an **Action Block** called `refreshDashboardTab` that: * Re-runs the backend queries used by the dashboard * Refreshes the UI components that depend on that data 4. On any **edit or save action** inside a tab: * Add **Trigger App Event** * Set the App Event to `DashboardDataChanged` Once triggered, all tabs that are listening for the event refresh automatically. ## How Event Processing Works[​](/concepts/app-events.md#how-event-processing-works "Direct link to How Event Processing Works") Understanding the event lifecycle helps you design reliable event-driven flows. Internally, when an App Event is triggered, it follows a predictable flow inside the app. The event is first placed in a queue and then processed in order. Based on its **scope** (Global or Local), the event is routed to the appropriate handler, which performs the defined actions. ![flow.avif](/assets/images/flow-ceddbd8c582d88da179d98bdc5de2b71.avif) Here are a few things to remember: * **Global events** are queued and processed sequentially. If multiple global events are triggered quickly, they run one after another, not in parallel. * **Local events** are broadcast to all active subscribers immediately when triggered. * **Wait for Completion** (Global events only, enabled by default) makes the event queue wait until the handler Action Block completes before processing the next event. Disable it for fire-and-forget behavior. * **Global events** always run their assigned handler, no matter where the event is triggered. * **Local event** handlers exist only while their page or component is active. When the page is disposed, the subscription is automatically removed. ## Best Practices[​](/concepts/app-events.md#best-practices "Direct link to Best Practices") ### When to Use Global vs. Local[​](/concepts/app-events.md#when-to-use-global-vs-local "Direct link to When to Use Global vs. Local") **Use Global events when:** * The reaction should happen **anywhere in the app**, regardless of which page is currently open (e.g., showing snackbars, handling auth state changes, logging events). * The logic should be handled in **one centralized place**. * The behavior is **app-wide** and should always run when the event is triggered. **Use Local events when:** * Only **specific pages or components** need to respond to the event. * Different parts of the UI may need to **react differently** to the same event. * The handler needs access to **page-level state or widget data**. In short, **Global events are for app-wide reactions**, while **Local events are for page-specific behavior**. ### Naming Conventions[​](/concepts/app-events.md#naming-conventions "Direct link to Naming Conventions") Use clear, past-tense names that describe **what already happened**, not what should happen. This keeps event flows easy to read and understand. Examples: * `User Logged In` (not `Login`) * `Cart Updated` (not `Update Cart`) * `Payment Completed` (not `Process Payment`) This makes action flows read naturally, for example, “When `Cart Updated` is triggered, refresh the product list.” info FlutterFlow automatically generates a camelCase identifier from this name behind the scenes, which is used internally in code. Examples: * `User Logged In` → `userLoggedIn` * `Cart Updated` → `cartUpdated` * `Payment Completed` → `paymentCompleted` ### Keep Handlers Focused[​](/concepts/app-events.md#keep-handlers-focused "Direct link to Keep Handlers Focused") Each event handler (Action Block) should perform **one clear responsibility**. This keeps event flows easier to understand and maintain. If multiple reactions are needed: * Use a **Local event** and add separate handlers on different pages or components, or * Use a **single Global handler** that runs a small sequence of related actions. ### Avoid Event Chains[​](/concepts/app-events.md#avoid-event-chains "Direct link to Avoid Event Chains") Avoid triggering many events from inside other event handlers. While this is technically possible, long chains of events can quickly become difficult to follow and debug. If you find yourself chaining events frequently, consider **passing additional data through a single event** instead. ### Use Debug IDs During Development[​](/concepts/app-events.md#use-debug-ids-during-development "Direct link to Use Debug IDs During Development") The **Debug ID** field in the **Trigger App Event** action lets you label where an event was triggered. This is especially helpful when the same event can be fired from multiple places in the app, making it easier to trace and debug event flows. ## FAQs[​](/concepts/app-events.md#faqs "Direct link to FAQs") Why do I see “No local app events available to handle”? This message appears when adding an **Add Local App Event Handler** action if either: * No App Events have been created with **Local** scope. * A handler has already been added for all available local events on the current page or component. **Fix:** Create a new App Event with **Local** scope, or check whether the event you want to handle already has a handler on this page or component. Why do I see “No local app event handlers available to cancel”? This message appears when adding a **Cancel Local App Event Handler** action if there are no active local event handlers on the current page or component. **Fix:** You must first add a handler using **Add Local App Event Handler** before you can cancel it. Why is my Global event handler not firing? Check the following: * The event scope is set to **Global**. * A valid **Handler Action Block** is assigned in the event configuration. * The **Action Block parameters** match the event’s data type (if the event includes data). Why is my Local event handler not firing? Verify the following: * The **Add Local App Event Handler** action is being executed (for example, placed inside **On Page Load** or **On Component Load**). * The event scope is set to **Local**, since global events will not appear in the local handler dropdown. * The page or component listening for the event is still **active and mounted** (has not been navigated away from). Why are events firing in an unexpected order? App Events are processed sequentially through an event queue. If **Wait for Completion** is enabled (`true`), each event finishes handling before the next one starts. If the order seems unexpected, check whether some triggers have **Wait for Completion** set to `false`, which allows subsequent events to start before the previous event finishes. **Also note** that global events are processed sequentially through a queue, while local events are broadcast immediately to all active subscribers and do not go through the queue. --- # Component Catalog The **Component Catalog** is the list of FlutterFlow components that GenUI can render inline in the conversation. Without a catalog, GenUI can still chat and call tools, but it has no specific UI to render. Internally, GenUI creates documentation for each catalog component. That documentation includes: * Component name * Component description * Parameter names * Parameter types * Required or optional status * Parameter descriptions The model's render decisions are only as good as the naming and descriptions you provide. ## Component Requirements[​](/concepts/component-catalog.md#component-requirements "Direct link to Component Requirements") #### The component must be serializable at the API boundary[​](/concepts/component-catalog.md#the-component-must-be-serializable-at-the-api-boundary "Direct link to The component must be serializable at the API boundary") Catalog components cannot expose **action parameters**. GenUI only knows how to pass structured data into the component, not callbacks or arbitrary closures. #### Parameters should use supported types[​](/concepts/component-catalog.md#parameters-should-use-supported-types "Direct link to Parameters should use supported types") Supported parameter categories in the generated catalog pipeline include: * `String` * `int` * `double` * `bool` * `Color` * `DateTime` * `TimestampRange` * `LatLng` * `GooglePlace` * `JSON` * `DataStruct` * `Enum` * media-path string types such as `ImagePath`, `VideoPath`, `AudioPath`, and `MediaPath` * `List` of supported item types #### Required complex parameters need explicit defaults[​](/concepts/component-catalog.md#required-complex-parameters-need-explicit-defaults "Direct link to Required complex parameters need explicit defaults") If a catalog parameter is non-nullable and uses one of these complex types: * `Color` * `DateTime` * `TimestampRange` * `LatLng` * `GooglePlace` * `DataStruct` * `JSON` then you should either: * set an explicit default value, or * make the parameter optional For instance, if your **EventCard** component has a required `eventDate: DateTime` parameter, you must either set a default value in the component editor or make the parameter optional. Without this, GenUI validation will reject the component. GenUI validation enforces this because those types do not have a safe implicit fallback in generated constructor code. ## Runtime Rules[​](/concepts/component-catalog.md#runtime-rules "Direct link to Runtime Rules") #### One root component per surface[​](/concepts/component-catalog.md#one-root-component-per-surface "Direct link to One root component per surface") Each GenUI surface renders exactly one catalog component as its root. That root component can be a rich component tree internally, but the model cannot compose arbitrary parent wrappers like `Column`, `Container`, or other widgets that are not in the catalog. #### The model can only use listed catalog components[​](/concepts/component-catalog.md#the-model-can-only-use-listed-catalog-components "Direct link to The model can only use listed catalog components") If a component is not in the catalog, it does not exist from the model's perspective. ## Best Practices[​](/concepts/component-catalog.md#best-practices "Direct link to Best Practices") #### Use list-friendly components[​](/concepts/component-catalog.md#use-list-friendly-components "Direct link to Use list-friendly components") Because a surface has one root component, a component that accepts `List` is often the right shape for result sets: * `TransactionList` * `SearchResultsGrid` * `CartItemsSummary` #### Prefer focused components over screen-sized composites[​](/concepts/component-catalog.md#prefer-focused-components-over-screen-sized-composites "Direct link to Prefer focused components over screen-sized composites") Good catalog components are reusable units, such as: * `ProductCard` * `OrderSummary` * `InvoicePreview` * `ReviewSummary` * `AppointmentConfirmation` These give the model flexible building blocks. A large page-like component is harder to reuse and usually harder for the model to choose well. #### Use consistent `DataStruct` across tools and components[​](/concepts/component-catalog.md#use-consistentdatastruct-across-tools-and-components "Direct link to use-consistentdatastruct-across-tools-and-components") If a tool returns `ProductStruct`, prefer catalog components that also accept `ProductStruct` or `List`. That keeps tool output and rendering input aligned and makes the tool-to-UI handoff more reliable. #### Describe parameters like you are documenting an API[​](/concepts/component-catalog.md#describe-parameters-like-you-are-documenting-an-api "Direct link to Describe parameters like you are documenting an API") Good: * `estimatedDeliveryDate`: "Expected arrival date in ISO 8601 format." * `inventoryStatus`: "Availability state shown to the user, such as inStock or backOrdered." Weak: * `date` * `status` #### Keep component names specific[​](/concepts/component-catalog.md#keep-component-names-specific "Direct link to Keep component names specific") Use clear, descriptive names that reflect the component’s purpose. Good: * `OrderStatusCard` * `SensorAlertSummary` * `QuoteBreakdown` Weak: * `Card1` * `Summary` * `Details` #### Avoid ambiguous overlap[​](/concepts/component-catalog.md#avoid-ambiguous-overlap "Direct link to Avoid ambiguous overlap") If two components do roughly the same thing, the model has to guess. Either merge them, rename them more clearly, or narrow their intended use. --- # Custom Code While FlutterFlow provides a wide range of pre-built components and functionalities, there may be times when you need to extend your app with custom logic or UI components that are not available out of the box. This is where writing custom code comes into play. There are a few different ways to make custom code accessible in FlutterFlow: * **[Custom Functions](/concepts/custom-code/custom-functions.md):** Custom Dart functions that can be used to set Widget or Action properties. * **[Custom Actions](/concepts/custom-code/custom-actions.md):** Custom Dart functions that can be triggered by [Action Triggers](https://docs.flutterflow.io/resources/functions/action-triggers/) or used as nodes in an [Action Flow](https://docs.flutterflow.io/resources/functions/action-flow-editor#action-flow-editor). These are usually `async` functions and are able to import [custom package dependencies](/concepts/custom-code.md#adding-a-pubspec-dependency). * **[Code File](/concepts/custom-code/code-file.md):** You can define custom classes, enums, and logic to manage your app’s data and behavior. * **[Custom Widgets](/concepts/custom-code/custom-widgets.md):** Custom Flutter widgets that can also import [custom package dependencies](/concepts/custom-code.md#adding-a-pubspec-dependency) and be used in the same way as [Components](https://docs.flutterflow.io/resources/ui/components) throughout your project. * **[Configuration Files](/concepts/custom-code/configuration-files.md):** You'll have the ability to edit native files for Android and iOS. Why Write Custom Code? * **Extend Functionality:** Add features that are not included in the standard FlutterFlow components. * **Custom Integrations:** Integrate with third-party packages or APIs / databases that require specific handling. * **Unique UI Elements:** Create unique user interface elements that require custom rendering or interactions. ## Writing Custom Code[​](/concepts/custom-code.md#writing-custom-code "Direct link to Writing Custom Code") Custom Code lets you add app-specific logic, custom widget, and native configuration directly in FlutterFlow. You can keep functions, actions, widgets, and code files organized with the pages and components they support, so each feature's files are easier to find and manage. warning Instructions and visuals on this page show the new Custom Code layout. You can switch from from the classic Custom Code editor by clicking **Try New Layout** in the toolbar. Switching to the new Custom Code layout is one-way for that project. After you switch, you cannot go back to the classic Custom Code editor for the same project. To safely try the new custom code layout first, create another branch, switch to that branch, and then click Try New Layout there. There are two main ways to write custom code in FlutterFlow: 1. Using the [**In-App Code Editor**](/concepts/custom-code.md#using-the-in-app-code-editor) 2. Using the [**Visual Studio Code Extension**](/concepts/custom-code/vscode-extension.md) ### Using the In-App Code Editor[​](/concepts/custom-code.md#using-the-in-app-code-editor "Direct link to Using the In-App Code Editor") You can use the In-App Code Editor to view and edit custom code directly in the FlutterFlow application. ![custom-code-common.avif](/assets/images/custom-code-common-fef32c326e8da841ae095b7003319aab.avif) tip To leverage the capabilities that go beyond our in-app code editor, you can click on the **VS Code icon** to open and edit your custom code directly in VS Code using the FlutterFlow [**VSCode extension**](/concepts/custom-code/vscode-extension.md). ![open-in-vscode](/assets/images/open-in-vscode-f5f99800d92ec16d1755c3eec08f1213.avif) Using the In-App Code Editor on Desktop Note that the desktop version of the In-App Code Editor is limited. We recommend using the Web editor or the **[VSCode Extension](/concepts/custom-code/vscode-extension.md)**. ### Code Copilot[​](/concepts/custom-code.md#code-copilot "Direct link to Code Copilot") Code Copilot is an AI-assisted feature that helps you generate code snippets, functions, or entire blocks of code based on natural language descriptions of what you want to achieve. It simplifies the app-building process by allowing you to describe the functionality you need, such as 'calculate the total price of items in a cart', and then the Copilot generates the necessary code. This can significantly speed up the building process and reduce the need for in-depth programming knowledge, making it especially useful for custom functions and actions. Limitation Your prompt must be at least 3 words and no more than 500 characters. ### Compile Code[​](/concepts/custom-code.md#compile-code "Direct link to Compile Code") When you are done adding your code snippets, you can compile it to ensure there are no compilation errors and that your code can be transformed into something that can execute when your app is running. To do so, click the **Compile Code** button. ![compile-errors.avif](/assets/images/compile-errors-28f35f69f11e403e11de0bd869a506a1.avif) How to recognize compile time errors To run your app, you must make sure **Custom Functions** are compiled. Custom Widgets and Actions don't need to be compiled to export code or test your app. However, you won't be able to preview Custom Widgets in the builder until they are compiled. You'll see a project warning if you don't compile Custom Widgets or Actions. Compiling Custom Functions should be pretty fast, but sometimes, compiling Custom Actions and Widgets takes a while. ### Code Analyzer[​](/concepts/custom-code.md#code-analyzer "Direct link to Code Analyzer") The code analyzer is available in all your custom code snippets and ensures the quality and correctness of your custom code. It automatically checks your Dart code for errors and warnings, providing real-time feedback as you write. ![code-analyzer](/assets/images/code-analyzer-83e3365c67e75c84432d4a930bc4badc.avif) When there is a compilation error, the code analyzer will stop running and display the errors caught by the compiler. Once fixed, save the code and restart the code analyzer to resume real-time analysis and receive feedback on updated code. ### Automatic FlutterFlow Imports[​](/concepts/custom-code.md#automatic-flutterflow-imports "Direct link to Automatic FlutterFlow Imports") When creating a new custom code snippet (Actions, Widgets, or Functions) in FlutterFlow, some fundamental imports will be automatically added for you. These imports cannot be modified by the developer. Custom Functions do not allow adding any custom imports, but you can add custom imports in Custom Actions and Widgets after the line **"Do not remove or modify the code above"**. ![automatic-imports.png](/assets/images/automatic-imports-138890e7d87bf374a4899f48d1e474b3.png) ### Custom Code Settings[​](/concepts/custom-code.md#custom-code-settings "Direct link to Custom Code Settings") When you edit a custom code snippet in FlutterFlow, the Settings menu opens on the right. This menu may vary slightly depending on the type of custom code (Actions, Functions, or Widgets), but here, we’ll cover the common settings. #### Generate Boilerplate Code[​](/concepts/custom-code.md#generate-boilerplate-code "Direct link to Generate Boilerplate Code") This setting allows you to generate boilerplate code, providing a structured starting point with essential code imports and a basic widget or function structure. ![copy-boilerplate-code.png](/assets/images/copy-boilerplate-code-01438f1964317e413b1636db9fb04d54.png) After creating a new resource file, click the code icon on the Widget Settings menu to generate the boilerplate code. Then, click "Copy to Editor" to add the boilerplate to your resource file’s code editor, where you can further customize it. #### References[​](/concepts/custom-code.md#references "Direct link to References") The References helps you understand where your custom code is being used throughout the project. When enabled, FlutterFlow scans your app and displays all locations where a custom function, custom action, or custom widget is referenced. warning Enabling References may increase the loading time of the Custom Code editor because FlutterFlow needs to scan the project and map all usage locations. ![references](/assets/images/references-f4efba18bcebc583fbb533c8fde1de65.avif) #### Exclude From Compilation[​](/concepts/custom-code.md#exclude-from-compilation "Direct link to Exclude From Compilation") If, for some reason, your action or widget fails to compile but you still want to compile the rest of your code, you can enable this toggle. Doing so will exclude the problematic code from the compile process. Scope This option is only available for Custom Widgets and Custom Actions. ![action-settings.avif](/assets/images/action-settings-50233cae6608254af92f074442cad4b6.avif) #### Include BuildContext[​](/concepts/custom-code.md#include-buildcontext "Direct link to Include BuildContext") This setting determines whether to pass the BuildContext of the widget calling this custom action as an argument. This is useful for actions that need to interact with the widget tree or access context-specific data. Scope This option is only available for Custom Actions. ## Input Arguments[​](/concepts/custom-code.md#input-arguments "Direct link to Input Arguments") When writing custom code in FlutterFlow, you can define input arguments to make your custom functions, widgets, or actions more dynamic and reusable. Input arguments allow you to pass data into your custom code, enabling it to perform different tasks based on the input provided. By using input arguments, you can create more flexible and powerful custom code that can adapt to various scenarios within your application. Here's an example of an action that takes 2 arguments: `cartItems` that is a `List of ItemsStruct` and `productId` that is a String. ![action-arguments.png](/assets/images/action-arguments-a52dd335f09d0cc5de625bda9e1fe960.png) Generated Code for custom data types When you define a custom data type in FlutterFlow, the generated code will refer to the type as `Struct`. For example, if your custom data type is called `Items`, it will be referenced in the generated code as `ItemsStruct`. ### Callback Action As Parameter[​](/concepts/custom-code.md#callback-action-as-parameter "Direct link to Callback Action As Parameter") A callback action is an action passed as a parameter to a custom action or widget and triggered at some point in the future when a specific event occurs. This is especially helpful when you want to trigger actions from within the custom action or custom widget logic and include them as part of the custom behavior. For example, if an error occurs inside the custom logic, you could trigger an action immediately to inform the user about the error, and then continue execution or end with a default value to return. What are callbacks? In programming, callbacks are functions passed to other functions to be called when a specific event occurs. In the following example, we have a Custom Action that takes an `onError(searchKeyword)` callback action with an Action Parameter `searchKeyword`. This means that the custom action will provide this search keyword back to the callback action when it calls it. ![explain-callback-action.png](/assets/images/explain-callback-action-bc00cb56afb51a047c1fc2e950c1efca.png) ### Add an Action to Callback Action[​](/concepts/custom-code.md#add-an-action-to-callback-action "Direct link to Add an Action to Callback Action") To provide a callback action to your main custom action, check out this quick guide where we provide a "**Show Snackbar**" action to `onError`, displaying a combined text using the search keyword. ## Return Values[​](/concepts/custom-code.md#return-values "Direct link to Return Values") In FlutterFlow, custom code can not only take input arguments but also return values, back to the caller. Return values allow your custom functions, or actions to pass data back to the main application, enabling further processing or UI updates based on the results of the custom code. Scope Return Values are only enabled for Custom functions & Custom Actions. Custom Widgets **cannot** return a value at the moment. Here's an example of an Action that returns a *nullable* integer. ![return-value-actions.png](/assets/images/return-value-actions-1b07a6e637fbbbb36ce6989a6a0d8c6a.png) ## Description[​](/concepts/custom-code.md#description "Direct link to Description") You can add a [**Description**](/flutterflow-ui/resource-hierarchy.md#resource-description) note on Custom Functions and Custom Actions to briefly explain their purpose, usage, or important details. This helps clarify what the function or action is intended for, making your project more understandable and maintainable—especially in libraries and collaborative environments. ![adding-description.avif](/assets/images/adding-description-086493054f5d563189ec6faf02660ecf.avif) You can view these descriptions as tooltips by hovering over the green note icon when selecting a Custom Function or Custom Action. ![description-note](/assets/images/description-note-c67c9c314192f68a70ea3dcc4f613bf7.avif) tip In the generated code, descriptions are added as comments before the function definition, and they also appear in the custom code editor. ![description-in-custom-code](/assets/images/description-in-custom-code-794f28d8e82c1262ae92fbc04913dcfb.avif) ## Organize Custom Code[​](/concepts/custom-code.md#organize-custom-code "Direct link to Organize Custom Code") Pages, components, and custom code can be grouped together in the same user folders. This makes it easier to organize a feature in one place instead of keeping its UI and custom code separate. For example, if you have a **Cart** folder that contains cart pages and components, you can drag a related custom function, such as `calculateCartTotals`, into the same folder. This keeps the page, component, and custom logic for that feature together in the widget tree. ## Adding a Pubspec Dependency[​](/concepts/custom-code.md#adding-a-pubspec-dependency "Direct link to Adding a Pubspec Dependency") [Pub.dev](https://pub.dev) is the official package repository for Dart and Flutter. It hosts a wide range of packages, libraries, and tools that developers can use to extend the functionality of their Dart and Flutter applications. Flutter Favorite Packages Flutter Favorite packages are a curated set of packages on pub.dev that have been recognized by the Flutter team and the community for their quality, popularity, and usefulness in Flutter development. These packages are marked with a "Flutter Favorite" badge, indicating that they meet a high standard of quality, reliability, and best practices. You can explore the Flutter Favorite packages on **[pub.dev's Flutter Favorites page](https://pub.dev/packages?q=is%3Aflutter-favorite)**. To add a pubspec dependency from pub.dev, go to **Settings and Integrations > Project Dependencies**, then open the **Custom Dependencies** tab. Click **Add Pub Dependency**, enter the **package name** and **version**, and click **Add** to include it in your project. ### Choosing the correct package from pub.dev[​](/concepts/custom-code.md#choosing-the-correct-package-from-pubdev "Direct link to Choosing the correct package from pub.dev") You will find varieties of dependencies for a specific requirement, and choosing the best one can be challenging. This section helps you identify the right dependency by examining its score. When you search for any dependency in *pub.dev*, you will get a list of dependencies. You can filter out the result based on which dependency is more inclined toward your needs. You can do so by opening and checking each dependency manually. Once you have a handful of dependencies, consider the following factors while choosing the final one. * **WEB**: It must support Web to run your app in our Run/Test Mode. * **Likes**: This shows how many developers have liked a dependency. * **Pub Points**: Tells the quality of the dependency (out of 130) based on code style, platform support, and maintainability. * **Popularity**: This metric indicates how many apps use the package. A high popularity score (out of 100%) can suggest that the package is stable and trusted by many developers. * **Documentation:** A well-documented package will save you time and reduce ambiguity. Check if the package has clear usage examples, a comprehensive README, and ideally API documentation. * **Maintenance & Updates**: Check the last update date. A regularly updated package is more likely compatible with the latest Dart/Flutter versions and has fewer bugs. ![Dependency-score.png](/assets/images/Dependency-score-605001293cfa6b24bf4ce7073feea1d8.png) When adding a pubspec dependency to your custom code in FlutterFlow, you’ll need two pieces of [information](/concepts/custom-code.md#setup-code): the Package name with its Version number and the Import statement. ### Using Unpublished or Private Packages[​](/concepts/custom-code.md#using-unpublished-or-private-packages "Direct link to Using Unpublished or Private Packages") FlutterFlow supports the use of unpublished packages, which allows you to integrate packages that are not yet available on **pub.dev**. This capability is particularly useful when working with custom, forked, or private packages hosted on public or private repositories. By leveraging this, you can enhance your app’s functionality with customized or proprietary libraries tailored to your specific needs. Possible Use Cases * **Using a Different Branch of a Package**: When you need to test or use features that are only available on a specific branch of a package. * **Forked Version for Customizing Features**: When you need to fork a package to customize its functionality or fix issues that the original maintainer hasn’t addressed. * **Private Packages for Internal Use**: Companies or enterprises may have internal Flutter libraries that they want to use in their FlutterFlow app but cannot publish publicly due to confidentiality or proprietary restrictions. #### Add Packages from Public Repositories[​](/concepts/custom-code.md#add-packages-from-public-repositories "Direct link to Add Packages from Public Repositories") For packages hosted on public repositories (e.g., GitHub), you can add them to your FlutterFlow project by specifying the repository URL in the following format. ``` package_name: git: url: https://github.com/username/repository_name.git ``` You can also fine-tune the dependency by using additional parameters like `ref` and `path` in the given format. Here are some examples: * **To use a specific branch** (e.g., `development`): ``` package_name: git: url: https://github.com/username/repository_name.git ref: development ``` * **To use from a specific commit**: ``` dependencies: package_name: git: url: https://github.com/username/repository_name.git ref: a1b2c3d4 ``` * **To use package located in a subdirectory of the repository**: ``` package_name: git: url: https://github.com/username/repository_name.git path: packages/subpackage_name ``` Here’s exactly how you do it: #### Add Packages from Private Repositories[​](/concepts/custom-code.md#add-packages-from-private-repositories "Direct link to Add Packages from Private Repositories") For packages hosted in private repositories, you’ll need to authenticate access. This can be done using HTTPS with a personal access token. For GitHub, you can go to your GitHub account’s settings and [generate a token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) with the necessary permissions and use it in the following format. You can also create and use a [fine-grained access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) that only has certain permissions. ``` package_name: git: url: https://:@github.com/username/private_repo.git ``` Replace `` with your GitHub username and `` with the generated token. ### Setup Code[​](/concepts/custom-code.md#setup-code "Direct link to Setup Code") To configure your custom code with the package, copy and paste the following items from the package's pub.dev page: 1. **Copy Package Name & Version** To use the dependency in your Custom Action or Custom Widget resource file, go to the package's pub.dev page and click the **Copy to Clipboard** icon next to the package name and version. Then, paste it into the **Pubspec Dependency** section (bottom right) of the FlutterFlow code editor. ![package-dependency-version-copy](/assets/images/package-dependency-version-copy-6caeb2d534461e682a1a8f8cf02d1e59.avif) See **[example](/concepts/custom-code.md#add-pubspec-dependency-to-custom-code-example-guide)** for more information. warning The current dependency might depend on other dependencies to work. So make sure you also copy the name and version of all the additional dependencies to specify in the code You can check if the current dependency has any additional dependencies inside the '*Dependencies'* section at the bottom right side. ![img\_1.png](/assets/images/img_1-9cf7d6b8156405f7205db311cbefb87d.png) 2. **Copying Import Statement** An import statement specifies the location of the dependency's code. When creating a custom widget or action, add this statement at the end of the default import statements in the code editor. Open the dependency page and select the **Installing** tab; under the **Import It** section, you'll find the import statement. To copy, click the **Copy to Clipboard** icon. ![copy-import-statement.png](/assets/images/copy-import-statement-3813e68c653aa5d19ffdd8c9d79d998f.png) 3. **Copy Example Code** Example code is always available in the **Example** tab on the package’s pub.dev page. Copy any relevant snippets that demonstrate usage, and paste them into your custom widget or function file. You can then modify the code as needed to fit your project. ## Add Pubspec Dependency to Custom Code: Example Guide[​](/concepts/custom-code.md#add-pubspec-dependency-to-custom-code-example-guide "Direct link to Add Pubspec Dependency to Custom Code: Example Guide") In this example, we are using the [**flutter\_rating\_bar**](https://pub.dev/packages/flutter_rating_bar) dependency to create a `ProductRatingBar` custom widget for our Product pages. See how we utilize the example code from pub.dev and add the customized widget in FlutterFlow: note This example demonstrates how to add a [**pub.dev**](https://pub.dev) package to a Custom Widget snippet, but you can follow the same process for adding a package to Custom Actions. For a deep dive, explore the detailed documentation on **[Custom Widgets](/concepts/custom-code/custom-widgets.md)** and [**Custom Actions**](/concepts/custom-code/custom-actions.md). ## Manage Dependencies[​](/concepts/custom-code.md#manage-dependencies "Direct link to Manage Dependencies") You can manage dependencies directly from **Settings and Integrations > Project Dependencies** > **Custom Dependencies** tab. If version conflicts occur, warnings will appear in both the **Custom Dependencies** tab and the **Custom Code** editor. You can also bump package versions directly from the list, making it easier to resolve issues and keep dependencies consistent. --- # Cloud Functions Cloud Functions let you run backend code in response to events and API requests without managing your own servers. They are commonly used for tasks such as processing data, calling external APIs, sending notifications, running AI workflows, or securely handling secrets and business logic. FlutterFlow supports both Firebase Cloud Functions and Supabase Edge Functions, allowing you to build scalable backend workflows. ## Firebase Cloud Functions[​](/concepts/custom-code/cloud-functions.md#firebase-cloud-functions "Direct link to Firebase Cloud Functions") [**Firebase Cloud Functions**](https://firebase.google.com/docs/functions) allow you to run server-side Node.js code triggered by Firebase services and HTTPS requests. For example, you can automatically send emails, process uploads, generate AI content, or react to database changes. FlutterFlow includes built-in support for creating, editing, deploying, and triggering Firebase Cloud Functions directly from the platform. note Read up on some interesting use cases of [**Cloud Functions**](https://firebase.google.com/docs/functions/use-cases). ### Adding Cloud Functions[​](/concepts/custom-code/cloud-functions.md#adding-cloud-functions "Direct link to Adding Cloud Functions") Let's see how to add a *Cloud Function* by building an example that generates logos based on user prompts. Here's how it looks: The Cloud Function takes input from a TextField widget and initiates an API call to an [image generation API](https://platform.openai.com/docs/api-reference/images/create). Once the image URL is retrieved, it's displayed within an Image widget. Here are the step-by-step instructions to build such an example: Before you Begin * Make sure the project is on Blaze plan on Firebase. * Completed all steps in the [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). **1. Add page state variables** For this example, you'll need to set up two [Page State variables](/resources/ui/pages/page-lifecycle.md#creating-a-page-state): 1. **generatingImage (*****Type: Boolean*****)**: This is used to control the visibility of a loading indicator during the logo creation process. Its value is set to *True* before initiating the API call and switched to *False* once the logo generation is complete. 2. **logoImage (*****Type: ImagePath*****)**: This is used to hold the generated logo image. After a successful API call, the retrieved image URL is stored here, allowing the logo to be displayed in the Image widget. ![img\_6.png](/assets/images/img_6-970302e076fc0dddef916c3973759c72.png) **2. Build a page** Let's add a page that allows users to enter the prompt. To speed up, you can add a page from the template or use [AI Page Gen](/resources/ui/pages.md#generate-with-designer). Here is the page added using AI Page Gen, and after some modification, it looks the below: Also, see how to [build a page layout](/concepts/layouts.md) if you want to build a page from scratch. ![img\_7.png](/assets/images/img_7-a82a145481ef54c0e8723db21c1d3519.png) Few things to note here: * We use the [**ConditionalBuilder**](/concepts/layouts/conditional-builder.md) widget to show/hide the loading indicator based on the *generatingImage* variable. **Tip**: The Else branch of this widget is nothing but a ProgressBar inside the Container with a [rotating loop animation](/concepts/animations/widget-animations.md). * The Image widget uses the *logoImage* variable to display the logo. **3. Create and deploy Cloud Function** To create and deploy a *Cloud Function* : 1. Click on the **Cloud Functions** from the [**Navigation Menu**](/flutterflow-ui/builder.md#navigation-menu) (left side of your screen). 2. Click **+ Add**. This will add the default `newCloudFunction`. 3. Set the **Cloud Function Name**. #### Boilerplate Settings[​](/concepts/custom-code/cloud-functions.md#boilerplate-settings "Direct link to Boilerplate Settings") On the right side, you can configure the following Boilerplate Settings: 1. **Memory Allocation**: You can specify the amount of memory your function should have when it's executed based on its complexity and needs. This setting is crucial as it influences the function's performance and the cost of running it. More memory can enhance performance for intensive tasks but also increase costs. 2. **Timeout (s)**: This refers to the maximum amount of time, in seconds, that a function is allowed to run before it is automatically terminated. If your function takes longer to execute, increasing the timeout setting may be necessary. However, be aware that longer timeouts can incur higher costs since billing is based on execution time. 3. **Require Authentication**: Turn on this setting if you want users to be authenticated to execute this cloud function. 4. **Cloud Function Region**: This determines the geographical location of the servers where your functions are hosted and executed. Ideally, you should keep this same as your *Default GCP resource location* and the Cloud Function Region specified in the Firebase Advanced Settings. ![cf-region.avif](/assets/images/cf-region-a94ec06c45dee0ff21e5bf4875dc28f0.avif) #### Configuring Input & Output[​](/concepts/custom-code/cloud-functions.md#configuring-input--output "Direct link to Configuring Input & Output") Your cloud function might need some data to process and return the result. You can do so by configuring the input and output. 1. To receive output from a Cloud Function, enable the **Return Value** and choose an appropriate Type for the output, like 'String' for text. For this example, set it to *ImagePath* to get the URL of the generated logo. 2. To input data: Click **+ Add parameters**. **Name** the parameter, select its **Type**, choose single or multiple items (**Is List** option), and uncheck **Nullable** if the value can be null. For this example, add a parameter 'prompt' with *Type* set to *String*. 3. When using [Custom Data Types](/resources/data-representation/custom-data-types.md), Cloud Function expects JSON, matching each field in the Data Type to a key-value pair in the JSON. If the Data Type is a list, the function expects a list of JSONs. For example, for a custom data type named 'Person' with fields 'Name' and 'Age,' the function should return: ``` //JSON: { "Name": "John", "Age": 30 } //Example Cloud Function Code: return { "name": person.name, "age": person.age }; ``` For a list, the function should return: ``` //JSON [ { "Name": "John", "Age": 30 }, { "Name": "Jane", "Age": 25 } ] //Example Cloud Function Code: return filteredpersons.map(filteredpersons => { return { "name": filteredpersons.name, "age": filteredpersons.age }; }); ``` #### To deploy[​](/concepts/custom-code/cloud-functions.md#to-deploy "Direct link to To deploy") 1. Click the `[]` icon to view the boilerplate code; a popup will open with the updated code, and then click **` Copy to Editor`**. **Tip**: To see if you are able to deploy the cloud function (before adding your own code), proceed directly with steps 8 and 9. 2. Inside the code editor, add the cloud function code. **Tip**: You can copy the boilerplate code to [ChatGPT](https://chat.openai.com/) and ask it to write the desired code based on that. 3. Click **Save Cloud Function**. 4. Click **Deploy**. Here's the code used for this example: ``` const functions = require('firebase-functions'); const admin = require('firebase-admin'); const https = require('https'); exports.logoMaker = functions.region('us-central1') .runWith({ timeoutSeconds: 10, memory: '512MB' }).https.onCall((data, context) => { return new Promise((resolve, reject) => { const prompt = data.prompt; if (!prompt) { reject(new functions.https.HttpsError('invalid-argument', 'No prompt provided')); return; } const postData = JSON.stringify({ model: "dall-e-3", prompt: prompt, n: 1, size: "1024x1024" }); const options = { hostname: 'api.openai.com', port: 443, path: '/v1/images/generations', method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer YOUR-APIKEY`, 'Content-Length': postData.length } }; const req = https.request(options, (res) => { let responseBody = ''; res.on('data', (chunk) => { responseBody += chunk; }); res.on('end', () => { try { const responseJSON = JSON.parse(responseBody); if (responseJSON.data && responseJSON.data.length > 0) { // Retrieve the URL of the first image const firstImageUrl = responseJSON.data[0].url; resolve(firstImageUrl); } else { reject(new functions.https.HttpsError('not-found', 'No images found')); } } catch (error) { reject(new functions.https.HttpsError('internal', 'Error processing response', error)); } }); }); req.on('error', (error) => { reject(new functions.https.HttpsError('internal', 'Error generating image', error)); }); req.write(postData); req.end(); }); }); ``` Important Always regenerate and use the updated boilerplate code or adjust your own code accordingly whenever there are changes in the code, boilerplate settings, or input/output parameters. Optional: Add package Your cloud function may require third-party packages to work. You can include any npm package (dependency) by listing it in the `package.json` file. This file not only manages the npm package dependencies for your functions but also holds project metadata, sets up scripts for tasks such as deployment and outlines the compatible Node.js versions. To add a dependency, open the `package.json` file and specify your package in the `dependencies` section. ![img\_9.png](/assets/images/img_9-90023c8e1828a702463ab4b7a5aceced.png) **4. Trigger Cloud Function** The newly created *Cloud Function* will be available as an action when you are adding one. For this example, on click of a button, we'll first set the *generatingImage*to *True* and then trigger the **Cloud Function Action**.
**5. Optional: Use Cloud Function result** To use the *Could Function* result, ensure you provide the *Action Output Variable Name* while adding the action, and then you can access it via the **Set from Variable menu > Action Outputs > \[Action Output Variable Name]**. For this example, we'll use the result (i.e., generated logo image URL) and set it to *logoImage* variable. Here's how you do it: ### Testing Cloud Functions[​](/concepts/custom-code/cloud-functions.md#testing-cloud-functions "Direct link to Testing Cloud Functions") The Google Cloud console has built-in functionality to allow you to trigger a Cloud Function for testing. This means that after deploying Cloud Functions, you can test them without writing to Firestore (either from FlutterFlow or otherwise). Here's how to test FlutterFlow's `sendUserPushNotificationsTrigger` function in the Google Cloud console: 1. Open your browser and navigate to the following URL: `https://console.cloud.google.com/functions/details/us-central1/sendUserPushNotificationsTrigger?env=gen1&project=&tab=testing`
In here: * Replace `` with your GCP or Firebase project. * If you want to test a different Cloud Function, update `sendUserPushNotificationsTrigger` with the relevant cloud function name. 2. Paste the following JSON into the `Configure Triggering Event` text area. * If you want to test a different Cloud Function, update `sendUserPushNotificationsTrigger` with the relevant cloud function name. ``` { "value": { "name": "projects//databases/(default)/documents/sendUserPushNotificationsTrigger/", "fields": { "scheduled_time": { "stringValue": "" }, "initial_page_name": { "stringValue": "" }, "notification_title": { "stringValue": "Your friends are missing you!" }, "notification_text": { "stringValue": "Please come back to Nanochat" }, "user_refs": { "stringValue": "users/VXu6EvFMl5M8KMXriYRvFEWTFHA2" } } } } ``` 3. In the `name` property: * Replace `` with your GCP or Firebase project. * Replace `` with the ID of the document. This document must already exist in Firestore. * If you're testing another function than `sendUserPushNotificationsTrigger`, update `ff_user_push_notifications` with the collection where the document is written. 4. Update the values under the `fields` property for the message you want to send.
The `fields` in the example above are for FlutterFlow's built-in `sendUserPushNotificationsTrigger` function. If you're testing a different Cloud Function, you will need to update the `fields` for the code in *that* function. 5. Click the `TEST THE FUNCTION` button. The Cloud Function will now run and gather the relevant entries from Google Cloud Logging. ### FAQs[​](/concepts/custom-code/cloud-functions.md#faqs "Direct link to FAQs") Why do cloud function deployments fail on newly created projects? This issue occurs because the newly created Google Cloud Platform (GCP) project hasn't been fully configured with the necessary APIs and permissions. Follow the steps below to enable the required APIs and set proper permissions. 1. Open your browser and navigate to the following URL: `https://console.cloud.google.com/functions/list?referrer=search&hl=en&project=` Replace `` with your GCP or Firebase project ID. 2. Click on the **Create Function** button. GCP will prompt you to enable the necessary APIs: **Cloud Build** and **Cloud Functions**. 3. After clicking **Next**, you will be prompted to enable the **Cloud Run Admin API**. ![cloud-run-admin-api](/assets/images/cloud-run-admin-api-6289d1d79337a0f909d0e29e555335f6.png) 4. Now, you need to grant the default compute service account the appropriate permissions. In the next page, you will see the option to deploy an example cloud function like `helloHttp`. Deploy this function. You will be prompted to grant permissions to the default compute service account. The message will look like: `You need to grant the following roles to the build service account to deploy a function: roles/cloudbuild.builds.builder to -compute@developer.gserviceaccount.com.` 5. Click **Grant** to provide the required permissions and deploy the example cloud function. Once deployed, you can delete this function if you wish. With the required permissions granted, you should now be able to deploy cloud functions from FlutterFlow without any further issues. I am getting Cloud Function Deployment Errors ![img\_10.png](/assets/images/img_10-56cfc3937708ca6bbbd99dad965068d1.png) If you encounter deployment errors, it may be helpful to check out [this community post](https://community.flutterflow.io/discussions/post/how-to-fix-cloud-function-deployment-errors-all-solutions-discussion-wgfMLgpLrBlmnUI) for possible solutions and insights. Why am I getting a CORS error when executing my Cloud Function? The CORS error occurs because the **Access-Control-Allow-Origin** header is missing from the response, preventing your request from being completed. This issue can arise with new Cloud Functions, whether deployed through FlutterFlow or not. Follow the steps below to fix the issue: 1. Open your Google Cloud Project's [**Cloud Functions List**](https://console.cloud.google.com/functions/list) 2. Select the function causing the issue. 3. Navigate to the **Permissions** tab. 4. Open the **VIEW BY ROLES** tab. 5. Ensure there's a row with `Cloud Functions Invoker` with principal set to `allUsers`. If it’s missing, click on the **Grant Access**, add `allUsers` with the `Cloud Functions Invoker` role. ![add-cf-invoker-role](/assets/images/add-cf-invoker-role-f06288f87aa8e929db35770df5aa1eb9.avif) ## Supabase Edge Functions[​](/concepts/custom-code/cloud-functions.md#supabase-edge-functions "Direct link to Supabase Edge Functions") [**Supabase Edge Functions**](https://supabase.com/docs/guides/functions) let you run secure backend logic using Deno and TypeScript directly on Supabase infrastructure. They are ideal for AI integrations, secure API wrappers, webhooks, payments, and server-side processing. Unlike Firebase Cloud Functions, Supabase Edge Functions are tightly integrated with your Supabase project and run closer to users for lower latency. Prerequisite Before using Supabase Edge Functions, make sure your FlutterFlow project is connected to Supabase. See the [**Supabase Setup**](/integrations/supabase/setup.md#connect-with-supabase-oauth) guide. ### Adding Edge Functions[​](/concepts/custom-code/cloud-functions.md#adding-edge-functions "Direct link to Adding Edge Functions") Let's see how to add an Edge Function by building an example that generates an AI summary of product reviews. The Edge Function takes a list of reviews as input, sends them to an AI model, and returns a JSON response containing a summary and overall sentiment. This example demonstrates a key benefit of Edge Functions: securely storing API key on the Supabase backend instead of exposing it inside the app. Here's how it looks: **1. Create and Deploy Edge Functions** 1. Open the **Cloud Functions** section from the Navigation Menu. 2. Click the **+** button and select **Supabase Edge Function**. 3. Give the Edge Function a name. In this example, we use `getAIReviewsSummary`. 4. Configure the function settings. In this example, the function accepts a list of reviews as input and returns a JSON response containing the AI-generated summary and sentiment. You can also configure additional settings: * **Verify JWT**: Verifies the JWT token from the request header. Disable this if you want to allow unauthenticated access. * **Enable CORS**: Automatically adds CORS headers and preflight request handling. Required when calling the Edge Function from web apps. * **Return Value**: Defines the response type returned by the function. * **Define Parameters**: Configure the request body parameters accepted by the function. 5. Click the code-generation button to generate and copy the boilerplate code into the editor. Copy the boilerplate code and use any AI assistant to generate the implementation for your specific use case. For example, you can ask the AI assistant to complete the function for generating AI-powered review summaries using the Anthropic API. Review the generated implementation and verify it matches your expected logic and response format. 6. Paste the generated code back into the Edge Function editor. 7. Click **Save Edge Function**. 8. Once saved, click **Deploy**. 9. FlutterFlow will show a deployment dialog listing the available Supabase Edge Functions for deployment. 10. Click **Deploy** for the selected Edge Function to deploy it to your connected Supabase project. Optional - Add Package You can also add external Deno, npm, or JSR packages using the `Dependencies (deno.json)` tab if your Edge Function requires additional libraries or SDKs. For example, to add the `lodash` npm package: ``` { "imports": { "lodash": "npm:lodash@4.17.21" } } ``` You can then use it inside your Edge Function: ``` import _ from "lodash" ``` ![add-packge-edge-function](data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAFl0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAiYAAADMAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAFmVtZGF0EgAKChgl4ly2CBAQNCAyzCxMBALcg8lrGb6vlm3okOXopdaMxkgQ22X5aA5gKYlfBsxqAFi2NXeY2fbdv76lodLjDLMIvH+JA2b5/I3MUKETHaw1mxeCClwZE/ksGln3ANO+hItKnjs/HO9M8yUp5Cl+KJARL0hzczhtLk0V1pxjwGjhUiGgZIPh9cy7Q3cap7F2LI9Vj1jJK+/GWoOJj5mun881MHzl29fqA7DS6ckRZdOviIaWTa5b8rpnUjRe5kNn2wHs2IhoqgdSLZRPRqlbNlXbl8n3M4OSD6UHMEh5ZdWQuQOjGjBLktsQ/r6YJwFrpLC45gCpE8F6ErnHULs+siS3DZ+6MJxlnNGkj+ZGdjEDu5o+lK00Z3yS/Fa3TYHAPf3oDDnQjcprUdDF2qmwxa+b30KCvveSFdbRSosn2KuQiBlyWRXx0mStXAS4mWVltSAWD1BfHUsmvGigWoMnd/kIaKBMjz8u0QCusZx8jxxBk4meF0LUSbrehCQ1RnLOOqh+FaaETzV7yTzDzYX6esODppQ87kqPBGR91E+aB1ttyDBrvTaKEw56Z16ahq7DdvZpPsCUPP56njRTkDVz5IfM9l4IT2RGdJrY2Af8iX1JO8OGECP/981gTYPwHBxAqFHMzPxVOlWXmcWVEtj9ks7ONtgCf6JkWSeF2HmrZBX8qICxsldp+Wu56ZYFpmzgWR9w8xtCmlvnu5R0GjyShmrXwM6NCM2HmrwuQ7UgJhwHKQmyBKiBpPqPf1csJz+bumlQqj1NCVQxhxJuryM/eef7YkkHS+/FeRBj9NudHPthon+vFF4fd1W6KcN2EtsRuoAtRxNcuUIqgP64nIt3HLhQ2o4aLCM03U44k0cAzIvkh1Aj9HW6eAlyLkcMGUKNWFgKyyFWbY93iOjYmhWJxPR16FfpZ5mGDXhCiXCrKmlmY9dHJFuWd6hi8odxVJTj7LXRoQkEvYAE/+D237wkIlntTjQ1PTpioELt0WjKWh+ZZ17jKD2rksoZ5D204FNPyP+pxa29i8NIS71saJe+a8ZN7cL8MPTx4ps+dVtA8X7jCn6VnkxNVB1Ond/9jPHwzskg5W6RoEUy3FGgmbKVZ/7CLgTCbh9eW4Bx83bBHxFtRdodbCVF2WGCzGLVmCpys0p6uXdgZDiXCTc+/Dp71iOZaIyHWhXUv4WQ+bk7jTIShuDVP2NuQiwXxXOMjY+MflMEWqFEgEc1hwbL3IFUqveDk83Andw58i1v+lxsI3jct7QDChTPdKanB5Ki9paV5DBN6/ENkyWgF234BG1f5COAz7LoQHtKr47CDPgo+5gYyZVrmEVNueXF0PaXH49qTW5V8w0kPhRD0LK8FIqbTJ+I/2oi67P+NibmodWVX+/kITBSn+hGD+2qXxsWtVzHlrqsClDRi+ycSsdAjF5MRvl9xweW6e0aPRoQDxr4L4bbRhpeEu7P2Bl5cSqwkqiZgG3RJPsaaC1Ys3QfOGBTruVaxEiOlCzgNfEuRffBE7jbPVLtMa01zIoN0T5mEbotWJXg7KZ379/B+3wrlrr1gT4Cqw5LfM1TuWZKrRtiNs7iekrlzyv4e9rmKjL8XKGayi8bDU5orTt9NTJ56LHL2Btw7kwitaBLR21hcpp6c8W3BmuDe79udgMUHfjAIScO0ngn998ZTMl/xe/pm2bKuj/36WXfWyoq6aGHwHcKSakDKhhEiCGnTjVzjEYJjV4fXG0HsHeuzsdLj9Jfhz//MSNOfTPwPgyZTeQ7QhYj9DyEsUmnAczTfhykBfkYLwueENalgjwWNK1huaRoFJimZYQFEi3ANoDVV3P8zU3ZwI+l+ixbNABdckKd5ZUBC6XbG67HxHKIj+8UU4CM5O5loxAgJgA3KuC4tz+atHTlpce1uB3kILjmvg9Hue3SJMaelovvyM1wj9BIFy/eKmGZCBztkw0T0InuPt+CDvRvMdc+sHiAERQ4aqMervdW5D/Dpfc3hX+eKrxqpqeribpNbDWnvqlAQ7yqaxdsCvI88IPQ7sMWpWD49rCUTbE+43Wd+9JcchLxTVJgq/WhLusmz/xqBxU3y48d3fggjipS/QDsnqMBZbxcO+QFz0LWww60uMimH1gCaaZ/rXQ8v/RwGOSo/tMIE2/dMBjfA8eYVI1rAQ2kiLt8lmHh+aQTuK4/k3CY7uVAzTI1rZKl8zx+qOWmALSPdvcXQJ7UBMUatm/wjlpbW09gY2cET5EpgfmLhn6sU02ck4UrpoPh1TGgMBOhJm61FYfjSv0zrAK46Dznd2q8cLadlKDrYOjhjA1rJs0WWW5tWA42tGoYOB8tXGY/sH0GaBJKQf/yaQRdgwQSDTFekSNp14eEvUAUEwalC44aiMP3CAOpEfWzr+Z6UywBVgK6r5YMnJVnSFNOAq97t7rtP+DUo4fIApxuHI98rGrfKnxA37G1iEZtGzVM2oWnSa88PQWwFmSwhXBoYrSU4HypXegXoYNmsfm6xtkoG1iNQvPaUgNYwf45CmXYFPNNwLNtHFKCITmOHvit0CanFRi2+yUZTSP5okhd5aXjPCh489OpIMFORxkFSpdl7jcJKhTZoyZP5PED+HdOKo0AUma+kpcC/QX8cU2mnl37xC4sKP1QG+Ecp/k3i3dvRrU5kCIHwTMqaRJ11s1TD3v+TsRKOAJ8UU9CG+ZUtKYaHcG6zs8ViVfpVsBLyuyFlmMcxy9Vlrkuo+NBDTlPkoPGBEOsUbmtzBzZWS4B37Ai3xWR/ULH+AENIt6GCcrCWjhac/osXRvhELfqIKNL78PDHufWshtoM8ggle8sQ17Ng9ZJXWv6D64i2Vf0zb3lq2nihWjbC6R/oWin7ieI57VS9PVoimkZ8ZA5uFHThIDlT7oxkVnsXhkJhSKzYHUR0PcdocKekscyY5Vpv553djOl+7l39Zzhn6rF8crOjcz49HwFbMUYAA2PAGRMW8Ar3uFl4ZF6X8hy/LZm5DXqlpMIs8nstxdyJgDswz8Szgt2PcT6NH0wtSCb+1q0qTqRxeo0gtqnG2J7hJU/g9mCBwv1Pn8y9mKNTB9olJ5wd2JGc6/WIN5GdpHwIuO3Ip7ZzTpnYCrJzQzQkuiipMoLSFQLbH+7xPRcymdBr8gfEFy8k4rPBS7NpBlPqXxhpKs9ilqK5eEw7W/3c9LZxaKwAq5o98QaGihVQ/718MaiCMQn3JVZWNf/asSO+9CVuDoU6og9jbR+KJdGpCiMom3mQB5xpBP4i7xqpjrMsMJguw4Vr6Q70Te8tS/+a5ka/KzECgcZHKFqbaUhfuS5gXgxYWDUBWJc19VVoGJc9Za/2+LzQE7cUv3QOyknb5KKDBgJjKZCA2UY+ZCbiyw1CAhtliOvVC+Vpxj2y84kcO668f8sHEcMD7wxE/j7Oy0YpK1p62neiIoof7S26cRG9uT8eVNpxSTfAlEVmG2n8zwXcZSn/ryzZjwUrSf5cWHmILw+PhEkLtjDB8OGhOAn6zYzH7X5vKTHdBokxSA0ePcucVlstW5/y1fzN7UN4vsL30OIx6iVLnEk9DgoCUkje2CIMxq1QsSvRtk6DssG1XaKXav7f7y2RhHhweLAQMbbvCaUH6rF/2tt9N+zc6NKib7MNrFewDOFOz8jz14C3GZjoCPaNd+XOhldapUBiD3452bgd+qQlKvMTNEVwkUngEjhjhCCT7I43TAgV8Xn5J/ssurppQ5bK9wtYtSYouyIhUsOqPqKL5Rz6bAvTzWWyWuCNvQdKJR8bfGfKYAfdX3Fu2MKdk/1GZvOVCGqtW2P5c31fb2zWYxZkwT/WynV4Fl4goY9x1FpRCXmPtJXFWmOrxXXyxru4feIocVjLZ2BuvJylAp+k0oAD80b+QNmMb5ozNxnM3VWbaZ1xz71ToeZDAwQ4dY7lfZ2wTPhVvzHG2RYYda4lXma+Leo/Pz4uflQo7UPHe2tf253PSuCBKo7StFnU2UgHNAS8yGqG/XE2NPCWL3tEtZAPxN+MXbCzQEEiGoLH0gPCs2fqO34tPIfFou15HiFJKur8M88tunvP0c0uzbDQZbnHp+2OeaiE1sMKZbvYg/SNj8h+VQZ7ikaNfgHT0i6HwMoIgLI9UrQ379kK2EVFujMDJmypLH762vBTBW18wl5JQWW2T0bMo0/n/45RRS4ADMSZoZh+JE3oZRJGdh7cuE2NJbHrP5pjYzG2HAuZjvn0I4gCP/9oT2+JievnPgVbQmVCT2bU1EJdmOmiUHOx4uv/aUHwi52GmoPe3G2+HGCM1mIygapR7c+LWrZ5glkL3cyr9iAD0x8lUjLyhHPmWqzi873q15kiq4UQSxXB9Rz78Qrc4kRk9AJHfm5Sp271aoVek9saRtZtqEcX19h+Sd8ly+1NEp2K3uZiVQGGi0uEa6HCgu8Axufb/z3M0yY/yZG3aO1ut3kFAQH3pik/kdBJE1vw83vAO/SEeNCI+1LbK/i6JXDKk8WSbAEczV+AkCRS1anlIIHV73v5zeAgKNVjDf5xa+lX0k9KpnRJqytVoCKT1Bdgil46tbr9gNym/XAxJ54kGB4rnFnHYckI6EQexdw3Z1V1I9d9j9zmWxLhdqa7BTFooyqhw3+FUowiw75+HhzpRFd/GyO6EkleWnTPLINvY6n3Ibrx+XC5DjDKhUAdK15psS8uJXExb7RX8LHaagOiTJ2HQmo3ffiSBaqLzDsLUhZDYS0t3R2Y8g+yty6yZo78tMhyPO7Dlgd0T/6xTiA589kKrUMb5xyWsfCBfMomTjNQomNOqkfX1S1hVqDGEL/FHKJLohqEGstj0t1Dew3aofiuE4GTK0QUGJAmklUHnpamOz7bmRCDStFcTSrRrAmpDUewy1biz3Lapxdz2dYRvZ9FKtoHAha8dRGLEDwIVTV7uV/lIE+bgNNuwJFCReLaiUIuWELIlC8lYMYCZeal2w5PE7TBSsv/fQuw0A7/EHef6/kh9p0rkln4/f66yATSvVVE33pBdVaAMrKw3M1CD8v1tJY6Lo84u2LldCaxorcTHucmK68XDgMN8TLDmKopEYBP/TbtRuJSckog+CNUMSEqRsVo67iuMhLZS7kXBVLcfaARnCvrIGWN/U5TlUR6w0d/LEMjeQNWRI4K/5Newe53lpjIOP2nEhDTvipWhlUcPLJFuQ8RFo0LUFf+njwkDQO8W25njyPoAz4tYEBtts8l1+kmGNHXIqney3cNkFipxZiZKqZWMvY0lmtRXGS+UsMrmOtDd8Hr0AD2yDr5sc8kdE4rz85+AjDw443XSmnq8/BQWzMjFIkRoXk02Meqbbws3hMoX58AKicvYheZdvoBCOuKiH0L4UqQZLPo16b5qiMIZGIK5MTyttVu6V3gqujIpD3JkMe4LV29IIdLFIPuMaMpQiE69LMOiTkb2qmCN+jpaIoJwjr5IVbomABCPs1loNchvwwRozpUqSP7CrSOICeTZxVY3tRBi/IBCmD5TLRQIII8P3CR7oMrk+Bm5u9s7H//UVv8dbD267wKQ1i4zvUK4h1vbP7pY3TBG7bArkX9DjpyqHMUSjKwM2kbx5cvXozykUAwXR6Ssc9urRdauqn/Lkw1Kp4lMXKv3giOJDQ58v1SzleOfqG/bnWEhHEb2k/OTyxkig9cPDh6XuoKl8YmN2/drt5/eWQ+qhMfM/X+t6ssBoBCsMkeymLco8qmwOEJYL2+5mJs6MY0l9G6vL0TwqEqkAZzB+oFW/cxp1xQAK7f0smcSPw+uY6BpBl60HEgM+xDX0/46oxRr2L5nx/0bCrYI2slTw9w73jdy8wCxRGw7Q+VuBfCB2f/mS/L8LhlMZOpzJw7+sOtpzd4OEN9RZ1eTD2Q7D1DK+Ev+ydGrXZPggSs9AlW5TeAqyQnFFTGCi76EIRamMUe1w1mK6XNjeEdV9/tfSAFoKvBWMlKoJMD8tbh7SjMgDtt7EgfBKymniZCY0dG1yMJC/JyzYtDyASejMIFD/c+EChAetoaCmVX6Ghy1j65KqFPntt4Qj7PSBWPUuTQCYLZ4UEzI+AnBRy3iSJo8cYzbsJrkGkWo1rgaWwddIFLeNUF2hsP/gykCKr2pCz88jAthRwmzbqGU7luNzlz5SXzv/TwOCOa4XV6NPBrs+t9c9HxIzAjjwz+HUf9NEwYhlAejnErYkqXD4v0vWialOWaMcIdYUDPPt5SN/n/2jNPHEBni9xJBp91yFKG/0if8wWu9knNQ5XCSeC9+M6pLY65dY+T911x4VR/1cdIjXad2pCuf/BgseiwWblfN97lKLRVQjgOkE+J7Ps/gzqW5kJsuE2+Rc+7Acny6MjVmqXvtZBhaCsWZzG06AHT2YH4qChCeVrau5X+IQhBf1mdM4TR3X5WNgRj3c8vEIev84RkPPdhRGC2Q2xqwwkF9ttvCBNl9dAKeG3TVWgtQLBghfZzd1drykNcT0w8LWW7Tg8VF9nxFI2jZ9z9nIhtsTyY664W+tpB0XGdgDdVslUbwQ+rlhPitP7f8Vr6KPXwJWzdkR1CPwVDFqgU3AuapriGmO06hwG65zEu2u5Pz0/ASOvKPHGbFpdJFHQjrzU2dMioHLuTHByhRLBnFwAxpU07Z8Dw/Klm2dd6eR1PsySz6DEePe9vVQ+w3g3gs1LeEQ4AAz3avh3FaT6pO3e9FCkXAO7r7S3qI7Fey74Z7GFm79mE1xCJSsURMybscCXsXb/fCI21NCs7cx6NTjcXBN4SpJsbAeL+0PH/TJVye9ueE/qL2sYCkITH/s2CYK9hbxdaUFqKPKOBYcxPDi+4r47fXz3cGs49aApXI7m9F/xlofQA8v8lk1lgDRxjDHAbA8kITqD0jG18RU0k/rYs2+PAji6H3XCz0zer27FH0bmz1kLG70W17zSzHgz/QQEsfj7k5ZJe/8jK5dv9eKW6h3d59PA5G74I9paT+ChnFhl+GbCZaAqn6rKViT4OPXfbecPSzeQEmNFNV8hjptxb8EVAgeuU3UTI1LJ+BLK1RKwoSYEUvUCFRgOicnFbQZahZHMdOaLjcOSJjxCw6IPaHFS12SWkHJOLRKYqZcg94DcXoUZkswX6E+zfWKhswkB7TgUO3vPZxmDIsrw2GDQSUoVBy8GrIOj4CIablc4muykjensSahKdxmSEK6oUEO+oMwmIdHfiqK00dOXYI5zZy0bjlWMH3sG00INef/QUsR+CWXWET6PR+5gB82IkJZSQ3KwAZboMQVm/ipeI7QHQ3YYxzbYO4155yxMk/GAah4rGFYFTVvy1pZbTnNvt1ZNGF1ItFIF5GFtIXKavZCYarLZz2ZFY9pp1pnkURwQiXrYUf6PcOCBfBdl6iMLKU/opDr/cFcLEY1sxD6EE+XyL7L6tRBGB361pNNOFR8ZxNeyYg+F9DNEm2AktQZWmCQcqGSIaJ6lX746gGtK9dDpVuUzkWNHHnNBGGQwVSqqnh6ZoUxZPzjChYSISQFrxOBp2LTCF/UElzF0u/kr14rPrVXwPm4E611AqhdxGbY9is4NR/VCA6nKHbMJv+KJH7+JoWj1dTnLEOc1oq4gQMyHjP2PbLaeVRAb7cucQCYJgpkkUSpY2aGtvWBVRO4gbXTssP77f1baTLlOSF2aM1IX3kHIwqsncA==) **2. Handling Secrets** When your Edge Function needs credentials, API keys, or tokens, do not hardcode them in the function code. Store them as Supabase Edge Function secrets so they remain encrypted and are not exposed in your app or repository. To add a secret, open your Supabase project, go to **Edge Functions > Secrets**, enter the secret name and value, then click **Save**. For example, you can add an any API key as: ``` ANTHROPIC_KEY=your-api-key ``` Then reference it inside your Edge Function using `Deno.env.get()`: ``` const apiKey = Deno.env.get("ANTHROPIC_KEY") if (!apiKey) { return new Response("Missing ANTHROPIC_KEY", { status: 500, headers: { ...corsHeaders, "Content-Type": "text/plain" }, }) } ``` ![edge-functions-handle-secrets.avif](/assets/images/edge-functions-handle-secrets-e43cc17dc005248fcc90a730698971e8.avif) This keeps sensitive values on the Supabase backend while allowing your function to securely use them at runtime. **3. Trigger Edge Functions and Use Result** Once the Edge Function is deployed, you can trigger it from an action in your app. For example, on a button tap or page load, add the **Edge Function** action and select the function you created. Pass the required input parameters, such as the list of reviews. ![trigger-edge-function](/assets/images/trigger-edge-function-07ee10274b9bb6cebace4c8584e60e7e.avif) If the function returns a value, provide an **Action Output Variable Name** while configuring the action. You can then use the returned data from: **Set from Variable > Action Outputs > \[Action Output Variable Name]** For this example, you can use the returned JSON values, such as `summary` and `sentiment`, to update page state variables or display the AI-generated review summary directly in your UI. ![use-edge-function-result](/assets/images/use-edge-function-result-89af9a939949c872bda0df3eca8c2716.avif) --- # Code File FlutterFlow allows you to add your own custom Dart files with [classes](https://dart.dev/language/classes) and [enums](https://dart.dev/language/enums). This means you can create reusable building blocks to manage your app’s data and logic more easily. Using custom classes, you can create custom data types, use their properties in the UI, call methods in action flows, and much more. ## Key Use Cases[​](/concepts/custom-code/code-file.md#key-use-cases "Direct link to Key Use Cases") * **Custom Models**: Define your own data models, such as `UserProfile`, `Product`, or `Order`, and use them throughout your app. * **Business Logic**: Add reusable utility methods like tax calculations, formatting, or conditional evaluations. * **Reusable Enums**: Define enums and use them in UI conditions and dropdowns. Limitations * **No Generics:** Classes with generic types (e.g., `class ApiResponse {}`) are currently not supported. * **No Function-Typed Parameters:** Methods or fields that have function types as parameters or fields are ignored (e.g., void Function(int) onTap). * **No Extensions:** Dart Extensions (e.g., `extension StringX on String { … }`) are not supported yet. ## Create Custom Class[​](/concepts/custom-code/code-file.md#create-custom-class "Direct link to Create Custom Class") To add a custom class, go to the **Custom Code** from the left navigation menu, click **plus (+)** button, and select **Code File**. Set the name of the file, add your code, and hit the **Save** button. Now, you must **validate** your code in the editor to catch basic syntax errors. If there are no errors, click the **Parse** button. FlutterFlow will scan your code and automatically detect supported classes and enums. Here’s an example of adding a `Review` custom class: Here's the code snippet of the `Review` custom class: ``` class Review { String id; String productId; String userId; String userName; String comment; double rating; // out of 5 ReviewStatus reviewStatus; DateTime date; int helpfulCount = 0; Review( this.id, this.productId, this.userId, this.userName, this.comment, this.rating, this.reviewStatus, this.date, ); // Method: Get a short version of the comment String shortComment() { if (comment.length <= 50) return comment; return comment.substring(0, 47) + "..."; } // Method: Get formatted date as string (e.g., "2024-05-22") String formattedDate() { return "${date.year}-${_twoDigits(date.month)}-${_twoDigits(date.day)}"; } String _twoDigits(int n) { return n >= 10 ? "$n" : "0$n"; } // Method: Check if review is positive (4 stars or more) bool isPositive() { return rating >= 4.0; } // Method: Check if review is recent (within last 30 days) bool isRecent() { final now = DateTime.now(); return now.difference(date).inDays <= 30; } // Method: Mark this review as helpful void markHelpful() { helpfulCount += 1; } } ``` tip You can also include import statements and access generated classes within your custom class files. For more details, [**see the examples**](/concepts/custom-code/common-examples.md) on how to access generated classes. ## Create Custom Class Instance[​](/concepts/custom-code/code-file.md#create-custom-class-instance "Direct link to Create Custom Class Instance") You need to create an instance of a class so you can work with actual data and use the class’s properties and methods in your app. Here’s a simple explanation: * A **class** is like a blueprint or template. For example, the `Review` class describes what a review is, but doesn’t hold any real review information itself. * An **instance** (or “object”) is a real, usable item made from that blueprint. See the code snippet below: ``` Review review1 = Review( 'r001', 'p123', 'u456', 'Alex Morgan', 'Great quality T-shirt!', 4.5, DateTime(2025, 5, 22), 3, ReviewStatus.approved, ); ``` * In FlutterFlow, you will store the instance of the custom class in the [state variables](/concepts/state-management.md#state-variables) of your app, page, or component. * You can create multiple instances of the same class, reusing the same structure multiple times, each with different review data. When you create an instance of a class, you can: * Store actual review details. * Access and update the fields (e.g., `review1.rating` or `review1.comment`). * Call methods that do something with that data (e.g., `review1.markHelpful()` or `review1.shortComment()`). To create an instance of a custom class, first you need to [create a state variable](/concepts/state-management.md#creating-state-variables) (of type Custom Class) that will hold the instance. Then, to create and add the instance to the state variable, open the **Set from Variable** dialog and select **Create Custom Class Instance**. Choose the class you want to use, then select the class name from the **Constructor** dropdown. After that, set values for each of the required fields. ## Using Custom Class[​](/concepts/custom-code/code-file.md#using-custom-class "Direct link to Using Custom Class") Once the custom class is added successfully, you can access its fields and methods in the Variable Dialog, call its methods in the Action Flow Editor, assign instances to state variables, pass them to page or component parameters, and use enum values in dropdowns or conditionals. ### Custom Class as Data Type[​](/concepts/custom-code/code-file.md#custom-class-as-data-type "Direct link to Custom Class as Data Type") You can select your custom class as a Type for variables, state, or parameters, just like a [Custom Data Type](/resources/data-representation/custom-data-types.md). ![custom-class-as-data-type.avif](/assets/images/custom-class-as-data-type-ae8e906e74a17fc8e9cbaff4f9e296e7.avif) ### Access Fields and Methods[​](/concepts/custom-code/code-file.md#access-fields-and-methods "Direct link to Access Fields and Methods") You can use custom class fields to display values directly in the UI, and call its methods in variable dialogs to return a result. ![access-fields-methods.avif](/assets/images/access-fields-methods-080e4deaf0c376847b91845fb82c62ae.avif) ### Set Field \[Action][​](/concepts/custom-code/code-file.md#set-field-action "Direct link to Set Field \[Action]") Use the **Set Field** action to update a specific property of a custom class instance. For example, you can set `review.comment = 'Great fit and quality!'` when a user updates the review, allowing the UI to reflect the new comment instantly. ### Call Method \[Action][​](/concepts/custom-code/code-file.md#call-method-action "Direct link to Call Method \[Action]") Use the **Call Method** action to invoke a method defined in your custom class. For instance, if your `Comment` class has a `markHelpful()` method, you can trigger it when a user taps a “Helpful” button to record the interaction. ## Using Static Members[​](/concepts/custom-code/code-file.md#using-static-members "Direct link to Using Static Members") Sometimes, you may want to define fields and methods that are shared across your app. In such cases, `static` fields and methods are ideal. Because they're tied to the class rather than an instance, static members are accessible globally, for example, utilities for formatting, calculations, or global configuration. This approach is typically used for **stateless utility classes** where shared functionality is needed across the app. For example, look at the class below: ``` class Utils { static int square(int x) => x * x; } ``` The `Utils` class contains a static method `square` that returns the square of a number without needing to create an object of the class. Here are couple more examples to understand it better: * This `StringFormatter` class below provides reusable static methods to capitalize text, convert it to lowercase, or format it in snake\_case. ``` class StringFormatter { static String lastFormatted = ''; static int formatCount = 0; static String capitalize(String input) => input[0].toUpperCase() + input.substring(1); static String toLowerCase(String input) => input.toLowerCase(); static String toSnakeCase(String input) => input.replaceAll(' ', '_').toLowerCase(); } ``` * The `MathHelper` class offers handy static methods to calculate tax, apply discounts, find percentages, and round off numbers. ``` class MathHelper { static double calculateTax(double amount) => amount * 0.18; static double applyDiscount(double amount, double discountPercent) => amount - (amount * discountPercent / 100); static double calculatePercentage(double part, double total) => (part / total) * 100; static int roundOff(double value) => value.round(); } ``` tip You can mix both **static** and **instance** members in a single class. Static members are shared across all instances, while instance members hold data specific to each object. For example, look at the class below: ``` class Review { static List flaggedWords = ['bad', 'spam', 'fake']; String id; String userId; String comment; int helpfulCount = 0; Review( this.id, this.userId, this.comment ); static bool isCommentAppropriate(String input) { return !flaggedWords.any((word) => input.toLowerCase().contains(word)); } void markHelpful() { helpfulCount += 1; } } ``` * `flaggedWords` is a static list used across all reviews. * `isCommentAppropriate()` is a static method that can be used without creating a `Review` instance, useful for validating comments before saving them. warning Using static members are powerful, but they should be used carefully. Overusing static methods can lead to less flexible code and potential issues, especially when the logic requires access to state or needs to evolve over time. Stick to static methods only when the logic is truly independent and doesn’t rely on instance-specific data. ### Access Static Fields and Methods[​](/concepts/custom-code/code-file.md#access-static-fields-and-methods "Direct link to Access Static Fields and Methods") You can access the static class data and methods directly via the ****Set from Variable**** menu. ![static-class-methods.avif](/assets/images/static-class-methods-d338fb2df9b78de2bf5c63a570edf8ce.avif) ### Set Static Field \[Action][​](/concepts/custom-code/code-file.md#set-static-field-action "Direct link to Set Static Field \[Action]") Use the **Set Static Field** action to update a static field on a custom class. For example, if you have a class `MathHelper` with a static field `amount`, you can set it using an input value when a user enters a price. This allows you to store that value globally and use it across different calculations. ### Call Static Method \[Action][​](/concepts/custom-code/code-file.md#call-static-method-action "Direct link to Call Static Method \[Action]") Use the **Call Static Method** action to run a static method of your class. For instance, you can call `MathHelper.calculateTax(amount)` to compute tax on a given amount during a checkout action, without needing to create an instance of the class. ## Custom Enums[​](/concepts/custom-code/code-file.md#custom-enums "Direct link to Custom Enums") Similar to how you add a custom class, you can also add Custom Enums in your app. [Enums](/resources/data-representation/enums.md) are a great way to define a fixed set of values, such as user roles, order statuses, or content types. Once parsed, these enums become available throughout your app and can be used in dropdowns, conditionals, and UI bindings. For example, you could define an enum called `ReviewStatus` with values like `pending`, `approved`, and `rejected`. Here's the code snippet for it: ``` enum ReviewStatus { pending, approved, rejected, } ``` ![custom-enums.avif](/assets/images/custom-enums-ddb46d1db418b8cc6c52f3a58b86d75d.avif) You can access the custom enums from **Set from Variable** menu > **Custom Enum** section. You’ll see your Dart file listed by name. Select the enum you want to use, such as `ReviewStatus`, and then choose the specific value you want to assign. ## Tips & Best Practices[​](/concepts/custom-code/code-file.md#tips--best-practices "Direct link to Tips & Best Practices") * Keep your custom class files modular and focused; ideally one class per file for better organization and reusability. * Avoid advanced Dart features that are not supported by FlutterFlow’s parser, such as generics or function-typed fields. * Re-parse your code after making changes to ensure FlutterFlow updates the parsed structure correctly. * Document your code with comments to make your custom classes easier to understand and maintain over time. ## FAQs[​](/concepts/custom-code/code-file.md#faqs "Direct link to FAQs") Can I add Custom Classes (Code Files) in a Library Project? Yes, you can. When a Library Project is imported, any custom code files you’ve defined will be parsed, and the resulting classes will be available for use in the consuming project. --- # Common Code Examples The custom code feature in FlutterFlow allows you to extend functionality by accessing generated classes and modifying global variables like App States and FlutterFlow themes. This guide covers common scenarios where you can leverage custom code to enhance your project by working directly with data models and other resources within your code. Disclaimer Custom Functions cannot import new files or packages outside of the default dedicated imports. Therefore, most of the suggestions below that involve adding a new import will not work in Custom Functions due to this restriction. However, they will work for Custom Widgets and Custom Actions. For example, a new [**Custom Function**](/concepts/custom-code/custom-functions.md) typically includes the following packages and files. Your custom function code changes should use only these packages & files: ``` import 'dart:convert'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:timeago/timeago.dart' as timeago; import 'lat_lng.dart'; import 'place.dart'; import 'uploaded_file.dart'; import '/backend/backend.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import '/backend/schema/structs/index.dart'; import '/backend/schema/enums/enums.dart'; import '/auth/firebase_auth/auth_util.dart'; ``` ### Access FlutterFlow Generated Classes[​](/concepts/custom-code/common-examples.md#access-flutterflow-generated-classes "Direct link to Access FlutterFlow Generated Classes") FlutterFlow generates a complete Flutter codebase for you as you build apps in its platform. Part of this code includes custom classes that are designed to streamline common tasks and encapsulate reusable properties or logic. For example: * **Button Widgets:** FlutterFlow provides custom button classes like `FFButton` that come with built-in styling and behaviors. * **Google Places:** The `FFPlace` class encapsulates properties of a Google Place, such as name, address, and coordinates. * **File Uploads:** The `FFUploadedFile` class represents files uploaded to your app, encapsulating properties like the file name, bytes, and URL. What is a Class? In programming, a class is a blueprint for creating objects. It defines properties (data) and methods (functions) that belong to objects of that type. For example, * A `Car` class might have properties like `color` and `speed` and methods like `drive()` and `stop()`. * In FlutterFlow, a class like `FFPlace` might have properties like `address` and `latLng`, and methods to manipulate or retrieve these values. These custom FlutterFlow classes in the generated code are mostly prefixed with `FF` or `FlutterFlow`. If you need to access these classes in your custom code, simply type "FF" or "FlutterFlow" in the code editor to locate them quick. ![suggestions-dropdown.png](/assets/images/suggestions-dropdown-7cdcb2e99a811ac6ad11b2e94aae4cf1.png) ### Leveraging Components in Custom Widget[​](/concepts/custom-code/common-examples.md#leveraging-components-in-custom-widget "Direct link to Leveraging Components in Custom Widget") Static Components vs Dynamic Use this approach only when the component is a fixed element that does not change across different use cases. If the child component needs to change based on user choices, pass it directly [**as a parameter**](/concepts/custom-code/custom-widgets.md#creating-a-new-custom-widget). In a **[Custom Widget](/concepts/custom-code/custom-widgets.md)**, you can integrate a previously built **[FlutterFlow Component](/resources/ui/components.md)** directly, saving you from recreating child content in code. For example, if you’re building a Custom Widget to display custom dialog boxes or bottom sheets using a package from [pub.dev](https://pub.dev/), you can simply return an existing Component created on the canvas, rather than coding a new one from scratch. Imports When referencing a Component class in your code, FlutterFlow will automatically add the necessary import statement. ![return-widget-custom-code.png](/assets/images/return-widget-custom-code-6c3678bc441c99b54929ff3c4028580e.png) ### Get FlutterFlow Theme in Custom Widget[​](/concepts/custom-code/common-examples.md#get-flutterflow-theme-in-custom-widget "Direct link to Get FlutterFlow Theme in Custom Widget") When building custom widgets, you often need to style parts of the widget, such as setting colors. Instead of using hardcoded color values, you can directly access the **FlutterFlow Theme**. This theme provides consistent styling across your app and reflects colors set by you or your project developer. To access theme colors in your custom widget, use the `FlutterFlowTheme.of(context)` method. This allows you to retrieve any theme property, such as the default `primary`, `primaryBackground`, or other custom-created colors, as well as text styles like `bodyLarge` or `bodyMedium`, ensuring that your custom widget aligns with the app’s overall theme. Here’s an example of how to use the primary color from FlutterFlow Theme in a custom widget: Imports Ensure you import `import '../flutter_flow/flutter_flow_theme.dart';` when accessing `FlutterFlowTheme` in your custom widgets. ``` class CustomButton extends StatefulWidget { final String label; CustomButton({required this.label}); @override _CustomButtonState createState() => _CustomButtonState(); } class _CustomButtonState extends State { bool isPressed = false; void toggleButton() { setState(() { isPressed = !isPressed; }); } @override Widget build(BuildContext context) { return ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isPressed ? FlutterFlowTheme.of(context).primary // Primary color when pressed : FlutterFlowTheme.of(context).secondaryBackground, // Default color foregroundColor: FlutterFlowTheme.of(context).secondaryText, // Text color ), onPressed: toggleButton, child: Text( widget.label, style: FlutterFlowTheme.of(context).bodyText1, // Themed text style ), ); } } ``` ### Modifying AppState from Custom Code[​](/concepts/custom-code/common-examples.md#modifying-appstate-from-custom-code "Direct link to Modifying AppState from Custom Code") In FlutterFlow, you can access or update AppState directly from the Action Flow Editor. However, certain scenarios may require you to access or modify AppState within custom code for more control over the operation flow. The `FFAppState` class also provides additional helper functions to modify AppState values. Let’s look at some examples: Imports Ensure you import `import '../../flutter_flow/flutter_flow_util.dart';` when accessing `FFAppState` in custom code resources. * **Get AppState value in Custom Code** ``` Future getCartItems() async { // Retrieve the current cart items from AppState final currentCartItems = FFAppState().cartItems; print('Current Cart Items: $currentCartItems'); } ``` * **Updating AppState Values in Custom Code** ``` Future enableDarkMode() async { // Enable dark mode in AppState FFAppState().update(() { FFAppState().enableDarkMode = true; }); print('Dark mode enabled'); } ``` * **Modifying a List in AppState Using Helper Functions** The `FFAppState` class offers a variety of helper functions to easily manage list variables in AppState. For a detailed overview of this generated class, check out **[this guide](/generated-code/ff-app-state.md#managing-appstatelist)**. Here are some examples of how to use these helper functions to modify an AppState list variable: ``` Future addLocation(LatLng value) async { // Add a new location to the LatLng list FFAppState().addToLatLngList(value); } Future removeLocation(LatLng value) async { // Remove a specific location from the LatLng list FFAppState().removeFromLatLngList(value); } Future removeLocationAtIndex(int index) async { // Remove a location at a specific index from the LatLng list FFAppState().removeAtIndexFromLatLngList(index); } Future updateLocationAtIndex(int index, LatLng Function(LatLng) updateFn) async { // Update a location at a specific index in the LatLng list FFAppState().updateLatLngListAtIndex(index, updateFn); } Future insertLocationAtIndex(int index, LatLng value) async { // Insert a new location at a specific index in the LatLng list FFAppState().insertAtIndexInLatLngList(index, value); } ``` ### Leverage Custom Data Types[​](/concepts/custom-code/common-examples.md#leverage-custom-data-types "Direct link to Leverage Custom Data Types") When you create a custom data type in FlutterFlow, it **[generates a corresponding `Struct` class](/generated-code/custom-data-types.md)**. In FlutterFlow's custom code, you can create new instances of such data types, pass instances back into an action, or manipulate and retrieve information from existing objects. Here are some examples to help illustrate working with an example `ProductStruct` class. #### Example 1: Creating a new Instance of `ProductStruct`[​](/concepts/custom-code/common-examples.md#example-1-creating-a-new-instance-of-productstruct "Direct link to example-1-creating-a-new-instance-of-productstruct") To create a new `ProductStruct` instance, initialize it with the required properties: ``` // Create a new instance of ProductStruct final newProduct = ProductStruct( productId: '123', name: 'Example Product', description: 'A sample product description.', category: 'Electronics', subCategory: 'Mobile Phones', price: PriceStruct(amount: 299.99, currency: 'USD'), sizes: ['Small', 'Medium', 'Large'], colors: [ColorsStruct(colorName: 'Red', colorHex: '#FF0000')], images: [ImagesStruct(thumbnail: 'https://example.com/image.jpg')], stockStatus: StockStatusStruct(xs: 0, small: 2), reviews: [ReviewsStruct(rating: 4, comment: 'Great product!')], ); ``` #### Example 2: Get Properties of an Existing `ProductStruct` object[​](/concepts/custom-code/common-examples.md#example-2-get-properties-of-an-existing-productstruct-object "Direct link to example-2-get-properties-of-an-existing-productstruct-object") If you have an existing `ProductStruct` object (e.g., retrieved from a list of products), you can access its properties or return specific values back to the calling Action. Let's assume you have an Action that calls a Custom Action to retrieve a field value from the provided `ProductStruct` object. * **Returning a Single Field from ProductStruct** This function retrieves and returns the product's name. The return type is `String?` to account for the possibility of a null value. ``` // Function to return the product name from a ProductStruct instance String? getProductName(ProductStruct product) { // Get and return the product name return product.name; } ``` * **Checking if a Field Exists in a `ProductStruct` Object** This function determines whether the `ProductStruct` object contains a non-null value for a specific field, such as `description`. It returns `true` if the field exists and is not null, and `false` otherwise. ``` // Function to check if the description field exists in a ProductStruct instance bool hasDescription(ProductStruct product) { // Return true if the description is not null, false otherwise return product.description != null; } ``` * **Returning a List of Review Comments from ProductStruct** This function retrieves a list of review comments from the reviews field in the `ProductStruct`. The return type is `List` as it returns a list of comments (or an empty list if there are no reviews). ``` // Function to return a list of review comments from a ProductStruct instance List getProductReviewComments(ProductStruct product) { // Check if reviews are present and return a list of review comments return product.reviews?.map((review) => review.comment ?? '').toList() ?? []; } ``` #### Example 3: Modifying Properties of an Existing `ProductStruct` Object[​](/concepts/custom-code/common-examples.md#example-3-modifying-properties-of-an-existing-productstruct-object "Direct link to example-3-modifying-properties-of-an-existing-productstruct-object") You can also modify the properties of an existing `ProductStruct` object. This can be helpful if you want to update a field before saving the data back to Firebase or passing it into an action. * **Simple Property Modification** In this example, we’ll modify a single property, like `productName`, of an existing `ProductStruct` object. This example is straightforward and demonstrates how to update a basic field in the object. ``` // Function to update the product name of a ProductStruct instance Future updateProductName(ProductStruct product, String newProductName) { // Update the product name with the new value product.productName = newProductName; } ``` * **Complex Property Modification - Nested Object Update** In this more complex example, we’ll modify a nested property within the `ProductStruct`, such as updating the price (which itself is a `PriceStruct` object). This shows how to update a property that itself contains multiple fields. ``` // Function to update the price of a ProductStruct instance Future updateProductPrice(ProductStruct product, double newAmount, String currency) { // Check if price is not null if (product.price != null) { // Update only the amount field product.price!.amount = newAmount; } else { // If price is null, optionally initialize it if needed product.price = PriceStruct( amount: newAmount, currency: currency, ); } } ``` * **Complex Property Modification - Updating a List Property** In this example, we’ll add new items to a list property, like adding new review comments to the `reviews` list in `ProductStruct`. This example shows how to work with a list of nested objects. ``` Future addNewReviews(ProductStruct product) { product.reviews ??= []; // Initialize the reviews list if it's null product.reviews!.addAll([ ReviewStruct(rating: 5, comment: 'Excellent product!'), ReviewStruct(rating: 4, comment: 'Good quality, but a bit expensive.'), ReviewStruct(rating: 3, comment: 'Satisfactory, meets expectations.'), ]); } ``` or if the new list of reviews is being provided to the Custom Action, then: ``` Future addDynamicReviews(ProductStruct product, List newReviews) { product.reviews ??= []; // Initialize the reviews list if it's null product.reviews!.addAll(newReviews); // Add the new reviews } ``` ### Using Firebase Auth Variables in Custom Code[​](/concepts/custom-code/common-examples.md#using-firebase-auth-variables-in-custom-code "Direct link to Using Firebase Auth Variables in Custom Code") When using Firebase Authentication for your app, FlutterFlow provides access to key authentication data, such as `currentUserDisplayName`, `currentUserUid`, and more. These variables can be used in your Custom Actions to build additional features that require such common data from authenticated users. For example, you can check if a user’s email is verified before proceeding with certain actions: ``` if (currentUserEmailVerified) { // Perform action for verified users } ``` Or, if you need to create a directory path that includes the user’s unique ID: ``` String directoryPath = '/users/' + currentUserUid + '/files'; ``` Here’s a list of other Firebase Auth variables that can be referenced in Custom Code: * `currentUserEmail` – The email address of the current user. * `currentUserUid` – The unique ID of the current user. * `currentUserDisplayName` – The display name set by the user. * `currentUserPhoto` – The profile photo URL of the current user. * `currentPhoneNumber` – The user’s phone number, if available. * `currentJwtToken` – The current user’s JWT token for secure requests. * `currentUserEmailVerified` – Boolean indicating if the user’s email is verified. * These variables make it easy to integrate Firebase Auth data into custom functionality, enhancing the user experience. ### Get Dev Environment Values in Custom Code[​](/concepts/custom-code/common-examples.md#get-dev-environment-values-in-custom-code "Direct link to Get Dev Environment Values in Custom Code") Similar to `FFAppState`, FlutterFlow generates a singleton `FFDevEnvironmentValues` class in your FlutterFlow generated codebase, if you are using **[Dev Environments](/testing/dev-environments.md)**. This class can also be accessed from custom code if needed. It is generated based on the environment selected by the user at the time of code generation. To access any Dev Environment values in custom code, simply use: ``` Future getWebhookId() async { // Add your function code here! return FFDevEnvironmentValues().webhookId; } ``` ### Access Library Components in Custom Code[​](/concepts/custom-code/common-examples.md#access-library-components-in-custom-code "Direct link to Access Library Components in Custom Code") When using a library dependency in your project, you can also access its components, such as Library App State, Library Values, and Library Widgets, in the user project's custom code. Here are a few examples: #### Get Library Values[​](/concepts/custom-code/common-examples.md#get-library-values "Direct link to Get Library Values") Similar to `FFAppState` or `FFDevEnvironmentValues` class, FlutterFlow generates a singleton `FFLibraryValues` class for library projects, which provides direct access to **[Library Values](/resources/projects/libraries.md#library-values)**. To access Library Values directly in custom code: ``` Future getSchema(StateStruct? syncStatus) async { print(FFLibraryValues().schema); } ``` #### Get Library Custom Code[​](/concepts/custom-code/common-examples.md#get-library-custom-code "Direct link to Get Library Custom Code") When you add a library dependency to your FlutterFlow project, FlutterFlow automatically includes necessary imports, allowing you to utilize custom code resources from the library project in your user project's custom code files. For example, if you have a library with project ID `library_hybw3o`, FlutterFlow will add the following import to your project: ``` import 'package:library_hybw3o/flutter_flow/custom_functions.dart' as library_hybw3o_functions; ``` Now, let's use the library’s custom functions in the user project's custom function: ``` int getRandomIndex(List indexList) { final item = library_hybw3o_functions.getRandomItem(); // Library's custom function // get Random Index final randomNumber = math.Random(); return ... } ``` #### Manually Add Library Imports[​](/concepts/custom-code/common-examples.md#manually-add-library-imports "Direct link to Manually Add Library Imports") If the library import doesn’t appear in your project automatically, you can manually add it and assign a custom alias. For example, to import a library’s custom actions into your project’s Custom Widget resource, add the import yourself as shown below: For example, let's import the library's custom actions into the user project's Custom Widget resource. If the import is not already available, you can add it manually as follows: ``` // Custom import import 'package:library_hybw3o/custom_code/actions/index.dart' as library_hybw3o_actions; // Assigning a custom alias to the import // Example Widget code class CustomDialog extends StatefulWidget { const CustomDialog({ super.key, this.width, this.height, }); final double? width; final double? height; @override State createState() => _CustomDialogState(); } class _CustomDialogState extends State { @override void initState() { library_hybw3o_actions.getSchema(StateStruct()); // calling library custom action super.initState(); } @override Widget build(BuildContext context) { return Container(height: 50, width: 50); } } ``` --- # Configuration Files FlutterFlow allows you to modify configuration files for your app, and platform-specific files, without leaving the FlutterFlow interface. In some cases, you’ll need to tweak the configuration files that FlutterFlow generates. This is usually required when integrating third-party packages such as analytics, ad networks, and payment solutions. Here are the key configuration files you can edit: * [**`AndroidManifest.xml`**](/concepts/custom-code/configuration-files.md#androidmanifestxml-android) – Configures app permissions, metadata, and intent filters for Android. * [**`build.gradle`**](/concepts/custom-code/configuration-files.md#buildgradle-android) – Defines Android specific build configurations such as compile SDK version, dependencies, build types, and signing configurations. * [**ProGuard files**](/concepts/custom-code/configuration-files.md#proguard-file-android) – Used for code shrinking and obfuscation in Android builds. * [**`Info.plist`**](/concepts/custom-code/configuration-files.md#infoplist-ios)– Manages iOS app settings, including permissions and configurations. * [**`Entitlements.plist`**](/concepts/custom-code/configuration-files.md#entitlementsplist-ios) – Defines iOS app privileges such as push notifications and Apple Pay. * [**`AppDelegate.swift`**](/concepts/custom-code/configuration-files.md#appdelegateswift-ios) – Manages iOS app launch behavior and runtime configuration. It registers Flutter plugins, initializes services like Firebase, and handles app lifecycle events and deep linking. * [**`main.dart`**](/concepts/custom-code/configuration-files.md#maindart-flutter) – The entry point of your Flutter app, where you can modify app-level logic. warning While editing configuration files can unlock advanced functionality, it comes with risks. A small mistake (e.g., a missing XML tag or a wrong key) can cause your app to fail compilation or crash at runtime. Incorrect changes might lead to App Store/Play Store rejections. So, it’s important to note your changes and thoroughly test your app after each edit. In short, edit native code only when necessary, and do so carefully. ## Editing Files[​](/concepts/custom-code/configuration-files.md#editing-files "Direct link to Editing Files") FlutterFlow provides two main ways to modify native files: [**Add Individual Snippets**](/concepts/custom-code/configuration-files.md#option-1-add-individual-snippets) and [**Manual Edit Mode**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). ### Option 1: Add Individual Snippets[​](/concepts/custom-code/configuration-files.md#option-1-add-individual-snippets "Direct link to Option 1: Add Individual Snippets") **Snippets** are small pieces of code that you can inject into the native files at predefined locations. Instead of opening the whole file to edit, you provide just the fragment you want to add, and FlutterFlow merges it into the file in the correct place. This is safer and easier for small additions such as a permission line or a meta-data tag. #### Snippet Placement for Android[​](/concepts/custom-code/configuration-files.md#snippet-placement-for-android "Direct link to Snippet Placement for Android") Let’s see how to add a snippet for the `AndroidManifest.xml` file, where you can add the following tags: * **Activity Tags:** Inserts XML code inside the `MainActivity` block. This is typically used to add child XML elements within the MainActivity, such as `` or `` to control aspects such as deep linking, theme application, or launch mode. * **Application Tags**: Used to inject properties or attributes directly on the `` tag itself. For example, you can use this to set values such as `android:icon`, `android:label`, `android:allowBackup`. * **App Component Tags**: Inserts complete XML components inside the `...` block. Use this to add additional activities, services, broadcast receivers, or content providers that your app depends on. To add a snippet to your `AndroidManifest.xml`, navigate to **Custom Code** from the left navigation menu, select **Configuration Files**, then choose `AndroidManifest.xml`. Click the **plus (+)** button next to the tag where you want to insert the snippet. Provide a name (this will be included as a comment in the file) and paste your snippet code. #### Snippet Placement for iOS[​](/concepts/custom-code/configuration-files.md#snippet-placement-for-ios "Direct link to Snippet Placement for iOS") For iOS, let’s see how to add a snippet for the `Info.plist` and `Entitlements.plist` files. There’s no nested application/activity structure like on Android. Instead, both files are dictionaries of key-value pairs. When you add a snippet, it’s placed directly under the root `` element of these plist files. To add a snippet to native iOS files, navigate to **Custom Code** (from the left-side menu) > **Configuration Files**, and select the desired file. Click the **plus** (+) button, provide a descriptive name (which will appear as a comment in the file), and paste your snippet code. tip * Snippet insertion isn't available for `main.dart`. Instead, you can directly modify the file using [**Manual Edit Mode**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). * You can also use your Development [**Environment Values**](/testing/dev-environments.md#environment-values) and [**Library Values**](/resources/projects/libraries.md#library-values) inside snippets. For more details, refer to the [**Include Variables in Native Code**](/concepts/custom-code/configuration-files.md#include-variables-in-native-code) section. ### Option 2: Manual Edit Mode[​](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode "Direct link to Option 2: Manual Edit Mode") For more complex changes, you can enable **Manual Edit Mode**, which unlocks the entire file for free-form editing. This is like opening the raw file in a text editor directly within FlutterFlow. **Note that** the manual mode is powerful but should be used carefully. To manually edit native files, navigate to **Custom Code** (from the left-side menu) > **Configuration Files**, select the file you want to edit, and click the **lock** button to unlock it. You can now freely modify the file. warning Once unlocked, the file stays in manual editing mode until you lock it again. Re-locking it will reset the file to a version generated by FlutterFlow, which will overwrite any manual changes you've made. tip * Don’t remove FlutterFlow’s existing entries unless you are sure. It’s safer to only add or modify necessary lines and leave the rest as is. * Use Manual Edit Mode for bulk or complex edits that the snippet can’t easily do, such as reordering tags, removing something, or pasting in a large chunk of config. Always verify that the app still builds and runs after such edits. * You can also use your Development [**Environment Values**](/testing/dev-environments.md#environment-values) and [**Library Values**](/resources/projects/libraries.md#library-values) inside snippets. For more details, refer to the [**Include Variables in Native Code**](/concepts/custom-code/configuration-files.md#include-variables-in-native-code) section. ## Include Variables in Native Code[​](/concepts/custom-code/configuration-files.md#include-variables-in-native-code "Direct link to Include Variables in Native Code") When editing native files in FlutterFlow, you may need to include dynamic values, such as API keys, app configurations, or environment-specific settings. Instead of hardcoding these values directly in **`AndroidManifest.xml`**, **`Info.plist`**, or other native files, you can use FlutterFlow [**Environment Values**](/testing/dev-environments.md#environment-values) and [**Library Values**](/resources/projects/libraries.md#library-values) to keep your app flexible and secure. To include a variable in a configuration file, start by creating a **file-level variable** and assigning it a value from either your **Environment Values** or **Library Values**. Then, reference this variable using a placeholder format (e.g., `{{apiToken}}`) within the configuration file. These placeholders in native files are automatically replaced with their actual values during the code generation process. Here’s exactly how you do it: tip * You can also directly insert a variable placeholder (e.g., `{{variableName}}`) into the code using a snippet or manual edit mode and FlutterFlow automatically creates the corresponding file-level variable. * You can use the file level variable across different snippets within the same file. Here are some examples that utilize variables in native code: **Example 1: Using API Keys in `AndroidManifest.xml`** Let’s say you are integrating the Mapbox package in your FlutterFlow app, and it requires an API Key in the form of a token inside the `AndroidManifest.xml` file. Instead of hardcoding the token, you can use a variable like this: ``` ``` Here, `{{MAPBOX_ACCESS_TOKEN}}` is a file level variable that holds the Environment Value. **Example 2: Configuring `Info.plist` for iOS** For iOS apps, you might need to configure App Transport Security (ATS) to allow non-HTTPS connections. Instead of manually setting `NSAllowsArbitraryLoads` to `true`, you can use a variable: ``` NSAllowsArbitraryLoads <{{ALLOW_HTTP_TRAFFIC}}/> ``` If `ALLOW_HTTP_TRAFFIC` is set to `true` in FlutterFlow’s Environment Value, the app will allow HTTP connections. **Example 3: Using Library Values** If you are building a [FlutterFlow Library](/resources/projects/libraries.md) and need to include public API keys in native code, you can use [Library Values](/resources/projects/libraries.md#library-values) as placeholders. This ensures that when someone installs your library, they can define their own values. For example, if your library integrates with a public weather API that requires an API key (such as Open-Meteo or WeatherAPI for general use), it’s best not to add the key directly in the manifest file. Instead, create a file-level variable and assign it a Library Value. ``` ``` The library user will define their own API key under Library Values when importing your library. At build time, FlutterFlow replaces `{{WEATHER_API_KEY}}` with the user-defined key. ## Editable Files[​](/concepts/custom-code/configuration-files.md#editable-files "Direct link to Editable Files") FlutterFlow allows editing several key native files. Below, we cover each file’s role, why you might need to edit it, and examples of real-world use cases. ### `AndroidManifest.xml` (Android)[​](/concepts/custom-code/configuration-files.md#androidmanifestxml-android "Direct link to androidmanifestxml-android") `AndroidManifest.xml` is the master configuration file for your Android app. It is located in the root directory of the app's `android/app/src/main` folder and declares essential app information to the Android OS and Google Play. This includes your app’s package name, components (activities, services, receivers), and the permissions it needs. It defines hardware and software features the app depends on, such as Bluetooth, GPS, or sensors. The manifest manages intents and filters, determining how the app responds to system events and deep linking. It also includes metadata and configuration for SDKs and libraries, such as API keys or feature flags. In short, the manifest is like an app’s identity card and permission sheet for Android. Here are some scenarios where you may need to modify the `AndroidManifest.xml` file: **Example 1: Declaring App Components (Activities, Services, Receivers)** For including additional screens (activities), background processes (services), or listeners (broadcast receivers), you must declare them in `AndroidManifest.xml`. ``` ``` This registers `NewScreenActivity` so the system knows it exists. **Example 2: Requesting Permissions** If your app requires access to restricted resources such as wake locks (to keep the device awake) or audio recording, you must declare the necessary permissions in `AndroidManifest.xml` by [manually editing](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode) the file. **Tip:** You can also add custom permissions directly through the [**Permission Settings**](/resources/projects/settings/project-setup.md#adding-custom-permission) in FlutterFlow. ``` ``` Without these, the app cannot keep the device awake or record audio. **Example 3: Adding Metadata for SDKs and Libraries** Many third-party packages (Google Maps, Firebase, AdMob, etc.) require `` tag in `AndroidManifest.xml` to pass configuration values. For example, the [**Mapbox Flutter**](https://pub.dev/packages/mapbox_flutter) plugin requires adding your Mapbox access token as a metadata entry for initialization. A real example: to initialize Mapbox, you’d add: ``` ``` **Example 4: Restricting the App to Specific Devices** You can specify device hardware requirements (e.g., GPS, camera, touchscreen) to ensure the app only installs on compatible devices. ``` ``` This prevents installation on devices without a camera. **Example 5: Enabling Cleartext Traffic** If your app needs to communicate over HTTP (unencrypted) for testing or legacy reasons, you might need to add `android:usesCleartextTraffic="true"` in the `` tag. This is to relax network security for HTTP URLs. ``` ``` tip You can modify the `AndroidManifest.xml` file by either [**adding a snippet**](/concepts/custom-code/configuration-files.md#snippet-placement-for-android) or [**editing it manually**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). ### `build.gradle` (Android)[​](/concepts/custom-code/configuration-files.md#buildgradle-android "Direct link to buildgradle-android") The `build.gradle` file is the main Gradle build script for your Android app module. It resides in the `android/app/` directory and controls how your Android app is compiled, packaged, and built. This file defines critical configuration such as: * SDK versions (`compileSdkVersion`, `minSdkVersion`, `targetSdkVersion`) * Dependencies for third-party libraries * Build types (like debug vs. release) * Signing configurations for release builds * Kotlin and Flutter settings * MultiDex and ProGuard rules * Android packaging options In short, the `build.gradle` file acts as the blueprint for how your Android app is built and prepared for distribution. **Example 1: Changing SDK Versions** To set which Android SDK your app compiles with, update the following section in `build.gradle`: ``` android { compileSdkVersion 33 defaultConfig { applicationId "com.example.myapp" minSdkVersion 21 targetSdkVersion 33 versionCode 1 versionName "1.0" } } ``` Use this when you want to upgrade to a newer Android API level or need compatibility with certain libraries. **Example 2: Adding Third-Party Libraries** To use Android-specific libraries (such as Play Services or Jetpack), add them in the `dependencies` section: ``` dependencies { implementation 'com.google.android.gms:play-services-maps:18.1.0' implementation 'androidx.work:work-runtime:2.7.1' } ``` Use this when integrating services like Google Maps, Firebase Messaging, or WorkManager. **Example 3: Adding ProGuard Rules for Release Build** If your app uses ProGuard (code shrinking/obfuscation), you can define custom rules or reference a rules file: ``` android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } ``` Use this to reduce APK size and protect code in production. **Example 4: Enabling MultiDex for Large Apps** If your app exceeds the 64K method limit (common when using many dependencies), enable MultiDex support: ``` defaultConfig { ... multiDexEnabled true } ``` Use this when your build fails with `Too many methods` errors or when integrating large libraries like Firebase. tip You can modify the `build.gradle` file by either [**adding a snippet**](/concepts/custom-code/configuration-files.md#snippet-placement-for-ios) or [**editing it manually**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). ### ProGuard File (Android)[​](/concepts/custom-code/configuration-files.md#proguard-file-android "Direct link to ProGuard File (Android)") The **ProGuard file (`proguard-rules.pro`)** is a configuration file used in Android projects to optimize, shrink, and obfuscate the app’s code. It helps reduce APK or AAB size, improves performance, and protects the app’s code from reverse engineering by making it difficult to decompile. The ProGuard files allow you to specify rules to keep certain classes or methods (prevent them from being removed or renamed), or to tweak the obfuscation behavior. Located in the **`android/app/proguard-rules.pro`** directory of an Android project, the ProGuard rules are applied when code shrinking is enabled in a release build. Here are some scenarios where you may need to modify the ProGuard file: **Example 1: Preventing Issues with Third-Party Libraries** ProGuard can obfuscate critical libraries, breaking their functionality. To prevent this, you need to keep specific classes used by the library. ``` # Firebase -keep class com.google.firebase.** { *; } # Gson (JSON Serialization) -keep class com.google.gson.** { *; } -keepattributes *Annotation* ``` This ensures that Firebase and Gson classes are not obfuscated, preventing serialization errors. **Example 2: Debugging ProGuard Issues** If your app crashes in release mode but works in debug mode, ProGuard might be removing important classes. To troubleshoot, you can add logging and keep rules. ``` -assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); public static *** i(...); } ``` This removes debug logs in release builds but retains them for troubleshooting. **Example 3: Improving Security by Removing Debug Information** Attackers can decompile APKs and view sensitive debug logs. To remove these debug logs, add: ``` -dontwarn android.util.Log ``` **Example 4: Keeping Native Libraries (JNI) Safe** If your app uses native C/C++ libraries (JNI), ProGuard may mistakenly remove required components. To prevent this: ``` -keep class com.example.native.** { *; } -keepclassmembers class * { native ; } ``` This keeps all native methods intact. **Example 5: Preventing Issues with Reflection-Based Code** Some libraries rely on reflection to dynamically call methods, which ProGuard may remove. ``` -keep class * implements android.os.Parcelable { *; } -keepclassmembers class ** { @android.webkit.JavascriptInterface ; } ``` This ensures reflection-based code continues working. ### `Info.plist` (iOS)[​](/concepts/custom-code/configuration-files.md#infoplist-ios "Direct link to infoplist-ios") `Info.plist` (Information Property List) is the configuration file for iOS apps. It’s a structured XML file that provides iOS with essential information about your app’s configuration and requirements. The `Info.plist`defines things such as your app’s bundle identifier, display name, version, and most importantly, usage descriptions for permissions and other settings iOS needs at runtime. The file is required for every iOS app and is located in the project’s `/ios/Runner/` directory of your FlutterFlow apps. Essentially, it’s the blueprint for iOS to understand your app’s capabilities and needs. Here are some scenarios where you may need to modify the `Info.plist` file: **Example 1: Requesting Permissions** If your app requires location access both while in use and in the background, you must declare the appropriate permissions in `Info.plist`. **Tip:** You can also add custom permissions directly through the [**Permission Settings**](/resources/projects/settings/project-setup.md#adding-custom-permission) in FlutterFlow. ``` NSLocationWhenInUseUsageDescription This app requires location access while in use to provide location-based services. NSLocationAlwaysAndWhenInUseUsageDescription This app requires background location access to enable continuous location tracking. ``` This ensures the app can access location services even when the user is not actively using it. **Example 2: Enabling App Transport Security (ATS) for HTTP Requests** By default, iOS enforces HTTPS connections for security reasons. If your app needs to communicate with **HTTP-only** servers, you must modify `Info.plist`. ``` NSAppTransportSecurity NSAllowsArbitraryLoads ``` This allows all HTTP requests but should be used with caution **Example 3: Configuring Background Modes** If your app requires background functionality (e.g., playing music, location tracking), you must enable background modes in `Info.plist`. ``` UIBackgroundModes audio location ``` This allows the app to play audio or track location when running in the background. **Example 4: Adding Keys** Many third-party packages require to add keys in the in `Info.plist` file. For example, If you’re using the Mapbox SDK, you need to provide an access token in `Info.plist` to enable map functionality. ``` io.flutter.embedded_views_preview MGLMapboxAccessToken YOUR_MAPBOX_ACCESS_TOKEN ``` The **`MGLMapboxAccessToken`** key is required for initializing Mapbox maps in your app. Additionally, the **`io.flutter.embedded_views_preview`** key must be set to `true` to support embedding native views inside Flutter widgets. tip You can modify the `Info.plist` file by either [**adding a snippet**](/concepts/custom-code/configuration-files.md#snippet-placement-for-ios) or [**editing it manually**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). ### `Entitlements.plist` (iOS)[​](/concepts/custom-code/configuration-files.md#entitlementsplist-ios "Direct link to entitlementsplist-ios") The `Entitlements.plist` file is a property list in iOS applications that defines the app’s security-related capabilities and permissions. It grants specific privileges to an app, allowing it to access Apple services such as iCloud, Push Notifications, App Groups, Background Modes, and Keychain access. It is located in the **`/ios/Runner/`** directory of your FlutterFlow app and is named **`Runner.entitlements`**. This file ensures that only authorized apps can use these features, maintaining security and preventing unauthorized access to sensitive system functions. Here are some scenarios where you may need to modify the `Entitlements.plist` file: **Example 1: Enabling iCloud Storage** If your app integrates **iCloud services**, such as syncing user data or storing documents, you must add iCloud entitlements. ``` com.apple.developer.icloud-container-identifiers iCloud.com.yourcompany.appname com.apple.developer.icloud-services CloudDocuments ``` This grants your app access to iCloud storage under the specified container. **Example 2: Enabling Keychain Access** If your app needs to store secure credentials, enabling Keychain Sharing is required. ``` keychain-access-groups com.yourcompany.appname ``` This allows secure storage of login credentials, API tokens, or encryption keys in the iOS Keychain. **Example 3: Enabling App Groups for Shared Data** If your app shares data between multiple apps or an app extension (e.g., a widget or a Siri shortcut), you must enable App Groups. ``` com.apple.security.application-groups group.com.yourcompany.shared ``` This allows different apps or extensions to access shared storage and user defaults. **Example 4: Enabling Wallet (Apple Pay & Passes)** If your app integrates with Apple Wallet, you need to add Wallet entitlements. ``` com.apple.developer.pass-type-identifiers pass.com.yourcompany.appname ``` This enables your app to create, manage, and present passes in Apple Wallet. tip You can modify the `Entitlements.plist` file by either [**adding a snippet**](/concepts/custom-code/configuration-files.md#snippet-placement-for-ios) or [**editing it manually**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). ### `AppDelegate.swift` (iOS)[​](/concepts/custom-code/configuration-files.md#appdelegateswift-ios "Direct link to appdelegateswift-ios") The `AppDelegate.swift` file is the entry point for your iOS application. It plays a crucial role in setting up your app’s runtime environment and handling app lifecycle events such as launching, backgrounding, and termination. This file is also where you register Flutter plugins and initialize SDKs like Firebase or Branch. It’s located at: `ios/Runner/AppDelegate.swift` **Example: Registering Custom iOS Plugins** For custom native iOS plugins that aren’t auto-registered, you can manually register them inside `AppDelegate.swift`. ``` override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller: FlutterViewController = window?.rootViewController as! FlutterViewController let myPlugin = CustomPlugin() myPlugin.register(with: controller) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } ``` Use this for custom iOS integrations that require manual setup. tip You can modify the `AppDelegate.swift` file by either [**adding a snippet**](/concepts/custom-code/configuration-files.md#snippet-placement-for-ios) or [**editing it manually**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). ### `main.dart` (Flutter)[​](/concepts/custom-code/configuration-files.md#maindart-flutter "Direct link to maindart-flutter") The `main.dart` file is the entry point of every FlutterFlow app. It is the first file that runs when the app starts and is responsible for initializing the application, configuring dependencies, and defining the root widget. Located in the **`lib/`** directory, `main.dart` contains the `main()` function, which is required for every FlutterFlow app. If you need to execute any custom Dart code at startup — such as initializing third-party SDKs, setting global configurations, service locators, printing a debug log, or running certain functions once — `main.dart` is the place to do it. info [**Adding Snippets**](/concepts/custom-code/configuration-files.md#option-1-add-individual-snippets) isn't available for `main.dart`. Instead, you can directly modify the file using [**Manual Edit Mode**](/concepts/custom-code/configuration-files.md#option-2-manual-edit-mode). Here are some scenarios where you may need to modify the `main.dart` file: **Example 1: Initializing Third-Party Packages** Many packages have initialization calls. For example, if you added a custom package for analytics or error tracking (say Sentry or a logging service), you might need to call `SentryFlutter.init()` or set up an error handler at app startup. By placing that call in `main.dart` (before or right after `runApp`), you ensure it’s executed early. ``` import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; Future main() async { runZonedGuarded(() async { await SentryFlutter.init( (options) { options.dsn = 'https://example@sentry.io/add-your-dsn-here'; }, ); runApp(MyApp()); }, (exception, stackTrace) async { await Sentry.captureException(exception, stackTrace: stackTrace); }); } ``` This ensures Sentry is ready before the app starts, just like Firebase initialization. **Example 2: Customizing the Status Bar Appearance** If you want to change the status bar color and adjust icon brightness for Android and iOS, you need to modify `main.dart` before calling `runApp()`. ``` import 'package:flutter/services.dart'; void main() { SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle( statusBarColor: Colors.redAccent, // Custom status bar color statusBarIconBrightness: Brightness.dark, // Dark icons for Android statusBarBrightness: Brightness.light, // Light icons for iOS ), ); runApp(MyApp()); } ``` **Example 3: Locking the Screen Orientation** Some apps require landscape-only or portrait-only modes. You can enforce screen orientation in `main.dart` before launching the app. ``` import 'package:flutter/services.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); runApp(MyApp()); } ``` This ensures the app only runs in landscape mode. **Example 4: Observing App Lifecycle Changes** If your app needs to respond to lifecycle events, such as tracking when the app goes into the background or returns to the foreground, you can attach an observer. ``` import 'package:flutter/widgets.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); WidgetsBinding.instance.addObserver(AppLifecycleObserver()); runApp(MyApp()); } class AppLifecycleObserver with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { print('App is in foreground'); } else if (state == AppLifecycleState.paused) { print('App is in background'); } } } ``` ## Best Practices[​](/concepts/custom-code/configuration-files.md#best-practices "Direct link to Best Practices") * **Backup:** Before making native file changes, ensure you have a backup of at least the text of the original file. You could also commit your changes so you can revert if needed. This way, if things go wrong, you can manually restore. * **One Change at a Time:** Add or modify one item at a time and then test your app. If you add multiple things and something breaks, it’s harder to pinpoint which change did it. * **Consult Package Documentation:** When you’re making changes for third-party packages, follow their instructions exactly. Usually, package docs show a snippet – use that in FlutterFlow’s [snippet](/concepts/custom-code/configuration-files.md#option-1-add-individual-snippets). Double-check official docs for Android or iOS if you’re unsure about the correct keys or tags. For example, if enabling background fetch, Apple’s docs will list the exact string to use in `Info.plist` (`fetch` in `UIBackgroundModes` array). * **Keep it Minimal:** Only add what you truly need. Don’t add a bunch of entitlements or permissions “just in case” as that can bloat and complicate your app, and even trigger store reviews for uses that your app doesn’t actually have. * **Use Comments:** As you modify files, annotate them. If six months later you or a team member look at the manifest, a comment like `` is very helpful. * **Testing on Devices:** Especially for anything related to `Info.plist` or entitlements, always test on a real iOS device if possible. Some issues (like missing entitlements or background mode usage) won’t show up in the simulator. Similarly, test Android changes on a device or emulator with a release build – because ProGuard rules effects, for example, only show in release mode. * **Monitoring Logs and Errors:** After making changes, monitor the Xcode console or Android logcat when running the app. If there are misconfigurations, you often get warnings. * **Stay Updated:** FlutterFlow may improve native editing features over time. Keep an eye on FlutterFlow’s docs or community announcements. If they introduce a new easier way, prefer that to manual editing when possible, as it will be more foolproof. * **Security Consideration:** Remember that anything in these files (especially `Info.plist`, `AndroidManifest.xml`) is essentially public in the distributed app. Don’t assume an API key in `Info.plist` is hidden – it’s not. For keys you must include (maps, etc.), consider using [private environment values](/testing/dev-environments.md#private-environment-values) and monitoring their usage. ## FAQs[​](/concepts/custom-code/configuration-files.md#faqs "Direct link to FAQs") My app won’t install on an iOS device. What should I check? Confirm that the entitlements in `Entitlements.plist` match your provisioning profile. If you see a “Missing entitlement” error, it means you added an entitlement not allowed by your profile. Remove it or update the profile in the Apple Developer Portal. How do I fix “Manifest merger failed” on Android? This error indicates a conflict in your `AndroidManifest.xml`. Common issues include **duplicate permissions** or attributes (e.g., two `` entries). The error message usually identifies the conflicting line. Remove the duplicate or ensure each property is set only once to resolve the conflict. Why my app isn't running in Test Mode after editing the `main.dart` file with Supabase enabled? There's a known limitation where editing the `main.dart` file with Supabase enabled prevents Test Mode from running. As a workaround, please use [**Local Run**](/testing/local-run.md) to test your app instead. Can I modify the Configuration Files in a Library project? Yes, you can. When a Library Project is imported, any configuration file snippets, such as those for `AndroidManifest.xml`, `Info.plist`, or `Entitlements.plist` are automatically merged into the importing project's configuration files. Additionally, your Library Project can pass values (like API keys) into those snippets using [**Library Values**](/resources/projects/libraries.md#library-values), making it easy to customize. ![config-values-in-library](/assets/images/config-values-in-library-daa58dd3085d67ffaec7a37c099da6fc.avif) This makes Libraries incredibly powerful and enables easy integration of tools like **PostHog** (analytics), **Sentry** (crash reporting), **CleverTap**, **flutter\_local\_notifications**, **flutter\_nfc\_kit**, and many more directly from the Marketplace. --- # Custom Actions Custom Actions in FlutterFlow differ from custom functions in that they always return a `Future`. This makes them particularly useful for complex operations that may take time to complete, such as querying a database or calling a function that returns results after a delay. Additionally, Custom Actions are beneficial when you want to add a third-party dependency from `pub.dev`, allowing you to extend the capabilities of your application with external packages. What is a Future? Futures in **Flutter** represent an asynchronous operation that will return a value or an error at some point in the future. `Future` indicates that the future will eventually provide a value of type `T`. So if your return value is a `String`, then the Custom Action will return a `Future`, and the `String` return value will be output at some point in the future. ## Key Use Cases[​](/concepts/custom-code/custom-actions.md#key-use-cases "Direct link to Key Use Cases") * **Database Queries:** Perform complex queries to retrieve or update data in a database. * **API Calls:** Make asynchronous HTTP requests to external APIs and handle the responses. * **File Operations:** Manage file reading or writing operations that require time to complete. * **Third-Party Integrations:** Incorporate external packages and dependencies to enhance functionality, such as an external analytics package. ## Using a Custom Action[​](/concepts/custom-code/custom-actions.md#using-a-custom-action "Direct link to Using a Custom Action") Once your Action code is finalized, saved, and compiled, you can start using this action as a part of your Action flow. In the following example, we have a Custom Action called `executeSearch` that takes an argument `searchItem` that is the search string from the search **TextField** of an ecommerce app's `HomePage`. ## Using the Custom Action Result[​](/concepts/custom-code/custom-actions.md#using-the-custom-action-result "Direct link to Using the Custom Action Result") In our previous example, we enabled the **Return Value** of the Custom Action to return a `List` when the search keyword is valid. With this change the code will change from ``` Future executeSearch(String searchItem) async { // Add your function code here! } ``` to ``` Future> executeSearch(String searchItem) async { // Add your function code here! } ``` Let's modify our Action Flow now so we can use the custom action result values within our Action Flow. LOOKING for other CUSTOM action properties? To learn more about Custom Action settings, such as the [**Exclude From Compilation toggle**](/concepts/custom-code.md#exclude-from-compilation), [**Include Build Context toggle**](/concepts/custom-code.md#include-buildcontext), and other properties like [**Callback Actions**](/concepts/custom-code.md#callback-action-as-parameter), [**Pubspec Dependencies**](/concepts/custom-code.md#adding-a-pubspec-dependency), please check out this [**comprehensive guide**](/concepts/custom-code.md). --- # Custom Functions Custom Functions in FlutterFlow allow you to perform simple Dart calculations and logic. These functions are ideal for tasks that require immediate results, such as data transformations, mathematical calculations, or simple logic operations. **Custom Functions** enable you to encapsulate reusable logic, making your code more organized and maintainable. Let's see some common examples: **To calculate discount given price and discount rate:** ``` double calculateDiscount(double price, double discountRate) { return price - (price * discountRate / 100); } ``` **To capitalize a String input:** ``` String capitalize(String input) { return input.isNotEmpty ? '${input[0].toUpperCase()}${input.substring(1)}' : ''; } ``` **To convert Celsius to Fahrenheit** ``` double celsiusToFahrenheit(double celsius) { return (celsius * 9/5) + 32; } ``` ## Key Use Cases[​](/concepts/custom-code/custom-functions.md#key-use-cases "Direct link to Key Use Cases") * **Data Transformation:** Convert or manipulate data before displaying it in the UI. * **Mathematical Calculations:** Perform complex calculations directly within your app. * **String Manipulation:** Format or parse strings based on specific requirements. * **Conditional Logic:** Implement logic that determines output based on given inputs. ## Test Functions[​](/concepts/custom-code/custom-functions.md#test-functions "Direct link to Test Functions") Custom Functions are typically straightforward input-output expressions designed to perform specific tasks. It is highly recommended to test your Custom Functions before integrating them into your project. Testing the Custom Function code ensures that it works as expected with various inputs, helping you catch potential issues early. Overall, it boosts your confidence in shipping your app to production, knowing that your logic is reliable and robust. LOOKING for other CUSTOM Function properties? To learn more about Custom Function properties such as [**Input Arguments**](/concepts/custom-code.md#input-arguments) and **[Return Values](/concepts/custom-code.md#return-values)**, please check out this [**comprehensive guide**](/concepts/custom-code.md). ## FAQs[​](/concepts/custom-code/custom-functions.md#faqs "Direct link to FAQs") I can't add imports! You can't have imports in a custom function. To be able to add imports, consider using a Custom Action. Getting error: The function 'FFAppState' isn't defined. You can't use the app state variable (i.e., `FFAppState().variablename`) directly in your custom function code. Instead, you can pass the app state variable as a parameter and then use it in your code. ## Utility Functions Library[​](/concepts/custom-code/custom-functions.md#utility-functions-library "Direct link to Utility Functions Library") Instead of building everything from scratch, explore our **[Utility Functions Library](https://marketplace.flutterflow.io/item/ZVBmWMGpXe6vqnASRHDA)** — packed with 50+ helpful functions for everyday tasks like formatting text, manipulating dates, validating input, and more. Easily plug them into your custom logic to save time and reduce errors. --- # Custom Widgets Custom Widgets allow you to create unique and reusable UI components that extend beyond the standard widget offerings in FlutterFlow. By leveraging Custom Widgets, you can achieve a higher level of customization and control over your app's user interface. In most cases, you can create a reusable component with the basic widget set available in FlutterFlow. However, when you want to include a UI package from [**pub.dev**](https://pub.dev), **Custom Widgets** are the better choice. ## Key Use Cases[​](/concepts/custom-code/custom-widgets.md#key-use-cases "Direct link to Key Use Cases") * **Unique UI Elements:** Create complex UI components that are not available in the default FlutterFlow widget set. * **Third-Party Integrations:** Integrate external UI packages from pub.dev to enhance the functionality and appearance of your app. ## Creating a New Custom Widget[​](/concepts/custom-code/custom-widgets.md#creating-a-new-custom-widget "Direct link to Creating a New Custom Widget") To create a new custom widget, add a new Custom Code snippet and follow the quick guide below. In this example, we will create a `ProductRatingBar` widget that uses a pub.dev dependency to display the rating bar UI. It will also take a callback action to provide the rating value back to the caller. Widget Builder as Parameter You can also leverage [**Widget Builders**](/resources/ui/components/widget-builder.md) that allow you to pass in widgets to be used within the custom widget tree. This is especially useful when you want to dynamically substitute content for some part of a custom widget - like displaying an item in a custom widget popup. ### Properties: Width & Height[​](/concepts/custom-code/custom-widgets.md#properties-width--height "Direct link to Properties: Width & Height") For custom widgets, it is mandatory to specify both width and height. These properties are required to size the custom widget appropriately. Without setting these dimensions, the custom widget will not render correctly within your application. ## Add Dependency to Custom Widgets[​](/concepts/custom-code/custom-widgets.md#add-dependency-to-custom-widgets "Direct link to Add Dependency to Custom Widgets") In this example, we are using the [**flutter\_rating\_bar**](https://pub.dev/packages/flutter_rating_bar) dependency to create a `ProductRatingBar` widget for our Product pages. See how we utilize the example code from pub.dev and add the customized widget in FlutterFlow: Choosing a Pubspec Dependency For a comprehensive guide on navigating external packages using pub.dev, evaluating packages, and making the best choices for your app, [**follow the guide**](/concepts/custom-code.md#adding-a-pubspec-dependency). ## Using a Custom Widget[​](/concepts/custom-code/custom-widgets.md#using-a-custom-widget "Direct link to Using a Custom Widget") To add a custom widget to your page, you can drag and drop it from the Widget Palette's Components section or through the Widget Tree section. Here is a demo: ### Providing the Callback Actions[​](/concepts/custom-code/custom-widgets.md#providing-the-callback-actions "Direct link to Providing the Callback Actions") Since we created the `onRating` callback action in our custom widget, we must provide an action when setting the widget in page. In this example, we set the `ratingValue` to the page state variable `userRating`. ## Preview Widget[​](/concepts/custom-code/custom-widgets.md#preview-widget "Direct link to Preview Widget") FlutterFlow also allows you to view your custom widget once it is successfully compiled. ![preview-custom-widget.avif](/assets/images/preview-custom-widget-14775d4bdaa999a2cdd0a91d2592e70f.avif) LOOKING for other CUSTOM action properties? To learn more about Custom Widget settings, such as the [**Exclude From Compilation toggle**](/concepts/custom-code.md#exclude-from-compilation), and other properties like [**Callback Actions**](/concepts/custom-code.md#callback-action-as-parameter), [**Pub Dependencies**](/concepts/custom-code.md#adding-a-pubspec-dependency), please check out this [**comprehensive guide**](/concepts/custom-code.md). --- # FlutterFlow Visual Studio Extension The **Visual Studio Code (VSCode) extension** allows you to work with your FlutterFlow project’s custom code directly in [Visual Studio Code](https://code.visualstudio.com/) (a local code editor). This extension facilitates easy editing, pushing, and pulling of custom code changes between FlutterFlow and your local development environment. While you can edit custom code inside FlutterFlow's in-app code editor, editing the code in Visual Studio Code may be preferable for a few reasons: 1. **Access to the Entire Codebase**: When writing custom code in Visual Studio Code, you'll have full access to your app's entire codebase, making it easier to reference component widget classes, custom data types, enums, and more. 2. **Real-time Autocomplete and Error Detection**: Working on a local machine typically provides more reliable access to real-time error detection and autocomplete features within the code editor, which can make your development process more efficient. 3. **Leverage Flutter & Dart Tooling**: Using Visual Studio Code allows you to take advantage of existing Flutter and Dart tools, making it easier to develop and refactor your custom code. 4. **Leverage the AI Ecosystem**: Additionally, you can easily utilize AI tools available in the Visual Studio ecosystem, such as Copilot. info The VS Code extension is only available on the Growth plan and higher. Check out our [**pricing**](https://www.flutterflow.io/pricing) section. ## Installation[​](/concepts/custom-code/vscode-extension.md#installation "Direct link to Installation") To fully leverage the Flutter, Dart, and AI tools in Visual Studio Code while editing your FlutterFlow custom code files, you can install the **FlutterFlow: Custom Code Editor** extension. Here are a few easy methods to set it up. ### Install from Marketplace[​](/concepts/custom-code/vscode-extension.md#install-from-marketplace "Direct link to Install from Marketplace") You can install the FlutterFlow extension from the [Visual Studio Code marketplace](https://marketplace.visualstudio.com/items?itemName=FlutterFlow.flutterflow-custom-code-editor\&ssr=false#overview) site. To install the extension directly from Visual Studio Code, open the editor, click on the **Extensions** icon (or press `Ctrl + Shift + X` / `Cmd + Shift + X`), search for "**FlutterFlow: Custom Code Editor**," and click **Install** to add the extension to your workspace. ### Add API Keys[​](/concepts/custom-code/vscode-extension.md#add-api-keys "Direct link to Add API Keys") To use the extension, you must set your **API key** in the editor's **Extension Settings**. You can generate an API key from the [FlutterFlow account page](https://app.flutterflow.io/account) and then add it to the extension settings page in Visual Studio Code. Here’s exactly how you do it: tip You can configure optional settings such as specifying the **Project ID** and **Branch** to pull and update code from. Additionally, you can set a **Download Location** to determine the initial directory where the code will be downloaded. ### Downloading Code[​](/concepts/custom-code/vscode-extension.md#downloading-code "Direct link to Downloading Code") The first step in editing custom code for your FlutterFlow project is to download its code. To download the code for your project, use the Visual Studio Code command palette (`cmd` + `shift` + `p` or `ctrl` + `shift` + `p`). In the command palette, you can use the `FlutterFlow: Download Code` command. This command will prompt you for three pieces of information: * **Project ID**: This is the Project ID, or unique identifier, for your FlutterFlow project. You can find the Project ID by hovering over the Project Name in the top left corner inside the FlutterFlow builder. * **Branch Name:** The name of the FlutterFlow project branch you want to work on. You can leave this blank to work on the main branch. * **Download Location:** A file picker will be presented for you to choose where to download your project code, the code will be downloaded to `thisdirectory`/`projectID`. ### Initializing a Code Editing Session[​](/concepts/custom-code/vscode-extension.md#initializing-a-code-editing-session "Direct link to Initializing a Code Editing Session") After the code has been downloaded, you will need to initiate a **Code Editing** session using the extension. When a Code Editing session has been initiated, you’ll be able to pull and push code from Visual Studio Code to FlutterFlow. ![extension-overview.png](/assets/images/extension-overview-4b40b34eeb52ddca5cde6f841672c3b1.png) To start a Code Editing session, run the command `FlutterFlow: Start Code Editing Session` from the Visual Studio Code Command Palette. This command will also automatically run `flutter pub get`. ![start-code-edit-session](/assets/images/start-code-edit-session-d3935aed9630b1f8d37de438187e6885.png) Editing Flutter & Dart Files It’s recommended that you install the [**Flutter & Dart Extensions**](https://docs.flutter.dev/tools/vs-code) which will make it easier to edit Flutter and Dart code. ## Editing Custom Code[​](/concepts/custom-code/vscode-extension.md#editing-custom-code "Direct link to Editing Custom Code") After successfully [installing](/concepts/custom-code/vscode-extension.md#installation) the Visual Studio Code extension and [downloading the code](/concepts/custom-code/vscode-extension.md#downloading-code), you can [initialize your session](/concepts/custom-code/vscode-extension.md#initializing-a-code-editing-session) to start adding or editing custom code. Currently, the following resources are available for customization: * **Custom Actions** * **Custom Widgets** * **Custom Functions** * **Package Dependencies** in `pubspec.yaml` ### Testing Changes Locally[​](/concepts/custom-code/vscode-extension.md#testing-changes-locally "Direct link to Testing Changes Locally") When working with custom code, it's important to test your implementations. We recommend integrating your Custom Function, Action, or Widget directly within your FlutterFlow project—for example, by adding the Custom Widget to a FlutterFlow Page. You can then choose to test your app from FlutterFlow, using a [Test Mode session](https://docs.flutterflow.io/testing/run-your-app/#test-mode) or [Local Run](https://docs.flutterflow.io/testing/local-run), or run your app locally from Visual Studio Code. Before testing from FlutterFlow, ensure you’ve [pushed your changes](/concepts/custom-code/vscode-extension.md#push-changes-to-flutterflow). To run your project from Visual Studio Code, make sure the Flutter extension is installed. Once set up, you can simply click the Run (play) button. For further details, refer to [Flutter’s official documentation](https://docs.flutter.dev/tools/vs-code#running-and-debugging). ### Push Changes to FlutterFlow[​](/concepts/custom-code/vscode-extension.md#push-changes-to-flutterflow "Direct link to Push Changes to FlutterFlow") To make your custom code available in FlutterFlow, you need to push your changes. When you push changes, all the files you've edited in Visual Studio Code will be updated in FlutterFlow. You can see which files have been changed in the **FF: Modified Files section** of the Explorer. This section updates whenever you save a file, showing what has been added, removed, or changed. ![see-modified-files.png](/assets/images/see-modified-files-e757b1b1addfaa784e09bb0bfe13b165.png) To push changes click the `Push to FlutterFlow` status bar icon, or run the `FlutterFlow: Push to FlutterFlow` command in the command palette. ![push.png](/assets/images/push-9d5f3bd9f958610077043871896911dc.png) warning This action can’t be undone. Make sure you don’t overwrite any changes in FlutterFlow that you want to keep. To avoid this, pull the latest changes from FlutterFlow before editing in Visual Studio Code, and push your updates once you're done. ### Pull Latest Changes from FlutterFlow[​](/concepts/custom-code/vscode-extension.md#pull-latest-changes-from-flutterflow "Direct link to Pull Latest Changes from FlutterFlow") Before editing any custom files, it's important to pull the latest changes from FlutterFlow into your local repository. This ensures you have the most up-to-date components, app state variables, and custom data types/enums that you might need to reference in your custom code. To pull the latest changes, click the `Pull Latest` icon in the lower status bar, or run the `FlutterFlow: Pull Latest Changes` command. ![pull.png](/assets/images/pull-e10f259ab4ecab254938ff5b84bfee72.png) warning Pulling changes will also overwrite any local modifications made in the code editor. ## Updating Files[​](/concepts/custom-code/vscode-extension.md#updating-files "Direct link to Updating Files") The VSCode Extension allows you to update **custom code resources**, including entire files or specific Dart/Flutter functions. For Custom Actions and Custom Widgets, there’s a one-to-one relationship between each action/widget and its corresponding file. If you create a new file in the `lib/custom_code/actions` or `lib/custom_code/widgets` directory, it will automatically add a new action or widget to your FlutterFlow project. For Custom Functions, all functions are contained within a single file: `lib/flutter_flow/custom_functions.dart`. You can add, edit, or delete custom functions directly within this file. For Package Dependencies, you can [add new dependencies](/concepts/custom-code/vscode-extension.md#adding-new-dependencies) in the `pubspec.yaml` file, but you cannot modify the existing ones. When you add a new dependency, it will appear in **Settings and Integrations > Project Dependencies > Custom Code Dependencies** section. ![custom-code-dependencies](/assets/images/custom-code-dependencies-fbaf0813d74afe6e76c14ecfc1093e83.png) ### Renaming Files[​](/concepts/custom-code/vscode-extension.md#renaming-files "Direct link to Renaming Files") To rename Custom Actions or Custom Widget, use the Visual Studio Code rename symbol functionality. Simply, right-click the name of a Custom Action or Widget and select **Rename Symbol**, then type the new name. If you change the name without doing this, you’ll need to update the name in the file where the Widget or Action is defined, as well as the index file that exports the Widget (`lib/custom_code/widgets/index.dart`) or Action (`lib/custom_code/actions/index.dart`). ### Creating New Resource[​](/concepts/custom-code/vscode-extension.md#creating-new-resource "Direct link to Creating New Resource") To add a new Custom Action or Widget, create a new Dart file in the `lib/custom_code/widgets` or `lib/custom_code/actions` directory and the boilerplate should appear within the new file. To add a new Custom Function, simply create a new Dart function in the `lib/flutter_flow/custom_functions.dart` file. We do not have automatic support for Custom Function boilerplate code in Visual Studio Code at this time. ### Deleting Files[​](/concepts/custom-code/vscode-extension.md#deleting-files "Direct link to Deleting Files") To delete a Custom Action or Widget, delete the associated file. ### Adding New Dependencies[​](/concepts/custom-code/vscode-extension.md#adding-new-dependencies "Direct link to Adding New Dependencies") You can add custom [pub.dev](https://pub.dev/) package dependencies with the `Dart: Add Dependency` command from the Visual Studio Code command palette. This will update the `pubspec.yaml` file. ## Using Flutter Version Management (FVM)[​](/concepts/custom-code/vscode-extension.md#using-flutter-version-management-fvm "Direct link to Using Flutter Version Management (FVM)") If you want to manage Flutter versions with [**Flutter Version Management (FVM)**](https://fvm.app/), you need to install it and add it to your system’s PATH. Follow these steps to get started: ### Install FVM[​](/concepts/custom-code/vscode-extension.md#install-fvm "Direct link to Install FVM") To install **FVM**, run the following command in your terminal. This installs FVM globally using Dart’s package manager. ``` dart pub global activate fvm ``` ### Add FVM to Your System’s PATH[​](/concepts/custom-code/vscode-extension.md#add-fvm-to-your-systems-path "Direct link to Add FVM to Your System’s PATH") After installation, you need to add the directory containing FVM’s executables to your **PATH variable** so that it can be accessed globally. #### For macOS & Linux[​](/concepts/custom-code/vscode-extension.md#for-macos--linux "Direct link to For macOS & Linux") 1. Open the Terminal and run the following command. It adds the `~/.pub-cache/bin` directory to your system's `PATH` permanently by updating your `~/.zshrc` file. This ensures that the FVM installed in `~/.pub-cache/bin` is accessible from anywhere in the terminal. ``` echo 'export PATH="$PATH":"$HOME/.pub-cache/bin"' >> ~/.zshrc # For Zsh echo 'export PATH="$PATH":"$HOME/.pub-cache/bin"' >> ~/.bashrc # For Bash ``` 2. Restart your terminal or run `source ~/.zshrc` (or `source ~/.bashrc`) to apply the changes. #### For Windows[​](/concepts/custom-code/vscode-extension.md#for-windows "Direct link to For Windows") 1. Locate the **FVM executable path**, typically: ``` C:\Users\YourUsername\AppData\Local\Pub\Cache\bin ``` 2. Add this path to your **System’s PATH variable**: 1. Open **System Properties** → **Advanced system settings**. 2. Click **Environment Variables**. 3. Under **System variables**, select **Path** → **Edit**. 4. Click **New** and add the above path. 5. Click **OK** and restart your terminal. ### Verify the Installation[​](/concepts/custom-code/vscode-extension.md#verify-the-installation "Direct link to Verify the Installation") To check if FVM is correctly installed and accessible, run: ``` fvm --version ``` If this command prints the installed version of FVM, it means FVM is successfully installed and added to PATH. ### Configure FVM in Your Flutter Project[​](/concepts/custom-code/vscode-extension.md#configure-fvm-in-your-flutter-project "Direct link to Configure FVM in Your Flutter Project") Once FVM is installed, navigate to your Flutter project folder and set up FVM: ``` cd your-flutterflow-project fvm init fvm install fvm use ``` *(Replace `` with the required Flutter version.)* ## FAQs[​](/concepts/custom-code/vscode-extension.md#faqs "Direct link to FAQs") How do I download code from the Beta or Enterprise version of FlutterFlow? If you're using a different version of FlutterFlow, such as *Beta* or *Enterprise*, you can override the URL by modifying the **Extension Settings > settings.json** file. For example: * For the **Beta** version, set the `flutterflow.urlOverride` value to `https://api-beta.flutterflow.io/v1`. * For the **Enterprise** version, set the `flutterflow.urlOverride` value to `https://api-enterprise-[region].flutterflow.io/v1` (replace \[region] with your specific region). --- # Design System A design system is a guideline to create a consistent UI/UX across the app. A design system includes colors, typography, fonts, icons, app assets, a nav bar, an app bar, and pre-designed UI components such as buttons and text widgets. This is especially helpful when you are working in a team of builders and designers in a large company. Let's say you have an app with several different features and pages, each with its unique design. However, you notice that you are starting to create inconsistencies in the design across different pages, such as using different colors, fonts, and layouts. To solve this issue, you can create a design system outlining common design guidelines. Then, the team members can use this design system, which ensures the design remains consistent. [Sharing a Project with a User](https://www.youtube.com/embed/moP9VtkoyjY) ## Adding Design System[​](/concepts/design-system.md#adding-design-system "Direct link to Adding Design System") You can add a design system from the [Library](/resources/projects/libraries.md) dependencies added to your project. A library can serve as a central repository for your design assets, components, and styles—effectively becoming a Design Library for your application(s). possible use cases * **Enterprise Applications:** Large organizations can develop a centralized design system as a library to ensure all internal applications maintain a cohesive look and feel, enhancing brand identity and user experience. * **Startup MVPs:** Startups can expedite the development of Minimum Viable Products (MVPs) by leveraging a pre-built design system library like [**shadcn**](https://marketplace.flutterflow.io/item/cNlm0zWW1Nfq11cFXBmp), allowing them to focus on functionality and user validation. * **Cross-Platform Consistency:** Teams aiming to deploy apps across multiple platforms (iOS, Android, Web) can use a popular platform based design system library to ensure uniformity in design, reducing the effort required for platform-specific adjustments. To add a design system from a library, start by creating the design system in a new FlutterFlow project and [publishing it as a library](/resources/projects/libraries.md#publishing-a-library). Next, [import](/resources/projects/libraries.md#importing-a-library) that library into the project where you want to use the design system. Then, navigate to **Theme Settings > Design System** and click **No Design System Selected**. From the dropdown that appears, **Select a library** you’ve just imported to apply its design system to your project. ## Import Figma Theme[​](/concepts/design-system.md#import-figma-theme "Direct link to Import Figma Theme") You can bring your Figma design system directly into your FlutterFlow project. This streamlines the design-to-development process by automatically importing colors and typography from your Figma file, helping you maintain visual consistency and reduce manual effort. To import a Figma theme into your FlutterFlow project, go to **Theme Settings > Design System** and click **Connect To Figma**. Authenticate your account and grant access to Figma. Once connected, paste your Figma file URL to fetch the theme. You’ll see a list of all imported colors; start mapping them to your project colors. You can filter these colors by whether they’re mapped or unmapped, and you also have the option to bulk delete any imported colors. After that, you can customize your project typography using the imported text styles. info All imported colors are accessible anytime under **Colors > Custom Colors**. [Sharing a Project with a User](https://demo.arcade.software/84lqVC1ZDkq7EFFnCusm?embed\&show_copy_link=true) If you prefer watching a video tutorial, here is the guide for you: [Sharing a Project with a User](https://www.youtube.com/embed/kWvWa5PSWhw) *** ## Loading Indicators[​](/concepts/design-system.md#loading-indicators "Direct link to Loading Indicators") To customize the **Loading Indicators** used in the app, you can make changes in this section. You have the option to specify the **Indicator Type**, **Color**, and **Radius**, and the preview of the changes will be displayed below. [Sharing a Project with a User](https://demo.arcade.software/6OiSlYPiCEY1p3fg0kpG?embed\&show_copy_link=true) tip Avoid mis-sized loading indicators or components, which lead to jumping layouts. Ensure loading components match the size and position of the content they replace. If you prefer watching a video tutorial, here is the guide for you: [Sharing a Project with a User](https://www.youtube.com/embed/3sG-O1lkv0M) *** ## Scrollbar Theme[​](/concepts/design-system.md#scrollbar-theme "Direct link to Scrollbar Theme") From here, you can customize the appearance of the scrollbar that shows up on scrollable elements like ListView, GridView, StaggeredView, Row, and Column. note The scrollbar currently shows up by default only on platforms where Flutter natively supports it, such as web and desktop environments. You can modify its color, adjust its thickness, give it a rounded border, and more. In the 'Preview' section, you'll also be able to see the immediate visual effect of your changes. Here are all the properties you can customize: 1. **Thumb Color:** This changes the color of the draggable portion of the scrollbar, often called the "thumb". ![thumb-color](/assets/images/thumb-color-bfd24701e544df03230cdec7f59dc6c2.avif) 2. **Thickness:** This increases width (in a vertical scrollbar) or height (in a horizontal scrollbar). ![thickness](/assets/images/thickness-e92ffc854e7214cf4e6fd84c946acb2b.avif) 3. **Border Radius:** This sets the curvature of the scrollbar's corners. By adjusting the border-radius, you can give the scrollbar a more rounded appearance (higher values) or a more squared appearance (lower values). ![border-radius](/assets/images/border-radius-2c41be300659fafb6f852df4df63de3a.avif) 4. **Min Thumb Length:** This refers to the smallest size that the draggable portion (thumb) of a scrollbar can be. This ensures that users can always see and interact with the thumb, even when the content is very long. ![min-thumb-length](/assets/images/min-thumb-length-7c35793294a21ba25b9092cadc1c6010.avif) 5. **Main Axis Margin:** This refers to the space or gap along the primary direction of the scrollbar. For instance, in a vertically scrolling list, it refers to the top and bottom spacing, and in a horizontally scrolling list, it refers to the left and right spacing. ![main-axis-margin](/assets/images/main-axis-margin-a8bb16785cca8391f53b0d75a7186802.avif) 6. **Cross Axis Margin:** This refers to the space or gap along the cross direction of the scrollbar. For instance, in a vertically scrolling list, it refers to the left and right spacing, and in a horizontally scrolling list, it refers to the top and bottom spacing. ![cross-axis-margin](/assets/images/cross-axis-margin-53328bce34a6c1272ee4b41bcd5771dc.avif) 7. **Thumb Always Visible:** This determines whether the draggable "thumb" element of the scrollbar constantly remains visible or fades out when not in use. When enabled, you can also specify whether to show the track as well with custom color and border color. 8. **Interactive**: Using this property, you can set different colors for different states of the thumb, including when it's hovered over or being dragged. ![interactive](/assets/images/interactive-0737de8ab3ff2050e4cfb129b96d39b8.gif) *** ## Pull to Refresh Style[​](/concepts/design-system.md#pull-to-refresh-style "Direct link to Pull to Refresh Style") From here, you can customize the appearance of the pull to refresh (i.e., the loading circle). You can modify its color, background color, and stroke width. In the 'Preview' section, you'll also be able to see the immediate visual effect of your changes. [Sharing a Project with a User](https://demo.arcade.software/KHdvetH4Eg46TfDmZQUJ?embed\&show_copy_link=true) ## Colors[​](/concepts/design-system.md#colors "Direct link to Colors") This section allows you to customize the colors of your app, giving you control over the visual appearance of your application. From here, you can configure colors for both light and dark themes. Additionally, you can preview existing theme colors, import colors from Coolors, and even extract colors from images. ### Add or replace color[​](/concepts/design-system.md#add-or-replace-color "Direct link to Add or replace color") By default, we add 16 predefined colors for light and dark themes. However, you might want to add a new color or replace the existing color to align better with your brand identity. To add a new color: 1. Click **Add Color** button. 2. The new color will be added as **Custom Color.** Click on it and enter the [Hex color value](https://www.w3schools.com/colors/colors_hexadecimal.asp). 3. You can also edit the name of the custom color. 4. Click **Use Color**. To update an existing color in a light and dark mode theme, click on the color and enter the hex color value. ### Explore Project Colors[​](/concepts/design-system.md#explore-project-colors "Direct link to Explore Project Colors") We allow you to browse through the commonly used colors in your app and some pre-defined color schemes that might align with your app branding. To do so: 1. Click on the **Explore Project Colors**. 2. **Select page** you want to preview. 3. To find common colors, scroll down and click **Find Common Colors**. This will list out all the colors being used in the app. Use the 'done' and 'cancel' icons to accept or reject colors. 4. To explore the pre-defined color schemes, switch to the **Explore** tab, scroll through all the schemes, and tick to see the preview. 5. Click the 'refresh' button to get back to the original theme. 6. To proceed further, click **Save Changes**. ### Import from Coolors[​](/concepts/design-system.md#import-from-coolors "Direct link to Import from Coolors") Importing colors from [Coolors](https://coolors.co/) website is a quick and easy way to add your preferred color scheme to your app. Coolors offers a vast library of color palettes that you can import with just a few clicks, saving you time and effort in creating your own custom color palette. To import from Coolors: 1. Go to the [coolors.co](https://coolors.co/palettes/trending), identify the palette you would like to add, click on the **options menu** (three dots), and then click on the **Export palette**. 2. Now, select the **Code**, and then copy the contents below the `/* Object */` section. 3. Open your project, and navigate to **Theme Settings > Colors**. 4. Click on the **Import from Coolors** button. This will open a new popup window. 5. Paste the copied content and then click **Import**. New colors will be displayed under the **Custom Colors** section. ### Extract from Image[​](/concepts/design-system.md#extract-from-image "Direct link to Extract from Image") This feature provides an easy way to create visually striking themes by utilizing the colors present in an image. You can generate a color palette that harmonizes perfectly with the colors in the image, resulting in stunning designs that capture the essence of your image. To extract and use color from the image: 1. Navigate to **Theme Settings > Colors**. 2. Click the **Extract from Image** button and select the image. 3. A pop-up will appear that displays the extracted color from the image. To proceed further, click **Extract & Continue**. 4. In the next step, click on any color to see and select the extracted color. 5. Click **Done**. ### AI Generated Theme Colors[​](/concepts/design-system.md#ai-generated-theme-colors "Direct link to AI Generated Theme Colors") With 'AI Gen Theme,' simply describe the desired color theme for your app, such as 'Tiger in the Jungle' or 'Kids bedtime story,' and watch as a comprehensive color scheme tailored to your needs magically appears. ### Video guide[​](/concepts/design-system.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: ## Typography & Icons[​](/concepts/design-system.md#typography--icons "Direct link to Typography & Icons") This section puts you in complete control of your app's text styling. With options to add responsive and custom fonts, you can ensure your app looks unique and consistent across all screen sizes. Moreover, you can also add custom icons to your app, allowing you to create unique and visually appealing user interfaces. ### Define Text Styles (Typography/Fonts)[​](/concepts/design-system.md#define-text-styles-typographyfonts "Direct link to Define Text Styles (Typography/Fonts)") To change the font family at the project level, open the **Theme Settings** (from the navigation menu) **> Typography & Icons**, click on the button below the **Primary Font Family** or **Secondary Font Family,** and search and select the new font. info The *Primary Font Family* is the font that you will use the most throughout your app. The *Secondary Font Family* is the font that you will use to serve slight variation or contrast to the primary font. You can customize the following properties of each text style: * **Font Size** - Use this to specify the size of the text. * **Letter Spacing** - Use this to set the space between characters. * **Italic** - Checkbox for enabling *Italic* font style. * **Font Weight** Choose the font weight among *Thin, Extra Light, Light, Normal, Medium, Semi Bold, Bold, Extra Bold & Black*. * **Color** - Set the color of the text using either the color picker or by specifying a Hex value. * **Font Family** - You can change the Font Family for any style from here. Click here to set the font family from [*Google Fonts*](https://fonts.google.com/) or choose from the uploaded Custom Fonts. You can also choose whether this style is a *Primary* or *Secondary Font Family*. You can also create fully custom text styles to match your design needs, going beyond the default styles like Display, Headline, or Title. Simply click the **+ Add Custom Text Style** button, a new text style will be added at the bottom, then edit the style name and customize the style properties. ![typography](/assets/images/typography-94af7225f1856e4d9f5de6f0ede5d83a.avif) PLANS Custom Text Styles are available on the **Business** plan and higher. Check our [**pricing plans**](https://flutterflow.io/pricing). #### Adding responsive text styles[​](/concepts/design-system.md#adding-responsive-text-styles "Direct link to Adding responsive text styles") When developing a mobile app, it's important to consider the different platforms on which it will run. You might notice that the text looks smaller on platforms with higher screen resolution, such as tablets, web, or desktops. This can impact the user experience and make your app difficult to read. To solve this issue, you can add responsive text that adjusts the font size based on the platform. See how the texts are displayed with and without responsive font style: * With responsive Text * Without responsive text ![with-responsive-text](/assets/images/with-responsive-text-e92f3ca8860018d5c26a5adf95ede1cf.avif) ![without-responsive-text](/assets/images/without-responsive-text-58150c933c57c50652d32bed6fbb01cf.avif) You can add the responsive style by following the instructions below: 1. Open the **Theme Settings** (from navigation menu) **> Typography & Icons**. 2. Click on the **Make Responsive** button. 3. Now, all the styles are available under the three tabs. *Mobile*, *Tablet*, and *Desktop*. Modify each style under the different platform tabs that you are supporting. 4. Run the app and see how the texts are displayed by changing the platform. ### Custom Fonts[​](/concepts/design-system.md#custom-fonts "Direct link to Custom Fonts") Adding Custom Fonts to your app makes it stand out from others. This section allows you to upload your own fonts. You can upload the custom font files of types `.ttf`, `.otf`, and `.woff.` Once the font is uploaded, you can use it directly from the widget or add it to the text style section to create a general theme. info Before you upload the Custom Fonts, make sure you have permission to use the font in your application. To add the *Custom Fonts*: 1. Open the **Theme Settings** (from navigation menu) **> Typography & Icons**. 2. Scroll down to the **Custom Fonts** section. 3. Click on the **+ Add Font** button. 4. Enter the **Font Family Name** and click the **Upload File(s)** button. 5. Select and upload your font. 6. Click **Add Font**. The newly added font will be displayed. 7. To use a custom font directly in a widget, move to the property panel, click on the already applied font family, select the **Custom Fonts** tab, and then choose the font. 8. To use a custom font for a common text style, open the Text Styles section, click on the already applied font family, select the **Custom Fonts** tab, and then choose the font. If you prefer watching a video tutorial, here's the one for you: ### Custom Icons[​](/concepts/design-system.md#custom-icons "Direct link to Custom Icons") Custom icons help reinforce your brand identity and add a unique touch to your app. Before uploading icons to FlutterFlow, you’ll first need to generate them using an icon font generator like [FlutterIcon](https://www.fluttericon.com/) or [IcoMoon](https://icomoon.io/). We’ve also built our **[own SVG to Custom Icon Generator](https://icons.flutterflow.app)** to make the process even easier — feel free to use that instead. info Make sure you have the proper rights or licenses to use the icons in your application. **Steps to Generate and Add Custom Icons** 1. Head over to the [IcoMoon](https://icomoon.io/app/#/select). 2. Import your custom icon (.svg) or select from the free icons set. 3. Select the **Generate Font** tab. 4. Click on the Settings button (gear icon) beside the download text on the bottom right side. 5. Enable **Generate Dart class for Flutter**. 6. Click on the **Download** button and then extract the downloaded file. 7) Open your FlutterFlow project, navigate to the **Theme Settings** (from navigation menu) **> Typography & Icons**. 8) Scroll down to the **Custom Icons** section. 9) Click on the **+ Add Icons** button. 10) Click on the **Upload Icon File** button. 11) Select and upload `.ttf` file under the downloaded folder > fonts. 12) Now click on the **Upload Icon Info** button. 13) Select and upload the `filename.dart` under the downloaded folder (besides the fonts folder). 14) Click **Add Icons**. #### Use the Custom Icon[​](/concepts/design-system.md#use-the-custom-icon "Direct link to Use the Custom Icon") To use a custom icon, add the **Icon** widget, move to the properties panel, and scroll down to the **Icon** section. Click on the already selected icon, select the **Custom Icons** tab, and then select your icon. If you prefer watching a video tutorial, here is the guide for you: ## Theme Widgets[​](/concepts/design-system.md#theme-widgets "Direct link to Theme Widgets") Creating a theme for widgets ensures that your app looks consistent and has a cohesive design. The Theme widgets can be reused, making it easy to update the styles of your app. If you decide to change any property of the widget, such as color scheme or fonts, you can update the theme widget instead of going through every widget individually. This can save a lot of time and effort, especially in larger projects. For example, creating theme widgets for different types of buttons such as 'primary\_button', 'secondary\_button', and 'tertiary\_button' with specific attributes like width, color, icon, border radius, and padding. Then, these widgets can be directly added to a page or applied to an existing widget. ### Adding theme widgets[​](/concepts/design-system.md#adding-theme-widgets "Direct link to Adding theme widgets") To add a theme widget to your app, you must create it and then use it on your page by dragging it from the Widget Palette or applying it to the existing widget. Here's how you do it: 1. Open the **Theme Settings** (from the navigation menu) > **Theme Widgets**. 2. Click **Create Widget** button. 3. Enter the **Theme Widget Name** and then select the widget. 4. Create a theme for the widget using its properties available on the right side and then click **Save**. 5) You can also make any widget a theme widget by right-clicking and selecting **Save as Theme Style Widget**. 6. Now, you can add this widget directly from the widget tree or Widget Palette. 7) To apply this widget styling to an existing widget, select the widget, move the **Properties Panel > Widget Styling >** click **Theme Style Unset >** select the theme widget. warning After applying theme widget styling, any previously set properties will be overridden except the properties with *Set from Variable*. However, you are free to modify the existing widget properties as you like. ### Video guide[​](/concepts/design-system.md#video-guide-1 "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: ## FAQs[​](/concepts/design-system.md#faqs "Direct link to FAQs") How is the theme widget different from creating a template and component? The Theme Widget allows you to customize the visual appearance of a single widget, whereas templates consist of multiple widgets that create a unique UI layout with a specific purpose. On the other hand, components are fully-featured custom widgets that combine multiple widgets and actions to complete a task. --- # File Handling FlutterFlow makes it easy to manage, upload, download, and display files within your app. It supports a variety of file types, including images, videos, and documents, and integrates seamlessly with popular storage solutions. Using built-in widgets and actions, you can effectively manage your app's media. This guide covers the following key aspects of file handling in FlutterFlow. * **Media Assets**: Upload any assets you want to use in your app from the Navigation Menu > Media Assets. This also shows the media assets of the Team. * [**Uploading Files**](/concepts/file-handling/uploading-files.md): Upload and save different file types, including images, audio, videos, and PDFs to cloud storage. * [**Displaying Media**](/concepts/file-handling/displaying-media.md): Fetch files from cloud storage or external URLs and display them in your app. * [**Download Files**](/concepts/file-handling/download-file.md): Allow users to download files directly to their devices. * [**Clear or Delete Media**](/concepts/file-handling/clear-delete-media.md): Allow users to delete uploaded files from their devices and cloud storage. Also see * **Stream Media with Mux**: [**Integrate Mux's broadcasting**](/integrations/mux.md) services in FlutterFlow by using the MuxBroadcast widget for live streaming. * **Request Permissions**: [**Request user permissions**](/resources/projects/settings/project-setup.md#request-permission-action) when implementing custom widgets or actions that access personal information, such as capturing photos or selecting images, especially if no built-in permission mechanism is available. --- # Clear or Delete Media The **Clear** and **Delete** **Media** actions provide essential functionalities for managing media files efficiently. ## Clear Uploaded Data \[Action][​](/concepts/file-handling/clear-delete-media.md#clear-uploaded-data-action "Direct link to Clear Uploaded Data \[Action]") When users upload media files, these files are first stored in a local state variable, i.e., *Uploaded File URL* for immediate access and display. This action is helpful when you want to offer users a straightforward method to remove any uploaded media, such as images or recordings. info For this action to work, the [**Upload or Save Media**](/concepts/file-handling/uploading-files.md#upload-or-save-media-action) action must already be added to the actions workflow. ## Delete Data \[Action][​](/concepts/file-handling/clear-delete-media.md#delete-data-action "Direct link to Delete Data \[Action]") The **Delete Data** action permanently removes uploaded media—such as images, videos, and PDF files—from external storage platforms like [Firebase Storage](https://firebase.google.com/docs/storage) and [Supabase Storage](https://supabase.com/storage). Inside the **URL** section, provide a valid media URL. This must be either the direct **Uploaded File URL** or a variable that holds the URL. tip Always prompt users for confirmation before deleting media files to prevent accidental loss of data. --- # Displaying Media Displaying media efficiently is crucial for enhancing user experience in your FlutterFlow app. Whether you're working with images, audio, video, or PDFs, FlutterFlow provides flexible options for integrating and managing media. This guide covers how to set media sources, customize playback settings, and implement best practices like lazy loading, caching, and BlurHash to optimize performance. ## Media Types[​](/concepts/file-handling/displaying-media.md#media-types "Direct link to Media Types") To display media on widgets, navigate to the **Properties Panel** and specify the media source under the **\[Media] Type** option (e.g., ImageType, AudioType, VideoType). Here are the available options: ### Network[​](/concepts/file-handling/displaying-media.md#network "Direct link to Network") Enter the URL of the media directly into the **Path** input field. This is for media hosted online. ![dm-network-path.avif](/assets/images/dm-network-path-b78e3f6052ed5884abf91f49e0ba4c1f.avif) If your media is uploaded to Firebase or Supabase, click **Set from Variable** on the **Path** input field, and select **Source** as **Widget State > Uploaded File URL**. ![dm-uploaded-file.avif](/assets/images/dm-uploaded-file-17833124a8c84bf658089bedabff8bcb.avif) For media uploaded via an API, choose **Source** as **Action Outputs > \[Action Output Variable Name] (API Response)**. Ensure that the API response contains the URL of the uploaded file. Learn how to extract the URL using [JSON path](/resources/backend-logic/rest-api.md#json-path). ![dm-api.avif](/assets/images/dm-api-266ac1624c53096b9476c369636e9f15.avif) info To handle scenarios where media takes time to load or fails to load, you can set a placeholder. Click **Set from Variable** on the **Path** field and specify a placeholder URL under the **Default Value** property. ### Asset[​](/concepts/file-handling/displaying-media.md#asset "Direct link to Asset") You can also display media files uploaded to your **Assets**. Assets are resources such as images, videos, documents, fonts, and other files that you include locally in your project. To upload assets, click on **Media Assets** in the left-side navigation menu and add files directly from your device. Alternatively, you can directly upload and display files when configuring media widgets by clicking the upload icon. tip For more details on how assets are stored in your project, see the directory [**Assets**](/generated-code/project-structure.md#assets) in the generated code. ![select-from-assets](/assets/images/select-from-assets-28f9446ec024836e64cb61fab0d1d70a.avif) ### Uploaded File[​](/concepts/file-handling/displaying-media.md#uploaded-file "Direct link to Uploaded File") You can also access media files within your app that are stored temporarily in your application. For example, if you'd like to preview an image before sending it to cloud storage, you can do so by setting the source to **Widget State -> Uploaded Local File**. ![dm-local-upload.avif](/assets/images/dm-local-upload-382c03139589aff098665200c4febdc2.avif) ## AudioPlayer[​](/concepts/file-handling/displaying-media.md#audioplayer "Direct link to AudioPlayer") The **AudioPlayer** widget allows you to integrate audio playback into your apps. You can play audio from both uploaded assets and external URLs. Refer to the [**Displaying Media**](/concepts/file-handling/displaying-media.md#media-types) section for more details on accessing media. Generated Code The AudioPlayer widget in FlutterFlow uses the [**assets\_audio\_player**](https://pub.dev/packages/assets_audio_player) package for audio playback. **Customization Options** * **Title:** Specify the audio title in the **Title** property. You can set this directly or bind it to a variable, such as an app state variable, API response, or Firestore document. * **Pause on Forward Navigation:** By default, the audio stops when navigating to another page. * **Play in Background:** Define how the audio behaves when the app moves to the background: * **Enabled:** The audio continues to play. * **Disabled, restore on foreground:** The audio pauses and resumes when the app becomes active again. * **Disabled, pause:** The audio stops immediately when the app goes into the background. * **Colors:** * **Background Color:** Customize the background using the **Fill Color** property. * **Playback Button Color:** Adjust the colors of the play and pause buttons. * **Active/Inactive Track Color:** Change the progress bar color that indicates the current playback position. * **Elevation:** Use the **Elevation** property to modify the shadow beneath the audio tile. A higher value increases the shadow size, while setting it to 0 removes the shadow. * **Text Styling:** * **Title Text:** Personalize the title’s font, size, and color in the **Title Text Style** section. * **Playback Duration Text:** Adjust the style of the playback duration text in the **Playback Duration Text Style** section. ## Audio Recording[​](/concepts/file-handling/displaying-media.md#audio-recording "Direct link to Audio Recording") You can implement audio recording functionality using the **Start Audio Recording** and **Stop Audio Recording** actions. warning Currently, audio recording is not supported in **Run** or **Test** modes due to certain limitations. ### Start Audio Recording \[Action][​](/concepts/file-handling/displaying-media.md#start-audio-recording-action "Direct link to Start Audio Recording \[Action]") This action starts the recording. It also provides a name to the recording, which you can use later to stop the recording using the [Stop Audio Recording](/concepts/file-handling/displaying-media.md#stop-audio-recording-action) *action.* Before adding this action, ensure you [request microphone permission](/resources/projects/settings/project-setup.md#request-permission-action). Within the **TRUE** block of the permission condition check, add the **Start Audio Recording** action. By default, the **Name** field value is a randomly generated string. You can change it to a more descriptive name for easier identification. tip After starting recording, you might want to update the state variables to reflect changes on the UI. For instance, you can enable/disable buttons or start recording animations to provide a visual cue of the ongoing process. This step allows you to enhance the user experience and provide real-time feedback during the recording. ![start-audio-recording.avif](/assets/images/start-audio-recording-061566456f74b9bcd7abc36d2d21fb1f.avif) ### Stop Audio Recording \[Action][​](/concepts/file-handling/displaying-media.md#stop-audio-recording-action "Direct link to Stop Audio Recording \[Action]") If you have multiple audio recording actions, all the Recorder object names (either auto-generated by FlutterFlow or manually set by the user) are listed under the Recorder Name dropdown. Choose the recorder object you want to stop, and it will stop the ongoing recording. To capture and play the recorded audio, make sure to specify the *Action Output Variable Name*, which can be used with the audio player. Here’s how you can setup this action: 1. When you add this action, choose the **Recorder Name** from the dropdown. This will be the name you provided in the Start Audio Recording action. 2. Specify the **Action Output Variable Name**. This will store the actual audio recording, which you can use with any audio player. It stores recording in an **Audio Path** data type. 3. If you want to upload the audio recording to Firebase or Supabase, you can use the [Upload file](/concepts/file-handling/uploading-files.md#upload-or-save-media-action) action. When you add this action: 1. Set the **Upload Type** to the preferred one. 2. Set **File Type** to **Uploaded File** because the *Stop Audio Recording* action internally stores recorded audio bytes (inside widget state). 3. Set the **File to Upload** to **Widget State > Recorded File**. 4. For uploading via API, *you don't need to add the Upload file action*. Just directly add the [**API call**](/resources/backend-logic/rest-api.md) and select the API that will upload the file to your server. **Note** that the request body for this API must be in *Multipart* format. You can pass the audio recording via **Widget State > Recorded File** in the API variable. See how to [configure an API for the multipart request body](/resources/backend-logic/rest-api.md#multipart-format). tip * After stopping the recording, you might want to update the state variables to reflect changes on the UI. For instance, you can enable/disable buttons or stop recording animations. * It's always a good idea to have a fail-safe mechanism to ensure recordings are properly stopped, even if the user forgets to do so manually. For example, you can use the [**On Dispose**](/resources/ui/pages/page-lifecycle.md#on-dispose-action-trigger) action trigger to stop recording when a user closes the app without manually stopping it. ### Playing audio recording[​](/concepts/file-handling/displaying-media.md#playing-audio-recording "Direct link to Playing audio recording") After you have stopped the recording, you can simply provide the *Action Output Variable Name* to the [Audio Player](/concepts/file-handling/displaying-media.md#audioplayer) widget to start playing the recorded audio. ## Play or Stop Sound[​](/concepts/file-handling/displaying-media.md#play-or-stop-sound "Direct link to Play or Stop Sound") The **Play Sound** and **Stop Sound** actions offer flexibility for enhancing the user experience with audio effects or background sounds. ### Play Sound \[Action][​](/concepts/file-handling/displaying-media.md#play-sound-action "Direct link to Play Sound \[Action]") The **Play Sound Action** allows you to play a sound that notifies users about the action they have taken—for example, playing a sound after refreshing a list or sending a message. tip It is advisable to use this action only for short audio. To play the more extended audio, consider adding the [**AudioPlayer**](/concepts/file-handling/displaying-media.md#audioplayer) widget. By default, this action is assigned a random **Name** to be stopped later using the [Stop Sound](/concepts/file-handling/displaying-media.md#stop-sound-action) action. You can adjust the volume using the **Volume** slider (0.0 = mute, 1.0 = full volume). The action is non-blocking by default, allowing subsequent actions to trigger immediately. To wait until playback finishes before proceeding, enable the **Await Playback** option. Use cases * **Feedback Sounds:** Play sounds for button clicks, form submissions, or error alerts to improve user interaction and feedback. * **Notifications:** Play sound alerts for reminders, messages, or task completion. * **Gamification:** Enhance gaming experiences with sound effects for achievements, levels, or interactions. ### Stop Sound \[Action][​](/concepts/file-handling/displaying-media.md#stop-sound-action "Direct link to Stop Sound \[Action]") You can stop a sound that is currently playing, which was started by the [Play Sound](/concepts/file-handling/displaying-media.md#play-sound-action) action. For example, If your app is playing any sound effects, you may need to stop them when the app is paused or stopped. info This action is enabled only when you have added a [**Play Sound**](/concepts/file-handling/displaying-media.md#play-sound-action) action on a page. ## VideoPlayer[​](/concepts/file-handling/displaying-media.md#videoplayer "Direct link to VideoPlayer") The **VideoPlayer** widget is used to show a video from uploaded assets or the URL link. The VideoPlayer widget can play various video formats such as MP4, MOV, WAV, MPEG, and JPEG motion photos. Refer to the [**Displaying Media**](/concepts/file-handling/displaying-media.md#media-types) section for more details on accessing media. Generated Code The VideoPlayer uses the [**video\_player**](https://pub.dev/packages/video_player) package for reliable video playback across different platforms. **Customization Options** The **VideoPlayer** widget includes several options to align with your app's design and functionality: * **Aspect Ratio:** Set the desired aspect ratio (e.g., 1.7 for a 16:9 ratio) to ensure the video displays correctly. * **AutoPlay:** Enable this option to automatically start playing the video when the page loads. * **Loop Video:** Choose whether the video should replay automatically after it ends. * **Show Controls:** Display playback controls, including play/pause buttons and the seek bar. * **Allow Full Screen:** Enable users to expand the video to full-screen mode. * **Playback Speed Menu:** Let users adjust the video playback speed. * **Load on Page Load:** When enabled, the video will preload when the page loads, reducing buffering time when the user starts playback. * **Pause on Forward Navigation:** If enabled, the video will pause automatically when the user navigates away from the page. ## YoutubePlayer[​](/concepts/file-handling/displaying-media.md#youtubeplayer "Direct link to YoutubePlayer") The **YouTubePlayer** widget in FlutterFlow allows you to integrate and play YouTube videos within your app. It offers customizable playback options and an intuitive interface for enhancing the user experience. Generated Code The YoutubePlayer uses a custom version of the [**youtube\_player\_iframe**](https://pub.dev/packages/youtube_player_iframe) package, hosted on FlutterFlow's GitHub repository. **Customization Options** * **Loop Video:** When enabled, the video will automatically replay after it finishes. * **Mute Video:** Starts the video in a muted state. * **Show Controls:** Displays playback controls such as play/pause, volume, subtitles, and fullscreen options. * **Show Full Screen Control:** This specifically displays the fullscreen toggle button among the controls. * **Pause on Forward Navigation:** Automatically pauses the video when the user navigates away from the page. * **Strict Related Videos:** Ensures that related videos shown at the end of playback come from the same channel as the currently played video. ## PdfViewer[​](/concepts/file-handling/displaying-media.md#pdfviewer "Direct link to PdfViewer") In FlutterFlow, the **PdfViewer** widget enables you to display PDF files within your app, supporting both network URLs and locally uploaded assets. Refer to the [**Displaying Media**](/concepts/file-handling/displaying-media.md#media-types) section for more details. Generated Code The PdfViewer in FlutterFlow uses the [**pdfx**](https://pub.dev/packages/pdfx) package for rendering PDFs. **Customization Options** * **Horizontal Scroll:** By default, the PdfViewer allows vertical scrolling through pages. Enable this option to allow horizontal scrolling. * **Use Proxy:** By default, FlutterFlow routes PDF fetching through a proxy in **Run Mode** and **Test Mode** to avoid CORS (Cross-Origin Resource Sharing) issues. **Switch this off** if you do not want the PDF request to be routed through the proxy. * **Use Custom Proxy URL:** If you need a specific proxy, enable this option and provide your own proxy URL instead of using FlutterFlow’s default proxy. ## Web Access for PDFs and Other Files[​](/concepts/file-handling/displaying-media.md#web-access-for-pdfs-and-other-files "Direct link to Web Access for PDFs and Other Files") Some types of files require additional configuration to be accessed on the web. In particular, the PDF Viewer requires network-hosted files (such as uploaded PDFs) to allow Cross-Origin Resource Sharing (CORS). For a deeper understanding of Cross-Origin Resource Sharing (CORS), you can refer to this guide. The key takeaway is that to allow users to upload and view PDFs using Firebase Storage, follow the steps below. You'll need to run a few commands to enable CORS for your Firebase project. No programming experience is required, but if you're comfortable with Firebase, you can refer to the official guide here: [Firebase CORS Configuration](https://firebase.google.com/docs/storage/web/download-files#cors_configuration). **Step 1: Find Your Firebase Project ID** You can find the Firebase project ID from **FlutterFlow > Settings and Integrations > Firebase**. Copy your **Firebase** **Project ID**. ![copy-firebase-project-id.avif](/assets/images/copy-firebase-project-id-8d7691404188fe706b09cf96b1a0c471.avif) **Step 2: Open Cloud Shell in Google Cloud Console** 1. Go to the following link, replacing **FIREBASE\_PROJECT\_ID** with your actual project ID: ``` https://console.cloud.google.com/home/dashboard?cloudshell=true&project=FIREBASE_PROJECT_ID ``` 1. If prompted, click **Continue**. 2. You should see a terminal at the bottom of the screen. If your project ID is not displayed in yellow, click the **down arrow** (🔽) next to the project name and select the correct Firebase project. ![cloud-shell](/assets/images/cloud-shell-2cfa3917b0d0c7a1d67f607c32776a87.avif) **Step 3: Run the CORS Configuration Command** 1. Click on the **Cloud Shell terminal** (the black screen). 2. Copy and paste the following command and replace `` with your actual storage bucket. To locate your Firebase Storage bucket name, navigate to Firebase Console > Storage > at top left side, you'll see your bucket's URL, which typically follows the format `your-project-id.appspot.com`. ``` touch cors.json && \ echo '[{"origin": ["*"], "method": ["GET"], "maxAgeSeconds": 3600}]' > cors.json && \ gsutil cors set cors.json gs:// ``` ![storage-bucket.avif](/assets/images/storage-bucket-2a38c52febd3dfc98c9b5ef68b90a86d.avif) 3. Press **Enter** (or **Return**) to execute the command. 4. If prompted, click **Authorize** to allow Cloud Shell to access your Firebase project. 5. Once the command executes successfully, you should see a confirmation message. ![cors-3](/assets/images/cors-3-59c800255b68c5e2f05da6b7ec2e748b.png) ## BlurHash[​](/concepts/file-handling/displaying-media.md#blurhash "Direct link to BlurHash") In FlutterFlow, **BlurHash** is a technique used to enhance the user experience by displaying visually appealing placeholders while images are loading. Instead of showing empty spaces or generic loading indicators, BlurHash generates a blurred preview that resembles the actual image, providing users with a smoother and more engaging experience. ![blurhash.avif](/assets/images/blurhash-0048ec5ea8985f9d4315c0af8f6003bb.avif) Here are the steps to generate and use the BlurHash: 1. When using the [**Upload/Save Media**](/concepts/file-handling/uploading-files.md#upload-or-save-media-action) action to upload images, you can enable the **Include Blur Hash** option. This setting automatically generates a BlurHash string for the uploaded image. ![enable-blurhash.avif](/assets/images/enable-blurhash-5eaf03e450524845bce4c0ee4d7a8d6b.avif) 1. After generating the BlurHash, it's advisable to store it alongside the image URL in your database (e.g., Firestore). The generated BlurHash is accessible via the **Widget State > Uploaded Local File > Media Blur Hash**. This approach ensures that both the image URL and its corresponding BlurHash are readily accessible when needed. ![save-blurhash.avif](/assets/images/save-blurhash-0a1b9edfce912604c4e320cffbd80525.avif) 1. To utilize the BlurHash as a placeholder, in the Image widget's properties, enable the **Use Blur Hash** option and then set the **Blur Hash String** value from a variable. ![use-blurhash.avif](/assets/images/use-blurhash-5eb017934e37e5f292dc0e0693ca198a.avif) ## Best Practices[​](/concepts/file-handling/displaying-media.md#best-practices "Direct link to Best Practices") * Enable [infinite scrolling](/resources/ui/widgets/composing-widgets/list-grid.md#adding-infinite-scroll) (lazy loading) in list views to load additional content as users scroll, rather than loading all data at once. * Leverage FlutterFlow's built-in cache manager, which automatically handles image caching. * Implement [local caching](/resources/backend-query.md#backend-query-caching) to store frequently accessed data on the device, reducing the need for repeated network requests. * Reduce the number of network calls by fetching only necessary data and utilizing caching strategies. * Ensure that database queries are efficient and retrieve only the data required for display. * Use [BlurHash](/concepts/file-handling/displaying-media.md#blurhash) to display a blurred preview of images while they load, enhancing the user experience. * Display loading indicators to inform users that data is being fetched, improving perceived performance. --- # Download File The **Download File** action allows you to enable users to download or save files locally on their devices. File Download Location * **Windows, macOS, Linux, and Web**: Files are saved in the **Downloads** folder by default. * **iOS**: Files are downloaded in the **Application Documents Directory**. * **Android**: Files are saved in the application's directory at `Android/data/your.package.name/files/your_file.extension`. ## Download File \[Action][​](/concepts/file-handling/download-file.md#download-file-action "Direct link to Download File \[Action]") To add a Download File action, select the **Widget** (e.g., button or any interactive widget) where you want users to initiate the file download and set the **Source** to one of the following. * **From URL**: Use this option for downloading files that are accessible through a direct link and specify the URL of the file that should be downloaded. * **From File (Bytes)**: Use this option when the file is uploaded to the device using the [Local Upload (Widget State)](/concepts/file-handling/uploading-files.md#local-upload-widget-state). You can access the file via ***Widget State > Uploaded Local File***. Optionally, you can specify a **Filename** to be used when the file is downloaded. ![file-download-action](/assets/images/file-download-action-d1fa481c006877dbdc588f6d3f713918.avif) --- # Uploading Files Uploading files is an essential feature for many apps, enabling users to share images, videos, documents, and more. FlutterFlow offers flexible actions to handle file uploads, whether you’re using Firebase, Supabase, or your own backend server. You can customize the upload process to suit your app’s needs, such as resizing media, setting quality, or temporarily storing files locally before uploading. This guide covers the available upload methods, configuration options, and workflows, including how to save media locally and upload it via an API. ## Types of Media Uploads[​](/concepts/file-handling/uploading-files.md#types-of-media-uploads "Direct link to Types of Media Uploads") FlutterFlow provides three methods for uploading media files, each catering to different needs: ### Firebase[​](/concepts/file-handling/uploading-files.md#firebase "Direct link to Firebase") Media files can be uploaded directly to **Firebase Storage**, a reliable cloud-based solution. Once the upload is complete, you can use the **Widget State > Uploaded File URL** to preview the media or store the file URL for later use. ![upload-type-firebase.avif](/assets/images/upload-type-firebase-456f096ed9a757798c5a1213e90035b5.avif) ### Supabase[​](/concepts/file-handling/uploading-files.md#supabase "Direct link to Supabase") You can upload media to a **Supabase bucket** at a specified location. After the upload, the file's URL is accessible via **Widget State > Uploaded File URL**, enabling you to preview the media or save the URL for later use in your app. ![upload-type-supabase.avif](/assets/images/upload-type-supabase-dd59212b4c2981e164125df7f5353a1e.avif) ### Local Upload (Widget State)[​](/concepts/file-handling/uploading-files.md#local-upload-widget-state "Direct link to Local Upload (Widget State)") This method initially stores your media on the device, making it accessible via **Widget State > Uploaded Local File**. You can preview, edit, or process the file before uploading it to a cloud storage. ![upload-type-local-and-api.avif](/assets/images/upload-type-local-and-api-141c3fa9b729007f50dfb7a245a6b140.avif) ## Upload or Save Media \[Action][​](/concepts/file-handling/uploading-files.md#upload-or-save-media-action "Direct link to Upload or Save Media \[Action]") This action allows you to upload a photo or video to your app. You can choose to store the file on [Firebase](/concepts/file-handling/uploading-files.md#firebase), [Supabase](/concepts/file-handling/uploading-files.md#supabase) storage, or your own server using an API. Once uploaded, you can access the file through its generated URL. This URL can be used to display the content immediately or store it in a database for future retrieval. Prerequisites for Firebase 1. **Firebase** should be connected to your project. Follow the instructions on [**this page**](/integrations/database/cloud-firestore/getting-started.md) for integrating Firebase with FlutterFlow. 2. **Firebase Authentication** must be properly configured. Check out [**this page**](/integrations/authentication/firebase/initial-setup.md) for setting up authentication. 3. **Firebase Storage** must be set up and properly configured. It takes just a second! Follow the instructions on [**this page**](/integrations/firebase-storage/storage-rules.md). 4. At least one **Firebase Collection** should be configured for the project so that you can store the generated URL. Prerequisites for Supabase 1. Make sure to [**integrate Supabase**](/integrations/supabase/setup.md) into your app. 2. [**Create a storage bucket**](https://supabase.com/docs/guides/storage/quickstart#create-a-bucket) in Supabase. By default, the **Public bucket** option is disabled, meaning uploaded media is not accessible by anyone without authentication. If needed, you can enable the Public bucket option, but this is not recommended for sensitive content. ![supabase-storage-bucket.png](/assets/images/supabase-storage-bucket-7c69ea6354974d2fa3a080cf97b48106.png) 3. Apply additional [**security rules**](https://supabase.com/docs/guides/storage/quickstart#add-security-rules) which determine who can access the bucket. **Tip**: If you are uploading to a folder structure like this '*pics/uploads*,' here is how you can add a policy that allows only authenticated users to upload their profile picture. To create an upload media workflow, add the **Upload/Save Media** action to the widget (e.g., a button or any interactive element) where you want users to initiate the file upload. Next, set the [**Upload Type**](/concepts/file-handling/uploading-files.md#types-of-media-uploads). In the **Media Type/Source** section, specify the type of media to upload: photo, video, or both. Then, use the **Media Source** dropdown to choose the source of the media: * **Camera**: Directly capture media using the device's camera. * **Gallery**: Select existing media from the device's gallery. * **Either Camera or Gallery**: Allows users to choose the source via a bottom sheet, letting them select either the camera or the gallery as the media source. Once the media is uploaded, see how to display it on a widget in the [next section](/concepts/file-handling/displaying-media.md). info When you set **Upload Type** to: * **Firebase**: You must [**deploy the storage rules**](/integrations/firebase-storage/storage-rules.md). * **Supabase**: Provide the **Bucket Name** and set the **Uploaded Folder Path** (e.g., pics/uploaded). This is the path where the media will be uploaded. The Upload Media action offers various settings to control how media files are uploaded, resized, and processed in your app. Below is a breakdown of all the available properties. ![configure-upload-media-action.avif](/assets/images/configure-upload-media-action-2c7983d290d8de71a3b58c526b9fd093.avif) * **Max Width** and **Max Height**: If you are uploading a photo, you can set a maximum width and height using these properties. This resizes the image while maintaining its original aspect ratio. * **Image Quality**: Control the image quality by adjusting the slider or entering a value between 0 and 100, where 100 retains the original quality. * **Include Media Dimensions**: Enable this option to retrieve the dimensions (width and height) of the uploaded media. Keep in mind that this operation is resource-intensive, so enable it only if necessary. * **Include Blur Hash**: Automatically generates a BlurHash for the uploaded image, allowing you to display a blurred placeholder while the full image loads. For more information, refer to the [BlurHash](/concepts/file-handling/displaying-media.md#blurhash) section. * **Source Picker Style**: Customize the appearance of the bottom sheet UI that appears when selecting a media source (e.g., Camera or Gallery). * **Allow Multiple Images**: Enable this option to allow users to select multiple images. Note that this requires the **Media Source** to be set to **Gallery**. Once multiple images are uploaded, you can access their URLs via **Set from Variable menu > Widget State > Uploaded File URLs (`List`)**. * **Show Snackbar**: Enable this option to notify users about the upload progress with a snackbar message. Check out our YouTube video for a detailed explanation of the **Upload or Save Media \[Action]** in FlutterFlow. ### Store Media for Upload[​](/concepts/file-handling/uploading-files.md#store-media-for-upload "Direct link to Store Media for Upload") You can also save the media file temporarily on the device before uploading it to cloud storage by setting the **Upload Type** to [**Local Upload**](/concepts/file-handling/uploading-files.md#local-upload-widget-state). This saves the file in Bytes, allowing you to preview, edit, or process it before finalizing the upload. Once the file is uploaded to the device, you can do the following: * **Preview or Validate the Media**: Show the user an in-app preview before they decide whether to finalize or discard the upload. * **Editing Before Submission**: In social media apps, users upload photos for posts or stories. The app temporarily saves the image on the device while users edit or apply filters, and then uploads the final image to cloud storage. * **Perform Data Operations**: In document scanning apps, users capture images of documents, which are temporarily stored on the device. The app accesses the file bytes to apply OCR (Optical Character Recognition), enhance contrast, or convert the image to PDF before uploading the final processed file to cloud storage. * **Offline Functionality**: Store the media locally and defer uploading until the user regains internet access. * **Upload to Server**: When you want to store the file externally, you can then make an API call (e.g., multipart form data) to transfer the local file. Be sure to retrieve and save the resulting file URL in your database if you plan to display it later. Here are some examples of uploading a file to a device and using it in different scenarios: **Example 1: Upload to Your Backend Server via API** First, set the **Upload/Save Media** action with the **Local Upload (Widget State)** upload type. Then, add the next action as an **API call** and select the API that will upload the file to your server. After the API call is complete, ensure your server returns the uploaded file's URL. Use this URL to save in the database or [display the uploaded image](/concepts/file-handling/displaying-media.md). info The request body for the API must be in *Multipart* format. See how to [**configure an API for the multipart request body**](/resources/backend-logic/rest-api.md#multipart-format). **Example 2: Compress Image Using Custom Action** First, configure the **Upload/Save Media** action with the **Local Upload (Widget State)** upload type. This temporarily saves the media file on the device. Next, create and add a [**Custom Action**](/concepts/custom-code/custom-actions.md) (e.g., `compressImageAction`) that takes the locally stored file as input and compresses it using its **bytes** data. Ensure the custom action processes the image and returns a compressed file. Once compressed, the file can then be uploaded to cloud storage using another **Upload/Save Media** action. ![compress-image](/assets/images/compress-image-ad8fa8677df4048a3189d92bf99fa084.avif) **Example 3: Upload to Firebase or Supabase** First, configure the **Upload/Save Media** action with the **Local Upload (Widget State)** upload type. Once the file is modified or processed, add another **Upload/Save Media** action to the widget that confirms the final upload. Set the **Upload Type** to **Firebase** or **Supabase**, choose **File Type** as the **Uploaded File**, and select **File to Upload** from **Widget State > Uploaded Local File**. ![local-upload-to-firebase-supabase](/assets/images/local-upload-to-firebase-supabase-2fb33a0122e06e4a4477a4d482c7a47b.avif) ## Upload or Save File \[Action][​](/concepts/file-handling/uploading-files.md#upload-or-save-file-action "Direct link to Upload or Save File \[Action]") You can upload any type of file to your app, such as PDFs, MP3s, and more. The process for uploading files is almost similar to the [Upload or Save Media Action](/concepts/file-handling/uploading-files.md#upload-or-save-media-action). Web access for PDF files If you plan to support the web version of your app or test the PDF upload feature in **Run Mode**, you’ll need to complete additional configuration steps required for certain file types (e.g., PDFs). Learn how to [**enable web access**](/concepts/file-handling/displaying-media.md#web-access-for-pdfs-and-other-files). --- # GenUI Chat Usually, applications follow a fixed model: developers design screens, define navigation, and hard-code interactions. Users are limited to these predefined flows, and anything outside those paths simply isn’t supported. With GenUI, your app provides agent-driven experiences. Instead of relying on rigid flows, an AI agent can assemble user journeys dynamically in real time. Developers no longer need to predict every scenario. Instead, they define the building blocks and the AI orchestrates them into meaningful, context-aware experiences for the user. This represents a fundamental shift, from building fixed applications to building flexible capabilities that an agent can compose on demand. Think of it as building the components, and AI decides when to use them. **Traditional App:** The user clicks 'View Order' → navigates to `OrderDetailPage` → sees order info + tracking + items list. The flow is fixed, and every interaction must be pre-built. **With GenUI:** Build `OrderSummaryCard`, `TrackingStatusCard`, `OrderItemsList` as separate components. Build `getOrderDetails` as a tool. The AI decides what to show based on what the user asks. For example, a user asks, “Show my recent orders.” Instead of responding with text, the agent renders **order card components** with details like items, price, and delivery status. The user then asks, “Where is my latest order?” Now, instead of showing another block of text, the agent switches to a **map component** to display the live delivery location. This demonstrates how the agent dynamically selects the most relevant UI component based on the user’s intent. ![personal-shopper.avif](/assets/images/personal-shopper-b9e7ccff90193aeb9638a5b8da227793.avif) GenUI is not a chatbot GenUI may look like a chat interface, but it is fundamentally different from traditional chatbots. Instead of responding with text messages, the AI renders real UI components, such as cards, lists, forms, and maps—directly in the interface. Users don’t just read responses; they interact with fully functional UI. This means GenUI is not about conversations, it’s about dynamically composing application experiences using your actual app components. note This doesn’t replace traditional UI. Navigation, dashboards, and structured flows still play an important role. GenUI introduces a **new layer** — dynamic, adaptive, and conversational — that handles the long tail of use cases traditional interfaces can’t efficiently cover. ## GenUI Is Built on A2UI[​](/concepts/genui-chat.md#genui-is-built-on-a2ui "Direct link to GenUI Is Built on A2UI") GenUI is FlutterFlow's implementation of [**A2UI (Agent-to-UI)**](https://a2ui.org/). An [**open project by Google**](https://github.com/google/A2UI) that defines a declarative UI protocol for agent-driven interfaces. A2UI allows AI agents to generate rich, interactive UIs that render natively across platforms (web, mobile, desktop) without executing arbitrary code. ## Three Pillars of GenUI[​](/concepts/genui-chat.md#three-pillars-of-genui "Direct link to Three Pillars of GenUI") GenUI introduces three core pillars that work together to transform your app into an agent-driven experience: **1. Component Catalog:** Instead of replying with plain text, the AI uses your FlutterFlow components, such as product cards, booking tiles, or dashboards, to present information directly in the interface. Users don’t read the text; they interact with real UI. **2. Tools:** Your existing FlutterFlow action blocks become capabilities the AI can use. Whether it’s fetching data, calling APIs, submitting forms, or triggering workflows, the AI can execute these actions and use the results instantly. It moves beyond conversation and starts performing real tasks inside your app. **3. App Event Integration:** Your app’s events provide real-time context to the AI. Things like user actions, state changes, or backend updates can trigger responses. With auto-response enabled, the AI doesn’t wait for input; it proactively reacts and updates the experience as things happen. ![three-pillars.avif](/assets/images/three-pillars-e833097b98c4cf315e6622864d5d66a1.avif) ## Adding GenUI[​](/concepts/genui-chat.md#adding-genui "Direct link to Adding GenUI") Let’s walk through how to add a GenUI Chat by building a simple product lookup assistant. Follow the steps below: 1. Make sure you’ve completed the [Firebase integration](/integrations/firebase/connect-to-firebase.md), including the [initial setup](/integrations/authentication/firebase/initial-setup.md) and configuration files. 2. Go to **Firebase Console > AI Logic** and enable it. GenUI is powered by **Google Gemini** via [**Firebase AI Logic**](https://firebase.google.com/products/firebase-ai-logic) and uses a **usage-based pricing model**. You can get started on the **Spark (free)** plan for testing and low usage, but for production or higher usage, you’ll need to upgrade to the **Blaze (pay-as-you-go)** plan, where costs depend on AI requests and token usage. tip We recommend monitoring your usage in the Firebase Console, setting up budget alerts to avoid unexpected charges, and upgrading to Blaze before moving to production. 3. In your FlutterFlow project, create a **`ProductListCard`** component, which displays product details such as the image, name, and description. This component accepts a parameter of Data Type **`Product`**. 4. Create an Action Block named **`getProductDetails`**, which retrieves the details of a single product and returns it as a **`Product`** data type. 5. Place the **GenUI Chat** widget on a page or component like any other FlutterFlow widget. 6. Go to the Properties panel and define domain instructions to guide how the assistant behaves and communicates in your app. These instructions help the AI understand your app’s context, tone, and what it should prioritize. If left empty, it defaults to a generic assistant that builds UI in response to user requests. **Example System Prompt:** `You are a helpful AI shopping assistant for an e-commerce app. Help users discover products, compare options, track orders, and complete purchases.` 7. Select the components that the AI is allowed to render in responses. For this example, select the `ProductListCard` component created in step 3. To learn how to configure components for GenUI, refer to the [Component Catalog](/concepts/component-catalog.md) documentation. 8. If needed, add the [Action Blocks](/resources/functions/action-blocks.md) that the AI can call. For this example, select the action block named `getProductDetails`, created in step 4. Note that only Action Blocks that return a value can be added. To learn how to configure them for GenUI, refer to the [Tools Configuration](/concepts/tools.md) documentation. 9. If needed, choose Local [App Events](/concepts/app-events.md) to connect to the conversation. To learn how to configure app events for GenUI, refer to the [App Events Integrations](/concepts/app-event-integration.md) documentation. ### Customization[​](/concepts/genui-chat.md#customization "Direct link to Customization") You can fully customize the chat interface using the following options available in the Properties panel: * **Layout & container:** Background, border radius, padding, message spacing, and max message width * **Header:** Visibility, title, background color, and text color * **Avatars:** Visibility, size, and image sources for both user and AI * **Message bubbles:** Background colors, text colors, and border radii for user and AI messages * **Input field:** Placeholder text, background, border radius, and padding * **Send button:** Icon and background styling * **Welcome state:** Visibility, title, and subtitle shown when the chat is empty * **Scrolling behavior:** Auto-scroll to new messages and animation duration * **Thinking/status message:** Text displayed while the AI is generating a response **Default Behavior:** * Header is shown by default * Avatars are enabled by default * Auto-scroll is enabled * Input placeholder defaults to “Type a message…” * Thinking message defaults to “Thinking…” * Welcome state is shown when there are no messages ## Examples[​](/concepts/genui-chat.md#examples "Direct link to Examples") #### 1. Customer Support Agent[​](/concepts/genui-chat.md#1-customer-support-agent "Direct link to 1. Customer Support Agent") **Traditional Approach:** Build a help center with FAQ pages, a ticket form, and a chatbot that matches keywords to canned responses. **GenUI Approach:** * **Catalog Components:** TicketStatusCard, FAQArticle, EscalationForm, SatisfactionSurvey, AgentContactCard * **Tools:** `lookupTicket(ticketId)`, `searchKnowledgeBase(query)`, `createTicket(details)`, `getCustomerHistory(customerId)` * **App Events:** `NewTicketUpdateEvent` (auto-respond) when a support ticket is updated in the backend, the AI proactively informs the user A user opens the support chat. They describe their issue in natural language. The AI searches the knowledge base using the tool, finds a relevant article, and renders it as a FAQ Article component. If that does not resolve the issue, the AI creates a ticket using `createTicket`, shows the TicketStatusCard with the new ticket ID, and says it will notify them of updates. Later, when the support team updates the ticket, a `NewTicketUpdateEvent` fires, and the AI proactively shows the updated TicketStatusCard with the resolution. The developer did not build a "ticket lookup flow" or a "knowledge base search screen." They built components and tools. The AI composed the journey. #### 2. E-Commerce Personal Shopper[​](/concepts/genui-chat.md#2-e-commerce-personal-shopper "Direct link to 2. E-Commerce Personal Shopper") **Traditional Approach:** Build product listing pages, filters, a search bar, a comparison tool, a cart, and a checkout flow. **GenUI Approach:** * **Catalog Components:** ProductCard, ComparisonTable, PriceHistoryChart, ReviewSummary, CartSummary, PromoCodeBanner * **Tools:** `searchProducts(query,filters)`, `getProductDetails(productId)`, `getReviews(productId)`, `addToCart(productId,quantity)`, `applyPromoCode(code)`, `getPriceHistory(productId)` * **App Events:** `CartUpdatedEvent` (context injection) keeps the AI aware of what is already in the cart; `FlashSaleEvent` (auto-respond) alerts the user about time-sensitive deals A user says, "I need a gift for my dad who likes woodworking and coffee." The AI searches products, shows a curated set of ProductCards, and when the user shows interest in a specific item, pulls up the ReviewSummary and PriceHistoryChart. The AI knows what is in the cart (via CartUpdatedEvent context) and can suggest complementary items. When a flash sale starts on a relevant product, the AI proactively shows the PromoCodeBanner. No search results page. No filter sidebar. No "compare" button. The AI built a personalized shopping experience from the components and tools available to it. ## Current Limitations[​](/concepts/genui-chat.md#current-limitations "Direct link to Current Limitations") Here are some important limitations and considerations to keep in mind: * The only supported backend today is **Firebase AI Logic**. * App event listeners currently work only with **LOCAL** app events. * Catalog components cannot expose action parameters. * Avatar images must be valid network URLs (local asset paths are not supported). * Each rendered surface supports only a single catalog component as its root. ## Best Practices[​](/concepts/genui-chat.md#best-practices "Direct link to Best Practices") #### Describe Everything[​](/concepts/genui-chat.md#describe-everything "Direct link to Describe Everything") The AI reads your component and parameter descriptions to decide what to render and what values to provide. The quality of your descriptions directly impacts the quality of the AI's responses. * Name components clearly: `ProductCard` not `Card1` * Name parameters descriptively: `estimatedDeliveryDate` not `date` * Add descriptions to parameters: "The product's price in USD" not just "price" * Add descriptions to action blocks: "Searches the product catalog and returns matching items with prices and availability" not just "search" The AI is only as smart as the vocabulary you give it. #### Design for Composition[​](/concepts/genui-chat.md#design-for-composition "Direct link to Design for Composition") Components and tools work best when they are designed to be composed: * **Retrieval Tool + Display Component:** `getOrderDetails()` returns an `OrderStruct` -> `OrderStatusCard` accepts an `OrderStruct` as a parameter. The AI calls the tool and passes the result to the component. * **Granular Over Monolithic:** A `ProductCard`, `ReviewSummary`, and `PriceChart` give the AI three options. A single `ProductDetailPage` component gives the AI one. * **Consistent Data Types:** Use the same DataStruct across related tools and components. If `searchProducts` returns `ProductStruct`, make `ProductCard` accept `ProductStruct`. #### Use Events for Temporal Awareness[​](/concepts/genui-chat.md#use-events-for-temporal-awareness "Direct link to Use Events for Temporal Awareness") App events give the AI a sense of time and change. Without them, the AI only knows what the user tells it. With them, the AI knows what is happening. * Use **auto\_respond: false** for continuous state awareness, such as user navigation, preference changes, background data updates. * Use **auto\_respond: true** for time-sensitive signals, such as alerts, completions, threshold breaches, incoming messages. #### Write System Prompts Like Onboarding Documents[​](/concepts/genui-chat.md#write-system-prompts-like-onboarding-documents "Direct link to Write System Prompts Like Onboarding Documents") The system prompt is the AI's job description. Write it like you are onboarding a new team member: * What is their role? * What domain should they know about? * What should they prioritize? * What should they never do? * What tone should they use? * What business rules must they follow? A great system prompt makes the difference between a useful assistant and a generic chatbot. ## Behind the Scenes[​](/concepts/genui-chat.md#behind-the-scenes "Direct link to Behind the Scenes") GenUI is powered by [**Firebase AI Logic**](https://firebase.google.com/products/firebase-ai-logic) (Google Gemini) as its LLM backend. At a high level, the system works as: **Your configuration → code generation → runtime widget powered by Firebase AI Logic and the [GenUI](https://pub.dev/packages/genui) package**. You define components, tools, and events in FlutterFlow, and GenUI automatically generates the necessary code and runtime behavior to render dynamic UI experiences. ## FAQS[​](/concepts/genui-chat.md#faqs "Direct link to FAQS") The widget builds but the AI only sends text Check the catalog first. If no component fits the request, text is the expected fallback. Also, confirm that your system prompt and component descriptions make it clear when each component should be used. I can't add a component to the catalog The most common causes are: * The component has an action parameter. * A required complex parameter is missing a default value. * The component was deleted or renamed after being configured. I can't add an Action Block as a tool The Action Block must return a value, and every parameter, plus the return type, must be supported by the tool serializer. My event listener is not firing Make sure the following are correctly set: * The event is LOCAL scope. * The right event is being triggered at runtime. * `auto_respond` is set the way you expect. Why does a component fail validation? Common reasons include: * It has an action parameter. * It is configured twice in the same catalog. * A required complex parameter is missing a default value. * The configured component no longer exists. Why is the model choosing the wrong component? Usually one of these is true: * The names are too generic. * Parameter descriptions are weak. * Multiple catalog components overlap too much in purpose. * The system prompt does not explain how the assistant should prioritize them. Can the model render multiple items? Yes, but the reliable pattern is to use a single catalog component that accepts a list rather than expecting the model to assemble multiple independent sibling components on its own. Why is the model not calling a tool? Usually, the issue is not codegen. It is tool discoverability: * The name is vague * The description is weak * The system prompt does not make it clear when the tool should be used * The model already has enough context to answer without calling it What happens when a tool fails? The generated tool code catches the exception, clears the loading state, and sends an error payload back to the model. The UI should remain stable, and the model can decide how to explain or recover. Why can't I select my event? The event must be **LOCAL** scope and must still exist in the project or dependency where it was defined. Why didn't the assistant respond immediately? Check the following: * whether `auto_respond` is actually `true` * whether the event is being triggered * whether the system prompt tells the model to react visibly Note: Even with immediate inference, not every event will result in a visible response. Why does the assistant only react on the next user message? That is the expected behavior for `auto_respond: false`. The listener queues hidden context instead of triggering a separate inference call. Can one GenUI widget listen to the same event twice? No. Duplicate listeners for the same event on the same widget are rejected during validation. Do conversations persist across app restarts? No. Conversations do not persist across app restarts. If a user closes and reopens the app, the chat history is reset. Can I choose the Gemini model or adjust parameters like temperature? GenUI uses Firebase AI Logic, which manages the underlying Gemini model and its configuration. At the moment, you cannot directly select specific model variants or adjust parameters like temperature or top\_p. The system is designed to provide a simplified, managed experience without requiring manual tuning. What happens when Firebase AI Logic quota or rate limits are exceeded? If you exceed Firebase AI Logic or Gemini free-tier limits, requests will fail with a 429 quota-exceeded error. This typically means you’ve hit limits such as requests per minute or free-tier usage caps. In some cases, the error will include a retry time, after which you can try again. While the Spark plan works for testing, it is subject to strict free-tier limits, so for higher usage or production apps, you should expect to upgrade to a paid plan and monitor usage closely --- # Building Layout In FlutterFlow, you build a page layout using Widgets. **Widgets**, such as [Text](/resources/ui/widgets/text.md), [Buttons](/resources/ui/widgets/button.md), [Images](/resources/ui/widgets/image.md), and [Icons](/resources/ui/widgets/icons.md), are visible on the screen. Others, like [Containers](/resources/ui/widgets/container.md), Rows, Columns, and Stacks, are not directly visible but help arrange and position the visible elements on the page. These widgets are categorized into four main types: [Layout Elements](/tags/layout-elements.md), [Base Elements](/tags/base-elements.md), [Page Elements](/resources/ui/pages/scaffold.md), and [Form Elements](/tags/form-elements.md). To build a page, you combine different widgets from these categories to get the desired look and feel of your app. ## Understanding Layout Concept[​](/concepts/layouts.md#understanding-layout-concept "Direct link to Understanding Layout Concept") One of the most common layout patterns is to arrange widgets either **vertically** or **horizontally**. To display widgets in a vertical layout, use the **Column** widget. For a horizontal layout, use the **Row** widget. If you need to place one widget on top of another, use the **Stack** widget. info **Composing widgets** is a fundamental aspect of creating layouts in FlutterFlow. It involves combining different widgets to form a cohesive and functional user interface. Understanding how to effectively compose widgets allows you to design complex layouts and create intuitive, user-friendly apps. Learn more about composing widgets [**here**](/resources/ui/widgets/composing-widgets/rows-column-stack.md). ## Building Layouts: Exercise[​](/concepts/layouts.md#building-layouts-exercise "Direct link to Building Layouts: Exercise") Let's walk through an exercise to build the following layout: ![build-layout-page.avif](/assets/images/build-layout-page-708a22947554f59924e51a2e876092f7.avif) The steps to build the given layout are as follows: 1. [Sketch the layout](/concepts/layouts.md#1-sketch-the-layout) 2. [Add Image section](/concepts/layouts.md#2-add-image-section) 3. [Add info section](/concepts/layouts.md#3-add-info-section) 4. [Add reviews section](/concepts/layouts.md#4-add-reviews-section) #### 1. Sketch the layout[​](/concepts/layouts.md#1-sketch-the-layout "Direct link to 1. Sketch the layout") When you are just starting out with building apps, this step is very crucial. Before you actually start adding widgets to the page, sketch a picture of how the main layout will be broken into smaller parts. Breaking down the given layout into sections looks like this: ![breaking-main-layout-2.png](/assets/images/breaking-main-layout-2-cd4430869a74e015f6e0021330903d14.avif) Next, identify the widgets that can replace those sections, such as Column, Row, and Stack. Once you have a clear idea of which widgets to use, you can begin adding them. In the figure above, the main section is replaced with the Column widget and is divided into smaller sections. The next step is to look carefully at these smaller sections and, if required, divide them into further small sections and replace them with the appropriate widget. You can repeat this process until you achieve the desired level of granularity. Splitting the smaller section further looks like this: ![divide-smaller-section-2.png](/assets/images/divide-smaller-section-2-b2b0ea71f1867fc5686d49de5168e1d1.avif) info A page can only have one parent widget. i.e., you can't have two containers (at the same level) inside the HomePage. For that, you can wrap the two containers inside the Column widget, which makes the Column widget a single parent. ![column-as-single-parent.avif](/assets/images/column-as-single-parent-308c0170df84f473826850c93ffa0e64.avif) #### 2. Add Image section[​](/concepts/layouts.md#2-add-image-section "Direct link to 2. Add Image section") The top section includes the Image and IconButton widgets. To place the IconButton on top of the Image, wrap them inside a Stack widget. Here's how you do it: #### 3. Add info section[​](/concepts/layouts.md#3-add-info-section "Direct link to 3. Add info section") The info section consists of a few Text widgets inside the Column. #### 4. Add reviews section[​](/concepts/layouts.md#4-add-reviews-section "Direct link to 4. Add reviews section") The review section consists of multiple different widgets. First, add a Column to separate the reviewer's information (image and name) from the actual review text. Next, display the reviewer's information inside a Row widget using the CircleImage and Text widgets. Here’s exactly how you do it: ## Common Layout Widgets[​](/concepts/layouts.md#common-layout-widgets "Direct link to Common Layout Widgets") Apart from Row, Column, and Stack widgets, there are some other widgets that are widely used for building the page layout. Here are some of them: * [Container](/resources/ui/widgets/container.md) * [Card](/resources/ui/widgets/built-in-widgets/card.md) * [ListView](/resources/ui/widgets/composing-widgets/list-grid.md) * [GridView](/resources/ui/widgets/composing-widgets/list-grid.md) * [TabBar](/concepts/navigation/tabbar.md) * [PageView](/concepts/navigation/pageview.md) * [Form](/resources/forms.md) ## Video guides[​](/concepts/layouts.md#video-guides "Direct link to Video guides") To learn more about building layout, watch our videos: --- # ConditionalBuilder The `ConditionalBuilder` widget allows you to dynamically display different widgets based on certain conditions (either [single](/resources/functions/conditional-logic.md#single-condition) or [multiple](/resources/functions/conditional-logic.md#multiple-conditions-andor)). Using this widget, you can define different conditions, each associated with a specific widget to be displayed when that condition is true. It's like having a switch that shows different things depending on what's happening in your app. For example, displaying different charts based on user roles. For team members, an individual progress chart can be shown. Team leads can view the overall progress of the entire team, while project managers can see over project progress chart. Just like the below: ![conditional-builder-widget-demo.png](/assets/images/conditional-builder-widget-demo-183b8ff6c3c63a19d3e8bd8be6880b31.png) ## Adding ConditionalBuilder widget[​](/concepts/layouts/conditional-builder.md#adding-conditionalbuilder-widget "Direct link to Adding ConditionalBuilder widget") To add the `ConditionalBuilder` widget to your app: 1. Add the **ConditionalBuilder** widget (from the **Base Elements**) to where you want to display dynamic widgets. 2. Move to the **Properties Panel** **>** **Conditional Builder Properties,** andUnder the **First Condition**, provide the **IF** [condition](/resources/functions/conditional-logic.md) by clicking on **UNSET**. 3. Now, besides the **THEN**, click **Empty**. This will automatically select the **IF** widget in the widget tree. Inside that, add a widget that you want to display if this condition is true. 4. To add one more condition-based widget, click on the "+" button, add a condition for the **ELSE IF** section, and add a widget inside the **Else If** widget in the widget tree. 5. If none of the conditions are satisfied, add a default widget to display inside the **Else** widget. 6. Use the **Show In UI Builder** option to see that particular widget in the [canvas area](/flutterflow-ui/canvas.md). You can see only one widget at a time. --- # Flex The **Flex** widget can be used as an alternative to **Row** and **Column**. It allows you to dynamically set the layout axis (horizontal or vertical) based on specific conditions or logic. This is especially useful for creative responsive layouts - where child elements should be horizontal when the screen is wide, and vertical when the screen is narrow. ![flex.png](/assets/images/flex-aaafa4fc69ce98d225fc76b00662819c.png) ## Adding Flex Widget[​](/concepts/layouts/flex.md#adding-flex-widget "Direct link to Adding Flex Widget") To use the Flex widget, add it from the **Layout Elements** section of the **Widget Palette**, then add child widgets inside it. From the properties panel, set a condition for the **Is Horizontal** property. When this condition evaluates to `True`, the items will be laid out horizontally. Consider an ecommerce app where recent orders are displayed vertically on mobile devices and switch to a horizontal layout on larger screens to make better use of the available space. Here's another example of using a Flex widget on a create account page to dynamically align the signup fields based on screen size. Best Practices * If you only need a simple vertical or horizontal arrangement, consider using [**Row**](/resources/ui/widgets/composing-widgets/rows-column-stack.md) or [**Column**](/resources/ui/widgets/composing-widgets/rows-column-stack.md). * For very large numbers of children, consider using [**ListView**](/resources/ui/widgets/composing-widgets/list-grid.md#listview-widget) or [**GridView**](/resources/ui/widgets/composing-widgets/list-grid.md#gridview-widget) instead of **Flex**, as they offer better performance for scrolling large lists of items. * When the content exceeds the screen limit, you can enable scrolling to make the content accessible. However, if you want to avoid scrolling altogether and still fit all the content on the screen, consider using a [**Wrap**](/concepts/layouts/wrap.md) widget. ## Customization[​](/concepts/layouts/flex.md#customization "Direct link to Customization") When **Is Horizontal** property is disabled, the Flex widget behaves like a Column, and when enabled, it acts as a Row. Settings like [main axis alignment](/resources/ui/widgets/composing-widgets/rows-column-stack.md#main-axis), [cross axis alignment](/resources/ui/widgets/composing-widgets/rows-column-stack.md#cross-axis), [scrollability](/resources/ui/widgets/composing-widgets/rows-column-stack.md#scrollability), and [spacing](/resources/ui/widgets/composing-widgets/rows-column-stack.md#spacing) work the same way they do for the Column and Row widgets. --- # Responsive Layout FlutterFlow is great at creating applications adaptable to a wide range of screen sizes, devices, and platforms. Ensuring that our screens maintain their aesthetic appeal across all these variations is crucial. Below, we outline various methods to enhance the responsiveness of your UI screens. **Note:** Please check out the examples in the order they're given, as they use the same example. Jumping to sections might make things confusing. ### Global Properties[​](/concepts/layouts/responsive.md#global-properties "Direct link to Global Properties") Let's start by demonstrating how screen width and height values change when you switch between devices in Test Mode. First, create a new project. In the Home Page, under a Column parent, add two Text widgets. Label one "Screen Width" and the other "Screen Height." ![screen-width-height.avif](/assets/images/screen-width-height-15b30c03124d32fadf7ae728754cac87.avif) Next, we'll display the changing values alongside these titles. For each Text widget, set its value to a variable. Select *Combine Text* from the options. Your first Text value should be either "Screen Width: " or "Screen Height: ". Then, add another Text value that will show the corresponding value. This second Text value is also set from a variable. Choose 'Global Properties,' and then select either 'Screen Width' or 'Screen Height' from the list. These options hold the current screen width/height values. Repeat this process for both Text widgets. ![setting-global-properties.avif](/assets/images/setting-global-properties-4f4375303e692ebdaf2ca886062b95ee.avif) Now, when you switch to Test Mode and try out different devices, you'll see the screen width and height values update accordingly. * Running app on Test Mode for desktop device sizes * Running app on Test Mode for mobile device sizes ![running-app-on-test-mode-for-desktop-device-sizes.avif](/assets/images/running-app-on-test-mode-for-desktop-device-sizes-a33815a0c05375fd8501943ab6d49d17.avif) ![running-app-on-test-mode-for-mobile-device-sizes.avif](/assets/images/running-app-on-test-mode-for-mobile-device-sizes-52410bce4805003848c8de270648d02a.avif) This is to show you that these screen values are always accessible as global properties. You can use them to design your UI logic effectively. ## Expanded & Flex[​](/concepts/layouts/responsive.md#expanded--flex "Direct link to Expanded & Flex") Let's explore a fundamental method to make columns and rows adapt to different screen widths and heights. While designing a Row or Column, it's important to avoid assigning fixed sizes to the children, unless specifically required by the design. Take, for instance, the navigation bar we're creating for our web version. It includes the page name, several navigation icons, and a search bar. Notice how the search bar adjusts its length based on the available width. This adaptability is achieved because the widths of the children are set relative to the available space in the horizontal section. ![web-version-of-our-shopping-app-example.gif](/assets/images/web-version-of-our-shopping-app-example-aa9a2440bf6f2a5e6b5b3e8b0ba3922f.gif) To design the same navbar, create the following widget hierarchy: ``` - Container (named as webHeader) - Row - Row - TextField (named as searchBar) - IconButton ``` The second Row is further broken down into the following: ``` - Row 2 - Text (named as pageName) - Row 3 (named as navIcons) - IconButton - IconButton - IconButton ``` ![row-breakdown.avif](/assets/images/row-breakdown-0865c1248a5728409c0b62b9c483791b.avif) As you can see, the search bar currently occupies the maximum available space. However, we want both `Row 2` and the `searchBar` to share the space equally. To achieve this, simply adjust the Widget properties. For `Row 2`, set its Expansion property to *Flexible* (the middle icon) and assign a Flex value of 1. Repeat the same steps for the `searchBar`. This change ensures they are allocated space in a 1:1 ratio, based on what's available. After this adjustment, you'll notice that the remaining space, following the placement of the *search IconButton* on the right, is evenly divided between `Row 2` and the `searchBar`. ![test-expansion.avif](/assets/images/test-expansion-22ad3f38b0413b27af8917f838f7fbdc.avif) We encourage you to test with different web dimensions and sizes to see how well this adapts. Depending on your design needs, there are various approaches to managing space. Let's consider a different scenario: What if we want the searchBar to always occupy 40% of the screen width, with `Row 2` taking up the remaining space after placing the `searchBar` and `search IconButton`? To do this, first set the `searchBar`'s width to 40% and its expansion to *Default* (the first icon). ![set-searchbar.png](/assets/images/set-searchbar-b7c349fa3407021706be9ba91ff8923b.png) Next, adjust the `Row 2` widget's Expansion setting to *Expanded* (the third icon). With these settings, you'll see that `Row 2` now occupies all the space left over after allocating 40% to the `searchBar` and placing the `search IconButton`. Note: We've added some padding and enhanced the UI of the `searchBar` to improve its appearance. ![enhanced-searchbar.avif](/assets/images/enhanced-searchbar-677c41baa49322699a4dad3e56c5e75b.avif) You can also go ahead and improve the spacing between the `pageName` and `navIcons` by adjusting the MainAxisAlignment to *Space Between* (represented by the last icon). Feel free to try this out with different screen sizes to see how it effectively adapts to the available space. ## Wrap method[​](/concepts/layouts/responsive.md#wrap-method "Direct link to Wrap method") Another effective method to enhance the responsiveness of a row or column containing multiple items is through the use of the Wrap widget. Let's consider a scenario with a Row of category cards. ![row-of-cards.avif](/assets/images/row-of-cards-e4e6d2a4cb846148878d7bbe80c99643.avif) You might observe that when the screen size is adjusted to resemble mobile dimensions, the cards begin to get cut off at the edges. ![row-card-resize.gif](/assets/images/row-card-resize-c2acaeb8d8830a78f3662c0d9f63e380.gif) To resolve this issue, simply replace the parent Row widget with a Wrap widget. Make sure to set the Wrap Direction to 'Horizontal' in the Wrap properties. You'll then see that the previously overflowing cards neatly move to the next line, as illustrated in the example. ![row-card-resize.gif](/assets/images/row-card-resize-2-f65d7398b92533417391457ad854c746.gif) The Wrap widget efficiently manages layout for varying screen sizes by automatically adjusting its children into multiple rows or columns. It prevents overflow and ensures a clean, responsive design, especially useful for adapting content like category cards from desktop to mobile views. ## Responsive Breakpoints[​](/concepts/layouts/responsive.md#responsive-breakpoints "Direct link to Responsive Breakpoints") Moving on to more complex layout scenarios, where the UI significantly differs between mobile and web platforms, it's important to first understand breakpoints. **Breakpoints** in responsive design are like thresholds for different screen sizes. They act as specific points where the layout of a user interface meets a certain screen size requirement and then changes to accommodate it. When the screen size crosses one of these thresholds, the layout adjusts. FlutterFlow has default breakpoints for different screen sizes, but you can also adjust these to better suit your app's design needs. ### Customize Responsive Breakpoints[​](/concepts/layouts/responsive.md#customize-responsive-breakpoints "Direct link to Customize Responsive Breakpoints") Go to your Theme Settings > Design System and find the Breakpoints section with default values already set. You can go ahead and customize it if needed. ![custom-responsive-breakpoints.avif](/assets/images/custom-responsive-breakpoints-74e45bbcc5192146f1511644cc2b9789.avif) The following sections will also rely heavily on *Responsive Visibility,* so let's proceed. ## Visibility of Nav Bar & App Bar[​](/concepts/layouts/responsive.md#visibility-of-nav-bar--app-bar "Direct link to Visibility of Nav Bar & App Bar") In many designs, the App Bar and Nav Bar are typically included in mobile or mobile + tablet screens, and often omitted in desktop formats. FlutterFlow makes it easy to enable or disable the Nav Bar and App Bar. Just go to App Settings, select Nav Bar & App Bar, and toggle the 'Show Nav Bar' option. Upon enabling it, you'll see additional settings. Icons for mobile, tablet, tablet (landscape), and desktop let you choose where the Nav Bar should appear. Generally, it's advisable to enable it only for mobile, or mobile and tablet. Let's follow these steps to configure it for our app. ![appbar-navbar-visibility.webp](/assets/images/appbar-navbar-visibility-824b7f96fe1dd292089af2f2b7dc4e85.webp) Now, when testing directly in our editor, observe how the navbar appears only for mobile and tablet screen sizes. This same approach can be applied to the App bar as well. ![appbar-navbar-visibility-resize.gif](/assets/images/appbar-navbar-visibility-resize-2aad6b0af11e814a4f0dbbf3ac048507.gif) ## Responsive Visibility[​](/concepts/layouts/responsive.md#responsive-visibility "Direct link to Responsive Visibility") In our previous examples, we improved many aspects, but some responsive issues still remained. For instance, our NavBar displayed the same navigation icons on both top header and bottom navbar. Additionally, when the screen width was reduced to mimic mobile dimensions, the header elements became cramped and difficult to interact with. To address this, we can leverage FlutterFlow's Responsive Visibility feature to implement distinct AppBar and SearchBar designs for mobile, while maintaining the current design for web. Imagine we've created a new widget, `mobileAppBar` , and added it to our layout. This widget cleverly separates the categories header and search bar vertically and includes a back navigation button. The goal is to activate this mobile-friendly layout for mobile and tablet screens, while preserving the 'webHeader' for tablet (landscape) and desktop views. ![responsive-visibility.avif](/assets/images/responsive-visibility-748f5a277989829f4c2427b745a218c8.avif) To implement this, we can go to its widget properties and toggle the device icons as shown in the following demo. And now you have a more responsive screen for this shopping app use case that looks good in both mobile and desktop formats. With these adjustments, your shopping app now boasts a highly responsive screen that seamlessly adapts to both mobile and desktop formats. This ensures an optimal user experience across all devices, maintaining both functionality and aesthetic appeal. ## Responsive Value[​](/concepts/layouts/responsive.md#responsive-value "Direct link to Responsive Value") **Responsive Values** allow you to define different property values, such as widths, heights, font sizes, or padding, for different device sizes (mobile, tablet, desktop, and wide). At runtime, your app evaluates the screen width and automatically applies the appropriate value based on your configurations. possible use cases * **Adaptive Layouts**: Automatically adjust element sizes to deliver a consistent UI across devices. * **Better Readability**: Increase font size on larger screens to improve legibility. * **Improved Spacing**: Use different padding or margins on tablets and desktops to optimize content flow. To set a responsive value, select a widget and choose a property that supports responsiveness. Click **Set from Variable > Responsive Value**, then enter different values for each screen size: * Mobile (below `Breakpoint Small`) * Tablet (below `Breakpoint Medium`) * Desktop (below `Breakpoint Large`) * Wide (above `Breakpoint Large`) As you preview on different devices, the property will automatically adjust based on the selected screen size. Customizing Breakpoints You can adjust the default screen size breakpoints (mobile, tablet, desktop, wide) in FlutterFlow’s Theme Settings. See how to [**Customize Breakpoints**](/concepts/layouts/responsive.md#customize-responsive-breakpoints). --- # Wrap The Wrap widget is similar to Row and Column as it shows its children one after another. If there is not enough space to show your item, the Wrap widget will automatically place it in a new row or column. ## Adding Wrap widget[​](/concepts/layouts/wrap.md#adding-wrap-widget "Direct link to Adding Wrap widget") Here's an example of how you can use a Wrap widget in your project: 1. First, drag the [**Container**](/resources/ui/widgets/container.md) widget from the **Layout Elements** tab (in the Widget Panel) or add it directly from the widget tree and set its **width** to **infinity** and **height** to **200**. 2. Add the **Wrap** widget from the **Layout Elements** tab inside the Container. 3. Add the **Button** widget inside the Wrap widget. 4. Copy-Paste and add a few more Button widgets. ![add-wrap-widget.gif](/assets/images/add-wrap-widget-22e05ab20c3829a655e8b53114fe0050.gif) See how the Button that won't fit in the remaining space is placed in the next line. ## Customizing[​](/concepts/layouts/wrap.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Changing Direction[​](/concepts/layouts/wrap.md#changing-direction "Direct link to Changing Direction") In the example above you saw that the items are added in the horizontal direction, which is a default axis for adding items. To change the direction in which the items are added: 1. Select the **Wrap** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Wrap Properties** section. 3. Spot the **Direction** dropdown, change it to **Vertical**. The Horizontal Direction makes the Wrap widget work like a Row while the Vertical Direction makes the Wrap widget work like a Column. ![wrap-change-direction.gif](/assets/images/wrap-change-direction-ee904fc260c4adb4ffd0d05cad584d74.gif) ### Adding Space Between Items[​](/concepts/layouts/wrap.md#adding-space-between-items "Direct link to Adding Space Between Items") To add space between items: 1. Select the **Wrap** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Wrap Properties** section. 3. In the **Spacing** input box, enter the value as 10. If the **Direction** is set to **Horizontal**, Wrap will insert the empty space of 10px vertically between the items. and If the **Direction** is set to **Vertical**, Wrap will insert the empty space of 10px horizontally between the items. 4. In the **Run** **Spacing** input box, enter the value as 15. If the **Direction** is set to **Horizontal**, Wrap will insert the empty space of 15px horizontally between the items. and If the **Direction** is set to **Vertical**, Wrap will insert the empty space of 15px vertically between the items. ![wrap-space-between-items.gif](/assets/images/wrap-space-between-items-3bbbf369615a39c1581569b2711c0f6a.gif) ### Adjust Alignment[​](/concepts/layouts/wrap.md#adjust-alignment "Direct link to Adjust Alignment") The default Main Axis for a Wrap Widget is the horizontal axis, so adjusting this will change how the child widgets are horizontally distributed in the Wrap widget. To change the Alignment: 1. Select the **Wrap** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to **Alignment**. 3. Select from the options displayed including **Start**, **Center**, **End**, **Space** **evenly**, **Space** **between**, and **Space** **around**. ![wrap-adjust-alignment.gif](/assets/images/wrap-adjust-alignment-a902a53a8ddb0577580119dbd7c7a464.gif) ### Adjust Run Alignment[​](/concepts/layouts/wrap.md#adjust-run-alignment "Direct link to Adjust Run Alignment") The default Run Axis for a Wrap Widget is the vertical axis, so adjusting this will change how the child widgets are vertically distributed in the Wrap widget. To change the Run Alignment: 1. Select the **Wrap** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to **Run Alignment**. 3. Select from the options displayed including **Start**, **Center**, **End**, **Space** **evenly**, **Space** **between**, and **Space** **around**. ![wrap-run-alignment.gif](/assets/images/wrap-run-alignment-c25ae3830a53093f5fd8384926c94de7.gif) ### Adding Items From Bottom[​](/concepts/layouts/wrap.md#adding-items-from-bottom "Direct link to Adding Items From Bottom") By default, the new items are always added from top to bottom direction. In a very rare case, you may need to change this behavior. To add items from the bottom to top: 1. Select the **Wrap** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to **Vertical Direction**. 3. Set the Dropdown value to **Up**. 4. Try adding items. ![wrap-add-items-from-bottom.gif](/assets/images/wrap-add-items-from-bottom-69bccc1a469b5e87a62343c4db9742ad.gif) ### Clipping The Items[​](/concepts/layouts/wrap.md#clipping-the-items "Direct link to Clipping The Items") If you add several items to the Wrap widget that exceed the size of the patent widget, the Wrap widget will continue to display the overflowing items. However, you can choose to hide the overflowing items using the Clip Behaviour property: To clip the overflowing items: 1. Select the **Wrap** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to **Clip Behaviour**. 3. Change it to **Clip Content**. ![wrap-clip-items.gif](/assets/images/wrap-clip-items-28e043558d15d6cca365f7aaeea92c68.gif) *** ## Video guide[​](/concepts/layouts/wrap.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: --- # Localization **Localization** (often abbreviated as **l10n**) is the process of making your app work for different languages, regions, and cultures. It involves translating the app's text, adapting date and number formats, and adjusting other elements to meet the cultural expectations of a particular locale. Difference Between Internationalization and Localization * [**Internationalization (i18n)**](https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization): This is the process of setting up your app in such a way that it can easily adapt to various languages and regions without requiring engineering changes. **Note that FlutterFlow handles most of the internationalization for you, so the only thing you need to take care of is localization.** * **Localization (l10n)**: This is the process of translating the content of your app and adapting it for a specific locale or culture. It involves providing translations for user-visible strings, formatting dates, times, and numbers, and adapting content to meet cultural norms. In a nutshell, internationalization is about making your app to support multiple languages, while localization is the actual process of translating the content and adapting it to specific locales. ## Add Multi Language Support[​](/concepts/localization.md#add-multi-language-support "Direct link to Add Multi Language Support") FlutterFlow enables you to translate all text in your app at once using Google Translate or to manually adjust translations as needed. Additionally, you can localize predefined messages, such as permission prompts, authentication snackbars, and other in-app notifications. Adding multi-language support is essential for making your app accessible to a wider audience. For instance, if your app provides exercise instructions only in English, non-English speakers may find it hard to understand and might choose a different app, even if it’s less effective, simply because it’s available in their language. Implementing a multi-language feature helps your app succeed globally by offering a user-friendly experience for diverse audiences. To add multi-language support in FlutterFlow, navigate to **Settings and Integrations** > **Languages**, add the languages to support, set a primary language as a fallback, and optionally choose a display language. Then, use **Translate All** for automatic translations and adjust them if needed. Finally, verify translations on different pages by changing the language dropdown in the canvas. warning Changing the primary language after translating all of your text will clear the existing translations for other languages. ## LanguageSelector Widget[​](/concepts/localization.md#languageselector-widget "Direct link to LanguageSelector Widget") The **LanguageSelector** widget in FlutterFlow allows users to switch to their preferred language in real-time without needing to restart the app. It displays the currently selected language and, when interacted with, presents a list of all available languages for easy selection. It's particularly useful on onboarding screens or within settings menus to allow users to customize their language preferences. ### LanguageSelector Properties[​](/concepts/localization.md#languageselector-properties "Direct link to LanguageSelector Properties") You can customize the appearance using the various properties available under the Properties Panel. ![language-selector-properties.avif](/assets/images/language-selector-properties-6e3a8952b1f093573c65fe53125ed6cb.avif) tip By default, the **LanguageSelector** widget does not persist the user's language choice across app sessions. To retain the selected language, enable the **Persist Selection** option under Language Settings. ## Set App Language Manually \[Action][​](/concepts/localization.md#set-app-language-manually-action "Direct link to Set App Language Manually \[Action]") Sometimes, you might prefer not to use the default [LanguageSelector widget](/concepts/localization.md#languageselector-widget) and instead implement a custom widget for language switching. For example, you could create a custom language selection screen that appears when the app first launches. You can use the **Set App Language** action to let users choose their preferred language from the available options. info Note that this action affects only the app's language and does not modify the device's system language. ![set-app-lang-action.png](/assets/images/set-app-lang-action-d2cd5297fb8801e5739b7a87862b0614.png) ## Managing Translation[​](/concepts/localization.md#managing-translation "Direct link to Managing Translation") There are two ways you can manage the app text translation: **Inside Language Settings** The Language Settings page lists all of your app's text, grouped by page, making it easy to manage translations in bulk. To manually add or update a translation, make changes directly in the language column and mark the text as **Fixed**. Marking it as **Fixed** will prevent auto translate from overriding your custom translations during the bulk translation process. To use Google Translate for new or existing text, click **Translate Page.** ![manage-translation-in-language-settings.avif](/assets/images/manage-translation-in-language-settings-c4fbd83ee771d8c104776bf8f537edf4.avif) **Inside Properties Panel** You can also add or update translations for individual text directly inside the properties panel. To do so, select the widget (e.g., Text, TextField, etc.), go to the properties panel, and click on the Globe icon. This will open a new panel. * To manually add or update a translation, make changes directly in the box under the language name. * To auto-translate for all languages, click on **Google Translate**. ![manage-translation-in-properties-panel.avif](/assets/images/manage-translation-in-properties-panel-068d129b671bcca4e8c18d198cea4785.avif) ## Translating Predefined Messages[​](/concepts/localization.md#translating-predefined-messages "Direct link to Translating Predefined Messages") FlutterFlow allows you to manage the translation for the following types of predefined messages. * **iOS Permission Messages**: iOS permission messages are the prompts shown to iOS users when your app requests access to device features, such as the camera or photo library. * **Preset In-App Messages**: These are built-in messages that FlutterFlow displays for specific actions, such as authentication and file upload actions. To add translations for predefined messages, navigate to **Settings and Integrations** > **Project Setup** > **Languages**. Scroll down to the **Translation** section and select the category containing the message you wish to translate. Start by entering your message in the base language. Then, either use the **Translate Message** button for automatic translation or manually add your translations and mark them as **Fixed** to prevent them from being overridden by auto-translate. info Permission messages are displayed based on the features included in your app. For instance, Camera and Photo Library permission messages appear when a page contains a button with the **Upload Photo/Video action**. ## Accessing Language-Specific Data[​](/concepts/localization.md#accessing-language-specific-data "Direct link to Accessing Language-Specific Data") When building a multi-language app, you may need data like the current language code or language-specific text. In FlutterFlow, you can retrieve the following types of language-related data: * **Current Language Code**: This provides the ISO language code for the current app language (e.g., en, de, fr). * **Language-Dependent Text**: Allows you to specify different values for each language. For instance, you might want to display a country flag or name based on the current app language. These options are accessible through **Set from Variable > Internationalization**. ![retrieve-lang-data.png](/assets/images/retrieve-lang-data-2eef5ceefd42854023441907287fb4f1.png) ## Localizing Dates[​](/concepts/localization.md#localizing-dates "Direct link to Localizing Dates") To ensure your app displays dates in formats familiar to users from different regions, you can use the predefined **DateTime Format Options** while displaying dates. For example, in the United States, dates follow a **month, day, year** format (e.g., 12/31/2023), whereas in India, they use a **day, month, year** format (e.g., 31/12/2023). To accommodate these regional differences, set the format option to `yMd`, a locale-aware format that automatically adjusts date representation based on the user's locale. ![localize-dates.avif](/assets/images/localize-dates-c521dbf44268a20cec711f682e08779d.avif) tip Here are a few more locale-aware formatting options you can use: * **`yMMMd`** – Formats the date with an abbreviated month and day, e.g., `Dec 31, 2023` (US) or `31 Dec 2023` (India). * **`jm`** – Displays time with minutes, e.g., `5:30 PM` (US) or `17:30` (Europe). For custom locale-specific date formats, you can also [**create your own patterns**](/resources/data-representation/global-properties.md#custom-formatting). ## Localizing Numbers[​](/concepts/localization.md#localizing-numbers "Direct link to Localizing Numbers") Different regions use different symbols for decimal and thousand separators. For example, the U.S. uses a period for decimals and a comma for thousands, while many European countries use the opposite. To localize the numbers, set the [**Number Format Options**](/resources/ui/widgets/text.md#formatting-numbers) to **Decimal** and then set the **Decimal Type** to **Automatic**. ![localize-numbers.avif](/assets/images/localize-numbers-4f0c4f5d020f9b74b44201477b9f5b59.avif) ## Localizing Currency[​](/concepts/localization.md#localizing-currency "Direct link to Localizing Currency") Currency symbols and their placement vary by locale. For example, in the U.S., the dollar sign appears before the amount (`$1,000.00`), whereas in countries like France, the currency symbol is placed after the amount (e.g., `1 000,50 €`). To handle this behavior, enable the **Display as Currency** option under Number Format settings and leave the **Currency Symbol** field empty to automatically adjust based on the user’s locale. ![localize-currency.avif](/assets/images/localize-currency-04a7d016aed333db6a0bed72beb35887.avif) ## Testing[​](/concepts/localization.md#testing "Direct link to Testing") Localization testing is crucial to ensure that all elements work properly across different languages and locales. Here are a few ways to test localization: * **Change Device Locale**: Test your app by changing the device locale to verify translations and layout adjustments. * **Use Emulators**: Use Android or iOS emulators to simulate different locales and ensure everything is displaying correctly. * **Long Texts**: Verify that long translations do not overflow or cause UI issues. * **Manual Testing**: Manually verify the accuracy of translations, date formats, number formats, etc. --- # Bottom Sheet A Bottom Sheet is an alternative to a menu or a dialog. It opens from bottom to top and can be dismissed by swiping it from top to bottom. When it opens, it prevents the user from interacting with the rest of the app. You can use the bottom sheet when you want to perform a small action without creating a separate screen. ## Types of Bottom Sheet action[​](/concepts/navigation/bottom-sheet.md#types-of-bottom-sheet-action "Direct link to Types of Bottom Sheet action") Below are the types of Bottom Sheet actions: 1. **Show**: This opens the bottom sheet. 2. **Dismiss**: This closes the bottom sheet. ## Opening Bottom Sheet[​](/concepts/navigation/bottom-sheet.md#opening-bottom-sheet "Direct link to Opening Bottom Sheet") Follow the steps below to add an action that opens the bottom sheet: 1. First, create a bottom sheet [component](/resources/ui/components.md). tip You can also create one from the 'BottomSheet' [**templates**](/resources/ui/components/creating-components.md#creating-component-from-template). 2. Select the **Widget** (e.g., Button) from where you want to open the bottom sheet. 3. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 4. Search and select the **Bottom Sheet** (under *Widget/UI Interactions*) action. 5. To open the bottom sheet, select **Show**. 6. **Select Component** as the component you created for the bottom sheet. 7. (Optional) set the **Height** value. You should set the height if you want the bottom sheet to appear only up to some portion of the screen. 8. You can set the **Background** and **Barrier Color** for the bottom sheet. ![Set Background and Barrier color](/assets/images/bottom-sheet-background-color-98450a3773ec892d19d8483d7c20002b.png) 9. You can also [pass parameters](/resources/ui/components/creating-components.md#creating-a-component-parameter) to a bottom sheet component. 10. By default, this type of action blocks the following action (if any) from triggering while this action is in progress. (i.e., meaning the bottom sheet is present on the screen). However, in some cases, you might want to allow the next action (after this) to execute, for example, making an API call immediately after showing the bottom sheet. To do so, enable **Non Blocking** option. 11. By default, **Non Dismissble** option closes the bottom sheet when you click outside of it. To disable this behavior, enable this option. 12. With **Enable Drag** option, you can open and close the bottom sheet using a swipe gesture. 13. Optional: If you are returning any value from the bottom sheet, provide the **Action Output Variable Name**. The result will be stored in this variable. ## Closing Bottom Sheet[​](/concepts/navigation/bottom-sheet.md#closing-bottom-sheet "Direct link to Closing Bottom Sheet") Follow the steps below to add an action that closes the bottom sheet: 1. Select the **Widget** (e.g., Button, ListTile, Container) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Bottom Sheet** (under *Widget/UI Interactions*) action. 4. To close the bottom sheet, select **Dismiss**. 5. If you want to return a value from the current bottom sheet, enable the **Has Value** toggle and pass the value by setting its *Data Type* and *Value Source*. 1. If you enable the *Has Value* option, you must come back to the action that opens this bottom sheet and provide the **Action Output Variable Name**. This will be used to retrieve the value from the bottom sheet. 2. Now you can use the *Action Output Variable Name* to get the data. Here is an example of returning the selected user name back to the page. --- # Deep & Dynamic Linking Support for Dynamic Links On August 25th, 2025, Firebase Dynamic Links will be shut down. Read more about the [**announcement here**](https://firebase.google.com/support/dynamic-links-faq). It's recommended to start exploring alternative solutions like [**Branch.io**](/concepts/navigation/deep-dynamic-linking.md#branch-deeplinking-library) for link management and deep linking. Adding deep and dynamic linking allows you to share a special type of link that takes the user right inside the specific page of your app. You can also send the custom data with a link to load the page content based on the data. For example, you could share an interesting social media post with your friends, and they can directly access its content without manually searching the post inside the app. It just works like any website link would work. The figure below illustrates how it works: ![img.png](/assets/images/img-16416344289e75695c203df1f638b767.png) Deep and Dynamic link flow When you click on the link, first, it checks if the app is installed. If not, the link opens the Playstore or Appstore (based on your device) to install the app. After installing, if the page requires authentication, you'll see a login page. After successful login, you can access the content shared with you. The best thing to note here is that even if the app has a different flow for accessing the page content (e.g., Home Page -> All Posts -> Single Post), you can bypass the flow and directly open a specific page (e.g., Single Post). ## Deep Link[​](/concepts/navigation/deep-dynamic-linking.md#deep-link "Direct link to Deep Link") The deep link allows you to create a URL that will open a specific page in your app. For the deep links to work, you must have the app installed on your device. ### URL Scheme (structure)[​](/concepts/navigation/deep-dynamic-linking.md#url-scheme-structure "Direct link to URL Scheme (structure)") The deep link consists of three parts. It begins with the scheme followed by the host and page name, such as `designersapp://designersapp.com/profile`. ![img\_1.png](/assets/images/img_1-e9bcc7b4c7a527a117dd98d2e4e33f33.png) If the page name is not provided (i.e. `designersapp://mydesignersapp.com/)`It will open the app's landing page. ![img\_2.png](/assets/images/img_2-67ea88901a2b863a7c95e0e5858a017c.png) ### Adding Deep Link[​](/concepts/navigation/deep-dynamic-linking.md#adding-deep-link "Direct link to Adding Deep Link") Let's build an example of sharing and opening a profile page using the deep link. The example looks like the below: ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/deep-link-example.gif?alt=media\&token=d6f40d74-f510-4f49-8026-9ccc87896ff4) Sharing and opening a deep link The steps to add the deep link are as follows: 1. [Set URL scheme](/concepts/navigation/deep-dynamic-linking.md#1-set-url-scheme) 2. [Setting page URL](/concepts/navigation/deep-dynamic-linking.md#2-setting-page-url) 3. [Sharing deep link](/concepts/navigation/deep-dynamic-linking.md#3-sharing-deep-link) 4. [Testing deep link](/concepts/navigation/deep-dynamic-linking.md#4-testing-deep-link) #### 1. Set URL scheme[​](/concepts/navigation/deep-dynamic-linking.md#1-set-url-scheme "Direct link to 1. Set URL scheme") In this step, You will set the URL scheme. To do that: 1. Navigate to **Settings & Integrations > General > App Details.** 2. If you want to add deep linking on multiple pages and all of them require users to log in, turn on the **Pages Requires Authentication by Default**. 3. In **URL scheme** fields, by default we add the values based on your project name. To change it, enter the **scheme** **name** (before "://") and **hostname** (after "://"). 4. If you want users to navigate back to the home page instead of closing the app when they press the back button from a deep link page, enable the **Pages Are Subroutes of Root Page** option. ![img\_3.png](/assets/images/img_3-efe840e2cf4de2fb903743d38cf7ebe5.png) tip We recommend enabling this option to increase user engagement with your app. #### 2. Setting page URL[​](/concepts/navigation/deep-dynamic-linking.md#2-setting-page-url "Direct link to 2. Setting page URL") The page URL points to the specific page in your app, which is used on the Web and for deep linking on mobile. To set the page URL: 1. Select the page that you would like to open via a deep link. 2. Move the **properties panel** on the right and open the **Route Settings** section. 3. By default, the Route is the current page name. Edit this if you want a different name in the page URL. 4. By default, the page does not require authentication when it opens via the deep link. However, checkmark the **Requires Authentication** if your app works only after login. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/set-page-url.gif?alt=media\&token=4ec81b75-a5b0-4130-8e3c-dda9aacd1c84) Setting page URL #### 3. Sharing deep link[​](/concepts/navigation/deep-dynamic-linking.md#3-sharing-deep-link "Direct link to 3. Sharing deep link") You can share the deep link of the current page by adding the [share action](/concepts/navigation/share-action.md). To share the deep link of the current page: 1. Select the page that you would like to open via a deep link. 2. From that page, select any widget (e.g. share button) from the widget tree or the canvas area. 3. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action flow Editor** in a new popup window. 1. Click on the **+ Add Action**. 2. On the right side, search and select the **Share** action. 3. Set the **Value Source** to **From Variable**. 4. Set the **Source** to **Global Properties**. 5. Set the **Available Options** to **Link To Current Page** and click **Close**. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/sharing-deep-link.gif?alt=media\&token=05521a84-43e4-4c21-869c-5ecdcd1e34c6) Sharing deep link #### 4. Testing deep link[​](/concepts/navigation/deep-dynamic-linking.md#4-testing-deep-link "Direct link to 4. Testing deep link") Deep links can not be tested in Run Mode. Instead, you will need to test the deep links on a real device/emulator. Before you test the deep link, you need to get it first. The easiest way to get it is to run the app on a device/emulator, click on the share button and then copy the deep link. Now, you can test the deep link in two ways: Using CLI tools If you have Android Studio with the SDK platform tools installed, you can run the following command in the terminal and replace it with your deep link. Copy ``` adb shell am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d "designersapp://designersapp.com/profile" ``` Using Firefox mobile browser You can also test the deep link in a Firefox mobile browser. To do so, open the browser, paste the URL in the search bar, open the options menu and click on the **Open in app**. Here is how you do it: ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/deep-link-example.gif?alt=media\&token=d6f40d74-f510-4f49-8026-9ccc87896ff4) Using Firefox mobile browser to open the deep link ## Dynamic Links with Firebase Dynamic Links \[Deprecated][​](/concepts/navigation/deep-dynamic-linking.md#dynamic-links-with-firebase-dynamic-links-deprecated "Direct link to Dynamic Links with Firebase Dynamic Links \[Deprecated]") The dynamic link opens a specific page in your app. Unlike the deep link, the dynamic link survives the app install. That means if the user has not installed the app, they can be taken to the respective store to install the app. After the app is installed, users can be taken straight to the intended app page. For the dynamic link to work, you need to enable the [deep link](/concepts/navigation/deep-dynamic-linking.md#adding-deep-link). You can think of a dynamic link as the additional benefit of the deep link. note FlutterFlow uses [**Firebase Dynamic Link**](https://firebase.google.com/docs/dynamic-links) (a product from Firebase) to create dynamic links. Let’s walk through an example of sharing and opening a profile page using a dynamic link. The example will look like this: ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/deep-link-example.gif?alt=media\&token=d6f40d74-f510-4f49-8026-9ccc87896ff4) Dynamic link example The steps to add the dynamic link are as follows: 1. [Setting up a domain](/concepts/navigation/deep-dynamic-linking.md#1-setting-up-a-domain) 2. [iOS setup](/concepts/navigation/deep-dynamic-linking.md#2-ios-setup) 3. [Set URL scheme](/concepts/navigation/deep-dynamic-linking.md#3-set-url-scheme) 4. [Setting page URL](/concepts/navigation/deep-dynamic-linking.md#4-setting-page-url) 5. [Sharing dynamic link](/concepts/navigation/deep-dynamic-linking.md#5-sharing-dynamic-link) 6. [Testing dynamic link](/concepts/navigation/deep-dynamic-linking.md#6-testing-dynamic-link) ### 1. Setting up a domain[​](/concepts/navigation/deep-dynamic-linking.md#1-setting-up-a-domain "Direct link to 1. Setting up a domain") The dynamic link requires a domain name that will be used as the URL prefix in the link. To set up the domain name, follow the steps below: 1. Open the [Firebase console](https://console.firebase.google.com/), and click on \*\*Dynamic Link \*\* (on the left side menu). 2. Click on the **Get Started** button. This will open a popup. 3. Enter the domain name. If you don't own a domain, you can select the free **Google Provided Domain** that ends with a **page.link**. To set up your own domain, follow the guide [here](https://firebase.google.com/docs/dynamic-links/custom-domains). 4. If you chose Google Provided Domain, you could **Finish** the setup. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/set-up-domain.gif?alt=media\&token=219b0780-3632-478b-918c-05fba91508a3)> Setting up a domain for the dynamic link ### 2. iOS setup[​](/concepts/navigation/deep-dynamic-linking.md#2-ios-setup "Direct link to 2. iOS setup") You must complete additional configuration for the dynamic link to work on the iOS devices. Setting up iOS includes: #### 2.1 Add App Store and Team ID to the Firebase project[​](/concepts/navigation/deep-dynamic-linking.md#21-add-app-store-and-team-id-to-the-firebase-project "Direct link to 2.1 Add App Store and Team ID to the Firebase project") To add the App Store and Team ID to the Firebase project: 1. Open the [Firebase console](https://console.firebase.google.com/), and click on **Project Overview** (on the left side menu). 2. Select the iOS project and click on the Settings (gear) icon inside. 3. Scroll down to see your selected iOS project. 4. Find the **App Store ID** field, click on the edit icon (pencil icon), enter the ID, and click \* *Save*\*. To know where is your App Store ID, click on the question mark icon beside the label. 5. Similarly, find the **Team ID** field, click on the edit icon (pencil icon), enter the ID, and click **Save**. To know where is your Team ID, click on the question mark icon beside the label. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/add-app-store-team-id.gif?alt=media\&token=116cb42a-9bc6-4af5-a9d6-cc5a8a5906f7) Adding App Store and Team ID to the Firebase project #### 2.2 Adding Associated Domain capability to App Store[​](/concepts/navigation/deep-dynamic-linking.md#22-adding-associated-domain-capability-to-app-store "Direct link to 2.2 Adding Associated Domain capability to App Store") To add the Associated Domain capability on App Store: 1. Open the [Apple Developer homepage](https://developer.apple.com/account) and select \* *Certificates, IDs & Profiles*\*. 2. Select **Identifiers** (far left menu) and then click on your app identifier. 3. Checkmark the **Associated Domains** and click **Save**. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/add-capability.gif?alt=media\&token=90315580-9480-40d4-a85b-1957c6d759e2) Adding Associated Domain capability to App Store ### 3. Set URL scheme[​](/concepts/navigation/deep-dynamic-linking.md#3-set-url-scheme "Direct link to 3. Set URL scheme") In this step, You will set the URL scheme. To do that: 1. Navigate to **Settings & Integrations > General > App Details.** 2. If you want to add deep linking on multiple pages and all of them require users to log in, turn on the **Pages Requires Authentication by Default**. 3. Also, turn on the **Use Firebase Dynamic Links**. 4. In **URL scheme** fields, by default, we add the values based on your project name. To change it, enter the **scheme** **name** (before `://`) and **hostname** (after `://`). 5. If you want users to navigate back to the home page instead of closing the app when they press the back button from a deep link page, enable the **Pages Are Subroutes of Root Page** option. \* *Tip*\*: we recommend enabling this option to increase user engagement with your app. ![img\_4.png](/assets/images/img_4-982d4b2527f6ec15aef536da88b9733a.png) ### 4. Setting page URL[​](/concepts/navigation/deep-dynamic-linking.md#4-setting-page-url "Direct link to 4. Setting page URL") The page URL points to the specific page in your app, which is used on the Web and for deep linking on mobile. To set the page URL: 1. Select the page that you would like to open via a dynamic link. 2. Move the **properties panel** on the right and open the **Route Settings** section. 3. By default, the Route is the current page name. Edit this if you want a different name in the page URL. 4. By default, the page does not require authentication when it opens via the dynamic link. However, checkmark the **Requires Authentication** if your app works only after login. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/set-page-url-dynamic-link.gif?alt=media\&token=537674be-d58e-431f-940d-59afda3089d6) Setting page URL ### 5. Sharing dynamic link[​](/concepts/navigation/deep-dynamic-linking.md#5-sharing-dynamic-link "Direct link to 5. Sharing dynamic link") You can share the dynamic link of the current page by adding the [\*\*Generate Current Page Link \*\*](/concepts/navigation/generate-current-page-link.md) action and then sharing it using the [**Share Action**](/concepts/navigation/share-action.md). To share the dynamic link of the page: 1. Select the page that you would like to open via a deep link. 2. Select any widget (e.g., share button) from the widget tree or the canvas area. 3. First, add the action to [Generate Current Page Link](/concepts/navigation/generate-current-page-link.md#defining-generate-current-page-link-action). 4. Now chain the next action to share the dynamic link. 5. To do that, click on the **+** button at the bottom of the box and select **Add Action**. 6. On the right side, search and select the **Share** action. 7. Set the **Value Source** to **From Variable**. 8. Set the **Source** to **Widget State**. 9. Set the **Available Options** to the **Current Page Link** and click **Close**. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/sharing-dynamic-link.gif?alt=media\&token=f9caa24f-efbf-47f2-af9c-ee1172de5863) Sharing dynamic link ### 6. Testing dynamic link[​](/concepts/navigation/deep-dynamic-linking.md#6-testing-dynamic-link "Direct link to 6. Testing dynamic link") Dynamic links can not be tested in Run Mode. Instead, you will need to test the links on a real device/emulator. Before you test the dynamic link, you need to get it first. The easiest way is to run the app on a device/emulator. Click on the share button and then copy the dynamic link. Now you can test the link in a Firefox mobile browser. To do so, open the browser and paste the URL into the search bar. Here is how you do it: ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/dynamic-link-demo-testing.gif?alt=media\&token=5b218ef8-198d-4941-be12-640e9babb3e4) Testing Dynamic Link ## Passing Data with a Link[​](/concepts/navigation/deep-dynamic-linking.md#passing-data-with-a-link "Direct link to Passing Data with a Link") In most cases, you might want to pass custom data with a link. For example, you send the product page link with a discount code and share the profile page with its profile ID. Passing custom data with the link can be used to retrieve the information required to display on the page. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/pasing-data.gif?alt=media\&token=2fc5c267-fc68-4f4f-aa39-41597a5b5e48) Passing profile id in the link To pass custom data with the link, you need to have the following: 1. Make sure you have a parameter defined on a page you want to pass in a dynamic link. ![img\_5.png](/assets/images/img_5-0f8209160033e995ee2a0e3a6fcc4434.png) Adding parameter on page 2. In the **Route Settings**, include a parameter as part of the route by prefixing it with a colon (**:**) for example, `profilePage/:profileId`. ![img\_6.png](/assets/images/img_6-d13a06bd905292dea8a8211cb0d81484.png) Including a parameter in the route That's all you need to pass custom data with a **Deep Link** or **Dynamic Link**. ## Deep Links with Branch.io[​](/concepts/navigation/deep-dynamic-linking.md#deep-links-with-branchio "Direct link to Deep Links with Branch.io") Since **Firebase Dynamic Links** have been deprecated and can no longer be used for new Firebase projects, we can integrate a powerful alternative: **[Branch.io](https://branch.io/)** — a cross-platform solution for deep linking and deferred linking. With Branch, we can support robust deep linking inside FlutterFlow apps without writing a backend from scratch. ### Branch.io Configuration[​](/concepts/navigation/deep-dynamic-linking.md#branchio-configuration "Direct link to Branch.io Configuration") Start by setting up your project in the [Branch Dashboard](https://dashboard.branch.io). Once you’ve created a project: **1. Note down your Branch Key** Once you create a project, the first thing you’ll need to do is note down your **Branch Key**. This key uniquely identifies your app and will be required later when setting up your FlutterFlow configuration. **2. Set up Redirect Links** In the Branch dashboard, you’ll find settings to define fallback URLs — these determine where users are sent if your app isn’t installed. Typically, you would redirect users to the App Store, Play Store, or a custom landing page. Setting up redirects is important because it ensures that your links don't break and that users always have a seamless experience, even if they need to install the app first. **3. Create a Smart Link** After setting up your project and redirects, you can create a new Smart Link from the **Quick Links** tab in the Branch dashboard. Here you’ll be able to set a link title, alias, add analytics tags, and customize the social media preview (such as the image, title, and description). Once saved, Branch will generate a Smart Link that’s ready to use across your campaigns and app flows. Here's a short demo: ### FlutterFlow Configuration Setup[​](/concepts/navigation/deep-dynamic-linking.md#flutterflow-configuration-setup "Direct link to FlutterFlow Configuration Setup") To make **Branch Smart Links** work in your FlutterFlow app, you’ll need to update the native configuration files via the **Custom Code** tab in your project. 1. First, create environment variables for: * `branchHostUrl` (e.g., `brnch4.app.link`) * `branchKey` (your Branch key, use it for production and optionally `branchKeyTest` for dev environments. You can toggle modes through Branch dashboard and also through FlutterFlow environment toggling). 2. Then navigate to, FlutterFlow > Custom Code **🔧 Android Setup** 1. Create two variables in `AndroidManifest.xml` file named `branchKey` and `branchHostUrl` and bind them to the environment variables we earlier created. 2. Add an `intent-filter` block to your **Main Activity** through the **Activity Tags** hook: ``` ``` 3. Add an **App Component** block for meta-data: ``` ``` **🍎 iOS Setup** 1. In `Info.plist`, add a new variable called `branchKey` and bind it to the environment variable. 2. In `Info.plist`, add the following code snippet. ``` branch_key {{branchKey}} ``` 3. In `Runner.entitlements`, add a new variable called `branchHostUrl` and bind it to the environment variable. 4. In `Runner.entitlements`, add the following code snippet. ``` com.apple.developer.associated-domains applinks:{{branchHostUrl}} ``` Branch automatically hosts and serves the `apple-app-site-association` file needed for Universal Links. You don’t need to manually upload it to your domain. **FlutterFlow Routing Setup** FlutterFlow also defines a Custom URI Scheme (like `myapp://`) by default. Even if you're using Branch for web-based Smart Links, it’s a good idea to keep this in sync. 1. Go to: Settings & Integrations > App Settings > App Details 2. Scroll to **Routing & Deep Linking** section. 3. Under Custom URI Scheme, match the URI host/domain to what’s defined in your Branch dashboard (e.g., `brnch4://` or `dreambrush://`). ![custom-uri.png](/assets/images/custom-uri-20835b4d1c0d276769203e5441f65618.png) Even if your links mainly use `https://`, FlutterFlow's routing engine may still use the custom URI internally. Keeping this field consistent prevents confusion or route mismatches. You're now ready to use Branch Smart Links in a FlutterFlow app with seamless deferred deep linking, App/Universal Link verification, and environment-based configuration. ### Integrate Flutter Branch SDK[​](/concepts/navigation/deep-dynamic-linking.md#integrate-flutter-branch-sdk "Direct link to Integrate Flutter Branch SDK") To integrate Branch with your FlutterFlow app, you'll use the [`flutter_branch_sdk`](https://pub.dev/packages/flutter_branch_sdk) Dart package. This will allow your app to listen to Branch links and respond accordingly. 1. Go to your **FlutterFlow project > Settings and Integrations > Pubspec Dependencies** tab, and add the following dependency. ``` flutter_branch_sdk: ^5.0.1 ``` Make sure to use the latest version available from [pub.dev](https://pub.dev/packages/flutter_branch_sdk) 2. Create a Custom Action to initialize the Branch SDK. This ensures the Branch session is set up when your app starts. ``` import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; Future initBranch() async { // Add your function code here! await FlutterBranchSdk.init(); } ``` Call this action inside the **Final Actions** of your `main.dart`. 3. Create another custom action to listen for Branch link clicks and optionally route the user: ``` // Automatic FlutterFlow imports import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/custom_code/actions/index.dart'; // Imports other custom actions import '/flutter_flow/custom_functions.dart'; // Imports custom functions import 'package:flutter/material.dart'; // Begin custom action code // DO NOT REMOVE OR MODIFY THE CODE ABOVE! import 'dart:async'; import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; import 'package:flutter/services.dart'; StreamSubscription? _branchSubscription; // stream subscription that listens for branch links final Set _handledBranchLinks = {}; Future handleBranchDeeplink(Future Function(dynamic data) onLinkOpened) async { // Add your function code here! if (_branchSubscription != null) return; // If already listening, ignore link _branchSubscription = FlutterBranchSdk.listSession().listen( (data) async { final clicked = data['+clicked_branch_link'] == true; if (!clicked) return; final uniqueId = data['~referring_link'] ?? data['deeplink_path'] ?? ''; if (_handledBranchLinks.contains(uniqueId)) return; _handledBranchLinks.add(uniqueId); await onLinkOpened(Map.from(data)); // call action defined by user & pass the link data. }, onError: (error) { if (error is PlatformException) { print('[Branch] PlatformException: ${error.code} - ${error.message}'); } else { print('[Branch] Unknown error: $error'); } }, ); } ``` You can pass custom key-value pairs like `"page": "paywall"` or `"navigation_type": "bottom_sheet"` when creating the Branch link, and retrieve them here to decide which screen to navigate to in FlutterFlow. Be sure to test both fresh installs (deferred deep links) and existing app sessions to confirm that your actions run as expected. tip For a complete walkthrough, check out the tutorial video: [YouTube video player](https://www.youtube.com/embed/nEBot6-zhfY?si=y-flWx8zoGH8mgjM) ## Branch Deeplinking Library[​](/concepts/navigation/deep-dynamic-linking.md#branch-deeplinking-library "Direct link to Branch Deeplinking Library") If you’d prefer not to integrate Branch.io from scratch, we have introduced the **Branch Deep Linking Library** that you can import from the Marketplace completely free. This library sets up everything you need for routing users into your app using Branch’s smart links — with native configuration, link handling, and deep link helpers already wired in. ### Install Library[​](/concepts/navigation/deep-dynamic-linking.md#install-library "Direct link to Install Library") You can install the [Branch Deeplinking Library from the Marketplace](https://marketplace.flutterflow.io/item/oAco1HzQHxtOVE1ssTcC). Refer to the [Add Library Item](/marketplace/adding-purchasing-item.md#add-library-item) instructions to see how to add it to your account. ### Branch Setup[​](/concepts/navigation/deep-dynamic-linking.md#branch-setup "Direct link to Branch Setup") You’ll need three values from your Branch dashboard: * **Branch Key**: Your production or test key from the Branch dashboard. * **Custom Link Domain**: Your primary Branch link domain (e.g., yourapp.app.link). This is used to generate and handle smart links. * **Alternate Link Domain**: An additional Branch domain (e.g., yourapp-alternate.app.link) that points to the same link data and behavior. This is recommended for ensuring better deliverability across platforms and channels, and must be included in your platform configuration. We recommend storing these values in Environment Variables so you can: * Manage them per environment (e.g., dev vs prod Branch keys). * Easily assign them to the library’s configuration when adding it to a project. **Adding Library Values** When you add the **Branch Deep Linking Library** to your project (ensure you are on +0.0.7 and above), it will prompt you to provide four values: * `branchApiKey` * `branchLinkDomain` * `branchAlternateLinkDomain` * `isTestMode` Use the environment variables you created to populate these values. info `isTestMode` should be set to false when running your app in production. Here’s a quick demo to show how to configure those values inside your library panel. #### Initialize the Branch SDK[​](/concepts/navigation/deep-dynamic-linking.md#initialize-the-branch-sdk "Direct link to Initialize the Branch SDK") Open your `main.dart` file in FlutterFlow and add the `initBranch` custom action under the **Final Actions** section. This ensures the **Branch SDK** is initialized when your app launches. ### Handle Branch Deeplink \[Custom Action][​](/concepts/navigation/deep-dynamic-linking.md#handle-branch-deeplink-custom-action "Direct link to Handle Branch Deeplink \[Custom Action]") To receive and act on deep link data, go to your **Entry Page** or **Logged-In Page** and add the `handleBranchDeeplink` action as the first action in the page flow. This `handleBranchDeeplink` action listens for incoming Branch Deeplinks and handles routing logic. This action should be added to your **Entry Page** or **Logged-In Page** under the **onPageLoad** trigger. It initializes a stream listener that waits for Branch links to be opened (either deferred or direct). Ensure this is the first action of your **on Page Load** action trigger. **`onLinkOpened` Action Callback** When a link is received, the `onLinkOpened` callback is triggered with the [**link data**](/concepts/navigation/deep-dynamic-linking.md#linkdata-action-parameter), allowing you to perform custom navigation or logic. You can perform your navigation logic in this action callback. #### `linkData` Action Parameter[​](/concepts/navigation/deep-dynamic-linking.md#linkdata-action-parameter "Direct link to linkdata-action-parameter") The `handleBranchDeeplink` action receives a `linkData` object that contains all the metadata sent with the link. The `linkData` parameter is a Map containing useful information from the Branch link. In the Dreambrush app example, we get the following link data: ``` { "$og_title": "Check out my Ai Image on DreamBrush!", "$publicly_indexable": true, "imageId": "QiC94EaGNoonEKzln07A", "~creation_source": 4, "$og_description": "This image was created with DreamBrush app. You can check it out here.", "+click_timestamp": 1750099254, "$match_duration": 100000, "~feature": "Ai Image Creation", "$tags[0]": "generation", "+match_guaranteed": true, "$alias": "", "$canonical_identifier": "/imageDetails/QiC94EaGNoonEKzln07A", "+clicked_branch_link": true, "~id": "1461141612502859827", "+is_first_session": false, "~campaign": "Image Generation", "~referring_link": "https://dreambrush.app.link/DZ9liDTc6Tb", "~channel": "Share" } ``` Link Structure Your link data might not look *exactly* like the example shown above. However, it will follow a **similar structure** with comparable keys and values. Some of the important keys we should know about: * **`$canonical_identifier`:** The original route path used when the link was generated (e.g., `/imageDetails/:id`). You can explicitly set this value when creating a link through the **[Generate Link](/concepts/navigation/deep-dynamic-linking.md#generate-link-custom-action)** action. If you don’t set it, Branch will infer it based on the link's destination or content metadata. * **`~referring_link`:** The full Branch URL that was clicked. * **`$og_title`:** This is the headline that will appear in the link preview. This is set by the user through the **[Generate Link](/concepts/navigation/deep-dynamic-linking.md#generate-link-custom-action)** action. * **`$og_description`:** This is the description text shown below the title in the link preview. This is set by the user through the **[Generate Link](/concepts/navigation/deep-dynamic-linking.md#generate-link-custom-action)** action. * **`~channel`**, **`~feature`**, **`~campaign`** and **`$tags[0]`** are part of Branch’s user-defined analytics and attribution metadata. These fields are explicitly set by users when creating a link (e.g., via the **[Generate Link](/concepts/navigation/deep-dynamic-linking.md#generate-link-custom-action)** action), and they help organize and analyze your link performance across platforms and campaigns. * **`page`:** This is a suggested custom key that can be set by the user when generating the link. It typically defines the target page or screen the app should navigate to when the link is opened (e.g., "paywall", "productPage", "onboardingStep2"). While not a reserved Branch key, it's a commonly used naming convention for handling deep links and routing logic within the app. * Any other custom parameters added during link creation (e.g., `productId`, `referrer`, etc.). Ensure the key and value are both `String`. This lets you write flexible, conditional navigation logic based on what was shared. For example, in the following example, we can even show a bottom sheet based on the page value. Use the link data from this callback to: * Navigate to a page. * Show a bottom sheet. * Load content from Firestore using a referenced ID. #### Using Global Context to Navigate[​](/concepts/navigation/deep-dynamic-linking.md#using-global-context-to-navigate "Direct link to Using Global Context to Navigate") In certain app structures, especially when the home page is removed from the navigation stack early, standard navigation using the local context may fail. To ensure deep linking and routing continue to work reliably in these scenarios, you can override the local context with the global navigator context. This approach ensures that navigation logic is not tied to the widget hierarchy at the time of execution, making it more robust and flexible. See a **[detailed example](/concepts/navigation/deep-dynamic-linking.md#dreambrush-example)** using the DreamBrush app. Testing Deeplinks It’s recommended to test deep links on a **physical device**, as link verification (especially for Universal Links or App Links) may not consistently work on emulators or simulators. We recommend using **[Local Run](/testing/local-run.md)** to run your apps on physical devices. ### Generate Link \[Custom Action][​](/concepts/navigation/deep-dynamic-linking.md#generate-link-custom-action "Direct link to Generate Link \[Custom Action]") The `generateLink` action allows you to create a custom Branch Smart Link directly from your FlutterFlow app. This is especially useful when you want to let users: * Share app content (like a post, product, or image). * Invite others with referral codes. * Trigger deep links that take recipients to specific app screens. The action accepts the following parameters: * **`canonicalIdentifier`** – A unique path for the content (e.g., `/imageDetails/:id`). This becomes the key reference used when routing the user back into the app. * **`title`** – The link's title (used in social previews or analytics). * **`description`** – (Optional) A short description of the content. * **`metadata`** – A dynamic map of custom parameters to include with the link (e.g., page: "imageDetails", imageRef: "abc123", etc.) * **`linkProperties`** – A dynamic map for configuring how the link behaves (e.g., set the `feature`, `channel`, `campaign`, or `stage` for analytics). JSON maps Due to a limitation, if you plan to leave map-type variables (like `metadata` or `linkProperties`) empty, you must still pass them as **empty maps**, not `null`.
Ensure all keys and values are **plain strings**, avoid nested JSON or non-string types.
Incorrect structure may cause the Link Generation action to fail silently. ### Branch Helper Functions[​](/concepts/navigation/deep-dynamic-linking.md#branch-helper-functions "Direct link to Branch Helper Functions") These functions help you safely work with deep link data, extract values, and conditionally navigate based on link metadata. * **`isTargetingPage(linkData, targetPage)`** - Checks whether the page value in the link data matches a specific screen name. The `page` parameter is set by the user when generating the link from Branch dashboard or FlutterFlow. For example, if the target page value in your deep link is "paywall", you can use this function to check for this value and navigate accordingly. * **`getCanonicalIdentifierFromLink(linkData)`**: Helper function that returns the canonical path (e.g., `/imageDetails/abc123`) that was originally attached to the smart link. Useful for extracting the base route or content reference associated with the shared link. * **`getReferringLinkFromLink(linkData)`**: Helper function that retrieves the full Branch smart link URL from the data (typically under the `~referring_link` key). Useful for tracking, analytics, or verifying the source of the link. * **`getLastPathSegmentFromMap(linkData, key)`**: Extracts the last path segment (e.g., `abc123`) from a URI stored inside a link data field (e.g., `/imageDetails/abc123`). This is especially useful when your deep link contains a structured path, like `/imageDetails/abc123` and you want to retrieve just the ID (`abc123`). * **`getLinkValue(linkData, key)`**: Safely retrieves any single value from the link data Map. Returns null if not found. (e.g., retrieving `showPromo` attribute value from the `linkData`). warning If you're trying to retrieve default Branch keys like `~channel` or `$canonical_identifier`, make sure to include the special character (e.g., `~` or `$`) as part of the key string. * **`createLinkProperties(...)`**: Returns a Branch Link Properties map used when generating a smart link. You can define values like: feature, campaign, stage, channel, alias or tags or custom fallback URLs. Useful for organizing and tracking generated links for marketing or referrals. ### DreamBrush Example[​](/concepts/navigation/deep-dynamic-linking.md#dreambrush-example "Direct link to DreamBrush Example") In the DreamBrush app, we can use `generateLink` after a user finishes generating an image. The link could include: * **canonicalIdentifier**: Current Page Route that is `/imageDetails/:imageRef`. * **page**: Target page name `imageDetails`. * **title**: "Check out my AI image!" This link can then be shared via WhatsApp, email, or social media — and when clicked, it brings the recipient directly to that content inside the app. Here's a quick example of generating a Branch link from a page that uses a **Firebase Document ID** as a route parameter. Now in your `handleBranchDeeplink` action callback, add the additional logic to handle such custom links: To demonstrate how to use the global context for navigation, add a new **Execute Custom Code** Action just before the **Navigate To** Action, and insert the following code. ``` final context = appNavigatorKey.currentContext!; ``` This ensures that the navigation logic uses the global navigator context, which is essential if your app structure removes the home page early in the lifecycle. In such cases, relying on a local context may cause deep linking to fail—using a global context guarantees that navigation still works reliably. Paid Plans Note: The **Execute Custom Code** Action is available only on the [**paid plans**](https://www.flutterflow.io/pricing). ### FAQs[​](/concepts/navigation/deep-dynamic-linking.md#faqs "Direct link to FAQs") Why isn't my deep link working when I navigate to another page from the home page? This often happens because the Home Page gets removed from the navigation stack, especially when **Allow Navigate Back** is disabled in the **Navigate To** Action. Since the deep link handler is typically defined on the Home Page, it gets disposed once the page is removed, causing deep links to stop working when triggered later. ✅ Preferred Solution: **Use Global Context for Navigation** Instead of relying on the Home Page's presence to handle deep links, configure your navigation logic to use the global navigator context. This ensures navigation will work even if the Home Page has been removed from the stack. You can do this by adding an **Execute Custom Code** Action before the **Navigate To** Action. See the **[complete example](/concepts/navigation/deep-dynamic-linking.md#using-global-context-to-navigate)**. ✅ Alternative (but limited) Solution: **Keep the Home Page in Stack** If you're not using global context, you can prevent this issue by keeping the Home Page in memory: Enable "Allow Navigate Back" on any navigation actions from your Home Page, even if the navigation isn't triggered from deep links directly. This keeps the Home Page alive so it can continue listening for deep link events. Why is my Branch link generation failing? This often happens because one or more of the inputs passed to the action (like `metadata` or `linkProperties` or `customParams` when using `createLinkProperties` helper function) contains invalid JSON formatting. Branch expects these values to be passed as a map of plain `String` key-value pairs, not as nested JSON, objects, or dynamic types. Ensure both **Key and Value's expected type** is `String` and `String` and try again. Why isn’t deep linking working when testing from a simulator? Deep linking, especially Universal Links and deferred deep linking may not work reliably on iOS or Android simulators/emulators due to platform limitations. Simulator Limitations: * **iOS:** Simulators cannot verify Universal Links properly (no App Store, limited AASA domain support). * **Android:** Some versions fail to auto-verify App Links or handle deferred deep links without Play Services. ✅ Recommended: Always test deep linking on a physical device for accurate behavior. --- # Generate Current Page Link Using this action, you can generate the dynamic link for the current page. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/dynamic-link-demo.gif?alt=media\&token=f6aee025-782a-45b9-baa6-3d357ca30cec) Sharing and opening a dynamic link Prerequisites Before adding this action, ensure you have performed all the steps to [**add the dynamic link**](/concepts/navigation/deep-dynamic-linking.md#deep-links-with-branchio). ## Defining Generate Current Page Link action[​](/concepts/navigation/generate-current-page-link.md#defining-generate-current-page-link-action "Direct link to Defining Generate Current Page Link action") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g. share button) on which you want to define the action. 2. Select **Actions** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu), and click **Open**. This will open an **Action flow Editor** in a new popup window. 1. Click on the **+ Add Action**. 2. On the right side, search and select the **Generate Current Page Link** action and click **Close**. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/adding-share-action.gif?alt=media\&token=b94f6e86-1c1f-4a19-ad0b-b83cc66fc08f) Adding Generate Current Page Link action --- # Launch URL \[Action] The Launch URL Action lets you specify a URL that will be opened using an app supporting it. If there is more than one app that can handle the specified URL, the user will be presented with a dialog from where one of the apps can be selected. ## Adding Launch URL Action[​](/concepts/navigation/launch-url.md#adding-launch-url-action "Direct link to Adding Launch URL Action") Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. On the right side, search and select the **Launch URL** (under widget/UI Interactions) action. 5. In the *URL Value Type* property, select either **Specify URL** (to add the URL as a String) or **From Variable** (to use the value stored in a String variable). 6. If using **Specify URL**, enter the URL that you want to use in the **URL** field. For example, you can enter "[https://flutter.dev](https://flutter.dev/)" to open the Flutter webpage. 7. If using **From Variable**, select the **Source** from which to fetch the URL value. You can also specify a **Default Value** that will be used when the variable value is not set (i.e. null). ![launch-url.avif](/assets/images/launch-url-d7c613a47c466132a877ded81754a642.avif) *** ## URL schemes[​](/concepts/navigation/launch-url.md#url-schemes "Direct link to URL schemes") A URL scheme is a way to define how different types of links, such as webpages, phone numbers, SMS messages, and emails, should be handled by an app or browser. The following are some common URL schemes that can be handled by an external app present on the user's device. ### Open a webpage[​](/concepts/navigation/launch-url.md#open-a-webpage "Direct link to Open a webpage") This URL scheme for loading up a webpage can be defined in this format: #### Scheme[​](/concepts/navigation/launch-url.md#scheme "Direct link to Scheme") `http:` `https:` #### Example[​](/concepts/navigation/launch-url.md#example "Direct link to Example") `https://flutter.dev` ![webpage.gif](/assets/images/webpage-e77c8217ba0e3808009841e02573d1ea.gif) ### Use a phone number[​](/concepts/navigation/launch-url.md#use-a-phone-number "Direct link to Use a phone number") This URL scheme helps to handle phone numbers inside your app. Using this, you can easily initiate a phone call to the provided phone number from the user's device. #### Scheme[​](/concepts/navigation/launch-url.md#scheme-1 "Direct link to Scheme") `tel:` #### Example[​](/concepts/navigation/launch-url.md#example-1 "Direct link to Example") `tel:2125551212` ![phone.gif](/assets/images/phone-0d973c2de6a23a5c6f6797c80608194d.gif) ### Compose a text message[​](/concepts/navigation/launch-url.md#compose-a-text-message "Direct link to Compose a text message") This URL scheme lets you redirect users from your app to compose and send an SMS message to a specified phone number. #### Scheme[​](/concepts/navigation/launch-url.md#scheme-2 "Direct link to Scheme") `sms:` #### Example[​](/concepts/navigation/launch-url.md#example-2 "Direct link to Example") `sms:2125551212` ![text-message.gif](/assets/images/text-message-da7b8687d93f06c971fecff069d70cbd.gif) ### Create an email[​](/concepts/navigation/launch-url.md#create-an-email "Direct link to Create an email") This URL scheme helps you to launch an email app on the user's device. It allows you to pass the *email to*, *subject*, and *body* to the app so that you have these fields prefilled with details as the email app is opened. #### Scheme[​](/concepts/navigation/launch-url.md#scheme-3 "Direct link to Scheme") `mailto:?subject=&body=` #### Example[​](/concepts/navigation/launch-url.md#example-3 "Direct link to Example") `mailto:name@example.org?subject=Welcome%20to%20FlutterFlow&body=Hey%20there` This will pass the following details to the email app: ***mailto:*** , ***subject:*** Welcome to FlutterFlow, ***body:*** Hey there ![ceate-email.gif](/assets/images/ceate-email-3b1a5a099752899687f96c37882c340d.gif) --- # Overview Navigation in FlutterFlow is a crucial aspect of app development, enabling users to move between different pages or screens. This is achieved through a system of routing, where each page is assigned a unique route identifier. Understanding how navigation works and what happens to the navigation stack under the hood can help you create a seamless user experience. ## What are Routes?[​](/concepts/navigation/overview.md#what-are-routes "Direct link to What are Routes?") Routes are essentially the paths that define different screens or pages within the app. Each route is associated with a specific screen and has a unique identifier that allows the app to recognize and navigate to it. For example, a route could point to the home screen, a product details page, or a user profile page. | Page | Route | | --------------- | ---------------- | | Home | /home | | Product Details | /product-details | | Cart | /cart | ## Navigation Stack Logic[​](/concepts/navigation/overview.md#navigation-stack-logic "Direct link to Navigation Stack Logic") The **navigation stack** is a data structure that keeps track of the routes as they are pushed and popped off the stack. It follows the Last In, First Out (LIFO) principle, meaning the last screen that was navigated to is the first one to be navigated away from when the user presses the back button. Here’s how the navigation stack logic works in FlutterFlow: ### 1. Pushing a Route[​](/concepts/navigation/overview.md#1-pushing-a-route "Direct link to 1. Pushing a Route") When you navigate to a new screen, that route is pushed onto the top of the stack. For example, if you are on the home screen and navigate to the profile screen, the profile screen route is pushed onto the stack. ![pushroute.avif](/assets/images/pushroute-cc5b83d167d62aa624456a276a131eff.avif) ### 2. Popping a Route[​](/concepts/navigation/overview.md#2-popping-a-route "Direct link to 2. Popping a Route") When you navigate back, the topmost route is popped off the stack, and the previous screen becomes visible. For example, if you are on the profile screen and press the back button, the profile screen route is popped off, revealing the home screen. ![poproute.avif](/assets/images/poproute-9da2a09d047959456e62dda3b2b3c9c9.avif) ### 3. Replacing a Route[​](/concepts/navigation/overview.md#3-replacing-a-route "Direct link to 3. Replacing a Route") Sometimes, you might want to replace the current route with a new one without adding to the stack. This is useful for actions like logging in, where you don’t want users to navigate back to the login screen after they have logged in. For example, after a successful login, replace the login screen route with the home screen route. ![replaceroute.avif](/assets/images/replaceroute-a31973716265cb977d00fc85c50fd311.avif) ## Navigation Actions[​](/concepts/navigation/overview.md#navigation-actions "Direct link to Navigation Actions") In FlutterFlow, there are three main navigation actions you can use to navigate between different screens in your app. Here are they: 1. [Navigate To (Push a Route)](/concepts/navigation/overview.md#1-navigate-to-push-a-route) 2. [Navigate Back (Pop a Route)](/concepts/navigation/overview.md#2-navigate-back-pop-a-route) 3. [Replace Route](/concepts/navigation/overview.md#3-replace-route) ### 1. Navigate To (Push a Route)[​](/concepts/navigation/overview.md#1-navigate-to-push-a-route "Direct link to 1. Navigate To (Push a Route)") This action involves navigating to a new screen by pushing a new route onto the navigation stack. **What Happens Under the Hood:** * When you push a route, a new screen is placed on top of the current stack. This means the previous screen is still in the stack but is not visible to the user. * The new screen becomes the active screen that the user interacts with. info Learn more about adding this action in the [**page navigation guide**](/concepts/navigation/page-navigation.md#navigate-to-action). ### 2. Navigate Back (Pop a Route)[​](/concepts/navigation/overview.md#2-navigate-back-pop-a-route "Direct link to 2. Navigate Back (Pop a Route)") This action involves navigating back to the previous screen by popping the current route off the navigation stack. **What Happens Under the Hood:** * When you pop a route, the current screen is removed from the stack, and the previous screen becomes active again. * This action effectively reverses the last push operation. info Learn more about adding this action in the [**page navigation guide**](/concepts/navigation/page-navigation.md#navigate-back-action). ### 3. Replace Route[​](/concepts/navigation/overview.md#3-replace-route "Direct link to 3. Replace Route") This action involves replacing the current route with a new route. Unlike pushing a route, replacing a route does not add to the stack but swaps the current route with the new one. **What Happens Under the Hood:** * The current screen is removed from the stack, and the new screen is added in its place. info * This is useful when you want to prevent the user from navigating back to the previous screen. * This action is essentially the **Navigate To** action with the **Replace Route** option enabled. Learn more about adding this action in the [**page navigation guide**](/concepts/navigation/page-navigation.md#navigate-to-action). --- # Page Navigation Page Navigation in FlutterFlow is handled through routing, where each page is identified by a unique route. Navigation can be programmed to happen on events like button clicks, leading to actions such as pushing a new route (opening a new page) or popping a route (returning to a previous page). FlutterFlow simplifies the routing process, allowing you to visually design the navigation flow of your app. Let's see how to do that in FlutterFlow: [Navigate](https://demo.arcade.software/EwmbXvNO5SvWtQdQyTBK?embed\&show_copy_link=true) ### Navigate To \[Action][​](/concepts/navigation/page-navigation.md#navigate-to-action "Direct link to Navigate To \[Action]") The Navigate To Action allows you to set the next page and modify other navigation-related properties: | Action Property Name | Type | Description | | ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Allow Back Navigation** | Toggle | Toggle this to prevent the user from navigating back to this page after moving to the next page | | **Replace Route** | Toggle | Use this option to replace the current page in the navigation stack. For example, if a user navigates from Page A to Page B and then to Page C, pressing the back button on Page C would normally return to Page B. However, if **Replace Route** is enabled on Page B, the route changes to Page A -> Page C; therefore, pressing the back button on Page C will take the user back to Page A. | | **Transition Type** | Drop Down | This allows you to specify an animation that will be applied while navigating away from a screen. Options include **Default, Instant, Fade In, Slide Up, Slide Down, Slide Left, Slide Right,** and **Scale**. | | **Transition Duration** | Double | Set the duration of the transition animation in milliseconds | | **Page Parameters** | | Use this to send data to the next page during navigation. | Note **Allow Back Navigation** does not affect the Android back button. To disable the Android back button, set **Disable Android Back Button** property on the destination page. ![Nav.png](/assets/images/Nav-d529f8e9c3602314f487d0cf3a6ab17d.png) Properties of a Navigate To Action ### Navigate Back \[Action][​](/concepts/navigation/page-navigation.md#navigate-back-action "Direct link to Navigate Back \[Action]") In the next page you are navigating to, ensure that you add a 'Navigate Back' action to the AppBar or wherever you want users to navigate from. Let's add a ' Navigate Back' action to our subsequent page, from which we navigated in the previous section: --- # PageView The PageView widget is used to create swipeable pages. In page view, you can add multiple child widgets, each of which is considered a page and can be scrolled horizontally or vertically. The PageView is useful when you have a collection of pages that you want to display one at a time, especially if you want the user to be able to swipe between them, such as in an onboarding screen, an app that shows a short video by swiping up or down just like Instagram, TikTok, Youtube shorts, etc. ![PageViewDemo](/assets/images/PageViewDemo-8515173fd8c7f97e54d5d4fef3983cf7.avif) ## Adding PageView widget[​](/concepts/navigation/pageview.md#adding-pageview-widget "Direct link to Adding PageView widget") To add the PageView widget to your app: 1. Add the **PageView** widget from the **Layout Elements** tab. 2. By default, it adds three pages and shows the first one in the canvas. In the widget tree, it is represented as **PageView Page**. To see another page in the canvas, move to the **Properties Panel >** set the **Active Page** to the page you want to see. 3. To add a new page, move to the **Properties Panel > Active Page >** click **+ Add Page**. 4. To delete any page, select the **PageView Page** (which you want to delete) from the widget tree or the canvas area and press the **Delete** key on the keyboard. 5. By default, PageView Page contains an [Image](/resources/ui/widgets/image.md) widget; however, you can customize it as per your requirement. For example, if you want to use the PageView widget to create an onboarding experience, you could wrap (`⌘` + B) the default image widget inside the Stack widget and then add some more widgets. ## Adding infinite scroll[​](/concepts/navigation/pageview.md#adding-infinite-scroll "Direct link to Adding infinite scroll") The PageView widget is an incredibly versatile widget that can be utilized in a variety of situations to create interactive applications. For example, you might want to use it in an app that involves reading books, magazines, or similar content to mimic the experience of flipping through pages. In such situations, you might consider adding an infinite scroll on this widget, which automatically loads the new pages as you swipe. We have already covered how to [add infinite scroll on ListView](/resources/ui/widgets/composing-widgets/list-grid.md#adding-infinite-scroll) widget, which will give you an overall idea of how to add infinite scroll on the PageView widget as well. ## Customizing[​](/concepts/navigation/pageview.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Changing the scroll direction[​](/concepts/navigation/pageview.md#changing-the-scroll-direction "Direct link to Changing the scroll direction") By default, the PageView comes with a horizontal scroll for the pages. To change the scroll direction to vertical, move to the **Properties Panel > Page View Properties >** set the **Axis** to **Vertical**. ### Enable/disable swipe to scroll[​](/concepts/navigation/pageview.md#enabledisable-swipe-to-scroll "Direct link to Enable/disable swipe to scroll") This widget allows you to change the page using a swipe gesture as well as clicking on the indicator (3 dots at the bottom indicate which page is being viewed). You can change this behavior and only allow changing the page on click of the indicator. To do so, move to the **Properties Panel > Page View Properties >** disable **Allow swipe scrolling**. ### Update page on swipe[​](/concepts/navigation/pageview.md#update-page-on-swipe "Direct link to Update page on swipe") Sometimes you might want to rebuild the page on which the PageView widget is contained. i.e., rebuilding the outside of the page view widget. You might want to load data or show/hide UI elements based on the page currently being displayed. For example, you could display a floating action button only on a certain page or show/hide certain widgets based on the page index. To do so, move to the **Properties Panel > Page View Properties >** turn on the **Update Page on Swipe**. Here's an example of displaying the current page index on a page that contains the PageView widget. ### Trigger action on swipe[​](/concepts/navigation/pageview.md#trigger-action-on-swipe "Direct link to Trigger action on swipe") You might want to trigger an action when the page is swiped in the PageView widget. For example, you might want to load data for a specific page only when the user swipes to it instead of loading all the data upfront. To trigger action on swipe: 1. Select the widget from the widget tree or canvas area. 2. Select **Actions** from the Properties Panel (the right menu), and click **+ Add Action**. 3. You will notice that the **Type of Action** (aka callback) is already set to **On Page Swipe**. That means actions added under this will be called whenever the page is swiped. 4. Now, you can add any action here. Here is an example showing the [snackbar](/resources/ui/pages/scaffold.md#snackbar) message whenever the page is swiped to the second page. ### Setting initial page index[​](/concepts/navigation/pageview.md#setting-initial-page-index "Direct link to Setting initial page index") You might want to display a specific page as soon as it is loaded. To do so, move to the **Properties Panel > Page View Properties >** enter the **Initial Page Index** value. Please **note** that the page index starts from 0. So, if you want to set page 1, you should enter 0. If you want to set page 2, you should enter 1, and so on. ![setting-initial-page-index.png](/assets/images/setting-initial-page-index-0e4bce1a95e33b2bef3bc25dacd1c5b7.png) ### Set margin[​](/concepts/navigation/pageview.md#set-margin "Direct link to Set margin") Margin adds a space between the PageView content and its border. To change the margin, select the **PageView** widget, move to the **Properties Panel > Page View Properties >** find the **Margin** property, and change the values. ### Customize the indicator[​](/concepts/navigation/pageview.md#customize-the-indicator "Direct link to Customize the indicator") The Indicator helps you identify which page is currently being viewed. You can change the appearance of the Indicator using the various properties available under the *Indicator Properties* section. To customize the indicator: 1. Select the **PageView** widget, and move to the **Properties Panel > Indicator Properties**. 2. To change the indicator position, 1. Find the **Horizontal Alignment** property and adjust the value by using the slider or entering a value. A value of -1 will place the Indicator all the way to the left, while a value of 1 will place the Indicator all the way to the right. 2. Similarly, you can also change the indicator position vertically using the **Vertical Alignment** property. A value of -1 will place the Indicator all the way to the top, while a value of 1 will place the Indicator all the way to the bottom. 3. To add padding around the indicator, find the **Padding** property and enter the values in L (Left), T (Top), R (Right), and B (Bottom) properties to get the desired result. 4. To change the active and inactive color, use the **Active Color** and **Inactive Color** properties to change the color. 5. To change the indicator dot size, use the **Dot Width** and **Dot height** properties. 6. To change the size of an active dot, you can use the **Expansion Factor** property. For example, if you enter 2, the active dot size will be twice its normal size. info The width of the active dot is calculated by multiplying the value of the **Dot Width** property with the value of the **Expansion Factor** property. That means if the Dot Width is set to 40 and *Expansion Factor* is set to 2, then the width of the Active dot will be 80. 1. To add space between the indicator dots, use the **Spacing** property. 2. To adjust the rounded corner of indicator dots, use the **Border Radius** property. 3. To show only the border, enable the **Outline** toggle. 4. If you want to hide the indicators, disable the **Show Indicator** toggle. ### Scroll PageView on button press[​](/concepts/navigation/pageview.md#scroll-pageview-on-button-press "Direct link to Scroll PageView on button press") If you use the PageView widget to create the onboarding experience, you may probably want to allow users to scroll the pages on button press (e.g., next, previous, and skip buttons) in addition to the swipe to scroll. You can do so by adding the PageView and then defining the Control Page View action on the Tap of a Button widget. Here's an example of scrolling PageView on button press: 1. First, [add the PageView](/concepts/navigation/pageview.md#adding-pageview-widget) widget. 2. [Customize the PageView](/concepts/navigation/pageview.md#customizing) widget and add buttons to go to the previous and next pages. 3. Now select any button and define the [Control Page View action](/concepts/navigation/pageview.md#control-page-view-action). ## Control Page View \[Action][​](/concepts/navigation/pageview.md#control-page-view-action "Direct link to Control Page View \[Action]") By using this action, you can gain more control over the scrolling behavior of the PageView widget. For instance, you can enable your users to move to the next or previous page with a single tap of a button or to quickly jump to a specific page index based on their preferences. ### Types of page view action[​](/concepts/navigation/pageview.md#types-of-page-view-action "Direct link to Types of page view action") These are the types of actions you can add to the pageview. * **Previous**: Scroll to the previous page in the pageview. * **Next**: Scroll to the next page in the pageview. * **First**: Scroll to the first page in the pageview. * **Last**: Scroll to the last page in the pageview. * **Jump to**: Scroll to a specific page in the pageview. Please note that the page index starts from 0. So, if you want to jump to page 1, you should enter 0. If you want to jump to page 2, you should enter 1, and so on. ### Adding Control Page View action[​](/concepts/navigation/pageview.md#adding-control-page-view-action "Direct link to Adding Control Page View action") Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Control Page View** (under *Widget/UI Interactions*) action. 4. Set the **Page View to Control** to the **name** of the page view added to your page. 5. Select the [**Page View Action Type**](/concepts/navigation/pageview.md#types-of-page-view-action). ## Video guide[​](/concepts/navigation/pageview.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: --- # Passing Data between Pages As you build your app, you'll often encounter the need to pass through or transfer data from one page to another. For instance, when a user taps on a product item, you may want to send product data to the next page to display its details. ## Page parameters[​](/concepts/navigation/passing-data.md#page-parameters "Direct link to Page parameters") This process of passing data between pages is accomplished using **Parameters**. When navigating from one page to another, you can send parameters to configure the destination page based on the data from the current page. This is useful for tasks like passing a user ID to a profile page or specific details to a detailed view page. To create a page parameter, follow the steps: [Create Page Parameters](https://demo.arcade.software/oZV2X0pKNYO61p1jhY22?embed\&show_copy_link=true) When a page parameter is set to Required, it indicates that this parameter is mandatory when navigating to this page. Users must provide this value; otherwise, FlutterFlow will throw errors. However, if you are creating an optional parameter, please ensure this option is unchecked. Additionally, you can specify a default value in the Default Parameter Value field to safeguard against incoming values that are empty or null. This step is optional. ![Page-Params.png](/assets/images/Page-Params-da3dd75f70356ff8b7bb002e2c199dd4.png) If you have created a **Required** Page Parameter and there is a Navigation Action already set on your previous page, FlutterFlow will throw errors because this required parameter has not yet been sent from the previous page. Let's fix that: [Send Page Parameters](https://demo.arcade.software/kp34JJipEW24hz0u5RsW?embed\&show_copy_link=true) info Passing data can only be tested in **Run** and **Test** Mode (it can not be tested in Preview Mode). ## When to use Page Parameters?[​](/concepts/navigation/passing-data.md#when-to-use-page-parameters "Direct link to When to use Page Parameters?") Page parameters are used to pass essential data between pages that is not persisted in the app’s global state but is necessary for specific functionalities or displays on the subsequent page. Here’s a breakdown of typical uses: * **Contextual Data:** Information that defines the context of the new page, such as identifiers for items or entities that the page must display. This could include identifiers for transactions, specific products, or user profiles that were selected on the previous page. * **Configuration Options:** Settings or options chosen by the user that affect how the next page functions or appears. For example, filter or sort preferences selected on a list page that need to be applied on a subsequent results page. * **Operational Parameters:** Values needed for calculations or logic on the next page that are generated through user activities on the current page. These could be values like quantities, dates, or configuration details necessary to perform operations or initiate processes on the next page. Page parameters are thus essential for maintaining a seamless user experience, enabling the new page to function as intended based on the specific needs and inputs from a previous interaction. ## Allowed Data Types[​](/concepts/navigation/passing-data.md#allowed-data-types "Direct link to Allowed Data Types") You can pass any supported data from one page to another via *page parameter(s)*. You can think of a *page parameter* as a variable that holds the value being passed from one page to another. info If you are using Firestore Database, most of the time, you would pass the *Document* (an actual record inside the Firestore collection) and *Document Reference (points to actual document)* between the pages. *** ## Video guide[​](/concepts/navigation/passing-data.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: --- # Share \[Action] The **Share Action** enables users to send text or URLs from your app using the native sharing capabilities of their device. This functionality allows users to share information through various applications installed on their devices, such as email, messaging apps, or social media platforms. warning It's important to note that the Share Action is designed for mobile platforms and is not supported in FlutterFlow's Run Mode or Preview Mode. To test this functionality, you need to [**run your app on an iOS or Android device or emulator**](/testing/local-run.md). ![share-action](/assets/images/share-action-b45517e6222a39ef2d068a4d3c2744cc.avif) --- # Overview FlutterFlow provides special navigation widgets like Tab Bar, NavBar, and PageView for advanced navigation scenarios: * **Tab Bar**: Used for navigating between different sections of your app with tabs, ideal for organizing content into categories. Learn more [here](/concepts/navigation/tabbar.md). * **NavBar**: A bottom navigation bar that helps users switch between major sections of your app seamlessly. Learn more [here](/resources/ui/pages/scaffold.md#nav-bar). * **PageView**: Allows for swipeable pages, perfect for creating onboarding screens or multi-step forms. Learn more [here](/concepts/navigation/pageview.md). --- # TabBar The TabBar widget displays a horizontal row of tabs, allowing users to switch between different content views by tapping on the tabs. Each tab typically represents a different section or category of content. It can be used in various types of apps, such as news apps with different categories, e-commerce apps with product categories, or social media apps with different sections like feeds, notifications, and messages. ![TabBarDemo.avif](/assets/images/TabBarDemo-d5bf1d3b69572bef578ea1da432a07b1.avif) ## Adding TabBar widget[​](/concepts/navigation/tabbar.md#adding-tabbar-widget "Direct link to Adding TabBar widget") To add the TabBar widget to your app: 1. Add the **TabBar** widget from the **Layout Elements** tab. 2. By default, it adds three tabs to the page and shows the first one in the canvas. In the widget tree, it is represented as **Tab** and **TabBar Page**. To see another tab in the canvas, select the **TabBar** widget, move to the **Properties Panel,** and \*\*\*\*set the **Active Tab** to the one you want to see. 3. To customize the Tab: 1. Select the **Tab >** Move to **Properties Panel**. 2. Use the **Text** property to change the label of the Tab. 3. You can also [add Icon](/resources/ui/widgets/icons.md), align it horizontally, and set its margin. **Tip**: To only display Icon, remove the Text value. 4. Inside the **TabBar Page**, you can replace the existing **Text** widget with any widget of your choice. 5. To add a new tab, move to the **Properties Panel > Active Page >** click **+ Add Page**. tip * If you want to adjust the height of a TabBar Page, wrap a TabBar widget inside a container and then set the container’s height. * You can find the currently selected tab index from *set from variable menu > widget state > TabBar Current Index*. ## Change tab in response to widget action[​](/concepts/navigation/tabbar.md#change-tab-in-response-to-widget-action "Direct link to Change tab in response to widget action") If you want to change the tab selection in response to a widget action, such as a button click, you can do so by adding the [Control Tab Bar](/concepts/navigation/tabbar.md#control-tab-bar-action) action. ## Customizing[​](/concepts/navigation/tabbar.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Customizing label[​](/concepts/navigation/tabbar.md#customizing-label "Direct link to Customizing label") To customize the tab label: 1. Select the **TabBar** widget > move to the **Properties Panel > Label Properties**. 2. To set different colors when the tab is selected and unselected, use the **Selected Color** and **Unselected Color** properties. 3. To add some space around the label, use the **Label Padding** property. 4. Use the **Label Style** property to change its [styling](/resources/ui/widgets/text.md#common-text-styling-properties). You can also set the label styling for the unselected tab text by enabling the **Custom Unselected Label Style**. ### Customizing tab[​](/concepts/navigation/tabbar.md#customizing-tab "Direct link to Customizing tab") By default, the tab in the TabBar widget is displayed with an indicator style, which includes a line under the tab to indicate the currently viewed page. However, you have the flexibility to change the tab's styling to achieve different visual effects. Instead of the indicator style, you can customize the tab to appear as a row of buttons or a toggle button, depending on your design requirements and preferences. To change the tab styling: 1. Select the **TabBar** widget > move to the **Properties Panel > Tab Properties**. 2. Choose the **Tab Bar Style** from **Indicator**, **Button** and **Toggle Button**. 3. When the style is set to **Indicator**, you can set the indicator **Color** and **Weight** (thickness). ![customizing-indicator-style.png](/assets/images/customizing-indicator-style-b7a47fa5c321f45886a3d56f30de3d4b.png) 4. When the style is set to **Button**, you have the following options to customize: 1. To set the tab background color for selected and unselected states, use the **Fill Color** and **Idle** **Fill Color,** respectively. 2. To set the border color for selected and unselected states, use the **Border Color** and **Idle Border Color,** respectively. Also, make sure to set the **Border Width** to see the border. 3. To adjust the rounded corner of each tab, use the **Border Radius** property. 4. You can also set the **Elevation** and **Button Margin** properties for all tabs. ![customizing-button-TabBar-style.png](/assets/images/customizing-button-TabBar-style-f1f73cb838dc1c4108e5c896afee3728.png) 5. When the style is set to **Toggle** **Button**, you have the following options to customize: 1. To set the tab background color for selected and unselected states, use the **Fill Color** and **Idle** **Fill Color,** respectively. 2. To set a border around all tabs, use the **Border Color** and **Border Width** properties. 3. To add a divider between the tabs, use the **Div** **Border Color** and **Border Width** properties. 4. You can also set the **Elevation** and **Button Margin** properties for all tabs. ![customizing-toggle-button-TabBar-style.gif](/assets/images/customizing-toggle-button-TabBar-style-07fbd2de26b3f0c8650363011009897b.gif) ### Setting initial tab index[​](/concepts/navigation/tabbar.md#setting-initial-tab-index "Direct link to Setting initial tab index") You might want to display a specific tab as selected as soon as the TabBar is loaded. To do so, move to the **Properties Panel > General Properties >** enter the **Initial Tab Index** value. Please **note** that the tab index starts from 0. So, if you want to set tab 1, you should enter 0. If you want to set tab 2, you should enter 1, and so on. ![tab-index.webp](/assets/images/tab-index-00877bde5779f8893f72fc07f031352a.webp) ![setting-initial-tab-index .gif](/assets/images/setting-initial-tab-index-6852460a76701225d98cfde9a9aa6cc2.gif) ### Change the tab bar position[​](/concepts/navigation/tabbar.md#change-the-tab-bar-position "Direct link to Change the tab bar position") Sometimes you might want to change the default tab bar position, i.e., from top to bottom. You can do so by navigating to **Properties Panel > General Properties >** changing the **Tab Bar Position** value. ![change-the-tab-bar-position.gif](/assets/images/change-the-tab-bar-position-943b2d2684eb747fac67e63083316fcc.gif) ### Making TabBar Scrollable[​](/concepts/navigation/tabbar.md#making-tabbar-scrollable "Direct link to Making TabBar Scrollable") When you have a large number of tabs, they may not all fit on the screen. To address this, you can make the tabs scrollable, allowing the user to scroll horizontally to view all the tabs. To make a TabBar scrollable, select the TabBar widget > move to the **Properties Panel > General Properties >** enable the **Tab Bar Scrollable** option. info If there are fewer tabs, you can control the alignment using the **Tab Bar Horizontal Alignment** property. However, for fewer tabs, you may not need to make them scrollable, but the option is available if required. ### Set margin[​](/concepts/navigation/tabbar.md#set-margin "Direct link to Set margin") Margin adds a space between the TabBar and its border. To change the margin, select the **TabBar** widget, move to the **Properties Panel > General Properties >** find the **Tab Bar** **Margin** property, and change the values. ![set-margin .gif](/assets/images/set-margin-f67175f0b39524cb5cf59d3b6cf1cf48.gif) ### Disable swipe to switch tab[​](/concepts/navigation/tabbar.md#disable-swipe-to-switch-tab "Direct link to Disable swipe to switch tab") By default, you can switch to another tab by swiping and clicking on the tab. In case you want to disable the swiping behavior, you can do so by navigating to **Properties Panel > General Properties >** disabling the **Allow Swiping to Switch Tabs**. ### Keeping tab state alive[​](/concepts/navigation/tabbar.md#keeping-tab-state-alive "Direct link to Keeping tab state alive") By default, when you switch to a different tab, the state of the previous tab is lost and gets rebuilt when you switch back to it. However, in certain scenarios, you may want to maintain the state of each tab to preserve user input, scroll positions, data from an API call, or any other relevant data. This is called keeping the tab state alive. To keep the tab state alive, select the **TabBar** widget **> Properties Panel > General Properties>** enable **Keep Tab State Alive**. ## Control Tab Bar \[Action][​](/concepts/navigation/tabbar.md#control-tab-bar-action "Direct link to Control Tab Bar \[Action]") By using this action, you can gain more control over the tab-switching behavior of the TabBar widget. For instance, you can enable users to move to the next or previous tab with a single tap of a button or to quickly jump to a specific tab based on their preferences. ### Types of action[​](/concepts/navigation/tabbar.md#types-of-action "Direct link to Types of action") These are the types of actions you can add to the TabBar. * **Previous**: Switch to the previous tab in the TabBar. * **Next**: Switch to the next tab in the TabBar. * **First**: Switch to the first tab in the TabBar. * **Last**: Switch to the last tab in the TabBar. * **Jump to**: Switch to a specific tab in the TabBar. Please **note** that the tab index starts from 0. So, if you want to jump to tab 1, you should enter 0. If you want to jump to tab 2, you should enter 1, and so on. ### Adding Control Tab Bar action[​](/concepts/navigation/tabbar.md#adding-control-tab-bar-action "Direct link to Adding Control Tab Bar action") Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Control Tab Bar** (under *Widget/UI Interactions*) action. 4. Set the **Tab Bar to Control** to the **name** of the tab bar added to your page. 5. Select the [action type](/concepts/navigation/tabbar.md#types-of-action). ## Video guide[​](/concepts/navigation/tabbar.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: --- # WebView The WebView widget lets you display the website content right inside your app. It's useful in a case where you don't want your users to leave your app to view the web page. ## Adding WebView widget[​](/concepts/navigation/webview.md#adding-webview-widget "Direct link to Adding WebView widget") To add the WebView widget to your app: 1. Add the **WebView** widget from the **Base Elements** tab. 2. Head over to Properties Panel, adjust the **Width** and **Height**, and then enter the Webview URL.(e.g., ). 3. Certain web pages may have restrictions that prevent them from being viewed within the WebView, such as popular websites like [Unsplash](https://unsplash.com/) or [Facebook](https://www.facebook.com/). However, you can override these restrictions by enabling the **Bypass Domain Restrictions** option. 4. You can also **Force Allow Vertical** and **Horizontal Scrolling** if needed. ## Customizing[​](/concepts/navigation/webview.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Load content from HTML[​](/concepts/navigation/webview.md#load-content-from-html "Direct link to Load content from HTML") Sometimes, you might choose to construct your own HTML with the desired styling and structure and then load that HTML into a WebView. For example, display a privacy policy page with a slight variation using modified HTML content (which might be different than the one hosted on your site). To do so, enable the **Load content from HTML** and then enter your **Webview HTML Content**. --- # Notifications **Notifications** are alerts or messages that appear on a user's device outside the normal UI flow of an app. They can inform the user of time-sensitive or high-priority messages, events, or actions that require attention. Notifications may appear as banners, alerts, pop-ups, or lock-screen notifications, depending on user preferences and platform design guidelines. Notifications enhance your app by increasing user engagement and delivering critical information in real time. Whether it’s an urgent alert or a gentle nudge, these timely messages: * **Prompt User Action**: Remind users to perform tasks or revisit the app, ensuring higher retention and conversion. * **Foster Engagement**: Encourage ongoing interaction through updates, promotions, or new content notifications. * **Deliver Value**: Provide relevant insights—such as location-specific alerts or personalized reminders—at the right moment. ## Types of Notifications[​](/concepts/notifications.md#types-of-notifications "Direct link to Types of Notifications") Generally, notifications can be divided into two main categories: **Local Notifications** and **Push (remote) Notifications**. **Local Notifications** are scheduled directly on the device and do not require a server component. They are commonly used for time-based reminders or location-based triggers, such as a daily workout reminder at 7:00 AM. To implement local notifications in FlutterFlow, you can integrate the [flutter\_local\_notifications](https://pub.dev/packages/flutter_local_notifications) package using [custom actions](/concepts/custom-code/custom-actions.md). **[Push Notifications](/concepts/notifications/push-notifications.md)**, on the other hand, are delivered from a remote server through a platform-specific push notification service. They are primarily used for real-time updates, such as chat messages, social media alerts, or news updates. In FlutterFlow, [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) (FCM) is used to handle push notifications, enabling seamless communication between your app and users. --- # OneSignal Integrating OneSignal lets you send emails and SMS (text messages) to your users. This can help you get more engagement, make more sales, and keep users coming back. After you set up OneSignal, you'll be able to easily add users to or remove them from OneSignal's subscription list. ![img.png](/assets/images/os-img-f9a915fcc8fd97f76cf0e8b93194261b.png) Prerequisites * Before you begin, make sure the project is on **Blaze plan** on Firebase. * [**Create an Account**](https://dashboard.onesignal.com/signup) on OneSignal ## Initial Setup[​](/concepts/notifications/one-signal.md#initial-setup "Direct link to Initial Setup") Here's a detailed, step-by-step guide to help you integrate OneSignal: ### Setup in OneSignal[​](/concepts/notifications/one-signal.md#setup-in-onesignal "Direct link to Setup in OneSignal") 1. To get started, you need an app created on OneSignal. You can create one from the [dashboard](https://dashboard.onesignal.com/apps). ![img\_1.png](/assets/images/os-img_1-7252718c2ea36679e6d21ba6376567fc.png) 1. After creating your app, activate the services you need, like SMS and Email. Go to your app settings by clicking **App > Settings > Platforms** and then select **Activate** for the services you want to use. * If you're planning to use SMS, you'll need a [Twilio](https://twilio.com/) account and then follow the steps from the official [SMS Quickstart documentation](https://documentation.onesignal.com/docs/twilio-setup#step-2-twilio-account-setup). ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/activate-SMS-service.gif?alt=media\&token=b655cf4b-0c4c-4e0a-99bb-be8cebc85997) SMS Configuration * For sending emails, configure your settings as per the guidelines provided in the OneSignal [documentation](https://documentation.onesignal.com/docs/email-quickstart). ### Setup in FlutterFlow[​](/concepts/notifications/one-signal.md#setup-in-flutterflow "Direct link to Setup in FlutterFlow") To enable OneSignal in FlutterFlow: 1. Navigate to **Settings and Integrations** > **Integrations** > **OneSignal**. 2. Switch on the **Enable OneSignal** toggle. 3. Gather your credentials: * **App ID**: Find this in your OneSignal dashboard under **Settings > Keys & IDs > OneSignal App ID**. * **API Key**: Located in the same section as the App ID, under **Rest API Key**. * **User Key**: Go to your user profile icon, then **Account & API Keys > User Auth Key**. * Click **Deploy**. 4) Now, at appropriate event in your app, you can [add an action](/concepts/notifications/one-signal.md#adding-onesignal-action) that adds the user to the OneSignal's subscription. 5) To test SMS functionality, follow the continuation of the instructions in the [SMS documentation](https://documentation.onesignal.com/docs/sending-sms-messages#sending-sms-notifications-from-dashboard). 6) To try out sending Emails, continue with instructions from [here](https://documentation.onesignal.com/docs/sending-email#sending-email-notifications-from-dashboard). ## Types of OneSignal action[​](/concepts/notifications/one-signal.md#types-of-onesignal-action "Direct link to Types of OneSignal action") There are two main actions you can utilize in OneSignal: * **Add**: This lets you add users with their details like Email Address, Phone Number, and Tags. * **Dismiss**: Use this to remove a user from the subscription list. ### Adding OneSignal action[​](/concepts/notifications/one-signal.md#adding-onesignal-action "Direct link to Adding OneSignal action") To add a OneSignal action, such as adding a user, follow these steps: 1. Select the **Widget** (e.g., Button, etc.) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu). 3. Search and select the **OneSignal** (under Integration) action. 4. Select the [Type](/concepts/notifications/one-signal.md#types-of-onesignal-action) of the action. 5. To add a user, enable the subscription options you want. You can set the value directly or use a variable. Remember, phone numbers should be in the [E.164 format](https://documentation.onesignal.com/docs/sms-faq#what-is-the-e164-format). 6. Optionally, add Tags for more personalized messaging. For example, you could tag users based on their spending amount to target them with specific emails or SMS messages about their purchases. You can find out if the user was successfuly added to the subscription by navigating to **OneSignal dashboard > App > Audience > Subscriptions**. ![img\_2.png](/assets/images/os-img_2-38c9b00550e4ec0a49a984f30c841cca.png) OneSignal for Supabase Users Currently, our OneSignal integration supports only Firebase authentication. If you want to use [**Supabase authentication**](/integrations/authentication/supabase/initial-setup.md), you may need to use [**custom code**](/concepts/custom-code.md) to notify your users. --- # Push Notifications **Push Notifications** let you deliver time-sensitive, real-time messages to users even when the app isn’t active. These notifications rely on [**Firebase Cloud Messaging (FCM)**](https://firebase.google.com/docs/cloud-messaging) behind the scenes, which routes messages to both Android and iOS devices. When integrated correctly, you can use push notifications to: * Send alerts for new content (e.g., chat messages, and updates). * Re-engage users with timely reminders or offers. * Provide relevant information (e.g., order status, location-based alerts). Push notifications involve several key components working together to deliver messages to users' devices. In FlutterFlow, you can construct and send notification payloads—such as title, message body, and additional data like image—to a push service, Firebase Cloud Messaging (FCM). FCM receives notifications and routes them to the appropriate devices. Each device is identified by a unique **Device Token/Registration Token** generated by the FCM to target specific devices. The user's device receives these notifications and handles the payload by displaying messages or navigating the user to specific screens. ## Push Notifications Setup[​](/concepts/notifications/push-notifications.md#push-notifications-setup "Direct link to Push Notifications Setup") You can add and send push notifications manually or trigger them based on user actions within the app. Here are the steps in detail: General Prerequisites Before you begin, ensure that you: * Complete all the steps in [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). Note that, while setting up, make sure to follow step number 5 and 8 carefully from [**Allow FlutterFlow to Access Your Project**](/integrations/firebase/connect-to-firebase.md#allow-flutterflow-to-access-your-project) section to properly add the **Cloud Functions Admin** role to **** user. * Upgrade your Firebase project to the [**Blaze plan**](https://firebase.google.com/pricing) to enable [**Cloud Functions**](https://firebase.google.com/docs/functions), which are required specifically for FlutterFlow’s push notification setup, such as retrieving the FCM token and sending notifications trigger from FlutterFlow. iOS Prerequisites To send push notifications to iOS devices, you must: * Have an active [**Apple ID**](https://appleid.apple.com/account?appId=632\&returnUrl=https%3A//developer.apple.com/account/). * Enroll in the [**Apple Developer Program**](https://developer.apple.com/programs/enroll/) (a paid membership is required). For more details, visit the [**Apple Developer Program**](https://developer.apple.com/programs/). ### Enabling Push Notification[​](/concepts/notifications/push-notifications.md#enabling-push-notification "Direct link to Enabling Push Notification") warning **Please note, push notifications will not work in these scenarios:** * Push notifications will not work on an iOS simulator. To test you will need to use a real device. * Push notifications will not be delivered to users who are logged out of your app. To send push notifications to users who are not logged in, consider implementing [**Anonymous Firebase Login**](/integrations/authentication/firebase/anonymous-login.md) within your app * Push notifications will not work if you have the app open on your device. To enable push notifications: 1. Navigate to the **Settings and Integrations > Push Notifications** and **Enable Push Notifications**. 2. Now, click on the **Deploy** button. This will create and deploy the *Cloud Functions* in your Firebase project that are necessary for push notifications to work. 3. Optionally, you can enable **Allow Scheduling** to send push notifications at a later time. Once enabled, you can select **Scheduler Granularity**, which determines how precisely the notifications will be sent. You can choose the granularity based on how time-sensitive your notifications are; For example: * If you need the notification to be sent at an **exact time** (e.g., 11:37 AM), choose **"1 minute"**. * If a slight delay is acceptable, you can select **"15 minutes"** or **"1 hour"**, meaning the notification will be sent within that timeframe. * **Higher precision (e.g., 1-minute intervals) requires more computing resources**, which may **slightly increase costs** (up to $0.50 per month). * **Lower precision (e.g., 1-hour intervals) is more cost-effective**, as it reduces the frequency of function execution (around $0.05 per month). Upgrading to Blaze Plan If you encounter deployment errors instructing you to contact support, it could be because you recently upgraded your Firebase project to the **Blaze plan**. After upgrading, Firebase may take approximately **10-15 minutes** to propagate the changes. If you receive this error, wait **10-15 minutes** and then try deploying again. ![img.png](/assets/images/enable-push-notification-75d9cd132af0f04b1b31f832849cb38a.avif) info By default, the **Automatically Prompt Users for Permission** option is enabled, meaning your app will automatically prompt users requesting for permission to receive push notifications when the app is started. However, this may be disruptive to your user sign-in flow. If you disable it, you can control when the permission is requested. To do so, you will need to manually [**Request Permission**](/resources/projects/settings/project-setup.md#request-permission-action) at the appropriate point in your app. **It is recommended to keep this option always enabled**. ### Configuring iOS App[​](/concepts/notifications/push-notifications.md#configuring-ios-app "Direct link to Configuring iOS App") To receive the push notifications in an iOS app, you need to perform the following additional steps. #### Step 1: Creating a Key[​](/concepts/notifications/push-notifications.md#step-1-creating-a-key "Direct link to Step 1: Creating a Key") Apple requires developers to create a key for the push notifications inside the *Apple Developer Console* to verify the push notification's sender. To create an APNs key in your Apple Developer account, go to the [**Keys**](https://developer.apple.com/account/resources/authkeys/list) section and click the **(+)** button. Enter a **Key Name**, select **Apple Push Notifications service (APNs)**, and click **Configure**. Choose the appropriate **Environment** (Sandbox, Production, or both) and set any [**Key Restriction**](https://developer.apple.com/documentation/usernotifications/establishing-a-token-based-connection-to-apns#Team-scoped-keys) as needed. Once configured, click **Save**, then **Continue** and **Register**. Finally, download and securely store the key file, as it will be required for integration with your Firebase project. tip After testing push notifications in the development environment, it's advisable to create a new key specifically for production use and upload it to your Firebase project. #### Step 2: Add APNs Key to Firebase Project[​](/concepts/notifications/push-notifications.md#step-2-add-apns-key-to-firebase-project "Direct link to Step 2: Add APNs Key to Firebase Project") To add the **APNs** key to your Firebase project, navigate to your **Firebase Project Dashboard > Project Settings** and select the **Cloud Messaging** tab. Scroll down to the **Apple app configuration** section and locate the **APNs Authentication Key**. Click **Upload** and select your APNs auth key file (that you downloaded in the [previous step](/concepts/notifications/push-notifications.md#step-1-creating-a-key)). Enter the **Key ID**, which can be found inside the key entry in [Keys](https://developer.apple.com/account/resources/authkeys/list). Finally, enter the **Team ID**, available in the [**Apple Developer Account**](https://developer.apple.com/account) inside the **Membership details** section. ## Send Push Notifications[​](/concepts/notifications/push-notifications.md#send-push-notifications "Direct link to Send Push Notifications") To send push notifications, go to **FlutterFlow** > **Settings and Integrations** > **Push Notifications**, then open the **Manually Trigger Notifications** section. Enter the notification details and click **Send Notification**. A confirmation popup will appear—type **"Send Notification"** and click **Send Notification** again to deliver your message. To send push notifications, you need to provide the following details: * **Notification Title:** Enter the title of the notification. * **Notification Text:** Provide the message content for the notification. * **Notification Image (Optional):** Upload an image to be displayed with the notification. * **Target Audience** **(Optional):** Choose whether to send notifications to **iOS**, **Android** users, or **All** users regardless of their device type. * **Deliver With Sound** **(Optional):** Enable this option if you want the notification to play a sound. * **Batch Notifications** **(Optional):** Toggle this setting if you want to send the notification in batches. Enable this only when you have over 10K users. * **Scheduled Time (Optional):** Choose the specific date and time for the notification to be sent. This option is available only when the **Allow Scheduling** option is enabled, and the selected date and time follow your timezone. * **User References (Optional):** Send push notifications to a specific user or a few users. Enter the user document reference (from the 'users' collection in Firestore) into the *User References* in this format: `/users/user_id`. tip You can easily copy and paste the document reference directly from the [**Firestore Data Manager**](/integrations/database/cloud-firestore/firestore-content-manager.md) in FlutterFlow. ![pn-with-data-2](/assets/images/pn-with-data-2-38ba3bb21ddf877d8c9bbf8ae2547113.avif) * **Initial Page (Optional):** Choose the page the app should open when the user taps the notification. ## Push Notifications with Data[​](/concepts/notifications/push-notifications.md#push-notifications-with-data "Direct link to Push Notifications with Data") Sometimes, you might want to include additional data with your push notifications, which can then be used to display more detailed information on the page when it is opened through a push notification. For instance, consider a news app that sends push notifications for breaking news. When the user taps the notification, the additional data like the article’s title, summary, and image can be displayed on the news page. warning Currently, we only support sending *Firestore DocumentReferences* as data. To send a push notification with data, you need a page that accepts a parameter of type **DocumentReference**. Start by building the notification, and set the **Initial Page** to the one that accepts the parameter. In the **Parameter Data** section, copy-paste the document reference from Firestore. Finally, click **Send Notification** to deliver the push notification with the specified data. tip On the page that receives the DocumentReference, you can fetch additional details of the item using the [**Backend Query**](/resources/backend-query/document-from-reference.md). ![pn-with-data.avif](/assets/images/pn-with-data-78bba6fa30b96c152057792dfbbec77d.avif) ## Trigger Push Notification \[Action][​](/concepts/notifications/push-notifications.md#trigger-push-notification-action "Direct link to Trigger Push Notification \[Action]") You may want to send a push notification when a specific event occurs in your app. For example, notifying a user when they receive a new message, when an appointment is booked, or when there is a price change. You can send the push notification when such an event occurs by adding the **Trigger Push Notification** action. In this action, you can decide who should receive the push notification by setting the **Audience** to either **Single Recipient** or **Multiple Recipients**. * **Single Recipient:** Sends a notification to one specific user. For example, notifying the **group creator** when a new member joins. * **Multiple Recipients:** Sends a notification to multiple users. For example, notifying **all group members** when someone joins the group. tip * You must provide the document reference of the user who should receive the notification. * You can set other notification details as per your requirements. ![trigger push notifications](/assets/images/trigger-pn-38ef8379e58d26e2b910534153ff7b10.avif) ## Testing Push Notifications Cloud Function[​](/concepts/notifications/push-notifications.md#testing-push-notifications-cloud-function "Direct link to Testing Push Notifications Cloud Function") You can also test the Push Notifications Cloud Function directly from the Google Cloud console, without needing to trigger from FlutterFlow. This is especially useful for debugging purposes. For step-by-step instructions, including an example and how to structure the request, refer to the [Testing Cloud Functions in Google Cloud Console](/concepts/custom-code/cloud-functions.md#testing-cloud-functions) section. ## Update App Badge Count (iOS only) \[Action][​](/concepts/notifications/push-notifications.md#update-app-badge-count-ios-only-action "Direct link to Update App Badge Count (iOS only) \[Action]") The **Update App Badge Count** action lets you manually display a numeric badge on your **iOS app icon**. This badge typically indicates pending tasks or updates, such as unread messages, notifications, or reminders. Platform Support In Android, badges automatically appear on app icons with push notifications. We would like to add this functionality for iOS. However, we are blocked by [**this**](https://github.com/firebase/flutterfire/issues/9563) issue. Therefore, it is important to note that this action **does not automatically set the badge count** when receiving a push notification in iOS—rather, it must be triggered manually while your app is running. ![badge-count](/assets/images/badge-count-950c23375ca20d6239ec2246c78fb86e.avif) possible use cases * In a **messaging app**, you might manually increment the badge count each time a new chat message arrives while the user has the app open or decrease it as they read the messages. * In an **email app**, you could manually update the badge count each time a new email arrives while the user is actively using the app and decrease it as emails are opened or marked as read. * In a **calendar app**, you might set the badge count to reflect the number of upcoming events for the day, incrementing or decrementing it based on the user's interactions or changes in their schedule. To implement, simply enter the number of **Badge Count** the app should display on the home screen icon. ![set-app-badge-count-ios](/assets/images/set-app-badge-count-ios-41c8bb9a7140b280051345356d6cdfbf.avif) ## FAQs[​](/concepts/notifications/push-notifications.md#faqs "Direct link to FAQs") Push notifications not working; Getting cloud function error: PERMISSION\_DENIED: Missing or insufficient permissions If you encounter an error with push notifications, specifically a cloud function failure due to permission issues, it might be related to your Google Cloud organization's settings. Organizations can disable automatic IAM grants for default service accounts, leading to this error. To fix this issue, manually grant the Editor role to the default service account used by your project. You can do this by visiting the GCP IAM page and assigning the Editor role to the following service account: * For App Engine (Gen 1): `{firebase-project-id}@appspot.gserviceaccount.com` * For Compute Engine (Gen 2): `{project-number}-compute@developer.gserviceaccount.com` ![pn-faq-img-1](/assets/images/pn-faq-img-1-c3cee31c4aaf730b44f2ec4c635c6682.png) Also, ensure that these principals (emails) and their roles are present in the permissions tabs in *App Engine Default service account*, *Default compute service account*, and *firebase-adminsdk*. You can do this by visiting the GCP Service Accounts page, clicking on each service account email, and granting access to these principals in the permissions tab. Below is a sample image for App Engine Default service account. ![pn-faq-img-2](/assets/images/pn-faq-img-2-6c76aedc7a87b21690b34f727945ed18.png) --- # State Management State management is a crucial concept focused on maintaining and controlling the **state** of an application. Simply put, it involves monitoring the changes within your app and updating the user interface to reflect these changes. The UI (user interface) displays information based on state variables. When these state variables change, the UI updates to reflect the changes. ## State Variables[​](/concepts/state-management.md#state-variables "Direct link to State Variables") In FlutterFlow, there are a few types of state variables that you can create: ![app stage overview](/assets/images/state_management_overview-fcd8004bf3a66a6cdbc87536a335b637.png) App State is shared across multiple pages in the application. Component State is specific to a component. Page State is shared across widgets on the page. * State variables are themselves [**variables**](/resources/data-representation.md#variable) - meaning they have a *name* and a *data type*. * They also have an initial value that is set when you create the variable. * Once you create a state variable, it's value can be used to change the configuration of widget properties - like any other variable. * You can update the value of state variables using the **[Update State Variable](/concepts/state-management.md#updating-state-variables)** action. ### Creating State Variables[​](/concepts/state-management.md#creating-state-variables "Direct link to Creating State Variables") * To create an **App State variable**, refer to this **[guide](/resources/data-representation/app-state.md#create-app-state-variable)**. * To create a **Page State variable**, refer to this [**guide**](/resources/ui/pages/page-lifecycle.md#creating-a-page-state). * To create a **Component State variable**, refer to this [**guide**](/resources/ui/components/component-lifecycle.md#creating-a-component-state). Note: Users cannot create **widget state variables**. These are automatically exposed by FlutterFlow when a Form widget is used. ### Updating State Variables[​](/concepts/state-management.md#updating-state-variables "Direct link to Updating State Variables") * To update an **App State variable**, refer to this **[guide](/resources/data-representation/app-state.md#update-app-state-action)**. * Refer to the [**Page Lifecycle**](/resources/ui/pages/page-lifecycle.md) guide to learn about updating **[Page State variables](/resources/ui/pages/page-lifecycle.md#update-page-state-action)**. * Refer to the [**Component Lifecycle**](/resources/ui/components/component-lifecycle.md) guide to learn about updating **[Component State variables](/resources/ui/components/component-lifecycle.md#update-component-state-action)**. Learn from video You can learn more about state management from this video: [YouTube video player](https://www.youtube.com/embed/jD6L4xjYjJA?si=-RjniUB-K0ZsMoB1) ## Rebuild \[Action][​](/concepts/state-management.md#rebuild-action "Direct link to Rebuild \[Action]") The **Rebuild** action allows you to refresh a page or a component’s UI. This is especially useful when data changes dynamically; for example, after an API call, a database update, a custom action, or a class method modifies the internal state, and you want the latest data or UI state to be reflected instantly. The Rebuild action provides different update types depending on where it is used: * **Rebuild Page:** When on a page, you will see the **Rebuild Current Page** option, which refreshes the entire page’s UI. * **Rebuild Component:** When on a component, you will see the **Rebuild Current Component** option, which refreshes only that specific component. * **Rebuild Containing Page:** When on a component, you will see this option as well, which refreshes the entire page that contains the component. For example, if you have a **"Confirm"** button inside a dialog component that updates an order’s status, selecting this action will refresh the parent page to instantly show the updated order list. ![rebuild](/assets/images/rebuild-51b6826a3452f3174ff2b8e615f78627.avif) --- # Widget State **Widget state** refers to the data or information that a widget holds, which can change over time and affect the widget's appearance or behavior. In FlutterFlow, the state is particularly important for form widgets, such as text fields, checkboxes, and radio buttons, as it allows these widgets to respond to user interactions. Additionally, **Widget Focus State** refers to the state that indicates whether a widget, such as a text field, currently has focus or not. When a widget has focus, it is ready to receive user input, and its appearance typically changes to indicate this (e.g., a text field with a blinking cursor). **Key Points:** * **Dynamic Data:** Represents values that change over time (e.g., user input in a text field). * **Automatic Management:** FlutterFlow handles the state, so developers do not need to write explicit state management code. * **Reactive Updates:** Changes in the state automatically update the widget's display. ![widget-state.png](/assets/images/widget-state-39a918ddf281ee26e78cda1368918400.png) ## Managing Widget States[​](/concepts/state-management/widget-state.md#managing-widget-states "Direct link to Managing Widget States") FlutterFlow simplifies state management by providing built-in support for handling widget states. This means developers do not need to manually create or manage the state of form widgets. Instead, FlutterFlow automatically manages the state for these widgets, ensuring a seamless and intuitive experience. Some examples of widget states exposed by FlutterFlow: * **Text Fields:** The state of text fields is automatically managed, including the input text and validation states. * **Checkboxes:** The state of checkboxes is managed, indicating whether they are checked or unchecked. * **Radio Buttons:** The state of radio buttons is managed to reflect the selected option. In the following example, we find widget state and widget focus state of a TextField being exposed by FlutterFlow on the page it was created and available as an option in the variable menu. ![using-widget-state.png](/assets/images/using-widget-state-12d396c12118d4cfb1607a772829fef1.png) Scope **Widget states** are mostly available for access on the page or component where they were created. However, when you add a component to a page, the widget states exposed in the component will also be available in its parent page. For instance, consider a component with two `TextFields` – one for the username and another for the password. This component could be utilized in both sign-in and sign-up pages. In such cases, you need to be able to retrieve the values from each TextField as if they were added directly to the page. You can access the widget state of a component's widgets on your page, just as you would for other widgets. Simply navigate to the **Set Variable menu > Widget State > \[component\_name] > \[your\_widget]**. FlutterFlow allows you to update the state of these widgets through actions exposed by the platform. For example, if you want to clear a TextField when the Send button is clicked on a form-like page, then in the Actions Flow, you can find relevant actions such as **Clear TextField**. This enables dynamic interaction and state management directly within the visual development environment. ![managing-widget-state.png](/assets/images/managing-widget-state-4c54f8309e04934c13235f8d65a5117c.png) ## Action Triggers for Form Widgets[​](/concepts/state-management/widget-state.md#action-triggers-for-form-widgets "Direct link to Action Triggers for Form Widgets") FlutterFlow allows you to bind action triggers to widget states, such as calling an API on focus change of a textfield or changing the appearance of a button when a checkbox is checked. **Most common Action Triggers exposed by form widgets:** * **On Focus Change:** Triggered when a widget, such as a text field, gains or loses focus. For example, showing additional tips or validation messages when the user starts typing in a text field. * **On Submit:** Triggered when a form or text field is submitted. For example, validating input and submitting data when the user presses the enter key or clicks a submit button. * **On Change:** Triggered when the value of a widget changes. For example, real-time validation or updating state as the user types in a text field or changes a selection in a dropdown. * **On Completed:** Triggered when a specific input is completed, such as entering a pincode. For example, automatically moving to the next step in a process after a complete and valid pincode is entered. * **On Selected:** Triggered when an option is selected in widgets like choice chips, checkboxes, radio buttons, or sliders. For example, updating the UI or performing actions based on the selected option. These triggers allow developers to create interactive and responsive applications by defining specific actions that occur in response to user interactions with form widgets. ![action-triggers-widget-state.png](/assets/images/action-triggers-widget-state-06bf4658efc13383380f0a863b4d8b31.png) --- # Tools Configuration In GenUI, **Tools** are Action Blocks that the model can call during a conversation. A tool is appropriate when the model needs fresh data or needs to perform work before it can answer. Common uses: * Query APIs or databases * Run calculations * Fetch records by ID * Transform structured data * Trigger a workflow that still returns a useful result warning If the Action Block does not return anything, it cannot be used as a GenUI tool. For each tool, GenUI includes: * Function name * Description * Parameters * Required or optional status * Parameter descriptions * Return type * Return description That means the Action Block name and description matter. They are part of the tool-selection signal the model sees. note If a tool throws an exception, the error is caught and sent back to the model as a structured error payload. The UI remains stable and the model can explain the failure or suggest alternatives. ## Tool Requirements[​](/concepts/tools.md#tool-requirements "Direct link to Tool Requirements") #### The Action Block must return a value[​](/concepts/tools.md#the-action-block-must-return-a-value "Direct link to The Action Block must return a value") Tools are designed around request/response semantics. No return value means nothing meaningful can be sent back to the model. #### Parameter and return types must be supported[​](/concepts/tools.md#parameter-and-return-types-must-be-supported "Direct link to Parameter and return types must be supported") Supported tool types include: * `String` * `int` * `double` * `bool` * `Color` * `DateTime` * `TimestampRange` * `LatLng` * `GooglePlace` * `JSON` * `DataStruct` * `Enum` * media-path string types such as `ImagePath`, `VideoPath`, `AudioPath`, and `MediaPath` * list forms of the same supported types Unsupported types are rejected during validation. #### Duplicate tools are not allowed on the same widget[​](/concepts/tools.md#duplicate-tools-are-not-allowed-on-the-same-widget "Direct link to Duplicate tools are not allowed on the same widget") Configuring the same Action Block twice on one GenUI widget is treated as an error. ## Loading Messages[​](/concepts/tools.md#loading-messages "Direct link to Loading Messages") Each tool can define its own loading message in the widget configuration. * If set, that message is shown while the tool runs. * If omitted, the generated tool uses `Processing...`. This is separate from the widget-level thinking message, which defaults to `Thinking...` and is shown before the tool call starts. ## Serialization Rules[​](/concepts/tools.md#serialization-rules "Direct link to Serialization Rules") The generated code serializes common FlutterFlow data types into model-friendly JSON: * **Color**: CSS color string. e.g., `Color(0xFF4CAF50)` → `"#4CAF50"` * **DateTime**: ISO 8601 string. e.g., `DateTime(2024, 3, 15)` → `"2024-03-15T00:00:00.000"` * **TimestampRange**: start|end milliseconds string. e.g., `TimestampRange(1700000000000, 1700086400000)` → `"1700000000000|1700086400000"` * **LatLng**: serialized string form. e.g., `LatLng(37.7749, -122.4194)` → `"37.7749,-122.4194"` * **GooglePlace**: serialized place payload (JSON object with place details) * **DataStruct**: converted using `toMap()`. e.g., `Product(name: "Shoes", price: 99)` → `{ "name": "Shoes", "price": 99 }` * **Enum**: serialized enum string. e.g., `OrderStatus.delivered` → `"delivered"` ## Best Practices[​](/concepts/tools.md#best-practices "Direct link to Best Practices") #### Keep tools focused[​](/concepts/tools.md#keep-tools-focused "Direct link to Keep tools focused") Prefer small, specific tools: * `getOrderDetails` * `searchProducts` * `getWeatherForLocation` * `calculateQuote` over broad tools like: * `handleRequest` * `fetchData` * `processWorkflow` #### Write descriptions for model behavior, not just for humans[​](/concepts/tools.md#write-descriptions-for-model-behavior-not-just-for-humans "Direct link to Write descriptions for model behavior, not just for humans") Good: `Retrieves the current order status, tracking number, and ETA for a given order ID.` Weak: `Looks up an order.` #### Return structured data when possible[​](/concepts/tools.md#return-structured-data-when-possible "Direct link to Return structured data when possible") If the output can be represented as Custom Data Type `DataStruct`, do that instead of flattening everything into strings. Structured output is easier for the model to feed into catalog components. #### Match tool output to catalog input[​](/concepts/tools.md#match-tool-output-to-catalog-input "Direct link to Match tool output to catalog input") Reliable GenUI setups usually follow this shape: * A tool returns `OrderStruct` * A catalog component accepts `OrderStruct` That gives the model a clean path from retrieval to rendering. ## Common Examples[​](/concepts/tools.md#common-examples "Direct link to Common Examples") #### Data lookup[​](/concepts/tools.md#data-lookup "Direct link to Data lookup") `getOrderDetails(orderId: String) -> OrderStruct` The model calls the tool, gets a structured order result, and renders an order summary component. #### Search[​](/concepts/tools.md#search "Direct link to Search") `searchProducts(query: String, maxPrice: double?) -> List` The model calls the tool and then renders a list-style catalog component using the returned products. #### Calculation[​](/concepts/tools.md#calculation "Direct link to Calculation") `calculateMonthlyPayment(amount: double, rate: double, termMonths: int) -> PaymentQuoteStruct` The model uses the result to explain the output and optionally render a quote component. --- # Apple App Store Deployment FlutterFlow allows you to deploy your apps directly to the App Store from within the platform. This guide covers all the necessary prerequisites, a step-by-step deployment process, and common troubleshooting tips. Prerequisites * Create an [**Apple account**](https://appleid.apple.com/account?appId=632\&returnUrl=https%3A//developer.apple.com/account/). * [**Purchase an Apple Developer membership**](https://developer.apple.com/programs/enroll/). Learn more about the program and enrollment process [here](https://developer.apple.com/programs/). * Set an App Launcher Icon for your app under **Settings & Integrations > General > App Assets**. **Note**: The launcher icon cannot be transparent or contain an alpha channel. * It's recommended to test your app on a real device before deployment. Follow [**these instructions**](/testing/local-run.md) to test your app locally. ## Deploy to App Store[​](/deployment/apple-app-store-deployment.md#deploy-to-app-store "Direct link to Deploy to App Store") The App Store deployment involves the following steps: ### 1. Create a Bundle Identifier[​](/deployment/apple-app-store-deployment.md#1-create-a-bundle-identifier "Direct link to 1. Create a Bundle Identifier") A **Bundle Identifier (ID)** is a **unique string** that identifies your app within the Apple ecosystem, typically formatted in reverse domain name notation like `com.example.myapp`. To create a Bundle ID, visit the [**Certificates, IDs & Profiles**](https://developer.apple.com/account/resources/identifiers/list) page, add a new **App ID**, and provide these details: 1. **Bundle ID:** Copy the **Package Name** from FlutterFlow. 2. **Description:** Add a brief description of your app. 3. **Capabilities:** Select the necessary app capabilities. Ensure you select **Push Notifications** if your app uses them, and **Sign In with Apple** if your app includes that feature. ### 2. Add New App[​](/deployment/apple-app-store-deployment.md#2-add-new-app "Direct link to 2. Add New App") [App Store Connect](https://developer.apple.com/help/app-store-connect/get-started/app-store-connect-homepage) is the platform used for submitting apps, managing app metadata, and much more. To add a new app, open the [App Store Connect](https://appstoreconnect.apple.com/) and then follow the official steps outlined [here](https://developer.apple.com/help/app-store-connect/create-an-app-record/add-a-new-app). ### 3. Add Apple App ID to FlutterFlow[​](/deployment/apple-app-store-deployment.md#3-add-apple-app-id-to-flutterflow "Direct link to 3. Add Apple App ID to FlutterFlow") An App ID is used by Apple to identify your app and associate it with your development team. To add your App ID to FlutterFlow, go to **[App Store Connect](https://appstoreconnect.apple.com/) > My Apps**, copy your **Apple ID** from **App Information**, and paste it into the **App ID** field in **FlutterFlow > Settings & Integrations > Mobile Deployment > App Store**. ### 4. Generate API key and add to FlutterFlow[​](/deployment/apple-app-store-deployment.md#4-generate-api-key-and-add-to-flutterflow "Direct link to 4. Generate API key and add to FlutterFlow") To generate your API Key, go to [**App Store Connect**](https://appstoreconnect.apple.com/) > **Users and Access** > **Integrations > [Team Keys](https://appstoreconnect.apple.com/access/integrations/api)**. If you haven't added a key before, you will see a **Request Access** button. For further details, watch a [demo](https://youtu.be/L2BpgVog4so?si=yS9r_PBeORgd6Uhp\&t=240) here. Generate a new API key by selecting **Add (+)**, entering a name, and assigning the **App Manager** role. Once the key is generated, download it and upload it to **FlutterFlow** under **Settings & Integrations > App Settings > Mobile Deployment > App Store > Private Key**. ### 5. Add issuer ID to FlutterFlow[​](/deployment/apple-app-store-deployment.md#5-add-issuer-id-to-flutterflow "Direct link to 5. Add issuer ID to FlutterFlow") Copy the **Issuer ID** from [**App Store Connect**](https://appstoreconnect.apple.com/) by navigating to **Users and Access** > **Integrations > [Team Keys](https://appstoreconnect.apple.com/access/integrations/api)**, and then paste it into the **Issuer ID** field under **App Store settings** in FlutterFlow. ### 6. Add Key ID to FlutterFlow[​](/deployment/apple-app-store-deployment.md#6-add-key-id-to-flutterflow "Direct link to 6. Add Key ID to FlutterFlow") Return to **[App Store Connect](https://appstoreconnect.apple.com/) >** **Users and Access** > **Integrations > [Team Keys](https://appstoreconnect.apple.com/access/integrations/api).** Find the row for the API Key you generated [here](/deployment/apple-app-store-deployment.md#4-generate-api-key-and-add-to-flutterflow), select **Copy Key ID,** and then paste it into the **Key ID** field under **App Store settings** in FlutterFlow. ### 7. Deploy[​](/deployment/apple-app-store-deployment.md#7-deploy "Direct link to 7. Deploy") To deploy your app from FlutterFlow, go to **Settings & Integrations > App Settings > Mobile Deployment > App Store** and click **Deploy To App Store**. Once deployed, you will receive an email from App Store Connect that a new build has been added to your app. ![deploy-to-appstore.avif](/assets/images/deploy-to-appstore-5aa199888f377af25dcdcfb05c5c4102.avif) info * Every time you deploy, we'll auto increment the **Build Number** (i.e., version code in Android) to ensure that each release is identifiable. If needed, you can update the *App Version* and *Build Number* yourself. * If another deployment is already in progress, deploying a new build will cancel the previous one. * It may take a few minutes for the request to process. Once completed, the status will be updated to **Submitted**. tip If you prefer to manage your deployment process outside of FlutterFlow, such as integrating with your own CI/CD pipeline, or if you want more control over versioning and custom code management directly on GitHub. You also have the option to [**Deploy apps from your GitHub repository**](/deployment/deploy-from-github.md). ### 8. Submit your app for App Store approval[​](/deployment/apple-app-store-deployment.md#8-submit-your-app-for-app-store-approval "Direct link to 8. Submit your app for App Store approval") From [**App Store Connect**](https://appstoreconnect.apple.com/), select **My Apps** and choose your app. Select **Prepare for Submission**, add the app assets and metadata, and then click **Add for Review**. ![add-for-review.avif](/assets/images/add-for-review-e313a116a9022d701e69edc01833f304.avif) Your app will now be reviewed by Apple. For additional information on Apple's review guidelines, please see [this link](https://developer.apple.com/app-store/review/guidelines/). *** ## Video guide[​](/deployment/apple-app-store-deployment.md#video-guide "Direct link to Video guide") Watch this video if you prefer watching a video tutorial. [Sharing a Project with a User](https://www.youtube.com/embed/4GFMsYep_S0) *** ## FAQs[​](/deployment/apple-app-store-deployment.md#faqs "Direct link to FAQs") Invalid App Store Icon. The App Store Icon in the asset catalog in 'Runner.app' can't be transparent nor contain an alpha channel. You need to update your App Launcher Icon (under Settings & Integrations --> General) with an image that isn't transparent and/or doesn't contain an alpha channel. After submitting my iOS app to the App Store, I am getting an 'ITMS-91053: Missing API declaration' issue. What should I do? Apple requires that apps using certain APIs have a Privacy Manifest file that declares the [**reason for using the API**](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api). Apple will begin requiring this file for App Store approval on May 1, 2024. Most packages that FlutterFlow uses already have a Privacy Manifest created by the package author or FlutterFlow team. However, there may be some cases where packages don't have the necessary privacy manifest needed. Similarly, if you have written custom code that calls these APIs directly or uses a package that calls the APIs, you must ensure that your app has the required manifest file. Here are the steps you can take to resolve this issue: 1. See if the custom package you use is listed [here](https://developer.apple.com/support/third-party-SDK-requirements/); ensure to use the latest version if you are using any of these. 2. If unsure which package is using protected APIs, you may be able to use a tool like [this](https://github.com/crasowas/app_store_required_privacy_manifest_analyser) to identify them. Once identified, update to the latest versions, as the package author may have addressed compliance issues. 1. To verify, look into the package's changelog or source code for a `PrivacyInfo.privacy` file, which indicates compliance (examples [here](https://github.com/fluttercommunity/plus_plugins/blob/main/packages/share_plus/share_plus/ios/PrivacyInfo.xcprivacy) and [here](https://github.com/flutter/packages/blob/main/packages/url_launcher/url_launcher_ios/ios/Resources/PrivacyInfo.xcprivacy)). 2. If the current package hasn’t resolved the issue, consider using an alternative package that complies, or contact the package's maintainer for a fix. 3. If you have written a custom iOS code that is accessing the APIs: 1. In FlutterFlow, navigate to **Settings & Integrations > App Settings > Privacy Manifest Configuration**. 2. Activate the necessary API reasons and select the appropriate reasons from the dropdown. A detailed explanation of each API reason can be found [here](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api). ![privacy-manifest-configuration](/assets/images/privacy-manifest-configuration-519c68fd856e18c19a57e7c2e76147ab.avif) --- # Deploy for Development Environments FlutterFlow provides flexibility in configuring deployment settings for different [environments](/testing/dev-environments.md), allowing you to manage your app builds for both mobile and web apps. With deployment settings tailored to each environment, you can test, isolate app functionality, and optimize for various use cases without impacting production builds. ## Mobile Deployment[​](/deployment/deploy-for-environments.md#mobile-deployment "Direct link to Mobile Deployment") You can configure and publish environment-specific builds of your app for both iOS and Android platforms, allowing each build to coexist and function independently for different environments. To set up deployment for different environments, go to **Settings & Integrations > App Settings > Mobile Deployment**, and select the desired environment from the **Current Environment** dropdown on the right side. Now, to submit an environment-specific build to the App Store and Play Store, you must have unique package names representing each environment. To set this up, go to **Settings & Integrations > General > App Details > Package Name**, select the **Current Environment** from the dropdown (on the right), and specify the package name for that environment. This ensures that when you switch environments, the package name changes and you can submit separate builds to the App Store and Play Store. For example, in an ecommerce app, you can set package names such as `io.flutterflow.ecommerceflow.dev` for the development environment and `io.flutterflow.ecommerceflow.staging` for the staging environment. Once this setup is complete, you can deploy to [App Store](/deployment/apple-app-store-deployment.md) and [Play Store](/deployment/google-playstore-deployment.md) as usual. For iOS * You can publish your apps as unlisted on the App Store to allow different builds without public exposure. * You must configure provisioning profiles, certificates, and App IDs unique to each environment to ensure secure and streamlined publishing. ## Web Deployment[​](/deployment/deploy-for-environments.md#web-deployment "Direct link to Web Deployment") Web deployment in FlutterFlow provides you with the ability to configure the entire web deployment for each environment, including custom URLs, page titles, metadata, and deployment history. To set up deployment for different environments, navigate to **Settings & Integrations > App Settings > Web Deployment**, and select the desired environment from the **Current Environment** dropdown on the right side. Then, set a new **Site URL** for the selected environment and [publish](/deployment/web-publishing.md) your app as usual. ![deploy-web-app-for-environments.avif](/assets/images/deploy-web-app-for-environments-65e46cce24b61a07d205e9e703aa5b87.avif) --- # Deploy from GitHub If your FlutterFlow project is connected to a GitHub repository, the generated code can be pushed to GitHub, giving you full control over your project’s code. Then, you can deploy your app directly from the same repository, rather than deploying through FlutterFlow. Deploying from GitHub is particularly beneficial when: * You have written custom code that cannot be managed directly in FlutterFlow, such as features that require advanced Flutter functionality. * You want to manage the source code in an external GitHub repository for better version control. * You want to automate the process of deploying your app directly from GitHub to the Play Store or App Store after modifying the code. * You want to deploy from a specific branch of your GitHub repository. ## Steps to Deploy[​](/deployment/deploy-from-github.md#steps-to-deploy "Direct link to Steps to Deploy") To deploy from a GitHub repository: 1. If you haven't already added your project to the GitHub repository, follow the instructions provided [here](/exporting/push-to-github.md#connect-a-github-repo). 2. In FlutterFlow, go to **Settings & Integrations > App Settings > Mobile Deployment.** 3. Locate the **Deployment Source** section and click the arrow icon on the right to expand it. 4. Turn on the toggle for **Use GitHub repo: \[your repo URL]**. 5. Enter the branch name of your repository that contains the code you want to deploy. Ensure the branch name is correct. 6. Click the **Deploy to App Store** or **Deploy to Play Store** button, depending on your desired platform for deployment. ![deploy-from-github](/assets/images/deploy-from-github-9e0534ff4e93223c90e2332a4c195c6f.png) important When deploying from your GitHub branch, you will need to manage the app versioning manually. This is done through the `pubspec.yaml` file. For example, to set the version to **1.1.0** and the build number to **2**, you can use the format: `version: 1.1.0+2`. ![update-version.avif](/assets/images/update-version-02789a60b90d0089cf1b990c1d858a68.avif) ## FAQs[​](/deployment/deploy-from-github.md#faqs "Direct link to FAQs") I am having an issue while Deploying from a GitHub branch. Error: *You uploaded an APK or Android App Bundle that was signed in debug mode. You need to sign your APK or Android App Bundle in release mode.* If you are experiencing problems deploying or uploading to the Google Play Store from a Github branch, check to make sure your `build.gradle` file is correct. 1. Open your `android/app/build.gradle` file. 2. Ensure your file has these lines of code: ``` def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } signingConfigs { release { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] } } ``` 3. Newer Flutterflow code will automatically have these lines added. If yours doesn't, you can push it to your `flutterflow` branch on GitHub and merge in the changes or add them like so: ![deploy-github-issue](/assets/images/deploy-github-issue-3dec70eb9ae21fab4f205ab76c8fedc1.avif) 4. Lastly, change `debug` (shown in the red box above) to `release` before deploying. --- # Google Play Store Deployment FlutterFlow allows you to seamlessly deploy your apps directly to the Google Play Store, all from within the builder. This guide provides comprehensive instructions on prerequisites, step-by-step process for deployment, advanced settings, and troubleshooting common issues. Prerequisites 1. Register for a [**Google Play Developer account**](https://play.google.com/console/u/0/signup). 2. [**Test your application**](/testing/local-run.md) on a real device. 3. Confirm the [**app details**](/resources/projects/settings/general-settings.md#app-details). Especially the package name, which can't be changed after your app is deployed. 4. Set an [**App Launcher Icon**](/resources/projects/settings/general-settings.md#launcher-icon). The App Launcher icon can't be transparent or contain an alpha channel. ## Deploy to Google Play Store[​](/deployment/google-playstore-deployment.md#deploy-to-google-play-store "Direct link to Deploy to Google Play Store") Deploying to Google Play Store comprises of the following steps: 1. [Creating an app on Google Play Store](/deployment/google-playstore-deployment.md#1-creating-an-app-on-google-play-store) 2. [Set up your app](/deployment/google-playstore-deployment.md#2-set-up-your-app) 3. [Adding service account credentials](/deployment/google-playstore-deployment.md#3-adding-service-account-credentials) 4. [Deploy to Google Play Store](/deployment/google-playstore-deployment.md#4-deploy-to-google-play-store) ### 1. Creating an app on Google Play Store[​](/deployment/google-playstore-deployment.md#1-creating-an-app-on-google-play-store "Direct link to 1. Creating an app on Google Play Store") Follow the steps below to create an app on Google Play Store: 1. Open the [Google Play Console](https://play.google.com/console). 2. Click on the **Create app** button at the top right side of your screen. 3. Enter the **App name**, select the app type, and choose whether the app is **Free** or **Paid**. 4. Accept the **Declarations**. 5. Click **Create app** at the bottom. [Sharing a Project with a User](https://www.loom.com/embed/f7060474fd3741cbbff64e885751d1ed?sid=75eb6e5e-7bcf-4ed8-9480-42bfc46ef622) ### 2. Set up your app[​](/deployment/google-playstore-deployment.md#2-set-up-your-app "Direct link to 2. Set up your app") To successfully deploy the app, you must fill in all the app details required by the Google Play Store. To proceed, navigate to the **Set up your app** section within the newly created app. Expand the **View tasks** section. Then, click on each task and fill in the necessary app information. ![setup-your-app](/assets/images/setup-your-app-5e3b2b145f130052273944cdbfc5b97d.avif) ### 3. Adding service account credentials[​](/deployment/google-playstore-deployment.md#3-adding-service-account-credentials "Direct link to 3. Adding service account credentials") Adding Service Account Credentials to FlutterFlow helps you publish your apps on Google Play. #### 3.1 Creating a Service Account[​](/deployment/google-playstore-deployment.md#31-creating-a-service-account "Direct link to 3.1 Creating a Service Account") To create the Service Account, you can follow the instructions from [here](https://developers.google.com/android-publisher/getting_started). To help you get started quickly, here are the exact steps you need to follow: 1. If you haven't set up Firebase in your app, you'll need to [create a Google Cloud Project](https://developers.google.com/android-publisher/getting_started#creating). 2. Then, head over to the [Google Play Developer API page](https://console.developers.google.com/apis/api/androidpublisher.googleapis.com/) in Google Cloud Console and click **Enable**. ![enable-play-api](/assets/images/enable-play-api-58a30239911273105a4d83c27e046b2f.avif) 3. In Google Cloud Console, go to [Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts), click + **CREATE SERVICE ACCOUNT,** and follow the steps as per in the visual below. [Sharing a Project with a User](https://www.loom.com/embed/221b44ff1f21449191ad400c368c98c1?sid=1d157ef4-8255-45f9-b313-b19c94fc4323) 4. On the right side of the newly created service account, click the action menu (three dots) icon and select **Manage keys**. Then, click **ADD Key > Create new key > select JSON > CREATE**. Keep the downloaded file at a safe place. [Sharing a Project with a User](https://www.loom.com/embed/ddacc773f607466dacda84d2bc5a65d3?sid=99860215-0e37-412d-bfb3-5f04791a7c11) 5) Now, return to the Google Play Console and follow the steps below: 1. Go to the [Users & Permissions](https://play.google.com/console/users-and-permissions) page. 2. Click **Invite new users**. 3. Put the email address for your service account in the email address field and grant the necessary rights to perform actions: * "Edit and delete draft apps" * "Release to production..." * "Release apps to testing tracks" * "Manage testing tracks and edit tester lists" 4. Click **Invite user**. [Sharing a Project with a User](https://www.loom.com/embed/c0df78e3c850419787559a399ca5eebd?sid=429452db-f87d-46af-8d80-c6d64d400dc6) #### 3.2 Uploading service account credentials to FlutterFlow[​](/deployment/google-playstore-deployment.md#32-uploading-service-account-credentials-to-flutterflow "Direct link to 3.2 Uploading service account credentials to FlutterFlow") To upload the service account credentials on FlutterFlow: 1. Return to FlutterFlow, navigate to **Settings & Integrations > App Settings >** **Mobile** **Deployment,** and scroll down to the **Google Play Store** section. 2. Under the **Service Account Credentials**, Click on **Upload Credentials** and select the downloaded credential, i.e., the `.json` file in the previous step no.4. [Sharing a Project with a User](https://www.loom.com/embed/a59cb331fc6944af97249dd6aec378bc?sid=62fd1920-4994-45e3-abba-e84f75a8f705) ### 4. Deploy to Google Play Store[​](/deployment/google-playstore-deployment.md#4-deploy-to-google-play-store "Direct link to 4. Deploy to Google Play Store") To enable FlutterFlow to deploy your app to the Google Play Store on your behalf for the first time, you have to download the [`.AAB`](https://chat.openai.com/share/6f5714c1-eb13-428b-b9ee-9772f2810284) file from FlutterFlow and upload it to the [Internal Testing](https://play.google.com/console/about/internal-testing/) Track on the Google Play Store. Once the Internal Testing track is ready (with `.AAB` file), FlutterFlow can handle the subsequent releases. #### 4.1 Getting the AAB (App Bundle) file[​](/deployment/google-playstore-deployment.md#41-getting-the-aab-app-bundle-file "Direct link to 4.1 Getting the AAB (App Bundle) file") To get the AAB file: 1. Set the **Google Play Track** to **Internal** and hit **Deloy to Play Store**. 2. Wait for a couple of minutes and then click **Check Build Status**. If you don't see the **AAB APK** options yet, wait for some time. 3. Click on the **AAB** to download the `.aab` file. info You need to perform this step only for fresh deployment (i.e., first-time setup). [Sharing a Project with a User](https://www.loom.com/embed/2c432b6bc4ba41d0bf7ec0db3912a0bd?sid=7f8df4ca-107a-4e82-aa34-06194c188ed9) #### 4.2 Creating a testing track[​](/deployment/google-playstore-deployment.md#42-creating-a-testing-track "Direct link to 4.2 Creating a testing track") info While you can certainly release your app directly to the Production Track, it's advisable to first release it within your team using the Internal Testing Track. Inside the [Google Play Console](https://play.google.com/console), create a testing track as per in the steps below: [Sharing a Project with a User](https://www.loom.com/embed/01500472234942f78af65b48d1f6eacf?sid=fb31285c-b957-4011-ad0a-8285b7c553b8) #### 4.3 Deploy[​](/deployment/google-playstore-deployment.md#43-deploy "Direct link to 4.3 Deploy") You can now deploy directly from FlutterFlow or from your GitHub repository. info * Every time you deploy, we'll auto increment the **Build Number** (i.e., version code in Android) to ensure that each release is identifiable. If needed, you can update the *App Version* and *Build Number* yourself. * We'll [**auto-generate**](https://developer.android.com/studio/publish/app-signing#generate-key) and [**sign**](https://developer.android.com/studio/publish/app-signing#sign_release) your app for the release with the Keystore (i.e., upload key). If you wish to download the keystore, click the orange key button. Ensure the **Google Play Track** is set to **Internal** and hit the **Deloy to Play Store** again. On successful deployment, you will see the status as 'finished'. ![deploy-flutterflow](/assets/images/deploy-flutterflow-cf0c6a4c693f61019a33c88d9766fd35.avif) tip If you prefer to manage your deployment process outside of FlutterFlow, such as integrating with your own CI/CD pipeline, or if you want more control over versioning and custom code management directly on GitHub. You also have the option to [**Deploy apps from your GitHub repository**](/deployment/deploy-from-github.md). #### 4.4 Verify deployment[​](/deployment/google-playstore-deployment.md#44-verify-deployment "Direct link to 4.4 Verify deployment") To verify that the app is deployed to Play Console: 1. Open the **Internal testing** in [Google Play Console](https://play.google.com/console). 2. Under the **Releases** section, find your release and click on the **Show Summary** button. 3. See the **Version Codes** number is increased. ![verify-deployment](/assets/images/verify-deployment-1fd5e667065c7ab8011980cadaec5aa2.avif) #### 4.5 Deploy to production[​](/deployment/google-playstore-deployment.md#45-deploy-to-production "Direct link to 4.5 Deploy to production") To deploy your app to production: 1. Inside the **Internal testing** in [Google Play Console](https://play.google.com/console). 2. Under the **Releases** section, find and click on the **Promote Release** dropdown. 3. Select the **Production**. This will create the Production track and you can continue to release your app from there onwards. 4. Next time onwards in FlutterFlow, you can publish directly to the Production track by setting the **Google Play Track** to **Production**. * Google Play Console: Promote to production * FlutterFlow: Set Google Play Track to Production ![play-console-deploy-prod](/assets/images/play-console-deploy-prod-3cadf567d429b6586aa9a04b5bd74208.avif) ![play-console-deploy-prod](/assets/images/ff-deploy-prod-393c02301c78bd8fcee7a5aff9fa9325.avif) *** ## Advanced Settings[​](/deployment/google-playstore-deployment.md#advanced-settings "Direct link to Advanced Settings") ### Upload Keystore[​](/deployment/google-playstore-deployment.md#upload-keystore "Direct link to Upload Keystore") If you've previously deployed an app to the Play Store using your own keystore file, you must enable this option. Once enabled, proceed to **Upload Keystore** file and provide the **Keystore Alias**. ![upload-keystore](data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAGRsAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAsYAAACpAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAGSNtZGF0EgAKChgl7FqGCBAQNCAyijJMBALX/hgNjAXteYdjAZYlLx4TKALn8A/uHJG9Btqmb46WwQVeVhTCDPjSYIPJIsFYgUkYSytFoh6xSB6XkNs/eYPiR14nGPVoKYuhUNhlOt/u7uQo8t7xoNvoM7ysEkdVwy9khZ6SDHHnQWe7ebZ+xU9O0n8xXLtiVW6Bdev2/u9TMzgwD5RuTSzhxjAKcNMdEvUP+q1OW8lZ82ASFQakPPZUBuXXLYulNvWTaZWfuairJ4H+idRt++CWwmqZeS4LNqbbhmBVMiQOBHjrbKG9+OOhV6Cultj2OcOXxoYYsXWbkqoOnXlqHLkDGwmL+eeylBVzEj/N/QionZ9yfnIgHHvp0oxIHtfbK3rScJCZyh0fElvislmAsap3pNIKYwQzOaq0FqtutdstCONBiynOkIvu5VearMrmYdCdO7TL//QkJaqel6J7ZzJHoZhdGTlzxkvrs1KdsUEQDAR+dB+2uSvQM1MeKwYyQ6fbM/ITDIEWb4eNMn5ZFp1XOFnlr0wYNAe0oy/w/aSDMWRuyFSz1pHmrB1oK32u9lKQ92j87abzWGy+dMBrew3wl//z0hN7vJFgG5CR484VzoaTxtNrkB2MrKrLOxblEydocbZg7+NbXlzjtxkzUfoEe0sjzFdA/42AKzOH/Gv4wcacG8hQT2BXDskLbzCPjpP9EK65jQnr3eIATS1EXFb1MbLhH1oz6HKTGEk4sw3UZYmZ9G9wDxFkzYKtQHu0FQpJWueaUghYqu1S0gn7J7KJgIy9JJZiWVUIRoE4HG5ALv4qT7c7TfeF6wzZqauIhjoyaSFJXMYYZPdBoGtC+zOrGhT2e6WeIHAzcFsspi+NFYowJYFfR8iwwJeI/crHS5zq2t4g82syazDsTDvvQB9Qyc0qLK8TSLG8135CtnGz1D188igQnHn0JumVUlSDSKU5SRLeQLzQRU8D9qNWhmHMUoGqmpTg4uNclPxEJsfHErOt4+nTqW/qYpixW22X/qfNqRqz4fDNumN8zewVM8RgX1QVLlAXSUSwZvTmq7xd53IRFa76Psg2eahfoBCKlr5alNXorhosIKaT7O5IcXU/mHw5t/cahW6m584Uu4X7ImwzurZPArtmKtnRKP1Hjhzv1jA0GzMoNJg8m2U/HSCIkGPXKmxLk+rQZWX89AunY9DGfuNrUbe5Lxer2WchdTvYVe/SgvA3MWYUMWHAR95oy6bKW1ce6x768xqgMq5wdlVCszEmgWsN788+z+ye00JShIyGO4yWQNTLcBx4x2ulpSlKQJBHTg8A/3hcMi+WgShzgG/JMQHk0lSy8EWOiF2PsDBB3NtqMaBTAR0S3qtFNjKwd7vxxVxY6y4y4VJbmr5O6gm6KgS1lbIREu6GhyX9/g7odCf/1BRynx/FVEmESs+tcmp87m85OI2At4Tm+qTrx1IWcWIXfrPE7fTd6PduuP19qQmOsHTjA4S6WsWgP4VVy0t1EK2pK/26aFR6DsCVTlM1KtcQ9HdbVN9lq2kJKCarfUYBDvQ+Ju2OTmEUhabgeFXPWgdKQWdLpriDX8LTkJ/dXWM08JQAW90wBZ1qpcXh37aRj4v/L5Z7JbE58u7JNB1x1LKj8if30Z1mjVT+VHsCHr0hNRxLagevQcVdXPPzJIanJwnFHKvqLaXoMAo7rdnVMVWoFl/KnFhPFCmU1fubQB7P+x+wmbvkYOIOwVNzfP1vLDOR/ocl6JQyo3zsRaeK4l90wn3ltl07Akfnd27GsbJVB7UlGzzSEPLRG8CXXFBm5QlFgNqCSX7JKB+QmsLWX41gIYKPPfSm8rO736wynYsTX3v91IECyo4QYnxaUJ/uJG3VtVFVIVL4S0LEKszAU83Wv592wF/MKGFR+34AvX2pnVNnsS/I9TYcOFCzpYI5HigcdRVe12Hp6jevu0MXWTdXSWVBFhfQzrhDzjCbSzqjX2Xb4l7dR/zlmsKk7CDGwaWUQVoYMk4htoC9QXX1UFb/QF5sHAsaWO5u3O+rriNLryHbYz6JOL/GYjqcOBEZv5POES3lAIsRwcKM7lMMXUOaOhDGXwlW7p99OY3+jGc7dI4cyJ16NK6PIZimwln8ApMy/IE8QjTTsUYVgGRoZYAsD2y/2R247N8QWui/kOr710gpR/B0l99Ee6VGl36XSc4S4HC9ZEHYN3CQeyIfuWBBv3kaot92vp3Ui5j9/GNjqzyYD5Qph3E21L//vcNHWf8y5byQj7MjG4S8sFncD9UoArloYJGL5v/LrM75l60v8RbvzLckiZFejlKgh8iIW6pkwswNpKXxhbvLssb3IL25RbvEt6mwemxpZyWCgwspJwaqdD8H/tuVqHza+wwMH7C59wz2SgbIF9oY/oVseH+zRdp2vaEpDIgB3yC8+OAszsu04RECeQqSRRKRBz6VbUNi2T1/jCLxkL5T3vhxnmwBqWAXVqkj03wjb0nMYXYgvtHhM3c0OwO1C6Do5o1DH1apcsHQkX4Akgu2a1oln3DRLjZm2o8nFX7P/D71kY3auuDj6RbZOtEPvv7lM3ZjkkgR78i4qkeqEoqBY9/b3xv7D8AaoGSTX4HV36fSPmmF9nY57mLDwV5x7HDQBgWa9yorING6vh5/nf01NwZCQBksqhFvrv2i6xu+gWjxfPpa8bC4MpX1q4Gf/16npWCunPODm5L9jfEf9f2QG+wJIqs/T5F60MLbSK4DsFrpMBOsFMYPCqudR1JzQ5BWUfFiE5bY2W5AllzqOmF/G7W44Ith8QsybGmh2CmIXjBAHEHBY7Nm/gPjA3v5I2BvXNmq8Ei0v3VUuUMDXCGio9Ydtmk8BwyPAXCA3VxjKWAjF+eCBvvTkvNHTB8D4rf/3olS1HziICfr/RrXTv4fEBTAcjjtcwDtYhcYDE80uzxUv5/d8cF7DoAEkGm9h4SoE+yMXy84SNFcuPbvyTne7FE17ITjjb4AbFcSq7aU8u3y7vLwBENGDZ5+i88vK/gc/G9vRO4N56X80dL+G9U+gGeNn0R7R6CrVjemaqgcOkMpMXpL0yparslYwx579YMBr9c2mpP6Hw+Z4ZRXEMHIJWn3hoHJdJ/kTItU9oqxWBvoIUzRr7AHjTYGGOkP0NFtHTAFuU9Zt7dumybRDdgU6axwHqkXGOSCLkyI7kXZV18WQnlS7Pv9iBFhIpjSOCtNHyVoLm3hqyOgRw7CNIIF5hboTT07Oa0UNPDTVx5ts5pE6U2QM9xezoMNEj0vnfGXKM177edMotQDGnjDTUYCAVWJhmLSGhiU8CpBENLsn09MVIscfJvYFwGl+eLmmoCHi/M71sF72mKfOo8oP/yX6aD0dVxFFzdo4zGuBQ8B6fzb1hfBtYN8+OG5heOb4GyxpuhGKTw2xaQmNYw6C4Qf2KDYgLjIV3kZeg1/VLcNHcfEK/A+aGWBhJkHMEcmRxWGF2qRMYJ/CzkzhUnMxAkrpcZZWCM6v8ty7p8Yw8/fGXRZdfxy56AFtflhdj7JhcNoQXcjvjqgsaArRE/e5DbRVbBHsqlc8H8Az/louJ+k1Rx30hbZRTeMpuxL8wf7WEotFJW2BAVpxWmHzRtE88+LwJu3eVRZi1YGaqkfCvbKdZmQGNTtDWJX3yoJutUgyBZY+EgsQie7iXakOvHAirc++A7IYjNs3TuDl+M3+nWEoqtUbRK49eC3JeM/MHX44wWrnrKwkdPQLmvbyFaAdXtsk0c0lqVkf+w3+nplfQYXBeeeKrzTrcF7ePatc1s9FPBVgJQRMUMLmfdA4IV5FcAg0ndciXOBeMGAyv77z4Kh26KBNdItBvpSmjEHix9CHoNkyxhVV782Nv5XiJy0pC2wvBeyTyGZDetBmbuz3kZDdLt3o/WfSmJvM7S+0Dm3ZI1ur+pCpvp1P/3eD7QEb1owZ1x+maTWxozJa3FEZRxA2jIW0NUAIs7DLiPcWPc84eSKB8sLh/LibRizNSjmfTGLcWpHCC6Mxtv9idRIUSYEzBRCdNg51dlX2vQDlPwoq8DSeK6hp8k2hoCv6hd2Qh4fyBvaMoiDMnATjQBha5oFfgV+ytzfu0vKlMDm7f/rK5DvfG1b8GV8crasrEPFPfcUXe/ZyMr+kLlo4DSb+mpAAAx/2Bvtx9Ysg5yDrczmbXhf5ewHbmaR25IBm6miJP0vDWClmgVLOVnojI9RiUtctTZBmqMBbqvSKg0NAnBFmmHoGbLTitaa3aP4B1im2fK19b8zYQD1/5u/hFRfbhsM2eW3nTIvNUyaamqQLqVjyW5Z+4Kf/vRhOoq6i9PmAOz/cvS48eskZ6jIGbVwe6peswhxr5veh/IdtK//slCQvGBIKc6AB5/Q9OFHi7zcuJx6IaKxq7+ytGHSvG9lcrMzZD9joxgog1P/maSdvXYMguQQu2AQva9DiQj19UKr4JGbTUcVw/TLq8E2NEYDrgquc3+OdBFGtp5a5pfHoSx/6y0m8I/KtJ8TfwMTV7lrfLlgKc0124HNcGW7r/+/F6JdoyPv/8LqRv7W6S9JeHqtl5JMt9NKNkC111cgGT5NUXPXSHAchtRg2ECIWS6aTSCkrziOJMpd4Ym1yMBGmuWF9HT1lohz4rdu5Ei7sCAkBFHe7sz5+bqyMsFlw01PH8X0IKi+RCnjr2ddOd6vtdx3ORLciOFqeVHk/1sAOPMktCVfALRtxX1EAKqqydZegV3YVWcJBTr2pupOftcANR16KE9/YA8sOwWuUQhjJJZa346cEwOfYkl80LmuHwAZxl+Lj0zeA/ZR4AsUYDTC/EhK4xlNtl5KKlMgXUy5fNJXlXBZEmWJntLh5lDsWJfxQoGhDjW24hC45Mh0S0YgzZSeUi5brAPc7Gkcy6hQlbmvd0uEM5caWYv6xai4cgk50XcLM+MpnG3dEmfxZygqJutKFP3YxjKzNw0ZgcAPGkU5vhzKTHQ2kZkw5dG3I7dAaFUSqFiZPg53/vPobsgIJJUvm3CDtiAj6AB3W/4QZZuW64TpVtoBdZ4gLiVxhvcaDUxtMyzIPjxEvBwMKQds5cI9gQESrG+NlyJPt9RGUrQy+BYyYWBhdimZLNOOthCufG3La+Tk4GtN3J5LFeVfypcuObIHLzRHCKhYDnV/nUd5btpwkZhyAFff6E4pgZpXQQYKFhScSISq+YNNpabc1QiNsX4tzCth/qBmKba/ncRF/h10gvHBiP2qJ8YvvUiRxOuu2TVFVeo7Wg6Z6nJDYBx8eQ1sZDAnuh05cbckUW/jpTIy0K4yQ+rYh8jJ4WrzTCprxZWt5lGytzgznsiG676yi8ePjrzFlRJqtoE3W8iw49c9UFivSqNQ4OHKjKaqnlGsVq++dJfstG7sMMwPZhau8FKzKQ4F+TUSo9PId1/zF0TwqLFLtoNc7QfC0Lm7iPAXidLX8H14jvtN4CCpVfu4URD4PzWUKHstkX3CA3jmbFYfqdH4wd9vA+7yMjOFhH5K5p6IH2xyr1NWzAvF9uzTBdZTzT/k0BkyYJTKCeqL0u4AoGi3q6i2mO7bETRyzFXRSm/+0fUtnYKTmQvUs05kxo+J25Y1hjFNJOWXOoU4hDzL3tZQTrp///6zanYXtlZh927oVyCZEmKvp/UaiDpOqkvEzCKyVFyiEWBE5dTMC8N2rmHWg6+BeIuUgL8vazxV58e38R8US9kx7jl1nhccRrh+5yweqcQrv8HE/BCj/xf5918ueeVu3VVnblkJnzMIpNNyWs+6V/8sAAUzbS+P7TRaCREtSKZ3vdrzCGP5aD5ii0erFLJ+G+TJtYkj2D+MrzZEa7ITNbRa9LDlVZmfaIBM7FwWVPX7wd8bj5h1/VZGN/u0vXVuUVRH51wVNKD/EfdZ9Sesr2RAKlKCe18bX6VlJ2ZMXc1d57WoCVIYIJ9YrleT3DjOjPGYaDA+U1dLbwPZCOmZA4LPqWI0bVmGzXBCaZkmYyfTRbWYAfrNfItIk2AeANvS18Z5hzRiCVKdKh68rQ3VsEwM/KF3/vcOq6DpQr+5uIaE7TRchc8+qpi7bJiSSn6XLcyAzQhCplBoW0Xr0oAML8DP1J3TkFF2ujXrUEBgQmlyAL8Ink7YyVL496glSk2OaRGJ1h53gQB8/i+OxII4xSODHBG6ayS+rqQLxfgv8Hcjo2XSTUZz0hGyqOdtBlno6CmuEtcunETbdVkYiHglNgb0l5KLj4pu+TuXJvT+AbqMvFOqneKI7Wwl6PIqiMW0aDEPXsZcAcW06g6/LKiYzPH7qsESWF5U9giqNd4aYah+v4atIFmw0jkrYuqo93FFnLt0iIl4571obyd5d4H0UQ3d1gUMNGdMe45+gPxLp3HKuchoBul1syLNXKB13ZptNcHJ5cjMS5BEa8Ss9yn6pQGRNHkniZwQC1V6cBZBfxO9EEP0li4GY4dRJO855mXgwf6rX63kjTmA8Tg/Y/mTD63vOBOgBFjfF8P/BEywWoxKq1dKnaMYG8WggfktNXtiKvb+4LcnDfw+LgkRnxxxvckH/YSv9lgrI88qvz8Dau9OntF4SMC2aQMgEzqC3TgQW/6ebF6Y/IV+ZBZoqgjkKw8kilStaAO9G98tZ6+XmaFxrerL8WO26sW9evPt0vJdrL43ILqIrgbCASGCnjUPgaQmqIaWAPaCMZkUIRP+10f6brGq4o8QRiV0AXRdkdeZCh0twOhJf1L8z1cw+6+ydiGex79DZaNNogogr+pAQvPswun3/RQqouC8FZ5Gr+CuMePAzZ9g4TT9eR+UJ9Ef1+Ed9wcdPnN4fhr7wKOkjnEAdYZlmIZ7ULO+XW/5KCuBtX8ya4Zvwd9tH4EEyFi7OPxXWlWNO+/z0FiiT9CfsxMuZICsxmrSB+aYKsrAKc6EOIWHOrDJmwoSjV9qNCNOMy/0S6dXgw9TE0laO/LeOASajGgAP1F9lOsA9D+dRsxQJSjXzLfO6WRuBxY7AWzyJ80cBliapa1ZId6Wof30CVgULQ3bEemYOCetCF4U+vseBiIFhKtkuiDIQ3gRvCalIZPW0t5SZwXFYd8ttXdUVoe3sm/kumnH02vEhF4ZuIMUcDnR4LZd9B/5YPneuq52zJNVQElRJ4eJWPls9BsQcCdKQ8Mra6TXGv7FdCtdLn7y1mzu/uI/R/Z2dAQNTBAANu3QchPvbCrpjRhWXHwIhSroondf6KC6gYPhr1Rf4TiSwccxeviBgXrzFV6TgAsGqZM9o7AtWmaEB+g6KfAp8RO4VC+wH3CZk+lA823980Zs9VDy9uPu7CY00ttGOtXH6MZNCIWtPrqY461CsXsdrE1mq4qrdduWYCvNYh4T2jGoNN+xzTNsJjSPo9WJ0/0REo3vtC26i4nkWWV1Jq1raoZ9cFiVmD0Tq4zidkBUzk/lfidRZDFDlgC3Bc9DtxNwdPgD4j1nPW8IH95IVFZKgnoIrcHLXsUwrB//c04frjQPdZnCDuAAmR6exgzNYvYLBuZJQ/yoTNR8z9runO+I1d+Fw498Yw4ahCQEnOFjTpOhThOI2O6BQXP+pO8oArHXqJRuaQjenVeMTlgIk+A3xwcVKXje1E/iWoG+MkN7RQV7deOja1MpxX1d4Xg7cI0wP4Iw5D9Q3u4cPzy1UC8pkX+0SfK9ybi87maMehU3bPpVCgSgcQ5wXPybaecXpBqaa7jke0cf7mNrsWg/aiY4pAAyV+5L+0rp+LJ5RCm5Be4UtsosJjc+SYnSqAHb67R4+QZBL/RCnQz/SMsPDdJHDACpfmNgLlw6E7+JIOZICE79xtNuw+TlrFPNlczlaAgMMXn6QWMznPpmYL2erZ9nH8qJWJnRCSFceDAe86nchUFWLvl09u3tqMTnaj+Zja2B+e3nsfwqNZdaTQqmpWVKlH1CL/0cnjVkLoaDll2SSHvMS7VQ8OR6bWms1eovKwLGT1T/VbKgdQD6m8+N1JGL+ZnNUdyoV3v7FNF6fVYkmekQgU47G4sdhGbs+dG7eNHWzXlZNkb0hJAgDC7zJgd69P+0gl0lks2zQosq+WxGx0ypbNKPbDMdxP4oONhPt6ICm72RbV79P+x6kTjJOtTzKz1h1V0vPkeUbVCyPFKZfMvmDRZ9S3AEcjBuKUAS9wWDUSm/IpQDgF/f0CPUkP5NmhMDc4qOI86nxrGxgQS723zo7vc+VzE+s6Js/lia7TqVgk6wKmlWR2gDeyvQBihRLykz6CIsd/VIqX8mUrmJeb9ds693MzMyGYiOuUNpkAbBIwERgUT7GZUXUyMDob34mShgj1WlCCh4qe0GWtWL98GacXebtprogmfuF9WwkwHn1npns76MRU5nbHTqXj1lgsnn3iZ7FB1gUf1lq9eCMpT+eiRjAg11XExmK5KJpn6HARrbUsnzgBNvqpydFaKHR9h74i5nx/KjAJJYysFuhu/LhS9ijfWshPkr/lS5F1ZPEGpP9m4yy7VlcMh8MQXkSEboOkBIYGPTa8Imdewh1+mkgMrVJrZEdF6L6ZlbUT5vB2e9rLl8GfvSTisyfmn9OxUPBnsvqLkck/MDD9qBzs55BLOAleVDDwbf/i96TWATuvOlSaQ5oA==) ### Changes not sent for review[​](/deployment/google-playstore-deployment.md#changes-not-sent-for-review "Direct link to Changes not sent for review") If you face an error that says '*Changes cannot be sent for review automatically*', enable this option and retry deployment. ### Submit as draft[​](/deployment/google-playstore-deployment.md#submit-as-draft "Direct link to Submit as draft") While deploying, if your app is still in draft mode, meaning it is not available on the Play Store yet, you may encounter an error message stating, 'Only releases with the status draft may be created on a draft app.' To resolve this, enable this option, and you'll see that the release will be created as a draft. You'll then need to manually roll out the app. *** ## Video guide[​](/deployment/google-playstore-deployment.md#video-guide "Direct link to Video guide") Watch this video if you prefer watching a video tutorial. [Sharing a Project with a User](https://www.youtube.com/embed/kLfcAzAHA6o) --- # Pre-checks Before Publishing This page outlines the important steps and checks to be made before publishing your app. These steps are crucial to ensure that your app works as expected, meets platform guidelines, and to gather preliminary feedback. Here’s a comprehensive list of these prechecks: 1. **Functionality Testing**: Test the app manually across devices. You can also implement integration tests using FlutterFlow’s [**Automated Tests**](/testing/automated-tests.md) framework to cover various scenarios. 2. **Get Feedback**: Run your app in Run Mode to generate a shareable link to the session. You can share these links to gather feedback from users and testers, providing valuable insights and potential areas of improvement before the public release. 3. **Optimizations & Enhancements**: Improve performance by implementing [optimization and enhancement](/flutterflow-ui/toolbar.md#project-suggestions) suggestions. Ensure that images are properly sized, consider using higher compression for assets, and remove unused assets and custom widgets. These will help improve your app's speed and size. 4. **User Interface:** Check UI consistency across different screen sizes and resolutions using the [Canvas Size](/flutterflow-ui/canvas.md) option. 5. **Accessibility Checks**: Add semantic labels to make the app more accessible to users with disabilities by providing meaningful descriptions. 6. **Security Measures**: Make sure all data handling practices comply with legal standards, including GDPR if applicable. Use HTTPS for all network connections and ensure that sensitive data is encrypted. 7. **Compliance with Store Guidelines**: Review the submission guidelines for [Apple’s App Store](https://developer.apple.com/app-store/review/guidelines/) and [Google Play Store](https://play.google/developer-content-policy/). Check for any specific requirements such as app metadata, privacy policies, and minimum functionality. 8. **Localization and Internationalization**: If your app targets users in multiple countries, consider [adding multi-language](/concepts/localization.md) support. 9. **License and Third-Party Attributions**: Adhere to licenses and include necessary attributions for third-party libraries and assets. 10. **Prepare Marketing Assets**: Prepare all the necessary marketing assets, such as screenshots, app icons, and promotional text. You can easily [generate screenshots](/deployment/pre-checks-before-publishing.md#generate-screenshots) right within FlutterFlow. *** ## Generate Screenshots[​](/deployment/pre-checks-before-publishing.md#generate-screenshots "Direct link to Generate Screenshots") Alongside crafting beautiful apps, you can also generate screenshots for your mobile app right within the builder. Screenshots are captured in all the recommended device sizes required for publishing to the App Store and Play Store. info If pages are rendered using a **WebView** widget, the generated screenshots will appear blank. Let's explore how to generate screenshots for your app: [Sharing a Project with a User](https://demo.arcade.software/PgdOhHS8UBVdVTrem2Fy?embed\&show_copy_link=true) --- # Web Publishing FlutterFlow supports web publishing, allowing you to build and publish web applications in addition to your mobile apps. This guide provides details on how to use FlutterFlow for web publishing. From enabling web support and making design adjustments to deploying your app and adding custom domains. info * You can ship your existing mobile app as a web app with little or no change to the current setup. * We offer free hosting and custom subdomains for all users. * We've rebuilt some of the components to work better on the web. ## Publish to Web[​](/deployment/web-publishing.md#publish-to-web "Direct link to Publish to Web") Publishing to the Web comprises of the following steps: 1. [Enabling web support](/deployment/web-publishing.md#1-enabling-web-support) 2. [Make design adjustments (optional)](/deployment/web-publishing.md#2-make-design-adjustments-optional) 3. [Resolving errors](/deployment/web-publishing.md#3-resolving-web-compatibility-warnings) 4. [Adding general information](/deployment/web-publishing.md#4-adding-general-information) 5. [Deploy](/deployment/web-publishing.md#5-deploy) 6. [View live web app](/deployment/web-publishing.md#6-view-live-web-app) ### 1. Enabling web support[​](/deployment/web-publishing.md#1-enabling-web-support "Direct link to 1. Enabling web support") By default, FlutterFlow allows you to run your app on *Android* and *iOS* without any additional effort. But, to run and deploy your app on the *Web*, you need to add platform support for the Web. To add platform support, navigate to the **Setting and Integrations > Project Setup > Platform >** turn on the **Web** toggle. ![enable-web](/assets/images/enable-web-643a2696a52194f7129f78a65175e7dc.avif) info Enabling web support automatically enables [**deep linking**](/concepts/navigation/deep-dynamic-linking.md) for your project. This helps in creating URLs for every page of your app. #### Advanced Web Settings[​](/deployment/web-publishing.md#advanced-web-settings "Direct link to Advanced Web Settings") 1. **Use CanvasKit**: Enabling this option can provide high-quality graphics and text rendering on web platforms. 2. **CORS Proxy for Images (Optional)**: When using CanvasKit, some images can be blocked from loading if the server is not configured to allow loading them from other websites. This happens because Flutter web uses WebGL for rendering, which requires access to raw image data and is subject to browser security restrictions called [Cross-Origin Resource Sharing (CORS)](https://docs.flutter.dev/platform-integration/web/web-images#cross-origin-resource-sharing-cors). Choose the appropriate option based on where your images are hosted: * **None**: If you are only loading images from your Firebase Storage, select this option and configure Firebase Storage for web access. FlutterFlow automatically excludes Firebase Storage images from CORS proxy requirements. * **Deploy with Firebase**: If images are hosted on external servers (not Firebase Storage) *but you use Firebase for your app*, choose this option. FlutterFlow will automatically deploy a regional CORS proxy function to your Firebase project for optimal performance. Simply click the **Deploy** button that appears below this option. * **Custom Proxy URL**: If you're not using Firebase or prefer to manage your own CORS proxy, specify your custom proxy URL here. If you don't have one, you can create one using services like [cors-anywhere](https://github.com/Rob--W/cors-anywhere) or CloudFlare Workers. warning **Performance Note**: Using a CORS proxy adds a network hop for external images, which may slightly increase loading times. For best performance, host images on Firebase Storage or a CORS-enabled CDN when possible. 3. **Import Emoji Library**: Importing the Emoji library is necessary if your app may use emojis anywhere in any text widget. However, this will increase the size of your app on web. 4. **Use Wasm (Beta)**: Enabling this option will build your app using Flutter’s **Wasm (WebAssembly) web renderer**. For more details, see the [Flutter Documentation on Wasm](https://docs.flutter.dev/platform-integration/web/wasm). warning * This feature is currently in *Beta*, so it should be used with caution. * Wasm is not supported in *Test Mode*. #### Troubleshooting CORS Issues[​](/deployment/web-publishing.md#troubleshooting-cors-issues "Direct link to Troubleshooting CORS Issues") If you're experiencing image loading issues on web: 1. **Check browser console**: Look for CORS-related error messages 2. **Verify image sources**: Ensure external image servers allow cross-origin requests 3. **Test proxy configuration**: Verify your custom proxy URL is accessible and functioning 4. **Firebase Storage setup**: Confirm Firebase Storage rules allow public read access for web ### 2. Make design adjustments (optional)[​](/deployment/web-publishing.md#2-make-design-adjustments-optional "Direct link to 2. Make design adjustments (optional)") If you're creating a web-only application, setting the canvas size to desktop and building pages accordingly can work well. However, if you plan to target both mobile and web users, some design adjustments may be necessary to ensure that the UI is optimized for both platforms. You can create separate widgets for different platforms and control their visibility using [Responsive Visibility](/concepts/layouts/responsive.md#responsive-visibility). ### 3. Resolving web compatibility warnings[​](/deployment/web-publishing.md#3-resolving-web-compatibility-warnings "Direct link to 3. Resolving web compatibility warnings") If you have previously built a mobile app and have recently enabled web support, you may encounter warnings regarding web compatibility. Due to the distinct nature of mobile and web platforms, some of the widgets and actions in FlutterFlow, including [AdMob](/integrations/ads/admob.md), [RevenueCat](/integrations/payments/revenuecat.md), [Share](/concepts/navigation/share-action.md) action, and [Launch Map](/deployment/web-publishing.md) action, or your custom widgets may not function as expected because they are not yet supported on the web. Any known *Web Support* Issues will be displayed as a **Platform Support Warning**. This won't stop you from deploying your app to the web, but it can result in poor user experience and unexpected app behavior. ![platform-warnings](/assets/images/platform-warnings-ab76fa005365e723233d93bc4bb2337b.avif) In such a situation, you can try to find a replacement package on [pub.dev](https://pub.dev/) (considering it meets your requirements and has a good score). warning **Important**: Make sure to double-check any *pub.dev* packages you are using have *Web* Support. ![web-support](/assets/images/web-support-09e3ea561fe01d5c695f7288e073cf8c.avif) ### 4. Adding general information[​](/deployment/web-publishing.md#4-adding-general-information "Direct link to 4. Adding general information") In this step, you must provide general information about your web app by following the steps below: 1. Navigate to the **Setting and Integrations >** **App Settings >** **Web Deployment**. ![web-pub-general-settings](/assets/images/web-pub-general-settings-66306bc810313d0b7ff879d85786f4e4.avif) Inside the **General Information** section, enter the following details: * **Site URL**: You can define the *Site URL* by adding the subdomain, for example, *mywebapp.flutterflow\.app*. You can only change the subdomain, i.e., the part before *flutterflow\.app*. warning * You can remove or change the existing subdomain by simply entering the new one and hitting the publish button. Note that when you change your subdomain, it only takes effect the next time you deploy. * Old addresses can stop working anytime and be given to another user. * There is a limit on the number of subdomains you can register per user. *Paying users can register up to 20 subdomains*. You will receive an in-app warning if you are approaching the limit. * **SEO Title**: This appears in social sharing previews and search results. * **Site Description**: A text that you would like to appear in the social sharing preview card and search results. * **Page Title**: This appears in the browsers tab for all pages of your app. * **Favicon**: An icon that typically appears before the web app name inside the browser's tab. To change it, click on the **Upload Favicon +** and upload the icon. You can generate it for free from [here](https://favicon.io/). * **Status Bar Color**: This is to change the status bar color when viewed on the Safari browser on iOS and installed as a PWA on mobile devices. * **Social Share Image URL**: The image from this URL will be displayed inside the social share preview card (e.g., OpenGraph and Twitter card). * **Individual Page Titles**: Enabling this will display the current page name in the browser tab. If you do so, ensure you provide the **Page Title** under **Page > Properties Panel > Route Settings**. * **Show Watermark**: By default, a button with 'Built in FlutterFlow' text appears as a watermark at the bottom right side of your page. To remove, disable the **Show watermark** toggle. * **Allow Showcasing**: If enabled, we may feature your project on our website. * **Allows Search Engine Indexing**: This is to let people discover your site via search engines. * **Enabling PWA**: Enabling this can provide an app-like experience right in the browser. PWA app can be installed on the device, supports offline functionality, sends push notifications, and can be accessed without the need to go through an app store. * **Use CanvasKit**: Enabling this can provide high-quality graphics and text rendering on web platforms. CanvasKit can be used as an alternative to the default HTML renderer when higher graphical fidelity is needed in Flutter web apps. * **Use Original Engine Initialization**: This uses original Flutter web engine initialization, which sometimes helps in better loading time in the deployed web app. info Tip: Only users on the paid plans can remove the FlutterFlow watermark. ### 5. Deploy[​](/deployment/web-publishing.md#5-deploy "Direct link to 5. Deploy") When you are ready to deploy, click **Publish.** This will take approximately 2-3 minutes. ![publish-button](/assets/images/publish-button-69cee7cd9d757ee661957d2b9ca6c0f9.avif) By default, you will publish to a subdomain based on your project id. These default subdomain addresses do not count toward the subdomain quota, and you can deploy as many projects as you'd like. The URL would look like this: `your-project-id-1234.flutterflow.app` You can also modify the address by specifying a custom subdomain address, in the **Settings > Web Publishing** tab's **Site URL** field, as long as it's available. You can have up to **2** custom subdomain URLs on the Free plan, up to **20** on any of our Paid plans, and **unlimited** custom subdomain URLs on the Enterprise plan. info Once it is published, you can make any changes live to your users by clicking the **Publish** button again. If you try to publish to a domain that is already taken, you will receive a warning like ‘*Error reserving subdomain: Subdomain `your-domain-name` is already used by another project.*’ To overcome this, enter a different subdomain inside the **Site URL** and select **Publish** again. info In case you want to unroll your web app, hit the **Unpublish** button at the bottom. ### 6. View live web app[​](/deployment/web-publishing.md#6-view-live-web-app "Direct link to 6. View live web app") To view the live version of your app, click the **eye icon** next to the 'Publish' button. ![view-published-site.avif](/assets/images/view-published-site-fb67b90e4b5a97af7a7c101aeea1061d.avif) *** ## Adding custom domain[​](/deployment/web-publishing.md#adding-custom-domain "Direct link to Adding custom domain") Adding a custom domain to your web app can give it a more professional look and feel and make it easier for your users to remember and find. FlutterFlow allows you to connect your own domain name to your web app and have it up and running in no time. This feature is perfect for those wanting to establish a strong online presence and increase brand awareness. Important * All our paid plans include one free custom domain, with the option to purchase more if needed. * A single custom domain slot can be linked to only one domain or subdomain. * You can connect only one domain to a project, which can be either a root domain (like 'myapp.com') or a subdomain (such as 'beta.myapp.com'). That means if you connect a root domain, none of the subdomains under it will connected to the project. This leads to the rule of '*One project => One domain OR subdomain'*. To add a custom domain: 1. Enter your **Custom Domain URL**. Ensure you only enter the domain name (without www) and extension (e.g.,*mywebapp.com* and not *[www.mywebapp.com](http://www.mywebapp.com)*). 2. Now, you must set up the DNS. To do so: 1. Visit the website from where you bought the domain. 2. Open the DNS manager and create the records as per displayed in UI. **Note** that there should not be other A or AAA records after adding this. Here are quick links on how to do this on popular domain-selling websites. 1) [Godaddy](https://in.godaddy.com/help/add-an-a-record-19238) 2) [Namecheap](https://www.namecheap.com/support/knowledgebase/article.aspx/319/2237/how-can-i-set-up-an-a-address-record-for-my-domain/) 3) [Google Domains](https://support.google.com/a/answer/2579934?hl=en). Here's an example of how it looks in Godaddy. ![custom-domain-listing.avif](/assets/images/custom-domain-listing-15b4939f2bd6b275d07c417ec1a89ff1.avif) 3. Click **Connect**. 4. Once the domain is connected, hit the **Publish** button again. ![connect-custom-domain.avif](/assets/images/connect-custom-domain-7e79a9bf73bf3f1f372ca67e5c0706da.avif) *** ## Add custom headers[​](/deployment/web-publishing.md#add-custom-headers "Direct link to Add custom headers") If you are familiar with HTML, you may set any additional headers (e.g., [style](https://www.w3schools.com/tags/tag_style.asp) and [script](https://www.w3schools.com/tags/tag_script.asp)) that you would like to be used in your published web app. For example, adding inline or external javascript. This will appear inside the head tag of your published app. warning These headers are used directly in the `index.html` of your site, so malformed headers may cause unexpected behavior (just as directly editing `index.html` would). To add a custom header, enter your tag inside the *Custom Headers* input box and publish the web app again. info You can verify the added custom header by opening the inspect element window (**Command+Option+i** on **Mac** or **F12** on **PC**) and finding your tag inside the head tag. ![custom-header.avif](/assets/images/custom-header-530514e7514209099851f8a6c95f6777.avif) *** ## Changing Firebase dynamic link[​](/deployment/web-publishing.md#changing-firebase-dynamic-link "Direct link to Changing Firebase dynamic link") If you do web deployment and utilize Firebase dynamic links in your app, it's recommended that you update your Firebase Dynamic Links URL scheme. This adjustment is necessary to ensure shared links open correctly on the web. By doing so, your dynamic links will function properly for users across all platforms. ![update-firebase-dynamic-link.avif](/assets/images/update-firebase-dynamic-link-4237c8bcd04bc639c96bcc9368428f0d.avif) *** ## Adding subdomain as Authorized domain (Firebase)[​](/deployment/web-publishing.md#adding-subdomain-as-authorized-domain-firebase "Direct link to Adding subdomain as Authorized domain (Firebase)") If you are using *Firebase Authentication*, you must add your custom subdomain as an authorized domain in the [Firebase console](https://console.firebase.google.com/). Otherwise, social and phone sign-in will not work. To enable your subdomain as an authorized domain: [Sharing a Project with a User](https://demo.arcade.software/lT8TyH1hZARTobmthlwI?embed\&show_copy_link=true) *** ## See deployment history[​](/deployment/web-publishing.md#see-deployment-history "Direct link to See deployment history") Deployment history is essential for maintaining transparency, accountability, and a clear understanding of how a web application has evolved over time. Each deployment entry in the history includes a timestamp indicating when the deployment occurred. It also display the status of each deployment (e.g., successful, failed). This helps in quickly identifying whether a deployment was completed without issues. Click **View Full History** to review the previous successful version. ![view-deploy-history.avif](/assets/images/view-deploy-history-f47685e0faae99366d95363c2472065d.avif) --- # Craft your intent on the canvas.
Bring the cost of design iteration to zero. [**FlutterFlow Designer**](https://designer.flutterflow.io/) provides the fastest UI generation in the world, without sacrificing quality or control integrated with agents for a seamless design-to-code experience. Available on the web and as a desktop app for macOS and Windows. [FlutterFlow Designer Demo](https://www.youtube.com/embed/NhH8j169voo) ## Get started[​](/designer.md#get-started "Direct link to Get started") [🚀](/designer/quickstart.md) [Quickstart](/designer/quickstart.md) [Generate your first app design from a simple text prompt and export it in just a few steps.](/designer/quickstart.md) [PromptingStyle explorationExport](/designer/quickstart.md) [🧭](/designer/workspace.md) [Tour the workspace](/designer/workspace.md) [Get familiar with the panels, canvas, and tools that make up the Designer environment.](/designer/workspace.md) [CanvasPanelsThemeComponents](/designer/workspace.md) ## Explore the Designer[​](/designer.md#explore-the-designer "Direct link to Explore the Designer") [🧩](/designer/components.md) [Components](/designer/components.md) [Build reusable UI blocks with variants and parameters to keep your design system consistent across screens.](/designer/components.md) [Reusable UIVariantsParametersHas expression](/designer/components.md) [📥](/designer/import.md) [Import & Export](/designer/import.md) [Bring existing FlutterFlow screens into Designer or hand off designs back to FlutterFlow, PNG, or agent-ready prompts.](/designer/import.md) [ImportExport to FlutterFlowPNGAgent prompts](/designer/import.md) [🤖](/designer/integrations.md) [Integrations](/designer/integrations.md) [Integrated with agents for a seamless design-to-code experience. Connect Designer with Claude, Gemini, and other AI tools to edit designs in natural language.](/designer/integrations.md) [ClaudeGeminiMCPNatural language editing](/designer/integrations.md) FlutterFlow Designer empowers both non-technical creators and development teams to move faster during early UX and UI exploration, then hand off straight to code. --- # Collaboration Collaboration lets multiple people work on the same design at once. Edits, cursors, and comments all stay in sync, so a team can explore and refine a design together without passing files back and forth. ## Sharing & Access[​](/designer/collaboration.md#sharing--access "Direct link to Sharing & Access") Designs are shared by email. You invite someone by entering their email address, and they get access to that specific design — there are no public links or anonymous access. Each collaborator is given one of two roles: * **View** — open the design and follow along, without making changes. * **Edit** — make changes to the design alongside everyone else. The owner manages who has access and can add, change a role, or remove collaborators at any time. Access is granted per design, so sharing one design doesn't expose anything else in your workspace. ### Add a Collaborator[​](/designer/collaboration.md#add-a-collaborator "Direct link to Add a Collaborator") 1. Open the design you want to share, then open the **Collaboration** dialog from the share control. The owner is shown at the top, with everyone the design is shared with listed under **Shared with**. 2. In the email field (`name@company.com`), type the address of the person you want to add. 3. Use the role dropdown next to the field to set their access — **View** or **Edit**. New collaborators default to **Edit**. 4. Select **Add** (or press Enter). They appear in the **Shared with** list, and live collaboration turns on for the design. ### Change a Collaborator's Role[​](/designer/collaboration.md#change-a-collaborators-role "Direct link to Change a Collaborator's Role") In the **Shared with** list, open the role dropdown on that person's row and switch between **View** and **Edit**. The change applies immediately. ### Remove a Collaborator[​](/designer/collaboration.md#remove-a-collaborator "Direct link to Remove a Collaborator") In the **Shared with** list, select the remove icon on that person's row. They lose access right away. Removing the last collaborator turns off live collaboration for the design. ## Real-Time Editing[​](/designer/collaboration.md#real-time-editing "Direct link to Real-Time Editing") Everyone with Edit access works on the same canvas at the same time. Changes appear instantly for all collaborators, like adding a frame, restyling an element, or updating the theme is reflected for everyone as it happens, with no manual saving or refreshing. Because changes are merged automatically as they're made, multiple people can edit at the same time without overwriting each other's work or running into "who saved last" conflicts. Active collaborators are shown on the canvas as you work, so you always know who else is in the design and what they're focused on. ## Comments[​](/designer/collaboration.md#comments "Direct link to Comments") Comments let you leave feedback directly on a design — pinned to a frame, an element, or the canvas itself — so discussion stays anchored to what it's about. Each comment opens a thread for replies, and the Designer Agent can act on a comment to make the change for you. ### Add a Comment[​](/designer/collaboration.md#add-a-comment "Direct link to Add a Comment") 1. Right-click on the canvas, a frame, or an element, and choose **Comment**. A **New comment** popover opens, anchored where you clicked. 2. The popover shows the target above the input — **On Canvas**, **On Frame …**, or the element name — so you know what the comment is pinned to. 3. Type in the **Add a comment** field (up to 4,000 characters). 4. Select **Send** (or press Enter). A canvas comment drops a pin with your avatar; a frame or element comment adds a count bubble to the frame. ### View and Reply in a Thread[​](/designer/collaboration.md#view-and-reply-in-a-thread "Direct link to View and Reply in a Thread") 1. Select a canvas **pin**, or a frame's **comment bubble** (tooltip: *View comments*). A bubble with multiple threads opens a list first — pick the one you want. 2. The thread shows the original comment and all replies, oldest to newest. Long threads load more replies as you scroll. 3. Type in the **Write a reply** field at the bottom and press Enter or select **Send**. ### Edit or Delete Your Own Comment[​](/designer/collaboration.md#edit-or-delete-your-own-comment "Direct link to Edit or Delete Your Own Comment") These actions appear only on comments you authored. 1. Hover the comment you wrote and select the **⋯** menu (tooltip: *Comment actions*). 2. Choose **Edit**, change the text in place, and send. The comment is marked **Edited**. 3. Choose **Delete** to remove the comment. ### Ask the Designer Agent[​](/designer/collaboration.md#ask-the-designer-agent "Direct link to Ask the Designer Agent") 1. Hover a comment or open its thread, then select **Ask agent to fix** (the sparkles action). It's available from a comment row, the thread header, and individual messages. 2. The agent reads the full thread, original comment and replies and applies the requested change to the target frame or element. It's unavailable while the editor is already busy. 3. When it finishes, the agent posts a reply in the thread confirming the change. info For comments on the canvas, this action instead creates a new frame from the comment. --- # Components A **component** is a reusable UI building block that you can use across your app design. Instead of creating the same UI again and again, you build it once as a component and reuse it wherever needed. This helps keep your app design consistent and easier to maintain. When you update a component, all places where it is used automatically get updated. Imagine you are having a settings screen with multiple rows, such as: * Notification toggle * Privacy option * Account settings Each row has a similar layout with an icon, text, and an action such as switch or arrow. Instead of having each row separately, you can create one **Settings Item component** and reuse it multiple times with different content. ### Creating Component[​](/designer/components.md#creating-component "Direct link to Creating Component") To create a new component, start by selecting an existing UI block on the canvas. Then click **Create Component** from the right-side panel, give your component a name, and choose the parameters you want to include (such as text, image, or icon). Once you confirm, the component is created and opens in Component Studio. Inside Component Studio, you can bind these parameters to different UI elements. Select an element, then connect its properties (like text or image) to a parameter from the right panel. You can also add new parameters if needed. This allows each instance of the component to display different content while keeping the same structure and design. tip Once the component is created, you can also use AI to quickly update your component by describing the changes instead of manually editing everything. ### Create Variants[​](/designer/components.md#create-variants "Direct link to Create Variants") A **variant** is a different version of the same component that allows you to change its appearance without creating a new component. Variants help you manage multiple styles, states, or layouts in one place to make your components more flexible and reusable. For example, a button component can have variants like **Filled** and **Outlined**. To create a variant, first open your component and click **Add variant**. This creates a new option for the current component, such as an alternate style or layout. Once the new variant appears, select it and customize its properties to make it visually different from the default version, such as changing borders, spacing, colors, or other styling details. If you want to introduce a completely new category of variation, click **+ Add variant** again to create a new dimension for the component. ### Add Toggle[​](/designer/components.md#add-toggle "Direct link to Add Toggle") A **toggle** lets you switch between two states of a component, such as on/off or active/inactive, within the same component. For example, a settings item can have a toggle to show **enabled** or **disabled** states, or a card can toggle between **selected** and **unselected** styles. To add a toggle, open your component and click **Add toggle** from the variants panel. This creates a new toggle dimension for your component. Once added, you’ll see two states (i.e., true/false). Select each state and customize the component to define how it should look in each case. #### The `Has` Expression[​](/designer/components.md#the-has-expression "Direct link to the-has-expression") The `Has` expression lets you automatically control a Boolean property based on whether a component parameter has been provided. This is useful when you want part of a component to appear only when data exists, without manually setting a separate true/false value each time. For example: * Show an image only when `image_url` is set * Show a subtitle only when `subtitle` is set * Show a time row only when `time` is set A `Has` expression checks whether a parameter contains a value. If it does, the result is `true`. If it does not, the result is `false`. Suppose you have a flight booking card component with an optional image on the right side. Instead of adding both `image_url` and a separate `show_image` flag, you can just use `image_url` and bind the **Visible** property to `has(image_url)`. If an image is provided, the card displays the image, and if not, it just appears as a text-only layout. ![control-using-has-expression](/assets/images/control-using-has-expression-33d71531048737ae78b8b2cda1971a4c.avif) --- # Export Once your screens are finalized, you can export your design for implementation. FlutterFlow Designer provides flexible export options depending on whether you want static assets, reusable prompts, or direct integration into FlutterFlow. ## Export Options[​](/designer/export.md#export-options "Direct link to Export Options") * **Export Frames as PNGs:** Download high-quality PNG screenshots of your frames. This is ideal for adding to documentation, or presenting visual concepts. * **Export Agent Prompt:** Download an agent-ready prompt as a Markdown file. This allows you to reuse the generated design structure as context in AI workflows or modify it further using natural language instructions. * **Export to FlutterFlow:** Copy all frames directly to your clipboard for pasting into a FlutterFlow project. Simply select a widget on a FlutterFlow project page and paste to import all your design instantly. ## Export Storyboard[​](/designer/export.md#export-storyboard "Direct link to Export Storyboard") To export the entire storyboard, open the top-left **FF Designer** menu and choose one of the export options (PNGs, Agent Prompt, or FlutterFlow). This method is best when your full flow is ready for implementation. ![export-all.avif](/assets/images/export-all-51648fdd1bb34062810c024ff8917b21.avif) ## Export a Single Frame[​](/designer/export.md#export-a-single-frame "Direct link to Export a Single Frame") To export a single frame, select a specific frame and use the **Export** section in the right panel. Use this when you only need to implement a particular screen. ![export-single-screen.avif](/assets/images/export-single-screen-e6ce2e1682fbb34dd8ecef32b722bc23.avif) --- # Import from FlutterFlow Importing from FlutterFlow allows you to bring your existing app screens directly into the Designer environment. Instead of rebuilding UI from scratch, you can enhance layouts, explore new styles, and refine user experience faster. This is especially helpful when you want to modernize an existing app, experiment with different design directions, or quickly generate improved versions of your current screens. To import screens from FlutterFlow, select **Export to Designer** from the canvas menu options, then choose the pages you want to send in the export dialog. Once selected, click the export button to transfer them. After the process completes, the selected pages will open in FF Designer, where you can continue customizing and iterating on them. --- # Integrations You can connect FlutterFlow Designer with external AI agents and developer tools. This enables you to generate, edit, and inspect designs directly from your preferred AI environment instead of working only inside the Designer UI. For example, you can use an agent like Claude or Codex to open your design, make layout changes, add components, or iterate on styles just by describing what you want in natural language. Prerequisites Before using integrations, make sure the following are set up: * [**FlutterFlow Designer Desktop App**](https://storage.googleapis.com/flutterflow-downloads/designer/macos/prod/flutterflow-designer-latest-macos.dmg) is installed (currently available only on macOS) * **Agent MCPs** are installed via CLI. The install commands are: * **Claude Code:** `npm install -g @anthropic-ai/claude-code` * **Gemini CLI:** `npm install -g @google/gemini-cli` * **Codex CLI:** `npm install -g @openai/codex` * Supported **IDEs** are installed on your system. To download, use the official links: * **Cursor:**  * **Antigravity:** [https://antigravity.google/download](https://cursor.com/download) ## Installation[​](/designer/integrations.md#installation "Direct link to Installation") To add integrations, go to the **Integrations** section inside FlutterFlow Designer. Here you will see available integrations under **Agent MCPs** (such as Claude Code, Gemini CLI, and Codex) and **IDEs** (such as Cursor, Antigravity). Click **Install** next to any integration you want to use. ## Launch Agent[​](/designer/integrations.md#launch-agent "Direct link to Launch Agent") To launch an agent and update the design: 1. Open your design project and click the agent menu option in the top-right side. 2. Choose where you want to open the project, such as **Open in Claude Code**, **Open in Gemini CLI**, **Open in Cursor**, or **Open in Antigravity**. 3. Once the terminal opens, describe the change you want to make. For example, ask the agent to create more variations of a selected screen or modify the current design. 4. When the agent asks for permission to run a Designer MCP tool, approve the request so it can inspect and update the project. 5. Wait for the agent to complete the task. It will create or update frames inside your current design project. 6. Return to FF Designer and review the generated frames or changes. ## MCP Calls[​](/designer/integrations.md#mcp-calls "Direct link to MCP Calls") MCP Calls let an AI assistant work directly with your current FF Designer project. It can read, edit, create screens, update UI, manage components, adjust themes, and export designs. There are two main calls: * `create_session`: Connects the assistant to your open project and returns a session ID with available tools. Must be called first. * `call_design_script`: Executes actions like editing screens, creating layouts, updating content, or exporting designs. Using MCP Calls, an assistant can work with: * **Designs**: Create, open, rename, or export projects * **Frames (Screens)**: Create, duplicate, edit, or organize screens * **Nodes (Elements)**: Update text, colors, images, layout, and structure * **Components**: Create reusable UI, edit once and update everywhere * **Theme**: Control colors, typography, spacing, and styles * **Images & Assets**: Upload, replace, generate, and manage media * **Captures**: Take screenshots of screens or elements * **Selection**: Work on currently selected items * **History**: Undo, redo, and review changes The assistant operates directly on your design, making it easy to iterate quickly and visually. --- # Iterate After generating your initial storyboard, you can refine and improve your screens in two ways: [editing visually](/designer/iterate.md#edit-visually) on the canvas and [using AI prompts](/designer/iterate.md#use-ai-prompt). Each method is useful depending on the type of change you want to make. ## Edit Visually[​](/designer/iterate.md#edit-visually "Direct link to Edit Visually") This is useful when you want precise control over layout and structure. It makes it easy to quickly add or adjust elements exactly where you want them. To start, click on any UI element in the canvas. The selected element will be highlighted, and small dots will appear around it. You can click any of these dots to add a new UI element at that position. When you click a dot, a selector pop-up opens, allowing you to choose and insert a new element. You can also rearrange elements using drag and drop. Simply select an element and move it to a new position within the layout. ### Use Properties Panel[​](/designer/iterate.md#use-properties-panel "Direct link to Use Properties Panel") The Properties Panel allows you to make precise adjustments to any selected widget. When you click on an element in the canvas, its editable properties appear on the right side. From there, you can modify properties such as text content, typography settings, spacing, alignment, colors, borders, and other styling attributes. This gives you direct control over how each element looks and behaves without needing to regenerate the entire screen. Unlike AI-driven changes, edits made here are exact and predictable, making it ideal for polishing the design once the overall layout and structure are already in place. ## Use AI Prompt[​](/designer/iterate.md#use-ai-prompt "Direct link to Use AI Prompt") This method is best for structural, layout, or multi-element changes. To make a change using AI Prompt: 1. Click on the screen (frame) you want to update from the canvas or Frames panel. 2. Use the prompt bar at the bottom to clearly describe what you want to modify. 3. If you're not satisfied with the result, use the regenerate option to explore a new variation of the same instruction. 4. You can click directly on a widget. The selected widget will automatically be added to the prompt bar as context for your next instruction, allowing more precise AI updates. You can also select a page and ask the AI to generate variations of it. This helps you quickly explore different design directions. ## Edit Theme[​](/designer/iterate.md#edit-theme "Direct link to Edit Theme") Editing a **Theme** allows you to modify the global design system of your entire storyboard at once. Instead of adjusting individual widgets, you can change core styling elements such as brand colors, typography, spacing, corner radius, and text scaling. Any updates made in the Theme Editor automatically apply across all screens, ensuring visual consistency without manual updates on each page. --- # Prompting Prompting is how you turn an idea into screens. Describe your app in the main prompt box, optionally attach a reference image, and the Designer generates a complete editable storyboard for you. ## Create Designs[​](/designer/prompting.md#create-designs "Direct link to Create Designs") Here's how you generate an initial screen design, refine, and export it: 1. You can select [**Explore Styles**](/designer/prompting.md#explore-styles) to first browse and refine design ideas, or choose **Instant Generation** to skip that step and quickly create designs from your prompt. 2. Go to the **main prompt box** and write your app vision with important details (e.g., app type, target users, key screens, primary actions, and any must-have features). For example: *"Design a travel planning app with a modern card-based layout, destination image grids, a bottom navigation bar with Explore, Trips, Bookings, and Account tabs, saved itineraries, map integration, and a trip detail screen with timeline and booking information."* 3. Optionally, [**attach an image**](/designer/prompting.md#add-image-attachments) such as a sketch, wireframe, or screenshot using the image attachment button below the prompt field. The Designer will use it as a reference to transform it into a fully editable design. 4. Use the mobile and desktop toggle to set your target platform. This ensures your designs are generated with the correct layout and screen dimensions for your intended device. 5. Click the **submit** (up-arrow) button to generate your design storyboard. 6. Review the generated screens. Scan through the generated frames to confirm: * The right screens exist (and no critical screens are missing) * The overall flow makes sense * The UI direction matches your intent 7. Select a screen to refine. You can [**use the prompt bar**](/designer/iterate.md#use-ai-prompt) to request changes to the selected screen. 8. You can also make precise tweaks from the [**Properties panel**](/designer/iterate.md#use-properties-panel). Select an element and adjust its properties on the right side, such as text, typography, spacing, styling. 9. To add a new screen, click on an empty area of the canvas, then in the bottom prompt input, describe the screen you want to add. Include its purpose, key UI elements, and how it connects to the overall flow, then submit. 10. Open the **Theme** tab to edit global styling like colors, typography, spacing, and radius so the entire design stays consistent. 11. You can generate a **shareable link** for feedback and review. 12. To export your design, open the top-left app menu (FF Designer) and choose an **export option**. ### Explore Styles[​](/designer/prompting.md#explore-styles "Direct link to Explore Styles") **Explore Styles** helps you try different visual directions for your app before generating the final design. Instead of going straight to a full build, you can first browse style variations, compare layouts, adjust colors, and guide the design toward the look you want. This is useful when you already know what your app should do, but want help deciding how it should look. Once the styles are generated, browse through the generated variants and look for the one that feels closest to your vision. Each style gives you a different take on the same app idea, such as a different layout, spacing, typography, or visual mood. When you hover over a style, you can use the following options: * **Regenerate**: Recreates the style if something looks off or needs improvement. * **More Like This**: Generates more variations similar to the selected style. * **Remix Colors**: Keeps the overall style but changes the color palette. * **Prompt for Changes**: Lets you describe specific updates you want for that style. * **Use This Style**: Selects that style and starts the full generation process. ### Add Image Attachments[​](/designer/prompting.md#add-image-attachments "Direct link to Add Image Attachments") You can attach reference images directly in the prompt to guide the design generation process. This is useful when you have an existing sketch, low-fidelity wireframe, competitor screenshot, or inspiration design that you want the Designer to follow. To do so, simply click the image attachment button below the prompt field and upload your image. The AI will analyze the layout, visual hierarchy, and structure, then transform it into a clean, fully editable multi-screen design. For example, you might upload a rough wireframe of a food delivery app showing a home screen with a search bar, restaurant cards, and a bottom navigation bar. Along with the image, you can add a prompt such as "Using the attached screenshot as a reference, convert this wireframe into a modern food delivery app design." ## FAQ[​](/designer/prompting.md#faq "Direct link to FAQ") Are charts from Designer converted into FlutterFlow chart widgets? Yes. Bar charts, line charts, and pie charts created in the Designer are automatically converted into fully functional FlutterFlow chart widgets. ![ff-designer-chart-conversion](/assets/images/ff-designer-chart-conversion-fa4fa0a5bbaffd262bbff76cbf48c1c3.avif) --- # Quickstart FF Designer lets you generate complete app designs from simple text prompts. Just describe what you want to build, explore different style directions, and the Designer will create a full set of screens for you. From there, you can refine the design using AI or manual editing and export it directly to continue building. Ready to design your app? Simply: 1. Open FF Designer and click the prompt bar that says **Describe your app** 2. Enter your app idea and press **Enter** to generate designs 3. Browse style variants and click **Use This Style** 4. Review the generated screens in the canvas 5. Edit using AI or manually adjust elements and properties 6. Export your design to FlutterFlow, PNG, or Agent Prompt The Designer generates complete app screens in seconds, giving you a strong starting point that you can quickly refine and turn into a real app. ## Sample Prompts[​](/designer/quickstart.md#sample-prompts "Direct link to Sample Prompts") Here are some prompts you can try in FF Designer to quickly generate complete app designs. ### Personal Finance Tracker[​](/designer/quickstart.md#personal-finance-tracker "Direct link to Personal Finance Tracker") **Prompt** Design a personal finance app that helps users track expenses and manage budgets. Show a dashboard with total balance, recent transactions, and spending categories. Include detailed views for transaction history and budget insights. Make the UI clean, modern, and easy to understand. ![finance-app.avif](/assets/images/finance-app-8ffa78c3f7319b18a7e7762a8e221bdf.avif) ### Fitness Workout App[​](/designer/quickstart.md#fitness-workout-app "Direct link to Fitness Workout App") **Prompt** Create a fitness app with a home screen showing daily workouts, progress stats, and streaks. Include workout detail screens with exercises, sets, and timers. Make the design energetic, modern, and easy to follow. ![fitness-tracking-app.avif](/assets/images/fitness-tracking-app-bb5432071c0bf822fb2e5e325f0b1753.avif) ### Food Delivery App[​](/designer/quickstart.md#food-delivery-app "Direct link to Food Delivery App") **Prompt** Design a food delivery app with a home screen showing nearby restaurants, categories, and featured items. Include restaurant detail pages, menu listings, and a checkout flow. Make the UI modern, colorful, and easy to navigate. ![food-delivery-app.avif](/assets/images/food-delivery-app-2d46a90c637607c3c9370ed25f2929cc.avif) --- # Slides **Slides** turns a FlutterFlow Designer project into a presentation deck. Instead of designing phone, tablet, or desktop screens, each frame becomes a 16:9 slide. You get speaker notes, a real present-with-presenter-view mode, and two-way PowerPoint support: import an existing `.pptx` to edit, or export your deck back out to `.pptx`. ## Set up a slide deck[​](/designer/slides.md#set-up-a-slide-deck "Direct link to Set up a slide deck") 1. Switch the project's device type to **Slides**. 2. Frames become fixed presentation sizes: **1280×720** (720p) or **1920×1080** (1080p). 3. Describe the deck you want in the prompt textbox (your topic, key points, and how many slides) and let Designer generate it. 4. Once the deck is generated, refine each slide manually, just like any other design project. ### Speaker notes[​](/designer/slides.md#speaker-notes "Direct link to Speaker notes") Each slide gets its own **speaker notes** field in the right-hand panel. These notes show up in presenter mode and travel with PowerPoint import and export. ## Present the slideshow[​](/designer/slides.md#present-the-slideshow "Direct link to Present the slideshow") To start, click the **Present** button in the top bar (only visible on Slides projects), or use the present keyboard shortcut. You get a two-window setup: * A full-screen **audience view**: clean, black background, showing exactly what the room sees. * A separate **presenter window** showing the current slide, a preview of the next slide, your speaker notes, an elapsed-time timer, and prev/next controls. ### Navigation shortcuts[​](/designer/slides.md#navigation-shortcuts "Direct link to Navigation shortcuts") | Action | Keys | | -------- | ---------------------------------------------------------- | | Next | `→` · `↓` · `Space` · `Page Down` · or click/tap the slide | | Previous | `←` · `↑` · `Page Up` | | Exit | `Esc` | tip You can keep editing slides while presenting, and changes sync live into the presentation. When you exit, the canvas jumps back to whatever slide you ended on. ## Export to PowerPoint[​](/designer/slides.md#export-to-powerpoint "Direct link to Export to PowerPoint") In the left panel, choose **Export to PowerPoint**, or use the **Export presentation (.pptx)** button in the right panel. The result is a real, editable PowerPoint file: text stays as editable text and shapes stay as shapes (not flattened images) wherever possible. Charts export as native charts; icons that can't be represented natively are rendered as images. --- # Workspace The workspace is organized into panels that work together to provide a complete design experience. ![ff-designer.avif](/assets/images/ff-designer-36dc528ec8066b359fded4a387a0dc67.avif) * **Frames Panel**: Displays all screens of the app and allows quick navigation between them. * **Components Panel**: Create reusable UI elements. * **Theme Panel**: Make global theme customization. * **Layers Panel**: Shows the hierarchical structure of widgets within the selected screen. * **Canvas Area**: Visual preview of all screens in storyboard layout. * **Undo/Redo Controls**: Quickly revert or reapply recent design changes. * **Light/Dark Mode Toggle**: Switch between light and dark preview modes to instantly see how your design adapts across themes. * **Zoom Controls**: Adjust zoom level for better overview or detailed editing. * **Share Button**: Share the current design or collaborate with others. * **Properties Panel**: Edit properties of the selected widget such as layout, content, and styling. * **Prompt Bar**: Use AI commands to describe changes and modify the selected screen or widget. --- # Push to GitHub Repo This guide provides instructions on how to connect your FlutterFlow project to a GitHub repository and manage custom code. ## Connect a GitHub repo[​](/exporting/push-to-github.md#connect-a-github-repo "Direct link to Connect a GitHub repo") In this section, we'll learn how to connect your FlutterFlow project to a GitHub repository. This includes creating a new repository, installing the FlutterFlow GitHub App, and pushing your code to the repository. Here’s how you do it: 1. First, go to your GitHub account and create a new repository. [Sharing a Project with a User](https://demo.arcade.software/UhBD10h3wufXyozCBFhK?embed\&show_copy_link=true) 2. Once the repository is created, install the [FlutterFlow GitHub App](https://github.com/apps/flutterflow-github-app) in your GitHub account. [Sharing a Project with a User](https://demo.arcade.software/bxvvWOrBV7RFzfa2lEDP?embed\&show_copy_link=true) 3. You can now push your code to the repository. [Sharing a Project with a User](https://demo.arcade.software/f6L33Z7nNg7QNKeWQMWg?embed\&show_copy_link=true) tip * FlutterFlow always pushes changes to a branch named `flutterflow`. Avoid making direct changes to this branch, as your changes will be overwritten by the next push from FlutterFlow. * If you need to modify the code, make changes in a separate branch. Learn more about managing custom code. ## Manage Custom Code on GitHub[​](/exporting/push-to-github.md#manage-custom-code-on-github "Direct link to Manage Custom Code on GitHub") Writing custom code allows you to add features that are not supported by FlutterFlow's current functionality. This section outlines how you can manage custom code using GitHub to prevent FlutterFlow from overriding it. ![manage-custom-code](/assets/images/manage-custom-code-b95630fd004549dd5a784f05337c1c79.avif) The diagram illustrates the flow or process of managing code in GitHub. This process allows you to leverage all the features from FlutterFlow and deploy your app with a custom code. Here's a step-by-step explanation: ### 1. Connect FlutterFlow to GitHub[​](/exporting/push-to-github.md#1-connect-flutterflow-to-github "Direct link to 1. Connect FlutterFlow to GitHub") First, set up the connection between your FlutterFlow project and GitHub repository. [Follow these steps](/exporting/push-to-github.md#connect-a-github-repo) if you haven’t already done so. ### 2. Establish a custom code branch[​](/exporting/push-to-github.md#2-establish-a-custom-code-branch "Direct link to 2. Establish a custom code branch") After pushing your FlutterFlow code to GitHub, it lands in the `flutterflow` branch. To safeguard your custom modifications from being overwritten by future pushes, create a `develop` branch. 1. Navigate to your GitHub repository. 2. Switch from `main` to `flutterflow` in the branch dropdown. 3. In the branch creation field, enter `develop` and create the branch from `flutterflow`. ### 3. Add custom code[​](/exporting/push-to-github.md#3-add-custom-code "Direct link to 3. Add custom code") Once your `develop` branch is ready, [clone the repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) to your local machine. Open the project in your IDE, switch to the `develop` branch, and add your custom code. After making changes, commit and push them back to the `develop` branch. ### 4. Merge changes from FlutterFlow[​](/exporting/push-to-github.md#4-merge-changes-from-flutterflow "Direct link to 4. Merge changes from FlutterFlow") To integrate the latest updates of your FlutterFlow project into your custom code: 1. Create a pull request on GitHub from `flutterflow` to `develop`. 2. Review and merge the changes, resolving any conflicts if necessary. ### 5. Final testing and deployment[​](/exporting/push-to-github.md#5-final-testing-and-deployment "Direct link to 5. Final testing and deployment") After testing the changes in `develop`: 1. Merge `develop` into `main` via a new pull request on GitHub. 2. Once reviewed and merged, deploy your application from the `main` branch using FlutterFlow’s deployment features. tip Also, see how you can download the code using [**FlutterFlow CLI**](/flutterflow-cli.md) and [**Local Run**](/testing/local-run.md). --- # FlutterFlow CLI The [FlutterFlow CLI](https://pub.dev/packages/flutterflow_cli) lets you manage FlutterFlow projects from the command line. You can create new projects, modify existing ones using AI agents, and download them to your local machine. ## Installation[​](/flutterflow-cli.md#installation "Direct link to Installation") To use the FlutterFlow CLI, you first need to install it globally using Dart's package manager with the following command: ``` dart pub global activate flutterflow_cli ``` ### Get API Token[​](/flutterflow-cli.md#get-api-token "Direct link to Get API Token") To use the CLI, you'll need to create an API token and use it in your requests. See the documentation [here on how to generate an API token.](/accounts-billing/account-management.md#how-do-i-generate-an-api-token) Building with Codex? Install the FlutterFlow plugin for secure onboarding and a guided create, edit, and export workflow. See [Build with Codex](/flutterflow-cli/codex.md). ## FAQ[​](/flutterflow-cli.md#faq "Direct link to FAQ") I am getting an error as FormatException: Missing argument for… This error likely indicates that you haven't correctly entered the command option along with its value. Double-check that all required information has been entered. If everything is correct and you're still encountering the error, it might be due to using an outdated version of the FlutterFlow CLI. To resolve this, you can update to the latest version by running the installation command: ``` dart pub global activate flutterflow_cli ``` This should update the CLI and fix the issue. --- # Build with AI Agents The [FlutterFlow CLI](https://pub.dev/packages/flutterflow_cli) lets you create and edit FlutterFlow apps from the terminal using your own AI coding agent, such as Claude Code, Gemini CLI, Codex, or any MCP-compatible client. You describe what you want in plain English, the agent plans and applies the changes, and the result lands as a real FlutterFlow project you can open in the visual builder. A FlutterFlow project is the source of truth. The CLI is how you create or edit it from your local workspace. If you use Codex, see [Build with Codex](/flutterflow-cli/codex.md) for the FlutterFlow plugin installation, secure authentication flow, and Codex-specific troubleshooting. ![flutterflow-cli-ff-builder-using-same-ff-app](/assets/images/flutterflow-ff-builder-using-same-ff-app-ec46b7dc9dab6a03f2766eca594ffa83.avif) ## Architecture[​](/flutterflow-cli/build.md#architecture "Direct link to Architecture") `flutterflow ai init` creates a local **workspace** - a folder pre-configured with an MCP config file pointing at the FlutterFlow MCP server. When you launch your AI agent inside that folder, it discovers the MCP server and gains a set of tools that talk to FlutterFlow's cloud: 1. You prompt the agent. 2. The agent plans changes and calls the MCP server's tools. 3. The MCP server applies those changes to your FlutterFlow project. 4. You verify the result in the FlutterFlow visual builder. The workspace is just a folder on your disk. The actual project lives in FlutterFlow server. What is MCP? The [**Model Context Protocol**](https://modelcontextprotocol.io) is an open standard that lets AI agents call external tools. The FlutterFlow AI MCP server exposes FlutterFlow's project APIs to your agent so it can read and modify your project on your behalf. Remember * **FlutterFlow CLI is not a replacement for the visual builder.** FlutterFlow is still faster for most visual work. FlutterFlow CLI is for precision, repeatability, and automation. * **FlutterFlow CLI doesn't execute your app.** It produces a FlutterFlow project, which you can test and run inside the FlutterFlow visual builder. Prerequisites Before you start, make sure you have: * **FlutterFlow CLI installed.** See [**Installation**](/flutterflow-cli.md). * **A FlutterFlow API key.** See [**generating an API token**](/accounts-billing/account-management.md#how-do-i-generate-an-api-token). * **An MCP-compatible AI agent installed locally**, such as [**Claude Code**](https://www.claude.com/product/claude-code), [**Gemini CLI**](https://github.com/google-gemini/gemini-cli), or [**Codex**](https://github.com/openai/codex). * **A FlutterFlow project ID** (only if you're editing an existing project). Using Claude Code? The [**FlutterFlow plugin for Claude Code**](/flutterflow-cli/claude-code.md) can handle the CLI install and API key setup for you, as an alternative to the manual setup below. ## Setup Workspace[​](/flutterflow-cli/build.md#setup-workspace "Direct link to Setup Workspace") Open your terminal in the folder where you want the workspace to live, then run: ``` flutterflow ai init ``` By default, `flutterflow ai init` targets the production FlutterFlow environment. To initialize a workspace against a non-production environment, pass the environment explicitly: ``` flutterflow ai init --env beta flutterflow ai init --env enterprise-india ``` This launches an interactive setup wizard. Walk through the prompts: 1. **Workspace name.** A short, lowercase name with no spaces. This becomes the folder name for your project. ``` Workspace name Directory to scaffold the FlutterFlow AI workspace in. > mindfly ``` 2. **Existing project ID.** Press **Enter** with no input to create a new app, or paste an existing project ID to bind the workspace to it. ``` Existing project ID to edit (press Enter to create a new app) > ``` 3. **FlutterFlow API key.** Paste your API key and press **Enter**. Input is masked. 4. **Register MCP server with detected coding CLIs.** The wizard scans your `PATH` and offers to register the FlutterFlow AI MCP server with each agent it finds (Claude Code, Gemini CLI, Codex). Answer `Y` (default) for each one you plan to use. ``` Register FlutterFlow AI MCP server with coding CLIs Detected: claude, gemini, codex Register with claude? [Y/n] ``` 5. **Confirm.** The wizard prints a summary. Review it and press **Enter** (default `Y`) to proceed. ``` Ready to create: Workspace: mindfly Project ID: (none, unlinked) API key: set (***abcd) Base URL: https://api.flutterflow.io (built-in for prod) MCP CLIs: claude, gemini, codex Proceed? [Y/n] ``` When the wizard finishes, you'll have a workspace folder ready for your agent. Depending on which CLIs you registered, the folder will contain one or more of: * `.mcp.json` for Claude Code * `.gemini/settings.json` for Gemini CLI * `.codex/config.toml` for Codex Each file points the corresponding agent at the FlutterFlow AI MCP server. ## Launch your Agent[​](/flutterflow-cli/build.md#launch-your-agent "Direct link to Launch your Agent") Move into the workspace and start your agent. The example below uses Claude Code; the same pattern applies to any agent you registered in the wizard. `cd` into the workspace and launch the agent's CLI. Codex users who installed the FlutterFlow plugin should start a new Codex task from this workspace. See [Build with Codex](/flutterflow-cli/codex.md#choose-your-codex-surface). ``` cd mindfly claude ``` The first time the agent opens the workspace, it detects the new MCP server and asks you to approve it. The exact prompt varies by agent. Claude Code's looks like this: ``` New MCP server found in .mcp.json: flutterflow_ai MCP servers may execute code or access system resources. All tool calls require approval. > 1. Use this and all future MCP servers in this project 2. Use this MCP server 3. Continue without using this MCP server ``` Choose **option 1** to approve the FlutterFlow AI MCP server (and any others added to this workspace later) without being asked again. > **Why approve?** Without the MCP server, the agent can edit local files but can't push changes to your FlutterFlow project. With it approved, the agent has the same tools you'd run yourself from the CLI. ## Generate a New App[​](/flutterflow-cli/build.md#generate-a-new-app "Direct link to Generate a New App") With the agent connected, describe the app you want at the prompt: ``` > create a minimalist meditation app ``` Phrase it however you like: `a recipe-sharing app with a social feed`, `a habit tracker with streaks`, `a tip calculator for restaurants`. The agent plans the app, generates the changes, pushes them to FlutterFlow through the MCP server, and reports back. Open FlutterFlow in your browser and navigate to the project. The generated app will be reflected in the visual builder. From there you can keep refining visually or send another prompt to the agent. Once the app exists, the workspace is bound to it. Follow-up prompts in the same session are treated as edits, not new generations, you'll see the agent acknowledge the switch with something like: ``` The project is bound, so I'll switch to edit mode. Let me check the workspace and read the edit template. ``` From that point on, the same rules apply as when [editing an existing project](/flutterflow-cli/build.md#edit-an-existing-project) - concurrency, branches, scope, and refreshing context. ## Edit an Existing Project[​](/flutterflow-cli/build.md#edit-an-existing-project "Direct link to Edit an Existing Project") Prerequisite Have your **project ID** ready. Open the project in the FlutterFlow editor. The project ID is the path segment after `/project/` in the URL. Editing an existing project follows the same flow as [creating a new one](/flutterflow-cli/build.md#setup-workspace). You run `flutterflow ai init` to scaffold a workspace, then drive changes from your agent. The only difference is one step in the wizard: when it asks for an **existing project ID**, paste yours instead of pressing Enter: ``` Existing project ID to edit (press Enter to create a new app) > mindfly-c9lbgr ``` The workspace is now bound to that project. `cd` into the workspace folder, [launch your agent](/flutterflow-cli/build.md#launch-your-agent), and describe the changes you want, such as "add a profile screen", "switch the primary color to teal", or "wire up the login form to Firebase Auth". The agent reads the current project, plans the change, and pushes it through the MCP server. Open FlutterFlow in your browser to verify. ### Copy AI Selector[​](/flutterflow-cli/build.md#copy-ai-selector "Direct link to Copy AI Selector") You can right-click any widget in the builder and select **Copy AI Selector** when you want the agent to update a specific widget in your app. This copies a precise location for the selected widget, which you can paste into your prompt so the agent knows exactly which widget to inspect or modify. This is helpful when a page has repeated widgets, nested components, or similar labels. Instead of describing the widget only by its position or text, you can give the agent the copied selector value and ask for a targeted change, such as updating that widget's style, action, visibility, or data binding. ### Concurrent Edits with Builder[​](/flutterflow-cli/build.md#concurrent-edits-with-builder "Direct link to Concurrent Edits with Builder") You can edit visually while an agent is working, but writes use **optimistic concurrency**: when the agent pushes, the server checks the project's last-modified timestamp against the agent's snapshot. If anyone else (you in the visual builder, a teammate, or another agent) modified the project in between, the push is rejected. The agent will re-read the latest state and retry. This may also mean re-planning if your change conflicts with what it was about to do. So nothing gets silently overwritten, but expect occasional retries when you and the agent are editing the same project at once. ### Agent Edit Scope[​](/flutterflow-cli/build.md#agent-edit-scope "Direct link to Agent Edit Scope") **In scope** * Pages, components, app state, theme, navigation, action blocks, app events * Custom functions, actions, widgets, classes, and enums * API endpoints, queries, custom data types and enums * Pub and library dependencies, design tokens, GenUI catalog, Firebase Auth wiring **Out of scope** * Anything outside the FlutterFlow project itself, such as running the app, deploying it, creating Firebase projects, managing secrets, or handling App Store submissions. ### Refreshing Stale Context[​](/flutterflow-cli/build.md#refreshing-stale-context "Direct link to Refreshing Stale Context") If you've made visual edits since the agent last read the project, the agent's local snapshot is stale. Two ways to fix it: * **Ask the agent to refresh.** Most agents call the [`refresh-context`](/flutterflow-cli/build.md#mcp-tools) tool on their own when they detect drift, but you can prompt explicitly: "refresh the project context." * **Run it from the CLI.** `flutterflow ai context-check` reports whether the local snapshot is behind, and `flutterflow ai refresh-context ` pulls the latest. See [MCP tools](/flutterflow-cli/build.md#mcp-tools) for the full command list. ## Live Sessions[​](/flutterflow-cli/build.md#live-sessions "Direct link to Live Sessions") Live Sessions let your AI agent apply changes to a running FlutterFlow app and display those updates directly on the connected device. This is useful when you want to iterate quickly. You can ask the agent to update screens, fix issues, inspect logs, trigger hot reloads or hot restarts, capture screenshots, and then immediately review the results on your running devices. To use Live Sessions, run your app from the FlutterFlow desktop app on a connected device or simulator, then activate your agent. Once the live session starts, confirm its status in the desktop app, ask the agent to make changes, and review those updates as they appear in the running app. info Keep the desktop app and the running app session open for as long as you want live updates to continue. ## Branches and Rollback[​](/flutterflow-cli/build.md#branches-and-rollback "Direct link to Branches and Rollback") The CLI can point to any branch of a FlutterFlow project. Since each branch is accessed through its own URL, it has its own project ID. To work on a specific branch, open it in the FlutterFlow editor, copy the project ID from the URL, and paste it when `flutterflow ai init` prompts for an existing project ID. To roll back, use FlutterFlow's project version history in the visual builder, which is the same mechanism used for visual edits. Each agent push lands as a commit there with whatever commit message the agent supplied. ## Switching Projects[​](/flutterflow-cli/build.md#switching-projects "Direct link to Switching Projects") A workspace is bound to one project. To work on a different project, run `flutterflow ai init` in a **new** folder and link it to the new project ID. `init` refuses to run in a non-empty directory, so it won't re-bind an existing workspace. ## MCP tools[​](/flutterflow-cli/build.md#mcp-tools "Direct link to MCP tools") Run these from inside a FlutterFlow AI workspace. Your agent calls them via the MCP server; you can also run them directly in the terminal. | Category | Command | What it does | | ------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------- | | **Build** | `run` | Apply changes to your FlutterFlow project. | | | `validate` | Dry-run a change without pushing it. | | **Explore** | `inspect` | Whole-project summary or a scoped view of structure. | | | `resources` | List reusable project and library resources. | | | `search` | Search the project for a name or identifier. | | | `status` | Show workspace and project state. | | **AI integration** | `mcp` | Register the FlutterFlow MCP server with Claude Code, Codex, Gemini CLI, Cursor, Copilot, and other MCP-aware clients. | | **Plan & audit** | `plan` | Capture intent before a run. | | | `trace` | Replay a prior run. | | | `history` | List prior commands and outcomes. | | **Diagnose** | `doctor` | Check for common workspace problems. | | | `context-check` | Report whether the local snapshot is behind the live project. | | | `precache` | Pre-load project context. | | **Stay current** | `upgrade` | Update the FlutterFlow CLI tooling. | | | `refresh-workspace` | Refresh the workspace's local config. | | | `refresh-context` | Pull the latest project state into the local snapshot. | | **Learn** | `docs [topic]` | Open FlutterFlow AI documentation for a topic. | Run `flutterflow ai --help` from inside a workspace for the full command list and per-command flags. When the agent invokes a command via MCP, every call is subject to your agent's approval rules. --- # Claude Code Plugin The **FlutterFlow plugin for [Claude Code](https://www.claude.com/product/claude-code)** packages FlutterFlow's agentic building experience as a Claude Code plugin, and works in both the **Claude Code terminal (CLI)** and the **Claude Code desktop app**. Once installed, it: * **Installs the FlutterFlow CLI automatically** when a session starts. If the Dart SDK is missing, it points you to the installer instead of failing. * **Stores your API key securely** by reading it once from your clipboard, so the key never appears in the chat. * **Adds a guided build skill** (`/flutterflow:build`) that sets up a workspace, then follows an orient → validate → apply workflow for every change. The plugin drives the same [FlutterFlow CLI](https://pub.dev/packages/flutterflow_cli) described in [Build with AI Agents](/flutterflow-cli/build.md). If you use a different agent (such as Gemini CLI or Codex), or prefer to install the CLI and configure the MCP server yourself, follow that page instead. Both paths produce the same result. Open source The plugin is open source. Browse the code, releases, and issues on GitHub at [FlutterFlow/flutterflow-claude](https://github.com/FlutterFlow/flutterflow-claude). Prerequisites Before you start, make sure you have: * **Claude Code** installed and signed in (the terminal CLI, the desktop app, or both). Get them from [claude.com](https://www.claude.com/product/claude-code). * **Git**, which Claude Code uses to install the plugin. * **Dart**, bundled with [Flutter](https://docs.flutter.dev/get-started/install), which the FlutterFlow CLI requires. If it's missing, the plugin detects it and links you to the installer. Platform support macOS and Linux work out of the box. On **Windows**, the plugin's automatic setup requires a `bash` on your PATH (from Git Bash or WSL). Without one, install the CLI manually (see [Installation](/flutterflow-cli.md)) and let `flutterflow ai` prompt for your API key. The CLI itself supports Windows end-to-end. ## Choose Your Claude Code Surface[​](/flutterflow-cli/claude-code.md#choose-your-claude-code-surface "Direct link to Choose Your Claude Code Surface") The plugin behaves the same in the Claude Code terminal and the Claude Code desktop app. Only plugin installation and session startup differ. Both surfaces read the same Claude Code configuration, so installing the plugin once makes it available in both. ### Claude Code Terminal (CLI)[​](/flutterflow-cli/claude-code.md#claude-code-terminal-cli "Direct link to Claude Code Terminal (CLI)") Use this path when you run `claude` from a terminal. 1. Install the plugin with two slash commands inside a Claude Code session: ``` /plugin marketplace add FlutterFlow/flutterflow-claude /plugin install flutterflow@flutterflow ``` Or run the equivalent commands from your shell: ``` claude plugin marketplace add FlutterFlow/flutterflow-claude claude plugin install flutterflow@flutterflow ``` 2. Start a new session from the folder where you want to work. This can be an existing FlutterFlow AI workspace or the parent folder where a new one should be created: ``` cd /path/to/your/projects claude ``` 3. Describe the FlutterFlow outcome you want at the prompt. If Claude Code was already running when you installed the plugin, start a new session so the build skill and automatic setup load. note [`FlutterFlow/flutterflow-claude`](https://github.com/FlutterFlow/flutterflow-claude) is the GitHub repository the plugin installs from; `flutterflow` after the `@` is the marketplace name. 📸 **Image placeholder: terminal install.** Screenshot of a Claude Code terminal session after running the two `/plugin` commands, showing the marketplace added and the confirmation that the `flutterflow` plugin is installed and enabled. ### Claude Code Desktop App[​](/flutterflow-cli/claude-code.md#claude-code-desktop-app "Direct link to Claude Code Desktop App") Use this path when you work in the [Claude Code desktop app](https://www.claude.com/product/claude-code) on macOS or Windows. Installation happens entirely in the app, with no terminal commands required. 1. Open the account menu in the lower-left corner of the app and select **Settings**. ![Open Settings from the account menu in the Claude desktop app.](/assets/images/01-open-settings-276e42c1f7fc635ec4155a37f6e3ed4e.png) 2. Under **Customize**, select **Plugins**. ![Open the Plugins settings page under Customize.](/assets/images/02-plugins-settings-1b39f38ce6bb7f1b5bd129f1d58b4f5f.png) 3. Select **Add**, then **Add marketplace**. ![Open the Add menu and select Add marketplace.](/assets/images/03-add-marketplace-7f3d33fdb3eb99750740509dd4f6d5a1.png) 4. In **URL**, enter `https://github.com/FlutterFlow/flutterflow-claude`, then select **Use ""**. ![Add the FlutterFlow plugin marketplace by its GitHub URL.](/assets/images/04-marketplace-url-2b69a165b3c101dda6efcd21454771e4.png) 5. In the plugin directory, select **Code**. Under the `flutterflow` marketplace, find the **FlutterFlow** plugin and select the **+** button on its card. ![Find the FlutterFlow plugin in the Code directory and select its plus button.](/assets/images/05-install-flutterflow-084f2a256360b4d429ba250be3d896c9.png) 6. Wait for the **FlutterFlow is installed and ready to use.** confirmation. ![Confirmation that FlutterFlow is installed and ready to use.](/assets/images/06-installed-toast-4202bd5fb9f4859bdfea6407a7e6cc51.png) 7. Close Settings, open the **Code** tab, and start a new session. Choose **Local** as the environment, select the folder where you want to work, and describe the FlutterFlow outcome you want in the prompt box. The folder can be an existing FlutterFlow AI workspace or the parent folder where a new one should be created. Desktop app specifics * Plugins are available in the desktop app's **Local** and **SSH** sessions, not in cloud or WSL sessions. * The desktop app reads your `PATH` when it launches. If Dart or the FlutterFlow CLI was installed while the app was open and a session can't find them, quit and reopen the app. ## Set Up Your API Key[​](/flutterflow-cli/claude-code.md#set-up-your-api-key "Direct link to Set Up Your API Key") The plugin authenticates with your FlutterFlow API key, and key setup works the same on both surfaces. Until a key is configured, the plugin prints a reminder when a session starts: ``` [flutterflow] No FlutterFlow API key configured. [flutterflow] 1) Copy an API key from https://app.flutterflow.io/account [flutterflow] 2) Come back and tell Claude: "I copied my FlutterFlow API key" ``` **Never paste the API key into the chat**, because conversations are logged and retained. Instead, hand the key over through your clipboard: 1. Open your [FlutterFlow account page](https://app.flutterflow.io/account) and copy your API key. If you don't have one yet, see [generating an API token](/accounts-billing/account-management.md#how-do-i-generate-an-api-token). 2. Come back to Claude Code and say: **"I copied my FlutterFlow API key"**. Claude runs a script bundled with the plugin that reads the clipboard once, validates the key, stores it in `~/.config/flutterflow/claude-env.sh` with owner-only permissions, and clears the clipboard. The key itself never enters the conversation. If validation fails (for example, you copied something else in the meantime), Claude asks you to copy the key again and retry. Clipboard history managers Tools like Raycast, Alfred, Windows clipboard history (Win + V), and Apple's Universal Clipboard keep their own copy of everything you copy, even after the system clipboard is cleared. If you use one, purge the key from its history after setup. Working over **SSH or in a headless environment**? The clipboard hand-off isn't available there, so Claude offers a one-line terminal command that prompts for the key with hidden input. Alternatively, run `flutterflow ai` in your own terminal and let its setup wizard prompt for the key. note Avoid entering the key through `/plugin configure` for now, because its input dialog has known issues in current Claude Code versions ([#73530](https://github.com/anthropics/claude-code/issues/73530), [#62442](https://github.com/anthropics/claude-code/issues/62442)). The clipboard hand-off above is the recommended path. ## Build with FlutterFlow[​](/flutterflow-cli/claude-code.md#build-with-flutterflow "Direct link to Build with FlutterFlow") Start a new Claude Code session in your working folder: `claude` in the terminal, or a **Local** session in the desktop app. On the first session after setup, the plugin installs the FlutterFlow CLI automatically and prints a notice while it works: ``` [flutterflow] Installing the FlutterFlow CLI… ``` After that, describe what you want to build. The build skill triggers on FlutterFlow tasks, or you can invoke it explicitly with `/flutterflow:build`: ``` > Create a FlutterFlow app for tracking daily habits > Add a profile page with an avatar, display name, and a settings list > Change the primary color to teal and update the home page title ``` Under the hood, the skill follows a disciplined workflow: 1. **Workspace.** Ensures you have a FlutterFlow AI workspace (`flutterflow ai init`), bound either to a new app or to an existing project. If you're editing an existing project but don't have its ID handy, Claude lists your account's projects and asks which one to use. 2. **Orient.** Reads the project before changing it, using commands like `status`, `inspect`, `resources`, and `search`. 3. **Author → validate → apply.** Writes changes as declarative Dart files, checks them with `flutterflow ai validate` first, and only then applies them with `flutterflow ai run`. Each applied change lands as a commit in your FlutterFlow project. Open the project in the visual builder to verify the result, then keep refining, either visually or with follow-up prompts. MCP server approval Workspaces created by `flutterflow ai init` also register the FlutterFlow AI MCP server with Claude Code via a project-scoped `.mcp.json`. If Claude Code asks to approve a new MCP server named `flutterflow_ai` when you open a session in the workspace, approve it; the build skill and the MCP server drive the same project state. See [Build with AI Agents](/flutterflow-cli/build.md#launch-your-agent) for details. Everything works from the terminal too The plugin drives the standard `flutterflow ai` CLI, so the same commands work in any terminal, for example `flutterflow ai init my-app`, `flutterflow ai status`, `flutterflow ai validate`, and `flutterflow ai run`. See the [MCP tools reference](/flutterflow-cli/build.md#mcp-tools) for the full command list. ## Managing Your API Key[​](/flutterflow-cli/claude-code.md#managing-your-api-key "Direct link to Managing Your API Key") **Rotate the key.** Create a new API key on your [account page](https://app.flutterflow.io/account), copy it, and tell Claude *"I copied my FlutterFlow API key"* again. The stored key is replaced and used for subsequent commands. **Remove the key entirely.** Delete the stored key file and clear the CLI's cached credentials: ``` rm -f ~/.config/flutterflow/claude-env.sh flutterflow ai logout --all ``` **If a key ever appears in the chat**, treat it as compromised: delete it on your [account page](https://app.flutterflow.io/account) and create a new one. ## Update the Plugin[​](/flutterflow-cli/claude-code.md#update-the-plugin "Direct link to Update the Plugin") Plugin installs track the latest version of the FlutterFlow marketplace. To update, run this from a terminal (or `/plugin marketplace update flutterflow` inside a terminal session), then start a new session. The update applies to the terminal and the desktop app alike: ``` claude plugin marketplace update flutterflow ``` ## Troubleshooting[​](/flutterflow-cli/claude-code.md#troubleshooting "Direct link to Troubleshooting") The CLI installed, but the flutterflow command isn't found The CLI installs to Dart's pub-cache, which may not be on your PATH. Add it to your shell profile and restart the session: ``` echo 'export PATH="$HOME/.pub-cache/bin:$PATH"' >> ~/.zshrc ``` The CLI didn't install because Dart is missing The FlutterFlow CLI requires the Dart SDK, which ships with Flutter. Install [Flutter](https://docs.flutter.dev/get-started/install) (recommended) or [Dart on its own](https://dart.dev/get-dart), then start a new Claude Code session. The plugin retries the install automatically. The desktop app can't find the dart or flutterflow command The desktop app reads your PATH once, when it launches. If Dart or the FlutterFlow CLI was installed while the app was open, quit and reopen the desktop app. On macOS, make sure your shell profile (for example `~/.zshrc`) exports the path; on Windows, add it to your user PATH. Nothing happens on session start (Windows) The plugin's automatic setup runs as a bash script, so it needs a bash on your PATH. Install Git Bash or WSL. Alternatively, install the CLI manually with `dart pub global activate flutterflow_cli` and let `flutterflow ai` prompt for your API key the first time you run it. FlutterFlow rejected my API key (401 error) The key is invalid or was revoked. Copy a fresh key from your [account page](https://app.flutterflow.io/account) and tell Claude **"I copied my FlutterFlow API key"**. Don't paste the key into the chat. --- # Build with Codex The [FlutterFlow plugin for Codex](https://github.com/FlutterFlow/flutterflow-codex) lets you create and edit FlutterFlow projects by describing the outcome you want in plain language. The plugin guides Codex through FlutterFlow authentication, workspace setup, project inspection, branch-aware editing, validation, and code export. [`FlutterFlow/flutterflow-codex`Open GitHub ↗](https://github.com/FlutterFlow/flutterflow-codex) The plugin uses the FlutterFlow CLI and each workspace's project-scoped MCP server. FlutterFlow remains the source of truth for the project, so you can open the result in the visual builder and continue editing there. For the agent-independent architecture and full FlutterFlow AI command list, see [Build with AI Agents](/flutterflow-cli/build.md). ## Choose your Codex surface[​](/flutterflow-cli/codex.md#choose-your-codex-surface "Direct link to Choose your Codex surface") The FlutterFlow plugin works in both Codex CLI and Codex inside the [ChatGPT desktop app](https://learn.chatgpt.com/docs/app). The FlutterFlow workflow is the same after installation, but plugin discovery, installation, requirements, and task startup differ between the two surfaces. Follow only the section for the surface you use. FlutterFlow is currently distributed from the [FlutterFlow Codex GitHub repository](https://github.com/FlutterFlow/flutterflow-codex), which contains a single-plugin Codex marketplace. In Codex CLI, you add the GitHub URL and install the plugin with commands. In the ChatGPT desktop app, you open **Plugins** from the main sidebar and add the GitHub marketplace from the **Create** menu. ### Codex CLI[​](/flutterflow-cli/codex.md#codex-cli "Direct link to Codex CLI") Use this path when you run Codex from a terminal. #### Terminal requirements[​](/flutterflow-cli/codex.md#terminal-requirements "Direct link to Terminal requirements") Before you begin, make sure you have: * [Codex CLI](https://learn.chatgpt.com/docs/codex/cli) installed. * [Dart and Flutter](https://docs.flutter.dev/get-started/install) available on your `PATH`. * A FlutterFlow API token available from your [FlutterFlow account](https://app.flutterflow.io/account). You only need to copy it when the plugin asks you to authenticate. Install or update the FlutterFlow CLI: ``` dart pub global activate flutterflow_cli ``` #### Install in Codex CLI[​](/flutterflow-cli/codex.md#install-in-codex-cli "Direct link to Install in Codex CLI") 1. Add the [FlutterFlow Codex GitHub marketplace](https://github.com/FlutterFlow/flutterflow-codex) and install the plugin: ``` codex plugin marketplace add https://github.com/FlutterFlow/flutterflow-codex --ref main codex plugin add flutterflow@flutterflow ``` 2. Start a new Codex CLI session from the folder where you want to work: ``` cd /path/to/your/projects codex ``` 3. Enter `/plugins` to open the terminal plugin browser. Confirm that `flutterflow@flutterflow` is installed and enabled. 4. Close the plugin browser and describe the FlutterFlow outcome you want at the prompt. If Codex CLI was already running when you installed or updated the plugin, exit and relaunch it so the new session loads the FlutterFlow skill. ### ChatGPT desktop app (Codex)[​](/flutterflow-cli/codex.md#chatgpt-desktop-app-codex "Direct link to ChatGPT desktop app (Codex)") Use this path when you work with Codex inside the ChatGPT desktop app. #### Desktop requirements[​](/flutterflow-cli/codex.md#desktop-requirements "Direct link to Desktop requirements") Install and open the [ChatGPT desktop app](https://chatgpt.com/download/). You do not need Codex CLI or terminal plugin commands to add the FlutterFlow marketplace in the app. You can install the plugin before configuring FlutterFlow tooling or authentication. When you start your first FlutterFlow task, the plugin checks for Dart, Flutter, and the FlutterFlow CLI, then guides you through anything that is missing. It asks for a FlutterFlow API token only when authentication is required. Use the main Plugins page Select **Plugins** from the main Codex sidebar. **Settings > Plugins** manages installed plugins, skills, apps, and MCPs, but it does not provide the **Add marketplace** action. #### Install in the ChatGPT desktop app[​](/flutterflow-cli/codex.md#install-in-the-chatgpt-desktop-app "Direct link to Install in the ChatGPT desktop app") 1. Select **Plugins** from the main Codex sidebar. ![Codex sidebar with the Plugins option](/assets/images/chatgpt-sidebar-plugins-386fecc653c5150fa4bb621d7c48ac19.png) 2. On the Plugins page, open the **Create** menu and select **Add marketplace**. ![Create menu on the Plugins page with Add marketplace selected](/assets/images/chatgpt-add-marketplace-b9da6a81befa9db25652aae350aad6e9.png) 3. Enter the [FlutterFlow Codex marketplace GitHub URL](https://github.com/FlutterFlow/flutterflow-codex): ``` https://github.com/FlutterFlow/flutterflow-codex ``` 4. Complete the marketplace setup, then open the FlutterFlow plugin's details. 5. Select the plus button on the plugin details page to install it. 6. Start a new Codex task so the FlutterFlow skill loads. 7. Choose the parent folder where Codex should create a new FlutterFlow workspace, or choose an existing FlutterFlow AI workspace if you are continuing work on a project. 8. Describe the FlutterFlow outcome you want in the task composer. After adding the GitHub marketplace, follow OpenAI's [plugin installation flow](https://learn.chatgpt.com/docs/plugins) to open the plugin details, select the plus button, and start a new task. note The plugin does not replace the FlutterFlow CLI. It gives Codex a supported workflow for driving the CLI and the workspace-specific FlutterFlow tools. ## Authenticate securely[​](/flutterflow-cli/codex.md#authenticate-securely "Direct link to Authenticate securely") API token and API key The FlutterFlow account page calls this credential an **API token**. The FlutterFlow AI workflow also refers to it as an **API key** and uses the `FF_API_KEY` environment variable. Code export and Firebase deployment use the `FLUTTERFLOW_API_TOKEN` environment variable. Authentication works the same way in Codex CLI and the ChatGPT desktop app. You do not need to paste your FlutterFlow API token into the Codex task. When Codex needs authentication, it will ask you to: 1. Open your [FlutterFlow account](https://app.flutterflow.io/account) and copy the API token. 2. Return to Codex and type only `copied`. The plugin then runs its secure clipboard hand-off. It reads the clipboard once, validates the credential format, stores it in a private configuration file with restricted permissions, and clears the live clipboard without displaying the credential. warning Never paste a FlutterFlow API token into the chat. Avoid passing it with the `--api-key` command-line option because command arguments can be visible to other processes and the CLI persists that value to credential files. Clipboard-history applications and cross-device clipboard services may retain a copy that the plugin cannot clear. Clear those histories separately if you use them. ## Create a new FlutterFlow app[​](/flutterflow-cli/codex.md#create-a-new-flutterflow-app "Direct link to Create a new FlutterFlow app") In Codex CLI or the ChatGPT desktop app, start the task from the parent folder where you want the local FlutterFlow workspace, then describe the app you want. For example: > Create a new FlutterFlow app called `habit_tracker`. Make it a minimalist habit tracker with daily check-ins, streaks, and a weekly progress view. Codex will: 1. Check FlutterFlow authentication. 2. Initialize a version-matched FlutterFlow AI workspace. 3. Read the workspace's generated instructions and typed project SDK. 4. Plan and validate the app before creating the remote FlutterFlow project. 5. Return a link to open the project in FlutterFlow. After the first successful creation, use follow-up prompts in the same workspace to edit the project rather than trying to create it again. ## Edit an existing project[​](/flutterflow-cli/codex.md#edit-an-existing-project "Direct link to Edit an existing project") If you know the project ID, include it in the prompt: > Edit my FlutterFlow project ``. Add a settings page with profile, notification, and privacy sections. You can find the project ID in its FlutterFlow editor URL: ``` https://app.flutterflow.io/project/ ``` If you do not know the project ID, ask Codex to help you choose a project. The FlutterFlow CLI provides an interactive searchable project picker: > List my FlutterFlow projects so I can choose which one to edit. Codex creates or reuses a local workspace bound to that project. Before it applies changes, it confirms the active FlutterFlow branch, inspects the current project, and runs the workspace test gate. Target a specific widget In the FlutterFlow builder, right-click a widget and select **Copy AI Selector**. Paste that selector into your prompt when you want Codex to update an exact widget instead of identifying it from its label or position. ## Export Flutter code[​](/flutterflow-cli/codex.md#export-flutter-code "Direct link to Export Flutter code") Ask Codex for the export destination and identify the project: > Export my FlutterFlow project `` to Flutter code in `./generated_app`. Code export uses the standard FlutterFlow CLI export workflow rather than the AI project-editing workflow. See [Exporting Projects](/flutterflow-cli/exporting.md) for export options, branch selection, and `.flutterflowignore` behavior. ## What the plugin checks before applying changes[​](/flutterflow-cli/codex.md#what-the-plugin-checks-before-applying-changes "Direct link to What the plugin checks before applying changes") For project edits, the plugin guides Codex through these safety checks: * Work inside an initialized workspace containing `.flutterflow/config.yaml`. * Read the workspace's version-matched `AGENTS.md` before authoring changes. * Confirm the active FlutterFlow branch and its project ID. * Inspect the current project and use its typed SDK rather than guessing names. * Run `flutterflow ai test` before applying changes. * Apply changes with an explicit commit message. * Inspect history, trace information, and project context after the operation. Validation failures do not push a change. Failures later in the create, network, conflict, push, or post-push phases may have remote effects, so Codex inspects the result before retrying an operation. ## FlutterFlow MCP in Codex[​](/flutterflow-cli/codex.md#flutterflow-mcp-in-codex "Direct link to FlutterFlow MCP in Codex") FlutterFlow MCP is project-scoped. When the CLI initializes a workspace, it can write Codex configuration to `.codex/config.toml` for that workspace's vendored FlutterFlow MCP server. Open a new Codex task from the initialized workspace after this configuration is written so the tools load. If MCP tools are unavailable, the plugin can continue through the FlutterFlow CLI instead of blocking the workflow. ## Update the plugin[​](/flutterflow-cli/codex.md#update-the-plugin "Direct link to Update the plugin") ### Update in Codex CLI[​](/flutterflow-cli/codex.md#update-in-codex-cli "Direct link to Update in Codex CLI") Refresh the FlutterFlow marketplace and reinstall the terminal plugin from the updated snapshot: ``` codex plugin marketplace upgrade flutterflow codex plugin add flutterflow@flutterflow ``` Exit and relaunch Codex CLI, then start a new task. Existing CLI sessions and tasks do not reload an updated plugin bundle. ### Update in the ChatGPT desktop app[​](/flutterflow-cli/codex.md#update-in-the-chatgpt-desktop-app "Direct link to Update in the ChatGPT desktop app") Open **Plugins** in the ChatGPT desktop app and manage the installed FlutterFlow plugin from there. After installing an updated version, start a new task; already-open tasks do not reload plugin updates. Update the FlutterFlow CLI separately: ``` dart pub global activate flutterflow_cli ``` ## Troubleshooting[​](/flutterflow-cli/codex.md#troubleshooting "Direct link to Troubleshooting") The plugin is not available in Codex CLI Enter `/plugins` and look for `flutterflow@flutterflow`. Confirm that it is installed and enabled. If you installed or updated it while Codex CLI was running, exit and relaunch Codex, then start a new task. The plugin is not available in the ChatGPT desktop app Open **Plugins** from the main Codex sidebar, not from Settings. Open the **Create** menu and select **Add marketplace**, then add the [FlutterFlow Codex GitHub marketplace](https://github.com/FlutterFlow/flutterflow-codex): ``` https://github.com/FlutterFlow/flutterflow-codex ``` Open the FlutterFlow plugin details and select the plus button to install it. Then start a new task so the FlutterFlow skill loads. The `flutterflow` command is not found Install or update the CLI: ``` dart pub global activate flutterflow_cli ``` Restart your terminal or Codex if the Dart global executable directory was added to `PATH` during installation. Authentication is missing or rejected Copy a current token from your [FlutterFlow account](https://app.flutterflow.io/account), return to Codex, and type `copied` when the plugin asks. Do not paste the token into the chat. If FlutterFlow rejects a saved credential, generate a replacement token and repeat the secure clipboard hand-off. Codex cannot find the FlutterFlow workspace Run the task from the initialized workspace directory. Its root contains: ``` .flutterflow/config.yaml ``` Do not initialize a new workspace inside a populated, unrelated directory. If a workspace already exists for the project, open that folder instead of creating another one. MCP tools do not appear Confirm that the workspace contains `.codex/config.toml`, then start a new Codex task from that workspace. Codex loads project-scoped MCP configuration at the start of a task. The visual builder changed after Codex inspected the project Ask Codex to refresh the project context before applying another edit: > Refresh the FlutterFlow project context, then re-check the planned change. FlutterFlow uses optimistic concurrency to prevent Codex from silently overwriting a newer visual-builder or teammate edit. See [Concurrent Edits with Builder](/flutterflow-cli/build.md#concurrent-edits-with-builder) for details. ## Support[​](/flutterflow-cli/codex.md#support "Direct link to Support") * Review the [FlutterFlow Codex plugin source and release notes](https://github.com/FlutterFlow/flutterflow-codex). * Report plugin bugs through [FlutterFlow Codex GitHub issues](https://github.com/FlutterFlow/flutterflow-codex/issues). * Run `flutterflow ai docs [topic]` inside an initialized workspace for version-matched FlutterFlow AI reference material. --- # Exporting Projects Follow the steps below to export your project. [Sharing a Project with a User](https://demo.arcade.software/Rc3s1P8DFypUKoPzVITL?embed\&show_copy_link=true) ### Command Details[​](/flutterflow-cli/exporting.md#command-details "Direct link to Command Details") * If you wish to exclude assets from the download, use `-no-include-assets` in your command. This will download the project code without the assets. For example: `flutterflow export-code --project your_project_id --dest path_to_output_folde --no-include-assets --token your_token` * You can download code from a specific branch by switching to that branch and using the toolbar command, or by including the `-branch-name` or `-b` flag in your command and specifying the branch you wish to download from. #### All supported command options[​](/flutterflow-cli/exporting.md#all-supported-command-options "Direct link to All supported command options") | Flag | Behavior | Default | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | --dest / -d | Specifies a destination folder other than the current directory. | Current directory | | --\[no]-include-assets | Option to download assets (images, GIFs). Useful for consecutive code exports if the assets folder hasn't changed. | False | | --branch-name / -b | Downloads from a specific branch. | Main | | --\[no]-fix | Option to run dart fix on the code after downloading. | False | | --\[no]-parent-folder | Option to download the code into a subfolder instead of directly into the directory. | False | | --\[no]-as-module | Whether to generate the project as a Flutter module. | False | | --\[no]-as-debug | Whether to generate the project with debug logging to be able to use FlutterFlow Debug Panel inside the DevTools. | False | | --project-environment | Which [development environment](/testing/dev-environments.md) to be used. If empty, the current environment in the project will be downloaded. | Current environment | ### Filtered exports[​](/flutterflow-cli/exporting.md#filtered-exports "Direct link to Filtered exports") If you are updating an existing project and do not want certain files to be overwritten during a code export, you can create a `.flutterflowignore` file in the root of your project directory. This file should contain a list of files to be ignored using globbing syntax. #### Example:[​](/flutterflow-cli/exporting.md#example "Direct link to Example:") If your project is located at: ``` /Users/yourname/projects/my_flutterflow_app/ ``` Then, place the `.flutterflowignore` file in: ``` /Users/yourname/projects/.flutterflowignore ``` #### Example `.flutterflowignore` contents:[​](/flutterflow-cli/exporting.md#example-flutterflowignore-contents "Direct link to example-flutterflowignore-contents") ``` my_flutterflow_app/android/app/build.gradle # Prevents FlutterFlow from overwriting native Android build configuration my_flutterflow_app/ios/Runner/Info.plist # Keeps iOS app metadata unchanged my_flutterflow_app/web/index.html # Ensures custom modifications to the web entry file are retained ``` This ensures that the specified files and directories are not overwritten during code export. For more details on globbing syntax, refer to [this guide](https://pub.dev/packages/glob#syntax). ## FAQ[​](/flutterflow-cli/exporting.md#faq "Direct link to FAQ") I am getting an error as FormatException: Missing argument for… This error likely indicates that you haven't correctly entered the command option along with its value. Double-check that all required information has been entered. If everything is correct and you're still encountering the error, it might be due to using an outdated version of the FlutterFlow CLI. To resolve this, you can update to the latest version by running the installation command: ``` dart pub global activate flutterflow_cli ``` This should update the CLI and fix the issue. --- # App Builder On opening the project, you'll see the App Builder, which consists of four main sections: [Navigation Menu](/flutterflow-ui/builder.md#navigation-menu), [Toolbar](/flutterflow-ui/builder.md#toolbar), [Canvas](/flutterflow-ui/builder.md#canvas-area), and [Properties Panel](/flutterflow-ui/builder.md#properties-panel). ![navigation-menu.avif](/assets/images/navigation-menu-d7267cc6d7230adcd7258b08f5ceefaa.avif) ## Navigation Menu[​](/flutterflow-ui/builder.md#navigation-menu "Direct link to Navigation Menu") The Navigation Menu, located on the left side of the builder, allows you to switch between various FlutterFlow features. These include designing the UI, managing databases, setting up API, adjusting app settings, adding integrations, and more. Here is a list of all the features accessible from the navigation menu: 1. **Dashboard**: Manage projects, access account info, and FlutterFlow resources. 2. **Widget Palette**: Access all widgets for your app. 3. **Page Selector**: Manage pages, components, and custom code files, and organize them using folders. 4. **Widget Tree**: Get an overview of all widgets on a selected page. 5. **Storyboard**: Visualize app's design and navigation. 6. **Test Mode**: [Test your app](/testing/run-your-app.md#test-mode) in a live debugging environment. 7. **Firestore**: Create collections and adjust Firestore-related settings. 8. **Data Types**: Create custom data types for your app. 9. **App Values**: Manage [App State variables](/resources/data-representation/app-state.md) and Constants. 10. **API Calls**: Define API calls. 11. **Media Assets**: Upload assets for your app and team. 12. **Cloud Functions**: Write and deploy cloud functions for Firebase. 13. **Tests**: Add automated tests. 14. **Agents**: Create, configure, and manage [AI Agents](/integrations/ai-agents.md) to integrate conversational AI interactions into your app. 15. **App Events**: Define and manage [App Events](/concepts/app-events.md) that allow different parts of your app to communicate without being directly connected. 16. **Theme settings**: Customize visual appearance. 17. **Settings and Integrations**: Access app-related settings and integrations. ## ToolBar[​](/flutterflow-ui/builder.md#toolbar "Direct link to ToolBar") From [ToolBar](/flutterflow-ui/toolbar.md), you can search for project resources, change canvas size, see project history, branching, optimization and enhancements, view-download code, and run your app. ## Canvas Area[​](/flutterflow-ui/builder.md#canvas-area "Direct link to Canvas Area") In the [Canvas Area](/flutterflow-ui/canvas.md), you can see a preview of a device's screen and build your app page. ## Properties Panel[​](/flutterflow-ui/builder.md#properties-panel "Direct link to Properties Panel") The Properties Panel lets you modify both the visual appearance and interactive behavior of UI elements on the canvas. It allows you to add [Actions](/resources/functions/action-flow-editor.md), set up a [Backend Query](/resources/backend-query.md), add [Animations](/concepts/animations.md) and more. The Properties Panel will vary slightly depending on the entity you have selected. To explore the details of each Properties Panel, click on the following: * **[Page Properties](/resources/ui/pages/properties.md)** (when you have selected a Page) * **[Widget Properties](/resources/ui/widgets/properties.md)** (when you have selected any widget, including built-in components) --- # Canvas The Canvas shows the selected device screen, such as mobile, tablet, web, or desktop. It allows you to add widgets via drag-and-drop. You can select, move, and position widgets anywhere on the Canvas. The Canvas also includes zoom controls, light and dark previews, multi-language preview, App Bar and Nav Bar controls, text size simulation, and more. ![canvas area](/assets/images/canvas-5fb2f2b205ec872d07cffb04be6b5be8.avif) ## Show or Hide Navigation Menu[​](/flutterflow-ui/canvas.md#show-or-hide-navigation-menu "Direct link to Show or Hide Navigation Menu") From here, you can open or close the [Navigation Menu](/flutterflow-ui/builder.md#navigation-menu). ## Zoom Controls[​](/flutterflow-ui/canvas.md#zoom-controls "Direct link to Zoom Controls") There are zoom in (+) and zoom out (-) buttons to control the zoom level of the Canvas. While working on complex UI designs, this comes in handy when you want to zoom in on a specific area or zoom out for an overview. ## Preview Screen[​](/flutterflow-ui/canvas.md#preview-screen "Direct link to Preview Screen") The Preview Screen is where you build the UI for the selected device. You can customize the screen by adding widgets using drag and drop from the [Widget Palette](/flutterflow-ui/widget-palette.md) and by applying properties from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel). ## Set Preview Screen Size[​](/flutterflow-ui/canvas.md#set-preview-screen-size "Direct link to Set Preview Screen Size") Use the screen size controls at the top of the Canvas to preview your app at different dimensions. Select the mobile, tablet, or desktop icon to switch between device types and test how your layout responds on each screen size. You can also set a custom preview size by clicking the current size box, entering the desired **Width (px)** and **Height (px)** values, and then clicking **Save**. [Set Preview Screen Size](https://demo.arcade.software/DfBQoBkkkRX68CIYoWwD?embed\&show_copy_link=true) ## Add App Bar[​](/flutterflow-ui/canvas.md#add-app-bar "Direct link to Add App Bar") From here, you can add an [App Bar](/resources/ui/pages/scaffold.md#appbar) to your page. Clicking this button opens a popup displaying different App Bar styles for you to choose from. Select an App Bar style from the list, and it will appear in the Preview Screen. ![appbar-style.avif](/assets/images/appbar-style-69cc04345c9bf715e4889aafb113b01d.avif) ## Designer Import/Export[​](/flutterflow-ui/canvas.md#designer-importexport "Direct link to Designer Import/Export") Use this menu to copy screens between FlutterFlow and FlutterFlow Designer. * **Export to Designer:** Copy pages from your FlutterFlow project into Designer, where you can explore new styles and continue refining the layouts. See [Import from FlutterFlow](/designer/import.md) for detailed instructions. * **Paste from Designer:** Copy designs from Designer into your FlutterFlow project. In Designer, use **Export to FlutterFlow** to copy the frames, then select **Paste from Designer** on the Canvas. See [Export from Designer](/designer/export.md#export-options) for more information. ## Dark/Light Mode[​](/flutterflow-ui/canvas.md#darklight-mode "Direct link to Dark/Light Mode") Use this toggle to switch your app preview between light and dark mode, so you can ensure your design looks great in both modes. This feature is only available if you've enabled dark mode support in your project. ## Builder Settings[​](/flutterflow-ui/canvas.md#builder-settings "Direct link to Builder Settings") Builder Settings let you adjust how the FlutterFlow builder, Canvas, preview screen, and Property Panel behave while you design. ![builder-settings](/assets/images/builder-settings-5d1f3329679f6d47bcee8981a5e130f3.avif) ### Platform Settings[​](/flutterflow-ui/canvas.md#platform-settings "Direct link to Platform Settings") #### Set Builder to Dark Mode[​](/flutterflow-ui/canvas.md#set-builder-to-dark-mode "Direct link to Set Builder to Dark Mode") Use this option to switch the FlutterFlow builder between light and dark mode. This changes the appearance of the FlutterFlow platform, not the theme of your app. [Set Builder to Dark Mode](https://demo.arcade.software/95jb2CKZJKfviZqPsXKt?embed\&show_copy_link=true) ### Canvas Settings[​](/flutterflow-ui/canvas.md#canvas-settings "Direct link to Canvas Settings") #### Enable Snapping[​](/flutterflow-ui/canvas.md#enable-snapping "Direct link to Enable Snapping") Enable snapping to make widget width and height snap to multiples of the specified value while resizing. This helps keep widget sizes consistent as you adjust layouts on the Canvas. [Enable Snapping](https://demo.arcade.software/xdE4cilXUV1P7krYEJFg?embed\&show_copy_link=true) #### Show Resize Bars[​](/flutterflow-ui/canvas.md#show-resize-bars "Direct link to Show Resize Bars") Show resize bars to display handles on the right and bottom sides of the preview screen. You can use them to resize the preview screen to a custom size and test how your layout responds at different screen sizes. ![handle-bars](/assets/images/handle-bars-bc6d168860d8ace0c4aec71c1c127450.gif) #### Set Canvas Color[​](/flutterflow-ui/canvas.md#set-canvas-color "Direct link to Set Canvas Color") Use this option to change the background color of the Canvas. This can be helpful when creating components or previewing widgets against a different page background. For example, if a component uses dark text, setting a lighter canvas color can make it easier to see while designing. [Set Canvas Color](https://demo.arcade.software/XoUnydzOgh3Uc2EruRo0?embed\&show_copy_link=true) ### Device Preview Settings[​](/flutterflow-ui/canvas.md#device-preview-settings "Direct link to Device Preview Settings") #### Show Safe Area[​](/flutterflow-ui/canvas.md#show-safe-area "Direct link to Show Safe Area") Enable this option to show the device safe area in the builder. Safe areas help you preview where content may be affected by device notches, rounded corners, status bars, or other screen insets. note If the device bezel is displayed, the safe area is always enabled in the preview. [Show Safe Area](https://demo.arcade.software/mhjqT9pmmyxEprnF5YOY?embed\&show_copy_link=true) #### Adjust Text Sizing[​](/flutterflow-ui/canvas.md#adjust-text-sizing "Direct link to Adjust Text Sizing") Use this option to preview your app with different text scale settings. This helps you test how your UI responds when users increase text size from their device accessibility settings. [Adjust Text Sizing](https://demo.arcade.software/uodCNZIibPCNQIfXPSKg?embed\&show_copy_link=true) #### Display Keyboard[​](/flutterflow-ui/canvas.md#display-keyboard "Direct link to Display Keyboard") Enable this option to show the keyboard on the preview screen. This is useful for checking how form fields, buttons, and bottom-aligned content appear when the keyboard is open. [Display Keyboard](https://demo.arcade.software/xoat6tc8gNwwPPWsHG0t?embed\&show_copy_link=true) #### Display Device Bezel[​](/flutterflow-ui/canvas.md#display-device-bezel "Direct link to Display Device Bezel") Use this option to show the device frame in the preview. This is particularly useful for checking how your screen will look with device-specific features such as the safe area or notches on iPhones and Android devices. [Display Device Bezel](https://demo.arcade.software/pCZChdW9S252zmOfnD2t?embed\&show_copy_link=true) #### Show Overflows[​](/flutterflow-ui/canvas.md#show-overflows "Direct link to Show Overflows") Enable this option to show overflow errors in the builder as they will appear in Test Mode. This can help you catch layout issues before running or testing your app. [Show Overflows](https://demo.arcade.software/dXXZAlLs0wMEj1E5SBAX?embed\&show_copy_link=true) #### Display Language[​](/flutterflow-ui/canvas.md#display-language "Direct link to Display Language") If you've enabled multi-language support for your project, you can use this to preview your app in different languages. Open **Canvas Settings** and change the **Display Language** to preview the translated text in your app. tip This feature is valuable for testing your app across multiple locales without needing to run your app. [Display Language](https://demo.arcade.software/Wt1s0IIxQXNQ5cdAMIIf?embed\&show_copy_link=true) ### Property Panel Settings[​](/flutterflow-ui/canvas.md#property-panel-settings "Direct link to Property Panel Settings") #### Keep Common Properties Collapsed[​](/flutterflow-ui/canvas.md#keep-common-properties-collapsed "Direct link to Keep Common Properties Collapsed") Enable this option to keep common sections in the Property Panel collapsed by default, such as **Visibility**, **Padding**, and **Alignment**. This can make the Property Panel easier to scan when you only want to open the sections you need. [Keep Common Properties Collapsed](https://demo.arcade.software/iXj6ebaDiAZjr0WLSzkQ?embed\&show_copy_link=true) ## Add Nav Bar[​](/flutterflow-ui/canvas.md#add-nav-bar "Direct link to Add Nav Bar") Use this button to add the [Nav Bar](/resources/ui/pages/scaffold.md#nav-bar) to your page. Clicking it opens a popup where you can enable the Nav Bar for your project. Once the Nav Bar is enabled, you can customize it to match your design. ![add-navbar.avif](/assets/images/add-navbar-6d11cbb07831d4afe49cd682fe1b3671.avif) ## Video Guide[​](/flutterflow-ui/canvas.md#video-guide "Direct link to Video Guide") Watch this video if you prefer watching a video tutorial. [The Canvas | FlutterFlow University](https://www.youtube.com/embed/NDrte4nOXYc) --- # Dashboard When you log in to FlutterFlow, the first page you’ll see is the **Dashboard**. It serves as a central hub for managing your projects, including creating, searching for, deleting, and duplicating projects. The Dashboard also lets you choose your preferred theme—dark or light—for a more comfortable viewing experience. The Dashboard provides convenient access to organizational resources, facilitating seamless collaboration among team members. It also integrates with a marketplace where users can browse and download widgets, templates, and plugins. You can also find links to various resources to help you build apps with FlutterFlow. Your account information and plan details are easily accessible from this page as well. ![dashboard](/assets/images/dashboard-6259117ba315654d5e29a9450fc01022.avif) * **Projects**: Projects section displays all the projects you have created in FlutterFlow. Use the overflow menu to rename, duplicate, delete, leave the project, add tags, and open the project in a new browser tab. info When you duplicate a project with Firebase configured, you must delete the config files in the duplicated project and initiate a new [**Firebase setup**](/integrations/firebase/connect-to-firebase.md) for it. * **Notification Center**: The Notification Center simplifies how you manage comments and invites across projects. It centralizes all your project communications. When you're ready to address a comment, select it to go directly to the relevant section of the project. * **Dark/Light Mode**: The Dark/Light Mode option allows you to choose between a light and dark color scheme for the app builder. * **View Options**: Switch between **List View** and **Grid View** to choose how projects are displayed on the Dashboard. Grid View displays projects as tiles for visual browsing, while List View provides a compact layout for quickly scanning your projects. * **Search**: This option allows you to search for your projects. * **Filter Projects**: Filter projects by privacy setting: private, shared by you, or shared with you. * **Tag Projects**: You can create and add a tag to projects, providing a quick and organized way to classify and identify projects based on their characteristics, purpose, or status. For detailed steps, see [Creating and Managing Tags](/resources/projects/how-to-create-find-organize-projects.md#create-and-add-tags-to-projects). * **Create a New Project**: To create a new project, use the **+ Create New** button. Learn more about [creating a new project](/resources/projects/how-to-create-find-organize-projects.md#how-to-create-a-project). * From **My Teams** section, you can share custom code, assets, design systems, and APIs among team members and across projects. * **Marketplace**: Use the [**FlutterFlow Marketplace**](/marketplace) to access prebuilt components and templates created by other users and add new functionality to your app. * **Resources**: From the **Resources tab**, you can find various useful links that can help you build apps on FlutterFlow. [Video tutorials](https://www.youtube.com/@FlutterFlow/videos) are extremely helpful for learning about concepts visually. * **Community**: The **Community tab** redirects you to our [Community Forum](https://community.flutterflow.io/home), a place for you to share ideas, ask questions, and troubleshoot issues with other FlutterFlow builders. The community shares a lot of amazing ideas! Creating a Forum Account * When you select the [**Community**](https://app.flutterflow.io/community) tab, FlutterFlow automatically creates a forum account and redirects you to the Community Forum. To add a password to your forum account, go to the forum [**settings**](https://community.flutterflow.io/settings/account) and select **Forgot Password**. * Additionally, make sure your FlutterFlow profile includes a name. The same name will be used for the community forum profile. - **URL Access (Only Available for Enterprise Users)**: You can view and copy URLs that need to be whitelisted for FlutterFlow to function correctly in enterprise environments with restricted internet access. See **[Whitelisting URLs](/misc/enterprise.md#whitelist-urls)** for more information. ![url-access](/assets/images/url-access-dashboard-c7344917b601031042084edb5dee953c.avif) - **Account**: This is helpful if you want to look at your account information, upload a profile picture, reset your password, see your referrals, or delete your account. - **Log Out**: Safely log out from your FlutterFlow account. --- # My Teams On the My Teams page, you can manage billing for your team, edit projects simultaneously, and share code, design systems, APIs, and assets. This makes collaboration between team members much easier and helps keep everyone on the same page. Even if you don't have team members, you can still use this page to share resources between your own projects and keep your development process organized. By sharing resources from one place, teams can build more consistently across projects. ## Team Code[​](/flutterflow-ui/my-teams.md#team-code "Direct link to Team Code") warning **Team Code Libraries are deprecated**. Please use the new [**Libraries**](/resources/projects/libraries.md) to share and reuse projects across multiple projects. ## Team Media Assets[​](/flutterflow-ui/my-teams.md#team-media-assets "Direct link to Team Media Assets") Your team might be working on multiple projects that use the same icons, images, audio files, and other graphic resources. If each project has its own assets, the team has to upload the same resources multiple times. However, if the team shares an asset library across projects, they can save time, increase productivity, and ensure design consistency. If an asset needs to be updated, the team can update it in one place, and the changes will reflect across all projects. To share team media assets: 1. Go to **My Teams**, select your team, and click **Upload Media**. 2. Media assets shared with the team appear in the Media Assets tab of the Navigation Menu. You can then select and use these assets directly from the asset picker in the Properties Panel. * Upload shareable media assets * Access media assets ![upload-sharable-media](/assets/images/upload-sharable-media-d19e33e5d75a5a69f1d89951e3f07eb1.avif) ![access-media-assets](/assets/images/access-media-assets-178639dcb9dffac3183119af2868a154.avif) ## Team Design Library[​](/flutterflow-ui/my-teams.md#team-design-library "Direct link to Team Design Library") A company may have a website, a mobile app, and a desktop app, each with its own user interface and user experience. Instead of recreating the same design settings for each project, you can create a shared design system to speed up the work and keep designs consistent across projects. A design system includes colors, typography, fonts, icons, app assets, a Nav Bar, and an App Bar. tip To store pre-designed UI components, we recommend using [**Libraries**](/resources/projects/libraries.md) for easy reuse across projects. Here's how you can share the design library: 1. Navigate to **My Teams > Team Design Library** and click **+ Create New**. 2. Enter a name for the **Design System Project**. 3. A new project will open where you can configure the Theme, [Nav Bar](/resources/ui/pages/scaffold.md#nav-bar), [App Bar](/resources/ui/pages/scaffold.md#appbar), and [App Assets](/resources/projects/settings/general-settings.md#app-assets). [Create Team Design Library](https://demo.arcade.software/Dammx5Es92gc1hbdU31p?embed\&show_copy_link=true) 4. To use the shared design library, open the project where you want to use the design system and navigate to **Theme Settings** (navigation menu) **> Design System**. 5. Click **No Design System Selected**. 6. A popup opens displaying the list of shared design systems. Select one to add it to your project. [Use Team Design Library](https://demo.arcade.software/lIKiqtfucQxC9HLLKNTS?embed\&show_copy_link=true) ## Team API Library[​](/flutterflow-ui/my-teams.md#team-api-library "Direct link to Team API Library") warning **Team API Libraries are deprecated**. Please use the new [**Libraries**](/resources/projects/libraries.md) to share and reuse projects across multiple projects. ## Add Domains[​](/flutterflow-ui/my-teams.md#add-domains "Direct link to Add Domains") You can add custom domains and share them with all team members. This makes it simple to connect domains to the right projects and collaborate seamlessly. To add a domain, click **Add Domains** under **My Teams**. ![Add custom domain](/assets/images/add-custom-domain-81f55cb9f2c61724d3768dd789dac99b.avif) --- # Resource Hierarchy Overview This guide aims to help you understand the structure and elements of a typical FlutterFlow project. It will walk you through some important parts of the app, from the overall project down to individual design elements, explaining their purpose and how they relate to traditional Flutter app components. ## FlutterFlow App Parts[​](/flutterflow-ui/resource-hierarchy.md#flutterflow-app-parts "Direct link to FlutterFlow App Parts") The diagram below illustrates how a FlutterFlow app is structured. ![FlutterFlow app part.avif](/assets/images/ff-app-part-5d677a6580a1a528a2951299d2280d84.avif) 1. **Project**: Represents the overall application you are building in FlutterFlow. It encompasses all the other elements listed below and serves as the container for your entire app development effort within FlutterFlow. Learn more about creating a project [here](/resources/projects/how-to-create-find-organize-projects.md#how-to-create-a-project). 2. **Page**: Refers to individual screens within the FlutterFlow project. Each page represents a part of the user interface where users can interact with the app. Multiple pages collectively make up the complete user interface of your application. Learn more about pages in FlutterFlow [here](/resources/ui/pages.md#creating-a-page). 3. **Built-in-widgets**: These are pre-designed widgets provided by FlutterFlow that you can use to build your app’s user interface. Built-in widgets simplify the development process by offering common UI elements such as buttons, text fields, sliders, etc. 4. **Component**: A component in FlutterFlow is a reusable UI block that can be used across different pages within the project. Components are useful for maintaining consistency and reducing redundancy in the app design, as the same component (like a custom dialog box) can be inserted wherever needed. Learn more about creating a component [here](/resources/ui/components.md). 5. **Design System**: This refers to a set of standards for design within your FlutterFlow project. A design system in FlutterFlow includes predefined styles that ensure visual consistency throughout the app. Learn more about design system [here](/concepts/design-system.md). ## Flutter to FlutterFlow[​](/flutterflow-ui/resource-hierarchy.md#flutter-to-flutterflow "Direct link to Flutter to FlutterFlow") If you are coming from Flutter, it is beneficial for you to understand the Flutter to FlutterFlow mapping. The diagram below illustrates the correlation between traditional Flutter app components and their equivalents within FlutterFlow. ![Flutter to FlutterFlow app parts](/assets/images/flutter-to-flutterflow-c4f7ad3c554b0e399fdd6456dc36c176.avif) 1. **MyApp to Project**: In Flutter, `MyApp` typically represents the root of your application, where you set up routes and other global configurations. In FlutterFlow, the equivalent is the "Project," which encompasses the entire application you are building, including its configurations and settings. Learn more about creating a project [here](/resources/projects/how-to-create-find-organize-projects.md#how-to-create-a-project). 2. **MyPage to Page**: `MyPage` in Flutter represents a specific screen in the app. Similarly, In FlutterFlow, each "Page" corresponds to a screen, where you build the layout and functionality specific to that page of the project. Learn more about pages in FlutterFlow [here](/resources/ui/pages.md#creating-a-page). 3. **Column, Button, Text to Built-in widgets**: In FlutterFlow, widgets are categorized under "Built-in widgets," which users can drag and drop onto their canvas to build the UI. Learn more about widgets [here](/resources/ui/overview.md#widgets). 4. **Custom widget to Component**: `CustomWidget` in Flutter indicates user-defined widgets that serve specific functions not covered by built-in widgets. FlutterFlow translates this into "Component" allowing you to create and use custom components within your projects. Learn more about creating a component [here](/resources/ui/components.md). 5. **Theme/style constants to Design System**: In Flutter, theme and style constants are used to ensure consistent styling across an app. FlutterFlow uses a "Design System" to manage and apply uniform styles and themes throughout the application. Learn more about design system [here](/concepts/design-system.md). ## Resource Description[​](/flutterflow-ui/resource-hierarchy.md#resource-description "Direct link to Resource Description") A Resource Description is a brief text note that explains the purpose, usage, or key details of a particular resource. By supplying clear, concise descriptions, you create better project documentation and a smoother development experience—both for yourself and any collaborators. info Here are some reasons why resource descriptions can be helpful: * **Team Collaboration**: When multiple developers or designers work on the same project, concise descriptions help everyone understand each element’s role without guesswork. * **Better Search**: Descriptions are indexed in the FlutterFlow search. This helps locate pages, components, and other resources quickly, especially in large projects. * **Project Documentation**: Acts as built-in documentation of your app, which makes future updates easier. You can add a description for each of the following resources in FlutterFlow: * **Project**: Use the project-level description to summarize the overall goals or scope of your app. For instance, "A delivery management app for small businesses" helps keep the team aligned on the primary objective. * **Page**: Explains a page’s main function. Example: "Displays the user’s shopping cart and checkout options." * **Component**: Clarifies the functionality or design intention of a reusable component. Example: "Reusable card component to be used as ListTile." * **Action Blocks**: Provide a concise description of what the set of actions does (e.g., "Sends a notification to the user’s email address upon form submission"). * **Custom Functions**: Describe the logic or purpose behind the function. Example: "Calculates shipping costs based on weight and distance." * **Custom Actions**: Specify the custom behavior you’ve created, such as "Opens a QR scanner and returns the scanned value." * **Custom Widgets**: Explain the widget’s purpose or structure. Example: "Carousel widget for displaying multiple images with pagination." * **Data Type**: Summarizes the purpose of a custom data model. Example: "Represents a user’s order including items, total cost, and status." * **Parameters**: Provide context for how a parameter is used, including expected data types or value ranges. Example: "String to store the user’s phone number—must include country code." * **Page/Component State Variables**: Clarify what state data is being stored and why. For instance, "Tracks the currently selected tab in this component." * **App State Variables**: Describe the global data shared across pages. Example: "Stores the user’s authentication token for all network requests". * **Constant**: Add the intended purpose of any fixed value used throughout the app. Example: "Base API URL for all network calls". * **Enum**: Provide a rationale for the enumerated values. Example: "Defines possible user roles—admin, editor, viewer". * **Firestore Collection**: Explain what data the collection holds and how it relates to your app’s functionality. Example: "Stores all user profiles with fields for name, email, and profile photo URL". In FlutterFlow, you can read descriptions as tooltips when hovering over the green note icon. tip In the generated code, FlutterFlow inserts descriptions as docstring-like comments near the relevant classes, methods, or properties. For instance, a data type named `OrderInfo` with a description of “Represents a user’s order, including items, total cost, and status” will have that text added above the class declaration: ``` /// Represents a user’s order, including items, total cost, and status. class OrderInfo { /// The total price in USD for this order. double totalAmount; List items; // ... } ``` In a standard IDE (e.g., VS Code or Android Studio), if you place your mouse over a custom data type class name, the description set in FlutterFlow appears as a tooltip, helping you quickly grasp the purpose of a resource. ![resource-description.avif](/assets/images/resource-description-870afc6b99db9b5637c1beadba3677e5.avif) --- # Storyboard The Storyboard view allows you to visualize the overall design and navigation of your app. On Storyboard, you can see different screens and user interactions that make up your app. This allows you to see how users will navigate through your app and ensure that the user experience is as intuitive and user-friendly as possible. info This feature is currently in Beta. It is optimized for projects with 30 pages or less. ![storyboard.avif](/assets/images/storyboard-9f1b0d822bd94f58e43e928cedc4279a.avif) ## Storyboard legend[​](/flutterflow-ui/storyboard.md#storyboard-legend "Direct link to Storyboard legend") In a storyboard, a legend is a visual key or guide that explains the meaning of the different lines, icons, and colors used inside the canvas. We use the following elements inside the storyboard: ![storyboard-legend.avif](/assets/images/storyboard-legend-d18cbc2e4f0067bf3ae2e13505ea5687.avif) 1. The solid line is used to represent the [Navigate](/concepts/navigation/page-navigation.md#navigate-to-action) or Login action. 2. The dotted line is used to represent the Bottom Sheet action. 3. The right arrow icon represents hidden widgets. These widgets may not be visible in the current page view (e.g., they might be on another tab) but they still have a navigation action to display them. ## Highlight routes on a page[​](/flutterflow-ui/storyboard.md#highlight-routes-on-a-page "Direct link to Highlight routes on a page") With so many pages displayed on a Storyboard, it may be difficult to identify the route path from a specific page, especially when lines overlap each other. To highlight the pathways leading into and out of a specific page, just click on a page, and the routes will be highlighted in blue color. ![highlight-routes.avif](/assets/images/highlight-routes-0bcb55b5f6e3997421746074e0d61188.avif) ## Move pages[​](/flutterflow-ui/storyboard.md#move-pages "Direct link to Move pages") You might want to adjust the default arrangements of pages on canvas and group the pages that belong to the same feature. To do so, select the page and drag it to the desired place. ## Open a page from Storyboard[​](/flutterflow-ui/storyboard.md#open-a-page-from-storyboard "Direct link to Open a page from Storyboard") You can also open a page directly from a Storyboard. To do so, simply double-click on a page. *** ## Video guide[​](/flutterflow-ui/storyboard.md#video-guide "Direct link to Video guide") Watch this video if you prefer watching a video tutorial. [Navigating Pages & Storyboard | FlutterFlow University](https://www.youtube.com/embed/ukBii81pwm4) *** ## FAQs[​](/flutterflow-ui/storyboard.md#faqs "Direct link to FAQs") I am getting "Error: Unable to initialize Storyboard" This error typically occurs because the initial page has not been set. To resolve this, please set the initial page in the [App Details](/resources/projects/settings/general-settings.md#app-details) settings of your project. --- # Toolbar The Toolbar, located at the top of the app builder, provides easy access to numerous tools and features. It includes options for project configuration, saving versions of your app, accessing help, reporting or debugging issues, viewing project comments, downloading your app code, and running your app directly in FlutterFlow. ![toolbar](/assets/images/toolbar-786b8d54a4f950e1a2b76ccbc66cb716.avif) ## Project Info[​](/flutterflow-ui/toolbar.md#project-info "Direct link to Project Info") Click on the project info to view the project name, branch, environment, FlutterFlow version and release date, and the Flutter version used by the project. ## Help Menu[​](/flutterflow-ui/toolbar.md#help-menu "Direct link to Help Menu") From here, you can access essential resource links that can help you while building your app. 1. **Search Docs**: Paid users can search the FlutterFlow documentation directly from the builder. 2. **Community Forum**: Visit the [Community Forum](https://community.flutterflow.io/) to participate in discussions, share knowledge, and collaborate with other FlutterFlow users. 3. **Feedback**: You can provide feedback and help us improve the product. 4. **Bug Report**: You can submit a bug report from here. 5. **Generate Bug Report Code**: Click this option to generate a unique code that helps the FlutterFlow team assess and troubleshoot your issue. Include this code when submitting a bug report. 6. **Tutorials**: You can start the tutorial for building your first app directly in FlutterFlow. 7. **FAQs and Docs**: While building your app, you might need to consult our official documentation frequently. This option opens the FlutterFlow documentation. 8. **What's New?**: View the latest FlutterFlow features, improvements, and product updates. 9. **Current Status/Known Issues**: View FlutterFlow's current system status and any known issues. 10. **Show/Hide Chat**: You can use this option to show or hide the chat button at the bottom right of the app builder. ## Keyboard Shortcuts[​](/flutterflow-ui/toolbar.md#keyboard-shortcuts "Direct link to Keyboard Shortcuts") With keyboard shortcuts, you can perform common actions related to widgets and run your project in Test Mode or Run Mode with just a few keystrokes, saving you time and effort. Select this option to see all the shortcuts. ![keyboard-shortcuts.avif](/assets/images/keyboard-shortcuts-44a1ba8b277b35d1a7164217833fbfc9.avif) ## Command Palette[​](/flutterflow-ui/toolbar.md#command-palette "Direct link to Command Palette") Open the Command Palette by selecting the search button or pressing **Cmd/Ctrl + K**. Search for an item, then select the right arrow to see where it is used. Select a result to open it directly. ![command-palette.avif](/assets/images/command-palette-e3cd96697f631d56997e0646a0950a4c.avif) ## AI Agent[​](/flutterflow-ui/toolbar.md#ai-agent "Direct link to AI Agent") [AI Agent](/concepts/ai-agent.md) lets you work with AI coding agents directly from the FlutterFlow desktop app. It is helpful for making project updates with natural-language prompts, such as creating pages, adjusting widgets, wiring actions, or reviewing your project structure. ## AI Generation History[​](/flutterflow-ui/toolbar.md#ai-generation-history "Direct link to AI Generation History") The **AI Generation History** panel lets you track the status of your AI-generated items. It provides a list of all previously generated pages and components, and you can easily preview them in the panel. ## Project Comments[​](/flutterflow-ui/toolbar.md#project-comments "Direct link to Project Comments") Project Comments let you leave thoughts, questions, or feedback on a specific widget for your project team or client. While adding a comment, you can tag users, and they will be able to respond, creating a thread of conversation. info To tag users, select the **@** symbol and choose the project team member(s). ## Project Suggestions[​](/flutterflow-ui/toolbar.md#project-suggestions "Direct link to Project Suggestions") Project Suggestions identifies opportunities to improve your app's design and performance. **Optimizations**: This identifies elements that may slow down your app, such as queries on columns, unused or duplicate backend queries, and widgets with unbounded sizes. **UI Enhancements**: This provides tips for creating a more visually appealing and user-friendly design, such as increasing the size of a widget's tap target. info You can control which types of suggestions you receive by selecting the settings icon on the right. ![optimizations-UI-enhancements.avif](/assets/images/optimizations-UI-enhancements-c22c98b3aedf1373eac8dff13f47df86.avif) ## Project Issues[​](/flutterflow-ui/toolbar.md#project-issues "Direct link to Project Issues") This section displays errors and warnings that may cause build failures or app crashes. Select an issue to view its description and navigate to the relevant location in your project. Errors vs Warnings **Errors** prevent your app from compiling and running. These must be resolved in order to run the app. They can be due to missing actions, errors in custom code, incorrect data types, and so on. **Warnings**, while not preventing compilation, indicate potential issues such as incorrect rule configuration or performance problems. Although it's possible to ignore warnings, addressing them can enhance the quality of your app and prevent future issues. ![warnings-errors.avif](/assets/images/warnings-errors-4f6ae376c05b81d4a4a413c2e9f9ddbc.avif) ## Version Control[​](/flutterflow-ui/toolbar.md#version-control "Direct link to Version Control") **Version Control** is a system that tracks changes to your project's files over time, allowing you to revert to previous states if needed. In FlutterFlow, you can use [Branching](/collaboration/branching.md) to create a separate copy of your project to build or test features without affecting the main version. ## Developer Menu[​](/flutterflow-ui/toolbar.md#developer-menu "Direct link to Developer Menu") The Developer Menu provides access to tools such as code viewing, GitHub integration, and source code download capabilities. 1. **View Code**: This option lets you view the *Dart* code for all the pages of your FlutterFlow project. You can also view the dependencies used by the app here. 2. **Connect GitHub Repo**: You can use this option to connect your project to a [GitHub](https://github.com/) repository and upload its code. See [Connect a GitHub Repository](/exporting/push-to-github.md#connect-a-github-repo) for step-by-step instructions. 3. **Download Code**: You can download the entire codebase of the app generated by FlutterFlow using this option. 4. **Download APK**: Use this to generate a release build of your Android app. It will automatically download the `.apk` file after the build is complete. 5. **FlutterFlow CLI**: You can also download the code using *[FlutterFlow CLI](https://pub.dev/packages/flutterflow_cli)*. See instructions [here](/flutterflow-cli/exporting.md). note *Connect GitHub Repo*, *Download Code*, and *Download APK* features require a [**paid plan**](https://flutterflow.io/pricing). 6. **Open in VSCode**: This option lets you open your entire FlutterFlow project in a VS Code environment, offering a richer development experience. You’ll have real-time autocomplete and error detection, easier access to existing Flutter and Dart tooling, and the ability to leverage the AI ecosystem. 7. **Refactor Project**: This option opens your FlutterFlow project in a YAML-based file editor, allowing you to perform bulk edits more efficiently. You can search, edit, and replace values across multiple files—useful for renaming keys, updating data types, or migrating resources to a Library. Check out the [**Refactor Project**](/resources/projects/refactor-project.md) documentation for more details. ## Share Project[​](/flutterflow-ui/toolbar.md#share-project "Direct link to Share Project") You can make a project public so that others can view and clone your project. Before sharing your project, make sure to remove any sensitive information. note * You can only share projects where you are the owner. * The share feature can be used to create Marketplace items. See [**FlutterFlow Marketplace**](/marketplace) for more information. ## Preview App[​](/flutterflow-ui/toolbar.md#preview-app "Direct link to Preview App") You can use this option to run your app in [Preview mode](/testing/run-your-app.md#preview-mode). ## Test Mode[​](/flutterflow-ui/toolbar.md#test-mode "Direct link to Test Mode") Use this menu to run your app in [Test Mode](/testing/run-your-app.md#test-mode) or [Run Mode](/testing/run-your-app.md#run-mode). --- # Widget Palette The Widget Palette in FlutterFlow provides access to all UI elements. These are essentially FlutterFlow widgets that can be dragged and dropped onto the canvas. You can use the search bar to quickly locate a specific widget for your application. ![widget-palette.avif](/assets/images/widget-palette-1f9d01356928e265066e46fa3bd4e443.avif) ## 1. Widgets[​](/flutterflow-ui/widget-palette.md#1-widgets "Direct link to 1. Widgets") From the Widgets tab, you can access all standard FlutterFlow widgets. They are organized into different categories based on their purpose, making it easier to navigate and find the appropriate widget for your app. ## 2. Components[​](/flutterflow-ui/widget-palette.md#2-components "Direct link to 2. Components") Components are widgets with certain functionalities that can be reused throughout your app. They are constructed from either standard or custom widgets. Once you have created a [component](/resources/ui/components/creating-components.md) or [custom widget](/concepts/custom-code/custom-widgets.md), you can access it from here. ## 3. Templates[​](/flutterflow-ui/widget-palette.md#3-templates "Direct link to 3. Templates") Templates are predefined and ready-to-use widgets. These include UI elements that are commonly used in most apps and can serve as a starting point in creating parts of the user interface. You can also create your own templates from the standard widget. ## 4. Theme Widgets[​](/flutterflow-ui/widget-palette.md#4-theme-widgets "Direct link to 4. Theme Widgets") Theme Widgets enable you to customize the visual appearance of individual widgets and reuse them consistently throughout your app. They are an integral part of the design system, allowing you to build widgets based on your theme. Once you [create a theme widget](/concepts/design-system.md#theme-widgets), you can access it from here. ## 5. Floating Widget Palette[​](/flutterflow-ui/widget-palette.md#5-floating-widget-palette "Direct link to 5. Floating Widget Palette") The Floating Widget Palette gives you quick access to widgets directly from the canvas. This feature is useful for swiftly adding widgets without the need to open the Widget Palette via the navigation menu. ![Floating Widget Palette](/assets/images/floating-widget-palette-907809164f7c6c702ed4986cff32b266.gif) --- # Generated Code: Components Similar to a [**Page**](/generated-code/page-model.md), when creating a **[component](/resources/ui/components.md)** in FlutterFlow, it automatically generates two files: a `Widget` class and a `Model` class. Prerequisites This guide uses examples from the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v2)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. ## ComponentModel class[​](/generated-code/component-model.md#componentmodel-class "Direct link to ComponentModel class") `ComponentModel` classes are responsible for managing the state and behavior of individual components used within a page. These classes extend the `FlutterFlowModel` class, providing a consistent structure and shared functionality across all component models. This ensures that each component's state is isolated and reusable, making the app easier to maintain and scale. The lifecycle of a `ComponentModel` and its associated widget class follows the same structure as a page. For more details, refer to the documentation on **[Generated Pages](/generated-code/page-model.md)**. ### onComponentLoad Action: Generated Code[​](/generated-code/component-model.md#oncomponentload-action-generated-code "Direct link to onComponentLoad Action: Generated Code") When you define actions for the `onComponentLoad` action trigger of a component, these actions are added inside an `addPostFrameCallback` method within the page's `initState` method. This ensures that the actions are executed only after the initial widget tree is built. ``` @override void initState() { super.initState(); _model = createModel(context, () => ProductListPageModel()); // On component load action. SchedulerBinding.instance.addPostFrameCallback((_) async { await _model.updateTotalCost(context); safeSetState(() {}); }); } ``` --- # DataTypeStruct class Prerequisites This guide uses example of the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v2)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. When you create a custom data type in the FlutterFlow editor, a corresponding class is generated in the code to act as a structured container for your data, similar to a `Struct`. This class includes simple getters and setters for each field. For example, if your data type in FlutterFlow is named "Product", the generated class will be named `ProductStruct` and can be found in the `product_struct.dart` file. ![custom-data-type-gen-class.png](/assets/images/custom-data-type-gen-class-dada69e3fce9f4e9adb7fbba271143b9.png) --- # FFAppState Prerequisites This guide uses example of the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v2)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. The `FFAppState` class in FlutterFlow acts as a central hub for managing the application's global state. It's designed as a singleton, meaning there's only one instance of this class throughout the app's lifecycle. This class extends [**ChangeNotifier**](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), allowing widgets to listen and react to state changes. It includes methods for initializing and updating the app's persisted state and also defines various state variables with corresponding **getters and setters** for manipulating these values. Here is a basic template of the class, taken from the [**eCommerceFlow demo app**](https://bit.ly/ff-docs-demo-v2)'s generated code: ``` class FFAppState extends ChangeNotifier { static FFAppState _instance = FFAppState._internal(); factory FFAppState() { return _instance; } FFAppState._internal(); static void reset() { _instance = FFAppState._internal(); } void update(VoidCallback callback) { callback(); notifyListeners(); } // App State variable of primitive type with a getter and setter bool _enableDarkMode = false; bool get enableDarkMode => _enableDarkMode; set enableDarkMode(bool value) { _enableDarkMode = value; } } ``` The `_enableDarkMode` is an App State variable created by developer that creates its own corresponding getter and setter. ## Rebuild on Updating AppState[​](/generated-code/ff-app-state.md#rebuild-on-updating-appstate "Direct link to Rebuild on Updating AppState") When updating an `AppState` variable from the Action Flow Editor, you will be presented with several **[update type](/resources/data-representation/app-state.md#update-type)** options such as **Rebuild All Pages**, **Rebuild Current Page**, and **No Rebuild** in the Action Settings. Let's see how the generated code changes when these options are selected. ### Rebuild Current Page[​](/generated-code/ff-app-state.md#rebuild-current-page "Direct link to Rebuild Current Page") When a developer chooses to update App State with the update type set to **Rebuild Current Page**, the corresponding `setter` is called. Immediately after, `setState(() {});` is invoked, which updates only the current page. Here's an example of the generated code when we update the App State `enableDarkMode` in the `onInitialization` action trigger of the `ProductListPage`. ``` SchedulerBinding.instance.addPostFrameCallback((_) async { FFAppState().enableDarkMode = !(FFAppState().enableDarkMode ?? true); setState(() {}); }); ``` ### Rebuild All Pages[​](/generated-code/ff-app-state.md#rebuild-all-pages "Direct link to Rebuild All Pages") In this case, the update type is set to **Rebuild All Pages**, meaning that the `setter` is called, followed by the `update()` method. This method internally calls `notifyListeners()`, which is crucial for updating any widgets that depend on this variable. ``` SchedulerBinding.instance.addPostFrameCallback((_) async { FFAppState().enableDarkMode = !(FFAppState().enableDarkMode ?? true); FFAppState().update(() {}); }); ``` Updating App State from Custom Code When updating App State variables from custom code, such as Custom Actions, it's crucial to call the update function to ensure that the changes are reflected across all pages. For example, you should use: ``` FFAppState().update(() => FFAppState().enableDarkMode = !(FFAppState().enableDarkMode ?? true)); ``` ### No Rebuild[​](/generated-code/ff-app-state.md#no-rebuild "Direct link to No Rebuild") Only the setter is called with no setState or update method invoked afterward. This means that only the variable is updated, with no state changes occurring after the data update. ## watch\[​](/generated-code/ff-app-state.md#watchffappstate "Direct link to watch") When you add an [**Update App State**](/resources/data-representation/app-state.md#update-app-state-action) action via the Action Flow Editor, the corresponding pages will include this line within the build method: ``` @override Widget build(BuildContext context) { context.watch(); ... ``` By using `context.watch()`, the widget effectively subscribes to any changes in the `FFAppState` class. Whenever there's a change in the `FFAppState` object, this widget automatically rebuilds to reflect those changes. This ensures that your widget always displays the most current data and state of the app, maintaining an up-to-date and responsive user interface. ## Managing AppState\[​](/generated-code/ff-app-state.md#managing-appstatelist "Direct link to Managing AppState") When you add an App State variable of `List` type in FlutterFlow, several utility functions are automatically generated to help you manage this list. These functions include a getter, a setter, and methods for adding, removing, and updating items in the list. This setup ensures that you can easily interact with the list while keeping the app state consistent and responsive. Below is an explanation of these generated functions using the specific example of a LatLngList. ``` late LoggableList _LatLngList = LoggableList([LatLng(37.4071594, -122.0775312), LatLng(40.7358633, -73.9910835)]); List get LatLngList => _LatLngList?..logger = () => debugLogAppState(this); set LatLngList(List value) { if (value != null) { _LatLngList = LoggableList(value); } debugLogAppState(this); } void addToLatLngList(LatLng value) { LatLngList.add(value); } void removeFromLatLngList(LatLng value) { LatLngList.remove(value); } void removeAtIndexFromLatLngList(int index) { LatLngList.removeAt(index); } void updateLatLngListAtIndex( int index, LatLng Function(LatLng) updateFn, ) { LatLngList[index] = updateFn(_LatLngList[index]); } void insertAtIndexInLatLngList(int index, LatLng value) { LatLngList.insert(index, value); } ``` These functions are automatically generated to provide a convenient and consistent way to manage list-type App State variables, making it easier to maintain the app's state: * The list `LatLngList` is initialized as a private variable `_LatLngList` of type `LoggableList`, which helps in managing the list with additional logging capabilities. * The get `LatLngList` method allows other parts of the app to access the `LatLngList`. * The set `LatLngList` method allows you to replace the entire `LatLngList` with a new one. When a new list is assigned, it updates the private variable `_LatLngList` and logs this change using `debugLogAppState`. * The `addToLatLngList` function appends a new `LatLng` object to the LatLngList, dynamically updating the list as the app runs. * The `removeFromLatLngList` function removes a specific `LatLng` object from the `LatLngList`, ensuring the list remains accurate and up-to-date. * The `removeAtIndexFromLatLngList` function removes a `LatLng` object from the list based on its index position. * The `updateLatLngListAtIndex` function allows you to update a `LatLng` object at a specific index by applying an update function (`updateFn`) to it. * The `insertAtIndexInLatLngList` function inserts a new `LatLng` object into the `LatLngList` at a specific index, shifting the existing items as necessary. How to create App State variables To learn more about creating and using App State variables in FlutterFlow's UI, check out the[ **App State**](/resources/data-representation/app-state.md) guide. --- # FlutterFlow Model The `FlutterFlowModel` class is an abstract class used in FlutterFlow to provide a unified and extensible structure for managing state and behavior of widgets (both pages and components). It encapsulates **initialization, state management,** and **disposal** logic, making it easier to handle the lifecycle of widgets and their models. FlutterFlow automatically generates the `flutter_flow_model.dart` file, which contains the `FlutterFlowModel` class and utility methods like `wrapWithModel()` and `createModel()`. The diagram below illustrates how these utility classes and methods are utilized in a widget or model class: ![page-generated.png](/assets/images/page-generated-8c049279aadda77f6233554cca01deb8.png) When a component is added to your page (and every component you create [generates both a widget and a model class)](/generated-code/component-model.md), the flow below explains how the utility classes are used when there is a child component: ![page-component-generated.png](/assets/images/page-component-generated-f0a0aec0e4590657a5c9589fddf00b2f.png) Here’s a breakdown of the lifecycle of `FlutterFlowModel` class: ## Initialization[​](/generated-code/flutterflow-model.md#initialization "Direct link to Initialization") Ensures the model is initialized **only once** and is tied to the `BuildContext` and the widget it is associated with. ``` abstract class FlutterFlowModel { // Initialization methods bool _isInitialized = false; void initState(BuildContext context); void _init(BuildContext context) { if (!_isInitialized) { initState(context); _isInitialized = true; } if (context.widget is W) _widget = context.widget as W; _context = context; } ``` ## Widget & Context references[​](/generated-code/flutterflow-model.md#widget--context-references "Direct link to Widget & Context references") Provides references to the associated widget and its `BuildContext`. ``` // The widget associated with this model. This is useful for accessing the // parameters of the widget, for example. W? _widget; W? get widget => _widget; // The context associated with this model. BuildContext? _context; BuildContext? get context => _context; ``` `_widget` and `_context` (private fields) store the widget and context references. `widget` and `context` (getters) are the public accessors for `_widget` and `_context`. ## Disposal[​](/generated-code/flutterflow-model.md#disposal "Direct link to Disposal") Manages the cleanup of resources when the model or widget is disposed. ``` bool disposeOnWidgetDisposal = true; void dispose(); void maybeDispose() { if (disposeOnWidgetDisposal) { dispose(); } // Remove reference to widget for garbage collection purposes. _widget = null; } ``` The `disposeOnWidgetDisposal` determines whether the model should be disposed when the widget is removed. This defaults to `true` for **pages** and `false` for **components** (as parent models typically manage their child components). The `maybeDispose()` checks `disposeOnWidgetDisposal` before disposing. It removes the widget reference to aid garbage collection. ## Updates and Change Notification[​](/generated-code/flutterflow-model.md#updates-and-change-notification "Direct link to Updates and Change Notification") Allows the model to notify the associated widget or parent component/page when updates occur. ``` // Whether to update the containing page / component on updates. bool updateOnChange = false; // Function to call when the model receives an update. VoidCallback _updateCallback = () {}; void onUpdate() => updateOnChange ? _updateCallback() : () {}; FlutterFlowModel setOnUpdate({ bool updateOnChange = false, required VoidCallback onUpdate, }) => this .._updateCallback = onUpdate ..updateOnChange = updateOnChange; // Update the containing page when this model received an update. void updatePage(VoidCallback callback) { callback(); _updateCallback(); } ``` ## wrapWithModel()[​](/generated-code/flutterflow-model.md#wrapwithmodel "Direct link to wrapWithModel()") The `wrapWithModel()` method in FlutterFlow links a model to a widget and its child widgets, allowing them to access and manage state. It wraps the widget with a Provider, making the model available throughout the widget tree. --- # Generated Code: Pages When you create a new Page in FlutterFlow, it automatically generates two files: a `Widget` class and a `Model` class. So if the name of the page you created is called **ProductListPage**, FlutterFlow generation backend will automatically create **ProductListPageWidget** class and **ProductListPageModel** class. Prerequisites This guide uses examples from the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v2)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. ## PageModel class[​](/generated-code/page-model.md#pagemodel-class "Direct link to PageModel class") The `PageModel` classes are responsible for managing the state of individual pages and initializing the components used in these Pages. These classes extend the `FlutterFlowModel` class, which provides a consistent structure and shared functionality across all page models. The following diagram shows how FlutterFlow generates the model and widget class when you create a new Page in FlutterFlow: ![page-generation-initial.png](/assets/images/page-generation-initial-e7846168b8b4019ead1e2ca9dc71ab64.png) FlutterFlow Model To learn more about the utility classes and methods that FlutterFlow generates for all pages & components, see [**the FlutterFlowModel document**](/generated-code/flutterflow-model.md). #### Managing Local State[​](/generated-code/page-model.md#managing-local-state "Direct link to Managing Local State") A `PageModel` class typically holds local state fields specific to the page, which correspond to the **[Page State variables](/resources/ui/pages/page-lifecycle.md#page-state)**. For example, in the ProductListPage, user may create a Page State variable called `searchString`. Correspondingly, in the `product_list_page_model.dart` [file](https://github.com/FlutterFlow/sample-apps/blob/main/ecommerce_flow/lib/product/product_list_page/product_list_page_model.dart) (which is the `Model` file for the `ProductListPage`), the corresponding state field would be `_searchString`. This private field stores the current search string and includes a getter and setter to manage its value while logging any changes. ``` String? _searchString; set searchString(String? value) { _searchString = value; debugLogWidgetClass(rootModel); } String? get searchString => _searchString; ``` Private variables in Dart In Dart, variables that start with an underscore (`_`), such as `_searchString`, are private to the class. This means they cannot be accessed outside the class or its scope. In addition to managing local state, the given `PageModel` class also contains fields for handling the state of widgets on the page. For instance, `_dropDownValue` is a private field that stores the current value of a dropdown widget (if it is added to the current Page). Similar to `_searchString`, it has a getter and setter that logs changes to this field. ``` String? _dropDownValue; set dropDownValue(String? value) { _dropDownValue = value; debugLogWidgetClass(rootModel); } String? get dropDownValue => _dropDownValue; ``` #### Initializing child component models[​](/generated-code/page-model.md#initializing-child-component-models "Direct link to Initializing child component models") The `PageModel` class is also responsible for initializing the models of components used on the page. For example, if the page includes a `CartCounter` component, the model for this component is initialized within the page's model class. ``` // Model for CartCounter component. late CartCounterModel cartCounterModel; @override void initState(BuildContext context) { cartCounterModel = createModel(context, () => CartCounterModel()..parentModel = this); } ``` info Only the model class of a child component is initialized inside the page or parent model class. In the case of page model classes, they are initialized within the widget’s state class itself. See the **[Widget class section](/generated-code/page-model.md#pagewidget-class)** for more details. When dealing with dynamic lists of components, such as those in a `ListView`, Row, or Column widget, the `PageModel` initializes a `Map` to manage the state of each component instance. For example, if the page includes a list of `CategoryAvatar` components, the initialization might look like this: ``` // Models for CategoryAvatar dynamic component. Map categoryAvatarModels = {}; ``` #### dispose()[​](/generated-code/page-model.md#dispose "Direct link to dispose()") Finally, the `dispose` function in the `ProductListPageModel` class is used to clean up resources when they are no longer needed. This is a common practice in Flutter to prevent memory leaks. In this class, the `dispose` function is overridden to dispose of the `cartCounterModel`, `searchQueryFocusNode`, and `searchQueryTextController`. ``` @override void dispose() { cartCounterModel.dispose(); searchQueryFocusNode?.dispose(); searchQueryTextController?.dispose(); } ``` ## PageWidget class[​](/generated-code/page-model.md#pagewidget-class "Direct link to PageWidget class") The `PageWidget` classes are responsible for creating the UI of individual pages and holding the widget tree as designed in the FlutterFlow canvas. These classes always extend Flutter's `StatefulWidget` class utilizing Flutter's built-in state management through `setState` to handle dynamic updates and interact with the app's lifecycle. ``` class ProductListPageWidget extends StatefulWidget { const ProductListPageWidget({super.key}); @override State createState() => _ProductListPageWidgetState(); } ``` #### PageModel Initialization[​](/generated-code/page-model.md#pagemodel-initialization "Direct link to PageModel Initialization") Within the State class, the `PageModel` object is initialized. [This class](/generated-code/page-model.md#pagemodel-class) serves as a centralized place to manage the page’s state, handle business logic, and interact with the data layer. ``` class _ProductListPageWidgetState extends State { late ProductListPageModel _model; @override void initState() { super.initState(); _model = createModel(context, () => ProductDetailPageModel()); } ``` #### PageModel Dispose[​](/generated-code/page-model.md#pagemodel-dispose "Direct link to PageModel Dispose") Similarly, the [`dispose` method](/generated-code/page-model.md#dispose) of the `PageModel` class is invoked from the **overridden** `dispose` method of the widget's **State** class. This ensures that any resources managed by the `PageModel`, such as listeners or controllers, are properly released when the widget is removed from the widget tree. ``` @override void dispose() { _model.dispose(); super.dispose(); } ``` #### Global Scaffold Key[​](/generated-code/page-model.md#global-scaffold-key "Direct link to Global Scaffold Key") Each page includes a `GlobalKey` for the `Scaffold`, which can be used to manage the scaffold's state, such as opening or closing drawers or snackbars programmatically. ``` final scaffoldKey = GlobalKey(); return Scaffold( key: scaffoldKey, ...) ``` #### Keyboard Dismissal[​](/generated-code/page-model.md#keyboard-dismissal "Direct link to Keyboard Dismissal") Moreover, the root widget of every page is a `GestureDetector` with an `onTap` callback that unfocuses the current input field. This approach ensures that tapping anywhere outside an input field dismisses the keyboard or removes focus, creating a better user experience. ``` return GestureDetector( onTap: () { FocusScope.of(context).unfocus(); FocusManager.instance.primaryFocus?.unfocus(); }, ...) ``` These functionalities are automatically added by FlutterFlow to ensure seamless navigation and proper keyboard handling across pages. ### onPageLoad Action: Generated Code[​](/generated-code/page-model.md#onpageload-action-generated-code "Direct link to onPageLoad Action: Generated Code") When you define actions for the `onPageLoad` action trigger of a Page, these actions are added inside an `addPostFrameCallback` method within the page's `initState` method. This ensures that the **on Page Load** actions are executed after the widget is fully built and rendered. This avoids issues caused by trying to update the UI before it is ready. ``` @override void initState() { super.initState(); _model = createModel(context, () => ProductListPageModel()); // On page load action. SchedulerBinding.instance.addPostFrameCallback((_) async { _model.searchString = null; safeSetState(() {}); ... // more actions }); } ``` safe Set State The `safeSetState` method is a custom implementation built on top of Flutter's `setState` method. It ensures that `setState` is only called when the widget is currently mounted, preventing potential runtime errors. --- # Directory Structure Prerequisites This guide uses example of the generated code of the **[EcommerceFlow demo app](https://bit.ly/ff-docs-demo-v2)**. To view the generated code directly, check out the **[Github repository](https://github.com/FlutterFlow/sample-apps/tree/main/ecommerce_flow)**. When you download the code generated by FlutterFlow, you'll notice many additional files and folders beyond what you see in FlutterFlow's Code Viewer. These files make up the complete project structure, organized according to a specific architecture. Understanding this structure is like having a detailed map, guiding you through the code and making it easier to navigate and customize your FlutterFlow project later. So, let's dive in and explore this directory structure. ## Folder Structure[​](/generated-code/project-structure.md#folder-structure "Direct link to Folder Structure") ``` assets/ lib/ - actions/actions.dart - auth/ - firebase_auth/ - auth_manager.dart - base_auth_user_provider.dart - backend/ - api_requests/ - api_calls.dart - api_manager.dart - get_streamed_response.dart - cloud_functions/ - firebase/ - firebase_dynamic_links/firebase_dynamic_links.dart - supabase/ - schema/ - enums/enums.dart - structs/ - address_struct.dart - cart_struct.dart - ... - util/ - firestore_util.dart - schema_util.dart - carts_record.dart - ... - index.dart - backend.dart - pages/ ---// empty in this project - cart/ - cart_counter/ - cart_counter_model.dart - cart_counter_widget.dart ... - components/ - square_leading_model.dart - square_leading_widget.dart - styled_button_model.dart - styled_button_widget.dart - custom_code/ - actions/ - execute_search.dart ... - flutter_flow/ ---//FF generated files - custom_functions.dart - flutter_flow_animations.dart - flutter_flow_....dart - nav/ - app_constants.dart - app_state.dart - index.dart - main.dart pubspec.yaml ``` ### Pages & Components[​](/generated-code/project-structure.md#pages--components "Direct link to Pages & Components") FlutterFlow follows a layer-first approach to keep your app organized as it grows. Authentication and backend methods are neatly organized into their own sections **[auth](/generated-code/project-structure.md#auth)** and **[backend](/generated-code/project-structure.md#backend)**. Each page you create in FlutterFlow will generate its own folder, containing the `widget` file and the corresponding `model` file. Shared components are placed in subfolders under `components/`. If you've created nested folders in the FlutterFlow UI, these will directly translate into corresponding folders in the exported code. This gives you even more control to group and organize different features as you like. For instance, you could have separate folders for `products`, `user profile`, and `orders`. In the example above, `cart` is a folder explicitly created to hold all cart related pages and components. ### assets/[​](/generated-code/project-structure.md#assets "Direct link to assets/") The `assets/` directory is where you store static files that your app uses, such as images, fonts, and other resources. These files can be accessed in your code through asset paths and are bundled with your app when it's built. ### lib/[​](/generated-code/project-structure.md#lib "Direct link to lib/") The `lib/` directory contains all the Dart code that drives your Flutter app. This is where the main structure of your application resides. It's organized into several subdirectories to keep the codebase clean and manageable: ### actions/[​](/generated-code/project-structure.md#actions "Direct link to actions/") The actions folder contains app-level **Action Blocks**. Each Action Block is created as a separate function within this directory. For example, in the case of eCommerce demo app, the `addToWishlist` function is an app-level Action Block that is included in the `actions.dart` file. ``` Future addToWishlist( BuildContext context, { required String? productId}) async { // Add productId to wishlist object FFAppState().addToLocalWishlist(productId!); FFAppState().update(() {}); } ``` ### auth/[​](/generated-code/project-structure.md#auth "Direct link to auth/") Contains files and folders related to authentication logic, including integrations with Firebase or other authentication services. ### backend/[​](/generated-code/project-structure.md#backend "Direct link to backend/") The `backend/` directory is responsible for handling all the backend logic and integrations for your Flutter app. This includes API requests, cloud functions, database interactions, and managing data schemas. Each subdirectory within backend/ serves a specific purpose: * **api\_requests/**: The api\_requests/ directory handles all communication between your app and external services via APIs. It centralizes and organizes the code for making and managing HTTP requests and responses. * **cloud\_functions/**: This directory is used to store functions that interact with cloud-based services, such as Firebase Cloud Functions. These functions are used for operations that need to be performed on the server side, such as complex calculations, data processing, or sending notifications. * **schema/**: The schema/ directory is crucial for defining the structure of data used throughout your app. It contains the following subdirectories and files: * **enums/:** Stores enumeration types used across the app. * **structs/:** These are used to represent custom data types like `Address` or `Cart`. * **util/:** Contains utility functions like `firestore_util.dart` and `schema_util.dart`. ### custom\_code/[​](/generated-code/project-structure.md#custom_code "Direct link to custom_code/") Custom Actions and Custom Widgets created by the developer are stored in this folder, in their respective subdirectories: `custom_code/actions` and `custom_code/widgets`. ### flutter\_flow/[​](/generated-code/project-structure.md#flutter_flow "Direct link to flutter_flow/") This directory is generated by FlutterFlow and contains various utility files that support the app's operation, such as custom functions, generated themes, navigation and more. ### app\_constants.dart[​](/generated-code/project-structure.md#app_constantsdart "Direct link to app_constants.dart") This class is used to store constant values that are used throughout the application. ### app\_state.dart[​](/generated-code/project-structure.md#app_statedart "Direct link to app_state.dart") This file contains the [**FFAppState**](/generated-code/ff-app-state.md) class, which is responsible for managing the global App States created by the developer. ### main.dart[​](/generated-code/project-structure.md#maindart "Direct link to main.dart") The `main.dart` file serves as the entry point for your Flutter application. It begins by initializing the Flutter engine with `WidgetsFlutterBinding.ensureInitialized()`. Next, it sets up the URL strategy for the web application, initializes the `FlutterFlowTheme`, and sets up the `FFAppState` to manage the global state of your app. ### pubspec.yaml[​](/generated-code/project-structure.md#pubspecyaml "Direct link to pubspec.yaml") This file is the configuration file for your Flutter project. It defines the dependencies, assets, and other project settings. It also specifies which versions of Dart and Flutter your project uses, along with any third-party packages or plugins your app relies on. This structure makes it easier to manage and scale your app! --- # FlutterFlow State Management Correct topic? This document explains the generated code behind the state management approaches used in FlutterFlow. If you're looking for guidance on adding state variables in FlutterFlow, refer to the **[State Variables](/concepts/state-management.md)** documentation. FlutterFlow manages state in several ways, depending on the scope. Generally, state management is handled using the [Provider](https://pub.dev/packages/provider) package, which facilitates the provisioning of data models for components, pages, and the overall app state. ![state-management.avif](/assets/images/state-management-231f8221303479d7d7af7e747c6a57e7.avif) ## Page & Component Models[​](/generated-code/state-management.md#page--component-models "Direct link to Page & Component Models") In FlutterFlow, both component widget models and page models share a uniform structure, enhancing consistency throughout the framework. They include local state fields to store data specific to the component, such as sizes or user inputs. These models are also equipped with initialization and disposal methods: `initState` for setup when the widget initializes, and `dispose` for resource cleanup when the widget is no longer needed. Additionally, they provide space for action blocks, which are a set of actions that performs a specific task and can be reused in different parts of the app, and helper methods for extra functionalities needed by the component. This consistent structure across models helps efficiently manage the state and interactions of various components within the app. ## Page State[​](/generated-code/state-management.md#page-state "Direct link to Page State") [Variables](/resources/ui/pages/page-lifecycle.md) used exclusively within a page — such as a text field validator or the value of a checkbox — are stored in the `Model` of each page. These variables can be accessed by other component children on the same page. For instance, on a page with a form, tapping a button in one component may need to access the value of a text field in a different component. Variables within a page are tracked through `StatefulWidget` and are encapsulated into that page’s Model. ## Component State[​](/generated-code/state-management.md#component-state "Direct link to Component State") Similar to page state, [**Component State variables**](/resources/ui/components/component-lifecycle.md) are accessible within the component where they are defined. Each component has a corresponding `Model` and `Widget` class. Variables may be passed in from their parent as parameters. Additionally, you can access component state values from its parent Page widget. This accessibility is possible because the Model of a component is instantiated within the parent Page model. It utilizes the Provider method `context.read()`, which returns any existing model in the tree before instantiating a new one. Thus, any updates to the state in the component model will reflect in the parent’s instance of that component model. One of the helper methods in `flutter_flow_model.dart` is `wrapWithModel()`. This method wraps the child in a Provider model to make it accessible to the child and sets a callback function, which is generally used to call `setState()` in the parent page and update any changed values. We use this wrapper around widgets that need to access the data included in the model. For example, if a page includes a component with a text field and later on the page there is a button needing access to the text field’s value, the button would be wrapped with `wrapWithModel()`, including the text field component’s Model as a parameter. It’s important to note that components cannot directly access variables of other components on the same page. However, you can pass a variable from ComponentA as a parameter to ComponentB in their parent Page. This ensures that ComponentB receives all updates from ComponentA as expected. ## App State[​](/generated-code/state-management.md#app-state "Direct link to App State") FFAppState The generated code behind FlutterFlow's App State class is explained in the **[FFAppState](/generated-code/ff-app-state.md)** documentation. ## Variables[​](/generated-code/state-management.md#variables "Direct link to Variables") Variables required across multiple pages of the app, such as a username, should be added to the App State. Refer to `lib/app_state.dart`. All defined variables within the app state are components of the `FFAppState` class, which functions as a ChangeNotifier. This means listeners can subscribe and receive notifications when any changes occur. On each page that requires access to app state variables, the method `context.watch()` is called to initialize a listener for that page. This `watch()` method, provided by the Provider package, facilitates access to inherited widgets and acts as an effective wrapper. ## Persisting App State[​](/generated-code/state-management.md#persisting-app-state "Direct link to Persisting App State") When an app state variable is created, selecting the "Persisted" option enables FlutterFlow to save it on the device using the [**Shared Preferences**](https://pub.dev/packages/shared_preferences) package. This ensures the variable remains available even after the app is restarted, making it ideal for persisting settings such as login status or a user's choice between light and dark modes. If the "**Secure Persisted Fields**" option is enabled in the app state settings, FlutterFlow utilizes the [**Flutter Secure Storage**](https://pub.dev/packages/flutter_secure_storage) package to encrypt the data. Platform Differences If the platform is **Android**, then `flutter_secure_storage` stores data in [**`encryptedSharedPreference`**](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences), which are shared preferences that encrypt keys and values. It handles [**AES Encryption**](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) to generate a secret key encrypted with [**RSA**](https://en.wikipedia.org/wiki/RSA_\(cryptosystem\)) and stored in [**KeyStore**](https://developer.android.com/reference/java/security/KeyStore). For the **iOS** platform, it uses the [**KeyChain**](https://developer.apple.com/documentation/security/keychain_services) which is an iOS-specific secure storage used to store and access cryptographic keys only in your app. In the case of the **Web**, it uses the [**Web Cryptography**](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) (Web Crypto) API. ## Global State[​](/generated-code/state-management.md#global-state "Direct link to Global State") Global state variables are pieces of information related to the device that are accessible throughout the FlutterFlow app. These include: * Screen size * Platform (mobile, web, Android, iOS) * Keyboard visibility * Current time These variables are found in the "Global Properties" section and are automatically added by FlutterFlow, not generated by users. Users can utilize App State variables for their own global use cases. Global properties are retrieved through methods defined in `flutter_flow_utils.dart`. Typically, these methods utilize built-in Flutter libraries, such as `dart:io`, to gather the necessary information. ## Constants[​](/generated-code/state-management.md#constants "Direct link to Constants") For values that do not change throughout the app, such as API keys or environment flags, we utilize the `FFAppConstants` class, which can be found in `lib/app_constants.dart`. This is an abstract class, meaning it cannot be directly instantiated. Instead, it serves as a namespace for static constants, allowing these values to be organized and accessed consistently across the application. --- # AdMob Adding ads to your FlutterFlow project can be a powerful way to monetize your app. FlutterFlow supports the integration of popular advertising platforms like [Google AdMob](https://admob.google.com/home/), making it easy for you to add [Banner](https://developers.google.com/admob/android/banner) and [Interstitial](https://developers.google.com/admob/android/interstitial) ads to your projects. This guide provides a step-by-step walkthrough for integrating ads within your FlutterFlow project. ## Setup AdMob[​](/integrations/ads/admob.md#setup-admob "Direct link to Setup AdMob") Setting up an AdMob involves creating AdMob apps for both Android and iOS, obtaining the app keys, and configuring some optional settings. ### 1. Creating AdMob app[​](/integrations/ads/admob.md#1-creating-admob-app "Direct link to 1. Creating AdMob app") Visit the AdMob homepage and [sign up](https://admob.google.com/home/) using your Google account. Once logged in, create an Android and iOS app with the necessary details, such as platform and app name. info You should create two AdMob apps to display ads in both Android and iOS versions. ### 2. Adding keys to FlutterFlow[​](/integrations/ads/admob.md#2-adding-keys-to-flutterflow "Direct link to 2. Adding keys to FlutterFlow") You must add the App keys to your FlutterFlow project that will allow your app to communicate with the AdMob server. To do so, get the app key from the AdMob App Settings, navigate to **Settings and Integrations** in FlutterFlow, and add the Android and iOS app keys under **AdMob** integration settings. ### 3. Configure optional settings[​](/integrations/ads/admob.md#3-configure-optional-settings "Direct link to 3. Configure optional settings") Below are some AdMob settings (under **Settings and Integrations** menu) that you might need to configure based on your app and target audience. ![admob-settings](/assets/images/admob-settings-78d7d353740075c5d389a7af51222ae3.avif) * **Show Test Ads**: Test ads are placeholders provided by AdMob that simulate real ads. To enable test ads during development, enable this option. This allows you to click on ads without charging Google advertisers and prevents your account from being flagged for invalid activity. Once your app is ready for production, you can disable this setting to serve real ads. * **Show GDPR Consent Dialog at App Launch**: To display the GDPR consent dialog for users in the European Union (EU), enable this option. **Note that** the dialog will only appear if the user is from the EU and you created a [European regulations message](https://support.google.com/admob/answer/10113207). * **Child-Directed Settings**: To indicate that your content is directed towards children, enable this option. This will ensure that Google treats your content as child-directed when making ad requests. * **Users Under the Age of Consent**: This setting allows you to comply with privacy regulations for users in the European Economic Area (EEA) who are under the age of consent. It ensures that ad requests are appropriately handled, limiting data collection and targeting to meet legal requirements. This is important to protect user privacy and to avoid penalties for non-compliance. * **Ad Content Filtering**: To filter the type of ads displayed, select the appropriate content rating. AdMob will ensure that ads returned for these requests have a content rating at or below the level selected. These are the levels you can set: * **G (General Audience)**: Suitable for all audiences, with no adult content or explicit themes. * **PG (Parental Guidance)**: Ads may contain mild content, suitable for children with parental supervision. * **T (Teen)**: Ads with content appropriate for teenagers; may include some mature topics. * **MA (Mature Audience)**: Ads intended for adults, which may include strong themes or explicit content. Once the setup is completed, you can start to display [AdBanner](/integrations/ads/admob.md#adbanner) or [Interstitial ads](/integrations/ads/admob.md#interstitial-ad) in your app. ## AdBanner[​](/integrations/ads/admob.md#adbanner "Direct link to AdBanner") The **AdBanner** widget displays advertisement banners within your app. It can feature text, images, and rich media, including video ads. Here's an example for AdBanner widget with a test ad: ![adbanner-widget-with-test-ad](/assets/images/adbanner-widget-with-test-ad-3e70c55ef47b3ef473610ead643a2be6.avif) To display an **AdBanner** from AdMob, follow these steps: ### Adding AdBanner widget[​](/integrations/ads/admob.md#adding-adbanner-widget "Direct link to Adding AdBanner widget") First, add the **AdBanner** widget from the **Base Elements**. Next, create a new Banner Ad unit in AdMob, then copy and paste its **unit ID** into FlutterFlow. The Ad unit ID is a unique identifier assigned to each ad created in AdMob. info By default, ad banners are set to a dimension of 100 (width) x 50 (height). tip While building your app, clicking on too many ads may cause your AdMob account to be flagged for invalid activity. To avoid this, it's recommended to enable **Test Ads** during development. ### Testing AdBanner[​](/integrations/ads/admob.md#testing-adbanner "Direct link to Testing AdBanner") Ads cannot be tested in Test or Run Mode. They can only be tested on a real device or emulator. To do this, you can use [Local run](/testing/local-run.md) or [download the code](/flutterflow-cli/exporting.md) and run it in your IDE. ## Interstitial Ad[​](/integrations/ads/admob.md#interstitial-ad "Direct link to Interstitial Ad") An **Interstitial Ad** is a type of full-screen ad that appears at natural transitions or pauses in an app, such as when switching between pages. Unlike banner ads, which stay on-screen while users interact with the app, interstitial ads are shown at key moments and are designed to be closed before the user can continue. They typically support multiple formats, including: * **Image ads** * **Video ads** * **Rich media (interactive ads)** To display an interstitial ad in FlutterFlow, you need to use the **Load Interstitial Ad** and **Show Interstitial Ad** actions together. Here's how it works: ![interstitial\_ad\_flow](/assets/images/interstitial_ad_flow-37b9a4643342b96a27d51145de51744a.png) First, load the ad using the **Load Interstitial Ad** action, then display it with the **Show Interstitial Ad** action. Once the ad is shown, users can choose to either interact with it or dismiss it. After the ad is dismissed, it cannot be displayed again, so you'll need to load a new ad. The newly loaded ad will then be ready for display the next time you trigger the **Show Interstitial Ad** action. warning ***Allow sufficient time between calling Load Interstitial Ad and Show Interstitial Ad to ensure the ad has fully loaded.*** Since loading may take some time, it's recommended to load the ad well in advance to avoid display issues. For example, if you want to show an ad when a widget is tapped, you should load the ad as soon as the page loads. If the ad isn’t loaded in time, it won’t be displayed. Let's see an example displaying the interstitial ad when you navigate to the next page: ![interstitial-ad-flow-2](/assets/images/interstitial-ad-flow-2-8f38970216bede167890e5f3434036c9.avif) On the first page, trigger the **Load Interstitial Ad** action as soon as the page loads. Then, on a widget tap, add the **Show Interstitial Ad** action. The result of whether the ad is dismissed will be stored in the `interstitialAdSuccess` variable. If this value is true (the ad was dismissed), you can load a new ad and proceed to navigate to the next page. Here are the step-by-step instructions: ### Getting Ad Unit ID[​](/integrations/ads/admob.md#getting-ad-unit-id "Direct link to Getting Ad Unit ID") The Ad Unit ID is the unique identifier given to every ad on Admob. You can get this by creating a new Interstitial ad unit from your Admob account. You’ll need this ID when loading the ad. To get the ad unit ID, go to the AdMob dashboard, select your app under **Apps**, and create an **Interstitial** ad unit by following the steps under **Ad units**. Once created, copy the ad unit ID, and repeat the process for the iOS version if needed. ### Loading Ad on Page Load[​](/integrations/ads/admob.md#loading-ad-on-page-load "Direct link to Loading Ad on Page Load") Always load the ad in advance before you intend to display it. This ensures the ad has enough time to fully load its content, whether it's an image or video, before being shown. The best place to do it is the **On Page Load**. To load the ad when the page loads, select the page, add the **On Page Load** action trigger, and set the action to **Load Interstitial Ad**. Enter the iOS and Android **Ad Unit ID**s you obtained in [step 1](/integrations/ads/admob.md#getting-ad-unit-id). tip While building your app, clicking on too many ads may cause your AdMob account to be flagged for invalid activity. To avoid this, it's recommended to enable **Test Ads** during development. ### Display Interstitial Ad[​](/integrations/ads/admob.md#display-interstitial-ad "Direct link to Display Interstitial Ad") Now, you can display the ad using the **Show Interstitial Ad** action. This action returns `interstitialAdSuccess` (as an action output variable), which can be used to check if the user has dismissed the ad. If the ad is dismissed, load a new one and then proceed to navigate to the next page. ## Best Practices[​](/integrations/ads/admob.md#best-practices "Direct link to Best Practices") To maximize the effectiveness of AdMob ads in your app while maintaining a positive user experience and complying with AdMob policies, follow these overall best practices: * **Use Test Ads During Development**: Always enable Test Ads during development to avoid invalid traffic and protect your AdMob account from being flagged or banned. * **Comply with AdMob Policies**: Adhere strictly to AdMob’s guidelines regarding ad placement, frequency, and user interaction. This includes avoiding accidental clicks and ensuring that ads are not too intrusive. Learn more about [AdMob Policies & Restrictions](https://support.google.com/admob/answer/6128543?hl=en). * **Respect User Privacy**: Follow data privacy regulations (e.g., GDPR, CCPA) and give users control over their ad preferences by integrating privacy options. Learn more about [AdMob Privacy & Consent](https://support.google.com/admob/answer/7676680?hl=en) ### AdBanner Best Practices[​](/integrations/ads/admob.md#adbanner-best-practices "Direct link to AdBanner Best Practices") * **Strategic Placement**: Position AdBanner widgets in non-intrusive areas of the app, such as at the bottom or top of the screen, so they don’t interfere with the user’s interaction with the app’s core content. Learn more about [Banner Ad Placement Guide](https://support.google.com/admob/answer/6128877?hl=en). * **Avoid Clickbait**: Make sure the banner ad does not blend too much with the app content. Users should easily differentiate between the ad and the app’s content to avoid accidental clicks. ### Interstitial Ad Best Practices[​](/integrations/ads/admob.md#interstitial-ad-best-practices "Direct link to Interstitial Ad Best Practices") * **Loading Ads in Advance**: Interstitial ads should be loaded before they are needed, typically in the background, to avoid delays when it’s time to display the ad. * **Displaying at the Right Time**: Ensure ads are shown at natural transition points. Showing ads in the middle of an activity can disrupt the user experience. * **Monitoring Frequency**: Overuse of interstitial ads can lead to a negative user experience. It's recommended to show them sparingly and at appropriate times. * **Test Before Production**: Use test ads during development to ensure that your implementation is correct and that you don’t accidentally trigger invalid ad interactions, which could lead to an AdMob account suspension. --- # AI Agents AI Agents in FlutterFlow enable you to integrate AI-powered chat, image generation, video generation, text-to-speech, and speech-to-text directly into your app. An AI Agent is a configurable AI service that you define in FlutterFlow and then call from your app actions. You can build agents powered by providers such as **OpenAI**, **Google**, **Anthropic**, and **ElevenLabs**. Depending on the agent kind, you can create a conversational assistant, convert text into speech, transcribe audio into text, generate images from prompts, or generate video using the latest supported models. Here are some examples of AI Agents: * **AI Stylist:** In an e-commerce fashion app, an AI agent analyzes photos of clothing items users upload from their wardrobes and provides styling tips based on color combinations, styles, seasons, and individual preferences. * **Smart Recipe Assistant:** An AI agent in a cooking app that suggests recipes based on ingredients users have, dietary restrictions, or meal preferences, and offers interactive cooking guidance. * **Marketing Image Generator:** An image generation agent that creates product thumbnails, social posts, or campaign visuals from a prompt. * **Language Learning App:** A text-to-speech agent reads practice sentences aloud so learners can hear pronunciation in a natural voice. * **Meeting Notes App:** A speech-to-text agent transcribes uploaded meeting recordings or voice notes into searchable text. * **Social Media Campaign Builder:** A video generation agent creates short promotional clips from prompts for product launches, announcements, or ads. Prerequisite Before you begin setting up AI Agents, make sure you: 1. Complete all the steps in [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). Note that, while setting up, make sure to follow step number 5 and 8 carefully from [**Allow FlutterFlow to Access Your Project**](/integrations/firebase/connect-to-firebase.md#allow-flutterflow-to-access-your-project) section to properly add the **Cloud Functions Admin** role to **** user. 2. Upgrade your Firebase project to the [**Blaze Plan**](https://firebase.google.com/pricing), as we rely on [**Firebase Cloud Functions**](https://firebase.google.com/docs/functions) to handle AI-related communication securely. 3. Get an API key for the provider you want to use, such as [**OpenAI**](https://platform.openai.com/api-keys), [**Anthropic**](https://platform.claude.com/settings/keys), [**Google AI Studio**](https://aistudio.google.com/app/apikey), or [**ElevenLabs**](https://elevenlabs.io/docs/api-reference/authentication). ## Create AI Agent[​](/integrations/ai-agents.md#create-ai-agent "Direct link to Create AI Agent") To create an AI agent, select the **Agents** tab from the left-side navigation menu, then click the **(+)** button. Provide a descriptive **Agent Name** (e.g., "ShoppingAssistant") and click **Create**. info You can create one AI Agent on the Basic plan and unlimited AI Agents on the Growth plan and higher. After creating the agent, start with the common agent settings: * **Agent Kind**: Select what the agent should do. Supported kinds include **Chat**, **Image Generation**, **Text-to-Speech**, **Speech-to-Text**, and **Video Generation**. * **Internal Description**: Add a short note describing what the agent is for. This is for your own reference and is not sent to the AI model. note The selected agent kind determines which model settings and app actions are available. For example, a **Chat** agent is used with the **Send Message** action, while an **Image Generation** agent is used with the **Generate Image** action. ## Chat[​](/integrations/ai-agents.md#chat "Direct link to Chat") Use a **Chat** agent when your app needs a conversational assistant that can respond to users with text, markdown, or structured JSON. Chat agents are useful for support bots, tutors, product recommenders, content assistants, and agents that analyze user-provided text, images, PDFs, audio, or video. The chat settings are as follows: **System Message** Defines the AI’s role and how it should behave when responding to users. For instance, “You are an AI fashion stylist…” tells the agent to respond like a professional stylist, focusing on outfits, colors, and suggested combinations. **Preloaded Messages** Preloaded messages allow you to set predefined interactions between the AI and users. It is useful for training the agent with example responses to ensure it understands the expected format of answers. * **Role**: Specifies whether the message is from the **User** or the **Assistant**. * **Message**: The actual text input that either the user or assistant might send. * **Example:** * **Role = User:** "What outfit suits my medium skin tone for a sunny day?" * **Role = Assistant:** "For your medium skin tone on a sunny day, a pastel-colored top with white chinos would look fantastic! Consider adding sunglasses and comfortable footwear." tip It is always recommended to include at least one sample conversation with both a user message and an assistant response. **Model Settings** * **Provider**: Allows you to select the AI vendor for this agent. Supported chat providers include **OpenAI**, **Google**, and **Anthropic**. * **OpenAI & Anthropic**: If you choose OpenAI or Anthropic, FlutterFlow will create a [Cloud Function](https://firebase.google.com/docs/functions) in Firebase to relay requests to the AI API securely. Hence, your Firebase project must be on a [Blaze](https://firebase.google.com/pricing) plan (paid) to deploy the necessary cloud function. **Note that** the deployed cloud function will only be accessible to authenticated users. * **Google**: When selecting Google as your provider for chat agents, you need to enable the following in your Firebase project. * [**Firebase Authentication**](/integrations/authentication/firebase/initial-setup.md): This ensures secure interactions between users and your AI agents. * [**Vertex AI**](https://firebase.google.com/docs/vertex-ai): Vertex AI is Google's comprehensive AI platform used to manage and deploy machine learning models. FlutterFlow internally uses the [`firebase_vertexai`](https://pub.dev/packages/firebase_vertexai) package to integrate Google's AI models within your Firebase-connected project. * **Model**: Choose from the list of available models for the given provider. Models differ in capabilities, supported parameters, and cost structure. * **API Key:** Enter your provider’s API key when the selected provider or model requires one. FlutterFlow securely stores this key within the deployed cloud function to ensure it remains hidden from end-users and network requests. **Request Options** Define the types of inputs users can send to the AI agent. You can enable one or more of the following options: * **Text**: Allows users to send written messages, questions, or prompts. * **Image**: Enables users to upload photos for the AI to analyze visual content, such as objects, styles, or scenes. * **PDF** (Anthropic and Google Agent only): Lets users submit PDF documents, allowing the AI to extract and interpret information from files like resumes, reports, or forms. * **Audio** (Google Agent only): Supports voice input, enabling users to record or upload audio clips for transcription, sentiment analysis, or voice-based commands. * **Video** (Google Agent only): Allows users to submit video files, enabling the AI to analyze visual elements. Selecting multiple input types makes it easier for users to clearly communicate what they need. Instead of relying only on text descriptions, users can combine inputs. For instance, in an AI Stylist agent, enabling both Text and Image allows users to either describe their outfits in words or upload clothing photos for personalized analysis. **Response Options** Defines the type of output you want from the agent. You can select from the following options: * **Text**: Returns plain text responses. * **Markdown**: Allows richer formatting (headings, lists, links) if you display content as markdown. For example, an FAQ chatbot can use formatted bullet points, bold text, or italic text to highlight key information. * **JSON**: Returns structured data, which can be parsed programmatically. For example, a restaurant finder app might need structured data, e.g., `{ name: 'Pizza Palace', distance: '2.4 miles' }` to display a dynamic map. **Model Parameters** Here, you can fine-tune how the agent generates responses. * **Temperature**: Controls how creative or random the AI’s responses can be on a scale of 0 to 1. A lower value (e.g., 0.1) makes responses more factual and consistent. A higher value (e.g., 1.0) makes responses more creative and varied (e.g., brainstorming ideas). * **Max Tokens**: Limits the total number of tokens used, including both the user's request and the agent's response. Adjusting this helps manage costs and ensures concise interactions. * **Top P**: Another technique for controlling the variety of words the AI considers. Typically kept at default unless you want fine-tuned sampling control. For example, in a **Blog-Writing Assistant**, you might set a moderate to high temperature for creative phrasing and a high max tokens limit for detailed paragraphs. Conversely, a **Financial Chatbot** would benefit from a lower temperature to deliver consistent, accurate, and stable responses without unnecessary creativity. ### Send Message \[Action][​](/integrations/ai-agents.md#send-message-action "Direct link to Send Message \[Action]") The **Send Message** action allows your app to pass user input (such as text or images) to a selected AI Agent and receive a response based on its system instructions, preloaded messages, and model settings. For example, you can add this action when a user taps a “Send” button after typing in a text field. The AI Agent can then reply based on its system instructions, preloaded messages, and model settings. You can configure the following options for this action: * **Select Agent**: Here, you select the specific AI Agent you previously configured. * **Conversation ID**: The Conversation ID is a unique identifier you assign to maintain context and continuity across multiple interactions within the same conversation. Using a consistent ID (e.g., `user123_AIStylist_202503181200`) allows the AI to remember past interactions and keep conversations coherent and contextual. * **Text Input**: This is where you specify the user's message or input text that the AI agent will process. Typically, this input comes from a widget state (e.g., TextField). * **Image Input**: If your agent supports image processing, you can provide an image. * **Audio Input**: If your agent supports audio processing, you can pass audio files. * **Video Input**: If your agent can analyze video content, provide a video file. info * You can send media files either from [**network URL**](/concepts/file-handling/displaying-media.md#network) or a [**local device**](/concepts/file-handling/displaying-media.md#uploaded-file) storage. * For non-Google agents, we only support network URLs for now. To pass media files from your device, [**upload it first to cloud storage**](/concepts/file-handling/uploading-files.md#upload-or-save-media-action) and then provide its generated URL. - **Action Output Variable Name**: This field stores the AI agent's response to let you display the response to users or process it further. ![ai-agent-send-message-action.avif](/assets/images/ai-agent-send-message-action-6da8af9808becbc25e86f651e78cbf36.avif) ### Clear Chat History \[Action][​](/integrations/ai-agents.md#clear-chat-history-action "Direct link to Clear Chat History \[Action]") The **Clear Chat History** action allows you to clear the remembered context for a Chat agent. It takes the **Conversation ID** and stops referencing the existing thread ID when you next send a message. ![ai-agent-reset-action.avif](/assets/images/ai-agent-reset-action-8cce58c7726c2cb53e5ce5db6adcbdea.avif) ## Text-to-Speech[​](/integrations/ai-agents.md#text-to-speech "Direct link to Text-to-Speech") Use a **Text-to-Speech** agent when your app needs to convert text into spoken audio. This is useful for reading messages aloud, generating narration, creating voiceovers, or helping users hear content in a selected voice. Text-to-speech settings include: * **Provider**: The text-to-speech provider, such as ElevenLabs. * **Model**: The speech generation model, such as Eleven Flash v2.5. * **API Key**: The provider API key used by the deployed agent function. * **Voice ID**: The default voice used to generate speech. Actions can override this per call. * **Output Format**: The audio output format, such as MP3. * **Stability**: Controls how consistent the voice output should be. * **Similarity Boost**: Controls how closely the generated speech should match the selected voice. * **Speed**: Controls the speaking speed. ![AI Agent text-to-speech settings](/assets/images/ai-agent-tts-15504ccad9953eed64be954eaf9bd4db.avif) ### Generate Speech \[Action][​](/integrations/ai-agents.md#generate-speech-action "Direct link to Generate Speech \[Action]") The **Generate Speech** action allows your app to send text to a Text-to-Speech agent and receive generated audio. You can configure the following options for this action: * **Select TTS Agent**: Select the Text-to-Speech agent you previously configured. * **Text Input**: The text to convert into speech. * **Voice ID Override (optional)**: Optionally override the agent's default voice ID for this call. * **Action Output Variable Name**: Stores the generated speech result so you can play it or use it in later actions. ![AI Agent generate speech action](/assets/images/ai-agent-tts-action-61ebd60b29bdc583d9daea5c3d63ee5b.avif) ## Speech-to-Text[​](/integrations/ai-agents.md#speech-to-text "Direct link to Speech-to-Text") Use a **Speech-to-Text** agent when your app needs to convert audio into text. This is useful for transcribing voice notes, meeting recordings, support messages, uploaded audio files, or audio from a URL. Speech-to-text settings include: * **Provider**: The transcription provider, such as ElevenLabs. * **Model**: The transcription model, such as Scribe v2. * **API Key**: The provider API key used by the deployed agent function. ![AI Agent speech-to-text settings](/assets/images/ai-agent-stt-d3e0ac06cd0860241d9ec9e9170e99aa.avif) ### Transcribe Audio \[Action][​](/integrations/ai-agents.md#transcribe-audio-action "Direct link to Transcribe Audio \[Action]") The **Transcribe Audio** action allows your app to send audio to a Speech-to-Text agent and receive the transcribed text. You can configure the following options for this action: * **Select STT Agent**: Select the Speech-to-Text agent you previously configured. * **Audio Source**: Choose where the audio comes from. Supported sources include **Audio URL** and **Uploaded Audio File**. * **Language Code (optional)**: Provide a language code, such as `en`, to guide transcription. * **Action Output Variable Name**: Stores the transcribed text so you can display it or use it in later actions. ![AI Agent transcribe audio action](/assets/images/ai-agent-stt-action-cf19eac005869f09005de4169fe54f6c.avif) ## Image Generation[​](/integrations/ai-agents.md#image-generation "Direct link to Image Generation") Use an **Image Generation** agent when your app needs to create images from a text prompt. This is useful for generating product thumbnails, profile artwork, backgrounds, campaign visuals, or other app-specific images. You can configure image generation with supported providers such as **Google** or **OpenAI**, choose the model, add the API key, and set a default image size. Image settings include: * **Provider**: The provider used to generate images, such as Google or OpenAI. * **Model**: The image generation model, such as Gemini image models or GPT Image models. * **API Key**: The provider API key used by the deployed agent function. * **Image Size**: The default image size for Generate Image calls. Actions can override this per call. ![AI Agent image generation settings](/assets/images/ai-agent-image-gen-467092e7d216240586d252a2bb8d9262.avif) ### Generate Image \[Action][​](/integrations/ai-agents.md#generate-image-action "Direct link to Generate Image \[Action]") The **Generate Image** action allows your app to send a prompt to an Image Generation agent and receive a generated image. You can configure the following options for this action: * **Select Image Generation Agent**: Select the Image Generation agent you previously configured. * **Prompt**: The text prompt that describes the image to generate. * **Size Override**: Optionally override the agent's default image size for this call. You can select **Use Agent Default** or choose a supported size such as **Square (1024 x 1024)**, **Portrait (1024 x 1536)**, or **Landscape (1536 x 1024)**. * **Action Output Variable Name**: Stores the generated image result so you can display it or use it in later actions. ![AI Agent generate image action](/assets/images/ai-agent-image-gen-action-ac750333f6ba958387b3344a18c3c3de.avif) ## Video Generation[​](/integrations/ai-agents.md#video-generation "Direct link to Video Generation") Use a **Video Generation** agent when your app needs to generate video from a text prompt. This is useful for creating short clips, campaign visuals, animated concepts, visual storyboards, or social media assets. Video generation settings include: * **Provider**: The video generation provider, such as Google. * **Model**: The video generation model, such as Veo 3.1. * **API Key**: The provider API key used by the deployed agent function. * **Aspect Ratio**: The default video aspect ratio for Generate Video calls. Actions can override this per call. * **Duration**: The target video duration. ![AI Agent video generation settings](/assets/images/ai-agent-video-gen-8b8cdeb1a67fcb3be18b72776bc28612.avif) info Video generation can take 30 seconds to several minutes. The cloud function keeps the connection open while the provider job runs. ### Generate Video \[Action][​](/integrations/ai-agents.md#generate-video-action "Direct link to Generate Video \[Action]") The **Generate Video** action allows your app to send a prompt to a Video Generation agent and receive a generated video. You can configure the following options for this action: * **Select Video Generation Agent**: Select the Video Generation agent you previously configured. * **Prompt**: The text prompt that describes the video to generate. * **Aspect Ratio Override**: Optionally override the agent's default aspect ratio for this call. You can select **Use Agent Default** or choose a supported aspect ratio such as **Landscape (16:9)**, **Portrait (9:16)**, or **Square (1:1)**. * **Action Output Variable Name**: Stores the generated video result so you can display it or use it in later actions. ![AI Agent generate video action](/assets/images/ai-agent-video-gen-action-3d97b3cfcebd8f28675e578436697c0d.avif) ## Deployment Settings[​](/integrations/ai-agents.md#deployment-settings "Direct link to Deployment Settings") Here, you can fine-tune how your AI Agent is executed. These settings help balance performance, security, and cost for your use case. * **Require Authentication**: By default, this is set to ON to restrict access to only authenticated Firebase users. When set to OFF, anyone can call your agent, which may pose a security risk. * **Timeout (seconds)**: Defines how long the agent function can run before being terminated. For example, a value of `60` allows the function up to 60 seconds to complete. Increase if your agent performs long-running operations or processes complex logic. * **Memory**: Allocates memory for your agent. Higher memory improves performance for heavy workloads but may cost more. For example, choose `256MB` for standard tasks or `512MB+` for agents handling large data or complex logic. * **Min Instances**: The number of instances kept warm and ready at all times. Set to `0` to minimize costs. For example, setting `Min Instances` > 0 can improve response speed by avoiding cold starts, but this incurs additional cost. Set to `0` for development or low-traffic environments. * **Max Instances**: The maximum number of instances that can run simultaneously. Helps scale under load and avoid throttling. For example, setting `Max Instances = 10` limits concurrency to 10 requests. Once configured, click the **Publish** button to make it live. For non-Google Agents After you successfully deploy the agent, changes to its configuration, such as modifying the system message, model, or temperature, require you to redeploy the agent. For Google chat agents, the configuration is stored on the client side, so redeployment isn't necessary. --- # Authentication Methods Overview Authentication enables users to create accounts and log into your app, establishing a secure, verified connection. In the dynamic world of applications, users can authenticate using various methods, including **Email Login**, **OAuth**, and **phone authentication**, among others. While each method has its unique features and advantages, they all share a common goal: enhancing security and verifying the identity of users to provide a safe and personalized user experience. ## Email Login Authentication[​](/integrations/authentication-methods.md#email-login-authentication "Direct link to Email Login Authentication") The Email Login method involves users registering with an email address and password. Security in this approach is enhanced through **Email Verification**, where a link or code is sent to the user's email to confirm ownership. This step prevents unauthorized account creation and ensures that the user can recover their account and receive important communications. ![email-login.png](/assets/images/email-login-3b784eac7a0f93e27a53e54d7bdb9bdb.png) ## OAuth (Open Authorization)[​](/integrations/authentication-methods.md#oauth-open-authorization "Direct link to OAuth (Open Authorization)") **OAuth** is a popular authentication protocol that enables users to authorize one application to interact with another on their behalf without revealing their password. This method is commonly used to allow applications to access service features or user information from other services, such as logging into a third-party app using Google or Facebook credentials. By using OAuth, the user's login credentials stay secure with the original service provider, and only specific permissions are granted to third-party apps via access tokens. This approach minimizes the risk of exposing sensitive user data and streamlines the login process across various platforms. ## Phone Authentication[​](/integrations/authentication-methods.md#phone-authentication "Direct link to Phone Authentication") Another method is phone authentication, where a user's phone number is used as a form of identity verification. Upon registering or logging in, the user receives a text message with a verification code that must be entered to proceed. This method leverages the security of mobile networks and the uniqueness of phone numbers to ensure that the person attempting access is the legitimate owner of the account. ![phone-login.png](/assets/images/phone-login-2eb5b670e54ed779af7155996897dc7b.png) ## Anonymous Authentication[​](/integrations/authentication-methods.md#anonymous-authentication "Direct link to Anonymous Authentication") Anonymous Authentication allows users to interact with your application without signing in with permanent credentials, by creating temporary anonymous accounts. This method is beneficial for users who want to test services before committing to creating an account. If a user decides to sign up later, their anonymous account can be upgraded to a regular account, preserving their data and interactions. Each anonymous session is typically isolated, with strict permissions to prevent access to sensitive features or user data. When upgrading to a full account, secure practices are used to link the anonymous data to the new authenticated profile, ensuring that no data leakage or unauthorized access occurs during the transition. Each authentication method aims to balance user convenience with high security, ensuring that personal and sensitive data remains protected while providing a seamless user experience. ![anon-user.png](/assets/images/anon-user-14e3fb5dbb8951bf1fbb1c8e7ad29b20.png) --- # Overview FlutterFlow provides native support for a variety of Authentication Services, including **Firebase**, **Supabase**, and **Custom Authentication** options. To integrate these services into your app, simply navigate to 'App Settings,' select 'Authentication,' and then choose your preferred service. From there, you can set up initial pages for both entry and logged-in states. Follow any additional steps as necessary to complete the setup. ## Firebase Authentication[​](/integrations/authentication-types.md#firebase-authentication "Direct link to Firebase Authentication") In FlutterFlow, you can seamlessly connect with **Firebase** and utilize the available authentication methods. Firebase Authentication integrates tightly with other Firebase services, leveraging industry standards like OAuth 2.0 and OpenID Connect. This makes it highly adaptable for use with your custom backend, ensuring a secure and scalable solution. info Learn how to enable [**Firebase Authentication**](/integrations/authentication/firebase/initial-setup.md) and integrate popular auth providers in your FlutterFlow app. ## Supabase Authentication[​](/integrations/authentication-types.md#supabase-authentication "Direct link to Supabase Authentication") In FlutterFlow, you can also integrate Supabase to manage authentication efficiently. Supabase provides a powerful and flexible authentication solution, similar to Firebase but with some unique advantages like support for PostgreSQL. info Discover how to set up [**Supabase Authentication**](/integrations/authentication/supabase/initial-setup.md) and link it with available auth providers within your FlutterFlow app. ## Custom Authentication[​](/integrations/authentication-types.md#custom-authentication "Direct link to Custom Authentication") In FlutterFlow, you have the flexibility to implement custom authentication solutions tailored to your specific needs. This allows for a highly personalized approach to security, enabling you to design and integrate authentication mechanisms that perfectly fit the unique requirements of your application. Whether you need to work with legacy systems or have specific security protocols, custom authentication provides the necessary control. info Explore how to implement [custom authentication](/integrations/authentication/custom-authentication.md) strategies in your FlutterFlow app, ensuring your authentication flow aligns with your business requirements. --- # Custom Authentication Custom authentication allows you to manage auth-related data (login details) while utilizing your own backend to authenticate users. concepts Understanding the concept of [**Token**](/integrations/authentication/tokens.md) is essential for grasping how secure access and user verification work in an application. ## Adding custom authentication[​](/integrations/authentication/custom-authentication.md#adding-custom-authentication "Direct link to Adding custom authentication") Let's see how to add custom authentication by building an example that looks like this: The steps to add custom authentication are as follows: 1. [Enabling custom authentication](/integrations/authentication/custom-authentication.md#1-enabling-custom-authentication) 2. [Building pages](/integrations/authentication/custom-authentication.md#2-building-pages) 3. [Authenticate users](/integrations/authentication/custom-authentication.md#3-authenticate-users) 4. [Save auth data](/integrations/authentication/custom-authentication.md#4-save-auth-data) 5. [Access auth data](/integrations/authentication/custom-authentication.md#5-access-auth-data) 6. [Update auth data](/integrations/authentication/custom-authentication.md#6-update-auth-data) 7. [Logout](/integrations/authentication/custom-authentication.md#7-logout) ### 1. Enabling custom authentication[​](/integrations/authentication/custom-authentication.md#1-enabling-custom-authentication "Direct link to 1. Enabling custom authentication") To enable custom authentication in FlutterFlow: 1. Open **Setting and Integrations** () **>** **App Settings > Authentication**. 2. Turn on the **Enable Authentication** toggle and set **Authentication Type** to **Custom**. 3. To ensure that your users are directed to the appropriate pages based on their login status, you must set the initial pages. 4. By default, the **Persist Auth Sessions** option is enabled, which means users remain logged in until they actively log out. With this option enabled, your app will automatically open to the homepage whenever it's restarted. 5. After successful authentication, your backend typically sends login details like an authentication token, a refresh token, and user details. To keep the user logged in within your app, you must store this data. You can achieve this by enabling **Associate User Data Type** and setting **User Data Type** to the [Custom Data Type](/resources/data-representation/custom-data-types.md). **Note** that the structure of your Custom Data Type should closely resemble the structure of a successful authentication's JSON response. At the very least, it should include critical fields like the authentication token. ### 2. Building pages[​](/integrations/authentication/custom-authentication.md#2-building-pages "Direct link to 2. Building pages") Let's add a page that allows users to create accounts and log in. To speed up, you can add a page from the template. Here is the page added from the templates, and after some modification, it looks the below: Also, see how to [build a page layout](/concepts/layouts.md) in case you want to build a page from scratch. ![auth-2-template.avif](/assets/images/auth-2-template-99e25264064ae6a07dac7abe7788f881.avif) ### 3. Authenticate users[​](/integrations/authentication/custom-authentication.md#3-authenticate-users "Direct link to 3. Authenticate users") On each page, on click of a button, you can add appropriate authentication related [API calls](/resources/backend-logic/rest-api.md). For this example, we use [this](https://dummyjson.com/docs/auth). ### 4. Save auth data[​](/integrations/authentication/custom-authentication.md#4-save-auth-data "Direct link to 4. Save auth data") After successful authentication, you can save the auth related data using the 'Log in' action. Here's how you do it: 1. Inside the **TRUE** branch of the [previous API call](/integrations/authentication/custom-authentication.md#3-authenticate-users), add the **Log in** (under *Backend/Database > Custom Authentication*) action. 2. Under the **User Auth Properties**, you can set values for **Authentication Token**, **Refresh Token**, **Token Expiry Time**, and **User UID**. **Note that for the 'Persist Auth Sessions' option to work, you must set the Authentication Token**. 3. **Set User Data** to store the result of the previous API call (i.e., auth details) in a Custom Data Type. See how to get the [JSON into Data Type](/resources/backend-logic/rest-api.md#json-to-data-type). ### 5. Access auth data[​](/integrations/authentication/custom-authentication.md#5-access-auth-data "Direct link to 5. Access auth data") To access the auth data after a user logs in, open the **set from variable** menu **> Authenticated User >** choose **from Auth Properties** or **User Data Fields**. ### 6. Update auth data[​](/integrations/authentication/custom-authentication.md#6-update-auth-data "Direct link to 6. Update auth data") You may want to update the auth data in situations like updating the access token with the new one after it has expired. You can do so using the **Update Authenticated User** action. Here's exactly how you do it: 1. Once you get the 401 status code, i.e., unauthorized user error, ensure to make an API call to renew the access token. 2. On getting the new access token, add a new action named **Update Authenticated User**. 3. Under the **User Auth Properties**, you can update a value for the **Authentication Token** with a new access token. ![update-auth-data.avif](/assets/images/update-auth-data-454adf49f58fd1bf0ee90a499bdcccee.avif) ### 7. Logout[​](/integrations/authentication/custom-authentication.md#7-logout "Direct link to 7. Logout") You can logout a user by adding the **Log Out** action. ![logout.avif](/assets/images/logout-2c596eacb8601f332f9a25c382850528.avif) --- # Anonymous Login Prerequisites Before getting started with this section: * Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) * Complete [**Initial Setup**](/integrations/authentication/firebase/initial-setup.md) required for authentication. * Learn more about the concepts of [**Anonymous Authentication**](/integrations/authentication-methods.md#anonymous-authentication) ## Enable Anonymous Authentication in Firebase[​](/integrations/authentication/firebase/anonymous-login.md#enable-anonymous-authentication-in-firebase "Direct link to Enable Anonymous Authentication in Firebase") To enable Anonymous authentication, first go to your Firebase console and enable the authentication provider: ## Add Anonymous Login Action[​](/integrations/authentication/firebase/anonymous-login.md#add-anonymous-login-action "Direct link to Add Anonymous Login Action") 1. On the button designated for anonymous authentication, add a new Action. 2. Search for and select the **Log In** action (located under Backend/Database > Firebase Authentication). 3. Set the Auth Provider to **Anonymous**. 4. Enable the **Create User Document** toggle and set the Collection to *users*. This action will create an entry for the user in the database without any details upon successful login. info To let users log out of your app, you can use the [**Logout**](/integrations/authentication/firebase/auth-actions.md#logout-action) action. --- # Apple Login Apple Sign-In allows users to authenticate using their Apple Accounts. Support Apple sign-in functionality is only supported for iOS. Prerequisites Before getting started with this section: 1. Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). 2. Complete [**Initial setup**](/integrations/authentication/firebase/initial-setup.md) required for authentication. 3. Created an [**Apple account**](https://appleid.apple.com/account?appId=632\&returnUrl=https%3A//developer.apple.com/account/). 4. [**Purchased an Apple Developer membership**](https://developer.apple.com/programs/enroll/). Read more about the [**Apple Developer Program**](https://developer.apple.com/programs/) and how to sign up. 5. Apple sign-In can not be tested in Run Mode. You will need to test it on a real device or emulator. Try with Local Run! ## Adding Apple sign-in[​](/integrations/authentication/firebase/apple.md#adding-apple-sign-in "Direct link to Adding Apple sign-in") Adding Apple sign-in comprises of the following steps: 1. [Configure email communication](/integrations/authentication/firebase/apple.md#1-configure-email-communication) 2. [Enable Apple sign-in in your App ID](/integrations/authentication/firebase/apple.md#2-enable-apple-sign-in-in-your-app-id) 3. [Enabling Apple sign-in in Firebase](/integrations/authentication/firebase/apple.md#3-enabling-apple-sign-in-in-firebase) 4. [Add an Apple sign-in button](/integrations/authentication/firebase/apple.md#4-add-an-apple-sign-in-button) 5. [Add login action](/integrations/authentication/firebase/apple.md#5-add-login-action) 6. [Adding logout action](/integrations/authentication/firebase/apple.md#6-adding-logout-action) 7. [Preparing to test the app](/integrations/authentication/firebase/apple.md#7-preparing-to-test-the-app) 8. [Verify user creation](/integrations/authentication/firebase/apple.md#8-verify-user-creation) ### 1. Configure email communication[​](/integrations/authentication/firebase/apple.md#1-configure-email-communication "Direct link to 1. Configure email communication") "Apple sign-in" is a privacy-focused authentication system. One of its notable features is the ability to hide a user's real email address when signing up for apps and services. When users choose to hide their email, you get one random email address that forwards to the user's actual Apple ID email. This helps users keep their real email addresses private. ![User opting to hide the email address](/assets/images/opt-to-hide-email-9ce8755a705492a463894be5e304fef7.png) So, in order to contact such users, you must register email sources that your organization will use for communication. info Also, If you use any of the Firebase Authentication features that send emails to users, including email link sign-in, email address verification, etc., you must add `noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com` as well. To register email sources: 1. From your Apple developer account, open the [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list) page and select [services](https://developer.apple.com/account/resources/services/list). 2. Under the 'Sign in with Apple for Email Communication,' click on the **Configure** button. 3. Click on the **(+)** button on the right side of **Email Sources**. 4. Enter the email in the **Email Addresses** section and click **Next**. 5. Now click on **Register** and then the **Done** button. ### 2. Enable Apple sign-in in your App ID[​](/integrations/authentication/firebase/apple.md#2-enable-apple-sign-in-in-your-app-id "Direct link to 2. Enable Apple sign-in in your App ID") Here's how you do it: 1. From your *Apple developer account*, open the [Identifiers](https://developer.apple.com/account/resources/identifiers/list) section. 2. Open the identifier with your existing APP ID. 3. Select **Sign In with Apple** from the list. 4. Click **Save**. ### 3. Enabling Apple sign-in in Firebase[​](/integrations/authentication/firebase/apple.md#3-enabling-apple-sign-in-in-firebase "Direct link to 3. Enabling Apple sign-in in Firebase") To enable Apple authentication in the Firebase: 1. Open the [Firebase console](https://console.firebase.google.com/) and click on **Authentication**. 2. Click on the **Get started** button (this may not be visible if you have already set up other forms of Authentication). 3. Select the **Sign-in method** tab. 4. Click on **Apple** (Under the 'Additional Providers' section). If you have already added any other provider, click on the **Add new provider** and then click on **Apple**. 5. Find the **Apple** switch and enable it. 6. Click on the **Save** button. ### 4. Add an Apple sign-in button[​](/integrations/authentication/firebase/apple.md#4-add-an-apple-sign-in-button "Direct link to 4. Add an Apple sign-in button") To allow users to authenticate, you need a login page with a button. You can create your own or use the one from the widget template or page template. Here's how you can add the Apple sign-in button from our page template: ### 5. Add login action[​](/integrations/authentication/firebase/apple.md#5-add-login-action "Direct link to 5. Add login action") When you click the Apple sign-in button, it will trigger the 'Log In' action, prompting users to provide their Apple ID credentials. To add login action: 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu) and select **Add Action**. 3. Search and select the **Log in** (under *Backend/Database > Firebase Authentication*) action. 4. Set **Auth Provider** to **Apple**. 5. Tick the **Create User Document** and set the **Collection** to **users**. After successful login, this will insert the user's email address into the 'users' collection. If a user already exists, it won't add details again. ### 6. Adding logout action[​](/integrations/authentication/firebase/apple.md#6-adding-logout-action "Direct link to 6. Adding logout action") To let users log out of your app, you can use the [Logout](/integrations/authentication/firebase/auth-actions.md#logout-action) action. ### 7. Preparing to test the app[​](/integrations/authentication/firebase/apple.md#7-preparing-to-test-the-app "Direct link to 7. Preparing to test the app") For testing your app on a real device, you must configure the project in Xcode. This includes adding a team to your project and setting an appropriate signing certificate. Here's how you configure your project in Xcode: 1. From the Local Run, [open your project in Xcode](/testing/local-run.md#access-project-code). tip If you are using Android Studio, right-click on the **ios** folder, find **Flutter,** and then click on the **Open iOS module in Xcode**. 2. In Xcode, click on **Runner** (left side menu) and then select the **Signing and Capabilities** tab. 3. We recommend choosing the **Automatically manage signing** option. This will auto-create the profiles, app ID, and certificates required to build and run your app. If you don't, you'll have to [manually create a 'provisioning profile'](https://blog.codemagic.io/distributing-native-ios-sdk-with-flutter-module-using-codemagic/) and then add it in the Xcode. 4. Under the **Signing** section, find the **Team** dropdown and select your team. 5. Use [Local Run](/testing/local-run.md) to test the app on a real device. ### 8. Verify user creation[​](/integrations/authentication/firebase/apple.md#8-verify-user-creation "Direct link to 8. Verify user creation") Run and test your app. To confirm the successful integration of Apple authentication and the creation of users, navigate to your **Firebase project > Authentication > Users** and check the user entries. --- # Common Auth Actions Here's a list of common authentication actions: ## Logout \[Action][​](/integrations/authentication/firebase/auth-actions.md#logout-action "Direct link to Logout \[Action]") This action enables users to securely log out of their account and clear their session data from the app, which ensures that their account remains safe and secure. Follow the steps below to add this action: 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Logout** (under *Backend/Database > Firebase Authentication*) action. ![logout](/assets/images/logout-action-83a4b4ad85e39bbdfd4f272f230f599f.png) ## Reset Password[​](/integrations/authentication/firebase/auth-actions.md#reset-password "Direct link to Reset Password") With Firebase Authentication, there are two ways you can allow users to reset their password in your FlutterFlow app: ### In-App Password Change[​](/integrations/authentication/firebase/auth-actions.md#in-app-password-change "Direct link to In-App Password Change") This option allows users to change their password while they are logged into the app. This is useful when a user is authenticated but wants to update their password for security reasons. To implement this, create a new page in your app, such as a **ChangePassword** page. This page should include two **TextFields** for the user to enter a new password and confirm it, along with a button (e.g., **Update Password**) to submit. On the button's click, add the **Update Password** action (under *Backend/Database > Firebase Authentication*) and bind the **Password Field** and **Confirm Password Field** to their respective input widgets. ![firebase-update-password.avif](/assets/images/firebase-update-password-adcf2e4b82e68e4bc9a690bf398281e9.avif) info By default, the **Navigate Automatically** option is enabled. This means that after the password is successfully updated, the user will be redirected to the **Logged In Page** specified in your [**Initial Page**](/resources/projects/settings/general-settings.md#initial-page) settings. ### Reset Password Link[​](/integrations/authentication/firebase/auth-actions.md#reset-password-link "Direct link to Reset Password Link") This allows users who are logged out to reset their password. It sends a password reset link to the user's email address. When clicked, the user is directed to a Firebase-hosted webpage where they can set a new password. To set this up, create a page in your app, such as a **ForgotPassword** page. This page should include a **TextField** for the user to enter their email address and a button (e.g., **Send Reset Link**) to submit the request. On the button's click, add the **Send Reset Password Email** action (under *Backend/Database > Firebase Authentication*) and set the **Email Field** dropdown to the widget that takes user’s email. This action will send a password reset link to the provided email address. ![firebase-send-reset-link.avif](/assets/images/firebase-send-reset-link-017287b02614912f3a5e9e3d90e33e8d.avif) ## Update Email \[Action][​](/integrations/authentication/firebase/auth-actions.md#update-email-action "Direct link to Update Email \[Action]") This action allows users to change their registered email address linked to their user profile, thus ensuring their account details are up-to-date. This is helpful in scenarios where a user may have changed their primary email address or entered an incorrect one during initial registration. Also, if users lose access to their original email or forget their login credentials, being able to update their email addresses can assist in resetting passwords or recovering account access. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Update Email** (under *Backend/Database > Firebase Authentication*) action. 4. As a best practice, it's also recommended to send the email verification link to the new email (using the [e-mail verification](/integrations/authentication/firebase/email-login.md#send-email-verification-link-action) action) followed by this action. ![adding-update-email-action](/assets/images/adding-update-email-action-fd7c5ad244258b04db50c45d4e8a50a4.avif) ## Delete User \[Action][​](/integrations/authentication/firebase/auth-actions.md#delete-user-action "Direct link to Delete User \[Action]") Using this action, you can delete the user account created using the [Firebase authentication](/integrations/authentication/firebase/initial-setup.md). Additionally, you can also set up to delete all data associated with that user. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Delete User** (under *Backend/Database > Firebase Authentication*) action. 4. As a best practice, it's also recommended to log out the user (using the [logout](/integrations/authentication/firebase/auth-actions.md) action) following this action. ![adding-delete-action](/assets/images/adding-delete-action-caeaca8b26c96db25760f62310cfb042.avif) 5. To delete all records and data associated with that user's account: 1. Navigate to the **Firestore** (from the Navigation Menu) > switch to **Firestore Settings** > **Firestore Rules**. 2. Identify the collection from which you want to delete the user's data and ensure the **Delete** rule is set to **Tagged Users**. This will open the 'Tag Users' popup; here you can select the field that contains the document reference. See how to [setup a rule](/integrations/database/cloud-firestore/firestore-rules.md). 3. Tick the checkbox. 4. See the **Delete User References** section and click on **Preview** to verify the generated rule. 5. Click the **Deploy** button. ## FAQs[​](/integrations/authentication/firebase/auth-actions.md#faqs "Direct link to FAQs") While adding Delete User \[Action], I can't see or select field in 'Tag Users' popup If you can't see or select the field containing the user reference, ensure that you have enabled the 'Create User Document' option in the **Create Account** action. Enabling this option ensures that the 'users' collection is properly set up and its reference can be accessed in the 'Tag Users' popup. --- # Email Login using Firebase Prerequisites Before getting started with this section: * Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) * Complete [**Initial Setup**](/integrations/authentication/firebase/initial-setup.md) ## Enable Email Login Provider in Firebase[​](/integrations/authentication/firebase/email-login.md#enable-email-login-provider-in-firebase "Direct link to Enable Email Login Provider in Firebase") 1. Open the Firebase Console and click on **Authentication** 2. Click on the Get started button (this may not be visible if you have already set up other forms of Authentication). 3. Select the **Sign-in** method tab. 4. Click on Email/Password (Under the 'Native providers' section). If you have already added any other provider, click on Add new provider and then click on Email/Password. 5. Find the Email/Password switch and enable it. 6. Click on the Save button. ## Add a Login Screen with Email/Password Fields[​](/integrations/authentication/firebase/email-login.md#add-a-login-screen-with-emailpassword-fields "Direct link to Add a Login Screen with Email/Password Fields") In FlutterFlow, you can utilize the Page Templates feature to create a new authentication page that includes both a "Create Account" component and a "Log In" component. This setup aligns with Firebase's authentication process, which requires users to first create an account using their email and then allows them to sign in using the email ID they registered with. ## Create Account Action[​](/integrations/authentication/firebase/email-login.md#create-account-action "Direct link to Create Account Action") The Create Account action is the entry point for new users in any application. It's about establishing a user's credentials and granting them access for the first time. This action involves collecting necessary information such as email, password, and potentially other user-specific details like name or phone number. The primary goal is to register and store new user data securely in your backend or authentication service (like Firebase). This process typically includes steps like validating the data format (e.g., email format), checking for unique usernames or email addresses etc. To enable this in FlutterFlow, follow these steps: 1. Create a page using Page Templates or from scratch, and add fields such as Email, Password, and Confirm Password. Based on your requirements, you may add additional fields. 2. Add a "Create Account" or "Sign Up" button and attach an action to it. 3. Search for and select the **Create Account** action under **Backend/Database > Firebase Authentication**. 4. Set the **Auth Provider to Email**. 5. Configure the fields to retrieve values from variables, which are usually found under Widget State > Field Name. 6. The **Create User document** is enabled by default. This means a user document will be created in the 'users' collection after the user is authenticated, if it does not already exist with details like email and UID. * To create a user document in a different collection, adjust the **Created Document > Collection** dropdown to the desired collection. * If additional details such as name, age, and birthday are needed at signup, click on the **+ Add Field** and set its value. Make sure these fields are already created in the 'users' collection. ![create-account-action.png](/assets/images/create-account-action-f8867c7b7b34ca3a6767314b5d86415c.png) ## Send Email Verification Link \[Action][​](/integrations/authentication/firebase/email-login.md#send-email-verification-link-action "Direct link to Send Email Verification Link \[Action]") info To understand why email verification is required when authenticating with an email and password, refer to [**Authentication Methods**](/integrations/authentication-methods.md) 1. Add a new action immediately after the **Create Account** action. 2. Search for and select the **Send Email Verification Link** (located under **Backend/Database > Firebase Authentication**) action. The user's email is automatically retrieved from Firebase Authentication, and a verification link is sent to the user for confirmation. [Send Email Verification Link](https://demo.arcade.software/3aDUDdUKXWmpBPiTO5oe?embed\&show_copy_link=true) The user should receive an email verification link in their inbox. Upon successful verification, they will see a success message. ## Log In \[Action][​](/integrations/authentication/firebase/email-login.md#log-in-action "Direct link to Log In \[Action]") The **Log In** action, on the other hand, is for users who already have an account. It involves verifying the credentials provided by a returning user against stored data to grant access to the system. This action is crucial for maintaining secure access control as it ensures that the entity attempting to gain access is indeed who they claim to be. The process usually requires users to provide their registered email and password, which are then checked for correctness through your authentication system. To enable this in FlutterFlow, follow these steps: 1. Create another Log In page using Page Templates or from scratch, and add fields such as Email, Password. 2. Add a "Log In" button and attach an action to it. 3. Search for and select the **Log In** action under **Backend/Database > Firebase Authentication**. 4. Configure the fields to retrieve values from variables, which are usually found under Widget State > Field Name. ![login-action.png](/assets/images/login-action-a2b64c94132eea7dee3cfaa7eee87893.png) info To let users log out of your app, you can use the [**Logout**](/integrations/authentication/firebase/auth-actions.md#logout-action) action. ### Verify user created in Firebase Dashboard[​](/integrations/authentication/firebase/email-login.md#verify-user-created-in-firebase-dashboard "Direct link to Verify user created in Firebase Dashboard") To verify that you have successfully added the email authentication and that users are being created, you can head over to your **Firebase project > Authentication > Users** and verify the user entries. --- # Facebook Login Facebook login allows users to authenticate using their Facebook Accounts. Prerequisites Before getting started with this section: * Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) * Complete [**Initial Setup**](/integrations/authentication/firebase/initial-setup.md) ## Adding Facebook sign-in[​](/integrations/authentication/firebase/facebook.md#adding-facebook-sign-in "Direct link to Adding Facebook sign-in") Adding Facebook sign-in comprises the following steps: 1. [Create app on Facebook](/integrations/authentication/firebase/facebook.md#1-create-app-on-facebook) 2. [Configure app on Facebook](/integrations/authentication/firebase/facebook.md#2-configure-app-on-facebook) 3. [Add email permission](/integrations/authentication/firebase/facebook.md#3-add-email-permission) 4. [Enabling Facebook authentication in Firebase](/integrations/authentication/firebase/facebook.md#4-enabling-facebook-authentication-in-firebase) 5. [Enabling Facebook authentication in FlutterFlow](/integrations/authentication/firebase/facebook.md#5-enabling-facebook-authentication-in-flutterflow) 6. [Add a Facebook sign-in button](/integrations/authentication/firebase/facebook.md#6-add-a-facebook-sign-in-button) 7. [Add login action](/integrations/authentication/firebase/facebook.md#7-add-login-action) 8. [Add logout action](/integrations/authentication/firebase/facebook.md#8-add-logout-action) 9. [Prepare to test the app](/integrations/authentication/firebase/facebook.md#9-prepare-to-test-the-app) 10. [Verify user creation](/integrations/authentication/firebase/facebook.md#10-verify-user-creation) ### 1. Create app on Facebook[​](/integrations/authentication/firebase/facebook.md#1-create-app-on-facebook "Direct link to 1. Create app on Facebook") When you create an app on the [Facebook Developer Console](https://developers.facebook.com/), you are given a unique *App ID* and *App secret*, ensuring secure communication between your app and Facebook's servers. Additionally, it lets you define required permissions and user data access and also restricts login origins for enhanced security. Here's is how you create app on Facebook: ### 2. Configure app on Facebook[​](/integrations/authentication/firebase/facebook.md#2-configure-app-on-facebook "Direct link to 2. Configure app on Facebook") Now, you must add and configure platforms that will support Facebook authentication - For example, Android and iOS. To do so follow the steps below: * Configure Android App * Configure iOS App ### 3. Add email permission[​](/integrations/authentication/firebase/facebook.md#3-add-email-permission "Direct link to 3. Add email permission") When users log in using third-party providers (like Google or Facebook), fetching their email addresses reduces the steps they need to take during sign-up. For Facebook sign-in, to access a user's email, you must add email permission in Firebase developer console. Here's how you do it: ### 4. Enabling Facebook authentication in Firebase[​](/integrations/authentication/firebase/facebook.md#4-enabling-facebook-authentication-in-firebase "Direct link to 4. Enabling Facebook authentication in Firebase") Here's how you enable Facebook auth in Firebase: ### 5. Enabling Facebook authentication in FlutterFlow[​](/integrations/authentication/firebase/facebook.md#5-enabling-facebook-authentication-in-flutterflow "Direct link to 5. Enabling Facebook authentication in FlutterFlow") To enable the Facebook authentication in FlutterFlow, follow the steps below: ### 6. Add a Facebook sign-in button[​](/integrations/authentication/firebase/facebook.md#6-add-a-facebook-sign-in-button "Direct link to 6. Add a Facebook sign-in button") To allow users to authenticate, you need a login page with a button. You can create your own or use the one from the widget template or page template. ### 7. Add login action[​](/integrations/authentication/firebase/facebook.md#7-add-login-action "Direct link to 7. Add login action") When you click the sign-in button, it will trigger the 'Log In' action, prompting users to provide their Facebook credentials. info Switch on the **Create User Document** and set the **Collection** to **users**. After successful login, this will insert the user's email address into the 'users' collection. If a user already exists, it won't add details again. ### 8. Add logout action[​](/integrations/authentication/firebase/facebook.md#8-add-logout-action "Direct link to 8. Add logout action") To let users log out of your app, you can use the [Logout](/integrations/authentication/firebase/auth-actions.md#logout-action) action. ### 9. Prepare to test the app[​](/integrations/authentication/firebase/facebook.md#9-prepare-to-test-the-app "Direct link to 9. Prepare to test the app") Facebook Sign-In functionality does not work in Run or Test Mode. You can test your app on a real device or emulator using FlutterFlow’s Local Run. Follow the [Local Run documentation](/testing/local-run.md) and see [how to set up a physical device](/testing/local-run.md#setup-physical-device) to start testing. ### 10. Verify user creation[​](/integrations/authentication/firebase/facebook.md#10-verify-user-creation "Direct link to 10. Verify user creation") To confirm the successful integration and the creation of users, navigate to your **Firebase project > Authentication > Users** and check the user entries. --- # GitHub Login The GitHub auth provides a convenient way for users to authenticate and log in to your application using their GitHub accounts. ![github-demo.gif](/assets/images/github-demo-41380054c31b666044e4811ef9c1ffad.gif) Prerequisites Before getting started with this section: * Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). * Complete [**Initial setup**](/integrations/authentication/firebase/initial-setup.md) required for authentication. ## Adding GitHub auth[​](/integrations/authentication/firebase/github.md#adding-github-auth "Direct link to Adding GitHub auth") Adding GitHub auth comprises of following steps: 1. [Enabling GitHub authentication in Firebase](/integrations/authentication/firebase/github.md#1-enabling-github-authentication-in-firebase) 2. [Adding GitHub login action](/integrations/authentication/firebase/github.md#2-adding-github-login-action) ### 1. Enabling GitHub Authentication in Firebase[​](/integrations/authentication/firebase/github.md#1-enabling-github-authentication-in-firebase "Direct link to 1. Enabling GitHub Authentication in Firebase") To enable GitHub authentication in the Firebase: 1. Open the [Firebase console](https://console.firebase.google.com/), Click on **Authentication** ( in the left side menu). 2. Select the **Sign-in method** tab, and select **GitHub**. If you have already added another provider, click on the **Add new provider**, select **GitHub**, and **Enable** it. 3. To get the **Client ID** and **Client Secret**, [register your app](https://github.com/settings/applications/new) as a developer application on GitHub, and while doing so, paste the authorization callback URL to your GitHub app configuration. 4. Click **Save**. 5) To test the app in Run Mode, add our domain to **Authorized domains**. ![adding-authorized-domain-2.png](/assets/images/adding-authorized-domain-2-ffe3326be53615349de80ceabb861d10.png) ### 2. Adding GitHub Login Action[​](/integrations/authentication/firebase/github.md#2-adding-github-login-action "Direct link to 2. Adding GitHub Login Action") Follow the steps below to add GitHub login action: 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Login** (under *Backend/Database > Firebase Authentication*) action. 4. Set **Auth Provider** to **GitHub**. ![adding-github-login-action.png](/assets/images/adding-github-login-action-3b533ba2182aeb6b1ef822f4c605aa3c.png) info To let users log out of your app, you can use the [**Logout**](/integrations/authentication/firebase/auth-actions.md#logout-action) action. --- # Google Login Google Sign-In allows users to authenticate using their Google Accounts. Prerequisites Before getting started with this section: * Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) * Complete [**Initial Setup**](/integrations/authentication/firebase/initial-setup.md) * Added **SHA-1 key** and regenerated **Config Keys**. ## Enable Google Sign-in Provider in Firebase[​](/integrations/authentication/firebase/google-oauth-login.md#enable-google-sign-in-provider-in-firebase "Direct link to Enable Google Sign-in Provider in Firebase") Open the **Firebase Console**, click on **Authentication** and then follow the steps below to enable Google Sign in for your Firebase project. ## Add a Login Screen with Google Login Action[​](/integrations/authentication/firebase/google-oauth-login.md#add-a-login-screen-with-google-login-action "Direct link to Add a Login Screen with Google Login Action") ### Create a Login Screen[​](/integrations/authentication/firebase/google-oauth-login.md#create-a-login-screen "Direct link to Create a Login Screen") To allow users to authenticate, you need a Login or Sign-in Page with a button. You can create your own or use the one from page templates. ### Add Login Action[​](/integrations/authentication/firebase/google-oauth-login.md#add-login-action "Direct link to Add Login Action") 1. On your Google Login button, select **Actions** from the properties panel (the right menu) and select **Add Action**. 2. Search and select the Log In (under **Backend/Database > Firebase Authentication**) action. 3. Set **Auth Provider** to **Google**. 4. Enable **Create User Document** and set the **Collection** to **users**. After successful login, this will insert the user's details, such as email, name, and photo, into the *users* collection. **Note** that, if a user exists already, it won't add the details again. If you haven’t already, see how to [create *users* collection](/integrations/authentication/firebase/initial-setup.md#creating-the-users-collection). tip To let users log out of your app, you can use the [**Logout**](/integrations/authentication/firebase/auth-actions.md#logout-action) action. ## Test Google Login[​](/integrations/authentication/firebase/google-oauth-login.md#test-google-login "Direct link to Test Google Login") ### Running on Device[​](/integrations/authentication/firebase/google-oauth-login.md#running-on-device "Direct link to Running on Device") To test during development, you can run your app locally using FlutterFlow’s Local Run. Follow the [Local Run documentation](/testing/local-run.md) and see [how to set up a physical device](/testing/local-run.md#setup-physical-device) to start testing. ### Running on Test Mode/Run Mode[​](/integrations/authentication/firebase/google-oauth-login.md#running-on-test-moderun-mode "Direct link to Running on Test Mode/Run Mode") 1. To test Google sign-in in Test or Run mode, you must add the authorized domain in the Firebase console and Google cloud console. * **For Test mode**, you can open the browser console, try logging in, and get the domain from the browser console. It should look like `ff-debug-service-frontend-ygxkweukma-uc.a.run.app`. For *Pro* users, the above URL will also include `-pro`, such as `ff-debug-service-frontend-pro-ygxkweukma-uc.a.run.app`. * **For Run mode**, you can simply use 'app.flutterflow\.io'. 2. To add in Firebase console: 1. Open the Firebase console and click on Authentication and select the Setting tab. 2. Select **Authorized domains** from the left side menu. 3. Click **Add domain**. 3. To add in Google cloud console: 1. Head over to your [Project Credentials](https://console.cloud.google.com/apis/credentials?project=_) page. 2. Ensure you are on the correct project. In our case, we are using the [EcommerceFlow demo project](https://bit.ly/ff-docs-demo-v2), it will be different for you. ![credential-page.png](/assets/images/credential-page-06a701a56039dabdf631d49eb9a63a87.png) 3. Under the '**OAuth 2.0 Client IDs**', select '**Web client** (auto created by Google Service)'. 4. Under the '**Authorized JavaScript origins**', click ADD URI and add both the URL. 5. Similarly, under the '**Authorized redirect URIs**', click ADD URI, add both the URL and append '/\_\_/auth/handler' at the end. 4) If you don't see the Web client created yet, you can create new one by clicking **+ CREATE CREDENTIALS**, selecting OAuth client ID and then select Application type to Web application. ![add-app.gif](/assets/images/add-app-495cc4d69983deb104c266378e8402a1.gif) ### Verify user created in Firebase Dashboard[​](/integrations/authentication/firebase/google-oauth-login.md#verify-user-created-in-firebase-dashboard "Direct link to Verify user created in Firebase Dashboard") To confirm the successful integration of Google authentication and the creation of users, navigate to your **Firebase project > Authentication > Users** and check the entries. ![verify-google-auth-users.png](/assets/images/verify-google-auth-users-1b112d385f9937e80e9bb28c3ce22893.png) info To ensure that your Android release will authenticate to Google, make sure to use Google Play Console's SHA keys - see how to [**Get SHA keys for release mode**](/integrations/authentication/firebase/initial-setup.md#getting-sha-keys-for-release-mode). --- # Enabling Firebase Auth in FlutterFlow Skip if... You have already enabled authentication while creating a [**new project with Firebase setup.**](/integrations/firebase/connect-to-firebase.md) To enable authentication in FlutterFlow: 1. Open your FlutterFlow project where you are planning to use Firebase Authentication. 2. Open **Setting and Integrations > App Settings > Authentication**. 3. Turn on the Enable Authentication toggle and select **Authentication Type** to **Firebase**. 4. To ensure that your users are directed to the appropriate pages based on their login status, you must set the **Initial Page**. ![enable-auth-fr.png](/assets/images/enable-auth-fr-ae82ed0ec3ad5d152c5fbb0c1d1a2852.png) ### Setting Initial Pages for Authentication[​](/integrations/authentication/firebase/initial-setup.md#setting-initial-pages-for-authentication "Direct link to Setting Initial Pages for Authentication") You can specify your app's **Entry Page** and **Logged In Page** from this section. * **Entry Page** : This page will be displayed if the user is not logged in. This is typically used to display the onboarding flow or to provide the login/sign-up page. * **Logged In Page**: This page will be displayed if the user is already logged in to your app. Users are automatically navigated to the page you specify here on a successful sign-in attempt. ## Creating the 'users' collection[​](/integrations/authentication/firebase/initial-setup.md#creating-the-users-collection "Direct link to Creating the 'users' collection") Prerequisities To allow FlutterFlow to create user documents during authentication steps, it is important to enable Firestore Access in Firebase. Follow this section to enable it first. The 'users' collection stores the information for authenticated users. Skip if... You have already enabled 'Create User Collection' while creating a new project with [Firebase Setup](/integrations/firebase/connect-to-firebase.md). 1. Click on the Firestore tab from the [**Navigation Menu**](/flutterflow-ui/builder.md#navigation-menu). 2. Click on the **+ Create Collection** button. If you have any other collection already added, you can click on the Plus button. 3. Enter a collection\_name (this can be anything, but we recommend 'users') and click on Create button. 4. If you enter 'users' a popup will open which asks you to populate this collection with default fields. You can click Yes, and we will add all the fields. Follow the quicklink to see the steps Add Default Fields if skipped previously 1. Click on the Settings icon in the Firestore tab. 2. Find the **Users Collection** switch and enable it. 3. Find the **Collection** dropdown below, click on the **Unset**, and select the name of the collection you just created. 4. Now switch to the **Collection** tab. Now you should see all the default fields. To store and collect additional information or modify the default fields list, see how to add fields. WARNING You do not need to create a password field. This is handled internally by Firebase. ## Setup for Google or Phone sign-in setup for Android Apps[​](/integrations/authentication/firebase/initial-setup.md#setup-for-google-or-phone-sign-in-setup-for-android-apps "Direct link to Setup for Google or Phone sign-in setup for Android Apps") OPTIONAL If you aren't planning to use **Google** or **Phone Sign-In**, you can skip these steps. ### Generate the SHA-1 key[​](/integrations/authentication/firebase/initial-setup.md#generate-the-sha-1-key "Direct link to Generate the SHA-1 key") An SHA-1 key (aka the 'Secure Hash Algorithm') is required if you want to use Google Sign-in and Phone Sign-in. To learn more about the SHA-1 key, see this [link](https://developers.google.com/android/guides/client-auth). Release Guidelines While releasing the app, make sure to [**get the key from Play Console**](/integrations/authentication/firebase/initial-setup.md#getting-sha-keys-for-release-mode). 1. Open a terminal window: * **Mac**: Use the Launchpad or press (⌘ + Spacebar) for Spotlight search, type 'Terminal', and open it. * **Windows**: Click the Windows icon, navigate to the 'Windows System' folder, and open 'Command Prompt' either by clicking or right-clicking it. 2. Copy the following command (based on your operating system) and select Enter. Windows `keytool -list -v -keystore C:\Users\leon\.android\debug.keystore -alias androiddebugkey` If you get the following error while trying the above command: `ERROR:'keytool' is not recognized as an internal or external command` You might not have JAVA installed on your machine. [Here](https://codewithandrea.com/articles/keytool-command-not-found-how-to-fix-windows-macos/) is the helpful link to install JAVA and remove the above issue. Mac/Linux `keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore` 3. After being prompted for the key password, type 'android' and press 'Enter'. Note: For security reasons, you won't see the password as you type it. 4. Copy the SHA1 key. #### Add the SHA-1 key in the Firebase Console[​](/integrations/authentication/firebase/initial-setup.md#add-the-sha-1-key-in-the-firebase-console "Direct link to Add the SHA-1 key in the Firebase Console") 1. Open the **Firebase console > Project Overview > Project Settings** and scroll down to Your App section. 2. Select your Android App from the left side menu. 3. Find the SHA certificate fingerprints section and click on the Add fingerprint. 4. Enter the copied SHA-1 into the input box and click on Save. #### Getting SHA keys for release mode[​](/integrations/authentication/firebase/initial-setup.md#getting-sha-keys-for-release-mode "Direct link to Getting SHA keys for release mode") If you're releasing your app to the Play Store, you must add the SHA certificate fingerprints from the Play Console. To get the keys for the release app, navigate to **Play Store Console > Your project > Release Setup > App Signing** and copy the **SHA-1** and **SHA-256** keys. ![release-sha1-key](/assets/images/release-sha1-key-1cfa5eacd6051da6c81cc863811a8c7c.avif) ### Regenerate config files[​](/integrations/authentication/firebase/initial-setup.md#regenerate-config-files "Direct link to Regenerate config files") After adding the SHA-1 key you must re-generate the config files in FlutterFlow. To regenerate the config files: 1. Return to FlutterFlow. From the Navigation Menu, select **Settings & Integrations > Project Setup > Firebase**. 2. Click on the Regenerate Config Files. ![regerenate](/assets/images/regerenate-7b29b05d9cfb34aaa2f3b3800999eb91.png) --- # JWT Token Authentication [JWT](https://jwt.io/introduction) token sign-in allows you to log in and use the Firebase services such as Firebase Database and push notifications using the account created on your own server/backend. ![JWT-login-flow.avif](/assets/images/JWT-login-flow-261eda3f9f9786766286293886e3609b.avif) In JWT token authentication, you send login credentials, like email and password, to your server through an API endpoint. The server then creates a user account, generates a custom JWT token, and returns it to your app. This JWT token allows you to log in to Firebase and access its services. info You can learn more about Firebase and JWT tokens [**here**](https://firebase.google.com/docs/auth/admin/create-custom-tokens). ## Adding JWT token authentication[​](/integrations/authentication/firebase/jwt-auth.md#adding-jwt-token-authentication "Direct link to Adding JWT token authentication") Let's build an example that uses a JWT token to log into the app. Here's how it looks when completed: ![JET-token-authentication.gif](/assets/images/JET-token-authentication-f2de1605e4785a73127e0454ab1a4d27.gif) Prerequisites Before getting started with this section: * Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). * Complete [**Initial setup**](/integrations/authentication/firebase/initial-setup.md) required for authentication. Adding JWT token authentication comprises the following steps: 1. [Add login API](/integrations/authentication/firebase/jwt-auth.md#1-add-login-api) 2. [Adding login page](/integrations/authentication/firebase/jwt-auth.md#2-adding-login-page) 3. [Add login action](/integrations/authentication/firebase/jwt-auth.md#3-add-login-action) 4. [Adding logout action](/integrations/authentication/firebase/jwt-auth.md#4-adding-logout-action) 5. [Verify user creation](/integrations/authentication/firebase/jwt-auth.md#5-verify-user-creation) ### 1. Add login API[​](/integrations/authentication/firebase/jwt-auth.md#1-add-login-api "Direct link to 1. Add login API") You must [create an API](/resources/backend-logic/create-test-api.md) endpoint on your server that accepts email/username and password. If the credentials are valid, it generates the JWT token and passes it back in response. At your server, you can generate the JWT token either using the [Firebase Admin SDK](https://firebase.google.com/docs/auth/admin/create-custom-tokens#create_custom_tokens_using_the_firebase_admin_sdk) or a [third-party JWT library](https://firebase.google.com/docs/auth/admin/create-custom-tokens#create_custom_tokens_using_a_third-party_jwt_library). You can find the detailed instructions [here](https://firebase.google.com/docs/auth/admin/create-custom-tokens). info Alternatively, you can integrate Supabase authentication into your app and use the JWT token generated after [**account creation**](/integrations/authentication/supabase/auth-actions.md#log-in-action). The API endpoint should be similar to the following (Tip: Expand and see the '200 OK' section): #### Login API to be created on your server[​](/integrations/authentication/firebase/jwt-auth.md#login-api-to-be-created-on-your-server "Direct link to Login API to be created on your server") `POST` `/login` ##### Request Body[​](/integrations/authentication/firebase/jwt-auth.md#request-body "Direct link to Request Body") | Name | Type | Description | | ---------- | ------ | ----------- | | email\* | String | | | password\* | String | | ##### 200: OK[​](/integrations/authentication/firebase/jwt-auth.md#200-ok "Direct link to 200: OK") ``` { "user": { "id": 1, "role_id": 1, "name": "james", "email": "james@yopmail.com" }, "token_type": "Bearer", "expires_in": 3600, "jwt_token": "eyJraWQiOiItSE5TUmtwMWdXcG9QcC1wWVBmU1U4UW1fdng4Q0VwdzRSdTZTQU9WLThRIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULi1PaG5EdWREUG9qWklsZjMtVDRVWHlTWW5ERElHQ3dYTUdQcXk1c1JUbjAub2FydGh3ZmxpbzhZOVZJbHc0eDYiLCJpc3MiOiJodHRwczovL2Rldi00NTc5MzEub2t0YS5jb20vb2F1dGgyL2F1c2hkNGM5NVF0RkhzZld0NHg2IiwiYXVkIjoiYXBpIiwiaWF0IjoxNjU5MDAyOTQ5LCJleHAiOjE2NTkwMDY1NDksImNpZCI6IjBvYWhkaGprdXRhR2NJSzJNNHg2IiwidWlkIjoiMDB1aGVuaDFwVkRNZzJ1ZXg0eDYiLCJzY3AiOlsib2ZmbGluZV9hY2Nlc3MiXSwiYXV0aF90aW1lIjoxNjU5MDAyOTQ5LCJzdWIiOiJhcGktdXNlcjRAaXd0Lm5ldCJ9.g2TyTQECo-HCSjn58Fmazki8DBCtCq2hkG6OGQOJgr0JUq3uHgj8ulojoBI5ckv3e3TcVGFg1x9KknSwgiZo0LxRpbAdbF27hfF8truExjEv7hGKoV_oAOaiD56be5K-HjYkp6j-b5S6gXe4N10T1NtovLI7L6MZvmqCL_26qzXni5hNkCjgRm8Rd6GnJwbjDLpV3snp51bVNYNqhoAhOPBqjmOErFQvO2Wmfkj8DuVXzsvRqm_xfb8-7Oosx5oGVMVR3liXW5NZsRWes4TXXwsEou3qCyVy5fAhzm7rKjIk1zWv9vm0IOWMFwHHYTgEc_LTYWMovWtkuBx4ia546Q", "refresh_token": "dlIOQHHAmweyOrVkDlpNYpi1XM-DwX5Cgx70LoKIbTI" } ``` warning In most cases, you would make the app content available right after creating a new account. Hence, you should also generate and return the JWT token on the success of create account API and use it to login into the Firebase. info If you want to try the JWT token authentication without creating an API endpoint right now, you can [**generate the JWT token locally**](/integrations/authentication/firebase/jwt-auth.md#create-a-jwt-token-locally) for testing. ### 2. Adding login page[​](/integrations/authentication/firebase/jwt-auth.md#2-adding-login-page "Direct link to 2. Adding login page") Let's add a sign-in page from the templates and choose the **Authenticate Solo Alt** from under the **Auth** tab. Tip: After adding, remove the other social sign-in buttons. ![login-page.avif](/assets/images/login-page-31990c9ba390dc2395477fa4ac808aa0.avif) ### 3. Add login action[​](/integrations/authentication/firebase/jwt-auth.md#3-add-login-action "Direct link to 3. Add login action") The login process involves two steps. First, you trigger an API call to your server. Upon successful call completion, you'll use the returned JWT token in the JWT Token action. Here are the step by step instructions: 1. Select the **Widget** (e.g., Sign In) on which you want to define the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Add the login api and provide the **Action Output Variable Name**. If the call succeeds, this will be used to retrieve the token. 4. Inside the **TRUE** section, click on the **+** button and select **Add Action**. 5. On the right side, search and select the **Log in** (under Firebase Authentication) action. 6. Set the **Auth Provider** to **JWT token**. 7. Now, you must provide the actual JWT token. To set the token from an API response: 1. Click on the **UNSET** and select the **Action Outputs -> Action Output Variable Name** (that you specified in the API call section.) 2. Set the **API Response Options** to **JSON Body** and **Available Options** to **JSON Path**. 3. Enter the **JSON Path** to locate the token in API response, such as `$.token,` and click **Confirm**. 8. (Optional) add the [snackbar action](/resources/ui/pages/scaffold.md#show-snackbar-action) to display the success message. 9. (Optional) Inside the **False** section, add the snackbar action to display the failure message. ### 4. Adding logout action[​](/integrations/authentication/firebase/jwt-auth.md#4-adding-logout-action "Direct link to 4. Adding logout action") To let users log out of your app, you can use the [Logout](/integrations/authentication/firebase/auth-actions.md#logout-action) action. ### 5. Verify user creation[​](/integrations/authentication/firebase/jwt-auth.md#5-verify-user-creation "Direct link to 5. Verify user creation") To confirm the successful integration and the creation of users, navigate to your **Firebase project > Authentication > Users** and check the user entries. Tip: Notice the 'userid' (originally created by your server) is added inside the **User UID** column. ## Create a JWT token locally[​](/integrations/authentication/firebase/jwt-auth.md#create-a-jwt-token-locally "Direct link to Create a JWT token locally") Sometimes you might want to build and test the JWT authentication before the login or create account API is ready. You can achieve this by creating the JWT token locally and passing it inside the [login action](/integrations/authentication/firebase/jwt-auth.md#3-add-login-action). warning Use this method only for testing purposes. Ideally, you should be doing this on the server side. Below are steps to create a JWT token locally using Node.js: 1. In the Firebase dashboard of your project, navigate to the far left menu. Select **Project Settings( )** -> **Service accounts**. 2. Select **Generate new private key**. This will open a new popup. Again, click **Generate key** and save the `.json` file in some folder. You will need it while generating the token. 3. Now, download and Install [node.js](https://nodejs.org/en/download/). 4. Open a terminal at the folder where you have saved the `.json` file and enter this command: `npm install firebase-admin`. This will install Firebase Admin SDK inside the folder. 5. In the same folder, create an `index.js` file and add the below content. ``` const admin = require('firebase-admin'); const ServiceAccount = require('./[YOUR_SERVICE_ACCOUNT_JSON_FILE_NAME].json'); admin.initializeApp({ credential: admin.credential.cert(ServiceAccount) }); const uid= 'userid1'; // This user id will be stored in Firebase. admin.auth().createCustomToken(uid) .then((customToken) => { console.log(customToken); }) .catch((error) => { console.log('Error creating custom token:', error); }); ``` 1. To run this `index.js` file inside the terminal (at the same location where this file is located), hit this command: `node index.js`. This will print the JWT token in the console. 2. Copy this JWT token, return to FlutterFlow, and save it in the **app state variable** (String Datatype). 3. Open the JWT token action, click on **UNSET** (or a variable if you have already set it), and select the **App State -> variableName** (that holds the JWT token). ## Accessing Firebase Database[​](/integrations/authentication/firebase/jwt-auth.md#accessing-firebase-database "Direct link to Accessing Firebase Database") Once you log in via the JWT token, the *Authenticated User* object is available. This object contains the fields (i.e., logged-in user's data), especially **User Reference (users ref),** that you may need to provide while adding or retrieving Firestore documents. Here's an example of how you can use the *Authenticated User* object to filter the to-do items based on the user who created it. ![access-firebase-database.avif](/assets/images/access-firebase-database-e3ec523d4100cb53d23d2452081eef3a.avif) ## Sending push notifications[​](/integrations/authentication/firebase/jwt-auth.md#sending-push-notifications "Direct link to Sending push notifications") Once you log in via the JWT token, the *Authenticated User* object is available. This object contains the fields (i.e., logged-in user's data), especially **User Reference (users ref),** that you may need to provide while adding or retrieving Firestore documents. When such user reference is stored inside the Firestore documents, you can use them inside the **Single** or **Multiple Recipient** while defining the **Audience** inside the [Trigger Push Notification](/concepts/notifications/push-notifications.md#trigger-push-notification-action) action, as shown in the image below: ![send-push-notification-to-users-created-via-JWT-token.png](/assets/images/send-push-notification-to-users-created-via-JWT-token-ff46f39bfb7debb4b20811e130d309a4.png) To learn more about how to use user references for sending push notifications, please check the [push notification](/concepts/notifications/push-notifications.md) section. --- # Phone Login Phone login allows a user to sign in by sending an SMS message to the user's phone. The user login in using a one-time code contained in the SMS message. Prerequisites Before getting started with this section: 1. Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). 2. Complete [**Initial setup**](/integrations/authentication/firebase/initial-setup.md) required for authentication. ## Adding Phone sign-in[​](/integrations/authentication/firebase/phone.md#adding-phone-sign-in "Direct link to Adding Phone sign-in") Adding Phone sign-in comprises the following steps: 1. [Setting up phone sign-in](/integrations/authentication/firebase/phone.md#1-setting-up-phone-sign-in) 2. [Enabling phone authentication in Firebase](/integrations/authentication/firebase/phone.md#2-enabling-phone-authentication-in-firebase) 3. [Building phone number page](/integrations/authentication/firebase/phone.md#3-building-phone-number-page) 4. [Building verify code page](/integrations/authentication/firebase/phone.md#4-building-verify-code-page) 5. [Adding phone sign-in action](/integrations/authentication/firebase/phone.md#5-adding-phone-sign-in-action) 6. [Adding verify code action](/integrations/authentication/firebase/phone.md#6-adding-verify-code-action) 7. [Adding logout action](/integrations/authentication/firebase/phone.md#7-adding-logout-action) 8. [Testing phone sign-in](/integrations/authentication/firebase/phone.md#8-testing-phone-sign-in) 9. [Verify user creation](/integrations/authentication/firebase/phone.md#9-verify-user-creation) ### 1. Setting up phone sign-in[​](/integrations/authentication/firebase/phone.md#1-setting-up-phone-sign-in "Direct link to 1. Setting up phone sign-in") To use phone sign-in, you must [get the SHA-1 key](/integrations/authentication/firebase/initial-setup.md#generate-the-sha-1-key) and [regenerate the configuration files](/integrations/authentication/firebase/initial-setup.md#regenerate-config-files). You can find the detailed instructions [here](/integrations/authentication/firebase/initial-setup.md). **Note** that this step is often missed, so ensure you must complete this step before you proceed further. ### 2. Enabling phone authentication in Firebase[​](/integrations/authentication/firebase/phone.md#2-enabling-phone-authentication-in-firebase "Direct link to 2. Enabling phone authentication in Firebase") To enable authentication in the Firebase: 1. Open the [Firebase console](https://console.firebase.google.com/) and click on **Authentication**. 2. Click on the **Get started** button (this may not be visible if you have already set up other forms of Authentication). 3. Select the **Sign-in method** tab. 4. Click on **Phone** (Under the 'Native Providers' section). If you have already added any other provider, click on the **Add new provider** and then click on **Phone**. 5. Find the **Phone** switch and enable it. 6. Click on the **Save** button. ### 3. Building phone number page[​](/integrations/authentication/firebase/phone.md#3-building-phone-number-page "Direct link to 3. Building phone number page") To allow users to authenticate using their phone number, you need to create a page to accept the user's phone number. We provide a collection of ready-to-use templates. You can use one of our templates or create a page from scratch. Here is the page added from the templates, and after some modification, it looks the below: ### 4. Building verify code page[​](/integrations/authentication/firebase/phone.md#4-building-verify-code-page "Direct link to 4. Building verify code page") You need to create another page to verify the SMS code. Here's how you build the verify code page using templates. ### 5. Adding phone sign-in action[​](/integrations/authentication/firebase/phone.md#5-adding-phone-sign-in-action "Direct link to 5. Adding phone sign-in action") On click the 'sign-in' or 'send code' button, you will add the 'Phone Sign In' action, which redirects users to a page where they can enter the code received on their phone. To add this action: 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu) and select **Add Action**. 3. Search and select the **Phone Sign In** (under *Backend/Database > Firebase Authentication*) action. 4. Now provide the **Phone Number** via **Widget State > TextField** (that accepts the phone number). 5. Now, **Select Page** that you created to verify code. ### 6. Adding verify code action[​](/integrations/authentication/firebase/phone.md#6-adding-verify-code-action "Direct link to 6. Adding verify code action") On click of the 'Verify Code' button, you will add the 'Verify SMS Code' action, which opens the home page if the action is successful. 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu) and select **Add Action**. 3. Search and select the **Verify SMS Code** (under *Backend/Database > Firebase Authentication*) action. 4. Now provide the **SMS Code** via **Widget State > TextField** (that accepts the code). ### 7. Adding logout action[​](/integrations/authentication/firebase/phone.md#7-adding-logout-action "Direct link to 7. Adding logout action") To let users log out of your app, you can use the [Logout](/integrations/authentication/firebase/auth-actions.md#logout-action) action. ### 8. Testing phone sign-in[​](/integrations/authentication/firebase/phone.md#8-testing-phone-sign-in "Direct link to 8. Testing phone sign-in") #### 8.1 Test on Run or Test mode[​](/integrations/authentication/firebase/phone.md#81-test-on-run-or-test-mode "Direct link to 8.1 Test on Run or Test mode") To test phone sign-in in *Test* or *Run* mode, you must add the authorized domain in the Firebase console. Here's how you add the authorized domain: 1. For **Test mode**, you can open the browser console, try logging in, and get the domain from the browser console, and for **Run mode**, you can simply use '*app.flutterflow\.io*.' 2. Now open the [Firebase console](https://console.firebase.google.com/) and click on **Authentication**. 3. Select the **Setting** tab. 4. Select **Authorized domains** from the left side menu. 5. Click **Add domain**. Here's how it should look: ![adding-authorized-domain](/assets/images/adding-authorized-domain-44d8bcb1a06fe163b52139709e7bbacf.png) #### 8.2 Test on a real device[​](/integrations/authentication/firebase/phone.md#82-test-on-a-real-device "Direct link to 8.2 Test on a real device") Phone Sign In ***does not*** work in an Android emulator. You can only test it on a real device. To test on a real device, add the SHA-256 key in the Firebase console and enable the 'Google Play Integrity API' in Google Cloud. info Skip if you find the below steps already completed by our automated Firebase integration. 1. Get the SHA-256 key/fingerprint, add it to your Firebase project, and then regenerate the Firebase config files in FlutterFlow. **Note**: The instructions are similar to generating the SHA-1 key and are explained [here](/integrations/authentication/firebase/initial-setup.md#generate-the-sha-1-key). You will find the SHA-256 key in the terminal just below the SHA-1 key. This is required for the Firebase to verify that the sign-in request is coming from a legitimate device. warning While releasing the app, make sure to [**get the key from the Play Console**](/integrations/authentication/firebase/initial-setup.md#getting-sha-keys-for-release-mode). ![SHA-256 key](/assets/images/sha-256-key-5cdec65c7cedca46e5b2c6759f268cf1.png) 1. Open the [Google Developers Console](https://console.developers.google.com/) (Make sure your project is selected in the dropdown at the top), Click on the **Library** menu on the left, search for the **Google Play Integrity API,** and enable it. 2. Now, you can test your app on a real device using FlutterFlow’s Local Run. Follow the [Local Run documentation](/testing/local-run.md) and see [how to set up a physical device](/testing/local-run.md#setup-physical-device) to start testing. ### 9. Verify user creation[​](/integrations/authentication/firebase/phone.md#9-verify-user-creation "Direct link to 9. Verify user creation") To confirm the successful integration and the creation of users, navigate to your **Firebase project > Authentication > Users** and check the user entries. ## FAQs[​](/integrations/authentication/firebase/phone.md#faqs "Direct link to FAQs") How do I test with dummy numbers? To try phone sign-in without any limitations, you can add some fictitious numbers to the Firebase console. To add the fictitious number: 1. Open the [Firebase console](https://console.firebase.google.com/) and click on **Authentication**. 2. Select the **Sign-in method** tab. 3. Click on the **Phone** (Under the Sign-in providers section). 4. Scroll down, find the **Phone numbers for testing** menu, and click on it. 5. Enter any dummy phone number (Make sure it looks unreal). 6. Enter the verification code that you would use on the verify code page. 7. Click on **add**. Getting this error: "The given sign-in provider is disabled for this Firebase project. Enable it in the Firebase console, under the sign-in method tab of the Auth Section." 1. First, ensure you have clicked the "Save" button while [Enabling phone authentication in Firebase](/integrations/authentication/firebase/phone.md#2-enabling-phone-authentication-in-firebase). ![Enabling phone authentication in Firebase](/assets/images/adding-authorized-domain-44d8bcb1a06fe163b52139709e7bbacf.png) 1. If this is already enabled, head over to **Settings > SMS region policy >** select **Allow > Select regions** you want to support and click **Save**. ![SMS region](/assets/images/sms-region-5f476d85776e01b56251b53af01a767e.webp) --- # Authentication: Generated Code In FlutterFlow, enabling Authentication is a very simple task. You can check the documentation for the same here but ideally it is just enabling Authentication in Settings, choose your Authentication Type and adding an Action to your desired Auth button. But behind the scenes, a lot of code generation happens to enable this function for you, lets go through it one by one. We will first discuss the base authentication architecture and then discuss the code changes when we choose custom authentication vs Firebase/Supabase auth. ## File structure[​](/integrations/authentication/generated-code.md#file-structure "Direct link to File structure") When we enable Authentication in the Settings dashboard, it creates the following folders in our file structure to manage custom authentication. ``` lib/ auth/ custom_auth/ auth_util.dart custom_auth_manager.dart custom_auth_user_provider.dart ``` Similarly, when we enable say Firebase authentication, the following files and folders are generated for you. ``` lib/ auth/ firebase_auth/ auth_util.dart email_auth.dart (along with other providers) firebase_auth_manager.dart firebase_user_provider.dart auth_manager.dart base_auth_user_provider.dart ``` info This documentation is exclusively focused on the generated code for Custom Authentication. For instructions on integrating custom authentication into your FlutterFlow app, please refer here. # Custom Auth Manager The most crucial component of our generated authentication system is the `CustomAuthManager` class. It is responsible for managing authentication session attributes such as the `authenticationToken`, `refreshToken`, `tokenExpiration`, and user-specific attributes like `uid` and `userData`. This class provides essential functionalities including: `signIn()`: Handles user sign-in processes. `signOut()`: Manages user sign-out actions. `updateAuthUserData()`: Updates authentication and user data. `persistAuthData()`: Persists authentication data across sessions for persistent login capabilities. In addition to the `CustomAuthManager`, we have another important file in our authentication framework: `custom_auth_user_provider.dart.` This file defines a class, `AuthUser`, to encapsulate the state of an authenticated user. It leverages BehaviorSubject from the [rxdart](https://pub.dev/packages/rxdart) package to manage a stream of the user object, enabling real-time updates to the user's authentication state. This stream is initially set with a user object that indicates a logged-out state. Subsequent authentication actions will update this stream, enabling real-time adjustments to any part of the application that depends on the user's authentication status. Building on our authentication framework, the `custom_auth_manager.dart` file brings in the currentUser variable, an instance of the `AuthUser` class. This global reference allows for quick and centralized access to the currently signed-in user's information, enabling access to their authentication state across the application. The `loggedIn` property further simplifies verifying if a user is logged in by checking the currentUser's status. ## Auth Manager Initialization[​](/integrations/authentication/generated-code.md#auth-manager-initialization "Direct link to Auth Manager Initialization") Then, we have the auth\_util file, which contains a singleton instance of `CustomAuthManager` ``` final _authManager = CustomAuthManager(); CustomAuthManager get authManager => _authManager; ``` The `authManager.initialize()` is called in `main()` before runApp is executed. The `initialize()` method creates an instance of SharedPreferences, preparing it for `authToken`, `refreshToken`, etc, and also handles the logic for token expiration, including the automatic logout when these tokens expire. info Also note that this initialization occurs only because the 'Persist Auth Sessions' option has been enabled in the Custom Authentication Settings. ![Alt text for the image](/img/persist-auth-session.png) This file also offers easy-to-use getters for essential information such as the user's ID, login token, and other data. This setup simplifies the process of accessing and managing login details throughout your app. ## Log in Implementation[​](/integrations/authentication/generated-code.md#log-in-implementation "Direct link to Log in Implementation") When the Log In action is activated by tapping a button, we initiate a series of operations behind the scenes to ensure a smooth login process. Upon calling the signIn method, it triggers the `_updateCurrentUser` method from `CustomAuthManager` internally. This method receives various parameters such as `authenticationToken`, `refreshToken`, `tokenExpiration`, `authUid`, and `userData`, updating the CustomAuthManager class's properties with these details. Consequently, this stores the current session's authentication and user information effectively. info To learn more about the concepts of Authentication Token, Refresh Token, and Token Expiry Time, please refer the [Concepts](/integrations/authentication/tokens.md) doc. A new user object, marked as logged in (`loggedIn` set to true), along with the provided `authUid` and `userData`, is then added to the user object stream mentioned earlier. This update informs all the stream's subscribers about the changed user state, signaling that the user has successfully logged in. Additionally, the `persistAuthData` method is invoked to save the updated authentication details (tokens, expiration, user ID, etc.) for future sessions. After signing in, `context.goNamedAuth('AuthPage', context.mounted);` is called that navigates the user to the Logged In Page specified in FlutterFlow's Authentication Settings. --- # Apple Login Adding Apple Sign-In with Supabase offers a convenient, secure, and privacy-friendly way for users to sign up or log in to your app using their Apple ID. This guide will walk you through the steps necessary to integrate Apple login with Supabase, including configuring the necessary keys and settings in both Supabase and the Apple Developer Console. Prerequisites Before adding Apple Sign-In to your FlutterFlow project, make sure you have: 1. Completed all steps in the [**Supabase setup**](/integrations/supabase/setup.md) 2. Completed [**Initial setup**](/integrations/authentication/supabase/initial-setup.md) required for authentication. 3. Created an [**Apple account**](https://account.apple.com/account). 4. An active [**Apple Developer Account**](https://developer.apple.com/programs/enroll/). Read more about the [**Apple Developer Program**](https://developer.apple.com/programs/) and how to sign up. Adding Apple sign-in comprises of the following steps: ## Set Up in Apple Developer Console[​](/integrations/authentication/supabase/apple.md#set-up-in-apple-developer-console "Direct link to Set Up in Apple Developer Console") To set up Apple Sign-In, you need to configure a few settings in your Apple Developer Console. This includes setting up email communication to manage user privacy and enabling the Apple Sign-In capability for your App ID. ### Configure Email Communication[​](/integrations/authentication/supabase/apple.md#configure-email-communication "Direct link to Configure Email Communication") "Apple sign-in" is a privacy-focused authentication system. One of its notable features is the ability to hide a user's real email address when signing up for apps and services. When users choose to hide their email, you get one random email address that forwards to the user's actual Apple ID email. This helps users keep their real email addresses private. ![hide-apple-email.avif](/assets/images/hide-apple-email-4797a25c79fdfa22556f73f4aaf20d91.avif) So, in order to contact such users, you must register email sources that your organization will use for communication. To register email sources, open the [**Services**](https://developer.apple.com/account/resources/services/list) (under [**Certificates, Identifiers & Profiles**](https://developer.apple.com/account/resources/certificates/list)) section in your Apple developer account, configure **Sign in with Apple for Email Communication**, add the email source, and complete the registration process. ### Enable Apple Sign-In Capability in your App ID[​](/integrations/authentication/supabase/apple.md#enable-apple-sign-in-capability-in-your-app-id "Direct link to Enable Apple Sign-In Capability in your App ID") To enable Apple sign-in for your app, open the [**Identifiers**](https://developer.apple.com/account/resources/identifiers/list) section in your Apple developer account, select your existing **App ID**, enable **Sign In with Apple**, and click **Save**. tip If you haven't created an App ID yet, follow the instructions provided by Apple to [**register an App ID**](https://developer.apple.com/help/account/manage-identifiers/register-an-app-id/). ## Configure Apple Auth in Supabase[​](/integrations/authentication/supabase/apple.md#configure-apple-auth-in-supabase "Direct link to Configure Apple Auth in Supabase") To enable and configure Apple authentication in your Supabase project, open the [Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers), select your project, enable **Sign in with Apple** under the **Apple** section, enter the **Client ID** and **Secret Key**, and click **Save**. tip To obtain the secret key, use the tool provided under [**Configuration section**](https://supabase.com/docs/guides/auth/social-login/auth-apple?queryGroups=platform\&platform=flutter#flutter-configuration-web). ![get-secret-key.avif](/assets/images/get-secret-key-2f2a50880520c81cecf2784e729c7493.avif) ## Enable Apple Auth in FlutterFlow[​](/integrations/authentication/supabase/apple.md#enable-apple-auth-in-flutterflow "Direct link to Enable Apple Auth in FlutterFlow") To enable Supabase Apple authentication in FlutterFlow, go to **Settings and Integrations** > **Supabase** > **Supabase Authentication**, and toggle on **Enable Apple Authentication**. ![enable-apple-auth-flutterflow.avif](/assets/images/enable-apple-auth-flutterflow-1746e0bf61acaaef283ec81baaf789bb.avif) ## Create Account \[Action][​](/integrations/authentication/supabase/apple.md#create-account-action "Direct link to Create Account \[Action]") Now, proceed to add an account creation flow, which consists of the following two actions: 1. **Create Account Action**: Add the **Create Account** action (under Supabase Authentication). This will create an account in Supabase and add the user details to **Supabase Dashboard > Authentication > Users**. 2. [**Insert Row Action**](/integrations/database/supabase/database-actions.md#insert-row-action): The previous action does not automatically create an entry in the public "users" table you created [here](/integrations/authentication/supabase/initial-setup.md#1-creating-a-users-table). To do this, add a **Supabase Insert Row** action, to log the user's details, such as their email. ![create-account.avif](/assets/images/create-account-2fc4d2bda51572603955b581ef39054f.avif) ## Login \[Action][​](/integrations/authentication/supabase/apple.md#login-action "Direct link to Login \[Action]") To enable user login, add the **Log In** action (under Supabase Authentication). When users click on the sign-in button, they will be prompted to log in with their Apple credentials. ![login.avif](/assets/images/login-c58aab763a4eb1ce27d983109cc300d9.avif) ## Logout \[Action][​](/integrations/authentication/supabase/apple.md#logout-action "Direct link to Logout \[Action]") To let users log out of your app, you can use [this](/integrations/authentication/supabase/auth-actions.md#log-out-action) action. ## Prepare to Test[​](/integrations/authentication/supabase/apple.md#prepare-to-test "Direct link to Prepare to Test") To test your app on a real device, you must configure the project in Xcode. This includes adding a team to your project and setting an appropriate signing certificate. Here's how you configure your project in Xcode: 1. From the Local Run, [open your project in Xcode](/testing/local-run.md#access-project-code). tip If you are using Android Studio, right-click on the **ios** folder, find **Flutter,** and then click on the **Open iOS module in Xcode**. 2. In Xcode, click on **Runner** (left side menu) and then select the **Signing and Capabilities** tab. 3. We recommend choosing the **Automatically manage signing** option. This will auto-create the profiles, app ID, and certificates required to build and run your app. If you don't, you'll have to [manually create a 'provisioning profile'](https://blog.codemagic.io/distributing-native-ios-sdk-with-flutter-module-using-codemagic/) and then add it in the Xcode. 4. Under the **Signing** section, find the **Team** dropdown and select your team. 5. Now use [Local Run](/testing/local-run.md) to test the app on a real device. ## Verify User Creation[​](/integrations/authentication/supabase/apple.md#verify-user-creation "Direct link to Verify User Creation") To verify that you have successfully added the Apple authentication, you can come over to your **Supabase project > Authentication > Users** and verify the user entries. Also, verify entries in your public `users` table. ![user-entries-in-supabase-auth](/assets/images/user-entries-in-supabase-auth-37198d7578c002efde7b523278ed7e3b.avif) --- # Authentication Actions Currently FlutterFlow supports the following Actions for Supabase Authentication: ## Log in \[Action][​](/integrations/authentication/supabase/auth-actions.md#log-in-action "Direct link to Log in \[Action]") This action provides users with multiple login options to access their accounts. Follow the steps below to add Email Login action: 1. Select the widget(e.g., Button) on which you want to add the action. 2. Select **Actions** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu) and click + **Add Action**. 3. Search and select the **Log in** (under *Backend/Database > Supabase Authentication*) action. 4. Set **Auth Provider** to **Email**. 5. Set the **Email Field** dropdown to the widget name that accepts email (e.g., *TextFieldEmail*). 6. Set the **Password Field** dropdown to the widget name that accepts a password (e.g., *TextFieldPassword*). ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/supabase-login-action.gif?alt=media\&token=a4aa0271-50b9-450f-b1e0-69860f0e66b3) ## Create Account \[Action][​](/integrations/authentication/supabase/auth-actions.md#create-account-action "Direct link to Create Account \[Action]") By using this action, you can provide your users with the flexibility to create their accounts in different ways, according to their preferences. note As of now, we support creating accounts with Email/Password, Google and Apple auth providers. Follow the steps below to add email signup action: 1. Select the widget(e.g., Button) on which you want to add the action. 2. Select **Actions** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu), **Open** the **Action Flow Editor,** and click + **Add Action**. 3. Search and select the **Create Account** (under *Backend/Database > Supabase Authentication*) action. 4. Set **Auth Provider** to **Email**. 5. Set the **Email** **Field** dropdown to the widget name that accepts email (e.g., *TextFieldEmail*). 6. Set the **Password Field** dropdown to the widget name that accepts a password (e.g., *TextFieldPassword*). 7. Similarly, If you have a confirm password field in your UI, set the **Confirm Password Field** to the appropriate one. ![](https://firebasestorage.googleapis.com/v0/b/ecommerceflow-docs/o/create-account-action.gif?alt=media\&token=372a8285-bd24-4279-b141-4a02085168c0) ## Log out \[Action][​](/integrations/authentication/supabase/auth-actions.md#log-out-action "Direct link to Log out \[Action]") This action enables users to securely log out of their account and clear their session data from the app, which ensures that their account remains safe and secure. Follow the steps below to add this action: 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside **Action Flow Editor**) and select **Add Action**. 3. Search and select the **Log Out** (under **Backend/Database > Supabase Authentication**) action. ![img\_6.png](/assets/images/img_6-d59b94e106d404251a6053eb1e1ba61b.png) ## Send Reset Password Email \[Action][​](/integrations/authentication/supabase/auth-actions.md#send-reset-password-email-action "Direct link to Send Reset Password Email \[Action]") This action allows users to reset their password by sending a reset link to their registered email address. Prerequisites To build the reset password functionality, you need to create the following two pages in your app: 1. **ForgotPassword Page**: This page allows users to enter their email address and request a password reset link. 2. **UpdatePassword Page**: This page allows users to set a new password after clicking on the reset link. Here’s how you can add the Supabase reset password feature to your app: 1. On the **ForgotPassword Page**, add the **Send Reset Password Email** action and set the **Email Field** dropdown to the widget that accepts the user's email address. This action will send the reset password link to the provided email. 2. The reset link sent to the user will open the **UpdatePassword Page**. On that page, add the **Update Password** action and set the **Password Field** and **Confirm Password Field** to the respective input widgets. 3. Copy the route name of the **UpdatePassword Page** and paste it into the **Supabase Dashboard > Authentication > Email Templates > Reset Password > Source**. After **`"{{ .ConfirmationURL}}"`** add **`"/[here]"`** only if you're not using a [custom redirect URL](/integrations/authentication/supabase/auth-actions.md#use-custom-redirect-urls). If using a custom redirect URL, the confirmation URL will redirect directly to your specified path. 4. [Deploy your app to the web](/deployment/web-publishing.md). 5. Copy the URL of your deployed project and paste it into the **Supabase Dashboard > Authentication > URL Configuration > Site URL**. tip **For mobile**, you must set the **deep link URL** as the Site URL. To find this, navigate to **FlutterFlow > Settings & Integrations > App Details > Routing & Deep Linking**, open the **URL Scheme** tooltip, and copy the URL. ![mobile-deeplink.avif](/assets/images/mobile-deeplink-6d0ca0a2b81a9f9f8e817e8992f66d80.avif) ### Use Custom Redirect URLs[​](/integrations/authentication/supabase/auth-actions.md#use-custom-redirect-urls "Direct link to Use Custom Redirect URLs") Instead of relying on the default `{{ .ConfirmationURL }}` path, you could optionally configure a **custom redirect URL** in Supabase. This option allows you to bypass the default setup and send users directly to a custom page in your app for resetting their password. To configure a custom redirect URL: 1. When adding the **Send Reset Password Email** action in FlutterFlow, enter the **Redirect To** URL. For example `http://my-site.com/resetPassword`. 2. Whitelist this custom URL by navigating to **Supabase Dashboard > Authentication > URL Configuration > Redirect URL**, and click **Add URL** to include it. 3. Update the reset password template. Go to **Supabase Dashboard > Authentication > Email Templates > Reset Password > Source** and ensure only `{{ .ConfirmationURL }}` is present in the template (remove any appended route names). ## Delete User[​](/integrations/authentication/supabase/auth-actions.md#delete-user "Direct link to Delete User") At present, we do not support deleting Supabase user action. However, you can refer to this community video for guidance on how to do so. [YouTube video player](https://www.youtube.com/embed/PNBvc35CDAk) --- # Email Authentication Supabase email authentication is a secure and easy way to allow users to sign up and log in to your application using their email and password. Prerequisites Before getting started with this section, ensure you have, 1. Completed all steps in the [**Supabase setup**](/integrations/supabase/setup.md) 2. Completed [**Initial setup**](/integrations/authentication/supabase/initial-setup.md) required for authentication. ## Adding Email Authentication[​](/integrations/authentication/supabase/email.md#adding-email-authentication "Direct link to Adding Email Authentication") Let's see how to add a Supabase email authentication by building an example that looks like this: The steps to add Supabase email authentication are as follows: ### Configure Email Authentication in Supabase[​](/integrations/authentication/supabase/email.md#configure-email-authentication-in-supabase "Direct link to Configure Email Authentication in Supabase") Due to some Supabase auth behavior, you need to disable the email verification on the Supabase side. However, you can still add the email verification logic on your own in your app if you wish to. Here's how you disable email verification on the Supabase side: 1. In your Supabase project, navigate to **Authentication > Provider**. 2. Open the **Email** section and disable the **Confirm email** and **Secure email change**. ![img\_2.png](/assets/images/img_2-c7a194a0116b8eb06cc80e5ab6bea181.png) Disable email verification on the Supabase side ### Building pages[​](/integrations/authentication/supabase/email.md#building-pages "Direct link to Building pages") Let's add a page that allows users to create accounts and log in. To speed up, you can add a page from the [template](/resources/ui/pages.md#create-page-from-template). Here is the page added from the templates, and after some modification, it looks the below: Also, see how to build a page layout in case you want to build a page from scratch. ![img\_3.png](/assets/images/img_3-dd11fde15d52a4400a5f9616c6588301.png) ### Adding Create Account \[Action][​](/integrations/authentication/supabase/email.md#adding-create-account-action "Direct link to Adding Create Account \[Action]") Now, you can proceed to add an account creation flow, which basically consists of three actions in the following order: 1. Supabase [Create Account Action](/integrations/authentication/supabase/auth-actions.md#create-account-action) 2. Supabase [Insert row action](/integrations/database/supabase/database-actions.md#insert-row-action) 3. [Navigate](/concepts/navigation/overview.md) action The first one creates an account in Supabase and adds an email and password in the "auth.users" table (i.e., *Protected schemas > schema auth*). However, this action does not create an entry in the "users" table you created [here](/integrations/authentication/supabase/initial-setup.md#1-creating-a-users-table). To do so, you need to add another action called Supabase *insert row* action with the user's details, such as email and profile\_pic. Once the entry has been created, you can navigate to the home page using the navigate action. Here's how it looks: ### Adding Log In \[Action][​](/integrations/authentication/supabase/email.md#adding-log-in-action "Direct link to Adding Log In \[Action]") To allow users to log in with their credentials, you can use the [**Log In**](/integrations/authentication/supabase/auth-actions.md#log-in-action) action. ### Adding Logout \[Action][​](/integrations/authentication/supabase/email.md#adding-logout-action "Direct link to Adding Logout \[Action]") To let users log out of your app, you can use the [**Log Out**](/integrations/authentication/supabase/auth-actions.md#log-out-action) action. ### Verify user creation[​](/integrations/authentication/supabase/email.md#verify-user-creation "Direct link to Verify user creation") To verify that you have successfully added the email authentication, you can come over to your Supabase project > Table Editor > select the "users" table and verify the user entries. ![img\_5.png](/assets/images/img_5-b6c2a02e968b3063223f358f527a92da.png) ### What's next?[​](/integrations/authentication/supabase/email.md#whats-next "Direct link to What's next?") Now that you have successfully added the Supabase email authentication in your app, you can access the logged-in user's details, such as email, user id, phone number, email verified, and JWT token via the **Set Variable menu > Authenticated User**. Here's an example of filtering the to-do list based on the logged-in user using the **Set Variable menu > Authenticated User > User ID** property. --- # Google Login Google Authentication with Supabase offers a secure and convenient method for users to sign up and log in to your app using their Google accounts. Prerequisites Before getting started with this section, ensure you have, 1. Completed all steps in the [**Supabase setup**](/integrations/supabase/setup.md) 2. Completed [**Initial setup**](/integrations/authentication/supabase/initial-setup.md) required for authentication. ## Adding Google authentication[​](/integrations/authentication/supabase/google.md#adding-google-authentication "Direct link to Adding Google authentication") Let's see how to add a Supabase Google authentication by building an example that looks like this: The steps to add Supabase Google authentication are as follows: ### 1. Create and configure Google Cloud project[​](/integrations/authentication/supabase/google.md#1-create-and-configure-google-cloud-project "Direct link to 1. Create and configure Google Cloud project") To begin adding Google auth, you must first have an active [Google Cloud Platform](https://cloud.google.com/) account. You'll need to either set up a new project or use an existing one within this account. Here's how you do it: 1. If you haven't already, create a new project in [Google Cloud Console](https://console.cloud.google.com/). 2) If you haven't already, configure the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent). This helps Google display a consent screen to the user, including a summary of your project and its policies and the requested scopes of access. 3. Now, you must create credentials so that your app can access Google data. To do so: 1. Head over to [credentials page](https://console.cloud.google.com/apis/credentials), click **+ CREATE CREDENTIALS** and select **OAuth client ID**. 2. Set **Application type** to **Web Application**. 3. Below, under the **Authorized redirect URIs**, click **+ ADD URI**. To get this URI, open your **Supbase project > Authentication > Providers**. Open the **Google** section, copy the **Callback URL**, and paste it here. 4. Click **CREATE**. 5. Copy the **Client ID** and **Client secret**; you'll need this in the next step. 4) For *Android*, you'll need to create a new credential with the **Application Type** set to **Android**. While creating, you'll need to provide the package name and [SHA-1 key](/integrations/authentication/firebase/initial-setup.md#generate-the-sha-1-key). **Note** that after your app goes live, you must replace the SHA-1 key with the [key from the Play Console](/integrations/authentication/firebase/initial-setup.md#getting-sha-keys-for-release-mode). 5. Similarly, create credential for *iOS* platform as well. **Note** that after your app goes live, you must specify the *App Store* and *Team ID*. ### 2. Configure Google auth in Supabase[​](/integrations/authentication/supabase/google.md#2-configure-google-auth-in-supabase "Direct link to 2. Configure Google auth in Supabase") This step involes enabling Google login and providing the client IDs and secret in Supabase. Here's how you do it: 1. Head over to [Supabase project dashboard](https://supabase.com/dashboard/) **> Authentication > Providers**. 2. Open the **Google** section and turn on the **Enable Sign in with Google**. 3. Paste the **Client ID** and **Client secret** from the **Web** credential. 4. Paste the **Authorized Client IDs** from the **Android** credential. 5. Turn on the **Skip nonce checks** to support **iOS** platform. 6) Now, you must specify the redirect URL in [Supabase project dashboard](https://supabase.com/dashboard/) **> Authentication > URL Configuration**. It is the URL to which a user is sent after successful authentication. Here's how you do it for both web and mobile. ### 3. Enable Google auth in FlutterFlow[​](/integrations/authentication/supabase/google.md#3-enable-google-auth-in-flutterflow "Direct link to 3. Enable Google auth in FlutterFlow") To enable Supabase Google auth in FlutterFlow: 1. In FlutterFlow, navigate to the **Setting and Integrations** **>** **App Settings > Authentication**. 2. Open the **Supabase Authentication** section and turn on the **Enable Google Authentication** toggle. 3. Paste the **iOS** and **Web Client ID** obtained in step 1. ### 4. Add a Google sign-in button[​](/integrations/authentication/supabase/google.md#4-add-a-google-sign-in-button "Direct link to 4. Add a Google sign-in button") To allow users to authenticate, you need a login page with a button. You can create your own or use the one from the widget template or page template. Here's how you can add the Google sign-in button from our page template: ### 5. Adding create account action[​](/integrations/authentication/supabase/google.md#5-adding-create-account-action "Direct link to 5. Adding create account action") Now, you can proceed to add an account creation flow, which basically consists of two actions in the following order: 1. Supabase create account action. Here's how you add it: 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. Search and select the **Log in** (under *Backend/Database > Supabase Authentication*) action. 5. Set **Auth Provider** to **Google**. 2. Supabase [insert row action](/integrations/database/supabase/database-actions.md#insert-row-action) The first one creates an account in Supabase and adds the user details at *Supabase Dashboard > Authentication > Users*. However, this action does not create an entry in the "users" table you created [here](/integrations/authentication/supabase/initial-setup.md#1-creating-a-users-table). To do so, you need to add another action called Supabase *insert row* action with the user's details, such as email. ### 6. Adding login action[​](/integrations/authentication/supabase/google.md#6-adding-login-action "Direct link to 6. Adding login action") When you click the Google sign-in button, it will trigger the 'Log In' action, prompting a Google sign-in popup for users to input their credentials. To add login action: 1. Select the widget (e.g., Button) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu) and select **Add Action**. 3. Search and select the **Log in** (under *Backend/Database > Supabase Authentication*) action. 4. Set **Auth Provider** to **Google**. ![Adding login action](/assets/images/adding-login-action-a46c475b4cd576832776c52a3c962132.avif) ### 7. Adding logout action[​](/integrations/authentication/supabase/google.md#7-adding-logout-action "Direct link to 7. Adding logout action") To let users log out of your app, you can use [this](/integrations/authentication/supabase/auth-actions.md#log-out-action) action. ### 8. Preparing to test the app[​](/integrations/authentication/supabase/google.md#8-preparing-to-test-the-app "Direct link to 8. Preparing to test the app") Currently, testing the Supabase Google login feature isn't possible in Run or Test modes due to certain restrictions. But, for web platform testing, you can publish your app with a subdomain using our [web publishing](/deployment/web-publishing.md) feature. You can test your app on a real device or emulator using FlutterFlow’s Local Run. Follow the [Local Run documentation](/testing/local-run.md) and see [how to set up a physical device](/testing/local-run.md#setup-physical-device) to start testing. ### 9. Verify user creation[​](/integrations/authentication/supabase/google.md#9-verify-user-creation "Direct link to 9. Verify user creation") To verify that you have successfully added the Google authentication, you can come over to your Supabase project > Authentication > Users and verify the user entries. ![Verify user creation](/assets/images/verify-user-creation-8226603473539212d9a93c67818fc86d.avif) --- # Initial Setup To use authentication, you will need to complete the following initial setup: 1. [Creating a "users" table](/integrations/authentication/supabase/initial-setup.md#1-creating-a-users-table) 2. [Enabling authentication in FlutterFlow](/integrations/authentication/supabase/initial-setup.md#2-enabling-authentication-in-flutterflow) Prerequisites Before you begin, make sure you have completed the [**Supabase Setup**](/integrations/supabase/setup.md). ### 1. Creating a "users" table[​](/integrations/authentication/supabase/initial-setup.md#1-creating-a-users-table "Direct link to 1. Creating a \"users\" table") To use Supabase authentication, you'll need to create a table to store your users' data, such as their name, email, and profile picture. Also, it's recommended to create a [foreign key relationship](https://supabase.com/docs/guides/database/tables#joining-tables-with-foreign-keys) from the `id` column of your "users" table to the `id` column of the "users" table in auth (protected) schema, i.e., `auth.users.id` with `on delete cascade`. This ensures that when a user is deleted from the "auth.users" table, their corresponding data in your "users" table will also be removed. Here's how you do it: note The "users" table in auth (protected) schema is a private table that Supabase uses to store auth-related sensitive information such as email, encrypted pass, and confirmation token. ![img.png](/assets/images/img-b181589c7111a788900a2fa263eafee3.png) ### 2. Enabling authentication in FlutterFlow[​](/integrations/authentication/supabase/initial-setup.md#2-enabling-authentication-in-flutterflow "Direct link to 2. Enabling authentication in FlutterFlow") To enable authentication in FlutterFlow: 1. Open your FlutterFlow project. 2. Navigate to the Setting and Integrations () from the Navigation Menu > App Settings > Authentication. 3. Turn on the **Enable Authentication** toggle and select **Authentication Type** to **Supabase**. 4. To ensure that your users are directed to the appropriate pages based on their login status, you must set the [initial pages](/resources/projects/settings/general-settings.md#initial-page). ![img\_1.png](/assets/images/img_1-b7c8f0344e87b684233866df3d4144e5.png) --- # Tokens: Types and Lifespans Here are some key terms we'll encounter in [**Custom Authentication**](/integrations/authentication/custom-authentication.md). * **Authentication Token**: An Auth Token acts as a digital key provided to client apps upon successful login. This key verifies the user's identity for subsequent actions within the app, eliminating the need for repeated login prompts. It ensures secure and streamlined access to the app's features. * **Refresh Token**: This token functions as a secondary mechanism to renew the authentication token without requiring the user to re-enter login credentials. This is particularly useful for maintaining a user's session securely, even when the primary authentication token expires, enhancing both security and user experience. * **Token Expiry Time** : This refers to the lifespan of an authentication token. It defines the period during which the token remains valid. This is a critical security measure to prevent unauthorized access, ensuring that tokens are regularly refreshed and authenticated. Developers must save these values in the persisted app state to keep it secure. Remember Not all login APIs will return an Auth Token, Refresh Token, and Token Expiry Time. The details of the response depend on the configuration of your backend API. Always check your API documentation to understand what is returned upon successful authentication. ![login-request.png](/assets/images/login-request-3604fe8a80dae54fddb0ec5f4fdcfb4f.png) 1.1 An example of a Login API transaction that returns the above values in its response ### Example of Auth using Tokens[​](/integrations/authentication/tokens.md#example-of-auth-using-tokens "Direct link to Example of Auth using Tokens") For example, when a user logs in using a Login API, the server verifies the submitted credentials and typically responds with both an **Auth Token** and a **Refresh Token** (both strings). The authentication token is short-lived; for instance, it may be valid for 1 hour and is used to authenticate API requests (*as shown in diagram 1.1*). If you need to use the token, it is typically included in any authenticated API request, such as accessing a list of users available only to logged-in users. In this case, the app sends a request to the API with a header that includes an Authorization header containing the authentication token. ![token-success-request.png](/assets/images/token-success-request-6e28992f0e16301b69f5354a9a9330c1.png) An example of an Authenticated API request that sends a Authorization Bearer Token that uses the saved Auth Token After an hour, or as determined by the token's expiry time, the authentication token expires. Any attempt to access another post with the expired token will result in a 401 Unauthorized response. To retrieve a new token, a common practice involves the client app making a request to a specific endpoint, submitting the saved refresh token in the request body. The server then validates the refresh token and responds with a new authentication token. ![token-fail-request.png](/assets/images/token-fail-request-946120818dd871422ec0787c9a4e7e9e.png) Thanks to the refresh token, users can continue accessing the app without needing to log in again. This process enhances security by limiting the lifespan of each token while ensuring a seamless experience for the user. --- # Creating Collections A collection is a group of documents. For example, you could have a 'users\*'\* collection that contains a list of documents, each representing a single user. ![img\_20.png](/assets/images/img_20-f99f4388b62b57262c21368ac5281a0c.png) User collection document model Getting Started: Things to Know First * Get to know how to [**structure the Firebase Database**](/integrations/database/cloud-firestore/getting-started.md#structuring-the-database). * Ensure you've gone through and completed every step in the [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) for your project. ## Creating a collection[​](/integrations/database/cloud-firestore/creating-collections.md#creating-a-collection "Direct link to Creating a collection") Here are the steps to create a collection: 1. Click on the **Firestore** from the Navigation Menu (left side of your screen). 2. Click on the **(+)** Plus sign button. 3. A popup will appear, Enter the collection name and click **Create** Button. 4. Next, [define the collection schema](/integrations/database/cloud-firestore/creating-collections.md#define-schema-creating-fields) (create Fields) and [add some data](/integrations/database/cloud-firestore/firestore-actions.md#create-document-action) to the collection. info A collection will only appear on [**Firebase Console**](https://console.firebase.google.com/u/0/) if it contains at least one document. ### Define Schema (Creating Fields)[​](/integrations/database/cloud-firestore/creating-collections.md#define-schema-creating-fields "Direct link to Define Schema (Creating Fields)") A document represents a single item or entity, such as a user, post, animal, etc. To add data inside the document, you must define the document schema by creating Fields. Creating Fields helps you know what kind of data a document can contain. Although you can add more fields later on, it's always a good idea to add fields from the start. caution Field names cannot be changed, so ensure that you have used the correct Field names. To define the schema (create fields) for the document: 1. Select your collection from the list on the left side. 2. If you haven't added any fields yet: 1. You can choose from the template collections that have common fields needed in most applications. This will auto-add all the fields. 2. Click on **Start from scratch** to define your own schema. 3. Or, use [AI Gen Schema](/integrations/database/cloud-firestore/creating-collections.md#create-schema-using-ai-gen). 3. To add a new field, start typing its name (e.g., title, description, date, etc.) and choose the suitable **Data Type**. 4. While choosing the Data Type, you can set if it will be a list or not using **Is List?** toggle. 1. You can keep it disabled for storing only a single value. For example, fields such as title, description, price, etc., can have only one value. You can't have multiple titles for a single post. 2. You can enable it to store multiple values of the same data type. For example, to store the list of accessory names for the field accessories. 5. Click on the **Done** icon. tip You can also use *Tab* and *Enter* keys to navigate quickly while creating fields. ### Create schema using AI Gen[​](/integrations/database/cloud-firestore/creating-collections.md#create-schema-using-ai-gen "Direct link to Create schema using AI Gen") With **AI Gen Schema**, you can automatically generate a schema for your Firebase collection from a simple prompt. To get better results... ...you can try optimizing your prompt. i.e., make it more descriptive. Example prompts: * Generate a collection for books, their reviews, and their purchase history. * Create a database schema for music albums, their ratings, and sales records. * Generate a collection for video games, their user reviews, and purchase history. * Create a collection for art exhibits, visitor reviews, and ticket bookings. * Generate a collection for online courses, student feedback, and enrollment records. *** note To learn more about custom data types within FlutterFlow, [check this doc](/resources/data-representation/data-types.md#built-in-data-types) --- # Creating Subcollections [Collections](/integrations/database/cloud-firestore/creating-collections.md) that are created inside the document are called subcollections. For example, you could have a 'comments' subcollection inside the 'posts' collection to store a post's comments. Subcollection is best when you have several queries/filters or search on a collection based on the other collection. For example, loading or searching the comments of a specific post. (i.e., show all comments of a post with more likes.) Feature Completion At this time, FlutterFlow supports one level of nesting (e.g., collection -> subcollection). Second-level nesting is not currently supported ( e.g., collection -> subcollection 1 -> subcollection 2.) ![img\_21.png](/assets/images/img_21-eb0073a7794e33e857bb128f3b57d8f4.png) Getting Started: Things to Know First * Get to know how to [**structure the Firebase Database**](/integrations/database/cloud-firestore/getting-started.md#structuring-the-database). * Ensure you've gone through and completed every step in the [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) for your project. ## Working with subcollections[​](/integrations/database/cloud-firestore/creating-subcollections.md#working-with-subcollections "Direct link to Working with subcollections") In this section, you'll learn to work with subcollections by building an example that allows you to see all messages and post a new message in a chat room (example below). Here are a few tips on how subcollections work in FlutterFlow: * You can create a subcollection document under an existing reference if there is a subcollection defined. * You can either specify the reference to query a subcollection (UserA -> favorites) or can do a “collectionGroup” query across all subcollections (all Users -> Favorites) by not specifying the reference. Before we begin, we need to identify the collections and define the database structure. So looking at the requirements, it's very clear that we'll need two collections. One for storing chat room details and another for storing its messages. And we need to display the messages only for a specific chat room. So, having the message collection as a subcollection of the chat rooms seems to be a good option. Here's what the database structure looks like: ![img\_22.png](/assets/images/img_22-9f9568a94eb866b20899d864891eab8e.png) Building the chat room example comprises the following steps: 1. [Creating a collection](/integrations/database/cloud-firestore/creating-subcollections.md#1-creating-a-collection) 2. [Creating a subcollection](/integrations/database/cloud-firestore/creating-subcollections.md#2-creating-a-subcollection) 3. [Add data to the collection](/integrations/database/cloud-firestore/creating-subcollections.md#3-add-data-to-the-collection) 4. [Building chat room listing page](/integrations/database/cloud-firestore/creating-subcollections.md#4-building-chat-room-listing-page) 5. [Building messages page](/integrations/database/cloud-firestore/creating-subcollections.md#5-building-messages-page) ### 1. Creating a collection[​](/integrations/database/cloud-firestore/creating-subcollections.md#1-creating-a-collection "Direct link to 1. Creating a collection") [**Create the collection**](/integrations/database/cloud-firestore/creating-collections.md) called *chat\_rooms*. This will be used to hold the chat room details. While defining the schema for *chat\_rooms* collection, add the fields to display its name, i.e., *chat\_room\_name.* ![img\_23.png](/assets/images/img_23-0ab98eb05f49d1d28f073680489f1c7f.png) ### 2. Creating a subcollection[​](/integrations/database/cloud-firestore/creating-subcollections.md#2-creating-a-subcollection "Direct link to 2. Creating a subcollection") To create the subcollection: 1. Click on the **Firestore** from the Navigation Menu (left side of your screen). 2. Click on the **(+)** Plus sign button. 3. A popup will appear; enter the collection name as '*messages.'* 4. **Turn on** the **Is Subcollection** toggle. 5. The dropdown list with existing collections will appear. Click on the **Unset** and select the parent collection, *chat\_rooms* in this case. 6. Click **Create** Button. 7. Next, [define the document schema](/integrations/database/cloud-firestore/creating-collections.md#define-schema-creating-fields). While defining the schema for the 'messages' subcollection, add the fields such as *message* (to store the message body) and *from* (to store the sender name). ### 3. Add data to the collection[​](/integrations/database/cloud-firestore/creating-subcollections.md#3-add-data-to-the-collection "Direct link to 3. Add data to the collection") Add some default chat room details using [Firestore Content Manager](/integrations/database/cloud-firestore/firestore-content-manager.md). ### 4. Building chat room listing page[​](/integrations/database/cloud-firestore/creating-subcollections.md#4-building-chat-room-listing-page "Direct link to 4. Building chat room listing page") The first page shows the chat room listing, and when you tap, it opens the new page and shows all messages. The steps to build the chat room page are as follows: 1. Query the **chat\_rooms** collection and display the chat room names in a ListTile (inside ListView). 2. Add the **[Navigate To](/concepts/navigation/overview.md#navigation-actions)** action **on Tap** of the **ListTile** and open the messages page. **Note**: While navigating, pass the chat room record to the next page. Learn how to [pass data to the next page](/concepts/navigation/passing-data.md). . ### 5. Building messages page[​](/integrations/database/cloud-firestore/creating-subcollections.md#5-building-messages-page "Direct link to 5. Building messages page") The next page shows all the messages and allows you to send messages in the chat room. The steps to build the chat room page are as follows: 1. Use the **ListView**, **ListTile**, **TextField**, and **Button** widgets to design a page that looks like the below: ![img\_24.png](/assets/images/img_24-ca68c3f4b34738abbe7ff5dda39534c5.png) 2. On the ListView, query a subcollection as you would query any other collection; except for the subcollection, you must provide its parent collection reference (i.e., chat\_rooms reference in this case). This way, you'll only see messages from that specific chat room. 3) On tap of 'Send' button, add the [create document](/integrations/database/cloud-firestore/firestore-actions.md#create-document-action) action for `messages` collection and provide current `chat_rooms` reference. Also, provide the message to add via **From Variable > Widget State > \[TextFieldName]**. --- # Firestore Actions The Firestore action allows you to create, update, or delete a record from a Firestore Collection. Prerequisites * Get to know how to [**structure the Firebase Database**](/integrations/database/cloud-firestore/getting-started.md#structuring-the-database). * Ensure you've gone through and completed every step in the [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) for your project. * Created a [**collection**](/integrations/database/cloud-firestore/creating-collections.md) ## Types of Firestore Database Actions[​](/integrations/database/cloud-firestore/firestore-actions.md#types-of-firestore-database-actions "Direct link to Types of Firestore Database Actions") Following are the types of Firestore database action: 1. [**Create Document**](/integrations/database/cloud-firestore/firestore-actions.md#create-document-action)**:** Creates a new record inside the specified Firestore Collection. 2. [**Read Document**](/integrations/database/cloud-firestore/firestore-actions.md#read-document-action): Fetches document data using a reference. 3. [**Update Document**](/integrations/database/cloud-firestore/firestore-actions.md#update-document-action)**:** Updates the specified field value of the existing document. 4. [**Delete Document**](/integrations/database/cloud-firestore/firestore-actions.md#delete-document-action)**:** Deletes records inside the specified Firestore Collection. 5. [**Query Collection**](/integrations/database/cloud-firestore/firestore-actions.md#query-collection-action): Retrieves record(s) from the Firstore collection. ### Create Document \[Action][​](/integrations/database/cloud-firestore/firestore-actions.md#create-document-action "Direct link to Create Document \[Action]") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on **+ Add Action**. 4. On the right side, search and select the **Firestore** > **Create Document** action. 5. Set the **Collection** to your collection name. 6. Under the **Set Fields** section, click on the **+ Add Field** button. 7. Open the *Field* to pass its value from a widget: * Set the **Value Source** to **From Variable**. * Click on **UNSET** and select **Widget State > Name** of the TextField. 8. Similarly, add the field for the other UI elements. 9. By default, documents are added with an auto-generated ID. However, if you prefer to use your own ID for the document, you can enable the **Custom ID** toggle. ### Read Document \[Action][​](/integrations/database/cloud-firestore/firestore-actions.md#read-document-action "Direct link to Read Document \[Action]") There are some scenarios where you may want to fetch document data in response to a widget action. For example, fetching the user's profile details like name, profile picture, and bio to display them on click of a button. Here are some more use cases where you may find this action helpful: * Fetching additional user details for a post or comment. * Retrieve product details, price, and availability for order IDs in a user's cart. * Get details for cities referenced within a country document in a travel app. Let's see how to add this action with an example that fetches and displays the details of users who've reviewed a travel destination. Here's how it looks: Here's how collections are setup: ![img\_25.png](/assets/images/img_25-ac2455b536426cfbb3d2072fcf942d70.png) Follow the steps below to define this action to any widget: 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on **+ Add Action**. 4. On the right side, search and select the **Firestore** > **Read Document** action. 5. Now, **Select Reference to Read** data from. 6. Provide the **Action Output Variable Name**. This will be used to store the document data. 7) Now, you can use the *Action Output Variable Name* provided in the previous step to fetch the details. For example, to display data on Text widget, select the **Text widget > Properties Panel > Text > Set Variable menu > ***\[action\_output\_variable\_name]*** > select the field** you want to display. ### Update Document \[Action][​](/integrations/database/cloud-firestore/firestore-actions.md#update-document-action "Direct link to Update Document \[Action]") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on **+ Add Action**. 4. On the right side, search and select the **Firestore** > **Update Document** action. 5. In order to update a specific document within a Firebase collection, you need to specify the reference to that document. The reference acts as a pointer to the exact document you want to update. 6. Under the **Set Fields** section, click on the **+ Add Field** button. 7. Open the *Field* to pass its value from a widget: 1. Set the **Value Source** to **From Variable**. 2. Click on **UNSET** and select **Widget State > Name** of the TextField. 8. Similarly, add the field for the other UI elements. ### Delete Document \[Action][​](/integrations/database/cloud-firestore/firestore-actions.md#delete-document-action "Direct link to Delete Document \[Action]") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on **+ Add Action**. 4. On the right side, search and select the **Firestore** > **Delete Document** action. 5. In order to delete a specific document within a Firebase collection, you need to specify the reference to that document. The reference acts as a pointer to the exact document you want to delete. ### Query Collection \[Action][​](/integrations/database/cloud-firestore/firestore-actions.md#query-collection-action "Direct link to Query Collection \[Action]") There are certain scenarios where you may want to query a collection manually. For example, you might want to only fetch data in response to a specific user action, such as clicking a button or submitting a form. Additionally, If your app fetches different data under different conditions, you might find it more convenient to manually call queries. For example, fetching different tasks for admin and team members. To manually query a collection, follow the steps below to define this action to any widget: 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on **+ Add Action**. 4. On the right side, search and select the **Firestore** > **Query Collection** action. 5. Choose the **Collection** you want to query. 6. Choose the **Query Type** among the following: * **List of Documents:** Use this option when you need to query an entire list of documents from a collection. This is useful for retrieving multiple documents that can be ordered or filtered by specific criteria, such as a keyword. * **Single Document:** Select this when you want to fetch a specific single document from a collection, typically identified by its unique ID. * **Count:** Choose this option to determine the number of documents that meet certain criteria without retrieving the documents themselves. This is useful for getting quick insights or summaries, like the total number of entries that match a filter. 7. You can also [Filter](/integrations/database/cloud-firestore/firestore-actions.md#filtering-a-collection-query) and [Order](/integrations/database/cloud-firestore/firestore-actions.md#ordering-a-collection-query) the query result. 8. Provide the **Action Output Variable Name**. This will be used to store the query result. 9) Now, you can use the *Action Output Variable Name* provided in the previous step to generate children from a variable on **ListView**. 10) Finally, you can display data in a **Text** widget. To do so, select the **Text widget > Properties Panel > Text > Set from Variable menu** **> \[children\_from\_variable\_name] item > select the field** you want to display. #### Filtering a Collection Query[​](/integrations/database/cloud-firestore/firestore-actions.md#filtering-a-collection-query "Direct link to Filtering a Collection Query") Sometimes, you might need to filter a list based on a condition. For example, you might want to show only incomplete Todo items on the main listing. To add a filter when querying a collection: * In the Action properties of **Query Collection Action**, scroll down and click on the **+ Filter** button at the bottom * Find the **Field Name**, click on the Unset, and select a field on which you would like to apply the filter. * Find the **Relation** dropdown, click on the **Unset**, and choose the relation among the list. * Find the **Value** property and set it to an appropriate value and click **Confirm**. info * Select a filter relation that aligns with your specific needs. For instance, if you wish to display only incomplete todos, you can create a field named 'isDone,' set the relation to 'Equal To,' and define the value as 'False.' * Another example would be to showcase users older than 30; in this case, you'd create a 'Age' field, set the relation to 'Greater Than,' and specify the value as 30. * You can combine multiple filters using **AND** or **OR** operators to create more advanced filtering logic. This enables you to refine your data query to match specific conditions. #### Ordering a Collection Query[​](/integrations/database/cloud-firestore/firestore-actions.md#ordering-a-collection-query "Direct link to Ordering a Collection Query") You might want to show your list based on a specific order. For example, you could show a Todo list in order of due date. To specify the order when querying a collection: * In the Action properties of **Query Collection Action**, scroll down and click on the **+ Order By** button at the bottom * Find the **Field Name**, click on the **Unset**, and select the field which you would like to choose for ordering. * Find **Order** dropdown, click on the Unset, and choose the order either Increasing or Decreasing and click **Confirm**. info Choose the order based on your requirements. For instance, if you want to display Todo items sorted by their due dates, simply set the **Field Name** to date and the **Order** to Increasing. warning If you apply both filtering and ordering while querying a collection, an index is necessary otherwise FlutterFlow will throw an error. [**Learn how to avoid the errors.**](/integrations/firebase/connect-to-firebase.md#adding-indexes) ## Enabling Firestore Batch Write[​](/integrations/database/cloud-firestore/firestore-actions.md#enabling-firestore-batch-write "Direct link to Enabling Firestore Batch Write") When working with databases, you often need to create, update, or delete data. Typically, you would send individual requests to the database for each operation, which requires multiple round trips to the server. This can be time-consuming and inefficient. By enabling Firestore batch write, you can group multiple operations and send them to the database as a single request. With this, either all the operations within the batch will succeed or none of them will be applied. This guarantees data consistency, so you don't end up with a partially updated state if something goes wrong during the process. tip * You can learn more about [**Firestore Batch Write**](https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes). * If you are a newbie, we recommend watching [**this video**](https://youtu.be/dOVSr0OsAoU) first. Suppose you have an e-commerce application, and after a successful order, you need to update the product inventory count and create a new document in the 'orders' collection. Using a batch write, you can combine these operations and execute them together to ensure data consistency. To enable Firestore batch write, you must have multiple Firestore any combination of actions; inside the action editor, at the top right side, enable **Batch Firestore Writes**. ![img\_26.png](/assets/images/img_26-7e206b723fb16f08a5900b43e0de2fc5.png) Enabling Firestore Batch Write ## Trigger action on data change[​](/integrations/database/cloud-firestore/firestore-actions.md#trigger-action-on-data-change "Direct link to Trigger action on data change") Sometimes, you might want to trigger an action whenever the data changes inside the collection. For instance, In a news app, you might want to notify users when new news is available, like this: To do so: 1. Ensure you have added a **Query Collection** or **Document from Reference** on a widget with **Single Time Query** disabled. 2. Now, on the widget with **Query Collection** or **Document from Reference**, open the **Action Flow Editor** and set **On Data Change** as the [Action Trigger](/resources/functions/action-triggers.md). This ensures that any actions you add will be triggered whenever the data is updated, added, or deleted. 3. You can now [add any action](/resources/functions/action-flow-editor.md#adding-an-action-example) you want to perform, such as showing a notification, refreshing the UI, or fetching related data. info If you are using this trigger on a ListView, make sure to **disable** the **Infinite Scroll**. --- # Firestore Content Manager The Firestore Content Manager provides an easy way to visually create, edit, and add documents to your [**Firestore database**](/integrations/database/cloud-firestore/getting-started.md). info Subcollections are not supported in Content Manager at this time. Prerequisites Before getting started with this section, ensure you: 1. Become familiar with [**Structuring the Firebase Database**](/integrations/database/cloud-firestore/getting-started.md#structuring-the-database). 2. Completed all steps in the [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md). 3. Create a [**Collection**](/integrations/database/cloud-firestore/creating-collections.md). 4. [**Defined the Fields**](/integrations/database/cloud-firestore/creating-collections.md#define-schema-creating-fields) for the collection. Only fields defined in your Firebase schema are shown in the Firebase Content Manager. ## Adding Document[​](/integrations/database/cloud-firestore/firestore-content-manager.md#adding-document "Direct link to Adding Document") Before you add a new document to the collection, make sure you have some Fields added. For instance, the 'exam\_result' collection with basic fields looks like this: ![img\_12.png](/assets/images/img_12-97c5c5ba66602e35e36bf86d8b9995f8.png) 'exam\_result' collection To add a document: 1. Head to the **Firestore** (left side Navigation Menu) and click **Manage Content**. This will open up a new browser window. 2. Select the **Collection** to which you want to add a document and then select + **Add Document.** A popup will appear. 3. Enter the information for the record and click **Add Document**. caution If you get this error "**Could not create an account as to your Firebase project**", just enable the '[**Email Sign-In**](/integrations/authentication/firebase/email-login.md)' in your Firebase project. ![img\_13.png](/assets/images/img_13-252de78599d465db3bb3b5ca1d49aa86.png) ### Upload CSV file for bulk addition[​](/integrations/database/cloud-firestore/firestore-content-manager.md#upload-csv-file-for-bulk-addition "Direct link to Upload CSV file for bulk addition") You might want to migrate your data from somewhere else to the collection of your current project. Adding an extensive list of records one by one is an incredibly time-consuming process. If you can get or already have data in a CSV (comma-separated values) file, we allow uploading the CSV file, and your data will be loaded into the collection in just a few steps. info To successfully upload the data: * Ensure you have header rows in your CSV file. The header should contain the exact name of the fields you have in your collection. * If you are uploading lat-long data, make sure you format it like (lat, lng) or \[lat,lng]. * Dates must be in a format like YYYY-MM-DD HH:MM :SS , where hours should be in 24hrs format (e.g., 2022-11-07 13:05:32). To better understand, here is the sample places collection and CSV file: ![img\_14.png](/assets/images/img_14-1e462d868d3005b30aec2e5572e00be7.png) ***places.csv*** ``` name,location,last_updated Central,"(40.76835069123224, -73.97203144014624)",2022-11-07 13:05:32 Museum,"(40.8217031079394, -73.9256367137398)",2022-11-09 16:12:02 Zoo,"(40.85452267684994, -73.8774290321384)",2022-11-04 03:05:54 ``` Here's how you upload the CSV file: 1. Select the **Collection** and click the **Upload CSV** button (see top right side). A popup will open. 2. Click **Select File** and upload your CSV file. 3. Now, you can choose the **Separator Type** and enter the **Number of Rows to Upload**. If you leave this empty, all records will be imported. 4. Click **Upload CSV** button. 5. Once the file is uploaded, you'll see the preview of data with field name and its data type. 6. Click **Validate & Import**. If everything looks good, this will import the data and you can **Finish and Close**. If there is any issue with data type mismatch or formatting issue, you'll see a message like this: ![img\_15.png](/assets/images/img_15-7583d2accfff425dc4bf8a3f2d8ab94d.png) Formatting issue If your CSV file contains additional fields, you'll go through a quick *field import process* that will add the new fields with their data in your collection. *** ## Adding Advanced Fields[​](/integrations/database/cloud-firestore/firestore-content-manager.md#adding-advanced-fields "Direct link to Adding Advanced Fields") You might want to add some advanced fields to store data, such as a Document Reference, DateTime, LatLng, and Multiple Items. Let's see how to add them using Firestore Content Manager. ### Document Reference[​](/integrations/database/cloud-firestore/firestore-content-manager.md#document-reference "Direct link to Document Reference") To store the document reference, make sure you have a Field with **Data Type** set to **Doc/Record Reference** and **Reference Type** set to your **Collection**. The field looks like this: ![img\_16.png](/assets/images/img_16-7b20d6bc8df9015f970c3e4e2aeef656.png) To add a document reference: 1. First, select the **Collection** from which you want to get a document reference. 2. Click on the **id** of the record to **copy** the document reference. 3. Now, select the **Collection** you would like to add a document to and then select + **Add Document.** A popup will appear. 1. Find the **Field** that accepts document reference and **paste** it 2. Click **Add Document**. ### Date Time[​](/integrations/database/cloud-firestore/firestore-content-manager.md#date-time "Direct link to Date Time") To store the DateTime, make sure you have a Field with **Data Type** set to **Timestamp**. The field looks like this: ![img\_17.png](/assets/images/img_17-e8c831e2fba6ac377bbcf720adb80ae5.png) To add a Date Time: Select the **Collection** you would like to add a document to and then select + **Add Document.** A popup will appear. 1. Find the **Field** that accepts DateTime. 2. Click on it, choose the **Date,** and then click **OK**. 3. Now, select **Time** and click **OK**. 4. Click **Add Document**. note To modify the given Date Time, click on the Date Time Field again to open the Date Picker dialog. ### Lat Lng[​](/integrations/database/cloud-firestore/firestore-content-manager.md#lat-lng "Direct link to Lat Lng") To store the Latitude and Longitude of any place, make sure you have a Field with **Data Type** set to **Lat Lng**. The field looks like this: ![img\_18.png](/assets/images/img_18-d9aa8af378581bbeb0fb5b94368adb9e.png) To add a Lat Lng for any place: Select the **Collection** you would like to add a document to and then select + **Add Document.** A popup will appear. 1. Find the **Field** that accepts LatLng. There are two ways you can add LatLng. * Directly add LatLng value for any place. * Click on the icon to find the place and get the LatLng. 2. Click **Add Document**. ### Multiple Items[​](/integrations/database/cloud-firestore/firestore-content-manager.md#multiple-items "Direct link to Multiple Items") To store the multiple items of the same data type, For example, a list of Fruit names, make sure you have a Field with **Data Type** set and **Field Type** set to **List**. The field looks like this: ![img\_19.png](/assets/images/img_19-0e3e23776ec20189dd46edf05eca64f2.png) To add data to List Field: 1. Select the **Collection** you would like to add a document to and then select + **Add Document.** A popup will appear. 2. Find the **Field** that accepts a list and click on it. 3. Click on the **+ Add Item** and enter the value. 4. Similarly, add more items. 5. Click **Add Document**. ### Custom DataType (aka Firestore Map)[​](/integrations/database/cloud-firestore/firestore-content-manager.md#custom-datatype-aka-firestore-map "Direct link to Custom DataType (aka Firestore Map)") To add data to a custom data type field: Select the **Collection** you would like to add a document to and then select + **Add Document**.A popup will appear. 1. Find the **Field** that accepts a custom data type. 2. Select **Tap to Set Fields (Unset)** or **Tap to Edit Fields** (based on whether you are creating or updating the document). This will open a new popup. 3. Enter the values for the fields of the custom data type. 4. Select **Save Data**. 5. Click **Add Document**. *** ## Updating Document[​](/integrations/database/cloud-firestore/firestore-content-manager.md#updating-document "Direct link to Updating Document") To update a document: 1. Select the **pencil icon** in the row of the Document you want to update\*\*.\*\* You can also open the record by long-pressing any field in the Document (excluding the ID). 2. A popup will appear. Update the document as needed and then select **Update Document.** 3. You will now see the updated information displayed in your collection. *** Other Tips & Tricks * Clicking on the ID field will copy the \*reference\* to a record. This is a helpful feature when you need to reference a user while you are creating a document. * Clicking on assets will open the asset URL. *** ## FAQ[​](/integrations/database/cloud-firestore/firestore-content-manager.md#faq "Direct link to FAQ") Getting 'Error updating Firestore Security Rules...' To fix this issue, you must [**deploy the Firestore Rules**](/integrations/database/cloud-firestore/firestore-rules.md#deploy). Getting the error "Could not create an account as to your Firebase project. If you encounter such an issue, you just need to enable the [**Email Sign-In**](/integrations/authentication/firebase/email-login.md) in your Firebase project. --- # Firestore Rules Firestore security rules are essential in safeguarding your Firebase data from potential malicious users. These rules not only enhance security but also give you control over data access within your application. With Firestore rules, you can enforce restrictions, ensuring that only authorized users can interact with specific data. For instance, you can configure Firestore rules to permit appointment creation only for authenticated users, such as those who have signed in via Email, Google Sign-in, or other authenticated methods. tip If you are brand new to Firestore rules, check out this overview about [**Getting Started With Firestore Rules**](https://firebase.google.com/docs/firestore/security/get-started). ## Creating Firestore Rules[​](/integrations/database/cloud-firestore/firestore-rules.md#creating-firestore-rules "Direct link to Creating Firestore Rules") There are two ways you can set the Firestore Rules: 1. [Using FlutterFlow Firestore setting](/integrations/database/cloud-firestore/firestore-rules.md#1-using-flutterflow-firestore-settings) 2. [Using Firestore Database Console](/integrations/database/cloud-firestore/firestore-rules.md#2-using-firestore-database-console) ### 1. Using FlutterFlow Firestore Settings[​](/integrations/database/cloud-firestore/firestore-rules.md#1-using-flutterflow-firestore-settings "Direct link to 1. Using FlutterFlow Firestore Settings") To set up basic rules, you can use the *Firestore Setting* available right inside FlutterFlow. #### Overview of Firestore Rules inside of FlutterFlow[​](/integrations/database/cloud-firestore/firestore-rules.md#overview-of-firestore-rules-inside-of-flutterflow "Direct link to Overview of Firestore Rules inside of FlutterFlow") You can control the following operations that can be performed on a document: * **Create:** Allow users to create a new document inside the collection. * **Read:** Allow users to read documents inside the collection. * **Write:** Allow users to update a document of a collection. * **Delete:** Allow users to delete a document of a collection. ![img\_3.png](/assets/images/img_3-d37f2952bba1f6a6e4703b836f2ed6de.png) Default Rules We provide various levels of access control that allow you to define user permissions for data access: * **Everyone**: This grants access to all users, whether authenticated or unauthenticated, allowing them to create, read, write, and delete documents. * **Authenticated Users**: Access is limited to authenticated users only, such as those who have signed in through Email, Google Sign-in, etc. Any user logged into the app can now create, read, write, and delete documents. * **Tagged Users**: Allow users to read/update/delete a document if they are tagged in that document. For example, say there is a "posts" collection with a `created_by` field representing the user who created the post. Then the "Tagged User" rule can be set on the `created_by` field to only allow accessing (read/update/delete) the post if the logged-in user is the one who created it. ![img\_4.png](/assets/images/img_4-f33dd4a3f82494821ec3d9f33f715b3b.png) * **Users Collection**: Allow users whose authentication id is the same as the id of a document. Tip: This option is only applicable to a 'users' collection. * **No One**: No one is allowed to create/read/write/delete a document. Note For 'Tagged Users,' the document must contain a field that can either be a reference to the user or a string with the user id. #### Default rules applied to new collections[​](/integrations/database/cloud-firestore/firestore-rules.md#default-rules-applied-to-new-collections "Direct link to Default rules applied to new collections") When you create a new collection inside the [Firestore Content Manager](/integrations/database/cloud-firestore/firestore-content-manager.md), below are the default rules applied to the collection: * **Create -> Everyone**: All users can create a document. * **Read -> Everyone**: All users can read documents. * **Write -> No One**: No one can update a document. * **Delete -> No One**: No one can delete a document. ![img\_5.png](/assets/images/img_5-ee72ce8c6324bb4bfe724e501a717e5e.png) Default Rules The default rule is suitable while you are getting started, but before the app goes live, please think about limiting access to any collections that potentially include the user's private information. To help you with that, we mark it as 'Has Private Data'. This will show you a warning to update the rule and restrict access. For example, a newly created 'notes' collection allows everyone to read all notes by default. In reality, only the user who created it should be able to read it. But because we have marked it as '**Has Private Data**' it will show a warning like the one below, and you can modify the rules that allow only a user to read notes who created it. ![img\_6.png](/assets/images/img_6-c019fedd01873bec231fb5140062f695.png) Firestore Warning If you want more control over a specific collection, you can remove the FlutterFlow generated rule by checking the **Exclude** option. And then, you can set up advanced or custom security rules using the Firestore Database console. info To bring the rules into effect, you must deploy them. Click the **Deploy** button from here, and you will see the deployed rules at **Firebase Console > Firebase Database > Rules.** When a user is deleted from your app, you might want to delete all records and data associated with that user as well. To do so, first set the 'Tagged Users' for the delete rule, and then check the () option. #### Example: How to use Firestore Rules?[​](/integrations/database/cloud-firestore/firestore-rules.md#example-how-to-use-firestore-rules "Direct link to Example: How to use Firestore Rules?") Let's take an example to set up the rules on a *todos* collection for the following requirements: * Only authenticated users should be able to create a Todo item. * All users (authenticated/unauthenticated) can see all the Todo items. * Only a user who created the Todo item can update it. * No one can delete a Todo item. To set up the Firestore Rules for the above requirements: 1. Inside the **Firestore Rules** section, set the **Create** to **Authenticated Users**. 2. Set the **Read** to **Everyone**. 3. Set the **Write** to **Tagged Users**. This will open a popup named **Tag Users**. 2. Inside the dropdown, click on **Unset** and select the field that contains either user reference or user id. 5. Click **Save Changes**. 4. Set the **Delete** to **No One**. 5. Now you can [deploy](/integrations/database/cloud-firestore/firestore-rules.md#deploy) the rules. caution The rules set in the above examples are for simplification purposes. You should carefully understand your requirements and set the rules accordingly. ### 2. Using Firestore Database Console[​](/integrations/database/cloud-firestore/firestore-rules.md#2-using-firestore-database-console "Direct link to 2. Using Firestore Database Console") To set up more advanced or custom rules you can use the Firebase Cloud Firestore Console. Let's take an example to set up the rules on a *todos* collection for the following requirements: * To create a Todo item, a user must be authenticated and verified via email or phone, and it must be a valid Todo item. * All users (authenticated/unauthenticated) can see all the Todo items. * Only a user who created the Todo item can update it with valid Todo details. * Only a user who created the Todo item can delete it. To set up the Firestore Rules for the above requirements: 1. Open the Firebase console of your project, and click on the **Firestore Database** in the left side menu. 2. Select the **Rules** tab. 3. Paste the following code and click on **Publish**. ``` rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 1. function isSignedIn() { return request.auth != null; } // 2. function verified() { return request.auth.token.email_verified || request.auth.token.phone_number; } // 3. function isValidItem() { return request.resource.data.name.size() > 0 ; } match /todos/{document} { // 4. allow create: if isSignedIn() && verified() && isValidItem(); // 5. allow read: if true; // 6. allow write: if isValidItem() && resource.data.created_by == /databases/$(database)/documents/users/$(request.auth.uid); // 7. allow delete: if resource.data.created_by == /databases/$(database)/documents/users/$(request.auth.uid); } match /users/{document} { allow create: if request.auth.uid == document; allow read: if true; allow write: if request.auth.uid == document; allow delete: if false; } match /{document=**} { allow read, write: if request.time < timestamp.date(2022, 3, 4); } } } ``` Here’s a quick rundown of what’s going on in the code above: 1. **isSignedIn()**: This checks whether a user is authenticated. 2. **verified()**: This checks whether the user is verified via email or phone. 3. **isValidItem()**: This checks whether the Todo item is not empty. 4. **create**: Allow to create a Todo item only if a user is authenticated, verified, and created a valid Todo item. 5. **read**: Allow all users to see all Todo items. 6. **write**: Allow to update a Todo item with valid details to a user who created it. 7. **delete**: Allow to delete a Todo item to a user who created it. ## Deploy[​](/integrations/database/cloud-firestore/firestore-rules.md#deploy "Direct link to Deploy") To deploy the Firestore Rules, simply hit the **Deploy** button. Before you finally deploy the new rules, a popup asks you to review your changes. Here, you can check the difference between the before and after versions of the Firestone Rules and then click **Deploy Now**. caution * You must deploy rules every time you make a change. * Before publishing your app, ensure you remove default Firestore rules, such as 'allow read, write: if request.time < timestamp.date(2024, 5, 31);' and exit Test mode. ![img\_7.png](/assets/images/img_7-4144c7e2f496368e69e93a72ba51232a.png) ## Reverting to previous rules[​](/integrations/database/cloud-firestore/firestore-rules.md#reverting-to-previous-rules "Direct link to Reverting to previous rules") You can go back to the previous rule state with Firebase Cloud Firestore Console: 1. Open the Firebase console of your project, and click on the **Firestore Database** in the left side menu. 2. Select the **Rules** tab. 3. Select and copy the previous rule from the left-side menu. 4. Select the current rule from the left side menu and paste the previous rule. 5. Click on **Publish**. Learn More Learn more about [**creating custom Firestore Rules**](https://fireship.io/snippets/firestore-rules-recipes/). ## FAQs[​](/integrations/database/cloud-firestore/firestore-rules.md#faqs "Direct link to FAQs") Getting an error, "cloud resource location is not set," "It looks like you haven't used Cloud Firestore in this project before" or a red alert while deploying rules. **Error-1** ![img\_8.png](/assets/images/img_8-7fe4c5fe4e410c57b16c0be27e9317e9.png) **Error-2** ![img\_9.png](/assets/images/img_9-f9906f201f4bb7c03f4b000e3e997a37.png) If you encounter such issues, the 'Default GCP resource location" is probably not set in your Firebase project. To fix this issue: 1. First, ensure that you have [**configured the Cloud Firestore**](/integrations/firebase/connect-to-firebase.md#enable-firestore-for-database-access) 2. And then, head over to the second link (from the error) and set the GCP resource location. ![img\_10.png](/assets/images/img_10-acdc9f00cd36548d0ee86e3dbd5bf3d5.png) Highlighted Link ![img\_11.png](/assets/images/img_11-c9169a52d249792339aea6654fc71d6f.png) Set the link to Firebase Console > General Settings > Default GCP Resource Location --- # Cloud Firestore [Firestore Database](https://firebase.google.com/docs/firestore) is a product from Google's [Firebase](https://firebase.google.com/). It's a flexible, scalable, NoSQL cloud database. It allows you to store your app data and uses real-time listeners to keep the data in sync. Let's understand the Firestore database (Cloud Firestore, a NoSQL Database) in more detail. ## What is a NoSQL Database[​](/integrations/database/cloud-firestore/getting-started.md#what-is-a-nosql-database "Direct link to What is a NoSQL Database") The NoSQL database is a schema-less database. That means the data is NOT stored in the table format. You actually don't have any restrictions on how you store your data. The Firestore database uses the collection-document model to store the data. Key terms to remember: * **Collection:** A collection is simply a set of 'documents.' * **Document:** A document is a record that contains the 'fields.' * **Fields:** The key-value pairs inside the document are called 'fields.' e.g., name, place, age, etc. To better understand, see the figure below: ![img.png](/assets/images/img-f99f4388b62b57262c21368ac5281a0c.png) Collection document model Every user's information is kept in a unique document. Multiple of these documents come together to form a collection. The beauty of this system is that not all documents within a collection need to have identical fields. So, if you decide to add a new field (e.g., DOB, image) to a new document, there's no need to go back and add it to older ones. *** ## Structuring the Database[​](/integrations/database/cloud-firestore/getting-started.md#structuring-the-database "Direct link to Structuring the Database") To see how to structure the database, consider an example that allows users to comment on a post. With FlutterFlow, you can structure the database in the following ways: * [Top-level collections](/integrations/database/cloud-firestore/getting-started.md#top-level-collections) * [Subcollections within documents](/integrations/database/cloud-firestore/getting-started.md#subcollections-within-documents) ### Top-level collections[​](/integrations/database/cloud-firestore/getting-started.md#top-level-collections "Direct link to Top-level collections") In Top-level collections, multiple collections are created at the root level of your database. For example, you create collections such as 'comments' and 'posts' at the root level. Comments for all the posts are stored in a single top-level collection. To know which comment belongs to which post, you include additional reference fields that distinctly identify each post within this structure. ![img\_1.png](/assets/images/img_1-7304390dcb8f4f7d5971a1ed1d6c13f1.png) Top-level collection Pro Tip Use top-level collections when you often search or filter within one collection without depending on another. For instance, if you want to see all comments, regardless of their related post (i.e., showing comments with the most likes). ### Subcollections within documents[​](/integrations/database/cloud-firestore/getting-started.md#subcollections-within-documents "Direct link to Subcollections within documents") Collections are created inside the document. Such a collection is called subcollection. For example, you create the top-level collection, such as posts, and then create a 'comments' collection (as a subcollection) inside the 'posts' collection. The advantage? You don't need extra tags or reference fields to know which post a comment belongs to; it's already grouped right there. ![img\_2.png](/assets/images/img_2-5a73a7fd54b5b0550b9fe0617341145c.png) Subcollections Pro Tip Subcollection is best when you have several queries or filters or search on a collection that is based on the other collection. For example, loading or searching the comments of a specific post (i.e., show all comments of a specific post that have more likes.) info You can secure the data using the [**Firestore Rules**](/integrations/database/cloud-firestore/firestore-rules.md). *** Learn more [**MongoDB**](https://www.mongodb.com/), [**Cassandra**](https://cassandra.apache.org/_/index.html), and [**ElasticSearch**](https://www.elastic.co/) are the other No-SQL database solutions that exist in the market. If you are a visual learner, you can check out the video: ## Manage Databases[​](/integrations/database/cloud-firestore/getting-started.md#manage-databases "Direct link to Manage Databases") You can also create multiple Firestore databases within a single Firebase project. This is especially useful for enterprise use cases, for example, when managing region-based databases or supporting multiple clients with isolated data stores. Additionally, you can use multiple databases to simulate different environments such as development, staging, and production. **However, note that** this setup is not directly related to the [Development Environments](/testing/dev-environments.md) in FlutterFlow, which operates independently of Firebase's multi-database configuration. This means that you’ll need to manually switch Firestore Database ID when switching Development Environments. To create a new database, go to the **Firebase Console > Firestore Database** section. Click the button next to the default database, i.e, **Add database**. Choose a region and configure your security rules. Once the new database is created, you can switch between databases using the dropdown. Next, copy the new **Database ID** and navigate to **FlutterFlow > Settings and Integrations > Firebase > Advanced Settings**. Paste the ID into the **Firestore Database ID** input field. Finally, regenerate the config file. Your app will now use the newly created database. --- # Refresh Database Request \[Action] Using this action, you can see the updated values of an item inside the scrollable widgets such as ListView, GridView, StaggeredView, Row, and Column. Prerequisites If you are querying data via a Backend Query, ensure you have enabled the **Single Time Query** in the Backend Query properties (Query Collection or API Call) on any scrollable widget. Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. On the right side, search and select the **Refresh Database Request** (under *Backend/Database*) action. 1. From the dropdown, select the widget (e.g., ListView, GridView, etc.) on which you have added the backend query. 2. By default, the **Wait for Result** option is enabled. That means the subsequent action(s) will only trigger after this action is finished. If any subsequent action is not dependent on this action or you want to trigger them regardless of the completion of this action, you can turn off this option. 3. When the **Wait for Result** is enabled, you can specify the **Min Wait Time** and **Max Wait Time** in ms (e.g., 1000ms = 1 second). * **Min Wait Time**: Time before triggering the following action(s) or refreshing the UI. * **Max Wait Time**: Time after which the subsequent action(s) will trigger regardless of the completion of this action. 5. Click **Close**. --- # SQLite SQLite is a compact, efficient database management system. Unlike conventional databases that require a server, SQLite is serverless and embeds directly into applications. It's perfect for mobile apps where resources are limited, and a full-fledged database server is impractical. For example, it's ideal for a mobile app that needs to store data locally, such as a personal finance tracker or a health record app, especially when offline functionality is required. caution Currently, we don't support SQLite on Web-based apps. Let's understand how you can utilize SQLite in your app with an example. An app where users can add, update, and delete Notes. Here's how it looks when completed: Here are the steps to build such an example: 1. [Enable SQLite](/integrations/database/sqlite.md#1-enable-sqlite) 2. [Database configuration](/integrations/database/sqlite.md#2-database-configuration) 3. [Add SQL queries](/integrations/database/sqlite.md#3-add-sql-queries) 4. [Display all notes](/integrations/database/sqlite.md#4-display-all-notes) 5. [Add note](/integrations/database/sqlite.md#5-add-note) 6. [Update note](/integrations/database/sqlite.md#6-update-note) 7. [Delete note](/integrations/database/sqlite.md#7-delete-note) ## 1. Enable SQLite[​](/integrations/database/sqlite.md#1-enable-sqlite "Direct link to 1. Enable SQLite") To enable SQLite in FlutterFlow, navigate to Settings and Integrations > Integrations > SQLite > switch on the **Enable SQLite** toggle. ![img.png](/assets/images/img-69f9c51c511fba175c15e7c8ca5c2e0d.png) ## 2. Database configuration[​](/integrations/database/sqlite.md#2-database-configuration "Direct link to 2. Database configuration") In the database configuration step, you'll need to upload your SQLite database file and assign a name to it. This process is crucial for initializing the database when your app launches. If you don't yet have an SQLite database, you can easily create one using tools like [sqlitebrowser](https://sqlitebrowser.org/). Simply download [sqlitebrowser](https://sqlitebrowser.org/dl/), create a new database, set up your tables, and optionally add some data. After preparing your database, upload the file to FlutterFlow to integrate it with your app. For this example, we'll create a "Notes" table with `ID`, `Title`, `Details`, `DueDate`, and `IsCompleted` as columns. warning It is advisable to avoid using any SQL reserved keywords such as `type` and `data` as column names to prevent potential build errors or unexpected behavior. SQLite reserves certain words for its SQL syntax, and using these as identifiers without proper handling may cause issues. For a comprehensive list of reserved keywords, refer to the [**SQL reserved words**](https://en.wikipedia.org/wiki/List_of_SQL_reserved_words). Here's how you can create and configure the database: Important to note SQLite does not have dedicated date-time or boolean data types. For storing date-time values like `DueDate`, we use the integer data type and represent the date-time as a [**UNIX timestamp**](https://www.unixtimestamp.com/). Similarly, for boolean values, such as checking if a note is completed, SQLite uses integers where `0` represents `false` (or not completed) and `1` represents `true` (or completed). ## 3. Add SQL queries[​](/integrations/database/sqlite.md#3-add-sql-queries "Direct link to 3. Add SQL queries") SQL queries are statements used to interact with a database. We allow you to add queries in two different sections: #### 1. Read Queries[​](/integrations/database/sqlite.md#1-read-queries "Direct link to 1. Read Queries") This includes statements that retrieve data from the database but do not modify anything. Some common examples: * `SELECT * FROM customers;` - retrieve all rows and columns. * `SELECT name, city FROM customers;` - retrieve specific columns. * `SELECT * FROM customers WHERE city = 'New York';` - retrieve rows that match a condition. #### 2. Update Queries[​](/integrations/database/sqlite.md#2-update-queries "Direct link to 2. Update Queries") This includes statements that modify the database, such as: * `INSERT INTO customers (name, address, city) VALUES ('John', '555 Main St', 'New York')` - add new rows. * `UPDATE customers SET address = '123 Main Street' WHERE name = 'John'` - update existing rows. * `DELETE FROM customers WHERE city = 'Chicago'` - delete rows that match a condition. In general, to add any query, you need to provide a name, the query statement, and variables that are used to pass values from your app to queries. For *Read Queries*, you have to define the output columns as well. This will help you display the row data in the UI by selecting the column name. tip * To use variables, simply use the syntax `${variableName}`. For example: `SELECT \* FROM Notes WHERE id = ${noteId}` * When passing string or text data in queries, enclose variables in single quotes, like `${title}`, to signify them as strings. ![img\_1.png](/assets/images/img_1-f3fb3ba48d21061a2b7784fd6091e621.png) Below are the queries that we'll require for this example: #### 1. GetAllNotes[​](/integrations/database/sqlite.md#1-getallnotes "Direct link to 1. GetAllNotes") This will retrieve all notes from the database. ``` Select * from Notes ``` #### 2. AddNote[​](/integrations/database/sqlite.md#2-addnote "Direct link to 2. AddNote") This will add a new note to the database. ``` INSERT INTO Notes (Title, Details, DueDate, IsCompleted) VALUES ('${title}', '${details}', ${dueDate}, 0); ``` #### 3. UpdateNote[​](/integrations/database/sqlite.md#3-updatenote "Direct link to 3. UpdateNote") This will update the existing note based on the note ID. ``` UPDATE Notes SET Title = '${title}', Details = '${details}', DueDate = ${dueDate}, IsCompleted = ${isCompleted} WHERE ID = ${id}; ``` #### 4. DeleteNote[​](/integrations/database/sqlite.md#4-deletenote "Direct link to 4. DeleteNote") This will delete the note based on the note ID. ``` DELETE FROM Notes WHERE ID = ${id}; ``` ## 4. Display all notes[​](/integrations/database/sqlite.md#4-display-all-notes "Direct link to 4. Display all notes") To show a list of notes, you can use the **ListView** > **Container** widgets to design a page that looks like the following: ![img\_2.png](/assets/images/img_2-d8caf311fc98a1220bc08f26b9d65fa4.png) Now, on the ListView widget, add a SQLite backend query as per the following instructions: ### Add a SQLite Query:[​](/integrations/database/sqlite.md#add-a-sqlite-query "Direct link to Add a SQLite Query:") Go to your project page and follow the steps below to define an SQLite query: * Select the widget (or page) on which to apply the query. * Select **Backend Query** from the Properties Panel (the right menu). * Click **Add Query** and set the Query Type to **SQLite Query**. * Select the **Query Name**. (Only Read Queries will be displayed here.) and click **Confirm**. Once you have the SQLite query defined, you can use the data retrieved from the query to display on widgets present inside. Follow the steps below: * Select the widget (e.g., Text) on which you want to display the data. * From the Properties Panel, open the Set from Variable menu > select \[your query name] Row > select the column data that you want display here and click **Confirm**. info In our example, the due date is stored as a Unix timestamp, which isn't user-friendly for display purposes. Therefore, we've included a custom function in the [example project](https://app.flutterflow.io/project/note-taking-app-zto2ua) that converts this timestamp into a human-readable date format. ## 5. Add note[​](/integrations/database/sqlite.md#5-add-note "Direct link to 5. Add note") You can add a new note in the database using the SQLite query Action with the type set to **Update Query** and Query Name to [AddNote](/integrations/database/sqlite.md#2-addnote). Here's how you do it: ## 6. Update note[​](/integrations/database/sqlite.md#6-update-note "Direct link to 6. Update note") For updating note values, like marking a note as completed or modifying other fields, utilize the SQLite Query Action and set the type to **Update Query**. Here, set the Query Name to [Update Note](/integrations/database/sqlite.md#3-updatenote). Here's how you do it: info * In this example, we are updating the note on a bottom sheet component. To provide a better user experience, we initially display the current values of the note, ensuring that users have a clear idea of what they are going to edit. To display the note values in bottom sheet, we [pass](/concepts/navigation/passing-data.md) the current note with **Type** set to **SQLite Row**. ![img\_3.png](/assets/images/img_3-bfc44730b7360439ac421a992f0d2c12.png) * When updating a date value, we also verify if the date has been modified. If there's no change, we simply pass back the same value we received. ## 7. Delete note[​](/integrations/database/sqlite.md#7-delete-note "Direct link to 7. Delete note") You can delete an existing note from the database using the [SQLite query action](/resources/backend-query/sqlite-query.md) with the type set to *Update Query* and Query Name to **Delete Note**. Pro Tip To refresh the page, simply add an [**Update App State Action**](/resources/data-representation/app-state.md) Action with the Update Type set to 'Rebuild Current Page'. Here's how you do it: Example project Check out the complete [**example project**](https://app.flutterflow.io/project/note-taking-app-zto2ua) for reference. ## FAQs[​](/integrations/database/sqlite.md#faqs "Direct link to FAQs") Can SQLite handle complex data structures compared to App State Variables? Yes, SQLite can handle complex data structures much more effectively. It allows for structured data storage, complex queries, sorting, and filtering, which are challenging to implement with app state variables. Is SQLite a good choice for apps that require offline functionality? Absolutely. SQLite stores data locally, making it an excellent choice for apps that need to operate offline. Users can access and manipulate data without needing an internet connection. Will using SQLite affect my app's performance compared to using App State Variables? SQLite is designed to be lightweight and efficient, so it generally won't negatively impact your app's performance. In fact, for larger data sets, it's more efficient than storing data in app state variables. How does SQLite ensure data security and integrity? SQLite maintains data integrity and supports transactional operations. This means it ensures the database state remains consistent even in cases of unexpected interruptions, like app crashes or power failures. --- # Supabase Database Actions The Supabase Database Actions allow you to **Insert, Update**, or **Delete a Row** from a Supabase table. Note that beyond actions, you can also setup [**Backend Queries**](/resources/backend-query.md) for Supabase. This includes realtime streaming queries. Prerequisites Before getting started with this section, ensure you have, 1. Completed all steps in the [**Supabase setup**](/integrations/supabase/setup.md) 2. Ensure you have a table created for adding, updating, and deleting data. ## Types of Supabase Database Actions[​](/integrations/database/supabase/database-actions.md#types-of-supabase-database-actions "Direct link to Types of Supabase Database Actions") Following are the types of actions you can perform on a Supabase table. * [**Insert Row**](/integrations/database/supabase/database-actions.md#insert-row-action): Adds a new row in a table. * [**Update Row**](/integrations/database/supabase/database-actions.md#update-row-action)**:** Updates a row with the specified values. * [**Delete Row**](/integrations/database/supabase/database-actions.md#delete-row-action)**:** Deletes a row from a table. * [**Query Rows**](/integrations/database/supabase/database-actions.md#query-rows-action): Retrieves rows from a table based on specific criteria or conditions. ### Insert Row \[Action][​](/integrations/database/supabase/database-actions.md#insert-row-action "Direct link to Insert Row \[Action]") 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Click on **+ Add Action**. 2. On the right side, search and select the **Supabase** > **Insert Row** action. 3. Set the **Table** to your table name (e.g., assignments). 4. Under the **Set Fields** section, click on the **+ Add Field** button. 5. Click on the Field name. 1. Scroll down to find the **Value Source** dropdown and change it to **From Variable**. 2. Click on **UNSET** and select **Widget State > Name** of the TextField. 6. Similarly, add the field for the other UI elements. Pro Tip While adding this action, you can leave the **id** (if marked as *Primary*) and **created\_at** (if default value is `now()`) fields. Supabase will automatically add values for these fields. ### Update Row \[Action][​](/integrations/database/supabase/database-actions.md#update-row-action "Direct link to Update Row \[Action]") 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Click on **+ Add Action**. 2. On the right side, search and select the **Supabase** > **Update Row** action. 3. Set the **Table** to your table name (e.g., assignments). 4. Optional: If you want to get the rows after the update is finished, enable the **Return Matching Rows** option. 5. Now, you must set the row you want to update. Usually, this is done by finding a row in a table that matches the current row ID. To do so, click **+ Add Filter** button inside the **Matching Rows** section. 1. Set the **Field Name** to the field that contains the IDs. Typically, this is the **id** column. 2. Set the **Relation** to **Equal To** because you want to find a row with the exact id. 3. Into the **Value Source**, you can select the **From Variable** and provide the id of the row for which you just updated values in the UI. 6. Under the **Set Fields** section, click on the **+ Add Field** button. 7. Click on the Field Name. 1. Scroll down to find the **Value Source** dropdown and change it to **From Variable**. 2. Click on **UNSET** and select **Widget State > Name** of the TextField. 8. Similarly, add the field for the other UI elements. How to & Tips If you have a flow like this, *HomePage* -> *AssignmentDetailsPage* -> *UpdateAssignmentPage*, you can enable the **Replace Route** option (see point no. 5 [here](/concepts/navigation/page-navigation.md#navigate-to-action)) when you navigate from *AssignmentDetailsPage* to *UpdateAssignmentPage*. And then chain the [Navigate Back](/concepts/navigation/page-navigation.md#navigate-back-action) action after the update action. This will directly open the *HomePage* after the row is updated. ### Delete Row \[Action][​](/integrations/database/supabase/database-actions.md#delete-row-action "Direct link to Delete Row \[Action]") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Click on **+ Add Action**. 2. On the right side, search and select the **Supabase** -> **Delete Row** action. 3. Set the **Table** to your table name (e.g., assignments). 4. Optional: If you want to know which rows were deleted from a table, enable the **Return Matching Rows** option. 5. Now, you must set the row you want to delete. Usually, this is done by finding a row in a table that matches the current row ID. To do so, click **+ Add Filter** button inside the **Matching Rows** section. 1. Set the **Field Name** to the field that contains the IDs. Typically, this is the **id** column. 2. Set the **Relation** to **Equal To** because you want to find a row with the exact id. 3. Into the **Value Source**, you can select the **From Variable** and provide the id of the row you want to delete. tip You can chain the [**Refresh Database Request**](/integrations/database/refresh-db-request.md) action after this action to remove the deleted items from the list. ### Query Rows \[Action][​](/integrations/database/supabase/database-actions.md#query-rows-action "Direct link to Query Rows \[Action]") There are certain scenarios where you may want to query a Supabase table manually. For example, you might want to only fetch data in response to a specific user action, such as clicking on a button. Additionally, if your app fetches different data under different conditions, you might find it more convenient to manually call queries. For example, you might fetch different tasks for admin and team members. To manually query a Supabase table, follow the steps below to define this action to any widget: 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on **+ Add Action**. 4. On the right side, search and select the **Supabase** > **Query Rows** action. 5. Select the **Table** you want to query. 6. You can also [Filter](/integrations/database/supabase/database-actions.md#filtering-table-data) and [Order](/integrations/database/supabase/database-actions.md#ordering-table-data) the query results. 7. Provide the **Action Output Variable Name**. This will be used to store the query result. 8) Now, you can use the **Action Output Variable Name** provided in the previous step to generate children from a variable on **ListView**. 9) Finally, you can display data in a **Text** widget. To do so, select the **Text widget > Properties Panel > Text > Set from Variable menu > ***\[children\_from\_variable\_name]*** item > Get Row Field > select the row field** you want to display. #### Filtering table data[​](/integrations/database/supabase/database-actions.md#filtering-table-data "Direct link to Filtering table data") Sometimes you might want to filter a list based on a condition. For example, showing only completed assignments. You can do so by adding the Filter while you query a Supabase table. Let's see how to filter the Supabase table to display only desired items: * In your **Action properties** of Query Rows action, scroll down and click on the **+ Add Filter** button at the bottom. * Find the **Field Name**, click on the Unset, and select a column on which you would like to apply the filter. * Find the **Relation** dropdown, click on the Unset, and choose the relation amongst the list. * Find the **Value** property and set it to an appropriate value and click Confirm. tip You could choose a filter relation based on your requirements. For example: * **Equal To**: To show only completed assignments, set the **Field Name** to the column that holds the completion status (e.g., **is\_done**), set the **Relation** to **Equal To**, and set the **Value** to **True**. * **Greater Than**: To show only users older than 30, set the **Field Name** to the **age** column, set the **Relation** to **Greater Than**, and set the **Value** to 30. * **Like**: For filtering addresses with zip codes starting with '35,' set the **Field Name** to the **zip\_code** column, set the **Relation** to **LIKE**, and set the **Value** to **35%**. In the value field, you use the following wildcards to perform flexible pattern matching to filter your data effectively. * **Percent (`%`) Wildcard**: Represents zero, one, or multiple characters. * Example: `'A%'` matches any string starting with `'A'` (e.g., `'Apple'`, `'Apex'`). * Example: `'%A%'` matches any string containing `'A'` (e.g., `'Canada'`, `'Australia'`). * **Underscore (`_`) Wildcard**: Represents a single character. * Example: `'A_'` matches any two-character string starting with `'A'` (e.g., `'An'`, `'At'`). * Example: `'A__'` matches any three-character string starting with `'A'` (e.g., `'Ant'`, `'Art'`). info You can combine multiple filters using **AND** or **OR** operators to create more advanced filtering logic. This enables you to refine your data query to match specific conditions. #### Ordering table data[​](/integrations/database/supabase/database-actions.md#ordering-table-data "Direct link to Ordering table data") You might want to show a list from the Supabase table in a specific order. For example, showing assignments in order of the due date. To specify the order: * In your **Action properties** of Query Rows action, scroll down and click on the **+ Add Order** button at the bottom. * Set the **Table Field Name** to the column you would like to choose for ordering. * Find **Order** dropdown, click on the Unset and choose the order either **Increasing** or **Decreasing** and click **Confirm**. tip You could choose the order based on your requirements. For example, to show assignments in order of due date, set Table Field Name to due\_date and Order to Increasing. info Additional Note: Currently, you can only add "and" conditions to Supabase query filters. If you want to add an "or" filter like "status == 5 or status == 8", you can consider logic to apply "status in (5,8)" or any other logic. Fully customizable using API calls or custom actions. ## Trigger Action On Data Change[​](/integrations/database/supabase/database-actions.md#trigger-action-on-data-change "Direct link to Trigger Action On Data Change") Sometimes, you may want to trigger an action whenever data changes in a Supabase table. For instance, in an ecommerce app, you might want to notify users on the orders page when the status of their order is updated. To respond to data changes in a Supabase table: 1. Ensure you have added a **Supabase Query** to a widget (e.g., a ListView) with **Single Time Query** disabled to enable real-time updates. 2. On the widget with the **Supabase Query**, open the **Action Flow Editor** and set **On Data Change** as the [Action Trigger](/resources/functions/action-triggers.md). This ensures that any actions you add will be triggered whenever the data is updated, added, or deleted. 3. You can now [add any action](/resources/functions/action-flow-editor.md#adding-an-action-example) you want to perform, such as showing a notification, refreshing the UI, or fetching related data. info If you are using this trigger on a ListView, make sure to **disable** the **Infinite Scroll**. ## Offline Support for Supabase Apps[​](/integrations/database/supabase/database-actions.md#offline-support-for-supabase-apps "Direct link to Offline Support for Supabase Apps") If you need offline capabilities in your Supabase-powered app, consider using the **[PowerSync Library](https://marketplace.flutterflow.io/item/dm1cuOwYzDv6yQL2QOFb)** built by the **[PowerSync](https://www.powersync.com/)** team. It's designed specifically to enable seamless offline-first experiences by syncing your Supabase data locally and keeping it up to date when the device reconnects. --- # Import from FF Designer You can quickly bring your generated designs from [FF Designer](https://designer.flutterflow.io/) into FlutterFlow to continue building with real widgets, actions, and logic. This allows you to transform visual storyboards into fully functional app screens without recreating layouts manually. #### Step 1: Export from FF Designer[​](/integrations/designer/import-from-ff-designer.md#step-1-export-from-ff-designer "Direct link to Step 1: Export from FF Designer") 1. Open the top-left **FF Designer** menu. 2. Choose **Export to FlutterFlow**. 3. This copies the selected frames (or entire storyboard) to your clipboard. tip You can also use the shortcut **Cmd + C** to copy frames directly for faster export. #### Step 2: Import into FlutterFlow[​](/integrations/designer/import-from-ff-designer.md#step-2-import-into-flutterflow "Direct link to Step 2: Import into FlutterFlow") 1. Open your FlutterFlow project. 2. Navigate to the page where you want to paste the design. 3. Select any widget from the widget tree. 4. Paste the copied design. FlutterFlow will recreate the layout structure using real widgets, preserving hierarchy, spacing, and styling. To import a single component, copy the component’s root element and paste it into your widget tree. --- # Firebase Storage Library The [Firebase Storage Library](https://marketplace.flutterflow.io/item/Ec3NWw8sxqJ1tbriOIEE) provides access to the files in Cloud Storage through the Firebase SDK beyond what [FlutterFlow's built-in support](/concepts/file-handling.md) provides. ## Instructions[​](/integrations/firebase-storage/storage-library.md#instructions "Direct link to Instructions") To start using this library: 1. [Import the library](/resources/projects/libraries.md#importing-a-library) into your existing FlutterFlow project. 2. [Connect your FlutterFlow project to Firebase](/integrations/firebase/connect-to-firebase.md) (if you haven't done so already). The library will default to using the default bucket of your associated Firebase project. You can override this behavior by passing an explicit bucket URL to any of the actions. 3. [Use the Custom Actions](/concepts/custom-code/custom-actions.md#using-a-custom-action) and Custom Functions in your Action Flows. ### Custom actions[​](/integrations/firebase-storage/storage-library.md#custom-actions "Direct link to Custom actions") * `uploadFileToBucket` - Upload a file to any path in any bucket that you have write access to. * **Parameters:** * The `bucketName` (`String?`) to upload the file to. If you leave this empty, it uses the default bucket of the associated Firebase project. * The `fullPath` (`String?`) where the file will be written to inside the bucket. If this is specified, the `prefix` parameter is ignored. * The `uploadedFile` (`FFUploadedFile`) that is to be uploaded to Cloud Storage. This is the action output of a previous `Store media for upload` action. * The `prefix` (folder/directory) (`String?`) where the file will be uploaded to. If `fullPath` is not specified, the action uses this parameter and the `name` of the `uploadedFile` to determine the full path where it writes the file. * **Action result:** * If successful, the action result is a `fileObject` containing the full path of the uploaded file. * `listAllFilesInBucket` - List all files in any bucket that you have read access to. * **Parameters:** * The `bucketName` (`String?`) to list the files from. If you leave this empty, it uses the default bucket of the associated Firebase project. * The `listType` (`StorageListType?`) of the items to list (files, directories, both). If left empty, the action will list both files and prefixes (folders/directories). * The `prefix` (`String?`) is the `/` separated path from which to list files. If left empty, the action will list the items in the root of the storage bucket. * **Action result:** * If successful, the action results in a `List` of `fileObject` elements. * `downloadFile` - Download the data for a file that you have read access to. This downloads the actual data into your application code. If you instead want a public URL to the data, use `getDownloadUrl` instead. * **Parameters:** * The `bucketName` (`String?`) to download the file from. If you leave this empty, it uses the default bucket of the associated Firebase project. * The `fullPath` (`String`) of the file whose data will be read from the bucket. * **Action result:** * If successful, the action result is an `FFUploadedFile` with the data of the file that was read from the bucket. * `getDownloadUrl` - Get the download URL for a file in a bucket that you have read access to. This URL then provides public, read-only access to the file * **Parameters:** * The `bucketName` (`String?`) that contains the file. If you leave this empty, it uses the default bucket of the associated Firebase project. * The `fullPath` (`String`) of the file for which to get the download URL. * **Action result:** * If successful, the action result is a HTTP URL that allows public access to the file. * `getMetadataForFile` - Get the metadata for a file in any bucket that you have read access to * **Parameters:** * The `bucketName` (`String?`) that contains the file. If you leave this empty, it uses the default bucket of the associated Firebase project. * The `fullPath` (`String`) of the file for which to get the download URL. * A**ction result:** * If successful, the action result is a `FullMetadata` with all the metadata and custom metadata of the file. * `updateMetadataForFile` - Update the metadata for a file in any bucket that you have write access to * **Parameters:** * The `bucketName` (`String?`) that contains the file. If you leave this empty, it uses the default bucket of the associated Firebase project. * The `fullPath` (`String`) of the file for which to get the download URL. * The `metadata` (`SettableMetadata`) to write to the Cloud Storage bucket for the file. If any value is left out or empty in the metadata, it is left unmodified in Cloud Storage. * **Action result:** * If successful, the action result is a `FullMetadata` with all the metadata and custom metadata of the file after the update. * `getPathFromUrl` - Get the path for a file based on its (https\:// or gs\://) URL. This is a synchronous call, as it doesn't require any call to the server. * **Parameters** * The `Url` to parse. * **Action result:** * The action result is a `fileObject` derived from the URL. * `deleteFileFromBucket` - Deletes a file from any bucket you have write access to. * **Parameters:** * The `bucketName` (`String?`) that contains the file. If you leave this empty, it uses the default bucket of the associated Firebase project. * The `fullPath` (`String`) of the file to delete from the bucket. * **Action result:** * If the action succeeds the file has been deleted. There is no additional information. ### Enums[​](/integrations/firebase-storage/storage-library.md#enums "Direct link to Enums") * `StorageListType` is an enumeration of the types of items that the `listAllFilesInBucket` action can return. Values: * `files`: List only the files in the specified path. * `prefixes`: List only the prefixes in the specified path. You might more commonly refer to these as folders or directories, but since Cloud Storage doesn't actually have support for folders/directories, it uses `/` characters in the file names to emulate those and calls them prefixes. * `filesAndPrefixes`: List both files and prefixes in the specified path. ### Data Types[​](/integrations/firebase-storage/storage-library.md#data-types "Direct link to Data Types") * `fileObject` - the metadata for a file or prefix (folder/directory) in Cloud Storage. It has the following fields: * `fullPath` (`String`) - The full path of the file/prefix inside the storage bucket. The value does not start with a leading `/`. * `isPrefix` (`Boolean`) - Indicates whether the object is a file (`false`) or prefix (folder/directory) (`true`). * `FullMetadata` - the full metadata of an item in a storage bucket as returned by `getMetadataForFile`, modelled after the [`FullMetadata` class in the Firebase SDK for Cloud Storage](https://pub.dev/documentation/firebase_storage/latest/firebase_storage/FullMetadata-class.html). * `SettableMetadata` - the settable metadata of an item in a storage bucket, as passed to a call to `updateMetadataForFile`, modelled after the [`SettableMetadata` class in the Firebase SDK for Cloud Storage](https://pub.dev/documentation/firebase_storage/latest/firebase_storage/SettableMetadata-class.html). * `KeyValuePair` - A `String`/`String` key/value pair as used for the `customMetadata` in the `FullMetadata` and `SettableMetadata` data types. --- # Storage Rules Like [Firestore security rules](/integrations/database/cloud-firestore/firestore-rules.md), Firebase Storage security rules control who can access files uploaded by your users in your application. For example, by setting the storage rules, you can allow only authenticated users (e.g., via Email, Google Sign-in, etc.) to upload or send images. For beginners If you are new to storage rules, you may want to check out this overview about [**Getting Started With Storage Rules**](https://firebase.google.com/docs/storage/security). ## Deploying storage rules[​](/integrations/firebase-storage/storage-rules.md#deploying-storage-rules "Direct link to Deploying storage rules") To deploy the storage rules: 1. First, make sure Firebase Storage is enabled or configured in your project by visiting the [Firebase console](https://console.firebase.google.com/u/0/) and viewing the **Storage** tab. 2. Return to FlutterFlow, navigate to **Settings & Integrations > Project Setup > Firebase**. 3. Scroll down to the **Firebase Storage** section. 4. To set the storage rules outside of the FlutterFlow (i.e., from the Firebase Console), enable the **Manage Outside of FlutterFlow**. 5. To only allow accessing the images, videos, files, etc., to the users who uploaded it, enable **Make Users Uploads Private**. 6. Click the **Deploy** button. 7. A pop-up will open. Click **Yes** to continue and click **Deploy Now**. Learn more Learn more about Firebase Storage Rules [here](https://firebase.google.com/docs/storage/security). --- # App Check [Firebase App Check](https://firebase.google.com/docs/app-check) is a new security feature for protecting the backend services of apps. It blocks traffic that comes from sources other than the registered app, ensuring that usage costs are not incurred for illegitimate usage. App Check works by using attestation services, which already exist for iOS, Android, and the web. The feature can protect three different types of backends, including Firebase backends like Cloud Firestore, Google API services like Cloud Run, and API endpoints of your own server. ## **Adding Firebase App Check**[​](/integrations/firebase/app-check.md#adding-firebase-app-check "Direct link to adding-firebase-app-check") To add *Firebase App Check* to your app: 1. Navigate to the [Firebase Console](https://console.firebase.google.com/u/0/) > Build > App Check page. 2. If this is the first time, click the **Get started** button. ![Get started with App Check](/assets/images/get-started-e563b36a10af3562962ca8f78d842f5f.avif) 3. Now, you'll see the list of apps you have added to this Firebase project. To register attestation service(s), select the project, click **Register,** and then select attestation service. 1. For Android, you can select [Play Integrity](https://developer.android.com/google/play/integrity?authuser=1) and then follow step number 2 and 3 from [here](https://firebase.google.com/docs/app-check/android/play-integrity-provider?authuser=2#project-setup). 2. For iOS, you can choose from [Device Check](https://developer.apple.com/documentation/devicecheck) or [App Attest](https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity) and then follow step number 2 and 3 from [here](https://firebase.google.com/docs/app-check/ios/devicecheck-provider?authuser=2). 3. For the Web, select [reCAPTCHA v3](https://developers.google.com/recaptcha) or [reCAPTCHA Enterprise](https://cloud.google.com/recaptcha-enterprise) and then follow steps 2 and 3 from [here](https://firebase.google.com/docs/app-check/web/recaptcha-enterprise-provider?authuser=2#project-setup). **Note**: To run the app in Run/Test mode, you must register the **Web** version of the app as well. * Android * iOS * Web 4. Ensure that enabling Firebase App Check [won't disrupt your existing legitimate users](https://firebase.google.com/docs/app-check/monitor-metrics?authuser=2). 5. Now, you can select the service you want to secure. Switch to the **APIs** tab, select the service, and click **Enforce** button. A popup may open, telling you that once enabled, it will deny all requests that do not have *App Check* token. Click **Enforce** again if you are ok. **Note** that it might take up to 15 minutes to start the enforcement. 6) Navigate back to FlutterFlow and open **Settings and Integrations > Project Setup > Firebase >** scroll down and expand **App Check** section **>** switch on **Enable App Check** toggle. 7) You can fill out the optional details such as **reCAPTCHA Site Key** (you should have it while performing step 3.3) and [**Run/Test Mode Debug Token**](https://firebase.google.com/docs/app-check/flutter/debug-provider). To get the debug token, follow the steps below: 1. Navigate to the [Firebase Console](https://console.firebase.google.com/u/0/) > Build > App Check > Apps. 2. Open the app for which you want to generate the debug token. 3. Click three dots icon (i.e., overflow menu icon) and select **Manage debug token**. 4. Click **Add debug token**. 5. Give it a **Name** and click **Generate token**. 6. Copy the generated token and paste it in FlutterFlow's designated field. 7. Click **Save**. 5. You might want to see if it works on a real device or an emulator. To run on a real device, you can set the **Android Provider** to **Play Integrity** and to run on an emulator, set it to **Debug,** and then try checking it by downloading the APK. 1. If it doesn't work for *Play Integrity*, ensure you have enabled the Play Integrity API. See how to do it in step 2 [here](https://firebase.google.com/docs/app-check/android/play-integrity-provider?authuser=1\&hl=en#project-setup). 2. If it doesn't work for *Debug*, you can try [downloading the code](/flutterflow-cli/exporting.md), following the instructions [here](https://firebase.google.com/docs/app-check/flutter/debug-provider#android), and running it locally. tip To add the App Check on the app with the non-Firebase (i.e., your self-hosted) backend, follow the instructions [**here**](https://firebase.google.com/docs/app-check/flutter/custom-resource). --- # Connect to Firebase Firebase integration in FlutterFlow provides an effortless way to enhance your apps with powerful features such as user authentication, cloud storage, real-time databases, and more. This setup guide will walk you through integrating Firebase with FlutterFlow, empowering you to easily create feature-rich, scalable applications. ## Create a new Firebase project from FlutterFlow[​](/integrations/firebase/connect-to-firebase.md#create-a-new-firebase-project-from-flutterflow "Direct link to Create a new Firebase project from FlutterFlow") FlutterFlow allows you to automatically create a Firebase project directly from the builder using a quick three-step process. #### Step 1: Set Up Your Project[​](/integrations/firebase/connect-to-firebase.md#step-1-set-up-your-project "Direct link to Step 1: Set Up Your Project") Go to **Settings & Integrations > Project Setup > Firebase** in FlutterFlow to get started. #### Step 2: Select Your Region[​](/integrations/firebase/connect-to-firebase.md#step-2-select-your-region "Direct link to Step 2: Select Your Region") Hit **+ Create Project**. You’ll see a popup where you can confirm your project's name and choose the Firebase region that best serves your users. #### Step 3: Connect Your Google Account[​](/integrations/firebase/connect-to-firebase.md#step-3-connect-your-google-account "Direct link to Step 3: Connect Your Google Account") Choose **Create** or **Sign in with Google** to link your Firebase account. If asked, you must grant the access requested from 'flutterflow\.io' to be able to create and configure the Firebase project on your behalf. Here, you can **Select all** and click **Continue**. ![Alt text](/img/firebase/warning-firebase.png) Once initiated, FlutterFlow will handle the rest of the project creation in the background. Here's a quick walkthrough: [Shopping App - FlutterFlow](https://demo.arcade.software/C4Db1hkZU3Dyqd5VmY99?embed\&show_copy_link=true) As soon as the process is completed, you will see the following view in your Firebase Settings dashboard. ![Firebase Project Created](/img/firebase/firebase-created-managed.png) #### Enable Firebase Authentication[​](/integrations/firebase/connect-to-firebase.md#enable-firebase-authentication "Direct link to Enable Firebase Authentication") If you want to use the Firebase Authentication in your app or the Firebase Content Manager, you must enable the authentication in the Firebase console and enable the 'Email/Password' sign-in. #### Enable Firebase Storage[​](/integrations/firebase/connect-to-firebase.md#enable-firebase-storage "Direct link to Enable Firebase Storage") If you plan to use Firebase storage in your app, click on the Enable Storage on Firebase and enable it on Firebase console. #### Download Firebase Config files[​](/integrations/firebase/connect-to-firebase.md#download-firebase-config-files "Direct link to Download Firebase Config files") The configuration files are necessary when connecting to Firebase. It contains various settings and keys that enable your project to communicate with Firebase services. To generate those files, click on Auto Generate Config Files and then click Generate Files. ## Connect an existing Firebase project manually[​](/integrations/firebase/connect-to-firebase.md#connect-an-existing-firebase-project-manually "Direct link to Connect an existing Firebase project manually") If you already have a Firebase project and want to connect it to your current FlutterFlow project, go to **Settings & Integrations > Project Setup > Firebase** and click on the Firebase Setup Wizard. A pop-up dialog will appear. Follow these steps: #### Setup Firebase[​](/integrations/firebase/connect-to-firebase.md#setup-firebase "Direct link to Setup Firebase") In the dialog, scroll down to **Setup Firebase**, check that option, and click **Next Step**. The second page of the dialog will open. Before filling in more information, you need to allow FlutterFlow to access your Firebase project. The following section will guide you through this process. #### Allow FlutterFlow to Access Your Project[​](/integrations/firebase/connect-to-firebase.md#allow-flutterflow-to-access-your-project "Direct link to Allow FlutterFlow to Access Your Project") 1. Go to the Firebase console of your existing project, navigate to the far left menu, and select **Project Settings -> Users and Permissions**. 2. Select **Add Member** from the top right. 3. Add **** as an "**Editor**" for your project and select **Done**. Then press **Add Member**. ![firebase-add-member.png](/assets/images/firebase-add-member-c9dc098f376dda9328e0070f1f3b0f69.png) 4. On the same page (i.e., Users and Permissions), select **Advanced Permission Settings** (small blue text below the table). This will open the Google Cloud console in a new browser window. ![Steps 2, 3 and 4](/img/firebase/project-settings.png) 5. Find the row containing ** and select **Edit principal** (pencil on the far right of the row). ![In the Google Cloud console page](/img/firebase/firebase-principal.png) 6. Select **+ Add Another Role.** 7. Under **Select A Role**, search for **Service Account User** (you may need to scroll to find this). Select **Service Account User**. ![On choosing Select A Role and searching for Service Account User](/img/firebase/service-account-user.png) 8. Select **+ Add Another Role** again. Under **Select A Role**, search for **Cloud Functions Admin**. Select **Cloud Functions Admin**. info Note: The option to add Cloud Functions Admin may only show up if you are on a Firebase Blaze plan. In addition, you may need to [enable cloud functions](https://console.cloud.google.com/marketplace/product/google/cloudfunctions.googleapis.com) first. Cloud Functions Admin permissions are required for several FlutterFlow features (e.g., Push Notifications). Adding this Cloud Functions Admin is optional, but not doing so will prevent you from using any functions that require Cloud Functions. #### Connect and autogenerate files[​](/integrations/firebase/connect-to-firebase.md#connect-and-autogenerate-files "Direct link to Connect and autogenerate files") 1. From the Firebase dashboard of your project, navigate to the far left menu and select **Project Settings**. 2. Under Your Project, find the **Project ID**, right-click it, and copy. 3. Return to FlutterFlow, enter your Firebase Project ID in the dialog, and click Connect. A green checkmark will appear once the connection is successful. 4. Under Config Files, choose **Generate Config Files** and then select **Generate Files**. info Do not close or refresh the page while the files are being generated. ## Connect to Firebase on Creating a New FlutterFlow Project[​](/integrations/firebase/connect-to-firebase.md#connect-to-firebase-on-creating-a-new-flutterflow-project "Direct link to Connect to Firebase on Creating a New FlutterFlow Project") If you know you'll be integrating Firebase as you create your project, you can do the following: #### Step 1: Create a new project and enable Firebase[​](/integrations/firebase/connect-to-firebase.md#step-1-create-a-new-project-and-enable-firebase "Direct link to Step 1: Create a new project and enable Firebase") First, create a new project, and while doing so, keep the Setup Firebase option enabled and click Next Step. ![Alt text](/img/firebase/create-project-enable-firebase.png) #### Step 2: Connect to Firebase[​](/integrations/firebase/connect-to-firebase.md#step-2-connect-to-firebase "Direct link to Step 2: Connect to Firebase") If you'd like FlutterFlow to create a Firebase project for you, click **"+ Create Project"** and follow the [related steps](/integrations/firebase/connect-to-firebase.md#create-a-new-firebase-project-from-flutterflow). Alternatively, if you wish to connect an existing Firebase project manually, please follow the [manual steps here](/integrations/firebase/connect-to-firebase.md#connect-an-existing-firebase-project-manually). #### Step 3: Enable Authentication[​](/integrations/firebase/connect-to-firebase.md#step-3-enable-authentication "Direct link to Step 3: Enable Authentication") Turn on the Enable Authentication to allow users to log into your app using various sign-in methods, including email and password, social media providers, and even phone number. **Note:** this step only enables authentication. You will need to complete an additional setup to implement authentication logic later. ![Enable Authentication During Project Creation](/img/firebase/enable-auth-option.png) ## Enable Firestore for Database Access[​](/integrations/firebase/connect-to-firebase.md#enable-firestore-for-database-access "Direct link to Enable Firestore for Database Access") If you plan to use Firestore Database as your backend, follow these additional steps to enable Firestore. This will allow you to create collections and add documents directly from FlutterFlow. To configure Firestore Database: 1. From the Firebase dashboard of your project, navigate to the far left menu. Under Build, select Firestore Database and then select Create Database (marked in yellow in the screenshot). ![Alt text](/img/firebase/firebase-db-enable.png) 2. Next, you will need to set your **Firebase security rules**. To get started quickly, you can select Start in test mode and select Next. ![Alt text](/img/firebase/firebase-security.png) info We recommend updating your Firebase security rules before deploying your app. Please see [this link](/integrations/database/cloud-firestore/firestore-rules.md) for additional information on Firestore security rules. 3. Next, you will need to choose the location where your Firestore data will be stored. From the dropdown, select a location and then select Enable. Please see this link for additional information on Firebase locations. ![Alt text](/img/firebase/firebase-location.png) On completion, you land at the panel view of Cloud Firestore and can start creating collections and documents right away! ### Adding Indexes[​](/integrations/firebase/connect-to-firebase.md#adding-indexes "Direct link to Adding Indexes") Deploying indexes is necessary to perform certain queries in your Firestore database. Firestore automatically adds indexes for the most basic queries. However, when you apply both filtering and ordering while querying a collection, an index is necessary, and a warning will be generated to add it. We create indexes for you. The only thing you need to do is deploy them to your Firestore database. Here are the steps to deploy indexes. * Click on the Firestore from the Navigation Menu (left side of your screen). * Switch to the **Settings** tab. * Scroll down to the **Firestore Indexes** section and click on **Deploy**. Please note If you add a filtering/ordering on the query or change the existing filtering/ordering settings, you should deploy the Firestore Indexes again. ## Enable Billing[​](/integrations/firebase/connect-to-firebase.md#enable-billing "Direct link to Enable Billing") If you want to deploy [Cloud Functions](https://firebase.google.com/products/functions) (e.g., Braintree payments, Push Notifications) or use [Firebase Cloud Storage](https://firebase.google.com/products/storage), you will need to enable billing for your Firebase project. Please follow these steps to enable billing: 1. From the Firebase dashboard of your project, navigate to the far left menu. Under Build, select **Functions** and then select **Upgrade project**. 2. Select **Purchase**. If this is your first time enabling billing, you will be taken to a new page to provide your payment information. Otherwise, you can set a project budget. Please see [this link](https://firebase.google.com/pricing) for additional information on Firebase pricing. ![Alt text](/img/firebase/billing.png) --- # Firebase Crashlytics [Firebase Crashlytics](https://firebase.google.com/products/crashlytics) is a crash-reporting tool that helps you catch errors. It enables you to troubleshoot the issue by logging the details, such as the exact line number that caused the error, device name, OS version, and time when the crash happened. To enable Firebase Crashlytics, navigate to **Settings and Integrations** > **Project Setup** > **Firebase** > Expand the **Crashlytics** section and **Enable Crashlytics**. Firebase Crashlytics only supports catching errors on mobile platforms (Android and iOS). You can see all the logged errors/crashes inside the Crashlytics dashboard of your [Firebase console](https://console.firebase.google.com/). There, you'll see the list of crashes (with the page name and line number that caused the issue), and you can filter it by their state, signal, device type, and OS. ![Crashlytics dashboard](/assets/images/crashlytics-dashboard-2d40f05759331f5b6b4b39142a44ec4f.avif) 1. Click on the issue name to see its details. 2. To test the crash on your app, [download the app](/flutterflow-cli/exporting.md), add a code that throws an error, and run it on a mobile device or emulator with an active internet connection. ![Test crash](/assets/images/test-crash-b03b9e0d185ce6ce1c38646edc09447b.avif) --- # Performance Monitoring [Firebase Performance Monitoring](https://firebase.google.com/docs/perf-mon) is a tool that *automatically* collects performance data from your app and provides insights through the Firebase console. It can monitor both network requests and specific parts of your code. Enabling performance monitoring is beneficial for: * **Identify Bottlenecks**: Discover where your app's performance is lagging. * **Improve User Experience**: Slow or unresponsive apps lead to a poor user experience. * **Data-Driven Decisions**: Make optimization decisions based on real performance data. * **Monitor Network Calls**: See how long network requests take, helping identify slow APIs or network issues. To enable performance monitoring, navigate to Settings and Integrations > Project Setup > Firebase > Open the Performance Monitoring section and Enable Performance Monitoring toggle. --- # Remote Config [Firebase remote config](https://firebase.google.com/docs/remote-config) allows you to control your app's behavior and appearance without pushing an app update. For example, you could use it to change or show/hide certain elements of your app, such as a promo banner and Santa hat, or use it as a feature flag (payments, food delivery) with no need to publish an app update. ![Using Firebase Remote Config to show/hide a feature](/assets/images/show-hide-fi-2c2cc22236d36e12d343efc5e727a8f5.avif) When you enable the Remote Config, you must specify the parameter in our builder (called 'in-app defaults') and inside the Remote Config dashboard of your [Firebase console](https://console.firebase.google.com/). When the app starts, it fetches config values from the Firebase console, and for any reason, if it fails, your app will use the in-app defaults. warning The app will try to fetch values every time it starts. However, due to the minimum fetch interval of 1 hour (set by default), the values won't be fetched more than once in 1 hour. ## Using Firebase Remote Config[​](/integrations/firebase/remote-config.md#using-firebase-remote-config "Direct link to Using Firebase Remote Config") Follow the steps below to use the Remote Config: ### 1. Enable Remote Config[​](/integrations/firebase/remote-config.md#1-enable-remote-config "Direct link to 1. Enable Remote Config") To enable Remote Config, navigate to **Settings and Integrations** > **Project Setup** > **Firebase** > Expand the **Remote Config** section and **Enable Remote Config**. ![Enabling Remote Config](/assets/images/remote-config-d8b616f9817002cfc3f28ac542a0e971.avif) ### 2. Add parameter in Firebase Console[​](/integrations/firebase/remote-config.md#2-add-parameter-in-firebase-console "Direct link to 2. Add parameter in Firebase Console") You will be able to dynamically control your app using the parameters created in the Firebase Console of your project. To create the parameter: 1. Navigate to the [Firebase Console](https://console.firebase.google.com/u/0/) > Enagage > Remote Config\*\* page. 2. If this is the first time, click **Create configuration** button. 3. Click **Add parameter**. This will open the **Create parameter** section on the right side. 4. Enter the **Parameter name** (e.g., *show\_promo\_banner*, *primary\_color*, etc.). 5. Set the **Data type** among the *String*, *Number*, *Boolean*, and *JSON*. 6. Set the **Default value**. 7. If you enable the **Use in-app default** toggle, any change made to this parameter from here won't be reflected in your app. Instead, your app will use values from the parameters defined in our builder (see how to create it in the [next step](/integrations/firebase/remote-config.md#3-add-parameter-in-flutterflow)). 8. Click **Save**. 9. Click **Publish Changes** to make this parameter immediately available to your app. ### 3. Add parameter in FlutterFlow[​](/integrations/firebase/remote-config.md#3-add-parameter-in-flutterflow "Direct link to 3. Add parameter in FlutterFlow") Parameters added to your FlutterFlow project are called in-app defaults. To add them: 1. Navigate to **Settings and Integrations** > **Integrations** > **Firebase Remote Config**. 2. Click **+ Add Parameter**. A pop will open. 3. Enter the parameter **name**, select the **Data Type**, set its **Default Value**, and click **Create Parameter**. **Note**: The parameter name must match the name given in the [previous step](/integrations/firebase/remote-config.md#2-add-parameter-in-firebase-console). ### 4. Use parameter[​](/integrations/firebase/remote-config.md#4-use-parameter "Direct link to 4. Use parameter") Now you can access the newly created parameter from the **Set from Variable > Firebase Remote Config**. Here's an example of using the remote config parameter to set the [conditional visibility](/resources/ui/widgets/widget-commonalities.md#conditional) for the social login feature. Here's another example that changes the app's background using the color value from the Remote Config parameter. --- # Gemini With the Gemini action, you can generate text, process text-and-image inputs, and effortlessly count tokens. Deprecation Notice The Gemini action will eventually be deprecated. We recommend transitioning to the newer and more powerful [**AI Agent**](/integrations/ai-agents.md) actions. ## Setup[​](/integrations/gemini.md#setup "Direct link to Setup") Integrating [Gemini AI](https://gemini.google.com/app) into FlutterFlow unlocks Google's advanced AI capabilities right within your app. Follow this guide to integrate Gemini AI: 1. Visit [**Google AI Studio**.](https://aistudio.google.com/) and click on **Get API Key** > **Create API key**. You can create an API key within a new Google Cloud project by selecting *Create API key in new project*, or choose an existing Google Cloud project. 2. Once the API key is generated, copy it. tip To secure your API keys, refer to the Best Practices guide: [Secure API Keys](/best-practices/secure-api-keys.md) 1. Go back to FlutterFlow and navigate to **Settings and Integrations > Integrations > Gemini**. 2. Toggle on the **Enable Gemini** option and paste the copied **API key** into the designated field. 3. Now, you can add [Gemini actions](/integrations/gemini.md#gemini-action) at appropriate events within your app. With these steps, you’re all set to enhance your FlutterFlow app with powerful AI features. ## Gemini \[Action][​](/integrations/gemini.md#gemini-action "Direct link to Gemini \[Action]") To add a Gemini Action, follow these steps: 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. Click on the **+ Add Action**. 3. On the right side, search and select the **Gemini** (under *Integrations*) action. 4. Set the [**Action Type**](/integrations/gemini.md#types-of-gemini-action). **Note** that If you set this type to *Text from Image*, you must provide the image as well. 5. Provide the **Text prompt** that will be used to generate the result from the Gemini AI model. For this example, we use this prompt: `When users upload a photo, you analyze the food in the photo and tell if it is healthy to eat`. 6. Provide the **Action Output Variable Name** where the result of the generation will be stored. Later, you can access this variable from anywhere on the page. [YouTube video player](https://www.loom.com/embed/8b57fff59e3f496b84eb719f0a41bc85) ## Types of Gemini action[​](/integrations/gemini.md#types-of-gemini-action "Direct link to Types of Gemini action") Following are the types of Gemini actions you can add: ### Generate Text[​](/integrations/gemini.md#generate-text "Direct link to Generate Text") This action allows you to create natural language text based on the text prompts you provide. **Example**: * **Input**: *Text prompt* - "Write a brief summary of the benefits of exercise." * **Output**: *Action Output Variable Name* - "Exercise can improve mental health, increase lifespan, enhance physical fitness, and reduce the risk of chronic diseases." ### Count Tokens[​](/integrations/gemini.md#count-tokens "Direct link to Count Tokens") With this action, you can analyze the number of tokens in a given text prompt. This is particularly useful for applications that need to monitor or restrict the length of text inputs, ensuring that content stays within desired limits or quotas. A token can be a word, but it can also be a part of a word or even punctuation. The division of text into tokens depends on the tokenization algorithm being used. For Gemini models, a token is equivalent to about 4 characters. 100 tokens are about 60-80 English words. **Example**: * **Input**: *Text prompt* - "Gemini is fun!" * **Output**: *Action Output Variable Name* - 5 ### Text from Image[​](/integrations/gemini.md#text-from-image "Direct link to Text from Image") This action enables your app to analyze images and generate descriptive text about them. It can interpret the content of an image, such as identifying objects, scenery, or activities, and then provide a textual description. **Example**: * **Input**: *Text prompt* - "Identify the object in the image?" * **Input**: *Image Type* - There are two ways you can provide an image. * **Image Network URL**: You can provide the URL of the image hosted on the internet. If you upload an image to **Firebase** or **Supabase**, you can provide the image via ***Widget State > Uploaded File URL***\*.\* * **Uploaded Image File**: You can also provide an image file directly [from your device](/integrations/gemini.md) via ***Widget State > Uploaded Local File***\*.\* * **Output**: *Action Output Variable Name* - "This is a pipe organ. It is a large musical instrument that is used in churches, concert halls, and other large buildings. The sound of a pipe organ is very powerful and can be used to create a wide variety of music." --- # Google Analytics Integrating Google Analytics into your FlutterFlow project enables you to monitor user interactions, track app performance, and gain valuable insights to enhance user experience. Here's a comprehensive guide on setting up and utilizing Google Analytics within FlutterFlow. tip Google Analytics is integrated into Firebase. This means you must [**set up Firebase**](/integrations/firebase/connect-to-firebase.md) to enable analytics tracking and log events from your FlutterFlow app. ## Enable Google Analytics in Firebase[​](/integrations/google-analytics.md#enable-google-analytics-in-firebase "Direct link to Enable Google Analytics in Firebase") To enable Google Analytics in Firebase, open the [Firebase Console](https://console.firebase.google.com/) and select your project. From the left-side menu, navigate to **Analytics > Dashboard** and click **Enable Google Analytics**. Choose an existing Google Analytics account or create a new one, then **Finish** the setup. ## Enable Google Analytics in FlutterFlow[​](/integrations/google-analytics.md#enable-google-analytics-in-flutterflow "Direct link to Enable Google Analytics in FlutterFlow") To begin collecting analytics data, navigate to **Settings and Integrations > Integrations > Google Analytics** within your FlutterFlow project and toggle on the **Enable Google Analytics** option. Once enabled, you can set the [Predefined Events](/integrations/google-analytics.md#predefined-events). You can selectively toggle these options to log specific user interactions automatically. ![enable-google-analytics](/assets/images/enable-google-analytics-5d5b07e4f1dee8359e314b1148ef398c.avif) ### Predefined Events[​](/integrations/google-analytics.md#predefined-events "Direct link to Predefined Events") You can enable automatic logging for the following events: * **On Page Load**: Logs an event when a user opens a page, recorded with the Firebase-recommended name `screen_view`. The actual screen name is accessible within the `screen_name` parameter. * **On Action Start**: Captures events when users interact with widgets that trigger actions. Events are logged in the format `{WIDGET_NAME}_{TRIGGER_TYPE}`. For instance, if a user taps a button that navigates to another page, the event is logged as `Button_navigate_to`. * **On Each Individual Action**: This logs an event for every individual action or action chain for a given widget. It will be logged as `{WIDGET_NAME}_{TRIGGER_TYPE}` For example, when the user taps on a button and adds the *Upload Media* action followed by the *Update App State* action, the event will be logged as `Button_upload_media` and `Button_update_local_state`. * **On Authentication**: Logs events for authentication-related actions such as sign-up, login, logout, password reset, or account deletion. Events are logged using the action type, e.g., `sign_up` or `login`. tip To easily identify widgets in the analytics dashboard, consider giving them recognizable names, such as `BuyButton` instead of just `Button`. ## Google Analytics Event \[Action][​](/integrations/google-analytics.md#google-analytics-event-action "Direct link to Google Analytics Event \[Action]") In addition to predefined events, you can track specific user actions relevant to your app’s goals. This action allows you to log custom events and record additional information through parameters. For example, in an e-commerce app, you might log product purchases with parameters such as `product_category: electronics` to track item categories and `user_role: premium` vs. `user_role: guest` to differentiate user types. To log a custom event, add the **Google Analytics Event** action and enter a clear, descriptive **Event Name**. You can add parameters for extra context by clicking **+ Add Parameter** and providing **Key**-**Value** pairs (e.g., `product_category` as the Key and `electronics` as the Value). ![google-analytics-action](/assets/images/google-analytics-action-be22e990c2948c5b13eb97c716ff0b22.avif) ## Viewing Analytics Data[​](/integrations/google-analytics.md#viewing-analytics-data "Direct link to Viewing Analytics Data") To see all tracked events, both automatic and custom, open the [Firebase Console](https://console.firebase.google.com/) and select your project. From the left-side menu, navigate to **Analytics > Dashboard** to access detailed event reports. Use this data to gain insights into app screens, which funnels convert best, and where churn or drop-offs occur. In the long run, these metrics help you make data-driven improvements that enhance the user experience and maximize the impact of your FlutterFlow app. ## FAQs[​](/integrations/google-analytics.md#faqs "Direct link to FAQs") Why don’t I see any Analytics data yet? Event data may not appear instantly, which can be frustrating during development. Firebase may take up to **24 hours** to display event data in the main dashboards. Ensure your device has internet access and you’ve used the app at least once since enabling Analytics. --- # Maps & Places APIs FlutterFlow natively supports **Google Maps**, providing a seamless and efficient way to embed interactive maps into your FlutterFlow apps. It also supports **Places API** that returns formatted location data and imagery about establishments, geographic locations, or prominent points of interest. ## Add Maps APIs[​](/integrations/google-maps/generate-maps-keys.md#add-maps-apis "Direct link to Add Maps APIs") To enable **Google Maps** in your project, please follow the steps: ### Get API Keys[​](/integrations/google-maps/generate-maps-keys.md#get-api-keys "Direct link to Get API Keys") To start working with **Google Maps APIs**, you need to enable the **Maps API** from the [Google Cloud Console](https://console.cloud.google.com/). 1. As you land on the Cloud console, make sure you are in the correct Google Cloud project. Then, from the right menu, click on [**Library**](https://console.cloud.google.com/apis/library) and search for **Maps**. 2. You may receive a prompt from Google Cloud to add a billing account. Please add a billing account to continue. 3. You will see options such as the **Maps SDK for iOS, Maps SDK for Android**, and the **Maps Javascript API**. Select the platform you wish to support and then click **Enable**. If you are running on Run Mode, ensure that your Maps Javascript API is enabled. warning To secure your API keys, refer to the [**Best Practices guide: Secure API Keys**](/best-practices/secure-api-keys.md) * Click on the Credentials menu from the left panel. * Find the key for the platform you need, and copy the key. ### Add keys to FlutterFlow[​](/integrations/google-maps/generate-maps-keys.md#add-keys-to-flutterflow "Direct link to Add keys to FlutterFlow") Now add the API keys platform wise to FlutterFlow Settings page ![g-maps-settings.png](/assets/images/g-maps-settings-fb9c10318d1871f824e906ac5929daa4.png) ### Create a new Key if not available[​](/integrations/google-maps/generate-maps-keys.md#create-a-new-key-if-not-available "Direct link to Create a new Key if not available") If you don't find the Android key (auto created by Firebase) or iOS key (auto created by Firebase) in the Google developer console, here are the steps to create one: * On your Cloud console, click the **Credentials** menu on the left. * Click on the **+ Create Credentials** at the top. * Click on the **API Key** to create a new key for the Android app. Similarly, create one for iOS and Web. ## Add Places APIs[​](/integrations/google-maps/generate-maps-keys.md#add-places-apis "Direct link to Add Places APIs") You can [enable the **Places API**](https://console.cloud.google.com/apis/library/places-backend.googleapis.com) from your Google Cloud Console — make sure you are in the correct Google Cloud project. **Please note** that the current [PlacePicker widget](/integrations/google-maps/place-picker-widget.md) uses the legacy Places API. We plan to update the PlacePicker widget soon to support the new API. In the meantime, ensure that the legacy Places API is enabled for full functionality. ![places-api.png](/assets/images/places-api-75e9526626afcffa8bd5266aa4b37992.png) --- # Google Maps Widget The **Google Maps** widget enables the integration of interactive maps into your app, offering users valuable geographical insights. For instance, in a food delivery app, this widget could display the locations of restaurants. It offers a range of customization options, allowing you to tailor the display with various map types and markers to suit your specific needs. Feature Completion As we continuously enhance our platform, please note that while our integration is robust, it is not yet feature-complete. We encourage you to review the available APIs and features detailed below to ensure they meet your app development needs before integration. ![google-maps-widget.png](/assets/images/google-maps-widget-36f25b917240de88b6192e5a0fe57418.png) Prerequisite Ensure you have added the [**Google Map API keys**](/integrations/google-maps/generate-maps-keys.md#get-api-keys) before adding the Google Maps widget to your project ## Add Google Map widget[​](/integrations/google-maps/google-maps-widget.md#add-google-map-widget "Direct link to Add Google Map widget") 1. Open the Widget Palette and locate the **Google Map** widget under the **Base Elements** tab. You can drag it to your desired location or add it directly from the widget tree or canvas area. 2. By default, the map displays a random location. To set a specific location, go to the **Properties Panel > Initial Location**. 3. Enter the latitude and longitude values in the **Lat and Lng** fields to specify the location. To use the user's current location, set a variable through the **Set Variable menu > Global Properties > Current Device Location**. 4. To change the map type, go to the **Properties Panel > Map Type** and select one of the following options: * **Roadmap:** Displays the default road map view. * **Terrain:** Shows a physical map based on terrain information. * **Hybrid:** Combines normal and satellite views. * **Satellite:** Displays satellite images from Google Earth. 5. To customize the visual appearance of your map, navigate to the **Properties Panel > Map Style**. 6. To set the **initial zoom level** of the map, go to the **Properties Panel > Initial Zoom** of Map and enter the desired value. Note that a higher value will zoom in on the map while a lower value will zoom out. tip If you don't see your current location while testing, make sure you have enabled location permission in your browser. ![location-browser.png](/assets/images/location-browser-e5f61c7357f9afa44240d26841e467aa.png) ## Markers[​](/integrations/google-maps/google-maps-widget.md#markers "Direct link to Markers") A marker is an icon that appears over the map, indicating a location. To add markers: * Select the **Google Map** widget, move to the **Properties Panel > Num Markers** and select whether you want to show **Single** or **Multiple** markers. ### Set Markers from Firebase[​](/integrations/google-maps/google-maps-widget.md#set-markers-from-firebase "Direct link to Set Markers from Firebase") * Set the Marker Type to **Document** if the data is on Firestore Collection * In case of Documents, create a collection and query it on any widget (must be a parent of GoogleMap) or page. * In Marker Document, set the source of markers as shown in the following video. ### Set Markers from List of LatLng[​](/integrations/google-maps/google-maps-widget.md#set-markers-from-list-of-latlng "Direct link to Set Markers from List of LatLng") If you choose **LatLng**, you must provide a source that contains a list of locations as Data Type (LatLng) (e.g., App State > \[variable\_name] (List of **LatLng**)). ### Changing Marker Color[​](/integrations/google-maps/google-maps-widget.md#changing-marker-color "Direct link to Changing Marker Color") To change the marker color, move to the Properties Panel > Google Map > set the Marker Color dropdown value to the color you like: ![marker-color.png](/assets/images/marker-color-06977322341a142ddc3bbe1f5b90b2d9.png) ### Set Marker Image[​](/integrations/google-maps/google-maps-widget.md#set-marker-image "Direct link to Set Marker Image") Custom marker images can enhance your map interface by making it more intuitive and visually engaging, while also aligning with your app's branding. To set an image as a marker: * Move to the **Properties Panel > Google Map > set the Marker Icon to Image**. * Select the type of image you want to set: * For an image hosted online, set the Image Type to **Network** and specify the image URL in the Path field. * To provide an image from your system, set the Image Type to **Asset** and upload the image. ### Centering map on marker tap[​](/integrations/google-maps/google-maps-widget.md#centering-map-on-marker-tap "Direct link to Centering map on marker tap") To center a map on a marker tap, move to the **Properties Panel > Google Map > enable the Centering Map on Marker Tap toggle**. ## On Marker Tap \[Action Trigger][​](/integrations/google-maps/google-maps-widget.md#on-marker-tap-action-trigger "Direct link to On Marker Tap \[Action Trigger]") Sometimes, you might want to receive a callback when a user taps on a marker. This can be useful for dynamically displaying additional information about the location, opening a detailed view, or initiating other actions based on the selected marker. Here’s how you do it: * Select the **Google Map** widget. * From the Properties Panel, select **Actions** and open the **Action Flow Editor**. * Under the action trigger **On Marker Tap**, add any actions here. ![marker-tap.png](/assets/images/marker-tap-4a87b7d9ec158938bef841037f291923.png) ## Advanced Customizations[​](/integrations/google-maps/google-maps-widget.md#advanced-customizations "Direct link to Advanced Customizations") You can customize the appearance and behavior of this widget using the various properties available in the properties panel. ### Allow Interacting With the Map[​](/integrations/google-maps/google-maps-widget.md#allow-interacting-with-the-map "Direct link to Allow Interacting With the Map") By default, the map interaction feature is enabled, allowing users to drag, zoom in, and zoom out on the map. However, you can disable the **Allow Zooming the Map** and **Show Zoom Buttons** on the Map options if you wish to restrict the zoom functionality. To access these settings, navigate to the **Properties Panel > Google Map > Allows Interacting with the Map**. #### Map Takes Gesture Preference[​](/integrations/google-maps/google-maps-widget.md#map-takes-gesture-preference "Direct link to Map Takes Gesture Preference") When this is turned on, any gestures, such as zooming or dragging, will only affect the map, not the rest of the page. This is helpful if your map is inside a scrollable page, so users can interact with the map without accidentally scrolling the whole page. info This setting is only available if **Allow Interacting** and **Allow Zooming** are turned on. * Map Takes Gesture Preference (Disabled) * Map Takes Gesture Preference (Enabled) ### Show User Location[​](/integrations/google-maps/google-maps-widget.md#show-user-location "Direct link to Show User Location") When enabled, a blue dot appears on the map to indicate the user's current location. If the map is moved, users can re-center their location by clicking the button at the top right side. To enable this option, navigate to the **Properties Panel > Google Map > enable the Show User Location toggle**. note When you enable this option, make sure to set the **Initial Location to Global Properties > Current Device Location**. ### Showing Compass[​](/integrations/google-maps/google-maps-widget.md#showing-compass "Direct link to Showing Compass") While exploring the map, users may rotate the map (which can make it difficult to trace the route). Enabling compass will allow users to bring the map to its original direction. To enable the compass, navigate to the **Properties Panel > Google Map > enable the Show Compass toggle**. ### Enabling map toolbar[​](/integrations/google-maps/google-maps-widget.md#enabling-map-toolbar "Direct link to Enabling map toolbar") The Toolbar, located at the bottom right of the map, becomes visible when a user selects a marker. It offers quick access to either a map view or directions in the Google Maps mobile app. To enable the toolbar, navigate to the **Properties Panel > Google Map > enable the Show Map Toolbar toggle**. ### Showing Traffic on Map[​](/integrations/google-maps/google-maps-widget.md#showing-traffic-on-map "Direct link to Showing Traffic on Map") Showing traffic on the map allows user to know the flow of traffic on the roads and helps them decide on a better route. To show live traffic on a map, navigate to the Properties Panel > Google Map > enable the Show Traffic on Map toggle. ## FAQ[​](/integrations/google-maps/google-maps-widget.md#faq "Direct link to FAQ") Why Google Maps custom markers are not working in run mode or test mode? Due to a recent update, Google Maps custom markers won't work in Run or Test mode unless CanvasKit is enabled. This is expected behavior. To use custom markers effectively, enable CanvasKit from [**Advanced Web Settings**](/resources/projects/settings/project-setup.md#advanced-web-settings). --- # Move Map Center \[Action] This action allows you to center the map on a specified location, such as setting the pickup and drop-off points. You can define the location either by directly inputting the latitude and longitude values or by using a variable. Prerequisites * To implement this feature, add a Google Maps widget to your page or component. [**Learn how.**](/integrations/google-maps/google-maps-widget.md) * If you wish to enable users to select locations from a dropdown using FlutterFlow's PlacePicker widget, you can also integrate the Place Picker widget into your map view. [**Learn more here**](/integrations/google-maps/place-picker-widget.md). Assuming you've set up the Place Picker widget on your Google Maps widget view, let's add a button that triggers the action to move the map center, so the map centers on the newly selected location. In our example, we've added an IconButton with a location pin icon. For the button's OnTap action trigger, we'll add the Move Map Center action and set the LatLng to the LatLng of the Place Picker's selected place. You must check if the PlacePicker value (or the variable holding your new LatLng) is set before calling the Move Map Center action. ![move-map.png](/assets/images/move-map-365ef164a672a6e3878a06126998459f.png) --- # Place Picker Widget The `PlacePicker` widget is designed to retrieve information about places, such as establishments (e.g., buildings, parks, museums) and geographic features (e.g., roads, lakes, mountains). It provides details like name, address, city, state, country, zip code, and latitude-longitude coordinates. This widget is particularly useful in applications like cab booking services. For instance, it can be used to capture the exact location and full address of a destination, displaying this information on a page or integrating it into a Google Map. Visually, the PlacePicker appears as a button. When tapped, it enables you to search for a place by typing its name, and displaying a dropdown list of matching locations. Once a place is selected, its name is displayed on the button, and additional details are accessible through the placePickerValue variable from Widget State. Here's an example from the Demo app: [Place Picker Widget](https://demo.arcade.software/EQ4xhHBgjMp4wbm3aTin?embed\&show_copy_link=true) Prerequisites * The Place Picker **requires a Google Maps API key**. See how to [**create and add API keys**](/integrations/google-maps/generate-maps-keys.md#add-maps-apis) to FlutterFlow. * Ensure you have enabled the [**Places API**](/integrations/google-maps/generate-maps-keys.md#add-places-apis) from Cloud console. * Enable **Google Maps Platform Billing** via your Cloud console. Please note: Failing to enable the Google Maps Platform Billing will not show any place in an autocomplete list. ## Add Place Picker widget[​](/integrations/google-maps/place-picker-widget.md#add-place-picker-widget "Direct link to Add Place Picker widget") To add the PlacePicker widget to your project: [Add Place Picker widget](https://demo.arcade.software/uWaLSOHPZctjnGik03Pu?embed\&show_copy_link=true) By default, the `Place Picker` widget features an icon and the text "Select Location" on the button. You can modify the styling and properties of these elements from the Properties Panel on the right. If you retain the Text widget, the text will update to the name of the selected location when a user makes a selection. Both the icon and text are optional; adjust them according to your design requirements. ![place-picker-properties.png](/assets/images/place-picker-properties-40c1481618f4452598a0fc7b9fd9aefa.png) The widget properties of Place Picker widget ## Use PlacePicker Values[​](/integrations/google-maps/place-picker-widget.md#use-placepicker-values "Direct link to Use PlacePicker Values") The selected place’s details are stored in a `GooglePlace` custom data type provided by FlutterFlow. You can access this via **Widget State > placePickerValue**, which includes fields like name, address, latitude/longitude (LatLng), city, state, country, and ZIP code. These values can be used to display content in Text widgets or perform conditional logic based on the selected location. [Use PlacePicker widget state](https://demo.arcade.software/oje0Gsbf9IJh7M0pb6Tv?embed\&show_copy_link=true) --- # Static Map Widget The StaticMap widget shows an image of the map from the [mapbox](https://www.mapbox.com/). This widget is a good choice when you want to display a location on a map without interactivity or controls such as zoom-in, zoom-out, and map scrolling. tip To display a map with interactivity or controls, use the [**GoogleMaps**](/integrations/google-maps/google-maps-widget.md) widget. ## Adding StaticMap widget[​](/integrations/mapbox/staticmap-widget.md#adding-staticmap-widget "Direct link to Adding StaticMap widget") Here's an example of how you can add the StaticMap widget to your project: 1. First, drag the **StaticMap** widget from the **Base Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. You'll need the Mapbox API key to render the map image. Get the API key by creating the [Mapbox account](https://account.mapbox.com/auth/signup/) and then return to FlutterFlow, move to the properties panel, scroll down to the Static Map section and enter the key into the **Mapbox API Key** input box. 3. To display your location on the map, enter the **Latitude** and **Longitude** values inside the **Lat** and **Lng** input boxes. tip To get the lat long values for any location, open to Google Map, right-click on any place and click on the first item from the list. It should look like this `19.080045795863743`, `72.8794235725136`. ## Customization[​](/integrations/mapbox/staticmap-widget.md#customization "Direct link to Customization") You can customize the appearance and behavior of the widget using the various properties available under the [properties panel](/flutterflow-ui/builder.md#navigation-menu). ### Changing the map style[​](/integrations/mapbox/staticmap-widget.md#changing-the-map-style "Direct link to Changing the map style") Changing the map style allows you to change the overall theme and type of the map, such as Light, Dark, Street, and Satellite. To change the map style: 1. Select **StaticMap** from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Static Map** section. 3. Find the **Map Style** property and choose among the *Light*, *Dark*, *Outdoor*, *Street*, *Satellite*, and *Detailed* *Satellite*. ### Set zoom, tilt, and rotation[​](/integrations/mapbox/staticmap-widget.md#set-zoom-tilt-and-rotation "Direct link to Set zoom, tilt, and rotation") You can define the zoom level, adjust the map tilting and rotate the map as per your requirement. To set the zoom, tilt, and rotation value for the map: 1. Select **StaticMap** from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Static Map** section. 3. Find the **Map Zoom** property and set the value that is good enough to highlight the place. The value starts from 0 (which is a full zoom-out). To zoom in, set the higher value. 4. Find the **Map Tilt** property and enter the value to display the map in the sloping position. 5. Find the **Map Rotation** property and enter the value to rotate the map. ### Customizing marker[​](/integrations/mapbox/staticmap-widget.md#customizing-marker "Direct link to Customizing marker") By default, the marker is invisible on the map. You can make it visible by setting the marker color. You can also change the marker icon/image from the URL link. To customize the marker: 1. Select **StaticMap** from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Static Map** section. 3. To show the marker, find the **Map Marker Color** property, click on the box next to **Unset**, select the color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. 4. To display the custom marker image/icon, enter the URL into the **Map Marker URL** input box. This widget does not resize the marker image from the URL link. Make sure you provide the image with the appropriate size. ### Caching map image[​](/integrations/mapbox/staticmap-widget.md#caching-map-image "Direct link to Caching map image") Enabling the cache will store the map image and display it when the internet is unavailable. To cache the map image: 1. Select **StaticMap** from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Static Map Image** section. 3. Find the **Cache** toggle and turn it on. ### Changing the box fit[​](/integrations/mapbox/staticmap-widget.md#changing-the-box-fit "Direct link to Changing the box fit") Changing the box fit value allows you to control how the map should display inside the StaticMap widget. Various options under the Box Fit property help you scale (grow or shrink in size) the map image. To change the box fit value: 1. Select the **StaticMap** from the widget tree or the canvas area. 2. Move to the properties panel (on the right side of your screen) and scroll down to the **Static Map Image** section. 3. Find the **Box Fit** dropdown, and try changing it to the other values. --- # Launch Map Using this action, you can open the Map app installed on your device. For example, you could add this action on an event page to let users know more about the place inside the map apps like Google Maps, Apple Maps, and Waze app. You can specify the Lat Long details or full address of any place to access the additional information such as directions, call details, timings, photos, street view, reviews, and more. ## Types of Map apps[​](/integrations/maps/launch-map.md#types-of-map-apps "Direct link to Types of Map apps") This action lets you specify the type of map app to open. If it's not installed, the default map app of the platform will be used. For example, opening the Google Maps on iOS devices. If not installed, it will open the default Apple Maps app. You can launch the following types of maps apps: 1. **System Default**: Opens the default map app. That is opening Google Maps on Android devices and Apple Maps on iOS devices. 2. **Google Maps**: Google's default map app on Android devices. 3. **Apple Maps**: The default map app on iOS devices from Apple. 4. [**Waze**](https://play.google.com/store/apps/details?id=com.waze): App that tells you about real-time traffic, police, crashes, and more. ### Adding Launch Map \[Action][​](/integrations/maps/launch-map.md#adding-launch-map-action "Direct link to Adding Launch Map \[Action]") Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g. Location icon, Address text) on which you want to define the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Click on the **+ Add Action**. 2. On the right side, search and select the **Launch Map** action. 3. Set the **Preferred Map Type** among the **System Default**, **Google Maps**, **Apple Maps,** and **Waze**. 4. To open the map app using lat long: 1. Set the **Place Type** to **Location**. 2. Inside the **Location** section, enter the values in the **Latitude** and **Longitude** input boxes. You can also specify these values from a variable, such as an app state variable or a variable from an API response by clicking on the **Set from Variable**. 3. (Optional) To set the place name (which will be displayed when the map app is opened), Inside the **Title** section, enter the place name in the **Value** input box. To set it from the variable, click on the **Set from Variable**. 5. To open the map app using address: 1. Set the **Place Type** to **Address**. 2. Inside the **Address** section, enter the address into the **Value** input box. You can also specify the address from a variable, such as an app state variable, or a variable from an API response by clicking on the **Set from Variable**. 3. (Optional) To set the place name (which will be displayed when the map app is opened), Inside the **Title** section, enter the place name in the **Value** input box. To set it from the variable, click on the **Set from Variable**. 6. Click **Close**. --- # Mux Livestream Mux Livestream allows you to integrate live video streaming capabilities directly into your FlutterFlow app. It leverages Mux’s powerful streaming API, providing real-time broadcasting features. For a deeper understanding, check out [how live streaming works](https://blog.flutterflow.io/flutter-mux-live-streaming/#how-does-live-streaming-work). Possible use cases * **Live Events**: Stream conferences, workshops, or meetups. * **Educational Apps**: Conduct live classes, webinars, or tutorials. * **Social Platforms**: Allow users to broadcast and share real-time video content. * **Customer Support**: Provide interactive support sessions via live video streaming. ## Setting Up Mux Integration[​](/integrations/mux.md#setting-up-mux-integration "Direct link to Setting Up Mux Integration") To get started, go to **Settings and Integrations > Integrations > Mux Livestream** in FlutterFlow and enable **Mux Broadcast**. Then, create a Mux account and go to **Settings > API Access Tokens**. Click **Generate new token**, choose an environment (Development or Production), check **Mux Video** with **Write** access, name the token, and generate it. Copy the **Access Token ID** and **Secret Key**, paste them into FlutterFlow, and click **Deploy**. ## Adding MuxBroadcast Widget[​](/integrations/mux.md#adding-muxbroadcast-widget "Direct link to Adding MuxBroadcast Widget") To create a live stream, start by adding the **MuxBroadcast** widget to your page. Navigate to the page where you want the livestream to appear, then drag and drop the widget onto the canvas. After placing it, configure its properties using the options available in the right-side panel. The MuxBroadcast widget comes with three key properties to control the live stream: * **Show Streaming View**: By default, this option is disabled, meaning the widget only displays the starting interface (camera preview and "Start Stream" button). Enabling this option shows the live streaming UI on the canvas during design time, which helps with layout and styling previews. * **Broadcast Latency**: Choose between **Standard**, **Reduced**, and **Low** latency modes. Lower latency provides faster interaction but may reduce video quality or reliability depending on the network. * **Broadcast Audio Channel**: Select **Stereo** or **Mono** audio. Stereo provides richer sound with left and right audio separation, while Mono offers broader device compatibility and lower bandwidth usage. ![muxbroadcast-widget.avif](/assets/images/muxbroadcast-widget-3d31a0be6518f23e2e7223a7423f3958.avif) You can also customize the **MuxBroadcast** widget to match your app's design using various styling properties. These include: * **Start Button Style, Text, and Icon:** Adjust the appearance, label, and icon of the broadcast start button. * **Stop Button:** Customize how the stop button looks. * **Flip Camera Button:** Modify the button used to switch between front and rear cameras. * **Live Text Style:** Change the appearance of the "LIVE" text. * **Live Container & Icon:** Style the container and icon shown during live broadcast. * **Duration Text Style:** Customize how the elapsed time is displayed. * **Duration Container:** Style the container holding the duration display. ## Start and Stop Livestream[​](/integrations/mux.md#start-and-stop-livestream "Direct link to Start and Stop Livestream") You can manage livestreaming using the built-in action triggers available on the **MuxBroadcast** widget: **On Broadcast Start** and **On Broadcast Stop**. These allow you to trigger workflows when a stream begins or ends. ![streaming-action-triggers.avif](/assets/images/streaming-action-triggers-3d8c36d44f47d2fdaa3d3ce14328339e.avif) ### On Broadcast Start \[Action Trigger][​](/integrations/mux.md#on-broadcast-start-action-trigger "Direct link to On Broadcast Start \[Action Trigger]") The actions under this trigger execute when the user clicks the **Start Stream** button. From here, you can access the livestream URL via **Widget State → Mux Playback URL** and perform tasks such as creating a new database record to indicate the livestream has started. ![on-broadcast-start.avif](/assets/images/on-broadcast-start-ec4c0c129b2882a81e72ad3510da627c.avif) ### On Broadcast Stop \[Action Trigger][​](/integrations/mux.md#on-broadcast-stop-action-trigger "Direct link to On Broadcast Stop \[Action Trigger]") The actions under this trigger execute when the user stops the stream. You can use this trigger to update your database, such as setting a livestream's `is_live` status to `false`, saving the end time, or navigating away from the stream page. ![on-broadcast-stop.avif](/assets/images/on-broadcast-stop-98af6c37211b1d75ce3f3c711cc9f6f9.avif) ## View Livestream[​](/integrations/mux.md#view-livestream "Direct link to View Livestream") When a livestream is active, you can access the broadcast instantly via the **Mux Playback URL** provided by the **MuxBroadcast** widget. If the livestream has already ended, additional steps are required to retrieve the archived playback URL and enable playback of the recorded session. ### Viewing Active Livestream[​](/integrations/mux.md#viewing-active-livestream "Direct link to Viewing Active Livestream") Once your livestream is active, viewers can watch it in real-time using the **Mux Playback URL**. This URL can be passed to a dedicated page (for example, **ViewBroadcast**) to stream the live session. To display the livestream: 1. Navigate to your desired list or overview page where livestreams are listed. 2. When a viewer taps on a live broadcast card (e.g., from a `ListView`), navigate to the **ViewBroadcast** page and pass the **Mux Playback URL** as a page parameter. 3. Inside the **ViewBroadcast** page, the **Mux Playback URL** can then be used in a [**VideoPlayer**](/concepts/file-handling/displaying-media.md#videoplayer) widget to stream the live video. ### Viewing Past Livestream[​](/integrations/mux.md#viewing-past-livestream "Direct link to Viewing Past Livestream") When a livestream ends, its original **Mux Playback URL** becomes invalid. To replay an ended session, you need to fetch the archived asset's playback URL that was automatically created during the livestream. To achieve this, you will need to retrieve the live stream ID from its playback ID, then get the associated asset's playback ID from the livestream's recent assets. tip You'll need to write a [**custom code expression**](/resources/functions/utility.md#custom-code-expression) or [**custom function**](/concepts/custom-code/custom-functions.md) to extract the playback ID from the current Mux Playback URL (e.g., from `https://stream.mux.com/iSHXmiVyFshIPgeZf2F78OrvOGnEQd02Api00ipWRwWaQ.m3u8` extract `iSHXmiVyFshIPgeZf2F78OrvOGnEQd02Api00ipWRwWaQ`, which is a playback ID of a livestream) and then reconstruct the new playback URL using the asset's playback ID in the same format. ![get-past-stream-id.avif](/assets/images/get-past-stream-id-7539757ca9f9fc760aeb0afdfc0e4858.avif) The flow involves using three Mux APIs in sequence: * [**GET /video/v1/playback-ids/`{PLAYBACK_ID}`**](https://www.mux.com/docs/api-reference/video/playback-id/get-asset-or-livestream-id): Gives the livestream ID from the livestream playback ID. * [**GET /video/v1/live-streams/`{LIVE_STREAM_ID}`**](https://www.mux.com/docs/api-reference/video/live-streams/get-live-stream): Retrieves the livestream details including `recent_asset_ids` array. Extract the Asset ID from this api response. * [**GET /video/v1/assets/`{ASSET_ID}`**](https://www.mux.com/docs/api-reference/video/assets/get-asset): Fetches the asset details to get its playback ID from the `playback_ids` array. Now, use conditional logic to check the livestream status and pass the appropriate playback URL. For example, if the broadcast is live, use the current livestream playback URL directly. If the livestream has ended, call the APIs in sequence to get the asset's playback ID and construct the archived stream's playback URL. --- # Braintree You can accept payments in your app using [Braintree](https://developer.paypal.com/braintree/docs/start/overview) (a service provided by PayPal) integration. This will also allow your users to pay directly using a credit card or using a service like PayPal, Google Pay, or Apple Pay Prerequisites Before starting to set up payments, make sure you have: * Completed all the steps of [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) for your project. * Upgraded your Firebase project to [**Blaze Plan**](https://firebase.google.com/pricing). * Enabled [**Firebase Authentication**](/integrations/authentication/firebase/initial-setup.md) for your project. info FlutterFlow uses [**Firebase Cloud Functions**](https://firebase.google.com/docs/functions) to process a transaction using the selected service (Braintree/PayPal). ## Braintree Integration[​](/integrations/payments/braintree.md#braintree-integration "Direct link to Braintree Integration") Integrating the Braintree in your app comprises the following steps: ### 1. Setup payments integration[​](/integrations/payments/braintree.md#1-setup-payments-integration "Direct link to 1. Setup payments integration") Payments can be set up on FlutterFlow using Braintree. You should always test your payment processing using a Sandboxed environment, before deploying them to a production environment. Follow the steps below to set up using Braintree: 1. Go to [Braintree Website](https://www.braintreepayments.com/). 2. **Sign up** for getting access to the Sandboxed environment. You might receive an email with the additional steps for completing the sign-up process. If you already have a Braintree account just **Log In**. 3. Navigate to the **Braintree Settings** page of your FlutterFlow project by going to the **Settings and Integrations** > **In App Purchases & Subscriptions** > **Braintree**. 4. On this page, **Enable Braintree/PayPal** using the toggle. 5. Under the **Credentials (Sandbox)** section, you need to enter the **Merchant ID**, **Tokenization** **Key**, **Public Key** & **Private Key** of the Braintree account. 6. To get the required credentials, navigate to your Braintree account **Home** page. 7. Click the **gear icon** (top-right corner), select **Business**. From this page, you'll get the **Merchant ID**. 8. Now, go to the **API** page. Here, you'll get the **Public Key** & **Private Key**. 9. To generate a **Tokenization Key**, go to the **API** page, and click **Generate New Tokenization Key**. Copy the Key and enter it in the respective field of FlutterFlow. Finally, click **Deploy** to upload the Cloud Functions required for processing a payment using Braintree: ### 2. Enable Google Pay or Apple Pay (Optional)[​](/integrations/payments/braintree.md#2-enable-google-pay-or-apple-pay-optional "Direct link to 2. Enable Google Pay or Apple Pay (Optional)") Completing the payment integration by following the above steps will allow you to accept payments using a credit card or a PayPal account. Additionally, you can accept payments using Google Pay or Apple Pay. To accept payments using Google Pay or Apple Pay, you'll need to enter the respective **Merchant ID** of the Google/Apple account in the *Braintree Settings* page > *Credentials (Sandbox)* section. 1. To know how to find the Google Pay Merchant ID, navigate to [this page](https://support.google.com/paymentscenter/answer/7163092). 2. Steps for configuring Apple Pay and getting access to the Apple Merchant ID are [here](https://help.apple.com/developer-account/#/devb2e62b839). ### 3. Trigger payment action[​](/integrations/payments/braintree.md#3-trigger-payment-action "Direct link to 3. Trigger payment action") In order to initiate a payment, you have to use the *Braintree Payment Action*. Follow the steps below to add this action to any widget: 1. Select the **widget** on which you want to apply the Action. 2. Select **Actions** from the Properties panel (right menu). 3. Click **+ Add Action** button. 4. Choose a gesture from the dropdown among ***On Tap**, **On Double Tap**, or* **On Long Press**. 5. Select the **Action Type** as ***Braintree Payment***. 6. Enter the **Amount** either by defining a ***Specific Value*** or ***From Variable***. 7. Under **Payment Method**, you can select ***Credit Card***, ***PayPal***, or ***Drop-In***. The *Drop-In* option lets users choose which payment method to use. If you want to use the *Credit Card* option follow the steps [here](/integrations/payments/braintree.md#using-credit-card). 8. If you have chosen the **Drop-In** option, select the **Allowed Payment Types**. Using Google Pay or Apple Pay will require you to have their respective Merchant ID defined during the Payment Setup process. 9. Enter the **Currency Code** and you can define the optional parameters like **Tax Rate Percentage** and **Shipping Cost**. Enabling Apple Pay requires you to specify the **Country Code** in the respective field. warning Make sure the user is authenticated before triggering the Braintree Payment Action, otherwise, it will result in an error. You can follow the steps on [**this page**](/integrations/authentication/firebase/initial-setup.md) to set up Authentication. #### Using Credit Card[​](/integrations/payments/braintree.md#using-credit-card "Direct link to Using Credit Card") If you want to keep only the Credit Card option on your checkout page, you'll need to add the **CreditCardFrom** widget to the page. Follow the steps below: 1. Select the **Payment Method** as ***Credit Card***. 2. Drag and drop the [**CreditCardFrom**](/resources/ui/widgets/built-in-widgets/credit-card-form.md) widget onto the canvas. 3. You can modify the design of the form widget as per your app's needs. 4. Again select the **checkout button** to complete defining the Action. 5. Enter the **Currency Code** and you can define the optional attributes like **Tax Rate Percentage** and **Shipping Cost**. ### 4. Testing[​](/integrations/payments/braintree.md#4-testing "Direct link to 4. Testing") Braintree payments work on real Android devices or in emulators, and App Store purchases only work on real iOS devices. [This document](/testing/local-run.md) has instructions on how to run your app on an Android or iOS device. info The Braintree Payments cannot be tested in Preview Mode, Test Mode, or Run Mode. To test your app before deployment: 1. Download and run your project as described [here](/testing/local-run.md). 2. To test the purchase, you can use any of these [basic test card numbers](https://stripe.com/docs/testing#cards). ### 5. Releasing to production[​](/integrations/payments/braintree.md#5-releasing-to-production "Direct link to 5. Releasing to production") Before you release the app to production, complete the following steps: 1. Create the Braintree Account (Not sandbox) and get the production credentials. 2. Add the **production credentials** in the FlutterFlow *Braintree Settings* page > *Credentials (Production)* section. 3. Turn on the **Is Production** toggle present on that page. 4. Deploy the new Firebase Cloud Functions with the production credentials by clicking on the **Deploy** button. Now, you are ready to build and distribute your app with payments to production. --- # RazorPay [Razorpay](https://razorpay.com/) is a leading online payment gateway widely used by businesses in India to accept and process digital payments securely. It provides a platform for merchants and businesses to integrate payment solutions into their websites and mobile apps. It allows customers to make online payments using various payment methods such as credit cards, debit cards, net banking, UPI (Unified Payments Interface), and digital wallets. warning Currently, publishing to the web with Razorpay enabled is restricted due to some regulations. Prerequisites Before starting to set up payments, make sure you have, 1. Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) for your project. 2. Enabled [**Firebase Authentication**](/integrations/authentication/firebase/initial-setup.md) for your project. 3. Upgraded your Firebase project to [**Blaze Plan**](https://firebase.google.com/pricing). We use [**Firebase Cloud Functions**](https://firebase.google.com/docs/functions) to process a transaction. ## Razorpay Integration[​](/integrations/payments/razorpay.md#razorpay-integration "Direct link to Razorpay Integration") Integrating Razorpay in your app comprises the following steps: 1. [Setup Razorpay](/integrations/payments/razorpay.md#1-setup-razorpay) 2. [Trigger Razorpay payment](/integrations/payments/razorpay.md#2-trigger-razorpay-payment-action) 3. [Testing](/integrations/payments/razorpay.md#3-testing) 4. [Releasing to production](/integrations/payments/razorpay.md#4-releasing-to-production) ### 1. Setup Razorpay[​](/integrations/payments/razorpay.md#1-setup-razorpay "Direct link to 1. Setup Razorpay") Setting up the Razorpay payments includes creating an account, enabling test mode, acquiring the keys from your Razorpay account, and adding them to your project. warning You should always try out payments in a test mode before releasing them to your production application. Hence, the instructions below will guide you on how to get the test keys. Here are the steps: 1. Create a new Razorpay account from [here](https://dashboard.razorpay.com/signup). If you already have an account, [log in](https://dashboard.razorpay.com/signin). 2. Once you are logged in, turn on the **Test Mode**. Test mode helps you simulate the payments without involving real money transactions. ![Enabling test mode](/assets/images/enable-test-mode-63e84f711a6ce23e85cbd75de80ff2c0.avif) 3. From the left side menu, select **Account & Settings** > Under **Website and app settings** section, select **API keys**. 4. If you're asked to add a website link but your app isn't published yet, you can temporarily publish it to a subdomain using our [web publishing](/deployment/web-publishing.md) feature. Later, you can update this to your actual domain in both FlutterFlow and Razorpay. ![add-website-link](/assets/images/add-website-link-30819ee6335b8d13c0f6bc7aab70594a.avif) 5. Click **Generate Test Key** and copy the **Key Id** and **Key Secret**. To regenerate, click on **Regenerate Test Key** and choose how you want to deactivate the old key. ![Generate Test Key](/assets/images/generate-test-key-5421c844acd62e810d8a7be508ac4cb4.webp) 6. Return to the FlutterFlow project, navigate to **Settings and Integrations** > **In App Purchases & Subscriptions** > **Razorpay**. Use the toggle to **Enable Razorpay Payments**. 7. Under **Test Credentials**, paste the **Key ID** and **Key Secret** obtained in the previous step. 8. Set your **Business Name**. 9. Click the **Deploy** button. ![deploy](/assets/images/deploy-453f7e6cbf49e55ada68eed897e46030.png) ### 2. Trigger Razorpay payment \[Action][​](/integrations/payments/razorpay.md#2-trigger-razorpay-payment-action "Direct link to 2. Trigger Razorpay payment \[Action]") To initiate a payment using Razorpay, you must use the **Razorpay Payment** action. This action lets users process a payment inside your app using credit cards, debit cards, net banking, UPI (Unified Payments Interface), and digital wallets via Razorpay. Follow the steps below to add this action: 1. Select the widget (e.g., checkout button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click Open. This will open an **Action Flow Editor** in a new popup window. Click on the **+ Add Action**. 3. Search and select the **Razorpay Payment** (under *Integrations*) action. 4. Enter or use a variable for specifying the total amount under the **Amount** section. **Note** that the value should be specified in the currency's smallest unit. * For example, *$24.99* should be passed as *2499* (as a round-off integer; otherwise, it would be automatically rounded); similarly, for an amount of ₹120.00, 12000 should be passed. * Most probably, you'll specify this value from a variable. If you do so, you might need this [inline function](/resources/functions/utility.md#inline-function-code-expressions) to convert the total amount in the required format: `amount.toStringAsFixed(2).replaceAll(".", "");` 5. Enter the **Currency Code** to be used for the amount, for example, *INR*, *USD*, *EUR*, or *BRL*. Make sure you enter a valid currency code; otherwise, the transaction won't go through. Download the complete [list of supported currencies](https://razorpay.com/docs/build/browser/assets/images/international-currency-list.xlsx). ![Specifying amount and country code manually](/assets/images/specify-amount-and-code-manually-d3b5f57866da6b052eb72fd5706fa61d.avif) 6. With this action, you can also add some optional fields, such as **Receipt Number**, **Description**, **User Name**, **User Email**, **User Contact**, and **Timeout** (time for which the checkout dialog should remain active. By default, it is 180 seconds). 7) You can also customize the color scheme for the payment sheet using properties such as **Dialog Color, Barrier Color,** **Text Color**, **Processing Color**, **Success Color**, **Error Color,** and more. ![Customizing Razorpay payment sheet](/assets/images/customize-payment-sheet-b2c3a55da1a85dbc4cd3d088f7e65949.avif) 8. Enter an **Action** **Output Variable Name** where the payment ID would be stored on a successful transaction. 9. Now you must check if the payment was successful. You can do so by adding the [conditional action](/resources/functions/conditional-logic.md#conditional-actions). To do so, click the "**+**" button below the previous action tile and select **Add Conditional**. 10. On the right side (**Set Condition for Action**), 1. Select **UNSET** > **Condition** > **Single Condition**. 2. **First Value** > **Action** **Output Variable Name**. 3. Set the operator to **Is Set and Not Empty**. 11. Under the **TRUE** section, add an action that will be triggered if the payment is successful. 12. Under the **FALSE** section, add an action that will be triggered if payment is failed. warning Ensure the user is authenticated before triggering this action; otherwise, it will result in an error. You can follow the steps on [**this page**](/integrations/authentication/firebase/initial-setup.md) to set up Firebase Authentication. ### 3. Testing[​](/integrations/payments/razorpay.md#3-testing "Direct link to 3. Testing") You can test Razorpay payments on Run mode, Test mode, an emulator/Simulator, or a physical device. To test payments in Test or Run mode: 1. In your FlutterFlow project, navigate to **Settings and Integrations** > **In App Purchases & Subscriptions** > **Razorpay**. 2. Make sure the **Is Production** is disabled. 3. Make sure you have entered the correct **Test Credentials**. 4. Run your app in [Test mode](/testing/run-your-app.md#test-mode). 5. To test the purchase, you can try any method from [here](https://razorpay.com/docs/payments/payments/test-card-upi-details/#test-card-for-international-payments). ### 4. Releasing to production[​](/integrations/payments/razorpay.md#4-releasing-to-production "Direct link to 4. Releasing to production") Once you are done testing your Razorpay integration and you are ready to go **live**, follow the steps below: 1. Complete **KYC** (or the [Activation Form](https://dashboard.razorpay.com/app/activation?ref=blog.flutterflow.io)) to access the Razorpay Live API. 2. Log into the [Razorpay Dashboard](https://dashboard.razorpay.com/?ref=blog.flutterflow.io#/access/signin) and switch to **Live Mode** on the menu. 3. From the left side menu, select **Account & Settings** > Under **Website and app settings** section, select **API keys**. 4. Click **Generate Live Key** and copy the **Key Id** and **Key Secret**. To regenerate, click on **Regenerate Live Key** and choose how you want to deactivate the old key. 5. Return to the FlutterFlow project, navigate to **Settings and Integrations** > **In App Purchases & Subscriptions** > **Razorpay**. Turn on the **Is Production**. 6. Under **Production Credentials**, paste the **Key ID** and **Key Secret** obtained in the previous step. 7. Click the **Deploy** button. 8. [Test](/testing/run-your-app.md#test-mode) your app. --- # RevenueCat [RevenueCat](https://www.revenuecat.com/) simplifies implementing in-app purchases and subscriptions by handling all purchase validation operations. Pub.Dev package and Limitations The [**underlying package for RevenueCat**](https://pub.dev/packages/purchases_flutter) does not support web. Any functionality related to in-app purchases or subscriptions managed through RevenueCat will not be available on web platforms. ## Setup RevenueCat[​](/integrations/payments/revenuecat.md#setup-revenuecat "Direct link to Setup RevenueCat") To set up the RevenueCat, follow these steps carefully: 1. Sign up for a new RevenueCat account [here](https://app.revenuecat.com/). 2. [Create a project](https://www.revenuecat.com/docs/getting-started/quickstart#%EF%B8%8F-create-a-project), [add your app](https://www.revenuecat.com/docs/getting-started/quickstart#%EF%B8%8F-add-an-app--platform), and ensure that you [add service credentials](https://www.revenuecat.com/docs/getting-started/quickstart#%EF%B8%8F-service-credentials) to help RevenueCat communicate with the app stores on your behalf. 3. [Create subscriptions](https://www.revenuecat.com/docs/getting-started/quickstart#%EF%B8%8F-store-setup) in the respective stores. 1. While creating subscriptions in Google Play Console, if you see a message saying '***Your app doesn't have any in-app products yet**'* like in this picture, follow the steps below: ![error-while-creating-sub-in-play-console.avif](/assets/images/error-while-creating-sub-in-play-console-602d9fd2b8458217070b1dc3ad49b334.avif) 1. Return to FlutterFlow and navigate to **Settings & Integrations >** **In App Purchases & Subscriptions >** **RevenueCat**. 2. Switch on the **Enable RevenueCat**. For now, just enter any random string as your API Key (eg. `testkey`). We’ll update this later. 3. Now, from the toolbar menu, click **Download APK** 4. In the Play Console, create a [Closed testing](https://play.google.com/console/about/closed-testing/) track and create a new release. 5. Upload your **App Bundle** or **APK**, enter the release name, and create the release. 6. Open the **Subscriptions** tab again. It should let you manage subscriptions now. 4. [Create Products and Entitlements in RevenueCat](https://www.revenuecat.com/docs/getting-started/quickstart#%EF%B8%8F-configure-products-and-entitlements-in-revenuecat). ### Enable RevenueCat in FlutterFlow[​](/integrations/payments/revenuecat.md#enable-revenuecat-in-flutterflow "Direct link to Enable RevenueCat in FlutterFlow") To enable RevenueCat in FlutterFlow, follow the steps below: ## Displaying Subscription Details in Your App[​](/integrations/payments/revenuecat.md#displaying-subscription-details-in-your-app "Direct link to Displaying Subscription Details in Your App") To show in-app purchase and subscription information — such as pricing, product name, and description — within your app’s UI, you'll need to fetch these details from RevenueCat using the appropriate API or method. Here is an example of retrieving monthly subscription details: ## RevenueCat Actions[​](/integrations/payments/revenuecat.md#revenuecat-actions "Direct link to RevenueCat Actions") To manage in-app purchases and subscriptions inside your FlutterFlow app, you have to use the RevenueCat Actions. Below are the types of RevenueCat actions: * **Paywall** * **Purchase** * **Restore Purchases** ### Paywall \[Action][​](/integrations/payments/revenuecat.md#paywall-action "Direct link to Paywall \[Action]") This action checks whether a user has purchased an item. If not, you can open the Paywall (asking to buy an item or purchase a subscription). Follow the steps below to see if a user is subscribed and take action accordingly. ### Purchase \[Action][​](/integrations/payments/revenuecat.md#purchase-action "Direct link to Purchase \[Action]") This action allows you to purchase the item. Here’s how you add it: ### Restore Purchases \[Action][​](/integrations/payments/revenuecat.md#restore-purchases-action "Direct link to Restore Purchases \[Action]") Using this action, you can allow users to re-activate the subscription they have already paid for. This is helpful when a user has reinstalled the app or logged in to a new device. info * A good practice is to allow users to manually restore the purchase by showing a button or text (maybe on a paywall/settings page). * If you provide this option, please check [**How RevenueCat should respond to restore behavior**](https://www.revenuecat.com/docs/restoring-purchases#restore-behavior). ![adding-restore-purchase-action.avif](/assets/images/adding-restore-purchase-action-59390e74c4e0be5f07c417cfa440c923.avif) Adding action to restore purchase ## Testing Subscriptions[​](/integrations/payments/revenuecat.md#testing-subscriptions "Direct link to Testing Subscriptions") You can test your subscriptions using sandbox environments, which simulate real store behavior without incurring costs. Check out the full **[Sandbox Testing Guide](https://www.revenuecat.com/docs/test-and-launch/sandbox)** for more details. Before going live, make sure to review **[RevenueCat’s Launch Checklist](https://docs.revenuecat.com/docs/launch-checklist)** to ensure everything is properly set up for production. ## FAQs[​](/integrations/payments/revenuecat.md#faqs "Direct link to FAQs") I don't see offerings or products If you're testing in the sandbox and the products are not retrieved from Apple/Google, it's likely a configuration issue. To resolve this, ensure the following: 1. The product identifier set in RevenueCat matches exactly with the store. 2. You're testing on a physical device and not a simulator. 3. The bundle ID in Xcode \[iOS] or package name \[Google] matches what's in App Store Connect or Google Play Developer console. For iOS only, ensure that products are in the 'Ready To Submit' or 'Approved' state, you've signed your 'Paid Applications Agreement', and you're not using a StoreKit Configuration file. For Google only, ensure that the subscription product is in the Active state, your app is published on a closed track, and you've added a tester. See more details [here](https://community.revenuecat.com/sdks-51/why-are-offerings-or-products-empty-124). ## Looking for other options?[​](/integrations/payments/revenuecat.md#looking-for-other-options "Direct link to Looking for other options?") If you're looking for other tools to manage in-app subscriptions, [**Adapty**](https://adapty.io/) is a solid alternative to RevenueCat — it offers advanced analytics, paywall A/B testing, and seamless integration with iOS and Android apps. You can explore the [**Adapty Library on our Marketplace**](https://marketplace.flutterflow.io/item/Mf1oFJcqngHzERZSPNA8) — it's actively maintained by the Adapty team and always kept up to date. --- # Stripe Stripe helps integrate payment processing into your FlutterFlow app. Using this payment service, you can easily sell products directly inside your application and manage transactions easily. While using Stripe as the payment provider, users can buy products using credit cards, Apple Pay, or Google Pay. Prerequisites Before starting to set up payments, make sure you: 1. Complete [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) for your project. 2. Enable [**Firebase Authentication**](/integrations/authentication/firebase/initial-setup.md) for your project. 3. Upgrade your Firebase project to [**Blaze Plan**](https://firebase.google.com/pricing). We use [**Firebase Cloud Functions**](https://firebase.google.com/docs/functions) to process a transaction. ## Stripe Integration[​](/integrations/payments/stripe.md#stripe-integration "Direct link to Stripe Integration") Integrating the Stripe Payments in your app comprises the following steps: 1. [Setup Stripe payment](/integrations/payments/stripe.md#1-setup-stripe-payment) 2. [Apple Pay setup (optional)](/integrations/payments/stripe.md#2-apple-pay-setup-optional) 3. [Trigger Stripe payment](/integrations/payments/stripe.md#3-trigger-stripe-payment-action) 4. [Testing](/integrations/payments/stripe.md#4-testing) 5. [Releasing to production](/integrations/payments/stripe.md#5-releasing-to-production) ### 1. Setup Stripe Payment[​](/integrations/payments/stripe.md#1-setup-stripe-payment "Direct link to 1. Setup Stripe Payment") Setting up the Stripe payment includes acquiring the keys from your Stripe account and adding them to FlutterFlow. warning You should always try out payments in test mode before releasing them to your production app. Hence, the instructions below will guide you on how to get the test keys. Follow the steps below to set up payment using Stripe: 1. Create a new **Stripe account** from [here](https://dashboard.stripe.com/register). If you already have an account, [login](https://dashboard.stripe.com/login). 2. From the dashboard page, click **Developers**. 3. Enable **Test Mode** (top right side of your screen). 4. Switch to the **API keys** tab. 5. Return to the FlutterFlow project and navigate to **Settings and Integrations** > **In App Purchases & Subscriptions** > **Stripe**. Use the toggle to **Enable Stripe Payments**. 6. Copy the **Publishable Key** and **Secret Key** from the Stripe API keys page and paste them into the respective fields inside FlutterFlow. If you are using Stripe in test mode, make sure you paste them inside the **Test Credentials** section. 7. Under the **Additional Settings**, you need to specify the following: 1. **Merchant Display Name** (*Required*): Enter a name for the merchant (you) that the user will see while performing the payment. 2. **Merchant Country Code** (*Required*): Enter your country code. This must be the 2 digit ISO country code, such as US, IN, and AU. 3. **Apple Merchant ID** (*Optional*): You need to enter this if you want to accept payments through Apple Pay as well. The instructions for using Apple Pay are in [this section](/integrations/payments/stripe.md#2-apple-pay-setup-optional). 8. Click **Deploy**. This would deploy the Stripe payment service as a Firebase Cloud Function. Now, you are ready to trigger payments inside your app. ### 2. Apple Pay Setup (optional)[​](/integrations/payments/stripe.md#2-apple-pay-setup-optional "Direct link to 2. Apple Pay Setup (optional)") Setting up Apple Pay comprises the following steps: 1. [Creating Apple Merchant ID](/integrations/payments/stripe.md#21-creating-apple-merchant-id) 2. [Uploading Payment Certificate in Stripe](/integrations/payments/stripe.md#22-uploading-payment-certificate-in-stripe) 3. [Adding Apple Merchant ID in FlutterFlow](/integrations/payments/stripe.md#23-adding-apple-merchant-id-in-flutterflow) #### 2.1 Creating Apple Merchant ID[​](/integrations/payments/stripe.md#21-creating-apple-merchant-id "Direct link to 2.1 Creating Apple Merchant ID") To create Apple Merchant ID: 1. Go to Apple's Developer Center and select [**Certificates, Identifiers & Profiles**](http://developer.apple.com/account). 2. Under **Identifiers**, select ***Merchant IDs***. 3. Click the **Add button** (+) in the upper-right corner. 4. Enter a **Description** and specify an **Identifier**. The identifier is usually defined in the format `merchant` followed by the *Package Name* of your app (you'll find it inside the ***Settings and Integrations*** page of FlutterFlow), for example, `merchant.com.domainname.appname`. 5. Click **Continue**. 6. Review the settings, and click **Register**. 7. Click **Done**. 8. Now, again under **Identifiers**, select ***Apps IDs***. 9. Select your app's identifier from the list. 10. Under **Capabilities**, check the ***Apple Pay Payment Processing*** option. 11. Click **Configure**. 12. Select the merchant account that you just created, and click **Continue**. 13. Click **Save** and then **Confirm** in the dialog. #### 2.2 Uploading Payment Certificate in Stripe[​](/integrations/payments/stripe.md#22-uploading-payment-certificate-in-stripe "Direct link to 2.2 Uploading Payment Certificate in Stripe") To upload a payment certificate in Stripe: 1. First, go to the [**Settings**](https://dashboard.stripe.com/settings) page from your Stripe dashboard and select the **Payment methods** option. 2. Expand the **Apple Pay** tab under the **Wallets** section. 3. Click **Configure** to navigate to the [**Apple Pay settings**](https://dashboard.stripe.com/settings/payments/apple_pay) page. 4. Under **iOS certificates**, click **+ Add new application**. 5. This will download the **Certificate Signing Request (CSR)** file on your system and click **Continue**. 6. Select the **Merchant ID** with which you want to associate this certificate, and click **Create Certificate**. 7. Follow the instructions to **upload the CSR file** that you downloaded from Stripe. 8. To enable the certificate, click **Activate**. Then click **Download** to save it locally. 9. Go back to the Stripe page where the dialog box is displayed, and click **Continue**. 10. Upload the new certificate file. 11. Once uploaded, you should see the certificate listed under **iOS certificates**. #### 2.3 Adding Apple Merchant ID in FlutterFlow[​](/integrations/payments/stripe.md#23-adding-apple-merchant-id-in-flutterflow "Direct link to 2.3 Adding Apple Merchant ID in FlutterFlow") To add Apple Merchant ID in FlutterFlow: 1. Navigate to **Settings and Integrations** > **In App Purchases & Subscriptions** > **Stripe**. 2. Under the **Additional Settings**, enter your **Apple Merchant ID**. ![Adding Apple Merchant ID in FlutterFlow](/assets/images/adding-apple-merchant-id-d00eed09f3715d39b10df09e118fedde.png) ### 3. Trigger Stripe Payment \[Action][​](/integrations/payments/stripe.md#3-trigger-stripe-payment-action "Direct link to 3. Trigger Stripe Payment \[Action]") In order to initiate a payment using Stripe, you have to use the **Stripe Payment** action. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. On the right side, search and select the **Stripe Payment** (under *Integrations*) action. 5. Enter or use a variable for specifying the total payment amount under the **Amount** section. The value should be specified in the currency's smallest unit. For example, *$24.99* should be passed as *2499* (as a round-off integer, otherwise it would be automatically rounded), whereas *¥1925* can be simply passed as *1925*. For more information check out [this page](https://stripe.com/docs/currencies#zero-decimal). 6. Enter the **Currency Code** to be used for the amount, for example, *USD*, *EUR*, *BRL*. Make sure you enter a valid currency code otherwise, the transaction won't go through. 7. Next, you need to specify the **Customer Email** (required) and **Customer Name** (optional) to be used for the transaction. You can either use a variable or enter the value for them. If you are using authentication, these two values can be retrieved from the ***Authenticated User**.* 8. Specify a **Description** of the purchase for both your and the user's record. 9. To enable **Google Pay** or **Apple Pay** as the payment method, turn on the respective toggle. To use Apple Pay, you have to set up a *Merchant ID* by following the steps [here](/integrations/payments/stripe.md#2-apple-pay-setup-optional). 10. Select the **Payment Sheet Theme** among ***System Default***, ***Light Theme***, or ***Dark Theme**.* 11. Specify the **Primary Button Color** and **Button Text Color** to be used on the payment dialog. 12. Enter an **Output Variable Name** where the payment ID would be stored on a successful transaction. Later, you can use this variable elsewhere inside the page or pass it to a different page of the app. warning Make sure the user is authenticated before triggering the Stripe Payment Action. Otherwise, it will result in an error. ### 4. Testing[​](/integrations/payments/stripe.md#4-testing "Direct link to 4. Testing") You can test Stripe payments on mobile and the Web before deployment. To do that: 1. Go to the FlutterFlow project and navigate to **Settings and Integrations** > **In App Purchases & Subscriptions** > **Stripe**. 2. Make sure the **Is Production** is disabled. 3. Make sure you have entered the correct **Test Credentials,** such as **Publishable Key** and **Secret Key**. 4. [Download](/flutterflow-cli/exporting.md) and [run](/testing/run-your-app.md) your project.. 5. To test the purchase, you can use any of these [basic test card numbers](https://stripe.com/docs/testing#cards). ### 5. Releasing to Production[​](/integrations/payments/stripe.md#5-releasing-to-production "Direct link to 5. Releasing to Production") Before you release the app to production, complete the following steps: 1. [Login](https://dashboard.stripe.com/login) to your Stripe account and navigate to the **Developers** page. 2. Disable the **Test Mode** (top right side of your screen). 3. Select **API keys** from the left menu and copy the **Publishable Key** and **Secret Key**. 4. Return to FlutterFlow; under the **Production Credentials** section, paste the **Publishable Key** and **Secret Key**. 5. To deploy the Android app, follow the [Google Play Store Deployment](/deployment/google-playstore-deployment.md) guide. 6. To deploy the iOS app, follow the [App Store Deployment](/deployment/apple-app-store-deployment.md) guide. *** ## FAQs[​](/integrations/payments/stripe.md#faqs "Direct link to FAQs") I am getting "Error: Unknown error occurred" When encountering the "Error: Unknown error occurred" message, consider these troubleshooting steps: 1. **Stripe Settings Adjustment**: In FlutterFlow's Stripe settings, verify the Merchant country code is a 3-digit code, like "USA" instead of "US". If needed, remove previously deployed functions in the Firebase console and redeploy them after updating the country code. 2. **User Authentication Requirement**: Stripe payments require an authenticated user session. Ensure you're attempting the Stripe action after a user has successfully logged in to the app. 3. **Cloud Functions Permissions**: Check that your cloud functions have the **Cloud Functions Invoker** permission set for **allUsers** in the Google Cloud console. To do this, go to the Cloud Console, directly search for the **initStripePayment** function, open the function, switch to the **Permissions** tab, and confirm the permissions status. This permission is typically assigned by default, but it's good practice to double-check. ![unknown-error-occured](/assets/images/unknown-error-occured-b03b43f1bf942dbbd98e2685f95a4ebd.avif) --- # Algolia [Algolia](https://www.algolia.com/) is a powerful search-as-a-service platform that provides lightning-fast and highly relevant search capabilities. Integrating Algolia into your FlutterFlow app allows you to implement real-time search functionality, making it easier for users to find relevant information within your app. Prerequisites * Algolia integration in FlutterFlow is tied exclusively to Firestore collections. This means you must [**setup Firebase**](/integrations/firebase/connect-to-firebase.md) to sync data from Firestore into Algolia for searching. * **Upgraded** your Firebase project to the [**Blaze Plan**](https://firebase.google.com/pricing) for the Algolia Firebase Extension to work. * Have at least one **Firestore Collection** on which you want to perform the search queries. ## Algolia integration[​](/integrations/search/algolia-search.md#algolia-integration "Direct link to Algolia integration") Follow the steps below to integrate Algolia in your FlutterFlow apps: ### Setup Algolia[​](/integrations/search/algolia-search.md#setup-algolia "Direct link to Setup Algolia") Setting up Algolia involves creating an application, defining an index, and generating an API key with the necessary permissions. Here are the steps in detail: #### Step 1: Create an Algolia Application[​](/integrations/search/algolia-search.md#step-1-create-an-algolia-application "Direct link to Step 1: Create an Algolia Application") Login to [Algolia](https://www.algolia.com/). If you don’t have an account, sign up for a free account [here](https://www.algolia.com/users/sign_up). During registration, fill in the required details and select a **data center region**. After signing up, you’ll be presented with an **import data screen**, but you can skip this for now (see button at the top right). Next, name your application by navigating to **Settings > Applications** in the Algolia dashboard. By default, you should see an application called **"(unnamed application)"**. Click the three-dot button beside it, select **Rename**, enter a name for your application, and click **Save**. #### Step 2: Create an Index[​](/integrations/search/algolia-search.md#step-2-create-an-index "Direct link to Step 2: Create an Index") An **index** in Algolia is like a **database table** where your searchable data is stored. To create an index, go to the **Search** section in the left menu, then select **Index**. Click on **Create Index**, and **provide an exact name that corresponds to the Firestore collection** on which you intend to perform the search queries. #### Step 3: Generate an API Key[​](/integrations/search/algolia-search.md#step-3-generate-an-api-key "Direct link to Step 3: Generate an API Key") To integrate Algolia, you need an **API key** with the correct permissions. In the Algolia dashboard, go to **Settings > API Keys > All API Keys**, then click **New API Key**. Under **Indices**, select the index you created in the previous step. In the **ACL (Access Control List)** field, include these permissions: `addObject`, `deleteObject`, `listIndexes`, `deleteIndex`, `editSettings`, and `settings`. Click **Create**, then copy the generated API Key and keep it handy—you’ll need it next to [configure Algolia Firebase Extension](/integrations/search/algolia-search.md#sync-firebase-data). ### Sync Firebase Data[​](/integrations/search/algolia-search.md#sync-firebase-data "Direct link to Sync Firebase Data") To sync your data from Firebase to Algolia, you must install [Algolia Firebase Extension](https://extensions.dev/extensions/algolia/firestore-algolia-search). It allows you to seamlessly connect **Cloud Firestore** with **Algolia**, ensuring that any updates, additions, or deletions in Firestore are instantly reflected in your search index. Follow these steps to set up the official Firebase extension for Algolia search: 1. **Open Firebase Extensions:** Go to the [**Search Firestore with Algolia**](https://extensions.dev/extensions/algolia/firestore-algolia-search) extension page, then click **Install in Firebase Console**. Choose your project to proceed with the installation. 2. **Update Extension Instance ID (Optional)**: An extension instance ID uniquely identifies each installed instance of an extension within your Firebase project. This ID is used to manage the extension instance, including updating or uninstalling it. 3. **Review Billing and Usage:** A summary of billing details will appear. After reviewing, click **Next**. 4. **Review APIs Enabled and Resources Created:** This extension automatically creates some resources like Cloud Functions and APIs to interact with Algolia. Check the listed resources, then click **Next**. 5. **Review Access Granted to this Extension:** You'll be presented with a list of specific services and resources that the extension needs access to. Review the permissions, then click **Next**. 6. **Configure Extension:** During installation, you'll be prompted to provide the following details. * **Collection Path**: Specify the name of the Firestore collection you want to index for search. * **Indexable Fields (Optional)**: You can leave this blank to index all fields or manually list fields you want indexed. * **Force Data Sync (Optional)**: You can enable this to ensure that the extension performs an additional read operation from Firestore before processing and sending data to Algolia. It guarantees that the most recent and accurate data is indexed. * **Algolia Index Name**: The name of the index you created (in [step 2](/integrations/search/algolia-search.md#step-2-create-an-index)) in Algolia Setup. * **Algolia Application ID**: You can go to the Algolia dashboard page and check its URL, `https://www.algolia.com/apps/`. Copy the `application_id` and enter it in the field. * **Algolia API Key**: Paste the API key you created (in [step 3](/integrations/search/algolia-search.md#step-3-generate-an-api-key)) during the Algolia Setup and hit **Create Secret** button. * **Full Index Existing Documents**: Set this to **Yes** to import the existing data from the Firestore collection into the Algolia index. * **Cloud Functions Location**: Choose the region for deploying the Cloud Function. 7. **Install**: Click **Install extension** to finalize. Allow a few moments for the extension to install completely before proceeding to the next steps. ### Choose Searchable Fields[​](/integrations/search/algolia-search.md#choose-searchable-fields "Direct link to Choose Searchable Fields") To limit the fields used for searching in Algolia, you can specify which attributes should be indexed. From the **Algolia dashboard**, go to **Search > Index > Configuration** and click **+ Add a Searchable Attribute**. Enter the field name you want Algolia to use and repeat this step for additional fields. Once done, click **Review and Save Settings**, then confirm by clicking **Save Settings** in the dialog. Algolia will now search only within the specified fields in your app. ### Configure in FlutterFlow[​](/integrations/search/algolia-search.md#configure-in-flutterflow "Direct link to Configure in FlutterFlow") To integrate **Algolia Search** into your FlutterFlow app, go to **Settings and Integrations > Algolia** and enable it. Enter the **Application ID**, which you can find in your Algolia dashboard URL (`https://www.algolia.com/apps/`). Next, copy the **Search API Key** from **Algolia Settings > API Keys** and paste it into FlutterFlow. Finally, under **Indexed Collections**, select the Firestore collections you want to make searchable. Here’s exactly how you do it: ## Using Algolia Search[​](/integrations/search/algolia-search.md#using-algolia-search "Direct link to Using Algolia Search") You can use Algolia Search in your app using two methods: * [**Algolia Search Action**](/integrations/search/algolia-search.md#algolia-search-action): This method is useful when the user enters a search term in a TextField and then interacts with a widget, such as tapping a button, to initiate the search. * [**Backend Query**](/resources/backend-query/algolia-search-query.md): This approach automatically searches or refreshes search results as the user types in the TextField. It leverages the **Update Page On Text Change** property to dynamically update results. ### Algolia Search \[Action][​](/integrations/search/algolia-search.md#algolia-search-action "Direct link to Algolia Search \[Action]") To configure the **Algolia Search** action in FlutterFlow, begin by selecting the widget that will trigger the search, such as an **IconButton**. In the **Properties Panel**, navigate to the **Actions** tab and click on **+ Add Action**, choose the appropriate gesture, like **On Tap**. Search and select the **Algolia Search** action. Next, configure the search parameters: for **Firebase Collection**, select the Firestore collection you intend to search; for **Search Term**, choose **From Variable** and select the TextField's value (e.g., **Widget State > \[Your TextField]**); and specify the optional **Max Results** to determine the number of search results. Here’s an example of how you can add Algolia Search Action: ## FAQs[​](/integrations/search/algolia-search.md#faqs "Direct link to FAQs") Does Algolia work with other data sources like Supabase? By default, FlutterFlow’s built-in Algolia integration only supports Firestore as the data source. If you need to use Algolia with another database—such as Supabase—you would have to manage that integration via [**custom code**](/concepts/custom-code.md). However, out of the box, FlutterFlow currently does not offer an Algolia search on databases beyond Firestore. --- # Simple Search The simple search allows you to search the data present locally on a device. For example, you could search from the list of strings (stored in a variable) and from the Firestore collection and documents already retrieved on the user's device (displayed on the screen). When to use Simple Search vs Algolia We advise using a simple search only for the smaller Firestore collection (with limited records). Otherwise, it can be slow and/or expensive. For a more extensive collection, consider using the [**Algolia search**](/integrations/search/algolia-search.md). ## Types of Simple Search[​](/integrations/search/simple-search.md#types-of-simple-search "Direct link to Types of Simple Search") There are three types of search you can add to the page: * **Firestore collection**: To search from the Firestore collection. * **Documents**: To search from the list of documents stored in a variable. * **Strings**: To search from the list of strings stored in a variable such as app or page state variable. ## Simple Search \[Action][​](/integrations/search/simple-search.md#simple-search-action "Direct link to Simple Search \[Action]") Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Click on the **+ Add Action**. 2. On the right side, search and select the **Simple Search** action. 3. Select the **Search Type** among the **Firestore Collection**, **Documents**, and **Strings**. 4. If you select the **Firestore Collection**: 1. Set the **Collection** to the one that you want to search from. 2. **Select Searchable Fields** to the field that you want to perform the search on. 5. If you select the **Documents**: 1. Set the **Source** to the variable that holds the list of documents. For example, the result of the query at a top-level widget such as **Page** or **Column** 2. **Select Searchable Fields** to the field that you want to perform the search on. 6. If you select the **Strings**: 1. Set the **Source** to the variable that holds the list of strings (e.g., app or page state variable). 7. Inside the **Search Term** section, set **Widget State > TextField** (where users enter a search term). --- # Supabase Setup You can either use [Supabase OAuth](/integrations/supabase/setup.md#connect-with-supabase-oauth) for a quick and secure setup or [connect using API Keys](/integrations/supabase/setup.md#connect-with-supabase-api-keys) for self-hosted setups. ## Connect with Supabase OAuth[​](/integrations/supabase/setup.md#connect-with-supabase-oauth "Direct link to Connect with Supabase OAuth") To connect with Supabase using the OAuth method, follow the steps below: 1. Open **Settings & Integrations** and go to the **Supabase** section. 2. Select the **Connect with Supabase OAuth** tab. 3. Click **Connect to Supabase** to start the connection flow. 4. Choose your Supabase organization and authorize access. 5. After authorization, either select an existing Supabase project or click **Create New Project** to make a new one. 6. If creating a new project, enter the project name and region, then click **Create**. 7. Copy and save the database password, since it will not be shown again. 8. Click **Done** to finish the setup. 9. Once connected, you can view and manage the Supabase project from the Supabase settings, switch projects, or open it in a new browser tab. tip After [**creating**](/integrations/supabase/setup.md#create-tables-in-supabase) or updating tables in your Supabase database, make sure to click **Get Schema** to refresh and sync the latest table structure in FlutterFlow. ## Connect with Supabase API Keys[​](/integrations/supabase/setup.md#connect-with-supabase-api-keys "Direct link to Connect with Supabase API Keys") To connect using Supabase API Keys, you will manually link your Supabase project with FlutterFlow by providing the required credentials. warning Please note that this method is only intended for **self-hosted Supabase databases**. 1. First, create a project in Supabase from the Supabase dashboard. 2. In your Supabase project, navigate to [Project Settings > API](https://app.supabase.com/project/cwnjvtflygqlpxdpsujv/settings/api). Copy the **Project URL**. 3. Return to FlutterFlow, navigate to **Settings and Integrations > Integrations > Supabase**. Turn on the toggle (i.e., enable Supabase) and paste the **API URL**. 4. Similarly, from the Supabase [API section](https://app.supabase.com/project/cwnjvtflygqlpxdpsujv/settings/api), copy the **anon key** (under **Project API keys**) and paste it inside the **FlutterFlow > Settings and Integrations > Integrations > Supabase > Anon Key.** 5. Click on the **Get Schema** button. This will show the list of all tables with their schema (structure) created in Supabase. 6. (Optional) If you have defined an *Array* for any *Column Data Type* in Supabase, you must set its type here. To do so, tap the "**Click to set Array type**" and choose the right one. tip After [**creating**](/integrations/supabase/setup.md#create-tables-in-supabase) or updating tables in your Supabase database, make sure to click **Get Schema** to refresh and sync the latest table structure in FlutterFlow. ## Create Tables in Supabase[​](/integrations/supabase/setup.md#create-tables-in-supabase "Direct link to Create Tables in Supabase") If you haven't already, [create table(s)](https://supabase.com/docs/guides/database/tables#creating-tables). If you're just getting started, you can uncheck the **Enable Row Level Security (**[**RLS**](https://supabase.com/docs/guides/auth/row-level-security)**)** option to remove any restrictions on accessing the table data. Note It's important to note that while disabling Row Level Security (RLS) can be useful for testing and development purposes, **it's recommended that you re-enable RLS** and implement an access policy that aligns with your app's requirements before deploying your app. Here's an example of creating an "assignments" table with a [foreign key relationship](https://supabase.com/docs/guides/database/tables#joining-tables-with-foreign-keys) from `created_by` column to `public.users.id` with `on delete cascade`. This ensures that if a user is deleted from the "public.users" table, any data related to that user stored in your "assignments" table will also be deleted. note To use Supabase authentication, you must [**create a "users" table**](/integrations/authentication/supabase/initial-setup.md#1-creating-a-users-table). --- # Adding & Purchasing Items The **FlutterFlow Marketplace** lets you add new features to your app in just a few clicks. It includes ready-made components, templates, and libraries built by other users. These items can help you add things that are not yet available in FlutterFlow or would take more time to build from scratch. To add a Marketplace item, go to your FlutterFlow dashboard and click **Marketplace**, or visit [marketplace.flutterflow.io](https://marketplace.flutterflow.io/) directly. Click on any item to view its details. * For **free items**, click **+ Clone for Free**, then choose the project you want to add it to. * For **paid items**, click **Buy Now** and complete the purchase. Once added, the item will be available in your selected project for immediate use. * Free Item * Paid Item ![free-item](/assets/images/free-item-723adc39b8cba522e58a59b89a0cffb0.avif) ![paid-item](/assets/images/paid-item-1eb671388d39c50fbe09110f71035c44.avif) ## Add Library Item[​](/marketplace/adding-purchasing-item.md#add-library-item "Direct link to Add Library Item") To install a library item from the Marketplace, search for the library, open its details page, and click **+ Add for Free**. This adds the library to your FlutterFlow account, meaning you can reuse it in any of your projects. To add it to a specific project, go to **Settings > Project Dependencies**, click **Add Library**, and search for your library. ![branch-library-install](/assets/images/branch-library-install-c634fb2710a175e9f5cca5a7a7a738b9.png) --- # Creators Hub Welcome to the FlutterFlow Marketplace Creators' Hub! This section is designed to provide you with all the necessary information to contribute effectively and responsibly to Marketplace. Whether you are submitting your first item or looking to understand the legal nuances, you'll find detailed guidelines and helpful tips here. ### Submitting an Item for Review[​](/marketplace/creators-hub.md#submitting-an-item-for-review "Direct link to Submitting an Item for Review") * Understand the [criteria](/marketplace/creators-hub/submission-criteria.md) we apply to items submitted to Marketplace. * Learn how to prepare and [submit](/marketplace/creators-hub/submit-item-for-review.md) your items to the Marketplace with our step-by-step guide. ### Legal Guidelines for Creators[​](/marketplace/creators-hub.md#legal-guidelines-for-creators "Direct link to Legal Guidelines for Creators") A user-friendly [guide](/marketplace/creators-hub/legal-guidelines-for-creators.md) outlining what content can and cannot be published on our Marketplace. We've also compiled information on dealing with [external licenses](/marketplace/creators-hub/navigating-external-licenses.md), including excerpts from popular third-party marketplaces that restrict the creation of templates on platforms like FlutterFlow Marketplace. Finally, we have a detailed guide on how we handle [DMCA takedown notices](/marketplace/creators-hub/copyright-dmca-process.md). This is crucial for understanding how to manage copyright issues and ensure compliance. ### Creator FAQs[​](/marketplace/creators-hub.md#creator-faqs "Direct link to Creator FAQs") [Find answers](/marketplace/creators-hub/creator-faqs.md) to common questions from fellow creators. --- # Copyright (DMCA) Process danger This guide is meant for creators of items on FlutterFlow Marketplace. If you want to provide feedback on an item published in FlutterFlow Marketplace, please follow the relevant instructions at [**Submitting Feedback for Items**](/marketplace/submit-feedback.md). As a valued creator on the FlutterFlow Marketplace, it's important to understand the process that unfolds when an item you've submitted receives an infringement report. Our approach distinguishes between two main types of allegations: DMCA infringement claims and other types of infringement allegations. ### DMCA Infringement Claims[​](/marketplace/creators-hub/copyright-dmca-process.md#dmca-infringement-claims "Direct link to DMCA Infringement Claims") The [DMCA (Digital Millennium Copyright Act)](https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act) is a US copyright law that provides a mechanism for copyright owners to request the removal of content they believe infringes on their copyright. Here's how we handle these specific claims: 1. **Immediate Action**: If an infringement report is classified as a DMCA claim and the reporter provides adequate proof of ownership or authorized representation, we are legally required to act quickly. In such cases, the reported item is immediately removed from FlutterFlow Marketplace. 2. **Notification Email**: Upon removal, you will receive an email notification outlining the details of the claim and the steps you can take if you believe the item was wrongly removed. 3. **(Optional) Counter-Notice**: If you choose to submit a counter-notice, we will provide guidance on the process. Please email with any relevant details. ### Other Infringement Allegations[​](/marketplace/creators-hub/copyright-dmca-process.md#other-infringement-allegations "Direct link to Other Infringement Allegations") For other infringement reports, specifically those filed by individuals who are neither the copyright owner nor their authorized representatives, we follow a different process: 1. **Credibility Review:** Our team will first assess the credibility of the report. We will engage with the reporter to gather additional information if the initial claim lacks sufficient detail. If we determine the claim appears credible, we will then proceed to notify you the creator. 1. *Evidence of Prior Publication:* In cases where an infringement allegation is supported by a URL linking to similar content that was clearly published prior to the date of submission on FlutterFlow Marketplace, this URL will be considered sufficient preliminary evidence to establish the credibility of the claim. 2. **48 Hours Notice:** When we receive a non-DMCA allegation that is deemed credible after our initial review, we will notify you and provide a 48-hour period for you to respond to the claim. This window allows you to present any counter-evidence or resolve the issue by modifying or removing the item yourself. 3. **Review of Evidence**: If you provide evidence or make changes that address the report's concerns, we will review this new information before making a final decision on the item's status in Marketplace. 4. **Resolution**: If, after reviewing the evidence, the claim is found to be credible, or if no response is received within the 48-hour window, we will proceed with removing the item from Marketplace and inform you of the action taken. --- # Creator FAQs ## ⚖️ Intellectual Property and Legal Concerns[​](/marketplace/creators-hub/creator-faqs.md#️-intellectual-property-and-legal-concerns "Direct link to ⚖️ Intellectual Property and Legal Concerns") ### Why might my item be removed from Marketplace?[​](/marketplace/creators-hub/creator-faqs.md#why-might-my-item-be-removed-from-marketplace "Direct link to Why might my item be removed from Marketplace?") Your item might be removed from the Marketplace under several circumstances, mainly related to legal and quality standards. Here are the specific reasons: * **DMCA Takedown Notices:** If we receive a DMCA takedown notice claiming that your item infringes on someone else's copyright, we are legally required to remove the item immediately. We will notify you of the takedown, and you will have the opportunity to respond or counter-claim according to the legal processes set out by the DMCA. Please see [Copyright (DMCA) Process](/marketplace/creators-hub/copyright-dmca-process.md) for details. * **Other IP Violations:** If your item is found to violate IP laws outside of a formal DMCA complaint—i.e. if filed by someone other than the original author or their representative—we will inform you of the specific violation. You will be given a chance to provide proof of licensing or to correct the issue within **48 hours**. If satisfactory proof or corrections are not provided, the item may be removed to comply with legal standards. Please see [Copyright (DMCA) Process](/marketplace/creators-hub/copyright-dmca-process.md) for details. * **Violation of Marketplace Policies:** Aside from copyright issues, if your item violates other Marketplace policies, such as those related to quality, accuracy, or ethical standards, you will be notified of the specific issues. We will provide you with details about the violation and, depending on the severity, you may be asked to modify the item or it might be removed. Please see [Marketplace Item Submission Guidelines](https://flutterflow.io/flutterflow-marketplace-item-submission-guidelines) for more details. * **Critical Item Reports:** If we receive reports from users or other creators that critically challenge the legality or appropriateness of your item (e.g., reports of plagiarism, false advertising, or severe quality issues), these will be thoroughly investigated. Based on the findings, and in accordance with our commitment to maintaining a trustworthy and high-quality Marketplace, your item might be subject to removal. We will communicate with you throughout this process, offering details of the report and an opportunity to respond. ### Will I be notified if my item is removed from Marketplace?[​](/marketplace/creators-hub/creator-faqs.md#will-i-be-notified-if-my-item-is-removed-from-marketplace "Direct link to Will I be notified if my item is removed from Marketplace?") Yes, in all cases, you will be notified if your item is removed from FlutterFlow Marketplace. ### Can I list my item on other marketplaces?[​](/marketplace/creators-hub/creator-faqs.md#can-i-list-my-item-on-other-marketplaces "Direct link to Can I list my item on other marketplaces?") No. You cannot sell FlutterFlow projects on other marketplaces. Please see the non-circumvention clause in our [Marketplace Terms of Service](https://flutterflow.io/tos-marketplace). This policy helps ensure that all interactions with FlutterFlow templates are safe, compliant, and effectively managed for our users. ### What licenses are granted to users of my item?[​](/marketplace/creators-hub/creator-faqs.md#what-licenses-are-granted-to-users-of-my-item "Direct link to What licenses are granted to users of my item?") When users purchase or add items from the FlutterFlow Marketplace, they are granted use under specific licenses: * **Free items** are generally covered under the **MIT License (Open Source License)**, which allows extensive freedom to use, modify, and redistribute the content. * **Paid items** are usually governed by **Single Use License.** Please refer to the specific restrictions and conditions outlined in our [Marketplace Terms of Service](https://flutterflow.io/tos-marketplace) for each license type. ### What if someone copies my template code or design?[​](/marketplace/creators-hub/creator-faqs.md#what-if-someone-copies-my-template-code-or-design "Direct link to What if someone copies my template code or design?") Please reach out to for next steps. ## 🚨 Reporting Process[​](/marketplace/creators-hub/creator-faqs.md#-reporting-process "Direct link to 🚨 Reporting Process") ### What happens if someone reports my item?[​](/marketplace/creators-hub/creator-faqs.md#what-happens-if-someone-reports-my-item "Direct link to What happens if someone reports my item?") info We will always notify you if your item is removed from Marketplace. If the reporter is the original author and files a DMCA notice, the item will be removed immediately as required by law. You will be notified and have the opportunity to submit counter-evidence. Please see [Copyright (DMCA) Process](/marketplace/creators-hub/copyright-dmca-process.md) for details. For other reports, we will notify you **48 hours** before taking any action. During this time, you may submit counter-evidence or clarify the situation. ### If my item is reported, what details will you share with me?[​](/marketplace/creators-hub/creator-faqs.md#if-my-item-is-reported-what-details-will-you-share-with-me "Direct link to If my item is reported, what details will you share with me?") When your item is reported, our goal is to maintain transparency while respecting privacy and legal constraints. Here’s what we will share with you: * **Report Type:** We will inform you about the specific nature of the complaint, such as whether it’s a copyright or quality issue. * **Relevant Details:** We will provide any non-confidential details submitted within the report that you need to understand the complaint and to formulate your response. * **Deadlines:** We will inform you of any deadlines by which you need to respond or take corrective action to avoid having your item removed from Marketplace. Please note that we will not share the identity of the reporter. ### If my item is reported, what details will you share with the original reporter?[​](/marketplace/creators-hub/creator-faqs.md#if-my-item-is-reported-what-details-will-you-share-with-the-original-reporter "Direct link to If my item is reported, what details will you share with the original reporter?") When a report is filed, we ensure that the process is fair and respects the privacy of all parties involved. Here’s what we will share with the original reporter: * **Confirmation of Report:** We will acknowledge receipt of their report and confirm that we are taking it seriously. * **Contact Email:** We will provide your Marketplace Creator official email as specified in your [public profile](https://marketplace.flutterflow.io/profile). ### What if I see low-quality items on the Marketplace? How can I report an item?[​](/marketplace/creators-hub/creator-faqs.md#what-if-i-see-low-quality-items-on-the-marketplace-how-can-i-report-an-item "Direct link to What if I see low-quality items on the Marketplace? How can I report an item?") We strive to maintain a high standard, but some items may not meet these expectations. If you encounter an item on the Marketplace that appears to violate our policies, please submit feedback using one of the channels described on [this page](/marketplace/submit-feedback.md): ## 📦 Item Submission and Review[​](/marketplace/creators-hub/creator-faqs.md#-item-submission-and-review "Direct link to 📦 Item Submission and Review") ### What is the criteria for getting my item approved for Marketplace?[​](/marketplace/creators-hub/creator-faqs.md#what-is-the-criteria-for-getting-my-item-approved-for-marketplace "Direct link to What is the criteria for getting my item approved for Marketplace?") Please see our [Item Submission Criteria](/marketplace/creators-hub/submission-criteria.md). ### What happens if my item is rejected?[​](/marketplace/creators-hub/creator-faqs.md#what-happens-if-my-item-is-rejected "Direct link to What happens if my item is rejected?") If your item is not approved, it will be returned to draft status, allowing you to make the required edits. We will provide you with detailed feedback via email, specifying the criteria it did not meet. You can see more details about our criteria and suggested actions [here](/marketplace/creators-hub/submission-criteria.md). You can then make the necessary adjustments and resubmit it for review. ### When will my template be reviewed?[​](/marketplace/creators-hub/creator-faqs.md#when-will-my-template-be-reviewed "Direct link to When will my template be reviewed?") We have significantly improved our review wait time recently! ⚡ We aim to review your template within 7 days. However, depending on the volume of submissions or the complexity of your submission, the review process can take up to 30 days (20 business days). ## 🖐️ Other Questions?[​](/marketplace/creators-hub/creator-faqs.md#️-other-questions "Direct link to 🖐️ Other Questions?") If your question is not covered here, please **first review the other documentation pages** within the Marketplace section. If you are still facing issues, please reach out to the appropriate channel: * For legal issues, please email * For other issues, please email --- # Legal Guidelines for Creators As part of our creative community, your contributions are invaluable in helping others build amazing apps. To ensure a smooth experience for everyone and to adhere to legal requirements, we've outlined what types of content you can publish on Marketplace. This guide is designed to be user-friendly and straightforward, focusing on the legal aspects of your submissions. For a more detailed view of our policies, please review the [Marketplace Terms of Service](https://flutterflow.io/tos-marketplace). ### What You Can Submit[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#what-you-can-submit "Direct link to What You Can Submit") * **✅ Original Creations:** Your template should be your original work or properly licensed work that you have the right to distribute. Whether it’s a unique design layout, a functional module, or an innovative app solution, if you created it, we're excited to see it! * **✅ API Wrappers**: If you've developed a wrapper for public APIs (like AWS or SendGrid), you're welcome to submit it! Ensure your wrapper adds value through simplification, integration, or enhancement of the original API functionalities. * **✅ Educational Templates**: Educational or demonstrative templates that mimic functionalities of popular apps (like a "social photo app clone") are great for learning and are welcome, provided they do not use any trademarked names, logos, or proprietary UI elements from the actual apps. ### What to Avoid in Your Submissions[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#what-to-avoid-in-your-submissions "Direct link to What to Avoid in Your Submissions") * **❌ Designs from External Marketplaces:** Please refrain from submitting designs or templates that you've acquired from other marketplaces. Typically, these items are licensed, not sold, and come with restrictions that prohibit their redistribution on our platform. For guidance on navigating external licenses and understanding your rights, please review [Navigating External Licenses](/marketplace/creators-hub/navigating-external-licenses.md). * **❌ Proprietary Code or Data**: Do not include any proprietary code, data, or APIs that you do not have explicit rights to use and redistribute. This includes direct copies of proprietary software or use of internal SDKs not intended for public distribution. * **❌ Trademarked Material Without Permission**: Avoid using trademarked names, logos, or branding elements in your templates unless you have obtained explicit permission from the trademark owner. This includes mimicking the UI of a proprietary app exactly. * **❌ Misleading Content**: Ensure your template does not mislead users into thinking it's officially affiliated with or endorsed by any brand or service it might resemble, especially in educational or demonstrative templates. ### Best Practices for Template Submission[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#best-practices-for-template-submission "Direct link to Best Practices for Template Submission") * **✍️ Clear Attribution**: If your template includes third-party open-source components, ensure you comply with their licenses by properly attributing the original creators and including any required license texts or notices. Also see [Open Source Licenses](/marketplace/creators-hub/legal-guidelines-for-creators.md#open-source-licenses). * **💎 Use Generic Elements**: For educational or demonstrative templates, use generic names and design elements to avoid trademark issues while still providing valuable learning experiences. * **⚖️ Include Disclaimers**: When necessary, include disclaimers clarifying the purpose of your template, especially if it's for educational use, to avoid any potential confusion about its unofficial status. * **📣 Stay Informed**: Licenses and legal requirements can change, so it's crucial to stay informed about the legal aspects of the components and APIs you use in your templates. ## Licenses: What's Allowed?[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#licenses-whats-allowed "Direct link to Licenses: What's Allowed?") ### Open Source Licenses[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#open-source-licenses "Direct link to Open Source Licenses") Embracing open-source is part of our ethos, but not all licenses are created equal, especially in a commercial setting like ours. Here's a quick rundown of what fits on Marketplace: * **✅ Permissive Licenses**: Licenses such as MIT, BSD, and Apache 2.0 are generally fine because they allow commercial use and modification with minimal restrictions. Just make sure to give proper credit and include the original license text as required. * **🤔 Copyleft Licenses (Caution!)**: Licenses like GPL (General Public License) can be tricky. They often require derivative works to be distributed under the same license, affecting how your template can be used. If you're considering using GPL-licensed code, please review the specific terms carefully or consult with legal advice to ensure compliance. * **❌ No Unlicensed Code**: Ensure all open-source code used in your templates is properly licensed. Using unlicensed code or failing to comply with open-source license requirements can lead to legal headaches for you and for us. ### Licenses from Other Marketplaces[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#licenses-from-other-marketplaces "Direct link to Licenses from Other Marketplaces") We understand that inspiration can come from lots of different sources, including other marketplaces. However, it's important to respect legal and ethical standards, especially with the reuse of design elements and templates purchased elsewhere. Here’s what you should know: * **🤝 Ownership and Licensing**: When you purchase a design or template from other marketplaces, you're typically acquiring a license to use that item, not owning it outright. These licenses often come with significant restrictions, particularly around redistribution and creating derivative works meant for resale. It’s best to carefully review the license terms for each item and marketplace you use. See [Navigating External Licenses](/marketplace/creators-hub/navigating-external-licenses.md) for some examples. * **⚠️ End Products and Redistribution**: Most marketplace licenses restrict the use of their items as part of an “end product” that is not meant for redistribution or resale. For example, using a downloaded Figma design to create a template that you then sell on FlutterFlow Marketplace may violate the original marketplace's license terms, even under extended licenses. When in doubt, ask the original content provider. Obtaining explicit permission or clarification can prevent future disputes and legal challenges. * **Clarifications and Examples**: * **❌ Likely Prohibited**: Using a UI design purchased from another marketplace to create a FlutterFlow template that you intend to distribute or sell. * **✅ Allowed**: Creating a template inspired by design principles or trends you've observed, without directly copying or adapting a purchased item. * **✅ Allowed**: Implementing a UI design with the explicit written permission from the author and owner of that design. For more details and examples, please review: [Navigating External Licenses](/marketplace/creators-hub/navigating-external-licenses.md) ## Item Reports and Complaints[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#item-reports-and-complaints "Direct link to Item Reports and Complaints") At FlutterFlow, we're committed to maintaining a respectful and legally compliant community. We understand that there may be instances where content on FlutterFlow Marketplace may infringe on your rights or violate our policies. To address these concerns, we've established a straightforward process for filing complaints, including DMCA requests and other legal concerns. ### Reporting an Item[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#reporting-an-item "Direct link to Reporting an Item") We encourage our community members to resolve disputes amicably whenever possible. For details on contacting creators, reporting items, rating items, and filing a DMCA notice, please visit the following page. [Submitting Feedback for Items](/marketplace/submit-feedback.md) ### Responding to Item Reports[​](/marketplace/creators-hub/legal-guidelines-for-creators.md#responding-to-item-reports "Direct link to Responding to Item Reports") When you receive a report about an item you've listed on FlutterFlow Marketplace, it's important to respond promptly and thoughtfully. We've put together a breakdown of our process for responding to DMCA and other infringements here: [Copyright (DMCA) Process](/marketplace/creators-hub/copyright-dmca-process.md) Our goal is to foster a creative, respectful, and lawful environment for all users. By following these steps, you help us achieve this goal and ensure FlutterFlow remains a platform where innovation thrives within the bounds of respect and legality. For further assistance or questions, please contact . --- # Navigating External Licenses We know navigating the world of licenses and copyright rules can be a bit daunting, so we’ve put together this guide to help simplify things for you. Here, we’ll cover some of the most common licensing terms you’ll encounter and explain key concepts that will assist you in creating unique and compliant content for FlutterFlow Marketplace. Keep in mind that while we aim to highlight the most prominent licenses, it’s crucial to always check the most recent and applicable license terms for any assets you plan to use. If you’re ever unsure, please consult the relevant content provider or original author. ### Key Terms[​](/marketplace/creators-hub/navigating-external-licenses.md#key-terms "Direct link to Key Terms") **"Derivative Work"** The term **Derivative Work** refers to any new creation that incorporates a copyrighted asset in a form that is still closely related to the original. Most licenses, especially in digital marketplaces, prohibit using assets to create derivative works that are then sold as standalone products or used as base templates in a marketplace. This means you cannot take a design template, make minor adjustments, and resell it as your own template. The creation of derivative works generally requires significant transformation of the original asset, ensuring the final product is distinct enough to not merely be a slight variation of the original. **"End Product"** Some licenses may permit “unlimited end products”. However, it's important to understand that this typically refers to the ability to create multiple final projects using the original asset as long as each project remains within the terms set out by the original license. An **End Product** is a final, functional, and complete creation that is built from the initial resources but is substantially different from them. In the context of app development, an end product is typically the final app delivered to users, *not an app template* intended for further development or resale. ### License Examples[​](/marketplace/creators-hub/navigating-external-licenses.md#license-examples "Direct link to License Examples") As seen from the licensing terms of platforms like UI8, Envato, Creative Market, and Canva, there are clear restrictions against using their items directly or in slightly modified forms as base materials for creating new FlutterFlow Marketplace items. This practice could violate copyright laws and the specific licensing agreements set by these platforms. * UI8 * Envato * Creative Market * Canva **License Terms:** **Relevant Products:** [All Access Pass](https://ui8.net/products/all-access-pass) (Basic, Elite, and Lifetime) **Excerpt** *(as of April 22, 2024)* > \[You cannot] make a theme, template or derivative work of any product to sell on any marketplace. **hi** **Legal Contact:** *** **Result:** ❌ Not allowed to use in creating FlutterFlow Marketplace template **License Terms:** and **Relevant Products:** [Envato Elements subscription](https://elements.envato.com/pricing) Also, individual items sold on: * CodeCanyon: * ThemeForest: * VideoHive: * AudioJungle: * GraphicRiver: * PhotoDune: * 3DOcean: **Excerpts** *(as of April 22, 2024)* > You can’t re-distribute the Item as stock, in a tool or template, or with source files. You can’t do this with an Item either on its own or bundled with other items, and even if you modify the Item. You can’t re-distribute or make available the Item as-is or with superficial modifications. > You can’t use the Item in any application allowing an end user to customise a digital or physical product to their specific needs, such as an “on demand”, “made to order” or “build it yourself” application. You can use the Item in this way only if you purchase a separate license for each final product incorporating the Item that is created using the application. **Legal Contact:** *** **Result:** ❌ Not allowed to use in creating FlutterFlow Marketplace template **License Terms:** **Relevant Products:** [Membership](https://creativemarket.com/membership) with Extended Commercial License **Excerpt** *(as of April 22, 2024)* > Resale or Sub-Licensing of the Licensed Asset or any modification of it in a way that is directly competitive with the original Licensed Asset is strictly prohibited (e.g., as a stock asset or template). **Legal Contact:** *** **Result:** ❌ Not allowed to use in creating FlutterFlow Marketplace template **License Terms:** **Relevant Products:** [Canva Pro](https://www.canva.com/pro/) (with Pro Content license) **Excerpt** *(as of April 22, 2024)* > Unless it’s a template created for use on Canva, you can’t use Pro content in templates of any nature. **Legal Contact:** *** **Result:** ❌ Not allowed to use in creating FlutterFlow Marketplace template ### Securing Explicit Permissions[​](/marketplace/creators-hub/navigating-external-licenses.md#securing-explicit-permissions "Direct link to Securing Explicit Permissions") In situations where standard licensing does not meet the specific needs of your project or where the terms seem restrictive, **obtaining explicit permission from the original content creators can be a viable solution.** This allows for flexibility and ensures that your use of the assets is legally sound. We encourage reaching out directly to copyright holders whenever you are considering uses that are not clearly allowed under the standard license terms. Documenting such permissions in writing is essential to avoid any future misunderstandings or legal disputes. --- # FlutterFlow Marketplace Review Dispute Guidelines At FlutterFlow Marketplace, we believe in transparent and honest feedback. Reviews are an essential part of helping buyers make informed decisions and helping creators improve their work. However, not all reviews are created equal. Sometimes feedback is based on misunderstandings, irrelevant factors, or issues unrelated to the quality of the item itself. This guideline outlines when and how we handle review disputes. ### Criteria for Removing a Review[​](/marketplace/creators-hub/review-dispute-guidelines.md#criteria-for-removing-a-review "Direct link to Criteria for Removing a Review") We may remove a review if it meets **one or more** of the following criteria: * **Spam or Abuse:** The review contains offensive language, harassment, or unrelated spam content. * **Misuse of the Review System:** The review is about unrelated topics (e.g., FlutterFlow features, pricing, unrelated bugs). * **Critical Misunderstanding:** The review is based on a clear misunderstanding of the item's purpose or scope, despite the listing being accurate and transparent. * **Irrelevant to the Current Version:** The review references issues that have since been resolved, and the creator has updated the item significantly. note We may remove outdated reviews in cases where leaving them would misrepresent the current product. ### Reviews That Meet Our Standards[​](/marketplace/creators-hub/review-dispute-guidelines.md#reviews-that-meet-our-standards "Direct link to Reviews That Meet Our Standards") We **will not remove** a review just because it is negative if it: * Represents a real user experience * Critiques the item's quality, usability, documentation, or performance in good faith * Highlights friction that future buyers may encounter, even if subjective ### How to Dispute a Review[​](/marketplace/creators-hub/review-dispute-guidelines.md#how-to-dispute-a-review "Direct link to How to Dispute a Review") If you believe a review on your item qualifies for removal: 1. **Contact Us:** Email . 2. **Include:** * A link to the item and review * A short explanation of why you believe it qualifies for removal Decision Our team will review each case individually and respond within **10 business days**. Please note we are actively working on better creator tools to allow creators to reply directly to reviews. --- # Item Submission Criteria ## Item Submission Standards[​](/marketplace/creators-hub/submission-criteria.md#item-submission-standards "Direct link to Item Submission Standards") Below, you'll find the criteria our Submission Review Team uses to review items submitted to FlutterFlow Marketplace. ### 1. Originality and Ownership[​](/marketplace/creators-hub/submission-criteria.md#1-originality-and-ownership "Direct link to 1. Originality and Ownership") #### 1.1 Project Ownership[​](/marketplace/creators-hub/submission-criteria.md#11-project-ownership "Direct link to 1.1 Project Ownership") * **Criteria:** You must own the rights to the project you submit. * **Why it Matters:** Only the original creator has the right to share and potentially sell their work. This ensures fairness, prevents unauthorized distribution, and protects intellectual property. * **What To Do:** * **If you're the sole creator:** Great! Make sure you're submitting the project from your own FlutterFlow account. * **If you're collaborating:** The project owner should be the one to submit it to Marketplace. Discuss this with your collaborators beforehand. * **If you've acquired a project:** Ensure the original creator has officially [transferred ownership](/resources/projects/collaboration.md#transferring-project) rights to you. This may involve legal documentation, so it's important to handle it properly. #### 1.2 Significant Edits Made[​](/marketplace/creators-hub/submission-criteria.md#12-significant-edits-made "Direct link to 1.2 Significant Edits Made") * **Criteria:** Projects must demonstrate a substantial amount of original work and editing. * **Why It Matters:** The Marketplace thrives on innovation and creativity. Minor cosmetic tweaks to existing projects don't offer the same value as fundamentally unique creations or heavily modified versions showcasing your distinct design and development skills. * **What To Do:** * **Highlight your modifications:** Clearly demonstrate the unique components, functionalities, or design choices you've implemented. * **Go beyond superficial changes:** If your modifications are primarily visual (e.g., color swaps, logo replacements), consider adding more substantive improvements. #### 1.3 Not Based on an Existing Marketplace Item[​](/marketplace/creators-hub/submission-criteria.md#13-not-based-on-an-existing-marketplace-item "Direct link to 1.3 Not Based on an Existing Marketplace Item") * **Criteria:** Projects must not be direct derivatives of existing Marketplace items. * **Why It Matters:** Originality is key! Duplicating existing offerings diminishes the diversity and value of Marketplace. We want to empower users with a wide range of unique choices. * **What To Do:** * **Draw inspiration, don't duplicate:** While you can certainly learn from existing projects, aim to differentiate yours significantly. * **Add your own flavor:** Infuse your unique style, features, or functionalities to make the project distinctively yours. #### 1.4 Not Based on a Sample App[​](/marketplace/creators-hub/submission-criteria.md#14-not-based-on-a-sample-app "Direct link to 1.4 Not Based on a Sample App") * **Criteria:** Submissions should not be minimally modified versions of FlutterFlow's provided sample apps. * **Why It Matters:** Sample apps are fantastic learning tools, but Marketplace items should showcase a higher level of complexity and original thought. * **What To Do:** * **Use sample apps as a foundation:** Treat them as a starting point. Experiment, expand, and transform them into something new. * **Demonstrate advanced skills:** Go beyond basic layouts and features; integrate custom code, complex animations, or add helpful API calls. #### 1.5 Original Project Content[​](/marketplace/creators-hub/submission-criteria.md#15-original-project-content "Direct link to 1.5 Original Project Content") * **Criteria:** All project content—text, images, designs—must be original or appropriately licensed for commercial use. Please see [Legal Guidelines for Creators](/marketplace/creators-hub/legal-guidelines-for-creators.md) and [Navigating External Licenses](/marketplace/creators-hub/navigating-external-licenses.md) for more info. * **Why It Matters:** Using copyrighted material without permission can lead to legal issues and undermines the professional nature of Marketplace. * **What To Do:** * **Create your own assets:** This is the best way to ensure originality. * **Use royalty-free resources and properly licensed code:** Several websites offer high-quality, free-to-use assets. See also our guidance on [Open Source Licenses](/marketplace/creators-hub/legal-guidelines-for-creators.md#open-source-licenses). * **Purchase commercial licenses:** If you choose to use paid assets, secure the appropriate license for commercial distribution. This can be really tricky, so please review [Licenses from Other Marketplaces](/marketplace/creators-hub/legal-guidelines-for-creators.md#open-source-licenses) and [Navigating External Licenses](/marketplace/creators-hub/navigating-external-licenses.md). #### 1.6 No Library Dependencies (Libraries Only)[​](/marketplace/creators-hub/submission-criteria.md#16-no-library-dependencies-libraries-only "Direct link to 1.6 No Library Dependencies (Libraries Only)") * **Criteria:** Libraries cannot depend on other libraries. * **Why It Matters:** Dependencies between libraries create complexity in permissions management and version control, potentially leading to compatibility issues or broken functionality. * **What To Do:** * **Build Self-Contained:** Ensure your library contains all necessary functionality without requiring other libraries (from Marketplace or personal libraries). info When you publish a free item to Marketplace, you agree to license it under the [MIT License](https://opensource.org/licenses/MIT), which grants users perpetual rights to use, modify, and distribute the project. Paid items are subject to the license terms specified in our [Marketplace Terms of Service](https://www.flutterflow.io/tos-marketplace). While creators may remove their items from Marketplace at any time, this does not affect the rights of users who obtained the item while it was published - they retain their license rights according to the terms that were in effect when they acquired the item. Please review our [Legal Guidelines for Creators](/marketplace/creators-hub/legal-guidelines-for-creators.md) for more details about licensing and intellectual property rights. ### 2. Metadata[​](/marketplace/creators-hub/submission-criteria.md#2-metadata "Direct link to 2. Metadata") Clear, engaging, and accurate metadata helps users discover and understand the value of your project. #### 2.1 Submission in English[​](/marketplace/creators-hub/submission-criteria.md#21-submission-in-english "Direct link to 2.1 Submission in English") * **Criteria:** All item metadata (title, description, tags, etc.) must be in English. * **Why it Matters:** To ensure a broad understanding among our global community, all Marketplace items must be in English. * **What To Do:** Use clear, concise English throughout your submission. If English isn't your first language, consider using FlutterFlow's automatic [translation](/concepts/localization.md) feature. #### 2.2 Professional Title[​](/marketplace/creators-hub/submission-criteria.md#22-professional-title "Direct link to 2.2 Professional Title") * **Criteria:** Your project title should be clear, concise, and free of grammatical errors. * **Why it Matters:** A strong title grabs attention and communicates the essence of your project at a glance. * **What To Do:** * **Keep it brief and impactful:** Aim for a title that's easy to remember and accurately reflects the core purpose of your project. * **Use relevant keywords:** This helps users find your project when searching on Marketplace. * **Proofread carefully:** Typos and grammatical errors create a negative first impression. #### 2.3 Unique Title[​](/marketplace/creators-hub/submission-criteria.md#23-unique-title "Direct link to 2.3 Unique Title") * **Criteria:** Your title should be distinct from other items in Marketplace. * **Why it Matters:** A unique title helps your project stand out and prevents confusion among users. * **What To Do:** * **Research existing titles:** Before settling on a title, do a quick search to make sure it isn't already in use. * **Get creative with wording:** If you find similar titles, brainstorm alternative phrases or keywords that accurately describe your project's unique selling points. #### 2.4 Professional Description[​](/marketplace/creators-hub/submission-criteria.md#24-professional-description "Direct link to 2.4 Professional Description") * **Criteria:** Your project description should be well-written, engaging, and free of grammatical errors. * **Why it Matters:** The description provides users with a deeper understanding of your project's features, benefits, and intended use cases. * **What To Do:** * **Start with a strong opening:** Capture attention from the start and clearly state what your project does. * **Highlight key features:** Use bullet points or formatting to make it easy to scan for important information. * **Focus on benefits:** Explain *why* someone would want to use your item – what problems does it solve or what opportunities does it unlock? * **Proofread meticulously:** Errors in grammar and spelling can make your project seem unprofessional. warning While tools like ChatGPT can assist in drafting content, they often generate generic text that might not fully capture the unique aspects of your project or could sound overly promotional and insincere. Always personalize and proofread AI-generated content to ensure it aligns with your item's features and capabilities. #### 2.5 Accurate Description[​](/marketplace/creators-hub/submission-criteria.md#25-accurate-description "Direct link to 2.5 Accurate Description") * **Criteria:** The description should accurately reflect the project's functionality and avoid exaggerating its capabilities. * **Why it Matters:** Misleading descriptions lead to negative user experiences. Transparency builds trust within Marketplace. * **What To Do:** * **Be truthful and transparent:** Clearly state what your project can and cannot do. * **Avoid hype and jargon:** Focus on clear, concise language that everyone can understand. Do not overpromise. #### 2.6 Third-Party Service Information[​](/marketplace/creators-hub/submission-criteria.md#26-third-party-service-information "Direct link to 2.6 Third-Party Service Information") * **Criteria:** If your project relies on any external services or APIs, you must disclose this information in the description. * **Why it Matters:** Transparency about potential additional costs or dependencies ensures users have all the information needed to make an informed decision before purchasing or cloning an item. * **What To Do:** * **List all external services:** Include the name of the service, its purpose within your project, and whether it requires a paid subscription or API key. * **Provide links (if applicable):** Direct users to relevant documentation or pricing pages for the third-party service. #### 2.7 Professional Instructions[​](/marketplace/creators-hub/submission-criteria.md#27-professional-instructions "Direct link to 2.7 Professional Instructions") * **Criteria:** Instructions and documentation should be clear, easy to follow, and written in a professional tone. * **Why it Matters:** Well-written instructions ensure a smooth setup and implementation experience for users, increasing customer satisfaction. * **What To Do:** * **Assume no prior knowledge:** Write for someone who's completely new to your project and FlutterFlow. * **Use numbered steps:** Break down complex processes into manageable, actionable steps. * **Include video links:** Use the documentation URL to point users to a visual video walkthrough. Alternatively, point users to a Google Doc or similar written documentation for your item. * **Test your instructions:** Have someone else follow your instructions to identify any points of confusion. #### 2.8 Accurate Tags[​](/marketplace/creators-hub/submission-criteria.md#28-accurate-tags "Direct link to 2.8 Accurate Tags") * **Criteria:** Use relevant tags that accurately describe your project's category, features, and functionality. * **Why it Matters:** Tags play a crucial role in helping users discover your project through Marketplace search. * **What To Do:** * **Think like a user:** What keywords would someone use to search for a project like yours? * **Use a mix of broad and specific tags:** For example, use general tags like "e-commerce" or "social media" along with more specific ones like "shopping cart" or "user authentication". * **Don't use irrelevant tags:** This only makes it harder for users to find what they're looking for. #### 2.9 High-Quality Images[​](/marketplace/creators-hub/submission-criteria.md#29-high-quality-images "Direct link to 2.9 High-Quality Images") * **Criteria:** Images should be visually appealing, high-resolution, and representative of the project's design and functionality. Cover images should be at least 1200 x 800 pixels and in 1.5 aspect ratio. * **Why it Matters:** Images are the first thing users see – make a great visual impression! * **What To Do:** * **Showcase key screens and features:** Select images that highlight the most visually impressive and important aspects of your project. * **Use high-resolution images:** Avoid blurry or pixelated images. * **Maintain a consistent style:** Use similar image dimensions and visual treatments to create a cohesive look. tip Use FlutterFlow's [**screenshot generator**](/deployment/pre-checks-before-publishing.md#generate-screenshots) along with services like [**Shots.so**](https://shots.so/) to create beautiful cover images. #### 2.10 Image Representativeness[​](/marketplace/creators-hub/submission-criteria.md#210-image-representativeness "Direct link to 2.10 Image Representativeness") * **Criteria:** Images must accurately reflect the actual content and functionality of your project. * **Why it Matters:** Misleading images create a negative experience for users and erode trust in Marketplace. * **What To Do:** * **Use genuine screenshots or recordings:** Avoid showcasing designs or features that are not actually present in your project. * **Use abstract images sparingly:** While a certain level of abstraction or illustration can be effective for concepts that are hard to capture with screenshots, they should be used judiciously. Prefer to showcase actual product screenshots in your gallery images. #### 2.11 No FlutterFlow Logo in Images[​](/marketplace/creators-hub/submission-criteria.md#211-no-flutterflow-logo-in-images "Direct link to 2.11 No FlutterFlow Logo in Images") * **Criteria:** Do not include the FlutterFlow logo in your cover photos. * **Why it Matters:** Using the FlutterFlow logo might suggest an official endorsement or the appearance of an official template, neither of which may be accurate. Additionally, including the logo is redundant, as all items are exclusively offered through the FlutterFlow Marketplace. * **What To Do:** Remove any references to the FlutterFlow logo in your images. ### 3. Aesthetics & Design[​](/marketplace/creators-hub/submission-criteria.md#3-aesthetics--design "Direct link to 3. Aesthetics & Design") First impressions matter! We're looking for projects that go beyond basic functionality and demonstrate a strong understanding of visual design principles. #### 3.1 Design Standard[​](/marketplace/creators-hub/submission-criteria.md#31-design-standard "Direct link to 3.1 Design Standard") * **Criteria:** Projects should adhere to high standards of visual design, incorporating principles of usability, accessibility, and aesthetics. * **Why it Matters:** A well-designed app is not just visually appealing; it's intuitive, easy to navigate, and provides a positive user experience. * **What To Do:** * **Prioritize usability:** Make sure your design choices support, rather than hinder, the core functionality of your app. * **Consider visual hierarchy:** Guide the user's eye with clear visual cues – size, color, contrast, and spacing can all be used effectively. * **Maintain consistency:** Aim to use a theme colors and typography throughout your project, as well as consistent padding, list spacing, border radii, and navigation elements. * **Test with real users:** Get feedback from others to identify any areas of your design that are confusing or frustrating to use. #### 3.2 Screen Size Compatibility (Responsiveness)[​](/marketplace/creators-hub/submission-criteria.md#32-screen-size-compatibility-responsiveness "Direct link to 3.2 Screen Size Compatibility (Responsiveness)") * **Criteria:** Projects should be designed to adapt seamlessly to various screen sizes. * **Why it Matters:** Users expect Flutter apps to scale appropriately across a wide range of devices, from small smartphones to large desktop monitors. A responsive design ensures a positive user experience across the board. * **What To Do:** * **Follow responsive design best practices:** Use `Wrap`, Responsive Visibility, and Flex features to ensure your app can scale across devices. Read more about building responsively in [Responsive Layouts: 101](/concepts/layouts/responsive.md). * **Test on different devices:** Use FlutterFlow's different virtual devices in Test and Run Modes to test your project on a variety of screen sizes. Experiment with the canvas size in the builder to check how your designs scale. ### 4. Test Experience[​](/marketplace/creators-hub/submission-criteria.md#4-test-experience "Direct link to 4. Test Experience") A seamless and positive test experience is crucial for users to evaluate your FlutterFlow item before purchasing or cloning. This section focuses on ensuring your submission is functional, accessible, and easy to explore. #### 4.1 Functional Run Mode Link[​](/marketplace/creators-hub/submission-criteria.md#41-functional-run-mode-link "Direct link to 4.1 Functional Run Mode Link") * **Criteria:** The provided Run Mode link must be active and correctly load a working demo of your project. For mobile-only features or utility libraries that cannot be demonstrated in Run Mode's web environment, you must provide alternative demonstration methods. * **Why it Matters:** The Run Mode link is the primary way users can interact with your project before purchasing. A broken, inaccessible, or non-demonstrative link creates a significant barrier to understanding the item's value. * **What To Do:** * **For Standard Web-Compatible Items:** * Double-check your link before submitting to confirm it showcases the experience you want potential buyers to have. * Test the link multiple times to ensure consistent functionality. * **For Mobile-Only Features:** * Create a dedicated demonstration page in your project that explains the mobile-only functionality. * Include screenshots, videos, or mockups showing how the feature works on mobile devices. * Clearly indicate which features are mobile-only and why they cannot be demonstrated in Run Mode. * Optionally, provide a published FlutterFlow web deploy link that can be used instead of the Run Mode URL. * **For Utility Libraries (e.g., Analytics, Background Services):** * Create a demonstration page that explains the library's functionality. * Show configuration options and expected outcomes. * Include visual aids like flowcharts or diagrams to explain the library's operation. * Provide example code or configuration snippets. * Consider adding debug/test outputs that demonstrate the library is working. * **Documentation:** * Regardless of the type of item, ensure your documentation clearly explains how to implement and test the functionality in a real mobile environment. * Include troubleshooting guides and common implementation scenarios. tip For items that cannot be fully demonstrated in Run Mode, focus on creating a clear, informative demonstration page that helps users understand the value and implementation of your item. Visual aids, clear explanations, and comprehensive documentation are key to helping users make informed decisions. #### 4.2 User Sign-In (Anonymous Auth)[​](/marketplace/creators-hub/submission-criteria.md#42-user-sign-in-anonymous-auth "Direct link to 4.2 User Sign-In (Anonymous Auth)") * **Criteria:** Users should be able to explore the core functionality of your item *without* being required to create an account or log in. * **Why It Matters:** Requiring upfront authentication creates friction for users who simply want to try before they buy. Additionally, forcing users to provide personal information could cause privacy issues. Anonymous authentication allows for immediate exploration. * **What To Do:** * **Provide pre-filled demo credentials:** If your demo relies heavily on user-specific data, consider creating a demo account with pre-populated sample data accessible to guest users. Pre-fill the username and password on the sign in screen so that users can easily begin exploring your item. * **Implement anonymous authentication:** FlutterFlow supports easy integration with Firebase for [anonymous sign-in](/integrations/authentication/firebase/anonymous-login.md). This allows users to access your project's demo mode without creating an account. * **Remove authentication:** Another option is to remove the need for any authentication altogether. This will enable users to start exploring your item immediately without any barriers. #### 4.3 Accessible Navigation[​](/marketplace/creators-hub/submission-criteria.md#43-accessible-navigation "Direct link to 4.3 Accessible Navigation") * **Criteria:** All pages and sections within your project should be easily navigable and accessible. * **Why it Matters:** A confusing or broken navigation flow creates a frustrating user experience. Users should be able to intuitively explore all aspects of your project. * **What To Do:** * **Configure the Initial Page Properly**: In Settings > App Details, the 'Entry Page' determines the starting point for Run Mode links and the initial page users will see when they enter your app. Make sure this is set to the most logical and welcoming page to ensure a smooth user entry and navigation experience. * **Review your project's [Storyboard](/flutterflow-ui/storyboard.md) view:** This view displays the navigation across various pages and can help highlight any gaps. Please note that any page which is not accessible will not be shown. * **Test navigation thoroughly:** Click through *every* button, link, and menu item in Run Mode to make sure they lead to the correct destinations. #### 4.4 Functional Template[​](/marketplace/creators-hub/submission-criteria.md#44-functional-template "Direct link to 4.4 Functional Template") * **Criteria:** All core features and functionalities within your project must be working correctly. * **Why it Matters:** Broken features or functionalities lead to a negative user experience and give the impression of a rushed or incomplete project. * **What To Do:** * **Rigorous testing is essential:** Test *every* aspect of your project – from button clicks and form submissions to API calls and animations. * **Emulate real-world scenarios:** Don't just test with ideal data or happy paths. Introduce potential edge cases or user errors to see how your project handles them. * **Get fresh eyes on it:** Ask someone unfamiliar with your project to test it and provide feedback. ### 5. Build Quality[​](/marketplace/creators-hub/submission-criteria.md#5-build-quality "Direct link to 5. Build Quality") Building a solid app template goes beyond surface-level design. It's about creating a robust, efficient, and user-friendly project that can scale and makes efficient use of components. This section focuses on technical excellence and attention to detail. #### 5.1 Error-Free Functionality[​](/marketplace/creators-hub/submission-criteria.md#51-error-free-functionality "Direct link to 5.1 Error-Free Functionality") * **Criteria:** Projects should be free of runtime errors, crashes, and unexpected behaviors. * **Why it Matters:** Errors and crashes create a frustrating user experience and can damage the reputation of your project. * **What To Do:** * **Review project errors and optimizations:** Do not submit projects with any errors, and attempt to address most optimization suggestions in the top bar. * **Use FlutterFlow's debugging tools:** Take advantage of FlutterFlow's built-in debugging panel to identify and resolve issues. * **Handle nulls and errors gracefully:** Add default values for variables in case their value is ever `null`. Implement conditionals in action chains to respond appropriately to API errors or other cases when something goes wrong. #### 5.2 No Pixel Overflow[​](/marketplace/creators-hub/submission-criteria.md#52-no-pixel-overflow "Direct link to 5.2 No Pixel Overflow") * **Criteria:** Ensure your UI elements are positioned and sized correctly to avoid content overflowing its container, leading to visual glitches / cut off content. * **Why It Matters:** Pixel overflows are a sign of UI inconsistencies that can negatively impact the user experience, especially on different screen sizes. Pixel overflow issues can occur in Test Mode when there's a hardcoded pixel value and not enough space on the screen to render that exact value. * **What To Do:** * **Preview pixel overflows:** Toggle the pixel overflow icon in the top-right of the canvas to see if there are any overflow issues. * **Leverage FlutterFlow's layout tools:** Use Expanded and Flex values to help prevent layout issues. Make `Columns` or `Rows` scrollable to prevent overflows. Use auto-sizing text or text clipping where it makes sense. Remove hard-coded width and height where it makes sense. * **Test on different screen sizes:** Resize the canvas while building to preview any potential issues. #### 5.3 Error-Free Custom Code[​](/marketplace/creators-hub/submission-criteria.md#53-error-free-custom-code "Direct link to 5.3 Error-Free Custom Code") * **Criteria:** Any custom code integrated into your project (using Custom Functions, Actions, and Widgets) must be free of syntax errors, logical errors, and potential security vulnerabilities. * **Why it Matters:** Errors in custom code can lead to app instability, crashes, or even security risks. * **What To Do:** * **Write clean, well-documented code:** This makes it easier to debug and maintain your project. * **Test custom code thoroughly:** Isolate and test your custom code units (functions, actions) to ensure they work as expected. * **Use FlutterFlow's code validation:** Pay close attention to any warnings or errors highlighted by FlutterFlow's built-in code validation. #### 5.4 Coherent & Relevant Custom Code[​](/marketplace/creators-hub/submission-criteria.md#54-coherent--relevant-custom-code "Direct link to 5.4 Coherent & Relevant Custom Code") * **Criteria:** Custom code should be purposefully integrated and enhance your project's functionality in a meaningful way. Avoid unnecessary or redundant code. Avoid including unused or irrelevant custom code. * **Why it Matters:** While custom code offers flexibility, excessive or poorly integrated code can make your project harder to understand, maintain, and update in the future. * **What To Do:** * **Plan your custom code strategically:** Determine if FlutterFlow's built-in features can achieve the desired functionality before resorting to custom code. * **Comment your code effectively:** Explain the purpose and logic behind your custom code to improve readability and maintainability. * **Keep it modular:** Break down complex logic into smaller, reusable functions or actions. Prefer code blocks when a Custom Function is relatively short and will only be used once. #### 5.5 Testable Custom Code in Run Mode[​](/marketplace/creators-hub/submission-criteria.md#55-testable-custom-code-in-run-mode "Direct link to 5.5 Testable Custom Code in Run Mode") * **Criteria:** Ensure that the functionality implemented using custom code is accessible and verifiable within the Run Mode demo. * **Why It Matters:** Users should be able to experience the full impact of your custom code item within the Run Mode environment. * **What To Do:** * **Add a page that uses your custom code:** This page should ideally expose the ideal use case of your custom code or perhaps allow users to set and control parameter values in Run Mode. * **Provide clear instructions:** If special steps are required for users to test certain custom code functionalities in Run Mode, explain these instructions clearly within your project description or documentation. #### 5.6 Proper Firestore Rules (If Applicable)[​](/marketplace/creators-hub/submission-criteria.md#56-proper-firestore-rules-if-applicable "Direct link to 5.6 Proper Firestore Rules (If Applicable)") * **Criteria:** If your project utilizes Firestore as a database, ensure your Firestore Security Rules are correctly configured in Settings to protect user data and prevent unauthorized access. * **Why it Matters:** Improperly configured Firestore Rules can expose sensitive user data or create security vulnerabilities within users' apps. * **What To Do:** * **Implement Firestore Security Rules:** Familiarize yourself with how FlutterFlow exposes [Firestore rules](/integrations/database/cloud-firestore/firestore-rules.md) and make the necessary modifications in your base project. * **Test your rules thoroughly:** Create test accounts and attempt add, update, and delete operations on your data across different authentication states to verify your rules are working as intended. #### 5.7 Spelling and Grammar[​](/marketplace/creators-hub/submission-criteria.md#57-spelling-and-grammar "Direct link to 5.7 Spelling and Grammar") * **Criteria:** Maintain a professional tone with correct spelling and grammar throughout your project's UI text, descriptions, and documentation. * **Why it Matters:** Even small typos can detract from your project's credibility and create a negative user experience. * **What To Do:** * **Proofread, proofread, proofread:** Carefully review all text elements within your project. Ask a friend or colleague to review your text for errors. #### 5.8 User-Friendly Template[​](/marketplace/creators-hub/submission-criteria.md#58-user-friendly-template "Direct link to 5.8 User-Friendly Template") * **Criteria:** Your template should empower users to build upon it easily and intuitively, regardless of their FlutterFlow expertise. * **Why It Matters:** A user-friendly template increases its value and appeal. When users can quickly understand and customize your template, they're more likely to choose it, leading to greater success for your Marketplace item. * **What To Do:** * **Embrace reusable components:** Design your template with modularity in mind. Create reusable components that users can easily modify for their use case. * **Clear and concise naming conventions:** Use descriptive names for widgets, variables, and functions to make your template's structure understandable at a glance. * **Logical organization:** Structure your template's layout in a clear and logical manner, grouping related elements and using comments to guide users. * **Documentation is key:** Provide clear and comprehensive documentation that guides users on how to use and customize your template effectively. Include explanations of key features, customization options, and potential use cases. * **Test with diverse users:** Get feedback from users with varying levels of FlutterFlow experience. This helps identify potential pain points or areas where your template could be more user-friendly. #### 5.9 Appropriate State Management[​](/marketplace/creators-hub/submission-criteria.md#59-appropriate-state-management "Direct link to 5.9 Appropriate State Management") * **Criteria:** Implement state management effectively to ensure data is updated and reflected correctly across your application. * **Why it Matters:** Proper state management is crucial for building responsive and dynamic Flutter apps. It helps prevent data inconsistencies, improves performance, and makes your code easier to maintain. * **What To Do:** * **Choose the right state management scope:** FlutterFlow supports (1) [App State](/resources/data-representation/app-state.md) (2) [Page State](/resources/ui/pages/page-lifecycle.md#page-state) and (3) [Component State](/resources/ui/components/component-lifecycle.md#creating-a-component-state) variables. Familiarize yourself with these options and scope any state variables to where they are needed. For instance, do not use App State to control the value of a checkbox within a component. * **Rebuild efficiently:** Ensure changes to state rebuild only the necessary scope for efficiency. #### 5.10 Organized Widget Tree[​](/marketplace/creators-hub/submission-criteria.md#510-organized-widget-tree "Direct link to 5.10 Organized Widget Tree") * **Criteria:** Maintain a well-structured and organized widget tree within your FlutterFlow project. * **Why It Matters:** A clean and organized widget tree makes your project more understandable, maintainable, and less prone to errors. It also makes it easier for others to collaborate on your project. * **What To Do:** * **Use descriptive names for widgets and variables:** Make your code self-documenting by using clear and meaningful names for major nodes. * **Avoid deeply nested widgets:** If your widget tree becomes too deeply nested (>10 levels), consider breaking it down into smaller, reusable [components](/resources/ui/components.md). #### 5.11 Follow FlutterFlow Best Practices[​](/marketplace/creators-hub/submission-criteria.md#511-follow-flutterflow-best-practices "Direct link to 5.11 Follow FlutterFlow Best Practices") * **Criteria:** Adhere to recommended best practices and guidelines for building apps with FlutterFlow. * **Why It Matters:** Following best practices can help you avoid common pitfalls, improve the performance of your app, and ensure your project is compatible with future updates to FlutterFlow. * **What To Do:** * **Stay up-to-date:** Keep an eye on FlutterFlow's official documentation, blog posts, and community forums for the latest tips, tricks, and best practices. info Stay tuned for an upcoming "style guide" we're publishing that goes into deeper detail about best practices for building in FlutterFlow. #### 5.12 Limit Static Images[​](/marketplace/creators-hub/submission-criteria.md#512-limit-static-images "Direct link to 5.12 Limit Static Images") * **Criteria**: Minimize the use of large, unoptimized static images within your project to prevent app bloat and ensure that your template accurately represents the functionality of your app. * **Why It Matters**: Overusing large static images not only increases the download size and slows down performance, particularly on slower networks, but also risks misleading users. For example, using an image of a map or a credit card form, rather than building these elements, can give the false impression that your app includes functionalities that are merely visual mockups. This can disappoint users when they discover these components are non-interactive. * **What To Do**: * **Build Functional Components**: Wherever possible, replace static images with functional elements built using FlutterFlow. This ensures your app remains scalable and interactive, providing a genuine user experience across all device sizes and orientations. * **Use optimized images**: Reduce image file size using online compression tools, which maintain quality while decreasing load times. * **Leverage caching**: Implement image caching for network images to minimize repeated downloads of the same images, which enhances performance. #### 5.13 Limit Custom Code (When Possible)[​](/marketplace/creators-hub/submission-criteria.md#513-limit-custom-code-when-possible "Direct link to 5.13 Limit Custom Code (When Possible)") * **Criteria:** While custom code is powerful, strive to achieve as much functionality as possible using FlutterFlow's visual builder and built-in features. * **Why It Matters:** Over-reliance on custom code can make your project less maintainable, less user-friendly, and potentially more prone to errors. * **What To Do:** * **Explore FlutterFlow's capabilities:** Familiarize yourself with FlutterFlow's extensive library of pre-built widgets, actions, and integrations to see if they can fulfill your requirements before resorting to custom code. #### 5.14 Efficient Component Use & Avoiding Duplication[​](/marketplace/creators-hub/submission-criteria.md#514-efficient-component-use--avoiding-duplication "Direct link to 5.14 Efficient Component Use & Avoiding Duplication") * **Criteria:** Projects should demonstrate efficient use of FlutterFlow's components. Avoid unnecessary duplication of pages, widgets, or actions. Strive to create reusable components and implement action blocks in a scalable and maintainable way. * **Why it Matters:** Duplicating large sections of code or entire pages with only minor changes bloats the project size, reduces maintainability, and can mislead users about the project's complexity and value. * **What To Do:** * **Leverage Components:** Create reusable components for elements that repeat throughout your project (e.g., product cards, list items, headers, footers). * **Utilize Parameters:** Pass data and customize component instances using parameters instead of duplicating and hardcoding values. * **Review for Redundancies:** Before submitting, carefully examine your project for any unnecessarily duplicated pages, widgets, or action chains that could be consolidated or streamlined. #### 5.15 Library Values Implementation (Libraries Only)[​](/marketplace/creators-hub/submission-criteria.md#515-library-values-implementation-libraries-only "Direct link to 5.15 Library Values Implementation (Libraries Only)") * **Criteria:** Libraries must use [Library Values](/resources/projects/libraries.md) for sensitive keys and customizable elements that users need to configure. * **Why It Matters:** Library Values allow users to safely provide their own API keys and customize critical configuration without modifying the library's core functionality. This improves security and makes libraries more flexible and reusable. * **What To Do:** * **Identify Configurable Elements:** Review your library for any API keys, endpoints, or other values that users should be able to customize. * **Create Library Values:** Set up Library Values for these configurable elements in Settings > App Settings > Publish as Library. * **Document Requirements:** Clearly explain in your item description if any Library Values are required for your library to function correctly. * **Test Configuration:** Verify that your library functions correctly when Library Values are changed by users. #### 5.16 Automated Tests (Strongly Recommended)[​](/marketplace/creators-hub/submission-criteria.md#516-automated-tests-strongly-recommended "Direct link to 5.16 Automated Tests (Strongly Recommended)") * **Criteria:** Projects should include automated tests that verify core functionality and key user workflows. While not required for approval, this is strongly recommended for libraries and will positively impact visibility. * **Why It Matters:** Automated tests help ensure reliability, catch regressions, and demonstrate your commitment to quality. They also improve your item's visibility. * **What To Do:** * **Add Integration Tests:** Use FlutterFlow's [automated testing](/testing/automated-tests.md) features to verify your item's core functionality. * **Test Key Workflows:** Focus on testing critical user paths and features that users will rely on. * **For Libraries:** Since libraries are often used as building blocks in larger applications, thorough testing is particularly important to: * Verify that Library Values are properly implemented * Ensure core functionality works across different configurations * Demonstrate expected behavior to potential users * Catch issues before they affect downstream applications ### 6. Value (Paid Items)[​](/marketplace/creators-hub/submission-criteria.md#6-value-paid-items "Direct link to 6. Value (Paid Items)") A successful Marketplace item goes beyond just a functional app—it provides real value to users. #### 6.1 High Value Proposition[​](/marketplace/creators-hub/submission-criteria.md#61-high-value-proposition "Direct link to 6.1 High Value Proposition") * **Criteria:** Items should offer a compelling value proposition that justifies their price. * **Why It Matters:** Users are looking for solutions that save them time, effort, or resources, or that provide a unique experience they can't easily find elsewhere. * **What To Do:** * **Define Your Unique Value**: Identify and articulate what sets your project apart from others. Ensure it solves a specific problem in a way that is not readily available in Marketplace. * **Tag Appropriately**: Accurately categorize your item—whether it's a full app, UI kit, or library—to set the right expectations for potential users. * **Justify Your Pricing**: Make sure the pricing of your item reflects its true value and stands in fair comparison to similar offerings. Ensure it offers enough depth and uniqueness to warrant the minimum price point. * **For Paid Libraries**: Libraries should excel in at least one of these areas: * 🧘 Simplifying technical complexity (ease) * ⚡️ Enabling quick and seamless integrations (speed) * 🎛️ Offering diverse reusable components and features (quantity) * 🛠️ Providing robust, reliable functionality (quality) * 🙋‍♂️ Addressing specific, high-demand use cases with thoughtful solutions (relevance) ### 7. Legal & Security[​](/marketplace/creators-hub/submission-criteria.md#7-legal--security "Direct link to 7. Legal & Security") Building trust in the FlutterFlow Marketplace requires respecting legal boundaries and safeguarding user information. This section covers essential considerations to ensure your project adheres to ethical and legal standards. #### 7.1 Free of Inappropriate Content[​](/marketplace/creators-hub/submission-criteria.md#71-free-of-inappropriate-content "Direct link to 7.1 Free of Inappropriate Content") * **Criteria:** Projects must not contain any offensive, discriminatory, or illegal content. This includes, but is not limited to: * Hate speech or discrimination * Sexually explicit material or pornography * Content that promotes violence, illegal activities, or harm to others * **Why it Matters:** Maintaining a safe and inclusive community is paramount. Inappropriate content violates the [FlutterFlow Terms of Service](https://flutterflow.io/tos) and may have legal ramifications. * **What To Do:** * **Review your content carefully:** Ensure all text, images, and other assets align with community standards and legal guidelines. * **Err on the side of caution:** When in doubt, it's best to avoid potentially controversial content. #### 7.2 Free of Copyrighted Material[​](/marketplace/creators-hub/submission-criteria.md#72-free-of-copyrighted-material "Direct link to 7.2 Free of Copyrighted Material") * **Criteria:** Projects must not include any unauthorized use of copyrighted material, such as: * Images, illustrations, or graphics * Music or sound effects * Code snippets or libraries — see our docs on [Open Source Licenses](/marketplace/creators-hub/legal-guidelines-for-creators.md#open-source-licenses) for details * **Why it Matters:** Using copyrighted material without permission is a legal infringement and can result in serious legal consequences, including [DMCA takedown](/marketplace/creators-hub/copyright-dmca-process.md). * **What To Do:** Please review our [Legal Guidelines for Creators](/marketplace/creators-hub/legal-guidelines-for-creators.md) and [Navigating External Licenses](/marketplace/creators-hub/navigating-external-licenses.md) for more details. #### 7.3 Free of Trademarked Material[​](/marketplace/creators-hub/submission-criteria.md#73-free-of-trademarked-material "Direct link to 7.3 Free of Trademarked Material") * **Criteria:** Items must not misuse or infringe upon registered trademarks, including: * Brand names * Logos * Slogans * **Why it Matters:** Trademark infringement can lead to legal disputes and damage the reputation of FlutterFlow Marketplace. Please see our [Legal Guidelines for Creators](/marketplace/creators-hub/legal-guidelines-for-creators.md) for more details. * **What To Do:** Please review our [Legal Guidelines for Creators](/marketplace/creators-hub/legal-guidelines-for-creators.md) for more details. #### 7.4 Free of Confidential Data[​](/marketplace/creators-hub/submission-criteria.md#74-free-of-confidential-data "Direct link to 7.4 Free of Confidential Data") * **Criteria:** Projects should not expose any sensitive or confidential information, including: * API keys * User credentials * Personal data (e.g., names, addresses, financial information) * **Why it Matters:** Exposing confidential data can compromise the security of your project and put users at risk. * **What To Do:** * **Follow [API Key Best Practices](/best-practices/secure-api-keys.md):** Add restrictions, delete unnecessary API keys, and regularly rotate your keys to ensure keys are secured. * **Require users to provide their own API keys:** Use ephemeral, user-provided API keys in calls rather than hardcoding your own keys directly into code. * **Scrub your project before submission:** Double-check your project files and codebase to ensure no confidential information is accidentally included. ## Common Rejection Reasons[​](/marketplace/creators-hub/submission-criteria.md#common-rejection-reasons "Direct link to Common Rejection Reasons") To help streamline your submission process, here are some of the most frequent reasons projects are flagged: * [**Lack of Anonymous Authentication**](/marketplace/creators-hub/submission-criteria.md#42-user-sign-in-anonymous-auth): Make it easy for users to test your project without requiring logins. * [**Unclear Usage Instructions**](/marketplace/creators-hub/submission-criteria.md#27-professional-instructions): Provide detailed, step-by-step guidance on how to use and customize your template. * [**Image Issues**](/marketplace/creators-hub/submission-criteria.md#29-high-quality-images): Ensure images are high-resolution, sized appropriately, and don't include the FlutterFlow logo. * [**Poor Widget Tree Organization**](/marketplace/creators-hub/submission-criteria.md#510-organized-widget-tree): Utilize components and naming effectively to create a clean, well-structured project. * [**Use of Copyrighted Assets**](/marketplace/creators-hub/submission-criteria.md#72-free-of-copyrighted-material): Only include assets that you have created or have the legal right to use commercially. * [**Library Dependencies**](/marketplace/creators-hub/submission-criteria.md#515-library-values-implementation-libraries-only): Libraries cannot currently depend on other libraries from Marketplace. We're excited to see the amazing FlutterFlow projects you bring to Marketplace! By following these guidelines, you'll help us maintain a high-quality platform that benefits the entire FlutterFlow community. **Let's build something incredible together!** 🚀 --- # Submitting Item for Review All items submitted to the Marketplace are subject to a comprehensive review process prior to publication. While we have recently significantly improved review times, please note that the review period can take up to 30 days depending on the complexity and volume of submissions. Important: Review Submission Policies Please review our [**Submission Guidelines**](/marketplace/creators-hub/submission-criteria.md) and our [**Marketplace Terms of Service**](https://flutterflow.io/tos-marketplace) before submitting your item. It may also be helpful to review our [**Legal Guidelines for Creators**](/marketplace/creators-hub/legal-guidelines-for-creators.md), which explain your legal responsibility in plain language. ## How to Submit an Item[​](/marketplace/creators-hub/submit-item-for-review.md#how-to-submit-an-item "Direct link to How to Submit an Item") An item can be an entire project (in the case of Temlate Apps or Libraries), a page or a component (in the case of Template Page & Components) or a Custom Function, Action or Widget (in the case of Custom Code). ### 1. Set your project as a Marketplace project[​](/marketplace/creators-hub/submit-item-for-review.md#1-set-your-project-as-a-marketplace-project "Direct link to 1. Set your project as a Marketplace project") Marketplace items should belong to projects that are specifically made to publish Marketplace items (i.e., they should not be inside of a production project). In order to submit an item, it must be inside of a project that has been Set For Marketplace. A project that is set for Marketplace can not be deployed. To set a project for Marketplace: 1. Prerequisite: please enroll as a Marketplace creator first by setting up a profile in [Marketplace](https://marketplace.flutterflow.io/profile). You can optionally also apply to become a paid creator, which allows you to monetize your items. 2. Select the [**Share Icon**](/flutterflow-ui/toolbar.md#share-project) from the Toolbar (top right side of the screen). Please note that you must be the project owner to see this icon and to submit an item. 3. Select **Create New Item > Set For Marketplace > Yes** tip You can also clone an existing project and then set it as a Marketplace Project. ### 2. Fill out the submission form[​](/marketplace/creators-hub/submit-item-for-review.md#2-fill-out-the-submission-form "Direct link to 2. Fill out the submission form") Below is an overview of what is needed to create your Marketplace item: tip If you aren't ready to submit your item, select **Save As Draft** to continue editing your submission at a later time. #### Cover Photo[​](/marketplace/creators-hub/submit-item-for-review.md#cover-photo "Direct link to Cover Photo") The cover photo should be **1200x800 pixels** and help the users understand the purpose of the item. GIFs are allowed but should not be distracting, focus solely on the use and/or usability of the template, and be highly optimized to ensure a smooth load on the platform. Please do not include the FlutterFlow logo in your cover image. #### Gallery Photos (optional)[​](/marketplace/creators-hub/submit-item-for-review.md#gallery-photos-optional "Direct link to Gallery Photos (optional)") Include up to 4 additional photos that showcase your item's features. GIFs are allowed but should not be distracting, focus solely on the use and/or usability of the template, and be highly optimized to ensure a smooth load on the platform. Each should be should be **1200x800 pixels**. #### Name[​](/marketplace/creators-hub/submit-item-for-review.md#name "Direct link to Name") The item name should be professional, unique, and help the users understand the purpose of the item. Please use correct grammar and capitalization. #### Description[​](/marketplace/creators-hub/submit-item-for-review.md#description "Direct link to Description") The description should provide an overview of the key features, helping users determine if the item aligns with their requirements. If the item includes any third-party paid services or pub.dev packages/dependencies, those should also be mentioned in the description. Please use correct grammar and capitalization. #### Usage Instructions[​](/marketplace/creators-hub/submit-item-for-review.md#usage-instructions "Direct link to Usage Instructions") Provide clear and concise instructions on how to implement and utilize your item within FlutterFlow. Include any necessary steps, code snippets, or configurations required to get started. If your item depends on any third party services or pub.dev packages/dependencies, please provide full details of these including showing users where to find relevant API keys or more information. Please use correct grammar and capitalization. #### Marketplace Item Type[​](/marketplace/creators-hub/submit-item-for-review.md#marketplace-item-type "Direct link to Marketplace Item Type") Four types of items can be submitted: * Libraries * Template Apps * Template Page or Components * Custom Code - Libraries - Template App - Page or Component - Custom Code Libraries allow you to share resources like API endpoints, UI components, custom data types, custom code, action blocks and more with complete version control. To submit a Library to the Marketplace, first publish your project as a Library. Note that there are some limitations on Library projects - most notably there is currently no support for Firebase or Pages. For more details, see the [documentation on Libraries](/resources/projects/libraries.md). note *Libraries* can be monetized. The minimum price for Libraries is $50. Template apps contain multiple screens. There are 2 sub-types: * **Full App:** an app with authentication, complete navigation, multiple pages/flows, database schema, complete action trees, etc. * **UI Kit**: purely design-based templates and layouts note *Template Apps* can be monetized. The minimum price for Full Apps is $400 while the minimum for UI Kits is $50. Pages or Components are assembled modules that can be used within FlutterFlow. There are 2 sub-types: * **Page:** a single page in a FlutterFlow project * **Component:** a reusable UI element that can be integrated into any part of your application warning *Pages and Components* cannot be monetized at this time. Custom Code is Dart code that can be used within FlutterFlow projects. There are 3 sub-types: * **Custom Functions:** synchronous functions that do not have external dependencies. * **Custom Actions:** synchronous or asynchronous functions that may have external dependencies. If your action contains dependencies, please review our guide on [Open Source Licenses](/marketplace/creators-hub/legal-guidelines-for-creators.md). * **Custom Widgets:** user-defined Dart widgets that extend the capabilities of the standard FlutterFlow widget collection. If your widget contains dependencies, please review our guide on [Open Source Licenses](/marketplace/creators-hub/legal-guidelines-for-creators.md). *Please note that each custom code item needs to be submitted separately.* warning *Custom Code* cannot be monetized at this time. #### Template Tags (optional)[​](/marketplace/creators-hub/submit-item-for-review.md#template-tags-optional "Direct link to Template Tags (optional)") Template tags help users sort and filter items. If the tags listed don't match your item, enter your desired search terms under *Keywords*. #### Supported Platforms[​](/marketplace/creators-hub/submit-item-for-review.md#supported-platforms "Direct link to Supported Platforms") You can submit Marketplace items for Android, iOS, and Web (or all three!). Please make sure to test on all supported platforms to ensure the item works without issues or errors. #### Run Mode URL[​](/marketplace/creators-hub/submit-item-for-review.md#run-mode-url "Direct link to Run Mode URL") A Run Mode link of your Marketplace allows users to better understand how your item looks and works. info If your Run Mode link includes authentication functionality, please add a demo login button that uses [**Anonymous sign-in**](/integrations/authentication/firebase/anonymous-login.md) or pre-fill demo credentials in the email and password inputs. #### Documentation URL[​](/marketplace/creators-hub/submit-item-for-review.md#documentation-url "Direct link to Documentation URL") If there are complex installation or usage instructions, we highly recommend creating a documentation link for your Marketplace item. This can be written (e.g., Notion Doc, Google Doc) or video (e.g., YouTube, Loom). ### 3. Submit your item for review[​](/marketplace/creators-hub/submit-item-for-review.md#3-submit-your-item-for-review "Direct link to 3. Submit your item for review") Once the Marketplace item submission form is complete, you can submit it for review. To submit a Marketplace item for review: 1. Fill out the items in the Marketplace Item Submission Form 2. Select **Submit For Approval** Your item will be shown in your [Dashboard](https://marketplace.flutterflow.io/dashboard) under **Created Items** as "Pending Approval": ![Item in \"Pending Approval\"](/assets/images/image-29405d90490c33329a3a9f9ed007ae4e.avif) ### 4. Edit an approved item[​](/marketplace/creators-hub/submit-item-for-review.md#4-edit-an-approved-item "Direct link to 4. Edit an approved item") info At this time, it is not possible to edit an approved Marketplace Item. We are working to add this functionality soon. --- # Refund Policy note Please note that this policy does not override the local laws concerning refunds in your country, which remain applicable where necessary. At FlutterFlow, we're committed to ensuring that Marketplace offers high-quality templates that meet the diverse needs of our users. We understand the importance of finding the right tools to accelerate your app development, and we strive to ensure our Marketplace reflects the high standards you expect. ## No-Refund Policy[​](/marketplace/refund-policy.md#no-refund-policy "Direct link to No-Refund Policy") Due to the digital nature of Marketplace items, which include access to code, design, and layout, we maintain a **no-refund policy**. This policy is clearly outlined during the purchase process near the "Buy Now" button. Each template is designed for single use, and once purchased, the buyer gains immediate access to all its contents, making returns infeasible. ## Exceptional Circumstances[​](/marketplace/refund-policy.md#exceptional-circumstances "Direct link to Exceptional Circumstances") While our policy is to not offer refunds, we are committed to the satisfaction of our customers. If you encounter any of the following issues, you may be eligible for refund consideration: 1. **Major Defects:** All the items are thoroughly tested before being published, but unexpected errors may occur. Such issues must be submitted for verification. If any deficiency is confirmed, we will reach out to the item creator to address the issue and may issue a refund if we fail to address the defect within a reasonable time frame. 2. **Purchased with Incorrect Account:** If you purchased an item with a different account than you intended and have not yet used the item, we can help transfer the item to the correct account. If you believe an item qualifies, please contact us directly by emailing . Each request will be considered on a case-by-case basis, and in exceptional circumstances, we may issue a refund. Such cases are handled manually and may take 5-10 days to process. ## Feedback and Resolution[​](/marketplace/refund-policy.md#feedback-and-resolution "Direct link to Feedback and Resolution") Your feedback is vital in helping us improve the quality of the offerings on our Marketplace. If the template didn’t meet your expectations, please consider: * **Providing Feedback:** You can [rate the item](/marketplace/submit-feedback.md#rate-an-item) in your Marketplace dashboard, which helps us maintain quality standards and assists other users in making informed decisions. * **Contacting the Creator:** [Reach out directly](/marketplace/submit-feedback.md#contact-the-item-creator) to the item creator to express any dissatisfaction. * **Reporting Issues:** If you believe the item violates our standards or policies, please [report it](/marketplace/submit-feedback.md#report-an-item). We take these concerns seriously and investigate every report. --- # Submitting Feedback for Items At FlutterFlow Marketplace, your feedback is crucial to improving the quality and reliability of the items available. There are three main ways to submit feedback: ## Contact the Item Creator[​](/marketplace/submit-feedback.md#contact-the-item-creator "Direct link to Contact the Item Creator") For direct feedback or questions, we recommend contacting the item creator. This is great for providing constructive feedback or for support with minor item issues: **Via Item Detail Page:** 1. Navigate to the item's detail page on Marketplace. 2. Click the **Contact the creator** button. This action will launch a new email draft with the creator's official email address pre-filled. **Via Creator's Profile:** 1. Navigate to the creator's profile on Marketplace. 2. Click the **Contact** button. This action will copy the creator's official email address to your clipboard. 3. Send an Email to the copied email address. ## Rate an Item[​](/marketplace/submit-feedback.md#rate-an-item "Direct link to Rate an Item") You can rate items you have used, which helps other users make informed decisions: **Via Item Detail Page:** 1. Navigate to the item's detail page on Marketplace. 2. Navigate to the **Reviews** tab. 3. Click **Add a review**. 4. Select a star rating from 1 to 5, where 5 is the highest. 5. Optionally, add a comment to your rating. Please keep your feedback respectful and honest. **Via Dashboard:** 1. Go to the **Usage History** tab of your [dashboard](https://marketplace.flutterflow.io/dashboard). 2. Find the item you want to rate and click on the stars next to it. 3. Select a star rating from 1 to 5, where 5 is the highest. 4. Optionally, add a comment to your rating. Please keep your feedback respectful and honest. ## Report an Item[​](/marketplace/submit-feedback.md#report-an-item "Direct link to Report an Item") If you encounter any issues with an item that may require our attention, such as copyright or trademark infringements, or severe quality issues, you can report it. Reports are submitted anonymously and will alert both the creator and our Marketplace team. 1. Navigate to the item's detail page in Marketplace. 2. Click **Report this item** button. 3. Choose a report type and clearly describe the issue, including external URLs if necessary. 4. Click **Submit** tip If you are the original author or copyright holder of content that has been uploaded to the FlutterFlow Marketplace without your permission, you can file DMCA takedown request following the instructions in [**FlutterFlow's Terms of Service**](https://flutterflow.io/tos). ## Review Disputes[​](/marketplace/submit-feedback.md#review-disputes "Direct link to Review Disputes") If you're a creator and believe a review on your item was submitted inappropriately, you can learn about our review dispute process in our [Review Dispute Guidelines](/marketplace/creators-hub/review-dispute-guidelines.md). This covers when reviews may be removed and how to submit a dispute. --- # Additional Resources To Get Help ### FlutterFlow community forum[​](/misc/additional-resources.md#flutterflow-community-forum "Direct link to FlutterFlow community forum") The [FlutterFlow Community](https://community.flutterflow.io/) is a place for you to share ideas, ask questions, and troubleshoot issues with other FlutterFlow builders. The community shares a lot of amazing ideas! To join the FlutterFlow community, 1. Go to your account at [app.flutterflow.io](https://app.flutterflow.io) 2. Next Navigate to Resources tab on the left side 3. Click on "FlutterFlow Community". This will automatically log you in to the community. ![img\_5.png](/assets/images/img_5-a0490e088034a42f5ae6b0a70eb16425.png) Alternatively, If you are already in your project view, you can also find the **Help Menu** and click on **Community Forum**. ![img\_6.png](/assets/images/img_6-c8f43c9d55691803e9ab86d9965f0c18.png) ### YouTube[​](/misc/additional-resources.md#youtube "Direct link to YouTube") Our [YouTube channel](https://www.youtube.com/channel/UC5LueiosDVInA6yXE_38i9Q/featured) contains a variety of tutorials and how-to videos. ### Flutter community[​](/misc/additional-resources.md#flutter-community "Direct link to Flutter community") Questions about Flutter? The [Flutter Community](https://flutter.dev/community) is a great resource! ### Flutter performance best practices[​](/misc/additional-resources.md#flutter-performance-best-practices "Direct link to Flutter performance best practices") [Here are some tips](https://docs.flutter.dev/perf/rendering/best-practices) on how to write the most performant Flutter app possible. --- # Application & Data Ownership ## Intellectual Property[​](/misc/application-data-ownership.md#intellectual-property "Direct link to Intellectual Property") At FlutterFlow, we champion the principle of "Own Your Code," reflecting our commitment to enabling creators to retain ownership of their work. As you develop using FlutterFlow, you own the output of your work. FlutterFlow incorporates open-source packages which are included in the code you export. We are diligent in our selection of packages, opting for those with commercially friendly licenses, and any FlutterFlow-generated helpers or libraries will consistently adhere to permissive licenses such as MIT or BSD-3-Clause. Please be aware that third-party Flutter packages may undergo license changes or have dependencies that are not as commercially permissive. We recommend adhering to industry-standard practices to ensure compliance with all relevant licensing requirements. info Please read our [**Terms of Service**](https://flutterflow.io/tos) for full details on our Intellectual Property policies. ## Data Handling and Privacy[​](/misc/application-data-ownership.md#data-handling-and-privacy "Direct link to Data Handling and Privacy") The mobile applications you create with FlutterFlow are designed to operate independently of FlutterFlow's services, ensuring that your end-users' data remains exclusively within your application's ecosystem and does not interact with our servers. In instances where you utilize FlutterFlow's hosting services for web applications, either through our subdomain or your custom domain, we are responsible solely for delivering the frontend of the compiled Flutter web application to your end-users. FlutterFlow maintains a strict policy of non-interference with your end-users' data; we do not access, store, or collect any such data through our hosting infrastructure. info Please read our [**Privacy Policy**](https://flutterflow.io/privacy) for full details. --- # Customer Support Policy We love connecting with our users and supporting you as you build your application! However, there are a few things that fall outside the scope of our support team. To avoid confusion, we've created this document to outline our Customer Support Policy. Have a request for new documentation or tutorials we should create? You can share your ideas [here](https://flutterflow.typeform.com/to/hxg5nxbo). ### Support Hours[​](/misc/customer-support-policy.md#support-hours "Direct link to Support Hours") Our support team is available from 5 AM to 5 PM Eastern Time, Monday through Friday. ### How To Reach Us[​](/misc/customer-support-policy.md#how-to-reach-us "Direct link to How To Reach Us") Depending on your plan, there are multiple ways you can get support when using FlutterFlow: * **Account and Billing Support**: Available for all plans. You can always reach out for help with managing your account or billing-related questions. * **Community Support**: All users have access to the FlutterFlow Community Forums, where you can ask questions, share knowledge, and connect with other builders. * **Email Support**: Available starting from the **Basic** plan and above. Get direct help from our support team via email. * **In-App Support**: Available starting from the **Growth** plan and above. Chat directly with support specialists from within FlutterFlow for faster assistance. * **Dedicated Live Support**: Exclusive to the **Enterprise** plan. Gain direct access to dedicated support specialists for priority, hands-on help. ### What We Can Help With[​](/misc/customer-support-policy.md#what-we-can-help-with "Direct link to What We Can Help With") **We are happy to provide guidance on what is possible within FlutterFlow (e.g. can I use non-Firebase authentication), but we don't provide instructions on how to design/build/troubleshoot these features (e.g., how do I implement authentication via Microsoft).** ### Feature Design & Implementation[​](/misc/customer-support-policy.md#feature-design--implementation "Direct link to Feature Design & Implementation") We'd love to help you build your dream app, but there are some topics that are outside of the scope of our support team. If you aren't sure how to implement something, we recommend reaching out to the [FlutterFlow Community](https://community.flutterflow.io/) or connecting with a [FlutterFlow Expert.](https://experts.flutterflow.io/) The following topics are out of the scope of our support team: * Feature Design & Implementation * Data Infrastructure Design * Integration & Troubleshooting of 3rd-party APIs * Implementation and troubleshooting of Custom Widgets and Code ### Additional Resources[​](/misc/customer-support-policy.md#additional-resources "Direct link to Additional Resources") **Tutorials & How-To Guides** Our [YouTube channel](https://www.youtube.com/channel/UC5LueiosDVInA6yXE_38i9Q/featured) contains a variety of tutorials and how-to videos. In addition to our documentation, our [blog](https://blog.flutterflow.io/) also contains a number of how-to guides. **Troubleshooting Guides**Our documentation contains a number of [troubleshooting guides](/misc/customer-support-policy.md) to help you diagnose and fix common issues. Additionally, our [Community Forum](https://community.flutterflow.io/) is a great place to get ideas and troubleshooting tips from fellow FlutterFlow builders. Lastly, you can connect with a [FlutterFlow Expert](https://experts.flutterflow.io/) to help you troubleshoot an issue or implement a complex new feature. ### Bug Reporting Process[​](/misc/customer-support-policy.md#bug-reporting-process "Direct link to Bug Reporting Process") We regularly release feature updates and bug releases. To make sure you are on the most recent version of FlutterFlow select Ctrl/Cmd + R. If you think you've found a bug, please submit an [in-app bug report](/flutterflow-ui/toolbar.md#help-menu) or let us know via chat (Growth, Business and Enterprise users only). Please make sure to include: * A link to your project * The page(s) effected * The expected behavior and the behavior you are experiencing #### Our Approach To Fixing Bugs[​](/misc/customer-support-policy.md#our-approach-to-fixing-bugs "Direct link to Our Approach To Fixing Bugs") To ensure we fix the most critical issues first, we assess each bug based on the severity and number of users impacted. Our highest priority is fixing critical issues that impact a large number of users. Issues impacting a smaller number of users or that have a workaround are addressed after any critical issues are fixed. --- # Enterprise ## Whitelist URLs[​](/misc/enterprise.md#whitelist-urls "Direct link to Whitelist URLs") Enterprise environments often restrict internet access to enhance security and compliance. For example, they may allow access only to approved URLs that are essential for work-related tasks. FlutterFlow won't properly work in such restrictions because it accesses multiple services—Firestore, Cloud Functions, and various APIs—these URLs must be allowed in your corporate firewall for everything to function correctly. To find out which URLs need to be whitelisted, navigate to the URL Access page from the FlutterFlow [dashboard](/flutterflow-ui/dashboard.md). Any URLs marked as **Inaccessible** are currently blocked by your network, which may prevent certain features from functioning properly. You can copy these URLs individually or use the Copy All Inaccessible URLs button in the top-right corner to collect them all at once. Then, share the list with your IT team for whitelisting. ![url-access](/assets/images/url-access-9b3813d91a4ed89f5d6244f16873664b.avif) ## Enterprise Support Policy[​](/misc/enterprise.md#enterprise-support-policy "Direct link to Enterprise Support Policy") We understand our Enterprise customers often rely on FlutterFlow for mission critical applications. To that end, we have created a dedicated Enterprise support team to provide the highest level of service and support. This document outlines our support channels and scope for Enterprise customers. ### Support Channels[​](/misc/enterprise.md#support-channels "Direct link to Support Channels") Enterprise customers can reach our dedicated support team either through the chat widget in FlutterFlow or by emailing us. Our Enterprise support team is available 24x7, and we do our best to respond to every support request as quickly as possible. Depending on the complexity of the issue and your Enterprise support subscription, our team can assist through chat, email or video calls. ### What We Can Help With[​](/misc/enterprise.md#what-we-can-help-with "Direct link to What We Can Help With") Our goal is for every one of our Enterprise customers to be successful building in FlutterFlow. Here are some of the areas covered by our Enterprise support team: * Guidance on what is possible within FlutterFlow (e.g. can I use non-Firebase authentication) * General education on FlutterFlow features and platform capabilities * Bugs and technical issues with core FlutterFlow features * Team and user account administration Depending on your Enterprise support subscription, we may also provide advisory services in the following areas: * Feature Design & Implementation * Data Infrastructure Design * Integration & Troubleshooting of 3rd-party APIs * Implementation & Troubleshooting of Custom Widgets and Code ### FlutterFlow Bug Policy[​](/misc/enterprise.md#flutterflow-bug-policy "Direct link to FlutterFlow Bug Policy") We know that bugs can be frustrating and we work to fix these on an ongoing basis. If you think you've found a bug, please [submit an bug report](https://github.com/FlutterFlow/flutterflow-issues/issues). #### **Our Approach To Fixing Bugs**[​](/misc/enterprise.md#our-approach-to-fixing-bugs "Direct link to our-approach-to-fixing-bugs") To ensure we fix the most critical issues first, we assess each bug based on the severity and number of users impacted. Our highest priority is fixing critical issues that impact a large number of users. Issues impacting a smaller number of users or that have a workaround are addressed after any critical issues are fixed. We provide updates on our bug fixes in our marketing emails and in our Release Tracker. --- # Hire FlutterFlow Developer You can hire a skilled FlutterFlow Developer to build your app at: . **FlutterFlow Developers** include agencies and freelancers skilled in building apps using FlutterFlow. Many of them have the **FlutterFlow Expert** badge, awarded to those who demonstrate advanced technical proficiency. To earn this recognition, they must pass the FlutterFlow Expert training and submit a portfolio of their work for our evaluation. Please Note * FlutterFlow Developers are independent professionals, not employees, agents, or affiliates of FlutterFlow. * Any services provided are solely the responsibility of the Developer, not FlutterFlow. * We recommend signing a contract with the Developer before making any payments to ensure clarity on deliverables and timelines. Visit to get started. ![hire-dev-page.png](/assets/images/hire-dev-page-4ce5c7a7bbc3ba59365e91a8e49d50d5.png) There are two ways to find a Developer: * **Get matched**: Receive recommendations based on your project requirements and preferences (*recommended*). * **Browse Developers**: Explore available Developers, view their details, and reach out to specific ones. In both cases, you’d need to make an account with FlutterFlow, and fill out a project proposal about your project. Make sure to clearly convey your needs, objectives, and any specific requirements. This information is crucial for the Developer to provide you with an accurate timeline and quote. ## Get Matched With Developers[​](/misc/hire-flutterflow-developer.md#get-matched-with-developers "Direct link to Get Matched With Developers") To get matched with developers based on your project requirements, follow the steps below: 1. Create or log into your FlutterFlow account. ![login-ff-2.png](/assets/images/login-ff-2-93ef4a99466cd0f58b909780d6aed489.png) 2. Fill out your project details, including features, budget, geo, and language preferences. ![project-details.png](/assets/images/project-details-77fc83a0d4b7fa6cfca1c8b169106d76.png) 3. Review your project proposal for accuracy and completeness. ![review-project-proposal.png](/assets/images/review-project-proposal-b1640398d5c73bce35a263adb518c0d9.png) 4. Confirm your project details to get matched with Developers based on your requirements. ![confirm-developers.png](/assets/images/confirm-developers-40dd0af548322cba1cd6b0acd967d264.png) Please Note You can send your request upto 5 Developers at a time. Alternatively, you can browse Developers, view their profiles, and use the **Hire** button to send a personalized project proposal. ![browse-devs.png](/assets/images/browse-devs-f00bb90d36ed1738aa1290273230c11b.png) ## FAQs[​](/misc/hire-flutterflow-developer.md#faqs "Direct link to FAQs") Do FlutterFlow Developers work for FlutterFlow? No, FlutterFlow Developers are independent professionals, including designers, developers, and consultants with expertise in FlutterFlow. How are Developers selected for my project? Developers are matched based on your requirements, such as geo, language, budget, and project scope. Priority is given to Developers with the FlutterFlow Expert badge. Am I obligated to work with a Developer after contacting them? No, contacting a Developer does not obligate you to engage their services. How are contracts and payments managed? Contracts and payments are directly negotiated between you and the Developer. FlutterFlow does not handle contracts or payments. All terms, including scope, costs, and timelines, are agreed upon by both parties. Payments are processed through the Developer’s preferred billing system. --- # Security At FlutterFlow, we consider security to be our utmost priority. We understand the importance of safeguarding your data and ensuring a secure environment for our users. Below, we provide an overview of our security measures to give you confidence in the safety of your information. ## Commitment to Security[​](/misc/security.md#commitment-to-security "Direct link to Commitment to Security") Security is our top priority. We employ a comprehensive approach to ensure the protection of our users' data, and we continuously strive to enhance our security measures. ## Custom Data Safety[​](/misc/security.md#custom-data-safety "Direct link to Custom Data Safety") Your custom data is in safe hands. We implement robust security protocols to prevent unauthorized access, disclosure, alteration, and destruction of your data. Our systems are designed to ensure the confidentiality and integrity of the information you trust us with. ## SOC2 Type 1 Certification[​](/misc/security.md#soc2-type-1-certification "Direct link to SOC2 Type 1 Certification") FlutterFlow is proud to be SOC2 Type 1 certified. This certification attests to our commitment to maintaining strict security controls and measures, providing assurance to our users that their data is handled with the highest standards of security. ## GCP Best Practices[​](/misc/security.md#gcp-best-practices "Direct link to GCP Best Practices") We follow Google Cloud Platform (GCP) best practices to ensure the security of our infrastructure. By leveraging GCP's advanced security features, we aim to create a resilient and secure environment for our users. ## Security and Monitoring Services[​](/misc/security.md#security-and-monitoring-services "Direct link to Security and Monitoring Services") FlutterFlow utilizes a range of GCP security and monitoring services to enhance our overall security posture. These services include: * **Cloud Armor:** Protects against DDoS attacks by providing defense at the edge of the GCP network. * **Cloud IDS (Intrusion Detection Service):** Monitors and detects potential intrusions or security threats. * **Key Management Service (KMS):** Manages cryptographic keys used for encryption and decryption. * **Secret Manager:** Safely stores and manages sensitive information such as API keys, passwords, and certificates. * **Cloud Monitoring:** Monitors the performance, uptime, and overall health of our systems. These services collectively contribute to a robust security framework, ensuring that our users' data remains secure and our systems are actively monitored for any potential security incidents. At FlutterFlow, we believe in transparency and accountability when it comes to security. Rest assured that we are dedicated to maintaining the highest standards of security to protect your valuable information. --- # Submit Bug Reports This page guides you on submitting the bug reports in the GitHub issue tracker. We have created a [**GitHub repository**](https://github.com/FlutterFlow/flutterflow-issues/issues) specifically for tracking bug reports from our user community. This initiative fosters a more open and collaborative relationship with our users, encouraging them to report any bugs or glitches they encounter while using FlutterFlow. This will enable us to track, triage, and resolve issues in a timely and efficient manner. So, if you encounter any bugs or issues, please don't hesitate to report them on our GitHub repository! 🐛 info * You should use this only for reporting **FlutterFlow bugs**. * Any new features, suggestions, and questions should be discussed in the [**community**](https://community.flutterflow.io/home) or submitted through our user feedback form. Here is the simple flow you can refer to submit the bug report: ![Bug reporting flow](/assets/images/submit-bug-report-flow-5607bbbc75d9043356049ef34f4c03ed.avif) Before creating a new issue, it's recommended to search the issue tracker to avoid submitting duplicate reports. If you find an existing issue, show your support by upvoting it using the *Thumbs Up* icon. If you have additional information to share or clarifications to make, add them as comments on the original issue. In case you can't find the relevant issue, you can create a new one with all the necessary details. This will help us address the issue faster and more efficiently. Here are the step-by-step instructions: 1. Open the [issue tracker](https://github.com/FlutterFlow/flutterflow-issues/issues) and click on the **New Issue** button. Note: If you haven't already, you must [create a GitHub account](https://github.com/signup?ref_cta=Sign+up\&ref_loc=header+logged+out\&ref_page=%2F\&source=header-home). ![new-issue](/assets/images/new-issue-098523880b1bf4743ced662721047370.avif) 2. On the right side of the **Bug Report**, click the **Get Started** button. ![get-started](/assets/images/get-started-392ee263d173c89078bd128c601e3056.avif) 3. When describing your issue in the **Title** box, be as specific and concise as possible. Use descriptive words that accurately convey the problem. For example, instead of simply writing "*DatePicker issue*," provide more details such as "*Disabled future dates in the Date/Time picker action, still shows*". This will help us and others quickly understand the issue and can also help with searchability in case someone else has experienced the same problem. ![disabled-future](/assets/images/disabled-future-1272298d3b1583efaa5a0311fc22b0a0.avif) 4. If your issue doesn't exist and you allow us to access your project for the sole purpose of investigation, you can tick both checkboxes. ![issue-doesnt-exist](/assets/images/issue-doesnt-exist-acd0cd9386d92c1e28ebb9b8e8e437c0.avif) 5. In the '**Current behavior**' section, provide as much detail as possible about the behavior you are experiencing. ![current-behaviour](/assets/images/current-behaviour-ed947ee62061f9766d7ccc338f775ac4.avif) 6. In the '**Expected behavior**' section and enter a clear and concise description of what you expected to happen. Make sure that the expected behavior is realistic and achievable. ![expected-behaviour](/assets/images/expected-behaviour-3a811f4311b24626d56bb7205afe2963.avif) 7. In '**Steps to Reproduce**' section, write the step-by-step instructions to reproduce the bug. Also, mention any specific settings or configurations that might be relevant. This will help us diagnose the issue. **Note**: Issues cannot be accepted if they are too vague. For example, "project fails to build." ![steps-to-reproduce](/assets/images/steps-to-reproduce-19d02f4c751ffeae4968c577c7540852.avif) 8. The '**Bug Report Code**' is a unique code that helps us assess your issue. To copy it, open the **Widget Tree** > **Right Click** > select **Get Bug Report Code** and paste it here. **Note** that if an error is related to a specific widget, select the widget, right click and get the code. 9) Use the '**Context**' section to describe how it has affected you and what you are trying to accomplish. 10) In '**Additional Info**' you can provide a screenshot or recording, links, references, or anything else that will give us more context about the issue you are encountering. tip You can attach any media by dragging it here. 11. We must know the environment in which you experienced the issue. You can post such information under the '**Environment**' section. ![environment](/assets/images/environment-4d142e2107a005a0d1e7ec54f978a892.avif) 12. Click **Submit New Issue**. ![submit-new-issue](/assets/images/submit-new-issue-0556bf13483d804bc7c17ab64c87e51a.avif) Once done, your issue will be listed on the issues list, and our team will assign the appropriate label. ![submitted](/assets/images/submitted-56e7c458b66c92d76bad7e06f93fc9b6.avif) --- # Quickstart Guide Welcome to the FlutterFlow Quickstart Guide! This guide introduces the basic FlutterFlow concepts through a short, hands-on exercise. You'll build a product quantity selector that allows users to adjust the quantity of an item before adding it to their shopping cart. Before You Begin To complete this guide, you need: * A [**FlutterFlow account**](https://app.flutterflow.io/). * A web browser. * About 15-20 minutes. Below is a preview of what your completed app will look like: ![Quick start demo app](/assets/images/flutterflow-quick-start-app-demo-31982001a42882349b513c21db17a1e0.avif) ## What You'll Learn[​](/quickstart.md#what-youll-learn "Direct link to What You'll Learn") * Build a layout with widgets. * Customize widget styles. * Add interactivity with actions. * Manage page state in response to user input. * Run and test your app. Follow these steps to build the app: 1. [Clone the starter project](/quickstart.md#clone-project) 2. [Build the UI](/quickstart.md#build-ui) 3. [Customize styles](/quickstart.md#customize-style) 4. [Manage state](/quickstart.md#manage-state) 5. [Run the app](/quickstart.md#run-app) ## 1. Clone the Starter Project[​](/quickstart.md#clone-project "Direct link to 1. Clone the Starter Project") This guide uses a prepared starter app so you can focus on building the interaction. Open the [FlutterFlow Quickstart project](https://app.flutterflow.io/project/f-f-quick-start-app-umu392), click **Clone**, and the project will be added to your account. To begin with a separate project instead, see [Create a Project](/resources/projects/how-to-create-find-organize-projects.md#how-to-create-a-project). ![clone-project.avif](/assets/images/clone-project-994b7d019a46e8a06e35911a126d24d2.avif) After cloning the project, you’ll see a page with product images and a description. You’ll add a feature that allows users to update the product quantity. ![final-quick-start.avif](/assets/images/final-quick-start-f218dc46f227c5d088a7541ac6b6dddc.avif) ## 2. Build the UI[​](/quickstart.md#build-ui "Direct link to 2. Build the UI") Build the quantity control by combining layout and display widgets in the product page's Widget Tree. 1. Open the product page and locate the content below the product description. 2. Add a **Container** to hold the quantity control. 3. Add a **Row** inside the Container. 4. Add a **Text** widget for the "Quantity" label. 5. Add controls for decreasing the quantity, displaying its current value, and increasing it. 6. Arrange the widgets so the label appears on the left and the quantity controls appear on the right. [Sharing a Project with a User](https://demo.arcade.software/13kkejiZuiFeo9Fj8aWz?embed\&show_copy_link=true) info To learn more, see [**Building Layouts**](/concepts/layouts.md) and the [**Widget Overview**](/resources/ui/widgets.md). ## 3. Customize Styles[​](/quickstart.md#customize-style "Direct link to 3. Customize Styles") Next, style the quantity control to match the rest of the product page. Use the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) to adjust each selected widget. 1. Adjust the spacing and alignment of the Row. 2. Select the Container that holds the quantity control and adjust its background color, padding, size, and corner radius. 3. Style the "Quantity" label and value so they are easy to read. 4. Customize the decrease and increase controls with suitable icons, colors, and sizes. 5. Compare the result with the completed preview and make any final visual adjustments. [Sharing a Project with a User](https://demo.arcade.software/mA0EGCPhuyJ6UUQFPDUP?embed\&show_copy_link=true) ## 4. Manage State[​](/quickstart.md#manage-state "Direct link to 4. Manage State") Once your UI is set up, make your app interactive by adding a page state variable. A state variable stores data that can change as users interact with the page. In this exercise, it stores the current product quantity and updates the displayed value when users select the increase or decrease control. ### 4.1 Add a State Variable[​](/quickstart.md#41-add-a-state-variable "Direct link to 4.1 Add a State Variable") Add a [page state variable](/resources/ui/pages/page-lifecycle.md) that will hold the current quantity value. Here's how to add and use the state variable: 1. Select the page's root widget in the Widget Tree. 2. Open the page's state management settings and add a new field. 3. Name the field `quantity`, set its data type to **Integer**, and give it an initial value of `1`. 4. Select the Text widget that displays the quantity. 5. Set its value from **Page State > quantity**. info To learn more about this workflow, see [**Creating a Page State**](/resources/ui/pages/page-lifecycle.md#creating-a-page-state). [Sharing a Project with a User](https://demo.arcade.software/T8dg4g238t37cct3vrD2?embed\&show_copy_link=true) ### 4.2 Update the State Variable[​](/quickstart.md#42-update-the-state-variable "Direct link to 4.2 Update the State Variable") Use actions to change `quantity` when a user selects the increase or decrease control: 1. Select the increase control and add an **On Tap** action. 2. Choose **Update Page State**, select `quantity`, and set it to its current value plus `1`. 3. Select the decrease control and add another **On Tap** action. 4. Update `quantity` to its current value minus `1`. 5. Confirm that both controls update the Text widget bound to `quantity`. info See the [**Action Flow Editor**](/resources/functions/action-flow-editor.md) and [**Update Page State**](/resources/ui/pages/page-lifecycle.md#update-page-state-action) guides for more details. [Sharing a Project with a User](https://demo.arcade.software/rmxuLzwsP7uGgGQUI4YO?embed\&show_copy_link=true) ## 5. Run the App[​](/quickstart.md#run-app "Direct link to 5. Run the App") Use [**Test Mode**](/testing/run-your-app.md#test-mode) to try the interaction and see changes quickly. Test Mode runs a web version of your app and can automatically sync changes from the FlutterFlow builder. 1. Select **Test Mode** from the left-side menu. 2. Wait for the test session to start. 3. Click or tap the increase and decrease controls and confirm that the displayed quantity changes. [**Run Mode**](/testing/run-your-app.md#run-mode) creates a fully functional build that can include live data and be shared with project members. Because it creates a new build, it typically takes longer and does not support hot reload. [Sharing a Project with a User](https://demo.arcade.software/hdpwwkbCYcvsjsrkygDX?embed\&show_copy_link=true) Congratulations! You've built your first app with FlutterFlow. ## Verify the Result[​](/quickstart.md#verify-the-result "Direct link to Verify the Result") Before moving on, confirm that: * The initial quantity is displayed correctly. * The increase control raises the quantity. * The decrease control lowers the quantity. * The layout remains aligned as the value changes. * The interaction works in Test Mode. ## Next Steps[​](/quickstart.md#next-steps "Direct link to Next Steps") Continue learning with these guides: * [Building Layouts](/concepts/layouts.md) * [Widget Overview](/resources/ui/widgets.md) * [Page State](/resources/ui/pages/page-lifecycle.md#page-state) * [Action Flow Editor](/resources/functions/action-flow-editor.md) * [Run and Test Your App](/testing/run-your-app.md) ## Need Help?[​](/quickstart.md#need-help "Direct link to Need Help?") If you're experiencing any issues with the app, review the steps above and verify that each widget and action is configured as described. For additional help, ask a question in the [Community Forum](https://community.flutterflow.io/) or contact FlutterFlow Support. --- # Create & Test API Call In this guide, you'll learn how to create and test API calls in FlutterFlow. Integrating API calls allows your app to interact with external services, bringing in real-time data and functionality that enhances your app's capabilities. ## Create API Call[​](/resources/backend-logic/create-test-api.md#create-api-call "Direct link to Create API Call") To use an API in your app, you first need to create the API call in FlutterFlow. Simply select API Calls from the left navigation menu, click the **+ Add** button, and choose **Create API Call**. Enter an **API Call Name**, select the **Method Type** (GET, POST, DELETE, PUT, or PATCH), and input the API URL of the service you wish to access. Method Types The Method Type specifies the type of operation the API call will perform. Here’s a breakdown of common method types: * **GET:** Retrieves data from the server. * **POST:** Sends data to create or update a resource. * **DELETE:** Removes a resource from the server. * **PUT:** Updates or creates a resource with full data. * **PATCH:** Partially updates a resource. ### Dynamic API URLs[​](/resources/backend-logic/create-test-api.md#dynamic-api-urls "Direct link to Dynamic API URLs") If you want to use a dynamic URL, for example, `` where 2 is dynamic and `` where 5 is dynamic: 1. Replace the hard-coded value with a meaningful name inside the brackets (e.g., from `https://reqres.in/api/users/2`to `https://reqres.in/api/users/[user_id]`). 2. And then, [**create a new variable**](/resources/backend-logic/rest-api.md#creating-variables) with the same name you provided inside the brackets. The further instructions are based on the **Method Type** you selected. ### For `GET` & `DELETE` call[​](/resources/backend-logic/create-test-api.md#for-get--delete-call "Direct link to for-get--delete-call") If you selected `GET` or `DELETE` as the method type, follow the steps below: 1. Optional: If the API call requires request headers such as an authorization token, [add a header](/resources/backend-logic/rest-api.md#passing-request-headers). 2. Optional: If the API call requires query parameters such as page number or user id, [add query parameters](/resources/backend-logic/rest-api.md#passing-query-parameters). 3. Click **Add Call** to save the API Call. warning After making any changes, you must save the API call. In the above demo, a `GET` API call is defined to fetch users' data from [REQ | RES](https://reqres.in/) (which provides hosted REST API to try out HTTP requests). A demo of using a dynamic URL in a GET request is as follows: To add such an API call: 1. Replace the hard-coded value with a meaningful name inside the brackets (e.g., from `https://reqres.in/api/users/2`to `https://reqres.in/api/users/[user_id]`). 2. And then, [create a new variable](/resources/backend-logic/rest-api.md#creating-variables) with the same name you provided inside the brackets. The DELETE API Call can also be defined similarly; just make sure you select the **Method Type** as ***DELETE***. ### For `POST`, `PUT` & `PATCH` call[​](/resources/backend-logic/create-test-api.md#for-post-put--patch-call "Direct link to for-post-put--patch-call") If you have selected **POST request**, follow the steps below: 1. Optional: If the API call requires request headers such as an authorization token, [add a header](/resources/backend-logic/rest-api.md#passing-request-headers). 2. [Create a request body](/resources/backend-logic/rest-api.md#creating-request-body) for the API call. 3. Click **Add Call** to save the API Call. warning After making any changes, you must save the API call. In this demo, a POST API call is defined with two variables, `userName` and `userJob`. The variables are used inside the JSON request body. The PUT and PATCH API calls can be defined similarly; make sure you enter a valid API URL endpoint and select the correct Method Type. ## Grouping API calls[​](/resources/backend-logic/create-test-api.md#grouping-api-calls "Direct link to Grouping API calls") You can create a group of API calls that share the same base URL. Grouping the API calls helps you add all request headers (e.g., auth token) at once, and they will be automatically added for all the API calls inside the group. warning For [**private APIs**](/resources/backend-logic/rest-api.md#private-api-calls), headers defined within the group will not be automatically included. You'll need to manually add headers for APIs marked as private. To create the API Group: 1. Click on the **+** button (top left side) and select the **Create API Group**. 2. Enter the **API Group Name**. 3. Enter the **API Base URL**. This should be the portion that is common in all the APIs. **Note**: Do not keep the '/' in the end. 4. You can add request headers by clicking on the **+ Add Header** button. See detailed instructions on how to [add headers](/resources/backend-logic/rest-api.md#headers). 5. Click **Add Group**. This will display the group on the left side. 6. Open the newly created API group, and click on the **+ Add API Call**. 7. Add the API call as you would normally do. **Note**: Inside the API endpoint, enter the URL portion that starts after the base URL. ## Import API definitions[​](/resources/backend-logic/create-test-api.md#import-api-definitions "Direct link to Import API definitions") We allow you to add multiple API call definitions by importing them directly from the [Swagger/OpenAPI](https://swagger.io/) in bulk. With just a simple click, you can add a large number of APIs, significantly reducing the time and effort needed to create them manually. Furthermore, the ability to import Swagger/OpenAPI definitions directly into FlutterFlow eliminates the risk of errors that may occur when creating API definitions manually, ensuring that applications are reliable and efficient. info We also add all settings that are required to run the API, such as [headers](/resources/backend-logic/rest-api.md#headers), [query parameters](/resources/backend-logic/rest-api.md#query-parameters), [variables](/resources/backend-logic/rest-api.md#variables), and body as they are defined in the Swagger file. However, you might need to replace the hard-coded values in [Body](/resources/backend-logic/rest-api.md#body) text with the [variables](/resources/backend-logic/rest-api.md#variables). warning Please note that while it is possible to import APIs created with OAS 2.0 in FlutterFlow, you might face some issues, such as the body request being lost during the import process. Our import functionality is built based on the OAS 3.0 standard, so for the best experience and compatibility, it is recommended to use APIs that adhere to OAS 3.0 or above. To import API call definitions: 1. Click the **Import OpenAPI** icon. This will open a new popup. 2. Click **Upload File**. Here you can upload your swagger file available in `.yml` or `.json` file format. 3. After the import is successful, you will see the list of all APIs created and added as a [group](/resources/backend-logic/create-test-api.md#grouping-api-calls). Here's an example of importing API calls in bulk, taken from [here](https://editor.swagger.io/). ## Testing API calls[​](/resources/backend-logic/create-test-api.md#testing-api-calls "Direct link to Testing API calls") You should always test your API call before using it inside your app. We make it easy for you to try the API call inside our builder. To test the API call along with its response, follow the steps below: 1. Select an API call you have already created or are currently defining, and go to the **Response & Test** tab. 2. On the left side, you will see the **Variables** section, where you can enter the values for the variables defined for your API call. 3. On the right, the **Preview** section lets you check the API URL, request headers, request body, and response. In the **Test Response** tab, you can view the full API response, including both the JSON format and raw body text, as well as the response header. 4. Click **Test API Call** to trigger the API call. You'll notice that the status of the GET request is displayed, and if it's successful (status code `200`), the result returned from that request will also be displayed below. 5. Any value of the JSON result can be accessed by [defining the JSON path](/resources/backend-logic/rest-api.md#json-path). The demo below shows the testing of creating a new user using a POST request. The API Call takes two variables: `userName` and `userJob`. The successful POST request returns a status code of `201`. info The testing of `PUT` and `PATCH` requests would also be similar to this. ## API Call \[Action][​](/resources/backend-logic/create-test-api.md#api-call-action "Direct link to API Call \[Action]") Once the API calls are defined in your FlutterFlow project, you can use them wherever needed. Open the Action Flow Editor on the widget where the API call should be triggered. After selecting the desired Action Trigger, search for "API Calls" in the Actions dropdown and select the API call you want to use. ![use-api-call.png](/assets/images/use-api-call-ee46df3313018e6348554a1c7c8fdd68.png) tip You can also add the API Call as a [**Backend Query**](/resources/backend-query/api-call-query.md) that gets triggered automatically when the page or widget is loaded on the screen. Go to your project and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. On the right side, search and select the **API Call** (under *Backend/Database*) action. 1. Select the **Group or Call Name** from the dropdown. 2. Optional: If your API call requires variables (e.g., auth token, query parameters, user id, etc.), pass their value by clicking on the **+ Variable** button. 3. The **Action Output Variable Name** helps you retrieve the response of an API call. By default, we set it to any random name. However, you can change it to a meaningful name if you wish to. (e.g., loginResponse). 4. You can add a conditional action that checks if the API call is succeeded. 5. If the API call is succeeded, all actions under the TRUE path will be executed. For example, [navigate](/concepts/navigation/page-navigation.md#navigate-to-action) to the home page if the login is successful. 6. If the API call is failed, all actions under the FALSE path will be executed. For example, [showing a snackbar](/resources/ui/pages/scaffold.md#snackbar) if the login is unsuccessful. --- # API Calls On this page, you will learn the most basic knowledge on various concepts for adding an API call to your project. They are the building blocks of adding an API call. Depending on the API's definition, you may utilize some or all of these concepts to successfully implement the API call in your project. Here are they: * [Headers](/resources/backend-logic/rest-api.md#headers) * [Query Parameters](/resources/backend-logic/rest-api.md#query-parameters) * [Variables](/resources/backend-logic/rest-api.md#variables) * [Body](/resources/backend-logic/rest-api.md#body) * [API response (JSON) to/from Data Type](/resources/backend-logic/rest-api.md#api-response-json-tofrom-data-type) * [JSON Path](/resources/backend-logic/rest-api.md#json-path) * [Advanced Settings](/resources/backend-logic/rest-api.md#advanced-settings) ## Headers[​](/resources/backend-logic/rest-api.md#headers "Direct link to Headers") Headers typically carry the metadata associated with an HTTP request or response of an API call. HTTP headers are mainly grouped into two categories: * **Request headers** contain more information about the resource to be fetched or the client requesting the resource. * **Response headers** hold additional information about the response that the server returns. ### Passing request headers[​](/resources/backend-logic/rest-api.md#passing-request-headers "Direct link to Passing request headers") Some of the common request headers that you might need while sending a request are: * **Authorization**: Used for authenticating the request. * **Content-Type**: Used while sending a POST/PUT/PATCH request containing a message body. To pass the request header: 1. Select the **Headers** tab and click on the **+ Add Header** button. 2. Inside the input box, enter the header name followed by the colon(:) and its value (e.g., **Content-Type: application/json**). info The default **Content-Type** for any HTTP POST request is `application/json`, so if your data body is in JSON, you can skip defining the Content-Type. ### Passing auth token (as request header)[​](/resources/backend-logic/rest-api.md#passing-auth-token-as-request-header "Direct link to Passing auth token (as request header)") You might need to add an API that is secured. That means it only gives results if you pass the authorization token (aka auth token) in the header parameter. This is usually done to prevent abuse. Let's see how you can add the auth token. #### Passing static auth token[​](/resources/backend-logic/rest-api.md#passing-static-auth-token "Direct link to Passing static auth token") Some services provide you with a static auth token. Such a token never changes until you manually generate the new one. To pass the static auth token: 1. Select the **Headers** tab and click on the **+ Add Header** button. 2. Inside the input box, enter the header name as **Authorization** followed by colon (:) and its value (e.g., **Authorization: Bearer YOUR\_TOKEN**). #### Passing dynamic auth token[​](/resources/backend-logic/rest-api.md#passing-dynamic-auth-token "Direct link to Passing dynamic auth token") You would probably want to pass the auth token returned as a response in the login API call. Such a token changes every time when you log in. Hence, you need a way to pass the dynamic token. How to save an authentication token? After the login call is succeeded, ensure you save the authentication token in an app state variable (with Persisted -> True). Check the visuals below: ![api-token-variable.png](/assets/images/api-token-variable-acf56056503d2c4fb2717205d8d8c7d2.png) Now you can pass the dynamic token: 1. Select the **Headers** tab and click on the **+ Add Header** button. 2. Inside the input box, enter the header name as **Authorization** followed by a colon (:) and then enter any variable name inside the brackets (e.g., **Authorization: Bearer \[auth\_token]**). 3. Select the **Variables** tab and [create a new variable](/resources/backend-logic/rest-api.md#creating-variables) with the same name you provided inside the brackets. This will be used to pass the token value from the app state variable to the API call. Now from the API call (that requires an authentication token), pass the token value from the app state variable. ### Accessing response headers[​](/resources/backend-logic/rest-api.md#accessing-response-headers "Direct link to Accessing response headers") Sometimes you might want to retrieve the values of the response headers. For example, retrieving the auth token from the response headers of the Login API call. To access the response header: 1. Ensure you have added the [API call action](/resources/backend-logic/rest-api.md) and provided the **Action Output Variable Name**. 2. Now, whenever/wherever the **Value Source** is set to **From Variable**, select the **Action Outputs > \[Action Output Variable Name]** (e.g., Action Outputs > loginResponse). 3. Set the **API Response Options** to **Get Response Header**. 4. Enter the **Header Name**. Note that this must match the name of the response header from your API call. 5. Click **Confirm**. ## Query Parameters[​](/resources/backend-logic/rest-api.md#query-parameters "Direct link to Query Parameters") They are optional parameters you can pass with an API call; they help format the response data returned by the server. Usually, they are concatenated at the end of the URL with a question mark (`?`) as the delimiter and are represented as key-value pairs. An example of an URL with query parameters looks like this ([NASA Open API](https://api.nasa.gov/)): [https://api.nasa.gov/neo/rest/v1/feed?**start\_date=2015-09-07\&end\_date=2015-09-08\&api\_key=DEMO\_KEY**](https://api.nasa.gov/neo/rest/v1/feed?start_date=2015-09-07\&end_date=2015-09-08\&api_key=DEMO_KEY) Here, `start_date`, `end_date`, and `api_key` are the query parameters passed to receive the specific data. Here's another example, this API call `` has two query parameters. The `limit` parameter specifies 20 items to load per page, and the `offset` specifies the number of items to skip. This is called offset-based pagination. ### Passing query parameters[​](/resources/backend-logic/rest-api.md#passing-query-parameters "Direct link to Passing query parameters") To pass the query parameters for `GET` or `DELETE` API call: 1. Select the **Query Parameters** tab and click the **+ Add Query Parameter** button. 2. Enter the **Name** of the query parameter. 3. Set the **Value Source** to **Specific Value** or **From Variable**. 1. If you want to pass this value from your page, app state variable, or any other source (i.e. , dynamic value), choose the **From Variable,** and then from the **Select Variable** dropdown, choose the already created variable (see how to [create variable](/resources/backend-logic/rest-api.md#creating-variables)) or click on **+ Create New Variable**. Note: This will immediately create a new variable with the same name as of query parameter. However, you still need to open the **Variables** tab and set its **Type**. 2. If you want to pass a static/fixed value, select the **Specific Value**, set its **Type,** and enter its **Value**. Below is the example of passing query parameter for the URL -> `https://api.instantwebtools.net/v2/passenger?page=10&size=20` In a rare case, you might want to pass the query parameters for the other methods of API calls. Such as POST, PUT, and PATCH. To do so: 1. In your API URL, replace the hard-coded values with a meaningful name inside the brackets (e.g., from `https://api.instantwebtools.net/v2/passenger?``**page=0**` to `https://api.instantwebtools.net/v2/passenger?``**page=[page]**`). 2. Select the **Variables** tab and [create a new variable](/resources/backend-logic/rest-api.md#creating-variables) with the same name you provided inside the brackets. ## Variables[​](/resources/backend-logic/rest-api.md#variables "Direct link to Variables") Variables allow you to pass the dynamic values from any part of your app to the API calls. Here's when they come in handy: * Sending an auth token from your app's state to an API call's request header. * Using username and password from TextField widgets in the API call's request body. * Including selected dates as query parameters. * Changing the base URL with a dynamic URL. ### Creating variables[​](/resources/backend-logic/rest-api.md#creating-variables "Direct link to Creating variables") To create variables, select the **Variables** tab, enter its **Name**, select the appropriate **Type** and provide the **Default Value** if you wish to. ![variables.png](/assets/images/variables-581babfdb6cab0e35ece92e29388cba5.png) Now you can pass values to these variables while triggering the API call from your page. You can set its value from any widget, app state variable, or any other source. Here's how you can use a variable to create a dynamic base URL: ![dynamic-base-url.png](/assets/images/dynamic-base-url-1b204a7de91385a3f4287fcfaf1459d4.png) ## Body[​](/resources/backend-logic/rest-api.md#body "Direct link to Body") You can send data (as a request body) while calling the API of methods POST, PUT, or PATCH by defining them inside **Body**. The most common type is JSON format which is the easiest way of passing data inside the body of the reqest. ### Creating Request Body[​](/resources/backend-logic/rest-api.md#creating-request-body "Direct link to Creating Request Body") Here you'll see creating a request body in the following formats: #### JSON format[​](/resources/backend-logic/rest-api.md#json-format "Direct link to JSON format") To create a request body in JSON format: 1. First, If you haven't already, [create variables](/resources/backend-logic/rest-api.md#creating-variables) (e.g., username and password variables that will be required to pass values from a login page to the login API call). 2. Select the **Body** tab and set the Body dropdown to **JSON**. 3. Copy-paste your request body and replace the values with the variables by dragging and dropping them inside your JSON body. #### Text format[​](/resources/backend-logic/rest-api.md#text-format "Direct link to Text format") This format is used to send textual data in the request body of an API. For example, in a SOAP API, the request body is typically in text format and contains XML data. To create a request body in text format: 1. First, If you haven't already, [create variables](/resources/backend-logic/rest-api.md#creating-variables). 2. Select the **Body** tab and set the Body dropdown to **Text**. 3. Copy-paste your request body and replace the values with the variables by dragging and dropping them inside the request body. #### x-www-urlencoded format[​](/resources/backend-logic/rest-api.md#x-www-urlencoded-format "Direct link to x-www-urlencoded format") To create a request body in x-www-form-urlencoded format: 1. First, If you haven't already, [create variables](/resources/backend-logic/rest-api.md#creating-variables) (e.g., username and password variables that will be required to pass values from a login page to the login API call). 2. Select the **Body** tab and set the Body dropdown to **x-www-form-urlencoded**. 3. Click on the **+ Add Parameter** and enter the **Name** of the parameter. 4. Set the **Value Source** to **Specific Value** or **From Variable**. 1. If you want to pass this value from your page, app state variable, or from any other source (i.e., dynamic value), choose the **From Variable,** and then from the **Select Variable** dropdown, choose the already created variable (see how to [create variable](/resources/backend-logic/rest-api.md#creating-variables)) or click on **+ Create New Variable**. Note: This will immediately create a new variable with the same name as of parameter. However, you still need to open the **Variables** tab and set its **Type**. 2. If you want to pass a static/fixed value, select the **Specific Value**, set its **Type,** and enter its **Value**. #### Multipart format[​](/resources/backend-logic/rest-api.md#multipart-format "Direct link to Multipart format") A multipart request body is a data format used in HTTP requests that enable the transfer of multiple parts of data in a single request. It is commonly used in file uploads. To create a request body in the multipart format: 1. Select the **Body** tab and set the *Body* dropdown to **Multipart**. 2. Click on the **+ Add Parameter** and enter the **Name** of the parameter. 3. Set the **Value Source** to **From Variable,** and then from the **Select Variable** dropdown, click on **+ Create New Variable**. Note: This will immediately create a new variable with the same name as of parameter. 4. Now move to the **Variables** tab and set the **Type** to **Uploaded File**. This will allow you to pass the file stored locally on the device using an action such as **Upload/Save Media**. ## API response (JSON) to/from Data Type[​](/resources/backend-logic/rest-api.md#api-response-json-tofrom-data-type "Direct link to API response (JSON) to/from Data Type") Converting between API Response (JSON) and Data Types is often referred to as JSON deserialization and serialization. It allows you to convert JSON data from an API response into a [**Custom Data Type**](/resources/data-representation/custom-data-types.md) when you receive it. Also, it enables you to convert your Custom Data Type back into JSON when sending data in an API request. info This is a more robust and maintainable way to work with JSON data in your app. It reduces complexity and potential errors (e.g., typos) compared to manually navigating [JSON paths](/resources/backend-logic/rest-api.md#json-path). ### Create Custom Data Type matching to JSON structure[​](/resources/backend-logic/rest-api.md#create-custom-data-type-matching-to-json-structure "Direct link to Create Custom Data Type matching to JSON structure") First, [create a Data Type](/resources/data-representation/custom-data-types.md#creating-custom-data-type) with the same structure as your API response. Here's what the sample JSON response looks like after mapping it into a Custom Data Type. ![custom-data-type-json-response.png](/assets/images/custom-data-type-json-response-3a0d799722f6aaa023aa47d0142fd9a6.png) Creating custom data type as per the JSON response After this, you can choose to [convert to](/resources/backend-logic/rest-api.md#json-to-data-type) or [from](/resources/backend-logic/rest-api.md#json-from-data-type) the Data Type based on your requirements. ### JSON to Data Type[​](/resources/backend-logic/rest-api.md#json-to-data-type "Direct link to JSON to Data Type") Let's see how to get the JSON into the Custom Data Type using an example that fetches the list of products from [this API](https://dummyjson.com/docs/products). Here's how it looks: ![img.png](/assets/images/img-c6ed86116aa90ef908ab4f79a0262ceb.png) Here's how you do it: 1. First, ensure that you [create a custom data type](/resources/backend-logic/rest-api.md#create-custom-data-type-matching-to-json-structure) that matches your JSON structure. 2. Open your API call definition > **Response & Test tab > Response Type >** enable the **Parse as Data Type**. Select the **Data Type** that you want to convert into. For this example, it's 'AllProducts'. ![img\_1.png](/assets/images/img_1-05b380df971294e998ed9a503d802323.png) 3. On ListView, after adding the [API call backend query](/resources/backend-query/api-call-query.md), access the values by setting the following options. 1. **Generate Children from Variable** by setting **API Response Options** to **As Data Type**. 2. Set **Available Options** to **Data Structure Field** because we want to grab only a specific field, which has a list of products and not other items such as 'total' and 'skip'. 3. **Select Field** to the field that holds the list of products, i.e., 'products' for this example. 4. Click **Confirm** twice. 4) Now, you can bind data in UI elements as you would normally do by setting the **Available Options** to **Data Structure Field** and **Select Field** that you want to display. ### JSON from Data Type[​](/resources/backend-logic/rest-api.md#json-from-data-type "Direct link to JSON from Data Type") Sometimes you might want to dynamically create a JSON body and pass it along the API request instead of manually configuring each field in the API call editor. You can do so by adding data into a Custom Data Type and then converting it into JSON while making an API call. Let's see an example of adding a product by sending its data in JSON format in the API request. ![add-product.png](/assets/images/add-product-ef370dfe0ed90a1a547b26a09fb59236.png) Here's how you do it: 1. First, [create a custom data](/resources/backend-logic/rest-api.md#create-custom-data-type-matching-to-json-structure) type that matches the JSON format of the API request body. Here's how it looks for this example. ![img\_2.png](/assets/images/img_2-793efe2c929d07234b9fb57e4cac6d5b.png) 2. In your API call, [create a variable](/resources/backend-logic/rest-api.md#creating-variables) with type **JSON** and put it inside the **Body** section. 3) On click of **Add** button, we'll store values from UI into the page state variable of custom data type. Then, while making an API call, pass that page state variable and set the **Available Options** to **To JSON**. ## JSON Path[​](/resources/backend-logic/rest-api.md#json-path "Direct link to JSON Path") **JSONPath** is a query language for JSON. Using the JSON path, you can retrieve specific data out of the whole JSON response. note You'll usually get a response in JSON format from an API request. Learning a few JSON paths (or *JSONPath expressions*) will help you retrieve most of the data you need. Inside our builder, we allow you to try and add different JSON paths in real-time and suggest various options to get exactly what you are looking for. Some examples of JSONPath expressions are as follows: * `$.data.name` * `$.users[0].name` * `$.users[:].name` The leading `$` represents the root object, dot (`.`) is used for accessing keys present inside the JSON, the value inside brackets (`[0]`) represents the array index if the key contains an array, and the (`[:]`) will select all the objects inside the list. Let's see some real-world examples of the JSON path for the following API response: ``` { "page": 1, "per_page": 6, "total": 3, "total_pages": 2, "data": [ { "id": 1, "email": "george.bluth@reqres.in", "first_name": "George", "last_name": "Bluth", "avatar": "https://reqres.in/img/faces/1-image.jpg" }, { "id": 2, "email": "janet.weaver@reqres.in", "first_name": "Janet", "last_name": "Weaver", "avatar": "https://reqres.in/img/faces/2-image.jpg" }, { "id": 3, "email": "emma.wong@reqres.in", "first_name": "Emma", "last_name": "Wong", "avatar": "https://reqres.in/img/faces/3-image.jpg" } ], "support": { "url": "https://reqres.in/#support-heading", "text": "To keep ReqRes free, contributions towards server costs are appreciated!" } } ``` $.total This will return the following data: ``` 3 ``` $.data This will return the following data: ``` [ { "id": 1, "email": "george.bluth@reqres.in", "first_name": "George", "last_name": "Bluth", "avatar": "https://reqres.in/img/faces/1-image.jpg" }, { "id": 2, "email": "janet.weaver@reqres.in", "first_name": "Janet", "last_name": "Weaver", "avatar": "https://reqres.in/img/faces/2-image.jpg" }, { "id": 3, "email": "emma.wong@reqres.in", "first_name": "Emma", "last_name": "Wong", "avatar": "https://reqres.in/img/faces/3-image.jpg" } ] ``` $.data\[0] This will return the object at the 0th index (i.e., the first object). ``` { "id": 1, "email": "george.bluth@reqres.in", "first_name": "George", "last_name": "Bluth", "avatar": "https://reqres.in/img/faces/1-image.jpg" } ``` $.data\[0].email This will return the email value of the object at the 0th index. ``` "george.bluth@reqres.in" ``` $.data\[:].email This will return the email of all the objects inside the data. ``` [ "george.bluth@reqres.in", "janet.weaver@reqres.in", "emma.wong@reqres.in" ] ``` Important JSON keys must start with a letter, an underscore, or a dollar sign. They cannot begin with a numeric character. However, in cases where you have keys with numeric prefixes, such as `$.0_image`, you can access them using bracket notation, like this: `$.["0_image"]`. info Learn more about **[JSONPath](https://www.rfc-editor.org/rfc/rfc9535.html)** and how to define a proper expression. ### Add JSON Predefined Path[​](/resources/backend-logic/rest-api.md#add-json-predefined-path "Direct link to Add JSON Predefined Path") You can effortlessly define and manage **JSON Paths** for your API calls in FlutterFlow to parse and extract the data you need. Once added you can [use](/resources/backend-logic/rest-api.md#using-json-path) them as **Predefined Path** while accessing the **JSON Body**. First, [create and test](/resources/backend-logic/create-test-api.md) your API call. Inside the **JSON Paths** section, click **+ Add JSON Path**, enter your **JSON Path**, and assign it a name. If the expression is valid, a preview of the response appears under **Response Preview**. Click the **Preview** icon to see the full response. If the response contains a list of items, the **Is List** option will be enabled automatically. Under the **Recommended** section, you'll find suggested JSON paths that might contain the data you need. ### Using JSON Path[​](/resources/backend-logic/rest-api.md#using-json-path "Direct link to Using JSON Path") While accessing values from an API Call, you can either enter the custom JSON path or use the [predefined JSON path](/resources/backend-logic/rest-api.md#add-json-predefined-path). To use a predefined JSON Path, first, select your API response. Then, set the **API Response Options** to **JSON Body** and the **Available Options** to **JSON Path** or **Predefined Path**. Finally, specify the JSON Path Name or select from the predefined JSON Path to map the extracted data for use in your app. ## Advanced Settings[​](/resources/backend-logic/rest-api.md#advanced-settings "Direct link to Advanced Settings") You can make the API call private and change the proxy settings using advanced settings. ### Private API Calls[​](/resources/backend-logic/rest-api.md#private-api-calls "Direct link to Private API Calls") Making an API call private is helpful if it uses tokens or secrets you don't want to expose in your app. Enabling this setting will route this API call securely via the Firebase Cloud Functions. ![private-cloud-func.png](/assets/images/private-cloud-func-5752d692200c53e625e1907c0d726101.png) To make an API Call Private, open the **Advanced Settings** tab, turn on the **Make Private** toggle, Click **Save,** and then **Deploy APIs**. Optionally, you can force a user to be authenticated via the Firebase authentication to make this API call. To do so, turn on the **Require Authentication** toggle. Private APIs are deployed as [**Cloud Functions**](https://firebase.google.com/docs/functions) within your Firebase project. While deploying, you can configure the following options: * **Use Custom Name for Cloud Function**: When enabled, allows you to specify a custom name for the deployed Cloud Function. By default, this option is disabled and Cloud Function is named as `ffPrivateApiCall`. * **Private API Cloud Function Instances**: You can configure the number of Cloud Function instances to optimize performance and manage costs. * **Min Instances**: Set the minimum number of active instances to reduce latency and avoid cold starts. Setting this value greater than 0 will keep instances warm but may incur additional costs. * **Max Instances**: Define the maximum number of instances that can be scaled up based on demand. **Note**: To minimize costs, you can set the **Min Instances** value to 0. For detailed pricing information, refer to the [**Cloud Functions Pricing page**](https://cloud.google.com/functions/pricing-overview). note * If you make the API call private, **Firebase** should be connected to your project. Follow the instructions on [**this page**](/integrations/firebase/connect-to-firebase.md) for integrating Firebase with FlutterFlow. * If you enable the **Require Authentication** toggle, **Firebase Authentication** must be configured appropriately. Check out [**this page**](/integrations/authentication/firebase/initial-setup.md) for setting up authentication. ### Process Streaming Response[​](/resources/backend-logic/rest-api.md#process-streaming-response "Direct link to Process Streaming Response") When working with APIs that send data continuously, like Server Sent Events (SSE), you can enable this option. This ensures your app can handle the ongoing flow of data over a long-lasting HTTP connection to display real-time updates. Imagine you're building a live sports score application. The API provides real-time updates on match scores. To handle this continuous stream of data, you need to enable this option. info You can usually determine if an API supports streaming by checking its documentation. Look for keywords like "event stream" or "processing chunks. Learn More Learn more about adding and using [**Streaming APIs**](/resources/backend-logic/streaming-api.md). ### Change proxy settings[​](/resources/backend-logic/rest-api.md#change-proxy-settings "Direct link to Change proxy settings") By default, when you test your API calls inside our builder, Run mode, and Test mode, we use a proxy to route your calls to avoid the CORS issue. However, if you want to use your proxy, you can disable these settings and provide your proxy URL. To disable current proxy settings and provide your proxy URL: 1. Open the **Advanced Settings** tab. 2. Disable the **Use Proxy for Test** and/or **Use Proxy for Run/Test Mode**. 3. Enable the **Use** **Custom Proxy URL**. 4. Enter the **Proxy Prefix URL** (e.g., ****). ![proxy-settings.png](/assets/images/proxy-settings-9aa01af579d7fda154ac7ff56df5de6f.png) ### Cache API Results[​](/resources/backend-logic/rest-api.md#cache-api-results "Direct link to Cache API Results") You can enable this option for a specific API call. So when your app runs, multiple calls to this endpoint with the same arguments will be cached. Learn more about caching [here](/resources/backend-query.md#backend-query-caching). ### Decode Responses as UTF-8[​](/resources/backend-logic/rest-api.md#decode-responses-as-utf-8 "Direct link to Decode Responses as UTF-8") Enabling this option ensures that the data you get from a server or website is read as UTF-8, a common way of storing text. Usually, a server or website tells you how to read its data, but sometimes it doesn't. This option makes sure you read the data in UTF-8 way even if the website doesn't tell you to do so. ### API Interceptors[​](/resources/backend-logic/rest-api.md#api-interceptors "Direct link to API Interceptors") An interceptor allows you to capture and modify API requests and responses before they are sent or received by your app. For example, it can be used for tasks such as adding authentication tokens, logging, and error handling. It acts as a middleman between your app and the API server. So, when you make an API call from your app, the request goes through the interceptor first. The interceptor can then inspect the request, make changes to it (like adding headers or modifying the URL), and even cancel the request if needed. Similarly, when the server responds to your request, the response passes through the interceptor before reaching your app. Let's see how to add an interceptor: 1. Navigate to the **Advanced Settings** tab. 2. Click on **+ Add Interceptors** and select **+ Create New Interceptor** to open the [Custom Action](/concepts/custom-code/custom-actions.md) editor. 3. Enter the **Action Name**. 4. In the boilerplate code, add your custom code within the `onRequest` function for request interception and modification and within the `onResponse` function for response interception and modification. tip You can copy the boilerplate code into ChatGPT and request the completion for the specific interceptor code. Here is an [example](https://chat.openai.com/share/9fec2562-4a17-4b4c-8bf2-88043c9dae57). However, final adjustments may be needed. 1. **Save Action** and check for any errors. 2. The newly created interceptor will be added to the **API interceptors** list. Additonally * You can add multiple interceptors to any API call. * When the same interceptor is used by multiple APIs, you can create an [**API group**](/resources/backend-logic/create-test-api.md#grouping-api-calls) and add the interceptor under the **Advanced Group Settings**. However, you can override the interceptor for any API within the group if you wish to. Watch a video If you prefer watching a video tutorial, here's the one for you: ## FAQs[​](/resources/backend-logic/rest-api.md#faqs "Direct link to FAQs") Why is my Predefined Path not showing any options? This often happens if you added the Predefined Path but forgot to save the API call in FlutterFlow. Ensure you click Save after making any changes to your API call so FlutterFlow can properly recognize and display your predefined paths. Why am I getting a “Current variable is not valid” error? This error typically indicates that the widget isn’t receiving the data type it expects. For example, passing a list of colors directly to a text widget will trigger the error. In such cases, convert or supply the data as a string (or another compatible type) so the widget can properly display it. --- # SOAP APIs SOAP APIs (Simple Object Access Protocol) provide a standardized way to communicate between systems, typically using XML as the message format and operating over protocols such as HTTP, SMTP, and more. Unlike REST APIs, which use a flexible request/response model and typically exchange data in JSON, SOAP APIs are built around a formal contract defined by WSDL. This contract ensures strict adherence to communication standards, making SOAP APIs more rigid but also more reliable and secure—ideal for enterprise applications requiring transactional integrity and guaranteed message delivery. SOAP APIs are particularly well-suited for scenarios where robust security and detailed error handling are required, such as in financial services or telecommunications. ### Difference between SOAP APIs and REST APIs:[​](/resources/backend-logic/soap-api.md#difference-between-soap-apis-and-rest-apis "Direct link to Difference between SOAP APIs and REST APIs:") **Protocol and Message Format**: SOAP is protocol-based with XML messaging, while REST is more flexible, using HTTP methods and supporting multiple data formats like JSON and XML. **Connection Lifecycle**: SOAP operates with independent requests and responses, while REST is stateless, where each request is independent, making REST more scalable and easier to manage. **Use Case**: SOAP is preferred in scenarios where formal contracts and high security are required, while REST is more suitable for lightweight, scalable web services. * SOAP Example response * REST Example response ``` Red Dragons Silver Sharks 2-1 ``` ``` { "event": "match_score", "data": { "team1": "Red Dragons", "team2": "Silver Sharks", "score": "2-1" } } ``` ## Building an App[​](/resources/backend-logic/soap-api.md#building-an-app "Direct link to Building an App") This guide provides a step-by-step instructions on how to add and use SOAP APIs to build an example app that displays a list of countries. Upon tapping on a country name, the user is taken to a details page where the country flag is displayed. By following these instructions, you can learn how to add SOAP APIs into your app and create a basic navigation flow. The final app looks like this: What you'll learn * How to create SOAP APIs. * Creating API with dynamic data in the request body. * Parsing XML response. * How to navigate and pass data to a new page. To build such an app, you will need the following pages. 1. **HomePage**: It shows a list of all countries. 2. **CountryDetails**: Shows the country flag. Here's how you'll navigate between these pages: ![Navigation flow](/assets/images/navigation-flow-6713872bd4dceb0837937cdb61fffb33.avif) The steps to build the app are as follows: ### 1. Build UI[​](/resources/backend-logic/soap-api.md#1-build-ui "Direct link to 1. Build UI") Let's start with building the UI for both pages. #### 1.1 Home page[​](/resources/backend-logic/soap-api.md#11-home-page "Direct link to 1.1 Home page") On this page you display the list of all countries using [**ListView**](/resources/ui/widgets/composing-widgets/list-grid.md#listview-widget) and [**ListTile**](/resources/ui/widgets/composing-widgets/list-grid.md#listview-widget) widgets. ![HomePage](/assets/images/home-page-134fb34f64d264818ddd7863eef4c18a.avif) #### 1.2 Country details page[​](/resources/backend-logic/soap-api.md#12-country-details-page "Direct link to 1.2 Country details page") This page shows the country flag using the [**Image**](/resources/ui/widgets/image.md) widget. ![CountryDetails Page](/assets/images/details-page-8c62cc0c8054409306e24291f135b027.avif) ### 2. Create APIs[​](/resources/backend-logic/soap-api.md#2-create-apis "Direct link to 2. Create APIs") For building this example, we will use two APIs from Postman's [**Public SOAP APIs**](https://www.postman.com/cs-demo/workspace/public-soap-apis). Here are they: 1. [**getCountries**](https://www.postman.com/cs-demo/workspace/public-soap-apis/request/8854915-96a53688-6305-45be-ab8b-ca1d1c88f830) 2. [**getCountryFlag**](https://www.postman.com/cs-demo/workspace/public-soap-apis/request/8854915-4f5fae60-9ae1-4b77-8518-59e9143b8fb4) Before you build anything related to APIs in your app, you must create and test the APIs to make sure all the APIs are working correctly. So let's [create and test](/resources/backend-logic/create-test-api.md) these APIs in our project. #### 2.1 getCountries[​](/resources/backend-logic/soap-api.md#21-getcountries "Direct link to 2.1 getCountries") This API retrieves a list of all countries' names and codes. You can add this API by following the instructions [here](/resources/backend-logic/create-test-api.md). info It's **important** to note that you need to include the proper *Header* in your requests, such as "**Content-Type: text/xml; charset=utf-8**", and set the request *Body* type to "**Text**". Here's how you do it: #### 2.2 getCountryFlag[​](/resources/backend-logic/soap-api.md#22-getcountryflag "Direct link to 2.2 getCountryFlag") This API gets you the country's flag based on its code. You can pass the country code dynamically into the request body by creating a variable. See how to do it [here](/resources/backend-logic/rest-api.md#creating-variables). * Request body * Header ![request-body](/assets/images/request-body-a74543aa5908052ab57a91ead422d9f9.avif) ![header](/assets/images/header-5bfd5e07131507fcd5758e077529bb49.avif) ### 3. Create custom actions[​](/resources/backend-logic/soap-api.md#3-create-custom-actions "Direct link to 3. Create custom actions") The APIs you added in the previous step return the result in [XML](https://www.w3schools.com/xml/xml_whatis.asp) format, which needs to be parsed to extract relevant data or information. This can be accomplished using a [custom action](/concepts/custom-code/custom-actions.md). The custom action can utilize the '[xml](https://pub.dev/packages/xml)' package to parse the XML response and retrieve data in a format that can be easily displayed on UI widgets. For this example, you need to create two custom actions that parse the result for two APIs. Here are they: #### 3.1 parseListofCountries[​](/resources/backend-logic/soap-api.md#31-parselistofcountries "Direct link to 3.1 parseListofCountries") This custom action parses the result of [getCountries](/resources/backend-logic/soap-api.md#21-getcountries) API and gives the list of countries in a *List* variable with a *Type* *String*. Here's the code with an explanation in the comments: ``` // Automatic FlutterFlow imports import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/custom_code/actions/index.dart'; // Imports other custom actions import '/flutter_flow/custom_functions.dart'; // Imports custom functions import 'package:flutter/material.dart'; // Begin custom action code // DO NOT REMOVE OR MODIFY THE CODE ABOVE! import 'package:xml/xml.dart'; Future> parseListofCountries(String xmlResponse) async { final document = XmlDocument.parse(xmlResponse); final countryList = []; // Create an empty list to hold the countries // Find all elements with tag 'm:tCountryCodeAndName' final countryElements = document.findAllElements('m:tCountryCodeAndName'); // Loop through all the 'm:tCountryCodeAndName' elements found above for (final countryElement in countryElements) { // Extract the country code from the element final countryCode = countryElement.findElements('m:sISOCode').single.text; // Extract the country name from the element final countryName = countryElement.findElements('m:sName').single.text; // Add the country code and name to the country list as a single string countryList.add('$countryCode - $countryName'); } print(countryList); return countryList; } ``` Here's how it looks after adding: ![Custom action to parse list of countries](/assets/images/custom-action-af569d65f98ed6e56c1d1f4e08195a66.png) #### 3.2 parseCountryDetails[​](/resources/backend-logic/soap-api.md#32-parsecountrydetails "Direct link to 3.2 parseCountryDetails") This custom action parses the result of [**getCountryFlag**](/resources/backend-logic/soap-api.md#22-getcountryflag) API. It uses method chaining to navigate to the desired element and retrieve the flag URL. Here's the code with an explanation in the comments: ``` // Automatic FlutterFlow imports import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/custom_code/actions/index.dart'; // Imports other custom actions import '/flutter_flow/custom_functions.dart'; // Imports custom functions import 'package:flutter/material.dart'; // Begin custom action code // DO NOT REMOVE OR MODIFY THE CODE ABOVE! import 'package:xml/xml.dart'; Future parseCountryDetails(String xmlResponse) async { final document = XmlDocument.parse(xmlResponse); return document .getElement('soap:Envelope')! // Access the soap:Envelope element .getElement('soap:Body')! // Access the soap:Body element .getElement('m:CountryFlagResponse')! // Access the m:CountryFlagResponse element .getElement('m:CountryFlagResult')! // Access the m:CountryFlagResult element .text // Retrieve the text value of the element .trim(); // Trim any leading or trailing whitespaces } ``` Here's how it looks after adding: ![Custom action to parse country flag response](/assets/images/parse-country-flag-response-377ed062b1c56d4a256ff0c799e20325.png) ### 4. Showing a list of countries[​](/resources/backend-logic/soap-api.md#4-showing-a-list-of-countries "Direct link to 4. Showing a list of countries") You can now proceed to display the country list in *HomePage*. Here are the steps you should follow: 1. Open the HomePage. 2. Create a [page state](/resources/ui/pages/page-lifecycle.md#creating-a-page-state) variable (i.e., *countries*) to hold the list of countries. This will be used to bind data in a ListView. ![Page state variable](/assets/images/page-state-variable-38a6dd1d354b5ee0a2b784545b4d1481.png) 3. Select the page and add the following action chain. 1. The API call to [getCountries](/resources/backend-logic/soap-api.md#21-getcountries). 2. On success, [add a custom action](/concepts/custom-code/custom-actions.md) to [parseListOfCountries](/resources/backend-logic/soap-api.md#31-parselistofcountries). It's **important** to note that you must pass the result of a previous API call as a function argument and set the **API Response Options** to **Raw Body Text**. Also, add the *Action Output Variable Name*. 3. Add the [update page state](/resources/ui/pages/page-lifecycle.md#update-page-state-action) action and set the variable (i.e., *countries*) value with the output of the custom action (previously added). Ensure you keep the **Update Type** to **Rebuild Current Page**. 4. On ListView, [generate dynamic children](/resources/ui/widgets/composing-widgets/generate-dynamic-children.md) using the page state variable. 5. The page state variable stores the country name and code as a single string (e.g., Australia - AT). To display the name and code separately in a *ListTile*, we can use a [inline function](/resources/functions/utility.md#inline-function-code-expressions). To display the country name, we can use `var1.split("-")[1].trim()`, where `var1` is the current item in the list. To display the country code, we can use the same expression and replace `[1]` with `[0]`. ### 5. Navigate to the country details page[​](/resources/backend-logic/soap-api.md#5-navigate-to-the-country-details-page "Direct link to 5. Navigate to the country details page") On tapping the country name (ListView > ListTile widget), you will navigate to the *CountryDetails* page and pass the country code. This will be used to retrieve the flag of the country in the next step. To do so: 1. Select the **ListTile** widget and add an [action to navigate](/concepts/navigation/page-navigation.md#navigate-to-action) to the *CountryDetails* page. 2. Inside this action, click on the **Define** button. This will open the *CountryDetails* page, where you can define a parameter that will accept the country code. 3. After defining the parameter, open this action again and pass the country code using the code expression we used in the previous step (e.g., `var1.split("-")[0].trim()`). * Parameter on CountryDetails page * Passing country code while navigating to CountryDetails page ![Parameter on CountryDetails page](/assets/images/parameter-on-country-details-page-dd9b6607c8abbdae411027f142c886f0.png) ![Passing country code while navigating to CountryDetails page](/assets/images/passing-country-code-0108630a062cae5785f4316069325fed.png) ### 6. Show country flag[​](/resources/backend-logic/soap-api.md#6-show-country-flag "Direct link to 6. Show country flag") Whenever this page opens, it will have the country code (as a page parameter). You can use it to make an API call and get the respective country flag. Here are the step-by-step instructions: 1. Open the *CountryPage*. 2. Create a [page state](/resources/ui/pages/page-lifecycle.md#creating-a-page-state) variable with **Type** as **ImagePath** (i.e., *flagURL*) to hold the URL of the flag image. This will be used to display in the *Image* widget. ![PageState variable to hold flag URL](/assets/images/hold-flag-url-7e35a2108913fb7d71827ad82c21dfe0.png) 3. Select the page and add the following action chain. 1. The API call to [getCountryFlag](/resources/backend-logic/soap-api.md#22-getcountryflag). 2. On success, [add a custom action](/concepts/custom-code/custom-actions.md) to [parseCountryDetails](/resources/backend-logic/soap-api.md#32-parsecountrydetails). It's **important** to note that you must pass the result of a previous API call as a function argument and set the **API Response Options** to **Raw Body Text**. Also, add the *Action Output Variable Name*. 3. Add the [update page state](/resources/ui/pages/page-lifecycle.md#update-page-state-action) action and set the variable (i.e., *flagURL*) value with the output previously added custom action. Ensure you keep the **Update Type** to **Rebuild Current Page**. 4. Now simply use the page state variable to display the flag URL in the *Image* widget. ![Using page state variable to display image](/assets/images/use-page-state-to-display-04bdc5cd88b92b9be1ff101f9507e8e4.png) ## Get the example app[​](/resources/backend-logic/soap-api.md#get-the-example-app "Direct link to Get the example app") Get the clonable version of this app [here](https://app.flutterflow.io/project/soap-countries-4tbmom). --- # Streaming APIs Streaming APIs provide a continuous flow of data over a long-lived HTTP connection, enabling real-time updates for your application. Unlike REST APIs, which deliver data in response to specific requests, streaming APIs are designed to maintain an open connection between the client and the server, continuously sending data as it becomes available. This is particularly useful for applications that require live updates, such as live sports scores, stock market tickers, chat applications, and real-time notifications. This reduces latency and improves the user experience by providing immediate feedback. The most common protocol used for streaming APIs is Server Sent Events (SSE), but others like WebSockets can also be used depending on the application's requirements. ### Difference between REST APIs and Streaming APIs[​](/resources/backend-logic/streaming-api.md#difference-between-rest-apis-and-streaming-apis "Direct link to Difference between REST APIs and Streaming APIs") The primary difference between REST APIs and Streaming APIs lies in their data delivery methods: * **REST APIs**: * **Request/Response Model**: The client sends a request, and the server responds with the data. * **Connection Lifecycle**: Each request/response pair is independent, and the server closes the connection after sending the response. * **Use Case**: Suitable for applications where data doesn't change frequently and real-time updates aren't critical. * **Example response**: ``` { "event": "match_score", "data": { "team1": "Red Dragons", "team2": "Silver Sharks", "score": "2-1" } } ``` * **Streaming APIs (Server Sent Events)**: * **Continuous Data Stream**: The server maintains an open connection and continuously sends data to the client as it becomes available. * **Connection Lifecycle**: The connection remains open, allowing the server to push new data to the client without the client having to request it. * **Use Case**: Ideal for applications requiring real-time updates, such as live sports scores, real-time notifications, and live chat applications. * **Example response**: ``` event: match_score data: {"team1": "Red Dragons", "team2": "Silver Sharks", "score": "2-1"} event: match_score data: {"team1": "Red Dragons", "team2": "Silver Sharks", "score": "3-1"} event: match_score data: {"team1": "Red Dragons", "team2": "Silver Sharks", "score": "3-2"} ``` ## Example: AI Review Summary[​](/resources/backend-logic/streaming-api.md#example-ai-review-summary "Direct link to Example: AI Review Summary") Let's see how you can use streaming APIs in FlutterFlow by building an example that allows users to see an AI summary of product reviews. On page load, the app displays the AI summary in real-time, letting users watch the analysis unfold as it's being generated. The final app looks like this: The steps to build the app are as follows: 1. [Build UI](/resources/backend-logic/streaming-api.md#1-build-ui) 2. [Create API](/resources/backend-logic/streaming-api.md#2-create-api) 3. [Create page state variable](/resources/backend-logic/streaming-api.md#3-create-page-state-variables) 4. [Trigger and Parse API response](/resources/backend-logic/streaming-api.md#4-trigger-and-extract-data-from-api-response) 5. [Extract chart data](/resources/backend-logic/streaming-api.md#5-extract-chart-data) ### 1. Build UI[​](/resources/backend-logic/streaming-api.md#1-build-ui "Direct link to 1. Build UI") The user interface includes a section for the average rating, and number of reviews, followed by a detailed summary of the reviews including pros, cons, and sentiment distribution visualization. Here are key widgets to build the page: * [**Text Widget**](/resources/ui/widgets/text.md): Displays the AI-generated summary of the reviews and a list of the positive and negative points mentioned in the reviews. * [**Chart (Bar chart) Widget**](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md): Visual representation of the sentiment distribution (positive, neutral, negative) in a bar chart. ![streaming-api-example-demo.png](/assets/images/streaming-api-example-demo-76340fcccad2d986236d0ff057a0e2eb.png) ### 2. Create API[​](/resources/backend-logic/streaming-api.md#2-create-api "Direct link to 2. Create API") For building this app, we will use [OpenAI's Chat Completion API](https://platform.openai.com/docs/guides/text-generation/chat-completions-api) to generate a summary based on given reviews. Before you build anything related to APIs in your app, it's essential to create and test the APIs to ensure they work correctly. So let's [create and test](/resources/backend-logic/create-test-api.md) the Chat Completion API in our project. Once created, open the **Advanced Settings** and **enable** the **Process Streaming Response** toggle. Here's how you do it: ### 3. Create page state variables[​](/resources/backend-logic/streaming-api.md#3-create-page-state-variables "Direct link to 3. Create page state variables") In this example, to hold and display the result of the generated AI summary, you'll need two variables. 1. `summary`: This variable will hold the full text of the summary that includes the overall sentiment of the reviews, key points mentioned by customers, and lists of pros and cons. It is initialized as an empty string and will later be updated with the AI-generated text. 2. `sentimentValues`: This variable will store the sentiment distribution values. It is a list of *double* representing the number of positive, neutral, and negative reviews. **Note that**, these values will be used to provide the *Bar Values* in a bar chart. It is initialized with three zeros and will later be updated with the actual counts of positive, neutral, and negative reviews. ![streaming-page-state.png](/assets/images/streaming-page-state-dd31cbe47a26370ec079b4434bee871c.png) ### 4. Trigger and extract data from API response[​](/resources/backend-logic/streaming-api.md#4-trigger-and-extract-data-from-api-response "Direct link to 4. Trigger and extract data from API response") You can trigger the streaming API just like any other regular API. However, the method of extracting and parsing data differs from that of a standard API. Unlike non-streaming APIs, where you receive a response in an action output variable, the streaming API provides data through the following response actions: * **onMessage:** This action is triggered every time a new piece of data is received from the streaming API. You can use this action to update your UI or perform any logic with the incoming data in real-time. * **onError:** This action is triggered when there is an error in the streaming connection. You can use this action to handle errors gracefully, such as displaying an error message to the user or attempting to reconnect. * **onClose:** This action is triggered when the streaming connection is closed. You can use this action to perform cleanup tasks or to notify the user that the stream has ended. Whenever the data is received, you can access the response body via the **OnMessage > Set Variable menu > Action Parameters > OnMessageInput**. and then use the [**Response Stream Message Options**](/resources/backend-logic/streaming-api.md#response-stream-message-options) to extract the data. For this specific example, we use the *Server Sent Event Stream Data JSON* option and then use this JSON path `$['choices'][0]['delta']['content']` to retrieve the story data. Here's how exactly you do it: ### 5. Extract chart data[​](/resources/backend-logic/streaming-api.md#5-extract-chart-data "Direct link to 5. Extract chart data") The API returns a detailed summary as text, but to display counts of positive, neutral, and negative reviews on chart, you need to extract these data from the text. To achieve this, you can write a simple [custom function](/concepts/custom-code/custom-functions.md). Once the stream ends, pass the full text to the custom function to extract the relevant data and save the output in the `sentimentValues` page state variable we created earlier. Here's how you do it: tip * After saving the`sentimentValues`, it’s a good idea to remove the same data points from the generated review text to avoid redundancy. * Similarly, you can extract other data like 'pros' and 'cons' and display them the way you like. ## Response Stream Message Options[​](/resources/backend-logic/streaming-api.md#response-stream-message-options "Direct link to Response Stream Message Options") When working with Server Sent Events (SSE) in FlutterFlow, it's essential to understand how to process and handle the various components of the event messages. FlutterFlow provides several options that capture different parts of the SSE. Here are they: ### Server Sent Event Data JSON (Type: JSON)[​](/resources/backend-logic/streaming-api.md#server-sent-event-data-json-type-json "Direct link to Server Sent Event Data JSON (Type: JSON)") This field captures the result of JSON parsing. For example: ``` event: chat data: {"response": "hello", "version": 7} id: 2 ``` The Server Sent Event Data JSON would be: ``` { "response": "hello", "version": 7 } ``` **Note that** If the data is not in JSON format, it will be null: ``` event: ping data: Server time is 2024-06-28T11:52:56+00:00 id: 2 ``` The Server Sent Event Data JSON would be `null`. ### Server Sent Event Data Text (Type: String)[​](/resources/backend-logic/streaming-api.md#server-sent-event-data-text-type-string "Direct link to Server Sent Event Data Text (Type: String)") This field contains just the text of the "data" field from the SSE. If there are multiple "data" entries, they are concatenated with a new line. For example, from the event: ``` event: ping data: Server time is 2024-06-28T11:52:56+00:00 id: 2 ``` The Server Sent Event Data Text would be: `Server time is 2024-06-28T11:52:56+00:00` And from the event: ``` event: journalEntry data: Today I went to the park. data: For Lunch I had a sandwich. id: 3 ``` The Server Sent Event Data Text would be: ``` Today I went to the park. For Lunch I had a sandwich. ``` ### Server Sent Event Name (Type: String)[​](/resources/backend-logic/streaming-api.md#server-sent-event-name-type-string "Direct link to Server Sent Event Name (Type: String)") This field contains the text of the "event" field from the SSE. For example: ``` event: ping data: Server time is 2024-06-28T11:52:56+00:00 id: 2 ``` The Server Sent Event Name would be `ping`. ### Server Sent Event ID (Type: Integer)[​](/resources/backend-logic/streaming-api.md#server-sent-event-id-type-integer "Direct link to Server Sent Event ID (Type: Integer)") This field contains the text of the "id" field from the SSE, typically used to keep track of the last sent item from the server. For example: ``` event: ping data: Server time is 2024-06-28T11:52:56+00:00 id: 2 ``` The Server Sent Event ID would be `2`. ### Server Sent Event Retry (Type: String?)[​](/resources/backend-logic/streaming-api.md#server-sent-event-retry-type-string "Direct link to Server Sent Event Retry (Type: String?)") This field contains the "retry" field from the SSE, typically used to communicate to the client when to try reconnecting to the server. ### Message Text (Type: String)[​](/resources/backend-logic/streaming-api.md#message-text-type-string "Direct link to Message Text (Type: String)") This includes the entire Server Sent Event (SSE) message, including new lines and fields ('data', 'event', 'id', 'retry'). For example: ``` event: ping data: Server time is 2024-06-28T11:52:56+00:00 id: 2 ``` ## FAQs[​](/resources/backend-logic/streaming-api.md#faqs "Direct link to FAQs") Why does it show 'null'? The "null" value appears in the Server Sent Event Data JSON field when the data is not in JSON format. For instance, the following event data is not in JSON format: ``` event: ping data: Server time is 2024-06-28T11:52:56+00:00 id: 2 ``` The Server Sent Event Data JSON will be `null` because the data cannot be parsed as JSON. You can fix this by using the following expression inside the [Inline Function](/resources/functions/utility.md#inline-function-code-expressions) to handle the `null` case: ``` responseData ?? '' ``` This expression ensures that if `responseData` is `null`, it will return an empty string instead. --- # Backend Query **Backend Query** helps you to trigger a query automatically whenever a user navigates to the page containing the query. You can set a Backend Query on a particular widget or an entire page. The information retrieved using the Backend Query can be used in any widget present inside. ## Types of Query[​](/resources/backend-query.md#types-of-query "Direct link to Types of Query") We offer you the following types of Backend Queries that you can specify on any widget or page. * [**Query Collection or Table**](/resources/backend-query/query-collection.md)**:** This query type is used to fetch a single record or a list of records from a Firestore Collection or Supabase Table. * [**Document from Reference**](/resources/backend-query/document-from-reference.md)**:** Used to retrieve the details from a document reference. * [**API Call Query**](/resources/backend-query/api-call-query.md)**:** Used to initiate an API call. * [**SQLite Query**](/resources/backend-query/sqlite-query.md): Used to execute the SQL statements. * [**Algolia Search**](/resources/backend-query/algolia-search-query.md)**:** Used to trigger an Algolia search on a Firestore Collection. ## Difference between Actions & Backend Query[​](/resources/backend-query.md#difference-between-actions--backend-query "Direct link to Difference between Actions & Backend Query") | **Aspect** | **Actions** | **Backend Queries** | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | **Trigger** | Triggered by user interactions such as taps, double taps, or long presses on widgets, or they can be executed automatically on page load. | Automatically triggered when the user navigates to a page or widget containing the query. | | **Usage** | Can be used to navigate between pages, show messages, update variables, make API calls, and more. | For apps needing instant updates like chat or live scores, Backend Queries can auto-refresh the UI with the latest database changes. | | **Multiplicity** | You can specify multiple actions on the same widget. | Only one Backend Query can be specified on a particular widget or page. | | **Conditional Execution** | Can be conditional, meaning they can execute different actions on certain conditions. | - | | **Caching** | - | Can include caching mechanisms to improve app performance by reducing the number of server calls and providing offline access to data. | | **Handling States** | - | Often involve handling loading states and empty states, as the data fetching process can take time and might not always return results. | | **Data Fetching** | - | Only used to fetch data from a backend. | ## Change loading indicator[​](/resources/backend-query.md#change-loading-indicator "Direct link to Change loading indicator") While the backend query is busy retrieving results, it shows the default *Project Theme Loading Indicator* (which you can change from [**Navigation menu**](/flutterflow-ui/builder.md#navigation-menu) *> Theme Settings > Design System > Loading Indicator*.) However, if you want to replace this with a custom loading indicator in a specific backend query, follow the instructions below: To change the loading indicator: 1. Ensure you have added a backend query. 2. Open the **Backend Query** section (on the right side) and scroll down to the **Backend Query Loading Widget**. Open it by clicking on the arrow icon. 3. Set the **Loading Widget Type** to **Image**. You can also choose a [**Component**](/resources/ui/components/creating-components.md) if you have already designed a loading component. 4. Enable the **View in UI Builder**. This allows you to see your custom loading indicator on canvas (before you actually run the app). 5. Choose the **Image Type**, [add the image](/resources/ui/widgets/image.md#image-type), and adjust its **Padding** and **Width**. 6. To show the indicator in the center, turn on the **Center Image** toggle. 7. Run the app, and your custom loading indicator will appear while the data is being loaded. ## Copy Query[​](/resources/backend-query.md#copy-query "Direct link to Copy Query") Sometimes, you might want to display the same list of items with a little modification. For example, showing all Todo items and completed Todo items. In such a case, you can copy-paste the entire backend query to speed up the building process. This is helpful, especially when you have a complex backend query. To copy-paste the query: 1. Select the widget (e.g., ListView, GridView, etc.) where you have already added the backend query. 2. Select the **Backend Query** tab, and click the **Copy** button. 3. Now, select the widget (where you want to add the query), move to the **Backend Query** tab, and click **Paste Backend Query** button. 4. Click **Confirm**. ## Move query to parent widget[​](/resources/backend-query.md#move-query-to-parent-widget "Direct link to Move query to parent widget") You might want to utilize the same backend query on multiple widgets on a page. But if you do so, you end up making redundant server calls for the same thing. So, instead of copying it on every widget, you can move the query to any parent widget. The existing widgets will then use a generated variable derived from the parent widget's query. To move the query up to any parent widget, simply select the up arrow button and select the parent widget you would like the query to move to. ## Displaying empty list widget[​](/resources/backend-query.md#displaying-empty-list-widget "Direct link to Displaying empty list widget") The *Empty List* widget is a widget used to display a message when there are no items in a list. This widget helps to provide a better user experience by displaying a message instead of just an empty screen. To display the empty list widget: 1. Ensure you have added a backend query on any scrollable widget, such as **ListView**, **GridView**, **Column**, **Row**, DataTable, and **StaggeredView**. 2. Select the scrollable widget (on which you have added the backend query), move to the properties panel, and turn on the **Show Empty List Widget**. 3. Set **Widget Type** to **Image** or **Component**. The further options are available based on what you choose. 4. Try toggling the **View in UI Builder**. This allows you to see your empty list widget on canvas (before you actually run the app). 5. You can also control the size and centering of the widget using the available options. ## Backend Query Caching[​](/resources/backend-query.md#backend-query-caching "Direct link to Backend Query Caching") Backend query caching refers to the process of storing the result of a backend query in a cache so that subsequent queries for the same data can be served directly from the cache rather than making a new query to the backend. Caching a query can bring significant benefits to your app, including improved performance and reduced server load. Additionally, caching can enable your app to function offline by serving cached results when there is no internet connection available. For example, an e-commerce app can cache product data, such as product descriptions, prices, and images, to avoid making unnecessary API calls for each page load. note Caching backend queries works for all [types of queries](/resources/backend-query.md#types-of-query). Single time Query For Firebase queries, enable Single Time Query if you want the query to fetch data only once. Otherwise, the query operates in real-time, updating automatically as soon as the data changes. ### When to cache[​](/resources/backend-query.md#when-to-cache "Direct link to When to cache") In general, any data that is static, slowly changing, or read more often than they are updated can be cached to improve performance and reduce the load on the server. A few examples are 1. Static content such as images and videos. 2. Configuration data such as application settings or system parameters. 3. Data that is expensive to compute, such as complex reports or analytics. ### When NOT to cache[​](/resources/backend-query.md#when-not-to-cache "Direct link to When NOT to cache") Sometimes, it's not a good idea to cache the backend query. Here are some examples: 1. Large amounts of data can cause performance issues and may not be appropriate. 2. Sensitive or confidential data should not be cached, as it could lead to unauthorized access. 3. Frequently changing data, such as in real-time or near real-time scenarios, caching may not be appropriate as the cached data could quickly become stale or outdated. 4. Critical response time where the data needs to be up-to-date and accurate at all times. ### Example[​](/resources/backend-query.md#example "Direct link to Example") Let's see how to cache a backend query with an example app that shows a list of employees on the first page and employees' details on the second page. On the employee details page, the data is retrieved from a backend query and read more often than they are updated, so it's a good candidate to cache. To improve performance, you can cache the data on the details page so that it can be quickly retrieved and displayed to users. Here is how it looks: note In the visual above, see how the loading indicator appears for the first time a query is made on the page. However, subsequent queries will retrieve the result from the cache, and the loading indicator will not be displayed again. To cache the backend query: 1. Ensure you have added a backend query. For this example, to retrieve data from a Firebase document, we add a backend query at the page level as *Single Time Query*. We use a document reference to get the employee details. ![example-bq.png](/assets/images/example-bq-2e8203dff7f01fcac18d3e7ea5c25d0a.png) Querying employee details using document reference 2. Open **Query Cache Settings** and **Enable Query Caching**. 3. Determine the **Scope** of the cache. If you set it to **App Level** and the *exact* same query is made on any other page of the app, it will display the result from the cache. However, if you set the **Page Level**, the cached result will be used only on that page if the query is made multiple times on the same page. 4. If the current query is completely new/different, create a **Query Name**. If not, and you want to use the cached result of this query (that might be created somewhere else), select the name from the list. 5) If we leave this example here, we'll have data inaccuracy issues. That means when any employee data is cached, the same data will be used for all employees, which is not what we want. We want to cache data for all individual employees. To do so, we can set the **Unique Key**. Here the unique key can be the employee id or the document reference. * Data inaccuracy without Unique Key * Adding Unique Key 6. At this point, we have enabled the caching, but we still have one problem. Once the query is cached, it will be used forever, although we update the data in our backend. This is because we are not clearing or invalidating the cache at the appropriate time. To properly invalidate the cache, you can use the **Should Override Cache** property OR **Clear Query Cache** action. This helps you remove the cached data that has become stale or outdated. 1. The *Should Override Cache* property accepts a boolean (True/False). That means we can provide a variable (e.g., an *App State* variable named *isCacheOverride)* that knows when to override the cache. So create one and set it here. 2. Create one more *App State* variable, something like *lastCacheTime,* and set the current time as default. This will be used to save the time of results retrieved from the backend. You'll better understand how helpful it is in the logic we add in the next step. ![img\_3.png](/assets/images/img_3-2cb11f7171f5524d3bc5c77565c48d51.png) Setting Should Override Cache to App State variable 7. Now, we must add a logic that determines whether to override the cache (every time when the page is loaded) and set the *isCacheOverride* variable accordingly. Here is how it goes: 1. First, check if the *lastCacheTime* is set or not. If not, set the current time to it. 2. Then the idea is to create one custom action that checks if the current time is more than 30 minutes ahead of the *lastCacheTime*. **Note** that 30 minutes is the cache expiration time, and here, it is kept minimum just for simplification purposes; It's important to carefully choose the appropriate expiration time for your cache based on the nature of your data. 3. if **True** : 1. [Update](/resources/data-representation/app-state.md#update-app-state-action) the **lastCacheTime** with the current time and **isCacheOverride** to True. Make sure you keep the **Update Type** to **Rebuild Current Page** so that the backend query is made again, which will invalidate the cache and display updated data. 2. You can also add an action to [Clear Query Cache](/resources/backend-query.md). 3. Continuing the same action flow, [wait](/resources/time-based-logic/wait-action.md) for 1 sec and again update **isCacheOverride** to **False** so that the cached result won't override on page load for the next 30 min. Note **Note** that in this example, we use both the *Clear Query Cache* action and the *Should Override Cache* property to clear or invalidate the cache. Although both perform the same task, it's generally considered better practice to explicitly *Clear Query Cache* rather than relying on the *Should Override Cache* bool. However, in certain cases, you may want to override the cache conditionally instead of with an explicit action, so the option is there. Here is how the custom function looks in case you want to check: ``` bool isOverrideCacheAction(DateTime cacheTime) { // Add your function code here! return DateTime.now().difference(cacheTime).inMinutes > 30; } ``` ![custom-func-cache-override.png](/assets/images/custom-func-cache-override-d3687d8170b91d2b91af9e9b031b0b1f.png) Custom function to know if last cache time is more than 30 minutes tip You can have a separate *lastCacheTime* variable for all the employee records to avoid any conflict with others. Failing to do so may keep on updating the common *lastCacheTime* variable, and you might not see updated data. For example, creating a list of JSON that contains the id and *lastCacheTime* of an employee might help. Like this: `{ "id": 1, "lastCacheTime": '2023-03-22T14:30:00+00:00', }` ### Clear Query Cache \[Action][​](/resources/backend-query.md#clear-query-cache-action "Direct link to Clear Query Cache \[Action]") This action provides a simple way to clear the query cache, which can be helpful in situations where the cached data is no longer accurate or needs to be refreshed. By executing this action, you reset the query cache, allowing the app to fetch and display the most up-to-date data. tip This can help improve app performance and ensure users see the most recent information available. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the [properties panel](/flutterflow-ui/builder.md#properties-panel) (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Clear Query Cache** (under *State Management*) action. 4. Determine the **Scope** of the cache, whether it lives at the **App Level** or **Page Level**. 5. Set the **Query Name** to the one you gave while adding the query cache. 6. If you have set the **Unique Key** while caching a query, you should add the same key here as well. This ensures that the cache will be removed only for specific data. --- # Algolia Search Query You can set up an **Algolia Search Backend Query** to automatically trigger a search as soon as the user navigates to the page. This allows users to find documents within a Firestore Collection by simply providing a search term. This approach is particularly useful for enhancing the user experience, such as dynamically refreshing search results in a **ListView** as the user types in a TextField, like real-time updates. Prerequisites Before proceeding, ensure that you have **completed the [Algolia integration](/integrations/search/algolia-search.md#algolia-integration)** in FlutterFlow. To add an **Algolia Search Query**, begin by selecting the scrollable widget that will fetch the results, such as a **ListView**. In the **Properties Panel**, navigate to the **Backend Query** tab, click on **Add Query**, and set the **Query Type** to **Algolia Search**. Next, configure the search parameters: for **Firebase Collection**, select the Firestore collection you intend to search; for **Search Term**, choose **From Variable** and select the TextField's value (e.g., **Widget State > \[Your TextField]**); and specify the optional **Max Results** to determine the number of search results. --- # API Call Query You can use the **API Call Query** to trigger an API call automatically as soon as the page or widget is loaded. This is helpful if you want to retrieve the data from an API call and display it on a page or widget. For example, showing a list of items in a ListView, showing users details on several Text widgets. Prerequisites Before you add this query, ensure you [create an API call](/resources/backend-logic/rest-api.md) in your project ## Adding API Call query[​](/resources/backend-query/api-call-query.md#adding-api-call-query "Direct link to Adding API Call query") Adding API call query comprises the following steps: 1. [Querying API call](/resources/backend-query/api-call-query.md#1-querying-api-call) 2. [Showing query data in UI element](/resources/backend-query/api-call-query.md#2-showing-query-data-in-ui-element) ### 1. Querying API call[​](/resources/backend-query/api-call-query.md#1-querying-api-call "Direct link to 1. Querying API call") Go to your project page and follow the steps below to define an **API Call** backend query: 1. Select the **widget** (or page) on which to apply the query. 2. Select **Backend Query** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu). 3. Select the **Query Type** as ***API Call***. 4. Choose the API **Group or Call Name** from the dropdown. It would display all the API Calls created in your project. 5. If your API call requires variables (e.g., auth token, query parameters, user id, etc.), pass their value by clicking on the **+ Set Additional Variable** button. 6. Click **Confirm**. ### 2. Showing query data in UI element[​](/resources/backend-query/api-call-query.md#2-showing-query-data-in-ui-element "Direct link to 2. Showing query data in UI element") Once you have the API Call query defined, you can use the data retrieved from the query to display on widgets present inside. Follow the steps below: 1. Select the **widget** (e.g., `Text`) on which you want to display the data. 2. From the [Properties Panel](/flutterflow-ui/builder.md#properties-panel), select **Set from Variable**. 3. Select the **Source** as the **YOUR\_API\_CALL\_NAME Response**. 4. Set the **API response Options** to **JSON Body**. 5. Set the **Available Options** to **JSON Path**. 6. Set the **JSON Path Name** to either the custom JSON path or use the already created JSON path. See how to [**create a JSON path**](/resources/backend-logic/rest-api.md#add-json-predefined-path). 7. Click **Confirm**. --- # Document from Reference This backend query would help you in retrieving information from a document reference. You will require the **Document from Reference** query if you have passed a document reference to a different page of the app and want to retrieve the actual document information from the reference. Prerequisites In order to use this backend query, you should have: * Completed all the steps of [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) for your project. * At least one **Firestore Collection** is defined in your project. ## Defining the Query[​](/resources/backend-query/document-from-reference.md#defining-the-query "Direct link to Defining the Query") Go to your project page on FlutterFlow and follow the steps below to define a **Document from Reference** backend query: 1. Select the **widget** (or page) on which to apply the query. 2. Select **Backend Query** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu). 3. Select the **Query Type** as ***Document from Reference***. 4. Choose a **Collection** from the dropdown to which the document reference belongs. 5. Select the **Source** as the record reference name. ## Using Query Data[​](/resources/backend-query/document-from-reference.md#using-query-data "Direct link to Using Query Data") The document information retrieved from the backend query can now be set on the widgets present inside. Follow the steps below: 1. Select the **widget** (eg, `Text`) on which you want to set the record data. 2. From the [Properties Panel](/flutterflow-ui/builder.md#properties-panel), select **Set from Variable**. 3. Choose the **Source** as the record variable. 4. Under **Available Options**, select a field name. 5. You can also specify a **Default Value** (it is used if the record field is empty). 6. Click **Save**. You can follow similar steps for using the record data on the other widgets as well. --- # Query Collection / Table Quering Firestore Collection or Supabase Table helps you to retrieve a record (or a list of records) automatically whenever a user navigates to the page containing the query. The information that is present in the record can be used to update any widget present inside. Prerequisites * To query Firestore collection, complete the [**Firebase setup**](/integrations/firebase/connect-to-firebase.md) and have some data in a [**Collection**](/integrations/database/cloud-firestore/creating-collections.md). * To query Supabase table, complete the [**Supabase**](/integrations/supabase/setup.md) Setup and have some data in a [**table**](/integrations/supabase/setup.md#create-tables-in-supabase). ## Defining the Query[​](/resources/backend-query/query-collection.md#defining-the-query "Direct link to Defining the Query") Go to your project page on FlutterFlow and follow the steps below to define a **Query Collection** backend query: 1. Select the **widget** (or page) on which to apply the query. 2. Select **Backend Query** from the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) (the right menu). 3. Select the **Query Type** as ***Query Collection***. 4. Choose the Firestore **Collection** to use for performing the query. 5. Under **Query Type**, select either ***List of Documents*** (returns a list of document references) or ***Single Document*** (returns only one document reference). 6. If you have selected the **List of Documents**in the previous step, you can set a **Limit** to the maximum number of documents returned. 7. If you want to apply any **filter** for retrieving the documents, click **+ Filter** button. Select a **Field Name** that you want to use as the filter, choose a **Relation** ( eg, `Equal To`, `Greater Than`), and then select the **Value Source** (either as a `Specific Value` or `From Variable`) with which the relation is to be checked. 8. You can also set the **order** in which the documents should be returned, click **+ Order By** button. Select a **Field Name** to be used for ordering, and choose the **Order** to be either `Increasing` or `Decreasing`. 9. Below are some optional settings that you can configure based on your requirements: * **Single Time Query**: When this is disabled, the query results will automatically refresh whenever documents or rows are created, updated, or deleted. However, for **Supabase**, this option is enabled by default, meaning the query will run only once. To enable real-time updates, you must turn it off. * **Ignore Empty Filter Values**: Disabled by default, meaning the query will attempt to find documents with empty text fields if any filter value is empty. When enabled, the query will ignore fields with empty filter values instead. * **Filter on Null Values**: By default, if any filter value is null, the query will ignore that filter. Enabling this option will include null filters in the query. * **Enable Infinite Scroll**: To implement infinite scrolling, enable this option and follow the instructions here. 10. Click **Confirm**. 11. If the selected query returns a list of documents and if it's applied to any flexible widget (like `Column`, `Row`, or `ListView`) then FlutterFlow will generate the children widgets dynamically. A dialog will be displayed with a similar message, click **Confirm**. info The instructions to query a Supabase table are almost the same, except that for **Query Type**, you should select **Supabase Query**. Limitations of Supabase Streaming with Filters When using Supabase query with real-time updates enabled, you have the following limitations: * **Only One Filter is Supported:** Supabase streaming supports only a single filter. Combining multiple filters (e.g., `isActive = true AND city = 'Los Angeles'`) is not allowed. * **Delete Events are not Filterable:** Streaming queries do not detect deletions, even if the deleted row matches the filter condition. For example, If you are streaming rows with the filter `city = 'New York’` and a row is deleted, the query output will not reflect the deletion. * **Updates that remove Rows from Filters are not Tracked:** Changes that make a row no longer match the filter condition (e.g., updating `isActive` from `true` to `false`) will not trigger an update in the query output. For more details, refer to the limitations mentioned in the [**official Supabase docs**](https://supabase.com/docs/guides/realtime/postgres-changes?queryGroups=language\&language=js\&queryGroups=database-method\&database-method=dashboard#delete-events-are-not-filterable). ## Using Query Data[​](/resources/backend-query/query-collection.md#using-query-data "Direct link to Using Query Data") The documents retrieved from the backend query can be used to set the record values to the widgets present inside. Follow the steps below to use the document record data: 1. Select the **widget** (eg, `Text`, `Image`, or `ToggleIcon`) on which you want to set the record data. 2. From the [Properties Panel](/flutterflow-ui/builder.md#properties-panel), select **Set from Variable**. 3. Choose the **Source** as the record variable (the variable gets automatically generated when you add the Collection query). 4. Under **Available Options**, select a field name from the dropdown. 5. You can also specify a **Default Value** (it is used if the record field is empty). 6. Click **Save**. You can follow similar steps for using the record data on the other widgets as well. * Display Data from Firestore Collection * Display Data from Supabase Table ## FAQs[​](/resources/backend-query/query-collection.md#faqs "Direct link to FAQs") Why aren't real-time updates working for my table in Supabase project? First, ensure that the **Single Time Query** option is disabled in the query where you've added it. Then, verify that the real-time feature is enabled for your table in Supabase project. You can find this option in the top-right corner of the table viewer. ![enable-realtime-updates-sb-table.avif](/assets/images/enable-realtime-updates-sb-table-95fa64a0cdbd78f1e79188e5bf7b1185.avif) Additionally, you can enable real-time updates when creating a new table. ![enable-realtime-updates-sb-table.avif](/assets/images/enable-realtime-updates-sb-table-2-7d1cd5f9ba25c093103146d03d897787.avif) --- # SQLite Query SQLite Query can be set up to automatically execute SQL statements as soon as a page or widget loads. This feature is useful for fetching data from the database to display on a page or widget, such as populating a ListView with items or showing user preferences in Text widgets. ![img\_4.png](/assets/images/img_4-47bd29266be6894e21641f3871e9798b.png) Prerequisites Before you add this query, ensure you configure the database and define the query. Check detailed instructions [here](/integrations/database/sqlite.md). ## Adding SQLite query[​](/resources/backend-query/sqlite-query.md#adding-sqlite-query "Direct link to Adding SQLite query") Let's see how to display a list of items from the database using the SQLite query. Here are the steps: 1. [Add query](/resources/backend-query/sqlite-query.md#1-add-query) 2. [Showing query data in UI element](/resources/backend-query/sqlite-query.md#2-showing-query-data-in-ui-element) ### 1. Add query[​](/resources/backend-query/sqlite-query.md#1-add-query "Direct link to 1. Add query") Go to your project page and follow the steps below to define an SQLite query: 1. Select the **widget** (or page) on which to apply the query. 2. Select **Backend Query** from the Properties Panel (the right menu). 3. Click **Add Query** and set the **Query Type** to **SQLite Query**. 4. Select the **Query Name**. (Only *Read Queries* will be displayed here.) 5. Click **Confirm**. ### 2. Showing query data in UI element[​](/resources/backend-query/sqlite-query.md#2-showing-query-data-in-ui-element "Direct link to 2. Showing query data in UI element") Once you have the SQLite query defined, you can use the data retrieved from the query to display on widgets present inside. Follow the steps below: 1. Select the **widget** (e.g., `Text`) on which you want to display the data. 2. From the Properties Panel, open the **Set from Variable** menu **>** select **\[your query name] Row** **>** select the column data that you want display here. 3. Click **Confirm**. --- # Control Flow Concepts In app development, control flow refers to the order in which individual statements, instructions, or function calls are executed or evaluated. Proper control flow ensures that your app behaves as expected under various conditions and user interactions. This involves understanding and implementing **conditionals**, managing **sequential and parallel** logic flows, handling **blocking and non-blocking** actions, and deciding when and how to execute specific actions based on certain criteria. In this section, we will explore various control flow concepts and how they can be effectively implemented in FlutterFlow to create dynamic, responsive, and efficient applications. ## Conditional[​](/resources/control-flow-concepts.md#conditional "Direct link to Conditional") One of the fundamental aspects of control flow is the use of conditionals, which allow your app to make decisions and execute different blocks of code based on specific criteria. Conditional statements are expressions that evaluate to either true or false. Depending on the result of these evaluations, different logic sequences are executed. The primary conditional statements are `if`, `if-else`, and `else`. * **`if` Statement:** The if statement evaluates a condition and executes a block of code if the condition is true. The if statement evaluates a condition and executes a block of code if the condition is true. ![if-condition.png](/assets/images/if-condition-46b2d18ed56b7c74168db859e37fe0ae.png) * **`if-else` Statement:** The if-else statement provides an alternative block of code to execute if the condition is false. ![if-else-condition.png](/assets/images/if-else-condition-8a738666792cecca224759a3cd726e51.png) Here, if `userIsLoggedIn` is true, the app will show a welcome message. Otherwise, it will prompt the user to log in. * **`else if` Statement:** The `else if` statement can be used to check multiple conditions sequentially. ![if-elseif-condition.png](/assets/images/if-elseif-condition-8606606c66f1fe359dcf7d42f61183ca.png) This example demonstrates multiple conditions. If `userIsLoggedIn` is true, it shows a welcome message. If not, it checks if `userIsGuest` is true and shows a guest message. If neither condition is met, it prompts the user to log in. ### Implementing Conditionals[​](/resources/control-flow-concepts.md#implementing-conditionals "Direct link to Implementing Conditionals") In FlutterFlow, you can implement conditional logic in two primary ways: * **[When Setting Properties](/resources/functions/conditional-logic.md#setting-widget-properties-with-conditional-logic)** In FlutterFlow, you can set properties of widgets conditionally. For example, you might want to change the color of a button based on a variable's value. You can use conditional expressions to dynamically set these properties during runtime. * **[Conditional Actions](/resources/functions/conditional-logic.md#conditional-actions)** You can also perform conditional actions in FlutterFlow, where certain actions are executed only if specified conditions are met. This is useful for implementing logic like navigating to different pages based on user input or showing/hiding widgets. Example: If the user clicks a button and a form is valid, navigate to the next screen; otherwise, show an error message. info Check out the [**complete guide**](/resources/functions/conditional-logic.md) here. Are you looking to learn about implementing conditional UI instead? Check out our **[Responsiveness 101](/concepts/layouts/responsive.md)** guide instead. ## Sequential vs Parallel Logic Flow[​](/resources/control-flow-concepts.md#sequential-vs-parallel-logic-flow "Direct link to Sequential vs Parallel Logic Flow") * **Sequential Logic Flow**: Actions are executed **one after the other**. Each action waits for the previous one to complete before starting. This is useful for tasks that depend on the outcome of previous actions. **Example:** Submitting a form, waiting for a server response, and then showing a confirmation message. * **Parallel Logic Flow** Multiple actions are executed at the **same time**, independently of each other. This is useful for tasks that can be done simultaneously and do not depend on each other's outcomes. **Example:** Loading data from multiple sources simultaneously to speed up the data fetching process. ![parallel-sequential.png](/assets/images/parallel-sequential-14310ce3eccf7c5d31ebd268d3ddffb1.png) ## Asynchronous Functions[​](/resources/control-flow-concepts.md#asynchronous-functions "Direct link to Asynchronous Functions") Asynchronous functions are operations that do not complete immediately and may finish at a future time due to network delays or long computation times. They can be made **blocking** or **non-blocking** depending on the use case. Some examples of asynchronous operations include: * **Network requests** (e.g., fetching data from an API) * **Database operations** (e.g., reading or writing data) * **Long-running computations** (e.g., complex calculations) * **Animations** (e.g., transitions, widget animations) ### Blocking Actions[​](/resources/control-flow-concepts.md#blocking-actions "Direct link to Blocking Actions") Blocking actions are actions that halt the execution of subsequent actions until they are completed. These actions typically involve operations that take time, such as network requests or animations. Generated Code In the **generated code**, FlutterFlow uses the `await` keyword to pause the execution of an asynchronous function until the operation completes before proceeding to the next function. This approach is commonly used to handle asynchronous functions, ensuring that each operation finishes before the subsequent one begins. In the following example from **generated code**, the code **awaits** on `actions.getRandomIntAfterWait()` because it is an asynchronous function that takes around 2 seconds to complete and provide a result (in this case, a random integer). ``` _model.result = await actions.getRandomIntAfterWait(); _model.text1Value = _model.result.toString(); ``` The result of the `actions.getRandomIntAfterWait()` is stored in `model.result` variable and then the result then set to a Text widget using the Page State variable `text1Value`. ### Non-Blocking Actions[​](/resources/control-flow-concepts.md#non-blocking-actions "Direct link to Non-Blocking Actions") Non-blocking actions, on the other hand, allow the program to continue executing other subsequent tasks while waiting for the initial actions to complete in the background. Generated Code In the **generated code**, when an asynchronous function is made **non-blocking**, FlutterFlow removes the `await` keyword. This means the subsequent function will not wait for the asynchronous action to complete and will move to the next action immediately. The previous example will no longer work because it doesn't await the asynchronous function `actions.getRandomIntAfterWait()`. As a result, the variable `model.result` may not be ready or available when `_model.text1Value = _model.result.toString();` is executed. ``` _model.result = actions.getRandomIntAfterWait(); _model.text1Value = _model.result.toString(); // will throw errors ``` To ensure proper execution, make only those actions non-blocking whose subsequent actions do not depend on the results from these initial functions. ## Non-Blocking vs Parallel Actions[​](/resources/control-flow-concepts.md#non-blocking-vs-parallel-actions "Direct link to Non-Blocking vs Parallel Actions") | Non-Blocking Actions | Parallel Actions | | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Allows the subsequent action to run **immediately** after the current one without waiting for the current action to complete. | Allows users to run two or more actions at the **same time** independently. | | **Only asynchronous** functions can be made non-blocking. | **Both asynchronous and synchronous** functions can be included in parallel actions. | | Ideal for tasks where the result of the action is not immediately needed by the next action. | Ideal for independent tasks that can be executed simultaneously to improve efficiency. | | Ensures the app remains responsive by not waiting for long-running tasks. | Helps in reducing overall execution time by performing multiple tasks concurrently. | | **Example**: Fetching data in the background while allowing user interaction. | **Example**: Loading data from two APIs simultaneously to save time. | --- # Control Flow & Logic Control flow in programming refers to the order in which individual statements, instructions, or function calls are executed or evaluated. Proper control flow is crucial for determining how your app responds to user inputs and events. Here are some key elements: * **[Conditional Flows:](/resources/control-flow-concepts.md)** These include `if`, `else if`, and `else` flows that allow your app to make decisions based on certain conditions. For example, you might check if a user is logged in and then show different content based on their authentication status. * **[Loops:](/resources/functions/loops.md)** Loops allow your app to repeat a sequence of logic multiple times. This is useful for tasks like iterating through a list of items or retrying a failed operation. * **[Event Handling:](/resources/functions/action-flow-editor.md#action-triggers)** In certain cases, you will execute functions that are triggered by specific events such as user interactions (e.g., taps, swipes) or system events (e.g., page load, on focus change). Understanding how to handle such events effectively ensures that your app reacts appropriately to user interactions or events. **Logic** or **Functions** refer to the core operations and behaviors that determine how an app responds to user actions and interacts with data. This could include: * **Business Logic:** This is the part of the app that manages the rules and processes of the real world. For example, in an e-commerce app, it handles tasks like processing orders, calculating prices, and managing inventory. * **User Interface Logic:** This controls how the app looks and interacts with users. It includes tasks like validating forms, navigating between screens, and updating content based on user actions. * **Data Logic:** This manages the app's data. It includes tasks like fetching, storing, updating, and deleting data from databases or via APIs. Let's dive into few more key concepts: ## Functions[​](/resources/control-flow-overview.md#functions "Direct link to Functions") A function is a block of code designed to perform a specific task. Functions can be reused throughout your application to perform common tasks efficiently. ### Triggers or Running a Function[​](/resources/control-flow-overview.md#triggers-or-running-a-function "Direct link to Triggers or Running a Function") Functions can be executed in various ways: they can be called from properties within the app, such as performing a quick calculation or number formatting before setting the final value to a variable, or concatenating strings before setting the string to a text widget. Functions can also run in response to specific events, such as a button click or a page load. ### Types of Functions[​](/resources/control-flow-overview.md#types-of-functions "Direct link to Types of Functions") There are different types of functions you can use in your app. Some examples in FlutterFlow are: * **[Built-in Utility Functions](/resources/functions/utility.md):** Functions that perform general utility tasks, such as formatting data or performing calculations. In FlutterFlow, you can use [**Inline Function**](/resources/functions/utility.md#inline-function-code-expressions) for simple data manipulation tasks or use the **[Combine Text](/resources/functions/utility.md#combine-text)** built-in function to concatenate strings. * **[Actions](/resources/functions/action-flow-editor.md):** Sequence of Logic performed in response to user interactions. For example: * **[Updating State Variables:](/concepts/state-management.md)** Functions that modify the current state or data of the app, page, or component. * **Widget-specific Functions:** Functions applicable to various widgets that need specific actions, such as scrolling to an item in a ListView, clearing text fields, or calling third-party integration functions. * **[Custom Actions:](/concepts/custom-code/custom-actions.md)** More complex actions written in **Flutter & Dart** that can be added as a node to the action flow editor. * **[Navigation:](/concepts/navigation/overview.md)** Functions that handle the movement between different pages or screens within your app, including opening bottom sheets or dialogs. In FlutterFlow, such functions can either run automatically after certain related operations, such as Login/Create Account, or they can be added as individual **Actions** if the developer enables it. * **[Backend Queries:](/resources/backend-query.md)** Functions that interact with your database or external services to retrieve or manipulate data. * **[Custom Functions:](/concepts/custom-code/custom-functions.md)** Complex manipulation code written in **Dart**, used to set properties of a widget or an action. ## --- # Overview Data representation is a fundamental concept in app development. It refers to the methods and structures used to store and manipulate the data. The way data is structured can greatly influence how efficiently an app performs tasks. ## Variable[​](/resources/data-representation.md#variable "Direct link to Variable") In FlutterFlow, variables are key to managing dynamic data, ensuring your app remains interactive and responsive. They enable you to capture user inputs, track changes, and share data across different parts of your app. info Dig deeper into **[variables and variable scopes](/resources/data-representation/variables.md)**. ## Data types[​](/resources/data-representation.md#data-types "Direct link to Data types") Data types are used to define the kind of data that variables can store and manipulate within your app. Managing data types correctly is crucial for ensuring that your app functions as intended, particularly when handling user inputs, storing data, and interacting with databases. info Learn more about primitive and composite data types in this [**detailed guide**](/resources/data-representation/data-types.md) and then create your own **[custom data type](/resources/data-representation/custom-data-types.md)**. ## Data mutability[​](/resources/data-representation.md#data-mutability "Direct link to Data mutability") All variables in FlutterFlow are mutable. This means you can change their values at runtime based on user interactions or other events in your app. FlutterFlow also supports immutable data, such as [**Constants**](/resources/data-representation/constants.md) that cannot be changed once they have been set. ## Global Properties[​](/resources/data-representation.md#global-properties "Direct link to Global Properties") Global properties in FlutterFlow are built-in variables that you can use across your app, but they cannot be created or modified by users. Learn how to leverage these [**predefined properties**](/resources/data-representation/global-properties.md) to simplify common tasks. ## Encapsulation[​](/resources/data-representation.md#encapsulation "Direct link to Encapsulation") Encapsulation is a key concept in object-oriented programming (OOP). It bundles the data (fields) and the methods (functions) to manipulate the data. It also limits direct access to some data to prevent accidental changes. This concept is essential in improving security and functionality by managing data access and modification. ### How Encapsulation is achieved in FlutterFlow[​](/resources/data-representation.md#how-encapsulation-is-achieved-in-flutterflow "Direct link to How Encapsulation is achieved in FlutterFlow") FlutterFlow supports the principles of encapsulation through its visual development environment. Let’s understand this with some examples: 1. **Custom Widgets and Components**: In FlutterFlow, you can create custom widgets or use built-in widgets that encapsulate specific functionalities. These widgets can include both logic and UI elements that are bundled together. For example, if you are creating a user profile page, you can create a custom component that includes the user's photo, name, and contact button. This component can be reused wherever a user profile needs to be displayed in the app, ensuring that changes to the profile layout or functionality are centralized within this widget. 2. **Backend Actions**: FlutterFlow allows you to define backend actions that can be called from different parts of your app. These actions can encapsulate complex logic, such as processing user input, interacting with databases, or calling external APIs. By defining such actions, you can manage how data is processed and passed around in your applications. This helps in maintaining a clear separation between the UI and business logic, which is a core principle of encapsulation. ### Benefits of Encapsulation in FlutterFlow[​](/resources/data-representation.md#benefits-of-encapsulation-in-flutterflow "Direct link to Benefits of Encapsulation in FlutterFlow") * **Reusability**: Encapsulated components are reusable across different parts of the application without requiring duplication of widgets. * **Maintainability**: Changes to the application’s data handling or business logic can be made in a single place using action blocks rather than having to make widespread modifications across many actions. * **Scalability**: Applications can grow more naturally and with less complexity when their components are well-encapsulated. --- # App State App state variables are specific variables that hold the current state of an application. They can be accessed and modified throughout the entire application across all pages and components. This type of variable can be useful for storing data that needs to be shared between different parts of the app, such as user preferences and authentication tokens. ![app-state-variables.avif](/assets/images/app-state-variables-c3be8e44314611883d9decf575ab0882.avif) App state variables should be used in scenarios where the same data needs to be accessed and modified from multiple locations within the app. For instance, in a shopping cart app, items in a user's cart are usually accessible across different pages. App state variables should not be used for temporary data that doesn't impact the overall state of the application. For instance, a user's temporary input in a form should not be stored in an app state variable. It would be more appropriate to use a [page state](/resources/ui/pages/page-lifecycle.md#page-state) or [component state](/generated-code/state-management.md#component-state) variable instead. ## App State Variables[​](/resources/data-representation/app-state.md#app-state-variables "Direct link to App State Variables") Let’s see how you can manage the app state variable using an example of adding items to a cart in a shopping app. ### Create App State variable[​](/resources/data-representation/app-state.md#create-app-state-variable "Direct link to Create App State variable") Head over to the left-side navigation menu and follow the steps below to create a variable. [Sharing a Project with a User](https://demo.arcade.software/QjdQ0cTmGDqUeG6F1JMh?embed\&show_copy_link=true) #### App State Properties[​](/resources/data-representation/app-state.md#app-state-properties "Direct link to App State Properties") * **isList:** Whether this field is a list type (e.g List of String or List of Custom Data Type) * **Persisted:** Whether this app state is saved to disk so that it can be loaded when the app is restarted. Otherwise the field will be reset on restart. Generated Code Curious about what happens when the **Persisted** toggle is on? Check out the [**Generated Code**](/generated-code/state-management.md#persisting-app-state) guide. ### Use App State[​](/resources/data-representation/app-state.md#use-app-state "Direct link to Use App State") The variable can now be accessed via set from variable menu. For example, on the cart page, you can loop through the app state variable to display each item. ![access-app-state-variable.avif](/assets/images/access-app-state-variable-32ed5e4451632f8519881db24b694184.avif) ### Update App State \[Action][​](/resources/data-representation/app-state.md#update-app-state-action "Direct link to Update App State \[Action]") You can update an app state from the Actions Panel anywhere in the app, whether it's on tap of a widget in a component or page, or via custom code in FlutterFlow. When you update the app state via the Action Flow Editor, you will find the following options in the Action Settings. ![update-app-state-action.png](/assets/images/update-app-state-action-17d969477327725f5d471be230a97ee1.png) #### Update Type[​](/resources/data-representation/app-state.md#update-type "Direct link to Update Type") How this app state update will affect your app. * **Rebuild All Pages:** Rebuilds all pages in the app when this app state is updated. * **Rebuild Current Page:** Rebuilds only the current page when this app state is updated. * **No Rebuild:** No rebuild is required. Generated Code Curious about how state changes are handled internally when you choose different **Update Type** options? Explore the detailed [**FFAppState**](/generated-code/ff-app-state.md) guide. Here's a quick guide to updating the app state variable. We need to add an action to the 'Add to Bag' button. Within this action, we'll provide the product details and configure it to add to the current cart list. [Sharing a Project with a User](https://demo.arcade.software/FKv2dXq4jTjjJVLy6nxu?embed\&show_copy_link=true) tip If you want to rebuild a page or component without updating any state variables, use the [**Rebuild**](/concepts/state-management.md#rebuild-action) state action. ## FAQs[​](/resources/data-representation/app-state.md#faqs "Direct link to FAQs") Why are some variable types not available in App State? Certain variable types, e.g., **Firestore Documents** and **Supabase Row**, can be used in Page State or Component State, but not in App State. This is because App State variables are designed to be global, meaning they stay in memory throughout the app. When App State variables are marked as persisted, the variable’s value is saved to the device’s local storage. Storing large or complex data types like documents in App State could lead to **performance or size issues**, especially on lower-end devices. For this reason, FlutterFlow limits App State to lightweight types, while Page/Component State allows for more flexibility since their scope is smaller and temporary. If you need to work with such data types, it's recommended to store them in Page or Component state instead. --- # Constants Constants are used to define values that remain unchanged throughout the lifetime of an application. Using constants is a good practice for values that do not need to be recalculated or reassigned. Constants are used to define values that you believe are fixed, like API endpoints, standard mathematical values, maximum size limits set by business rules, etc. When to use Constants vs **[App state variables](/resources/data-representation/app-state.md)?** Constants don't change. Once you set its value (in builder), you can't change it from within the app. On the other hand, app state variables are dynamic. They can be updated in response to interactions in the application, such as a user clicking a button or entering data. ## Create and use Constants[​](/resources/data-representation/constants.md#create-and-use-constants "Direct link to Create and use Constants") [Sharing a Project with a User](https://demo.arcade.software/Dftl0AAL3w3fw6TjaiBR?embed\&show_copy_link=true) Naming Convention Prefer using a lowercase `k` prefix for constants to indicate their immutability, especially for project-specific constants. This approach is more concise and aligns with Dart's common practices. To learn more, refer to the guide on **[Naming Variables & Functions](/resources/style-guide.md)**. --- # Custom Data Types In FlutterFlow, custom data types allow you to define structured data models that enhance data management and consistency across applications. These data types serve as blueprints for organizing related data attributes. For instance, you can define a custom data type "Book" that combines predefined data types, such as a string for the title, an integer for the year of publication, and a list of strings for the authors. Custom data types have several key advantages: * **Reusable**: Define once, use everywhere. * **Easy to Update**: Change data structure in one place, and see it reflected throughout your app. * **Consistent**: Keeps data format uniform across the application. * **Efficient**: Simplifies complex data handling, reducing errors and redundant code. info * Use custom data type when predefined data types, such as *integer* and *string* may not be enough to store certain kinds of information. * FlutterFlow also supports some [**Built-in Data Types**](/resources/data-representation/data-types.md#built-in-data-types). ![custom-data-types.avif](/assets/images/custom-data-types-3b137f5c280f5408c0e9683670a4059d.avif) When you create a custom data type, it internally creates a Struct. A struct, or structure, is a composite data type that lets you combine fields of different data types to construct a data structure to suit your specific needs. info The class name for such data types is generated by appending "Struct" to the name of the data type. For example, if you create a custom data type called "Cart", the corresponding class would be named "CartStruct". ## Creating Custom Data Type[​](/resources/data-representation/custom-data-types.md#creating-custom-data-type "Direct link to Creating Custom Data Type") To create a custom data type, specify its name and the corresponding fields. Each field can have a distinct data type. You can also specify if a field should allow multiple entries using the **Is List** toggle. [Sharing a Project with a User](https://demo.arcade.software/fdx2RldmRxm5VeQdaHyd?embed\&show_copy_link=true) Naming Convention When naming custom data types, always use **UpperCamelCase**, as recommended by the Dart Style Guide. To learn more, refer to the guide on **[Naming Variables & Functions](/resources/style-guide.md)**. ## Accessing Custom Data Type[​](/resources/data-representation/custom-data-types.md#accessing-custom-data-type "Direct link to Accessing Custom Data Type") After creating a custom data type, it’s treated internally as a [Dart class](https://dart.dev/language/classes). However, just defining the custom data type doesn’t hold any real data. To work with actual data, such as storing a user profile or a review, you need to create an **instance** of custom data type. Creating an instance allows you to: * Assign specific values to each field in your custom data type. * Store the instance in app state, page state, or pass it between widgets. * Access individual fields wherever needed. To create an instance of a custom data type, first you need to [create a state variable](/concepts/state-management.md#creating-state-variables) (of type **Data Type**) that will hold the instance. Then, to create and add the instance to the state variable, open the **Set from Variable** dialog and select **Create Data Type Object > Project Data Type**. Choose the data type you want to use. After that, set values for each of the required fields. ### Custom Data Type in Custom Code[​](/resources/data-representation/custom-data-types.md#custom-data-type-in-custom-code "Direct link to Custom Data Type in Custom Code") Sometimes, you might want to access the custom data type in your custom code. Our custom code editor allows you to receive and pass data into a variable of a custom data type. For example, you could manipulate or analyze the data as needed, and then return the modified result in the custom data type. ![custom-data-in-custom-code.avif](/assets/images/custom-data-in-custom-code-f280a8eb9e12f1f2736693bf81d4e2f9.avif) ## Use case: mapping JSON responses from API calls[​](/resources/data-representation/custom-data-types.md#use-case-mapping-json-responses-from-api-calls "Direct link to Use case: mapping JSON responses from API calls") Consider a case where you're calling an API that returns product details. You could create a custom data type 'Product' representing the JSON structure and then map the JSON values to the custom data type field. So, if the JSON response looks like this: ``` { "id": "a1b2c3d4e5f678901234567", "name": "Jacket", "price": 199.99, "reviews": [ { "id": "rev101", "username": "mike", "rating": 4, "comment": "This product exceeded my expectations in every way. Highly recommended!", }, { "id": "rev102", "username": "kera", "rating": 2, "comment": "Great quality, but the color was not as shown in the picture.", } ], } ``` Here’s how you map into a custom data type: ![mapping-json-to-custom-data-type.avif](/assets/images/mapping-json-to-custom-data-type-3ea9203a0888b0f13cdb3fb1eca985c7.avif) --- # Data Types FlutterFlow supports a variety of data types to accommodate different needs in your app. These data types range from the basic, such as integers and strings, to more complex types like lists, maps, and built-in data types. ## Primitive Data Types[​](/resources/data-representation/data-types.md#primitive-data-types "Direct link to Primitive Data Types") Primitive data types are the most basic data types. They include **integers**, **doubles**, **booleans**, and **strings**. These are the building blocks and are essential in any kind of app development. ## Composite Data Types[​](/resources/data-representation/data-types.md#composite-data-types "Direct link to Composite Data Types") Composite data types are made up of primitive data types. They can hold multiple values and can be used to structure and organize data in a more meaningful way. Examples of composite data types include **lists** and **custom data types**. ### Custom Data Types[​](/resources/data-representation/data-types.md#custom-data-types "Direct link to Custom Data Types") You can also create your own custom data types. This can be especially useful when you need a specific structure for your data that doesn't fit into the predefined types. For example, you might create a custom data type for a user profile, which includes several pieces of data like a name, an email address, and a profile picture. info Learn more about creating and using [**Custom Data Types**](/resources/data-representation/custom-data-types.md). ## Built-in Data Types[​](/resources/data-representation/data-types.md#built-in-data-types "Direct link to Built-in Data Types") FlutterFlow's built-in data types are essential for effectively managing and organizing diverse information. They ensure data consistency and easy data retrieval. They handle functionalities from storing simple color values and media URLs to complex geographical data. For instance, the **GooglePlace** data type manages location data like coordinates, place name, and address, while the **Uploaded File** type handles uploaded file data, including file name, binary data, and image dimensions. This standardization is crucial as it allows you to focus on higher-level application logic without worrying about the underlying data handling specifics. Below is a list of all supported built-in data types: * **Color**: Stores color values. * **Image Path**: Stores the URL of uploaded images. * **Video Path**: Stores the URL of uploaded videos. * **Audio Path**: Stores the URL of uploaded audio files. * **Document Reference**: Stores references to documents, simplifying data fetching. * **Document**: Stores actual Firestore documents. * **Date Time**: Stores date and time values. * **Json**: Stores JSON values, such as `{"firstName":"John", "lastName":"Doe"}`. * **LatLng**: Stores the latitude and longitude of specific locations, aiding Google Maps integration. * **TimestampRange**: Stores start and end date-time values. * **GooglePlace**: Stores GooglePlace data. * **Data Type**: Stores custom data types. * **Supabase Row**: Stores actual row data from a Supabase table. * **Uploaded File (Bytes)**: Stores uploaded files in Bytes. ## Enums[​](/resources/data-representation/data-types.md#enums "Direct link to Enums") Enums, or enumerated types, are a special kind of data type that consists of a set of related values. They can be used to create a type-safe way of dealing with a specific set of values. For instance, you may have an enum for user roles, such as 'admin', 'user', and 'guest'. info Learn more about creating and using enums [**here**](/resources/data-representation/enums.md). --- # Enums In FlutterFlow, Enums (enumerations) provide a method for defining a set of named constants. They are typically used to represent a group of related values in a more readable and safe manner. They prevent invalid values from being assigned. For example, if you have an enum for days of the week, you can't mistakenly assign a non-existent day. In contrast, with strings or numbers, you might accidentally use an invalid or misspelled value like "Sundey" or "Sinday". ![enums](/assets/images/enums-fi-828b0b73ef99ab7ea726edd7172c6ca4.avif) Here are some real-world examples where using Enums is beneficial: 1. **Application States**: A media player might use enums to keep track of playback states (e.g., playing, paused, stopped). 2. **Product Types, Sizes, or Categories**: A clothing store app might use enums to categorize clothing sizes (small, medium, large). 3. **Order or Process Status**: For tracking the status of orders, processes, or tasks (pending, inProgress, completed, canceled). ## Create and use Enums[​](/resources/data-representation/enums.md#create-and-use-enums "Direct link to Create and use Enums") 1. You can create Enums from the left side navigation menu and add values to it. [Sharing a Project with a User](https://demo.arcade.software/U6crZTuELtgYinr4ZxQp?embed\&show_copy_link=true) 2. Access the Enum values by navigating to the **Set from Variable** menu, then selecting **Enums > \[your enum name] > Values**. ![enums.avif](/assets/images/enums-6f100f8b0496e611ff233cae9317751d.avif) Naming Convention When naming enums, always use **UpperCamelCase**, and for enum values, use **lowerCamelCase**, as recommended by the Dart Style Guide. To learn more, refer to the guide on **[Naming Variables & Functions](/resources/style-guide.md)**. --- # Global Properties Global properties are **built-in variable**s in FlutterFlow that you can use across all pages of your app. These properties are predefined by FlutterFlow, meaning you cannot create or modify them yourself. They are designed to help you perform common tasks efficiently, no matter what type of app you’re developing. For example, global properties can be used to redirect users to another page if they are not logged in or to enable specific functionality based on the platform your app is running on. You can access these properties through the **Set from Variable** menu **> Global Properties**. ![global-properties.avif](/assets/images/global-properties-334541daf62a438eecee710a65a556db.avif) caution Global properties are built-in variables exposed by FlutterFlow. You can't create one by yourself. ## List of Global Properties[​](/resources/data-representation/global-properties.md#list-of-global-properties "Direct link to List of Global Properties") A list of all the available global properties is as follows: * **Is User Logged In:** Indicates whether a user is currently logged into the app. Useful for providing exclusive features to registered users or adjusting UI elements based on login status. This property is only accessible if you have enabled authentication of any type. * **Current Time**: Fetches the current date and time. Explore [custom formatting](/resources/data-representation/global-properties.md#current-time) options to tailor the DateTime display to your needs. * **Current Device Location:** Returns the user's current location, ideal for updating their position on Google Maps or storing it in a backend database. [Check out examples](/resources/data-representation/global-properties.md#current-device-location) on how to retrieve and save the current device location. * **Link To Current Page:** Provides the [Deep Link](/concepts/navigation/deep-dynamic-linking.md#deep-link) of the current page. * **Current Route Path**: Provides the route name of the currently active or visible page in your app. This property is especially helpful in scenarios where you want to adjust or block specific actions if the active page isn't the one you expect. For example, if you launch the app through a push notification, the home page might still run in the background, even if the notification directs you to a different page. Using this property, you can prevent unnecessary action triggers, such as On Page Load from the home page. See details on avoiding [this issue](https://github.com/FlutterFlow/flutterflow-issues/issues/2765#issuecomment-2598915946). * **Current Route Stack:** Returns a list of route names representing every active page in your app’s navigation stack. It’s helpful for understanding how many pages deep the user is and what sequence of pages they’ve visited. You may need this data to manage custom back navigation, breadcrumb displays, or logging analytics. For instance, in an e-commerce app, you could examine the route stack to see if the user arrived at the checkout page from a specific page and tailor your promotional messages or apply discount accordingly. * **Fraction of Screen Width:** Determines the proportional width of the device's screen. * **Fraction of Screen Height:** Determines the proportional height of the device's screen. * **Screen Width:** Provides the total width of the current device's screen in pixels. * **Screen Height:** Provides the total height of the current device's screen in pixels. * **Is Android:** Determines if the user is accessing the app on an Android device. See [example](/resources/data-representation/global-properties.md#is-androidiosweb). * **Is iOS:** Determines if the user is accessing the app on an iOS device. See [example](/resources/data-representation/global-properties.md#is-androidiosweb). * **Is Web:** Determines if the user is accessing the app through a web browser. See [example](/resources/data-representation/global-properties.md#is-androidiosweb). * **Is Debug Mode:** Indicates if the app is currently running in debug mode, useful for displaying features or performing actions only during debugging. * **Is Dark Mode:** Checks if the app's current theme mode is set to dark. * **Is Light Mode:** Checks if the app's current theme mode is set to light. * **Is On-Screen Keyboard Visible:** Checks if the on-screen or soft keyboard is visible. This is helpful in making UI adjustments if keyboard is visible on screen. See a [quick example](/resources/data-representation/global-properties.md#is-on-screen-keyboard-visible). * **Current Environment**: Returns the current [development environment](/testing/dev-environments.md) value. Generated Code Learn more about the [**Generated Code**](/generated-code/state-management.md#global-state) behind Global Properties. ### Current Time[​](/resources/data-representation/global-properties.md#current-time "Direct link to Current Time") The **Current Time** property allows you to retrieve the current date and time. This option is available when the Source is set to Global Properties. You can use this property to display the current date and time on the screen or pass it to a FlutterFlow or custom widget for further processing. #### Custom formatting[​](/resources/data-representation/global-properties.md#custom-formatting "Direct link to Custom formatting") Sometimes, you might need to display dates and times in a format that we don't support. This is where the custom date and time formatting comes into play. *Custom Format* enables you to represent date and time data in a multitude of ways. For example, you can enter the text like '*yyyy/MM/dd || kk:mm*', and the date time will be displayed as '2023/07/25 || 10:30'. In the above example, '*yyyy/MM/dd || kk:mm* is the custom format. Here's what it stands for: * `yyyy` represents a four-digit year, like "2023". * `MM` is a two-digit month, such as "07" for July. * `dd` indicates a two-digit day, for instance, "25". * `kk` is for a two-digit hour in 24-hour format, like "10". * `mm` stands for a two-digit minute, such as "30". Here are some more format specifiers that you can use the build the custom format: * `d`: Day of the month. E.g., "2" for February 2nd. * `E`: Abbreviated weekday. E.g., "Mon" for Monday. * `EEEE`: Full weekday. E.g., "Monday". * `LLL`: Abbreviated standalone month. E.g., "Feb". * `LLLL`: Full standalone month. E.g., "February". * `M`: Month of year. E.g., "2" for February. * `Md`: Month and day. E.g., "2/2". * `MEd`: Abbreviated weekday, month, and day. E.g., "Mon, 2/2". * `MMM`: Abbreviated month. E.g., "Feb". * `MMMd`: Abbreviated month and day. E.g., "Feb 2". * `MMMEd`: Abbreviated weekday, month, and day. E.g., "Mon, Feb 2". * `MMMM`: Full month. E.g., "February". * `MMMMd`: Full month and day. E.g., "February 2". * `MMMMEEEEd`: Full month, weekday, day. E.g., "Monday, February 2". * `QQQ`: Abbreviated quarter. E.g., "Q1". * `QQQQ`: Full quarter. E.g., "1st quarter". * `y`: Year. E.g., "2023". * `yM`: Year and month. E.g., "2023/2". * `yMd`: Year, month, day. E.g., "2023/2/2". * `yMEd`: Weekday, year, month, day. E.g., "Mon, 2023/2/2". * `yMMM`: Abbreviated month and year. E.g., "Feb 2023". * `yMMMd`: Abbreviated month, day, year. E.g., "Feb 2, 2023". * `yMMMEd`: Weekday, month, day, year. E.g., "Mon, Feb 2, 2023". * `yMMMM`: Full month and year. E.g., "February 2023". * `yMMMMd`: Full month, day, year. E.g., "February 2, 2023". * `yMMMMEEEEd`: Weekday, full month, day, year. E.g., "Monday, February 2, 2023". * `yQQQ`: Abbreviated quarter, year. E.g., "Q1 2023". * `yQQQQ`: Full quarter, year. E.g., "1st quarter 2023". * `H`: Hour in day (24-hour). E.g., "15" for 3 PM. * `Hm`: Hour, minute (24-hour). E.g., "15:30". * `Hms`: Hour, minute, second (24-hour). E.g., "15:30:45". * `j`: Hour in day (12-hour). E.g., "3 PM". * `jm`: Hour, minute (12-hour). E.g., "3:30 PM". * `jms`: Hour, minute, second (12-hour). E.g., "3:30:45 PM". * `m`: Minute in hour. E.g., "30". * `ms`: Minute, second. E.g., "30:45". * `s`: Second in minute. E.g., "45". * `G`: Era designator. E.g., "AD" in "AD 2023". * `L`: Standalone month. E.g., "7" for July. * `c`: Standalone day. E.g., "2" for Tuesday. * `h`: Hour in AM/PM (1\~12). E.g., "3" for 3 AM. * `H`: Hour in day (0\~23). E.g., "15" for 3 PM. * `S`: Fractional second. E.g., "123" for 123 milliseconds. * `D`: Day in year. E.g., "50" for the 50th day of the year. * `a`: AM/PM marker. E.g., "AM" or "PM". * `k`: Hour in day (1\~24). E.g., "24" for midnight. * `K`: Hour in AM/PM (0\~11). E.g., "0" for 12 AM. * `Q`: Quarter. E.g., "4" for the fourth quarter. info For more detailed information, please refer to the [DateFormat class documentation](https://pub.dev/documentation/intl/latest/intl/DateFormat-class.html). ![img.png](/assets/images/img-9946195dba2959d39385e9e2bdab25fb.png) ### Current Device Location[​](/resources/data-representation/global-properties.md#current-device-location "Direct link to Current Device Location") This property is used to get the current device location (aka geolocation). You can access this when the **Source** is set to **Global Properties**. You can use this property to get the user's current location to update on Google Maps or store it in the backend database. warning At present, testing this property isn't possible in Test mode, but you can use the Run mode for this purpose. To run it on Android, iOS or desktop platforms, use [Local Run](/testing/local-run.md). #### Get Current Device Location: Example[​](/resources/data-representation/global-properties.md#get-current-device-location-example "Direct link to Get Current Device Location: Example") Let's see an example of getting the current device location and passing it to a widget (that supports accepting LatLong, for example, Google Maps). Here is an example of how you can retrieve the current device location: 1. Select the **widget** (e.g., GoogleMap) from the widget tree or canvas area. 2. Move to the properties panel, find the **Initial Location** property, and click on **Set from Variable**. 3. Set the **Source** to **Global Properties**. 4. Set the **Available Options** to the **Current Device Location**. 5. Click **Confirm**. #### Save the Current Device Location: Example[​](/resources/data-representation/global-properties.md#save-the-current-device-location-example "Direct link to Save the Current Device Location: Example") Here's how you can save the user's current location (Geolocation) in the Firestore document. 1. Create a **LatLng** field in your Firestore Schema. 2) After this, you need to set this field from a variable source and select **Current Device Location** from the **Global Properties**. ### Is Android/iOS/Web[​](/resources/data-representation/global-properties.md#is-androidiosweb "Direct link to Is Android/iOS/Web") Use these properties when you want to tailor the user experience for specific platforms. These properties determine whether the user is accessing the app on Android, iOS, or the Web. Knowing the user's platform is essential for customizing functionality to suit each environment. For instance, certain custom widgets or actions might be exclusive to Android. These properties allow you to implement platform-specific features and ensure your app behaves optimally across different devices. Some examples: * **Is Android**: Enable a custom push notification feature that only works on Android devices. By checking if the platform is Android, you can conditionally display a setup screen for this feature. * **Is iOS**: Optimize custom animations or gestures specifically for iOS. By detecting if the user is on an iOS device, you can enable these iOS-specific interactions while providing alternatives for other platforms. * **Is Web**: Implement a file upload feature with a drag-and-drop interface optimized for desktop environments. By checking if the platform is Web, you can provide an enhanced file handling experience that suits web users. ### Is On Screen Keyboard Visible[​](/resources/data-representation/global-properties.md#is-on-screen-keyboard-visible "Direct link to Is On Screen Keyboard Visible") This property helps check if the on-screen or soft keyboard is visible on screen. You can access this when the Source is set to Global Properties. #### Hiding bottom navigation bar when a keyboard is visible: Example[​](/resources/data-representation/global-properties.md#hiding-bottom-navigation-bar-when-a-keyboard-is-visible-example "Direct link to Hiding bottom navigation bar when a keyboard is visible: Example") Consider an app where users can input dog details, and a custom bottom navigation bar is present. When users enter dog details, the on-screen keyboard appears, causing the bottom navigation bar to appear over the keyboard. To optimize screen space and improve the user experience, you might want to hide the bottom navigation bar in such instances. Here's how it looks: To build such behavior, you can add [Conditional Visibility](/resources/ui/widgets/widget-commonalities.md#conditional) on the bottom navigation. While adding, use the "Is On-Screen Keyboard Visible" that will hide the bottom navigation bar whenever the keyboard is displayed. Using "Is On-Screen Keyboard Visible" to hide bottom navigation --- # Variable Variables in FlutterFlow let you store and manage dynamic data, which is essential for creating interactive and responsive applications. By using variables, you can capture user inputs, track states, and manipulate data across different parts of your app. ![variable](/assets/images/variable-c8dba55586bec76e5d8d02e8a5769538.avif) In this section, we'll dive into the different types of variables available in FlutterFlow, including: * **Local Variables:** Variables that are confined to a specific widget or page, used for handling data within a localized context. For example, Page State variables or Component State variables are scoped to the entity they were created in. * **Global Variables:** Variables that can be accessed and modified throughout the entire app, allowing for consistent data management across pages. For example, App State variables can be accessed from anywhere in the app. What are scopes? The scope of a variable is determined by where it is created. For instance, if it's created at the app level, it can be accessed throughout the app. However, a variable created at the page level can only be accessed on that page. ## Creating Variables[​](/resources/data-representation/variables.md#creating-variables "Direct link to Creating Variables") When creating variables in FlutterFlow, there are a few important considerations regarding their name, data type, nullability, and initial values. The specific *process* for creating variables differs depending on whether you are working with App State, Page State, or Component State variables, and you can find detailed instructions linked below. ### Naming Variable[​](/resources/data-representation/variables.md#naming-variable "Direct link to Naming Variable") Start by giving your variable a meaningful and descriptive name that reflects its purpose. This name will be used throughout your app to reference the variable, so it's important to keep it clear and consistent with your naming conventions. Recommended naming convention We recommend the `lowerCamelCase` naming convention for variables. Learn more about the **[recommended naming conventions](/resources/style-guide.md)** used in FlutterFlow and Flutter projects. ### Assigning a Data Type to a Variable[​](/resources/data-representation/variables.md#assigning-a-data-type-to-a-variable "Direct link to Assigning a Data Type to a Variable") Next, you need to select the appropriate data type for your variable. FlutterFlow offers several data types, such as **Text, Integer, Boolean,** or **String**. Refer to the **[Data Types guide](/resources/data-representation/data-types.md)** to learn more about the available data types. Choosing the correct data type is crucial, as it determines how the variable can be used and what kind of data it can store. ### Is List Property[​](/resources/data-representation/variables.md#is-list-property "Direct link to Is List Property") Enable the **Is List** toggle to indicate that this field should be of the **list** type. Example If the data type selected is `String` and the `Is List` toggle is enabled, FlutterFlow will create a **list of String variables**. This list can hold multiple string values, such as a list of city names. ### Nullable & Initial Value[​](/resources/data-representation/variables.md#nullable--initial-value "Direct link to Nullable & Initial Value") When creating variables in FlutterFlow, you have the option to make them **nullable** or **non-nullable**. This setting is crucial because it determines whether the variable can hold a null value (i.e., no value). Alongside this, you can also define an initial value for your variable, which ensures that it starts with a specific value as soon as it’s created. ![variables-null-initial-value.png](/assets/images/variables-null-initial-value-3b9551914e84a3be7ba26a84e5f0a070.png) * **Nullable:** This option determines if a variable can hold a null value, meaning it can exist without any data. If the Nullable option is enabled, the variable can start as null and only receive a value when needed. If disabled, the variable must always have a value, which means you’ll need to provide an initial value when it’s created. * **Initial Value:** If a variable is non-nullable (i.e., cannot be null), you are required to provide an initial value to ensure it always contains data. For nullable variables, setting an initial value is optional, allowing them to remain empty until a value is assigned. What is a null value? A null value represents the **absence of a value**. In FlutterFlow, allowing a variable to be null can be beneficial in scenarios such as: * **User Input:** Before a user enters data, a variable can start as null and only hold a value once the user provides input. * **Conditional Logic:** In cases where certain data might not always be applicable (e.g., an optional user setting), a null value allows you to handle the absence of data more flexibly. * **Loading States:** When fetching data from an API, variables can be null until the data is loaded, allowing you to differentiate between "loading" and "loaded" states easily. ### How to create variables?[​](/resources/data-representation/variables.md#how-to-create-variables "Direct link to How to create variables?") For step-by-step guides on how to create App State, Page State, and Component State variables, please refer to the following links: * [Creating App State Variables](/resources/data-representation/app-state.md) * [Creating Page State Variables](/resources/ui/pages/page-lifecycle.md#page-state) * [Creating Component State Variables](/resources/ui/components/component-lifecycle.md#component-state) ## Set Variable[​](/resources/data-representation/variables.md#set-variable "Direct link to Set Variable") The **Set from Variable** or **Set Variable** menu in FlutterFlow is a powerful feature that allows you to dynamically control the content or behavior of your widgets using **data** stored in variables. When you select a variable from this menu, you're instructing FlutterFlow to use the value of that variable to populate or modify the widget's properties, such as text, visibility, or styling. This menu provides a variety of variable **sources**, including data that's specific to the page or component, global properties that apply across the entire app, constants, and more. By choosing the appropriate variable, you can make your app more interactive and responsive to user input, data changes, or other conditions. ![set-variable-menu3.png](/assets/images/set-variable-menu3-36c77114a93aedc24f17013a0a1293e7.png) ## Manipulating Variables[​](/resources/data-representation/variables.md#manipulating-variables "Direct link to Manipulating Variables") When setting variables via the **Set Variable** menu, you have the ability to manipulate or transform the data before applying it to a widget or another variable. This manipulation allows you to tailor the data to fit specific needs or contexts, enhancing the flexibility and functionality of your app. For instance, you can: * **[Concatenate or Combine Strings:](/resources/functions/utility.md#combine-text)** Combine multiple text values into a single string. To learn how to manipulate strings before setting variables, see the [Utility Functions](/resources/functions/utility.md#combine-text) guide. * **[Filter or Sort Lists](/resources/data-representation/variables.md#list-options):** Organize or refine data in lists to display only what’s relevant or in a specific order. * [**Convert DateTime to UNIX:**](/resources/data-representation/global-properties.md#current-time) Change a DateTime object into a UNIX timestamp for compatibility or calculation purposes. * [**Apply Conditional Logic:**](/resources/functions/conditional-logic.md) Use If/Then/Else statements to set different values based on specific conditions. These manipulations enable you to create more dynamic and responsive user interfaces by ensuring that the data presented or used in your app is always in the most appropriate form. ### List Options[​](/resources/data-representation/variables.md#list-options "Direct link to List Options") While working with a list, you may need to extract specific data based on specific criteria. The List options provide a range of functionalities for efficient data extraction from these lists. Here's what it includes: #### Map List Items[​](/resources/data-representation/variables.md#map-list-items "Direct link to Map List Items") The option **Map List Items** allows you to prepare a list of specific fields from data types such as Documents, [Custom Data Types](/resources/data-representation/custom-data-types.md), and JSON. For instance, if you have a list of Firebase Documents containing fields like name, age, and position, you can specifically generate a list consisting only of names. This option allows you to create tailored lists from complex data structures. Here's an example of preparing a list of only cat names from Firebase documents (that contain other fields like name, age, and breed) and displaying them on dropdown. #### Filter List Items[​](/resources/data-representation/variables.md#filter-list-items "Direct link to Filter List Items") The **Filter List Items** option allows you to create a list of items based on specific criteria, generating a sublist of items that match. For example, you might want to create a list of users over a certain age from a larger user database or perhaps compile a list of products within a specific price range from an extensive inventory. #### First Few Items[​](/resources/data-representation/variables.md#first-few-items "Direct link to First Few Items") The **First Few Items** option extracts the initial elements of the list up to a specified number. ![first-few-items.png](/assets/images/first-few-items-df06267d42230386f8b6313cd234832a.png) #### Sort List Items[​](/resources/data-representation/variables.md#sort-list-items "Direct link to Sort List Items") If your list contains "native data types" (like numbers or strings), we can automatically sort these elements. Native data types have a "natural ordering." For example, numbers can be sorted numerically (1, 2, 3, ...), and strings can be sorted alphabetically ("apple", "banana", "cherry", ...). For such a list, set the **Sort Key** to the item in the element. Here's an example of displaying random names in alphabetical order: Reversing a list To reverse sort a list, first sort it using the sort option, then apply the listView's [reverse option](/resources/ui/widgets/composing-widgets/list-grid.md#advanced-functionalities) for descending order. For lists with [Custom Data Types](/resources/data-representation/custom-data-types.md), you need to tell which field to use for sorting by specifying it in the **Sort Key**, and this field should be a standard data type that has a clear, natural way to be ordered. Here's how you can display a list of items (of the custom data type 'Product') in order, sorted by their price. #### Unique List Items[​](/resources/data-representation/variables.md#unique-list-items "Direct link to Unique List Items") This option helps you create a list with unique items, such as extracting distinct product categories or unique customer names from a larger dataset. Here's an example of displaying a list of unique cat breeds: To get a list of unique items from a list of custom data type, first map the list of items to the field from which you want to extract unique items. For example, if you have a list of a custom data type named 'Products,' map this list to a list containing all product names. Then, derive the unique list items from this mapped list. #### Number of Items[​](/resources/data-representation/variables.md#number-of-items "Direct link to Number of Items") Choose the **Number of Items** option if you want to get the count of the total elements in the list. #### Item at Index[​](/resources/data-representation/variables.md#item-at-index "Direct link to Item at Index") The **Item at Index** option allows you to access a specific item by its position in the list. For instance, you could retrieve the third item from a list of customer names, or select the fifth product in a catalog list. This is especially useful in scenarios where the order of items carries significance, such as fetching the latest entry in a time-ordered log, or simply when you need to pinpoint a specific item without filtering through the entire list. #### Is Set and Not Empty[​](/resources/data-representation/variables.md#is-set-and-not-empty "Direct link to Is Set and Not Empty") To determine, if any value is present in the list or if the list is not empty, choose the **Is Set And Not Empty** option. For example, it can be used to check if a search query returned any results or to verify that a data collection process has successfully captured entries. ## Updating Variable Values[​](/resources/data-representation/variables.md#updating-variable-values "Direct link to Updating Variable Values") In FlutterFlow, you can update the values of variables through **[actions](/resources/functions/action-flow-editor.md)**. For example, when a button is clicked or when a form field is modified, you can trigger an action that updates a variable with a new value. Refer to the following guides for detailed instructions on updating and using these variables: * [App State Variables](/resources/data-representation/app-state.md#update-app-state-action) * [Page State Variables](/resources/ui/pages/page-lifecycle.md#update-page-state-action) * [Component State Variables](/resources/ui/components/component-lifecycle.md#update-component-state-action) --- # Forms Overview Forms are a fundamental part of many applications, serving as the primary method for users to input and submit data. Whether you're building a simple contact form or a complex multi-step survey, FlutterFlow provides a comprehensive set of tools to create, validate, and manage forms effectively. tip In this section, you'll learn how to add form widgets such as [**TextField**](/resources/forms/textfield.md), [**Dropdown**](/resources/forms/dropdown.md), [**RadioButton**](/resources/forms/radiobutton.md), [**Checkbox Widgets**](/resources/forms/checkbox.md) and add [**Validations**](/resources/forms/form-validation.md) and [**set**](/resources/forms/set-form-field.md)/[**reset**](/resources/forms/reset-form-field.md) actions on these widgets. --- # Checkbox In FlutterFlow, a checkbox is a versatile input widget used to capture binary choices from users, such as true/false or yes/no options. It is ideal for situations where you need to present users with options that can be individually selected or deselected. FlutterFlow provides three primary variations of the checkbox widget: **Checkbox**, [**CheckboxListTile**](/resources/forms/checkbox.md#checkboxlisttile), and [**CheckboxGroup**](/resources/forms/checkbox.md#checkboxgroup). Each of these widgets offers distinct features and use cases, making it easy to tailor your app's interface to your specific needs. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Checkbox[​](/resources/forms/checkbox.md#checkbox-1 "Direct link to Checkbox") The **Checkbox** widget is the simplest form of a checkbox. It consists of a small square that can be either checked or unchecked. This widget is typically used for individual boolean options. You can customize the appearance and behavior of the checkbox, such as its size, color, and whether it starts as checked or unchecked. ### Adding Checkbox[​](/resources/forms/checkbox.md#adding-checkbox "Direct link to Adding Checkbox") Let's see how to add a checkbox widget and build an example that shows its value on a Text widget. Here's how it looks: Here is a simple way to do it: 1. First, click on the **+ Add Widget**, drag the **Checkbox** widget from the **Base Elements** tab, or add it directly from the widget tree. 2. Below the Checkbox, add a [**Text**](/resources/ui/widgets/text.md) widget, move to the properties panel, click on **Set from Variable,** and choose the **Widget State > checkboxValue** (i.e., name of your checkbox). ### Setting Initial Value[​](/resources/forms/checkbox.md#setting-initial-value "Direct link to Setting Initial Value") You might want to show the checkbox with a default value, either check or uncheck. For example, showing the checked checkbox for travel insurance. To set the initial value: 1. Select the **Checkbox** widget, move to the properties panel, and see the **Checkbox Initial Value** property. 2. Use the checkbox to set this value manually, or click **Set from Variable** to set it based on the dynamic value. If you choose *Set from Variable*, ensure you pass the boolean value from the source (e.g., API response, Firestore document field). ### Saving Checkbox Value[​](/resources/forms/checkbox.md#saving-checkbox-value "Direct link to Saving Checkbox Value") You may want to immediately save the checkbox’s value when it is checked or unchecked. To do this, [add an action using the trigger](/resources/forms/form-triggers.md#on-toggled-on--on-toggled-off) that responds to changes in the widget’s selection. ### Customizing[​](/resources/forms/checkbox.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. #### Changing color[​](/resources/forms/checkbox.md#changing-color "Direct link to Changing color") To change the checkbox colors: 1. Select the **Checkbox** widget, move to the properties panel, and scroll down to the **Checkbox Properties** section. 2. To [change the color](/resources/ui/widgets/widget-commonalities.md#change-color) of the checkbox when it is selected and unselected, use the **Checked Color** and **Unchecked Color** properties, respectively. 3. To [change the color](/resources/ui/widgets/widget-commonalities.md#change-color) of the check icon, use the **Check Color** property. #### Add rounded corners[​](/resources/forms/checkbox.md#add-rounded-corners "Direct link to Add rounded corners") To change the rounded corner for this widget: 1. Select the **Checkbox** widget, move to the properties panel, and scroll down to the **Checkbox Properties** section. 2. Find the **Border Radius** property and enter the values for TL(Top Left), TR(Top Right), BL(Bottom Left), and BR(Bottom Right). Use the Lock button to change all values at the same time. Unlocking will allow you to adjust each value separately. #### Make it circular[​](/resources/forms/checkbox.md#make-it-circular "Direct link to Make it circular") If you want to make the checkbox circular in shape, select the **Checkbox** widget, move to the properties panel, find the **Circular Check** property and enable it. ![Circular checkbox](/assets/images/make-checkbox-circular-e535ab64fac473cf839c591597ac18c9.avif) #### Disable Checkbox[​](/resources/forms/checkbox.md#disable-checkbox "Direct link to Disable Checkbox") You may need to disable a checkbox if certain conditions aren't met. For instance, users should only be able to use the 'Same as Shipping Address' checkbox when a shipping address is provided. To disable a checkbox, move to the **Properties Panel** **>** turn on the **Checkbox Disable Options >** click **Unset,** and set the [**Condition**](/resources/functions/conditional-logic.md). Once set, you could also customize the disabled state colors using the *Disabled Check Color* property. ## CheckboxListTile[​](/resources/forms/checkbox.md#checkboxlisttile "Direct link to CheckboxListTile") The **CheckboxListTile** widget combines the functionality of a checkbox with a [ListTile](/resources/ui/widgets/composing-widgets/list-grid.md#listtile-widget), providing a more comprehensive option for displaying checkboxes alongside additional information. Unlike the Checkbox this widget includes a title, and an optional subtitle, all within a single, cohesive element. CheckboxListTile is ideal for use cases where you want to provide more context or descriptive text alongside the checkbox, such as in a settings menu or a form with detailed options. ## CheckboxGroup[​](/resources/forms/checkbox.md#checkboxgroup "Direct link to CheckboxGroup") The **CheckboxGroup** widget allows you to present a group of checkboxes as a single entity. This is particularly useful when you want users to select multiple options from a list. Each checkbox within the group can be checked or unchecked independently of the others. ### Adding CheckboxGroup[​](/resources/forms/checkbox.md#adding-checkboxgroup "Direct link to Adding CheckboxGroup") Here's an example of how you can use the CheckboxGroup widget in your project: 1. First, add the **CheckboxGroup** widget from the **Form Elements** tab or add it directly from the widget tree. 2. By default, the CheckboxGroup widget adds a single option named **Option 1**. To change the name, move to the properties panel (on the right side of your screen), and scroll down to the **Define Options** section. Find the **Option 1** property and change the **name**. 3. To add more options, move to the properties panel, and scroll down to the **Define Options** section. 1. Click on the **Add Option** text. 2. Enter the name in **Option 2 Text**. 4. To remove the option, click on the cancel icon displayed in the **Option name** property. 5. Click on the **Set from Variable** to show the options from a variable such as app state variable, API response variable, or Firestore Document. ### Trigger Action on Change[​](/resources/forms/checkbox.md#trigger-action-on-change "Direct link to Trigger Action on Change") See how to [trigger an action when a selection changes](/resources/forms/form-triggers.md#on-selected) on this widget. ### Setting Initial Selection[​](/resources/forms/checkbox.md#setting-initial-selection "Direct link to Setting Initial Selection") Sometimes you might want to display the CheckboxGroup with some options already selected. For example, selecting the topping options that are already served with Pizza itself. You can do so by setting the initial selection for the CheckboxGroup. To set initial selection manually: 1. Select the **CheckboxGroup** from the widget tree or the canvas area. 2. Move to the properties panel (on the right side of your screen) and scroll down to the **Initially Selected** section. 3. Click on the **Add Selected** and enter the option name that you would like to display as selected. **Note**: Make sure you enter the correct name and it matches with the option name added inside the define options section. 4. Similarly, you can display the other option(s) as selected. ### Clear/Select all items \[Action][​](/resources/forms/checkbox.md#clearselect-all-items-action "Direct link to Clear/Select all items \[Action]") You might want to allow users to clear or select all items in one go. You can do so by adding the following action. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Clear All/Select All** (under *Widget/UI Interactions*) action. 4. **Choose Multiselect Widget** name from the dropdown. 5. Finally, set the **Action Type** to **Clear All** or **Select All**. ### Customization[​](/resources/forms/checkbox.md#customization "Direct link to Customization") You can use the Properties Panel to customize the appearance of your widget. #### Set padding around the checkbox[​](/resources/forms/checkbox.md#set-padding-around-the-checkbox "Direct link to Set padding around the checkbox") To create empty space around the checkbox: 1. Select the **CheckboxGroup** from the widget tree or the canvas area. 2. Move to the properties panel and find the **Item Padding** property. 3. Set the padding for the L(Left), T(Top), R(Right), and B(Bottom) sides. Use the Lock button to change all values at the same time. Unlocking will allow you to modify each value separately. #### Changing checkbox color[​](/resources/forms/checkbox.md#changing-checkbox-color "Direct link to Changing checkbox color") To change the checkbox color: 1. Select the **CheckboxGroup** from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Checkbox Style** section. 3. To change the active color (i.e. color when the checkbox is selected), find the **Active Color** property, click on the box next to the already selected color, select the color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. You can also choose the color by clicking the **Palette** and **Simple** button. 4. the Similarly you can change the check color (i.e color of the done/tickmark icon inside the checkbox). #### Customizing checkbox border[​](/resources/forms/checkbox.md#customizing-checkbox-border "Direct link to Customizing checkbox border") To customize the checkbox border: 1. Select the **CheckboxGroup** from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Checkbox Style** section. 3. To change the checkbox border color, find the **Check Border Color** property, click on the box next to the already selected color, select the color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. You can also choose the color by clicking the **Palette** and **Simple** button. 4. To adjust the border corner, find the **Border Radius** property and enter the values in the TL (Top left), TR (top right), BL (bottom left), and BR (bottom right) boxes. Use the Lock button to change all values at the same time. Unlocking will allow you to modify each value separately. --- # ChoiceChips The ChoiceChips widget allows users to select a single option from a group of chips. Each chip is presented with an icon and accompanying text, making it easy to represent various choices. You could use this widget to implement a filter feature in an e-commerce app to let users select different product attributes like size, color, or price range. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding ChoiceChips widget[​](/resources/forms/choice-chips.md#adding-choicechips-widget "Direct link to Adding ChoiceChips widget") To add the ChoiceChips widget to your app: 1. Add the **ChoiceChips** widget from the **Form Elements** tab. 2. By default, this widget adds a single option named **Option 1**. To change the name, move to the Properties Panel, and scroll down to the **Define Options** section. Find the **Option 1** property and change the **name** and **icon**. 3. To add more options, click on the **Add Option** text and set the name and icon for new options. 4. To set any chip as selected by default, find the **Initial Option** property and enter the chip name. 1. To set this value dynamically, open the **Set from Variable** menu and set the variable. 2. When [multiselect](/resources/forms/choice-chips.md#allow-multiselect) is enabled, you can also set the list of options to pre-select. ### Trigger Action on Change[​](/resources/forms/choice-chips.md#trigger-action-on-change "Direct link to Trigger Action on Change") See how to [trigger an action when a selection changes](/resources/forms/form-triggers.md#on-selected) on this widget. ## Select or Clear All Choices \[Action][​](/resources/forms/choice-chips.md#select-or-clear-all-choices-action "Direct link to Select or Clear All Choices \[Action]") Users may need to swiftly deselect all chips or choose all available choice chips at once. You can do so by adding the **Clear All/Select All** action. info Before you add this action, ensure you [**allow multiselect**](/resources/forms/choice-chips.md#allow-multiselect) on this widget. ## Customizing[​](/resources/forms/choice-chips.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Allow Multiselect[​](/resources/forms/choice-chips.md#allow-multiselect "Direct link to Allow Multiselect") You might want to allow users to select multiple choices to filter the result. To allow multiselect, select the **ChoiceChips** widget, move to the properties panel, find the **Allow Multiselect** property and enable it. ### Disable ChoiceChips[​](/resources/forms/choice-chips.md#disable-choicechips "Direct link to Disable ChoiceChips") Sometimes, you may want to present the choices in a read-only mode, preventing users from making any changes. To do so, move to the **Properties Panel** **>** turn on **Disable >** click **Unset,** and set the [**Conditions**](/resources/functions/conditional-logic.md). This can be the [**Single Condition**](/resources/functions/conditional-logic.md#single-condition) or [**Combine Conditions**](/resources/functions/conditional-logic.md#multiple-conditions-andor) based on your requirement. **Note:** The ChoiceChips widget will be disabled only when condition(s) is true. ### Adding Space between Chips[​](/resources/forms/choice-chips.md#adding-space-between-chips "Direct link to Adding Space between Chips") To add a space between the chips, you can use the **Chip Spacing** ad **Row Spacing** property. * **Chip Spacing**: This adds horizontal gaps between individual chips. * **Row Spacing**: This adds vertical gaps between the chips in a row. ### Align Chips[​](/resources/forms/choice-chips.md#align-chips "Direct link to Align Chips") When you have chips in multiple rows, you can align them using the **Alignment** property. This is similar to setting main axis alignment for the Row widget. ### Customizing Selected and Unselected Chip Style[​](/resources/forms/choice-chips.md#customizing-selected-and-unselected-chip-style "Direct link to Customizing Selected and Unselected Chip Style") Various properties under the **Selected Chip Style** and **Unselected Chip Style** section allow you to customize chips to match your design. Here's how you do it: 1. To change the background color, use the **Color** property. 2. To change the icon's color and size, use the **Icon Color** and **Icon Size** property. 3. To add a shadow or to create a sense of depth for the chip, you can use the **Elevation** property. 4. To customize the border, use the **Border Color**, **Border Width** (thickness), and **Border Radius** (rounded corner) properties. 5. To create some space around the label, use the **Label Padding** property. 6. To change the label text styling, use the **Selected Text Style** property. 7) Similarly, you can customize the properties under the **Unselected Chip Style**. ![Customizing unselected chip style](/assets/images/customize-unselected-choice-76daa2c539e8b24886b48619effa7c27.png) --- # Dropdown The DropDown widget enables users to choose from a list of options. It requires a set of items to display and an initial value to indicate the current selection. When a user selects an item from the dropdown list, the value is updated to reflect the selected item. You can use this widget in any situation where you want users to select from a set of options, such as selecting a country, choosing a language, or picking a color. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding DropDown widget[​](/resources/forms/dropdown.md#adding-dropdown-widget "Direct link to Adding DropDown widget") Let's see how to add a *DropDown* widget and build an example that shows the selected value on a Text widget. Here's how it looks: 1. Add the **DropDown** widget, move to the **Properties Panel > Define Options >** click **Add Options** to add items. 2. To display the default value, move to the **Initial Configuration** section and enter the value. Ensure it matches one of the options added in the previous step. 3. The selected dropdown value can be accessed via *Widget State > DropDown*. To display it on the *Text* widget, add a [**Text**](/resources/ui/widgets/text.md) widget, move to the properties panel, click on **Set from Variable** and choose the **Widget State > DropDown** (i.e., name of your dropdown). ### Setting Initial Value[​](/resources/forms/dropdown.md#setting-initial-value "Direct link to Setting Initial Value") Setting a default or initial value for the DropDown is a common requirement for many apps. It can provide a better user experience by pre-selecting the most likely option. To set an initial value: 1. Select the **DropDown** widget > move to the **Properties Panel** > **Initial Configuration**. 2. In **Initial Option Value**, enter the option name that you want to set as default. 3. To set this value dynamically, open the **Set from Variable** menu and select the variable. 1. For example, to set this value from Firebase, ensure you have access to Firebase document that contains the field you want to set. 2. Open the **Set from Variable** menu > select **\[collection\_name] Document** > select the **field**. 4. If you don't set the initial value, the **Hint Text** will be displayed. ### Saving DropDown Value on Selection Change[​](/resources/forms/dropdown.md#saving-dropdown-value-on-selection-change "Direct link to Saving DropDown Value on Selection Change") You might want to save the dropdown value as soon as the selection changes. This approach is useful when you want to ensure that the user's selection is immediately saved without having to wait for them to submit the form. By doing so, you can provide a better user experience and reduce the risk of data loss in case of any interruption. You can do so by adding an action such as [update app state](/resources/data-representation/app-state.md#update-app-state-action), [update Firestore record](/integrations/database/cloud-firestore/firestore-actions.md#update-document-action) that [triggers when a selection changes](/resources/forms/form-triggers.md#on-selected) on this widget. ## Customizing[​](/resources/forms/dropdown.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Showing Option Label[​](/resources/forms/dropdown.md#showing-option-label "Direct link to Showing Option Label") The dropdown widget allows you to show a label than the actual option value. By adding the option label, you can have a simple/short name or abbreviation (which is quite easy to compare and process in the backend) instead of a tricky name (e.g., Falkland Islands (the) \[Malvinas]). For example, In a Country dropdown, you could have different *Option* *Values* to store in the backend and *Option Labels* to show in the dropdown list. Just like below: | Option Values | Option Labels | | ------------- | ---------------------------------- | | US | United States | | IN | India | | FK | Falkland Islands (the) \[Malvinas] | To show option label: 1. Select the **DropDown** widget, move to the properties panel, and turn on the **Add Option Labels** toggle. 2. Enter the value in the **Define Option Values** and **Define Options Labels**. Click **Add Option** (below the *Define Option Values*) to add more values and labels. 3. You must also set the **Data Type** for the values. For example, if the values you are going to store are in numbers like 1,2,3, set it to *Integer*. ### Searchable Dropdown[​](/resources/forms/dropdown.md#searchable-dropdown "Direct link to Searchable Dropdown") The *DropDown* widget is a good choice when you have a small number of options, up to around 10-20; however, If you have more options than that, consider using a searchable dropdown. A searchable dropdown allows users to search and filter options by typing in a search bar. As the user types, the dropdown list is dynamically filtered to only show matching options. This is especially useful when dealing with long lists of options and can improve the user experience by reducing the time it takes to find and select an option. To make the dropdown widget a searchable one: 1. Select the **DropDown** widget, move to the **Properties Panel > DropDown Search >** enable **Is Searchable** option. 2. You can also customize the **Search Hint Text** property. ![Making dropdown searchable](/assets/images/making-dd-searchable-f1de9e328ba77af1b6c50fbb028e23b0.png) ### Disable Dropdown[​](/resources/forms/dropdown.md#disable-dropdown "Direct link to Disable Dropdown") You might need to disable a dropdown when certain conditions are not yet met or need to be fulfilled. For example, when the dropdown options are dependent on other fields, and those fields are not filled yet. To disable the dropdown: 1. Select the **DropDown** widget, move to the **Properties Panel > DropDown Search >** enable **Disable Dropdown** option. 2. Click on **Unset** and select the source that returns the boolean value (i.e., True or False), such as boolean variable, [Conditions](/resources/functions/conditional-logic.md), [Inline Function](/resources/functions/utility.md#inline-function-code-expressions). ![Disabling dropdown](/assets/images/disabling-dropdown-05e5d82e8c412f1ae7cc909623b9e693.png) ### Allow Multi Select[​](/resources/forms/dropdown.md#allow-multi-select "Direct link to Allow Multi Select") You might want to allow users to select multiple options from the dropdown list. For example, on an e-commerce app, users might want to filter products based on multiple attributes, such as t-shirts in both 'blue' and 'red' colors. To allow multi-select, select the **Dropdown** widget, move to the properties panel, find the **Allow Multi Select** property, and enable it. info To clear the selection, you can use the [Reset Form Fields](/resources/forms/reset-form-field.md) action and choose the **Reset Dropdown Fields** option. Then, simply select the name of the dropdown widget you wish to reset. ### Changing Dropdown Size[​](/resources/forms/dropdown.md#changing-dropdown-size "Direct link to Changing Dropdown Size") To change the height and width of the dropdown, select the **DropDown** widget, move to the **Properties Panel > DropDown Properties > enter the Width and Height value**. ### Set Max Height[​](/resources/forms/dropdown.md#set-max-height "Direct link to Set Max Height") If needed, you can also control the dropdown height using the **Max Height** property. ### Adding Margin[​](/resources/forms/dropdown.md#adding-margin "Direct link to Adding Margin") Margin adds a space between the DropDown's text and its border. To change the margin, select the **DropDown** widget, move to the **Properties Panel > DropDown Properties >** find the **Margin** property, and change the values. ### Changing Background Color[​](/resources/forms/dropdown.md#changing-background-color "Direct link to Changing Background Color") To change the background color, move to the **Properties Panel > DropDown Style > set the Fill Color**. ![Changing background color](/assets/images/changing-background-color-2522beea0239fb73d5746b2a33cb77ac.png) ### Changing Menu Elevation[​](/resources/forms/dropdown.md#changing-menu-elevation "Direct link to Changing Menu Elevation") Menu elevation adds a shadow to the dropdown, giving it a sense of depth and making it appear above the surface it is placed on. To change the menu elevation (depth or Z-axis), move the **Properties Panel >** enter the **Menu** **Elevation** value. info The higher value draws the bigger size of the shadow. ### Adding Border[​](/resources/forms/dropdown.md#adding-border "Direct link to Adding Border") See how to [add a border](/resources/ui/widgets/widget-commonalities.md#adding-border). ### Show/hide Underline[​](/resources/forms/dropdown.md#showhide-underline "Direct link to Show/hide Underline") To show or hide the dropdown underline, move the **Properties Panel >** **DropDown Style** > use the **Hides Underline** toggle. ### Fix Position[​](/resources/forms/dropdown.md#fix-position "Direct link to Fix Position") By default, the dropdown options are displayed over/above the dropdown button. To display beneath/below the button, move the **Properties Panel >** **DropDown Style** > switch on the **Fix Position** toggle. ![Fix position for dropdown options](/assets/images/fix-position-44942cedca06778938f5864d54a1a709.webp) --- # Form Triggers **Form Triggers** in FlutterFlow allow you to respond dynamically to user input on widgets like dropdowns, sliders, toggles, and text fields. Whether it’s selecting an option, toggling a switch, or typing in a field, these triggers help you create interactive, responsive experiences by executing actions based on user interaction. ## On Selected[​](/resources/forms/form-triggers.md#on-selected "Direct link to On Selected") The **On Selected** action trigger is used to perform actions when a user selects or changes a value from a widget that presents multiple options. This trigger is associated with form widgets where selection input is required, such as [Dropdown](/resources/forms/dropdown.md), [RadioButton](/resources/forms/radiobutton.md), [CheckboxGroup](/resources/forms/checkbox.md#checkboxgroup), [ChoiceChips](/resources/forms/choice-chips.md), and [Slider](/resources/ui/widgets/built-in-widgets/slider.md). Possible use cases * **Dropdown – Shipping Method Selection:** User selects a shipping method from options like "Standard", "Express", or "Next Day". Action under the *On Selected* trigger sets the app state variable `shippingOption`, which updates pricing or estimated delivery time dynamically. * **Slider – Show Volume Level in Snackbar:** User adjusts a Slider from 0 to 100. The *On Selected* trigger displays a Snackbar showing the current volume: Volume set to: \[sliderValue]. * **ChoiceChips – Filter Products by Category:** User taps a chip like "All", "Electronics", or "Clothing". The *On Selected* trigger might set an app state variable (e.g., `selectedCategory`) and update the product list to match the chosen category. To use the **On Selected** trigger: 1. Start by selecting a supported widget, such as a Dropdown. 2. Open the **Actions** tab in the properties panel and click **+ Add Action**. 3. You will notice that the **Type of Action** (aka callback) is already set to **On Selected**. That means actions added under this will be called whenever the selection changes. 4. Finally, define the actions you want to perform when the user makes a selection, such as setting a variable, navigating to another page, or displaying a message. ![on-selected](/assets/images/on-selected-dc6bc4f81bd0d298bbbc71ca82d99b59.avif) ## On Toggled On / On Toggled Off[​](/resources/forms/form-triggers.md#on-toggled-on--on-toggled-off "Direct link to On Toggled On / On Toggled Off") The **On Toggled On** and **On Toggled Off** action triggers are used to perform actions when a user turns a toggleable widget on or off. These triggers are supported by widgets such as [Checkbox](/resources/forms/checkbox.md), [CheckboxListTile](/resources/forms/checkbox.md#checkboxlisttile), [Switch](/resources/forms/switch.md), and [SwitchListTile](/resources/forms/switch.md#switchlisttile), any widget that represents a binary state. These triggers are especially useful when you want to conditionally execute different actions based on whether a user enables or disables a setting, preference, or feature. Possible use cases * **Switch – Enable Dark Mode:** User toggles a Switch to enable Dark Mode. Action under the *On Toggled On* trigger sets the dark mode. * **Checkbox – Agree to Terms:** User checks a Checkbox labeled “I agree to the terms and conditions.” The *On Toggled On* trigger enables the Submit button. If the user unchecks it, the *On Toggled Off* trigger disables the button again. * **CheckboxListTile – Select Notification Channels:** User checks or unchecks options like Email, SMS, or Push Notifications. Each toggle fires either *On Toggled O*n or *On Toggled Off* to update selected preferences in the backend. To use the **On Toggled On** or **On Toggled Off** trigger: 1. Start by selecting a supported widget, such as a Switch. 2. Open the **Actions** tab in the properties panel and click **+ Add Action**. 3. Choose **On Toggled On** to define actions when the toggle is switched on, or **On Toggled Off** to define actions when it's switched off. 4. Add your desired actions, such as updating a variable, showing a message, enabling a button, or triggering a backend call. ![on-toggle](/assets/images/on-toggle-9bc23d1bd5b7301c0e2382945ce9fccf.avif) ## On Change[​](/resources/forms/form-triggers.md#on-change "Direct link to On Change") The **On Change** action trigger is used to respond to real-time user input as they type or modify the contents of an input field. This trigger is supported by widgets such as [TextField](/resources/forms/textfield.md) and [Pincode](/resources/ui/widgets/built-in-widgets/pincode.md). It’s ideal for enabling live form validations, updating app state as the user types, or enabling/disabling UI elements based on the current input. Possible use cases * **TextField – Enable Button When Email Is Entered:** As the user types in an email TextField, action under the *On Change* trigger checks if the input is a valid email. If it is, it enables the Continue button. * **Pincode – Auto Submit When Complete:** When a user finishes entering a 6-digit code in a Pincode widget, action under the *On Change* trigger checks if the full code is entered and triggers form submission or a backend call. To use the **On Change** trigger: 1. Start by selecting a supported widget, such as a TextField. 2. Open the **Actions** tab in the properties panel and click **+ Add Action**. 3. Choose **On Change** from the list of available triggers. 4. Define the actions to trigger, such as setting a variable, showing a message, or calling an API. ![on-change](/assets/images/on-change-88fcfa03bf04b3b1a30deca3915a861e.avif) *** ## On Focus Change[​](/resources/forms/form-triggers.md#on-focus-change "Direct link to On Focus Change") The **On Focus Change** trigger fires whenever an input field gains or loses focus, like when a user taps into or out of a [TextField](/resources/forms/textfield.md) and [Pincode](/resources/ui/widgets/built-in-widgets/pincode.md) widget. It’s useful for providing user guidance (on focus) or performing validations. Possible use cases * **TextField – Show Hint on Focus:** When the TextField gains focus, action under the *On Focus Change* trigger displays a helper text or tooltip with input instructions (e.g., “Enter your phone number without dashes”). * **Pincode – Validate on Exit:** When the user finishes entering the code and the Pincode widget loses focus, action under the *On Focus Change* trigger runs validation logic to check if the input is complete or valid, and displays an error if it's not. To use the **On Focus Change** trigger: 1. Start by selecting a supported widget, such as a TextField. 2. Open the **Actions** tab in the properties panel and click **+ Add Action**. 3. Choose **On Focus Change** from the list of available triggers. 4. Define the actions to trigger, such as showing helper text, validating input, or updating the UI based on focus. ![on-focus-change](/assets/images/on-focus-change-eb02d0ce94bb5b1e5310a75acda7f36e.avif) --- # Form Validation You can add validations to input fields by wrapping them inside the Form widget. The Form widget enables you to validate user inputs and display appropriate messages when validation criteria are not met. For example, you could use it to check if a user has given a valid email and password. This makes it easy to handle user input and ensure that the data is correct before it is submitted to the server or stored locally. ## Adding Form widget[​](/resources/forms/form-validation.md#adding-form-widget "Direct link to Adding Form widget") Let's see how to add a *Form* widget by building a signup example. Here's how it looks: Building and validating a *Form* includes the following steps: 1. [Adding input fields](/resources/forms/form-validation.md#1-adding-input-fields) 2. [Adding validations](/resources/forms/form-validation.md#2-adding-validations) 3. [Adding validate action](/resources/forms/form-validation.md#3-adding-validate-action) ### 1. Adding input fields[​](/resources/forms/form-validation.md#1-adding-input-fields "Direct link to 1. Adding input fields") A form widget can only validate if there are any input fields. Here's an example of adding input fields for the signup form. 1. First, add the **Form** widget itself from the **Form Elements**. 2. Inside the form, add the **Column** widget from the **Layout Elements** tab. 3. Now, add two [**TextFields**](/resources/forms/textfield.md) (one for email and one for password). 4. Add a [**Button**](/resources/ui/widgets/button.md) widget and then add **Date/Time Picker** action to get the date of birth. 5. Add one more **Button** to validate and submit the form. Here's how it looks: ![Input fields](/assets/images/fv-input-fields-217cea7794a21e90fc1475fe1cc83e15.avif) ### 2. Adding validations[​](/resources/forms/form-validation.md#2-adding-validations "Direct link to 2. Adding validations") Validation refers to the process of checking user input for correctness and ensuring that it meets certain criteria or requirements. This can include checking for the presence of required fields, verifying that a value is within a certain range or format, or validating against the custom pattern. After adding input fields, they will be available to be validated using the form widget properties. Here's how you do it: 1. Select the **Form** widget, and move to the **Properties Panel > Validate** section. 2. Identify the **TextField** on which you would like to add the validation and tick the box on the right side. 1. Inside the **Error Message** input box, provide the message that will be displayed (below the *TextField*) if a user leaves the *TextField* empty. 2. You can also specify the **Min Required Character** and **Max Allowed Characters**. 1. **Min Required Character**: This is the minimum character required for the validation to pass. For example, If you provide a value as 9 and a user enters the value as ** (which is 6 characters), \*\*then the validation fails, and an error message will be displayed. 1. Inside the **Minimum Character** **Error Text** input box, provide the message that will be displayed if a user doesn't provide the min required characters. 2. **Max Allowed Characters**: This is the maximum number of characters allowed for the validation to pass. For example, If you provide a value of 15 and a user enters a password that exceeds 15 characters, then the validation fails, and an error message will be displayed. 1. Inside the **Max Allowed Characters** **Error Text** input box, provide the message that will be displayed if a user enters more than the maximum allowed characters. 3) You can also choose to validate the input using our predefined validators or by creating the custom one. To do so, you can set the **Text Validator** to the one you need. 1. If the required validation is not on the list, you can select **Custom Regex** and specify your own **Regex (Dart/JS)**. Here are some examples of *Custom Regex*: | Examples | Regex (Dart/JS) | | ---------------------------------------- | --------------------------------------------------------------------------------- | | IP address (e.g., 192.168.1.1) | ^\d3.\d3.\d3.\d3$ | | Time in the 24-hour format (e.g., 13:45) | ^(\[01]?\[0-9] | 2. Also, provide a message in **Invalid Text Error Text**. This will be displayed If validation for the *Custom Regex* fails. 4) You can also add validation on certain actions that can be used inside the form, such as *Date/Time Picke*r and *PlacePicker*. To do so, find the action name and tick the box on the right side. 1. Now you must enable **Add Action on Error** and set the **Action Type** to the appropriate one. This will be triggered if the validation fails. For example, in this case, if a form is submitted without selecting the birth date, you can add a Show Snackbar action asking a user to select the date. ![Validating Date/Time picker](/assets/images/validating-date-time-picker-490b88db4d2332d91249ea6c37362dd6.png) ### 3. Adding validate \[Action][​](/resources/forms/form-validation.md#3-adding-validate-action "Direct link to 3. Adding validate \[Action]") Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Validate Form** (under *Widget/UI Interactions*) action. 4. Set the **Select Form to Validate** to your **Form name**. 5. You can chain the next action that will be triggered if the validation passes. ## Auto validating[​](/resources/forms/form-validation.md#auto-validating "Direct link to Auto validating") Rather than displaying an error message after the user submits the form, you can provide real-time feedback as they type in the *TextField* widget to indicate validation errors. This feature can be particularly useful for lengthy forms where it can save the user's time and effort. To auto validate a form, select **TextField >** move to the **Properties Panel > Add validations >** and then enable the **Automatically Validate**. ![Enabling auto validate](/assets/images/enable-auto-validate-c63d86f6dc81572a21a5ebeb15bb20ab.avif) ## Validating a Form on TextField On Submit[​](/resources/forms/form-validation.md#validating-a-form-on-textfield-on-submit "Direct link to Validating a Form on TextField On Submit") You can also validate a form when you are done entering a value inside the *TextField* using the *On Submit* action. To validate a form on *TextField* *On Submit*: 1. Select the **TextField** widget and select **Actions** from the Properties panel. 2. Click **+ Add Action** button, and ensure that the **Type of Action** is set to **On Submit**. 3. Search, and select the **Validate Form** (under UI Interactions) action. 4. Set the **Select Form to Validate** to your **Form name**. *** ## Video guide[​](/resources/forms/form-validation.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: --- # RadioButton The RadioButton widget is used to allow a user to select one option from multiple selections. You can use the **RadioButton** widget for implementing a single selection such as gender selection, notification preferences, etc. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding RadioButton to Your Project[​](/resources/forms/radiobutton.md#adding-radiobutton-to-your-project "Direct link to Adding RadioButton to Your Project") Here's an example of how you can use the RadioButton widget in your project: 1. First, drag the **Column** widget from the **Layout Elements** tab (in the Widget Panel) or add it directly from the widget tree. Set its **Cross Axis Alignment** to **Stretch**. 2. Now add the **RadioButton** widget from the **Form Elements** tab or add it directly from the widget tree. info The RadioButton widget adds a single option named **Option 1** by default. ### Trigger Action on Change[​](/resources/forms/radiobutton.md#trigger-action-on-change "Direct link to Trigger Action on Change") See how to [trigger an action when a selection changes](/resources/forms/form-triggers.md#on-selected) on this widget. ### Changing Option Name[​](/resources/forms/radiobutton.md#changing-option-name "Direct link to Changing Option Name") To change the name of the option: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Define Options** section. 3. Find the **Option 1** property and change the **name**. ### Adding or Removing Option[​](/resources/forms/radiobutton.md#adding-or-removing-option "Direct link to Adding or Removing Option") To add or remove an option from the RadioButton: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Define Options** section. 3. Click on the **Add Option** text. 4. Enter the name in **Option 2 Text**. 5. To remove the option, simply click on the cancel icon () displayed in the **Option name** property. ### Setting Initial Option[​](/resources/forms/radiobutton.md#setting-initial-option "Direct link to Setting Initial Option") When you run the app, no option is selected by default. To set the initial option: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Initial Option** property. 3. Enter the **name** of the option. For example, entering a value as **Jupiter** will show the second option selected on running the app. ### Styling Selected Option[​](/resources/forms/radiobutton.md#styling-selected-option "Direct link to Styling Selected Option") To change the text style of the selected option: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Radio Button Text Style** section. 3. Checkmark the **Change Selected Text Style**. (Click on it) 4. Under the **Radio Button Selected Text Style** section, change the text style. ## Retrieving RadioButton Selection[​](/resources/forms/radiobutton.md#retrieving-radiobutton-selection "Direct link to Retrieving RadioButton Selection") Let's build an example of showing the selected option in a Text widget. info For simplification purposes, the selected option is shown in the Text widget. In a real-world scenario, you may pass the RadioButton selection to your Backend (Firestore Database/API call). To retrieve the user's selection: 1. Add the [**Text**](/resources/ui/widgets/text.md) widget to your page. 2. Move to property editor and click on the **Set from Variable** text. (This will open a new panel) 3. Set the **Source** to **Widget State**. 4. Set the **Available Options** to **RadioButton**. 5. (Optional) Set the default value if you wish to. 6. Click **Save**. ## Changing the Properties[​](/resources/forms/radiobutton.md#changing-the-properties "Direct link to Changing the Properties") The Properties Panel can be used to customize the appearance and behavior of your widget. ### Changing Options Height[​](/resources/forms/radiobutton.md#changing-options-height "Direct link to Changing Options Height") To change the height of all options: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Radio Button Properties** section. 3. Enter the desired height into the **Option Height** box. ### Adding Space Around Option Text[​](/resources/forms/radiobutton.md#adding-space-around-option-text "Direct link to Adding Space Around Option Text") To add some space around the option text: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Radio Button Properties** section. 3. Find the **Margin** property and enter the values. 4. Click on the Refresh icon to reset the values. info Use the Lock button to change the Left, Top, Right and Bottom padding all at the same time. Unlocking will allow you to modify each value separately. ### Showing Options Horizontally[​](/resources/forms/radiobutton.md#showing-options-horizontally "Direct link to Showing Options Horizontally") By default, all options are shown as if they were inside the Column widget. Using *Axis* property, you can change this behavior to display all options horizontally as if they are inside the Row widget. To display all options horizontally: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Radio Button Properties** section. 3. Find the **Axis** property, change it to **Horizontal**. ### Aligning Options[​](/resources/forms/radiobutton.md#aligning-options "Direct link to Aligning Options") Changing the alignment will change how the options are distributed in the horizontal space. To change the option alignment: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Radio Button Properties** section. 3. Find the **Alignment** dropdown and select from the options displayed that include Start, Center, End. 4. If the **Axis** property is set to **Horizontal**, you will see options that include Start, Center, End, Space evenly, Space between, and Space around. ### Changing Button Position[​](/resources/forms/radiobutton.md#changing-button-position "Direct link to Changing Button Position") If you want to display the button on the opposite side of the option text i.e right side, you can do so using the *Button Position* property. To change the button position: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Radio Button Properties** section. 3. Find the **Button Position** property, change it to **Right**. ### Styling Radio Button[​](/resources/forms/radiobutton.md#styling-radio-button "Direct link to Styling Radio Button") To change the color of selected and unselected options: 1. Select **RadioButton** from the widget tree or from the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Radio Button Properties** section. 3. Find the **Selected Color** property, click on the box next to **Unset**, select the color, and then click **Use Selected Color** or click on **Unset** and enter a Hex Code directly. You can also choose the color by clicking on the Palette and Simple button. 4. Find the **Unselected Color** property, click on the box next to **Unset**, select the color, and then click **Use Selected Color** or click on **Unset** and enter a Hex Code directly. You can also choose the color by clicking on the Palette and Simple button. --- # Reset Form Field \[Action] The **Reset Form Field** action allows you to reset values in form widgets. This is especially useful for clearing previously entered data and giving users a clean slate. For example, after a form is successfully submitted, you can use this action to clear the input fields—making it easy for users to enter new information for another submission. ![reset-form-field](/assets/images/reset-form-field-9555bd87c26221abdff902bc72be91ad.avif) info You can also reset form fields that are inside the components. ![reset-form-field-component](/assets/images/reset-form-field-component-189c6112822c7a787500e872dedbe4d6.avif) --- # Set Form Field \[Action] The **Set Form Field** action allows you to programmatically populate or update the value of any input widget—like a TextField, Dropdown, or other form elements—at runtime. This is especially useful when you want to quickly fill or modify user input fields based on user preferences (e.g., saved addresses) or pre-stored information. possible use cases * **Use Saved Address:** If a user toggles "Use Saved Address," you might set the Full Name, Street Address, City, and ZIP Code fields to values pulled from a user profile or database. * **Edit Existing Data:** When navigating to an "Edit Profile" page, you can auto-populate the TextFields with the current user info so they only change what’s needed. * **Auto select Country/State Dropdown:** Automatically select the user's country and state based on location services or their account settings. While adding the Set Form Field action, select the target widget (e.g., `TextField`) and assign a value—this could come from a variable like `fullName` in your backend, app state, or page parameters. ![set-form-field-action.avif](/assets/images/set-form-field-action-0e8ca7dc0d5864645a21baa09f70a1b7.avif) If you need to update several widgets (such as a TextField and a Dropdown), use a separate Set Form Field action for each and specify the appropriate value. ![multiple-set-form-field.avif](/assets/images/multiple-set-form-field-bdcfae8c0444b80a9a4bdbaa571b1caa.avif) #### Focus Field When Set[​](/resources/forms/set-form-field.md#focus-field-when-set "Direct link to Focus Field When Set") You can also set additional preferences like whether the field should be focused and how the cursor should behave using the **Focus Field When Set** option. When you enable the option, it automatically sets the focus on the field once its value is assigned. This is helpful in scenarios such as an “Edit Full Name” switch—when turned on, the field preloads the existing name and positions the cursor for immediate editing. When **Focus Field When Set** is enabled, you can set one of the following **Cursor Position**: * **End**: Places the cursor at the end of the newly filled text, letting the user continue typing from the last character. * **Start**: Positions the cursor at the beginning of the text. * **Highlight**: Selects (highlights) the entire text, letting the user immediately overwrite it. * **Preserve**: Maintains the cursor location as it was (if any), which is useful when the user is already typing and only part of the text has changed. ![focus-field-when-set](/assets/images/focus-field-when-set-922b79f19bfb336f337de2da9304a23c.avif) info You can also set form fields inside the current widget’s child component. ![set-form-field-component](data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAIDEAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAoQAAAD6AAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQEMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAIDltZGF0EgAKChhl6D+WCBAQNCAyoEBMBAK0SXfcyYvIQUhY6FkRmHvEOBJI4nkskODet+RuOS2E4enKuEYn3tV3sVb/87UojPO8Ga3ZyL4gKhYZ6fribu85bDa/R/9q4dbVHCiWIPIjZ78nVf5cW2KiUKx3Qz/1fAPYUK4Cj2W7hQE57qapBeNEoBq1OPTLXef0hOV7CoXXT3DZUPXG76rqfneQVGSD/0FU1tMoGA1YV7SL3qaGNYoa7UqjjAWCVoG0n7poeWOcDJWjEGbBoIS84gieN5PdF1XspMCP6wBGUer8BfcEq/s7SHb27k76OUnwI7N8cnfLF9HecgFXEumeIDGJCviOvcMvq7l05TXMLLto4imKO4Ninv7rsQg/kjTprLMyK7a97qLO+EVI+hUjgpA6KPcqsL8gCmjN35FNGP+iGgmOTvDrlvHGRcO2RUtRC1i8JAr/GplQX+dr5JYRfntDR/DTAO42AT4UdslBn3vd9q+4XhKxEPffvwYXC9LYCcufdgZY4Fwo5KtKAwKZESA4fWfjZ/kgMXitTsjbCe6aToDTpOFevLXQ08gVbkYpDmT6nzeC9Upp5uaXjOqKDQKbD0QOZTTA6e1gf+GAIIPSSA20dbYgwIBXSxz3irTc8cNzf/3UnsifvpFfybVL3PNgEe8W8LVs2vc/vR+LyeUYLjcLDEZ3YhHWiKFcRsz9fhg+wDazWdMIXJbVqexjlODEEDqBNxPpdXKcxTm3zz6q8FJnSwt+oD0cKVfkgu1U+GByU+Fi02rlKVfQcCMODAkMYAqV/csel4AcEuFzJdNn5mmaO/C+3aw4PIx/nURfhSxfmwB7f+aQ8p7WZh+XMPVtz5dETaOiq8C4Ib70r3gH/nUJJlChB5z31uBvkmIYviRAoz7wEZVKognEv7M6P+mrPS9dO4Pe2AbVjtW4QvoWBELsva/M+5ipCAgnGYGlzbvHRwMIFGSSlvYagFAGJkve3DhhG+1SRQaFJuIPLrfacq0zRAj0GAx5Ws3NqMgr0yWstCF7OHeIoS+buyMimbRONJ/P3KcRDskoYLYze4KsdXhJ4mVIA2yuuZV+wxSdIb2afy0X5yLgxZsYIVWtKSOytmtX/rt+a5cq2a6z5HzzNwauM+WeGtUJyUDIJ4sxyK4UgM/P4Fz+V1waHToPwJ1ecZTnGGNSc/ruOo3GtGzf30ISeHW+piJycl6ZvfMvQ+wRER0BGDAh5UZ8UhOijmQ8l4ow4Xq6bnOMyt83/Ggb+4EnWnr7HUmPWjCKdV1U0xVPzisY35iT90lY2xFhtBO3muplEGc+4pDAxbHSbx9YcGBZlp8CgXQvn2jdpUFhXloPgpK1OBiO9+6F2BeIfMaA2rWo5YRAJgBVlRvRFFiNU65s7w343koaAhZG2EpvDrOB0wImbD/rha5NDLuWBvdkcnesE3yasBrPyGAH1VQmEKC4BiTIlqdKv0rSbSO46Lwgpj+Uxotc0RVaqNKJbM45ZnIsFdOwevRFuL5r41xIbYt1Zx0xpttv+/35RJ1sFHx/EWQDPH69Jv8mfqnCBt4kV1W7LZzGIWWYuJaPjjdJZV0pv7Avyru9HO1xrmclSrYqLCejYnneNFR2aRljosqkTVsW1N169ownMLaPJ2zjUlEsZBlY532/urzPNhFIaznev7QhKn7UOS9RiAKxkwEuus4GfD+OYnRI7r+U+Je7nfyrIKWREAU8T5tNqO0Ws3kEANLHmTlqE9JFzCCvm9uKyeSRRWJdRBkzNSN93CLUHWEXAfZs1F/TzQ4cwU1gfF30Msah5wqIwT6MLdADPwndiWvwPVMRGr8wodhAWwZj9y2W9Xk20xsRB0XFQ7x2BWfGxU3ZqkiuhChOo+YogsWmpXj0pb2WmtTh8Nxi1u8rllyH43H2tf7O7VyvLzOy8y8YRSw5jq+ozCosI6d9NkkV3zHt7GKPhNK/6MuzrI455woqaL3oYkJ7MQGxexfIJ+QWM0V/MZdpNwlW9lFWAqIDJTFIyQON72OXXL/JxAKDyozKHCrKbEpYUtCX7LN58EBbqORz0QBRSIOSn6RY3WPqa6MjnBxKThy6a2OTSH+lfS9PZdMWGSVZKAwE49no8paWj44cqTLOqSknTNfWws/w5QrUWNo47E7yKXOSlbo509pyG3vRL8m6PcNiFL4UuZ+GvdesxWmpWee5WnND0mbHZKGkI+01iCUK4zg46Folvp/XtZ3y+5BLBcIySlhDtkyVWIReFoPsP9pyaeQh4K7kg3ku98pPz1jkLS0H0MrGR68j4/OeL0i/naOt+HL6nVgLq4gRe8J8lZqr6edOJW+hGdQVsyiqMjmTTEJv8wHU2rkdvl4S5D1Y/p1VekWI+aVw2hVQh399sH9qx7e9GKoptWLgnotRIOPmTpXE7Alk3jGTOCDcUMvkaeIVcTyj+WhfRpwyyoCNUsKy33mJh3S4aR4JyDVQoW/aMDRKb7QBJ22adT6Ivoxfs+A4BDb/WTx5PBWgy9Bl8kbr+L/N7FQ7belJ7p4uuP9KM9HM3Twn9MNQTdStvmQYDorrVw4XfcGWuE4R4UXyITjFxej9KxUr1mr6KvVcVjB0hNN0/yF3Xslh1hKbyKHmPOYkEQ1wVwszBLZ8eyuUE4a6XXn5fs3hNcsLoBuuccKH5HPmFgVoGReuI3t79o0ZT8jJ4spk8zFCUjyCqbPKEkyR8lxE13Oe5K1TJvHn+U74FWdkTn637Zw6WuOes1c+aMiJV52sT/fDk1qDQcErBzHjH3SyUBRRLiRiFAQXIsReTGSu3yxbACE0SpAYLKM+kmbcnWAkqe7LZoAb6Ft/PpHxOBMhlD/qf/StyRRZBcoNc9irQyZCdP407gv6o5ON56ZpLl0mfJRQFMiRYXWJXn+c7YGuCOvlNJyAtw01hlDiS6LpkJsc7FbmnPRAPteb1fU/EVatERBkGy/yMAfCKLcA5DAh1d8fb5BItPEB2X8y5baCS8IuY9uIADd7i0Vjt9LsW+locH6WTg0IQiFVEl+Wo3h6vHaLcWxZRZ0N+J1saFbTYVIu4TGZkR9He9Z/I7zB38WJ59v0x4zAc7UmszPURvpAremDzJFjfQ2m66EIAsIgc8McW61GY+/ro0P1e5mTzk+cLt9SrY4i5lrr/9SBOg5zRZ9Tkszv9GsJfhwHD4jYTMqUWl5L0/YBa6hzUOp6MckupRFkaV30JHcB80ybBVk+N2KaIuYy3RWzszFg2CyqswQsVYPO0cV3uy8/8rrhVCABms+SLl4Z2fCZ4eZd2ahgBYvRDRmEK7RLKq+FmF7A1miUzlXegblwIsTgHiXYnPSIkVhC3yuMtkPJUnD3SU0Wsp/93gSQHYKROQYNHUyfuvfyxthZv8xTnrfiPtCqPhT1BiCzjNow3jvDrHo70pR+YOgTsyyvE/UA5nB0wX5wMxVTuZxX4UW2CCOodVw4Rx1Ai8EHyWU0FTVNqzAFvkT4J8gVke0X4mXQyaznNMOvTRkJREcTzNXEOXIBaFhkvxCKa2rGyBi2qVV+b4flMLcsk3sTydoGiidEYkhhyGfOGGq6NH3y0aVrqrPYwgHPc03D7LVCZxX3lKbhrtPFqw77zl2PfyTXKLiAFXeGxhz2yWax+iMgEjCqlttCcBSyhnEOJAN5MJQrLVknqEBxs0jUDaZj7DhQPonrwVMCSS2oI40yGoDqWM3HsMtrh0+AngaFqvofJJTFuQFO0exKh8YNcD/fW84Q6pV5zfp0gNWI4WsSACPP8UV2fjamQ2Cp0az/+fTfUiUomZS+XQModvsWuY+drrz5SffKXSwRQXf9VmLp2t+pDd30XDcV55z6rPvwv6TYYbHlTRPRRnf/Jnpv8vv+C1fZMc/UaSiPv1GBuy81GBxmt67paJeHi/z4+7kHsou4NkwubnPQtnwZmfr4A1i+GR3vVcemT8YzXH5nizhxH+TJQEJsZZaT4KbposF60bLYW7vt1Ja/vB0PxXloG4+aL46n/Z1NIIZ92r2QAuqIhJXWpXX4EgTVzkV/OkH1jT8vZI0aecIVHB0+2g5VGzdqCED8TrxyWVErgBgOA6JZk5Vw1iw8FNzbOWxe3gGgHlx69bMYJ0bACjJx7OFQiKtbGMAWG38JC6On/RcamqljWhtu301N5JOfeIP//XVWOERA7xJAqeU3nE87u+YQSsMym/x5zHejVHoRHhSjts0pfCPlaAI6tmuQ3NK15drKV1wFS2Sljl1KX195+uo3UMZTsPM5OHGOA2REwNXYFU3wRuQgJjoM0XzO9IncxII5GZKc22JWXE7bBLrpq3BfQzaawo8odZzFkHhDAQQFx72F3XQ1WqgNxhJsp/TZ9iRAxPRrTyTHBHD59BHGdMVN6+mxFpBbE6lF1WvNay9aJG4VeWxSrghll+qQsfd4OJ7Wy1yyiICK8JUsPC4Nwf303CWzrnzBMBwU3Ryrkmx/pXk1SWcFoerGLSyLvPxvAmjlRlqUHKLtLtIWEmeA+xkBcAFekOSpG8/MQVbLhEh4hA5D+67FBmBLvduSRY2OE9Ryonw6sl0r90vbQO3MTMW5VYEWDqSKol8mhuCoXmvZpQEoGAV7zBKlQqksW8coCZ5m6ZaTwOqL5nNYxvrvh1oa9x64kqRHSCvwh5KbGK/b00GI7k6NU/9aPJxV4jQUODHS27zBr2/re1YlejqMOvYEj5/OXlHY2lKMisSaziN3PUdIAfwNPNANDlF8OEXS48lYTfSbX7JfLRweqwXAeGLzUgyqWLCQrEBGoRjUTcgcRQa5T2PuVHGx7ytbqNws0yv1qLI4kVQoVZeAI5np52nhwEZZGAiZCxPhhLnRmfQoP2ySytE7wWxwI8PDzDVGIgyqi2Lj2ImmRDcrayISomHJhGFl/9ey6hhNq+SCbmcDeLaWLocn00ZPmpdoii7GXnG0XKjc+7BmB1Rv9A0+xOMfHnLf9e1jqZ7xuzjJ7xtZKvixLw+1DNVij/vKGUN9yuGfIlD+9Sz6b/CeYEB+N3vBIblNbwjJvg47w/SDkLl5UXKNAj/O1roKMrOvaAGMsfwmSAoXvx2dYyqnMvv115/JWVXF3rwp4dUZNYZ12zzCs2fx31Uerx0p4mrmgvcBHppldMO2HiXiapN3nwTIULHlVFu/IBNJCiFkY7xJwhLFmpAjFS6gj6Ucqs6nP7vtD3xmE6PuNB6tcNYRx8t4ugya4wBZ0EEFP+QRlBnmtn8uFnSVdc5qDZooflxphkYFqrc5lqKMUINyDKs5k0tMWpYDRufuK5ydSrKebo7r3C9aXpeIvUz/fEuIEB5tN0SZNdl74RYl2+tiOGa1y9BaKYM9LO7Eyvf1ZLY9FFQP4XLvPCHQiF0qz5T+iiFEJwqCfv1zeHTtGbGQZhjl9TKhUeysKk6+RAErQta7GfoDrIjYxZWdp7gpDfjPHkdDR2AgyghX5CuIBAHxWMSWMn3l4KrFan3XmELIYhIQ3D+flkAhnKTXrKjQJ3FXlLvdcwk1tMnTvzrUDmd2o1g5lfUrUwnSo5J1Oa8RKbQnFhdbm0Q08kXz354owh0nKbbbhfJ9mpdz/OO4sHiAr9Zc9o4UUA963QkLBSBjFWDvJxTfNGTfP47VsvRyytMCYJ/kP+pf7MNXmdmGhfmMfRRvmzmFIHfOsHfOTo5wQBv6aiV4DvhRSqrVEo4DyQRKE5+I+g+BvoKxZlsxLNW8vKFy56/0tOwRHaj/C8dDiecCAKbQW0nl4/7O0MFXOhL7iKife4XuA9jURBSYzFjruFF/EAFCQSxwEQjvPPETVuNc/ctxQ9gB3NtvfQieXoHbXrll5Tz6vTEf0eR40iGBaYFTWOGe986R+cOstGFPN8N07HkbGAqDJpR0Ef7BltyWjPBwGrg/uo2BfWuB8bQyiba7bXKhPxCRdvEU29gz/iSQ91jrGvCDcRV+KJ8FUIkjlqcNkdD05yOyAHludB/7KWH4IyxmM1N7vIBlr9PdwjK5PyBGtSx9Hq+dwfC+UEAU7Ed5Kp7Q8Y+xLF56WY3PNU4lNvLrvK06mjlTrXepbSol0VOEZTbFWY+MKNbKgDXMJzcbNInx4G+HWLlXMESuQiRCyRlwPJvXk4b1C5Hbr3Le2AOk2ZNGD6b9/7+dK2ChFhh58H8AOkGSvwrgpzIATs7FrYUd7s/Zk79toUBSzOZHB8EaHl1h/AjS1TiiKQKsfMVnXO52RbfVwo00ksxP7UAFSRtZMxtQgUp5i6zvNTf1pXLeR2OVcEor8bVvQOXbw7Admj4oBHfYXGnGDJ8lBbHmdWt2/DQ09s8xYgZWIFhV4Eyh8NBg4Rf9jaKrMiph+iN5Xno8Ae7KvwFVI3Hz4xTLLiNCBduv1JL1kz8NKTmUOcqD39BtGw41K+hR8IeRUfA6SkCuDWC9K6LAX4X1PouXqIfRmE7twfPDkz4whzSDjvnmEctmKO86DKpuMpD75BYyIBnWWgh3nYKzB05oPW+zfo2aGnxHaAjI7H6uFU3WD/oT1xJi+Tncu4pJE/m+CT0Np6gjI2EvbC26r9+SvwNrnIGdFSg8SbdS7ZuxbFurZs40fd5HFCCP5XjxaIfAxegXa9nn1wA3+RRIVeua8ekXg6TdfUEj7st4kHPLIGEZezIYTPXBYzaw6BsVKwDUtfZatvjeMFH48+Af5S5J5nRS3Z2dUIyGElIUtpop1hC/hnTOMYIfdAGlNTV98olvtM/e9oYbNe5ZTAuCXL2MzBrThPQ44TL5UemnkKf9Z+itTr7FuJuStuHSw9/IWaY1P6kBUW3pm57yqEXJxvI69L9qGPW2Mkfhqv+4iv7Anoa1ju2a35EyE+dMemyaDKAS4w/BaDHLIcH/ga+qlxAVAOyI4uGeSz5NGzX139sO2zVPThk2lRY5nFy1Fj4i9qAO+2xgdtC/APUD8Xh0dAE2YzIDWrrlFhAxl/zNLD/sMHTQ7G6/zoQHLOmNOzm2CtPP9Ryf5/nb5mNNS9La8V2U05UzGTseHPrh3c/2sm9NiiaQimA5hr2QVN8UH0JVJ9/nker41nPJUcjR3OKgMpwnJEU1p1NmOd+QVUopOPsJaSzOA7sHxyFfeIQuL9S9ujgTWgfcvTxKFmVKWf6axOU91kVE8vpYyBt+yxfHUeGmHQhk4qV6sn7C8uQLxEXsZ6vabsLJJn+sGxNFifqHPR1vzmsaBS5yBtLTJN4hm2bpiTokT3/dS5aZEdPDZ8urApZAdMPgCGLvRfEZuOGCzdy6+lGdfAuCPKPl5Bu6VcG94d3Zow4/BfFee55vovvpzB7GuRET2WYe3DpcJ4yDuiGmeaA3zthFAL11KiGf+YMTUC7Sa43VUlPk2degyHMgWFxVGwj8XQCmHAVNw78516TXg/JuR8+4/ZtPkZDlIOAcrBKGz6vGhCo4Osxlm64FHJGAi93jg9eWXbFEWp5DvhfiuiZmNYR1jSgkOdSfjXHsTl3EQf//D9z3cHQIfGyMEFq5hyyJE1Tdscx3ujDYyhOn4NCN3K2ABE1LOPGLE4SA2EaOpKF4YLrCSX6RVPCMklaBP8dIsbiV9h04KWpJy9nH5++5QRPLHJ9ZgrNvf/fYQtauQRv9aZ+AAoQb0aMRR1bAoRkUtj/iPD8rvEDU02QH2AD4rlmdCUpoMGOEH0DS6MTwKkkJUmu9b7E01EmlkyhFgkRZLqZb8ZXhKHVOO4CxU86TGbGM6Tq+pg5v4/4L+zWGoNNmqqIlrIuU5ERjwAua9ypV5Vfy8lkEKPFtxQwxh7dbPdeeMTqTKnmEPJ6EEJFoDxE4lngFLEru8UJM8FRaBtnxchcsN6csAVGLby5G6iH3KqRJZXWnmbCZLRzJJE+AupxzjHDx6WmZvTssrUZisWOXzMMfKrR1CwJmBqBK1+Syx8iNaMKfTasoN0nU9mTWnAl6FMHq+Ts6vgzPZH9akypyPH4zjGBh0A/w/tRA1gD4GKbujoEfirhubiXOK4bECQ8yb1ODSnUcKYMapruD+J6IiWJAw4YeF5HxEuc3bgvmsy7Kc9qCr79HYEJ2STxORMnPBrkjCdp4Y/RIA4UF7TZ6tUzpfUU++l2/wKePVO3PhUA/EBbgmW7s9YmVsNxbcib+tCmUStfPy40VIaI+g18fQTJLERWgNQAPPEwQvFG96VBRjs6ZTAiuRuwy5DLVpTWfXAfG5T1shKqmTbXWqgLR5ILMHxU5uVo72XjKGShRRzICUbFTTfKZYujAGfvo/+TcRLfTMJRdj86tR4c9BhOaByLS+bBZ472DxHfGuU9rI06PhdrDwrYRphqA+aLZtBjT1VqWv9XFtNUGYYnpkwj75b9wM0FXv+ApLRZ+79NBDxXroKd3wZ4JC9rzIlB6SUPs550m7bSOgOSajSeMQtReg+HRpKSNBYLTEyNfWIXi6lFiPQo0u1LfgEjYpxVHptAaLNnlpk3nAEvUsGkPlJOs4ZvAG+6RSaKq6uXiIYkRslOrOksVL1soKB2I7hbMzGeSrQhm2exCSv2iSviIZabaZ++GvExo6S7IcLHWRRm8VG7y65SJMXn/CfFkPknfofLtzxl9BTgtVOygqtC7ZgnfioAb9RKXBXEiCAVOdYng6rKzgT2TEzx69Hs7/VV8o1ObEwpwowH7qVHFaNAbOyVMAKFKuqq0sLIbKt/zCNr9aHRjwjwg86vC2P4XWw1NVv1ESqz3dvHmGabRwNn3VWZGoUZ9pScF2LLmpZunQ4Bl2jI7iJm5nqnGUot3jmnEnaVy+3IhX+etNyeE4tocqydr0z5QF8SPqjbg5h8PEBZL9jWPGpPN1Wkx2mOWJjmjNKB94KB2JeyNlKVmC5yKFFyObmRuu6kj9t77T0iiuDaXoG/FVfUlD3ktrtfv3ffX34pnfO6slGCvUBTrRu0chEGlLpXUsHnOyZt6Ih6eHle94F5V9j83ApyPMAWbbeTeIblL6rT0Tsu1VVGft4iqVjFGFpCFicrn02uO6nsv6g6ZBu2C3UK0L/B8a0wFQ362qFwvE6YjXmjFK9VPoseVUVuf2n75GsSE18qcddECUx/Wy9Rupo5t87HglUrBeWZKQ2Cqi7uiT//IFO088GnOx1OxuuoAZjhT7iU0QYlN5P/1b9CctHJ6ch2gWFCuQUTeyH/P3Ua08HWr/0DSTU3ZDsqQpA/9AJGqBi2aOWst0tkQ6D4OXVktk5p72hRwd0SPVpDDS3CO0050lEUPwOxnp+Qf3mHCYDu7shrAXA6fM6XaxnKLOE5wV3aOo/8KJL69+dG6XM/7dK51MEoEMM/r2+n8KucuTFOrSfsbs6CzLd1QfrDlO1eH7ypvFo3Z75tth3XaHT/5d4AJEzg7UrkTosKOqlgZlniZrNrq74SrkbzHlBVN2LOqXM77LTkF/b56CIUVCeNPnZkTXwDhzXUCSniJSMbH1eo4XH7coMfeRCm7w0JUY0QlLMOY24w4BUbYLx2kcKANLDTy37tewK0YIUUeksop+V9c9gY851sj6jWWQgvcdcGl9vRGC6+omy2Rgo6LSkkIuWK/1FI84GhuH0qpCxRxjud4ju8GuYFT1KK9Nslr03lEw166ZJ/yKmkaiD2uQlz6btZDs0iyv6PrPwRo7Efit9lFTUAPibUXRyeCFBZuX5J9G+nLeDV6nyB9K77yd9KJ+ef/8IHhiwTTgDziLDK2fnJDmwEDY9Dccel5Mbor1w5cPTuhS5L3hCQU2Xk52ENqeiWJzMTgoLyQgRt1f/VRdr/aNzUb0+KPWMTRdn5EZSOI7zmwqC05D1Ga6/ePuxKlk8Gd76Fo5yNhIS0ZgWJSe7PQARklwreZ4IW4C7zkD8pulzf61Y9FIwCY4NQWTtf5B88NgrFGBXz6knVzgFDMidiid6J/6PjF5CbT0/omvxSBMayCudhKDKzj80dmHbWOnADOko4D+5ZH5TNjXthcAHdM1GSt4Vcv+E5FcXZieieQajOCX1YNKsAFKj8dzfXaRPUA7tSvovUfu6t1wR3j9xK9QOGrbPz4s1cy0dS7WUL/p1HrYflyJmlhYmwP8NviAF5a5bvvHKpt2a69x6+GZHnIJlM/QAEPA10hfd6tiQ0NwQKtrIz56Cj9WMZuXJPtsCl0i40Se0nD7y0Dbt6LvdkLxE8HFddoIHFlUsIAE1FO7TvzNGhIYabMR5pKn6EkrBAy1SAWtgle/7/jQ2/smSg69401SMRsf/JeGRZLNPzMqYdTTmddkPK8XJyMk5GKMJQ9T3a2ipkSvHH1AxDSDAFXvaOtL09k4OPAPPuEL3FjYlPMw8h8eAiRYcjxD6CPJz6Qlu5Z17sOGNmO9+PFuDmzkqUyTTjPPwh1VlhxaRELM/9MITnRfmvW4nEpSrI9zkxoxqwwpqSu0Ky5lSvcAUBCHVbHteRpAA4GdqLuzK+82x9TJ0A47AkcByPyskk2J6Mb7DoA81IjUCvcOjTmLaysCe+nIawXM9dpzy3o/t6lK4eb/S2xPC/LSjPvWwDQqN69aPAwQDRIpLmpRR/Yp05Tgdh3fS4DUpMmHvNFC8BprEhHhVOSlQRlXFuFFnqDsJFKh8qfcCsJT4jVwb0RIYDemB/bfUfWkT+dArYoF22xBwNMBrIi6vBUQL1QH9epW4YI2vn7HSzeL4YskN5iRSpL0WO9FjnSOmbRE+jAuGRN/jespg9cbEwyXRjemwIYWQ1gMH/VhLc9SaXUbrmpGKxvxwO+MENnAawpb3uRYfhA0W4NnHoWUdYkGShHgVyHs+p2I42wjMB5/2mG8r9rEWQplrVH7ZpGOD0yPfo9fVCmQbDzRqj/22CYrf6CqzpEG/KiRagIB5Ls+WML1ceCzlYHPpcnxRqNqJhVT/zbc4b5Uc+zzvn86oH6GCFGkN0SxXnObdyX16+T5IyT5ZHRlfZtBY17VojlJwQbBB7Ud4a6bifpdEXJd8IXwWYKXw1jaEm6zQc7glEvmCQm2XT5Yxdjj/EfWw54wWoA+xfD9kwTTKgmHLEaqaJh6108NZLUS7coNSlT) --- # Switch Widgets In FlutterFlow, **Switch** widgets provide an intuitive way for users to toggle between two states, such as on/off or enabled/disabled. They are useful for settings, preferences, and other scenarios where a simple binary choice is required. FlutterFlow offers two primary switch widgets: [**Switch**](/resources/forms/switch.md#switch) and [**SwitchListTile**](/resources/forms/switch.md#switchlisttile). Each of these widgets provides unique features and use cases, making it easy to incorporate toggle functionality into your app's interface. ## Switch[​](/resources/forms/switch.md#switch "Direct link to Switch") The **Switch** widget is a straightforward toggle switch. It consists of a sliding button that can be moved between two positions, indicating an on/off state. You can customize the appearance and behavior of the switch, such as its color, and initial state (whether it starts as on or off). ### Adding Switch[​](/resources/forms/switch.md#adding-switch "Direct link to Adding Switch") Let's see how to add a switch widget and build an example that shows its value on a Text widget. Here's how it looks: Here is a simple way to do it: 1. First, click on the **+ Add Widget**, drag the **Switch** widget from the **Base Elements** tab, or add it directly from the widget tree. 2. Below the Switch, add a [**Text**](/resources/ui/widgets/text.md) widget, move to the properties panel, click on **Set from Variable** and choose the **Widget State > switchValue** (i.e., name of your switch). ### Setting Initial Value[​](/resources/forms/switch.md#setting-initial-value "Direct link to Setting Initial Value") You might want to show the switch with a default status, i.e., ON or OFF. For example, showing the location service setting with a default switch OFF. To set the initial value: 1. Select the **Switch** widget, move to the properties panel, and see the **Switch Initial Value** property. 2. Use the checkbox to set this value manually, or click **Set from Variable** to set it based on the dynamic value. If you choose *Set from Variable*, ensure you pass the boolean value from the source (e.g., API response, Firestore document field). ### Saving Switch Value[​](/resources/forms/switch.md#saving-switch-value "Direct link to Saving Switch Value") You may want to save the switch value as soon as it is toggled ON or OFF. To do this, [add an action using the trigger](/resources/forms/form-triggers.md#on-toggled-on--on-toggled-off) that responds to changes in the widget’s selection. ### Customizing[​](/resources/forms/switch.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. #### Changing color[​](/resources/forms/switch.md#changing-color "Direct link to Changing color") To change the switch colors, select the **Switch** widget, move to the properties panel, and scroll down to the **Switch Properties** section. Here you can [change the color](/resources/ui/widgets/widget-commonalities.md#change-color) for the following properties: * **Active Color**: The color of the thumb (circle) when the switch is ON. * **Active Track Color**: The color of a track (the line over which the circle slides) when the switch is ON. * **Inactive Track Color**: The color of a track (the line over which the circle slides) when the switch is OFF. * **Inactive Thumb Color**: The color of the thumb (circle) when the switch is OFF. #### Disable switch[​](/resources/forms/switch.md#disable-switch "Direct link to Disable switch") You may need to disable a switch if certain conditions aren't met. For instance, users should only be able to toggle the switch when the connected smart device is operational. To disable a switch, move to the **Properties Panel** **>** turn on the **Switch Disable Options >** click **Unset,** and set the [**Condition**](/resources/functions/conditional-logic.md). Once set, you could also customize the disabled state colors using the *Disabled Active Color, Disabled Active Track Color, Disabled Inactive Track Color,* and *Disabled Inactive Thumb Color* properties. ## SwitchListTile[​](/resources/forms/switch.md#switchlisttile "Direct link to SwitchListTile") The **SwitchListTile** widget combines the functionality of a switch with a **[ListTile](/resources/ui/widgets/composing-widgets/list-grid.md#listtile-widget)**, providing a more comprehensive option for displaying toggle switches alongside additional information. This widget includes a switch, a title, and an optional subtitle, all within a single, cohesive element. SwitchListTile is ideal for use cases where you want to provide more context or descriptive text alongside the switch, such as in a settings menu or a form with detailed options. ### Adding SwitchListTile[​](/resources/forms/switch.md#adding-switchlisttile "Direct link to Adding SwitchListTile") Here's an example of how you can use a SwitchListTile widget in your project: 1. Drag the **SwitchListTile** widget from the **Base Elements** tab and drop it inside the **Column**. 2. By default, the switch is enabled initially. 1. To turn it off, move to the properties panel, and **uncheck** the **Switch Initial Value** property. 2. To set its value based on the variable (e.g. app state variable, API response), move to the properties panel, click on the **Set from Variable** and choose the **Source**. 3. To set the title, scroll down to the **Title** section and change the **Text** property. 4. Similarly, scroll down, find the **Subtitle** section, and change the **Text** to add the description. ### Setting Platform Type[​](/resources/forms/switch.md#setting-platform-type "Direct link to Setting Platform Type") You can set the platform type to *Adaptive or Android* for this widget. Selecting the Adaptive type will display the widget in its native style. That means the widget will show iOS-style rendering when running on iOS devices and Android-style rendering when running on Android devices. To set the platform type: 1. Select the **SwitchListTile** widget from the widget tree or the canvas area. 2. Move to the properties panel and open the **Platform** section. 3. Set the **Platform Type** among the **Adaptive** or **Android**. ### Customizing[​](/resources/forms/switch.md#customizing-1 "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. #### Changing switch color[​](/resources/forms/switch.md#changing-switch-color "Direct link to Changing switch color") To change the switch color: 1. Select **SwitchListTile** from the widget tree or the canvas area. 2. Move to the Properties panel and scroll down to the **Switch List Tile Properties** section. 3. To change the color of the thumb (sliding circle), find the **Thumb Color** property and click on the box next to the already selected color, select the color, and then click **Use Color** or click on the already selected color and enter a Hex Code directly. 4. To change the color of the track (the line over which the circle slides), find the **Track Color** property and click on the box next to the already selected color, select the color, and then click **Use Color** or click on the already selected color and enter a Hex Code directly. #### Showing switch at the start[​](/resources/forms/switch.md#showing-switch-at-the-start "Direct link to Showing switch at the start") To make the switch appear before the title: 1. Select **SwitchListTile** from the widget tree or the canvas area. 2. Move to the Properties panel and scroll down to the **Switch List Tile Properties** section. 3. Scroll down and checkmark the **Leading** property (click on it). --- # TextField The TextField widget allows users to enter text, numbers, and symbols in your app. You can use the TextField widget to build forms, send messages, dialogs, search, etc. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding TextField Widget[​](/resources/forms/textfield.md#adding-textfield-widget "Direct link to Adding TextField Widget") Let's see how to add a TextField widget and see an example of displaying its value in an Alert Dialog. Here are the steps: 1. First, add the TextField widget, move to the properties panel and give it a name. 2. Add the [**Button**](/resources/ui/widgets/button.md) widget and on tap of it, add an Alert Dialog action. While adding this action, provide the Message **From Variable > Widget State > \[TextFieldName]**. ## Customizing[​](/resources/forms/textfield.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Changing Width[​](/resources/forms/textfield.md#changing-width "Direct link to Changing Width") By default, the TextField widget takes all the available space in the horizontal direction. You might want to limit its width to match your design. See how to change the width of this widget. ### Adding Multiline/auto Expand Support[​](/resources/forms/textfield.md#adding-multilineauto-expand-support "Direct link to Adding Multiline/auto Expand Support") By default, a TextField is only one line. So when you type in a long text that won't fit in one line, you'll be able to see an entire message using a horizontal scrollbar. You can change this default behavior and show the full message (without a horizontal scrollbar) by making the TextField multiline/auto-expand. To make a TextField multiline/auto-expand, move the **Properties Panel *>*** find the **Max Lines** and **Min Lines** properties. 1. To make the TextField auto-expand as long as its parent allows, remove the **Max Lines** value and set the **Min Lines** to **1**. 2. To make the TextField auto-expand up to a few lines and then show a vertical scrollbar to see the full message, set **Max Lines** to a value up to which you like to show an entire message (e.g., 3,5) and **Min Lines** to **1**. ### Setting Prefilled Value[​](/resources/forms/textfield.md#setting-prefilled-value "Direct link to Setting Prefilled Value") You might want to display a TextField with some initial value. This can be any specific value such as "*What are you looking for*", "*Input your Email*", or a value from any variable. To set the initial value, move to the **Properties Panel > TextField Properties > Initial Value** and enter the specific value or *Set from Variable*. ![setting-prefilled-value](/assets/images/setting-prefilled-value-c47b24e1996e7de1532b8b69333d6444.avif) ### Adding Label[​](/resources/forms/textfield.md#adding-label "Direct link to Adding Label") Showing a label helps users understand what should be entered into the TextField. If you don't have an initial value set, the *Label Text* will appear as full size in the TextField. Once the user taps the TextField, the *Label Text* will become smaller, and the *Hint Text* will appear. To set the label, move to the **Properties Panel > Label Properties >** enter the **Label Text**. ![adding-label](/assets/images/adding-label-902c402214265f2a53df93b3547b6c10.avif) When the TextField is set to [Multiline](/resources/forms/textfield.md#adding-multilineauto-expand-support) the label appears in the center. To get it closer to the hint text, switch on the **Align Label With Hint** property. ### Setting Hint Text[​](/resources/forms/textfield.md#setting-hint-text "Direct link to Setting Hint Text") Showing a hint text helps users know what information is needed to enter into the TextField. For example, showing hint text as "Enter Your Email Here" clearly informs the user to enter their email. To set the hint text, move to the **Properties Panel > Hint Properties > enter the Hint Text**. ![setting-hint-text](/assets/images/setting-hint-text-ea03c449e2bd46fc47082cf5b62a51b3.avif) ### Decorating TextField[​](/resources/forms/textfield.md#decorating-textfield "Direct link to Decorating TextField") Various properties under the *Input Decoration Properties* allow you to customize the TextField to match your design. ### Changing TextField Background Color[​](/resources/forms/textfield.md#changing-textfield-background-color "Direct link to Changing TextField Background Color") To change the background color, move to the **Properties Panel > Input Decoration Properties >** enable **Filled >** set the **Fill Color**. ### Adding Border[​](/resources/forms/textfield.md#adding-border "Direct link to Adding Border") Here's an example of how you can add a border around the TextField: 1. Select TextField widget, move to the **Properties Panel > Input Decoration Properties > select the Input Border Type**. 1. Choose **Outline** to place a border around the entire field. 2. Choose **Underline** to place a border only on the bottom of the field. 3. Choose **None** to completely remove the border. 2. You can also set a color to the border for various states, such as when TextField is in a *Focused* or *Error* state. To do so, use the **Border Color**, **Focused Border Color**, and **Error Border Color**. 3. To increase the border thickness, use the **Border Width** property. 4. To create the rounded border, use the **Border Radius** property. By default, any value your enter will be set for all corners, which are TL (Top left), TR (top right), BL (bottom left), and BR (bottom right). Click on the lock icon to change each corner separately. Use the refresh icon to reset the values. ### Add Content Padding[​](/resources/forms/textfield.md#add-content-padding "Direct link to Add Content Padding") Content Padding adds space between the test and the border of your TextField. To add content padding, move to the **Properties Panel > Input Decoration Properties >** enter the **Content Padding** value. ### Reducing TextField Height[​](/resources/forms/textfield.md#reducing-textfield-height "Direct link to Reducing TextField Height") To reduce TextField's height to as minimum as possible, select the TextField widget, move to the **Properties Panel >** enable the **Dense** property. ### Changing Error Message Styling[​](/resources/forms/textfield.md#changing-error-message-styling "Direct link to Changing Error Message Styling") You can also change the text styling for the error message. To do so, head over to **Properties Panel > Input Decoration Properties >** enable **Custom Error Style** and [change the text styling](/resources/ui/widgets/text.md#common-text-styling-properties). ![changing-error-message-styling](/assets/images/changing-error-message-styling-007121650f2b27c1d1bbe4fba7422883.avif) ### Adding Icon[​](/resources/forms/textfield.md#adding-icon "Direct link to Adding Icon") You might want to add an icon inside the TextField, either at the start or end. You can do so using the *Leading* and *Trailing* Icon property. To add a leading or trailing icon, move to the **Properties Panel >** find the **Leading** and **Trailing Icon** property > Click on the **None** button **>** search and select the icon. You can also [customize the icon's size and color](/resources/ui/widgets/icons.md#common-icon-properties). ![adding-icon](/assets/images/adding-icon-95bb6492fecbd52ce05440bc093e7a75.avif) ### Using TextField for Passwords[​](/resources/forms/textfield.md#using-textfield-for-passwords "Direct link to Using TextField for Passwords") To make a TextField a Password Field, move to the **Properties Panel > Additional Properties >** enable the **Password Field**. When you enter a password, it will be obscured with the dot (•). You can see and confirm the entered password by clicking on the **Toggle Hide Password Icon**. You can also customize its size and color. ![textfield-for-passowrd](/assets/images/textfield-for-passowrd-6b3f53f88dfc032e449d8199f664e528.avif) ### Clear TextField[​](/resources/forms/textfield.md#clear-textfield "Direct link to Clear TextField") A clear field icon inside the TextField allows the users to quickly remove the entered text. To clear a TextField, move to the **Properties Panel > Additional Properties >** enable the **Show Clear Field Icon**. You can also customize the icon's color and size. #### Adding Clear Text Fields/Pin Codes \[Action][​](/resources/forms/textfield.md#adding-clear-text-fieldspin-codes-action "Direct link to Adding Clear Text Fields/Pin Codes \[Action]") This action lets you clear the values from single or multiple TextField and PinCode widgets. This comes in handy while implementing a form inside your app, and you want to let the user reset the form with one click. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., IconButton, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), click **+** **Add Action** button. 3. Search and select the **Clear Text Fields/Pin Codes** (under *Widget/UI Interactions*) action. 4. Select the *TextFields* and *PinCode* widgets you want to reset. ![adding-clear-textfield-action](/assets/images/adding-clear-textfield-action-10eaab0d9d431d61dfa1c0c8308015bc.avif) ### Autofocusing TextField[​](/resources/forms/textfield.md#autofocusing-textfield "Direct link to Autofocusing TextField") When you autofocus a TextField, it mimics the tap event and immediately shows the keyboard. This makes TextField ready to receive input from you without having you click on TextField. To autofocus a TextField, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** enable the **Autofocus** property. ### Enable Interactive Selection[​](/resources/forms/textfield.md#enable-interactive-selection "Direct link to Enable Interactive Selection") The **Enable Interactive Selection** toggle controls whether users can interact with the text selection features, such as long-press selection, copy/paste menus, and selection handles. By default, this property is set to **True**, allowing users to select, copy, and paste text using the platform's built-in text selection controls. Disabling this can help prevent unintended text copying or editing, especially in sensitive fields. ![interactive-selection](/assets/images/interactive-selection-323dae6cef2a9187fe13cfa47857e9a5.avif) ### Autocomplete a TextField[​](/resources/forms/textfield.md#autocomplete-a-textfield "Direct link to Autocomplete a TextField") You might want to allow users to enter the text by suggesting them a list of items. The suggested items are shown if it contains the currently entered text from TextField. For example, using autocomplete to get the *Country* *name*, *Fruit* *name*, etc. info This helps avoid spelling mistakes and enhances the user experience as users won't have to enter the complete text. To autocomplete a TextField, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** enable the **Autocomplete** property. Now you can customize the autocomplete using the **Autocomplete Properties** section. Here's how you do it: 1. Inside the **Autocomplete Options**, click **Add Option** and provide item names that you would like to appear in the suggestion box. 2. You can also **Set from Variable** to show items from any variable, such as app state variable, API response, and Firestore collection. info If you *Set from Variable* and run the app in preview mode, you can try entering the country name. The list will be populated with matching countries. 3. You can also customize the appearance of the suggestion box using properties such as **Height**, **Elevation**, **Background Color**, and **Highlight Color** (highlighting the currently selected option in the dropdown list). 4. To style the text displayed inside the dropdown list, you can use the **Option Text Style** and **Substring Style** (can be used to highlight the matching text in an item name). ### Auto Fill Hint[​](/resources/forms/textfield.md#auto-fill-hint "Direct link to Auto Fill Hint") When *Auto Fill Hint* property is enabled, it uses the operating system's autofill service to suggest the relevant information to the user, such as usernames, passwords, or credit card numbers, based on the context of the text field. For example, you have a form where the user needs to enter their credit card information. You can use this property to help the autofill service suggest the user's credit card number and expiration date. To enable the Auto Fill Hint property: 1. Select the TextField widget, move to the **Properties Panel** **> Additional Properties >** enable the **Auto Fill Hint** property. 2. Set the **Auto Fill Hint Options** to one that you want to provide a hint about. warning The availability and behavior of the *Auto Fill Hint* may vary by platform and user settings, and it does not guarantee that the operating system's autofill service will suggest the correct information to the user. ### Update Page on Change[​](/resources/forms/textfield.md#update-page-on-change "Direct link to Update Page on Change") You might have added the TextField widget inside the search page and want to refresh the search result as the value inside the TextField changes. info Enabling this feature will refresh the page whenever a user types into TextField after a configurable delay. Here's an example of displaying the TextField value in a Text widget in realtime: 1. Select the TextField widget, move to the **Properties Panel** **> Additional Properties >** enable the **Update Page On Change** property. 2. Also, set the **Update Delay (ms)**, which specifies the time interval after the user stops typing before the page refreshes its UI. For example, if the *Update Delay (ms)* value is set to 2000 ms (2 seconds), the page will update 2 seconds after the user stops typing. For this example, let's set it to 0 ms. 3. Now select the **Text** widget, move to the **Properties Panel > Set from Variable > Widget State > \[TextFieldName]**. Tip: You can also set the default value to be displayed until the user has entered any text. tip We advise setting the delay value if you make an API call that accepts the input from TextField. ### Read only TextField[​](/resources/forms/textfield.md#read-only-textfield "Direct link to Read only TextField") Sometimes you might want to restrict users from entering or updating anything into TextField and only allowed it if they are in edit mode. You can accomplish this by switching the **Read Only** property. ### Change Cursor Color[​](/resources/forms/textfield.md#change-cursor-color "Direct link to Change Cursor Color") In a form with many text fields, changing the cursor color for the currently focused field can help the user understand where their input will go when they start typing. To change the cursor color, head over to **Properties Panel** **> Additional Properties >** change the **Cursor Color**. ![change-cursor-color](/assets/images/change-cursor-color-05f5660685d861eddeefade60a07d7ab.avif) ### Changing Keyboard Type[​](/resources/forms/textfield.md#changing-keyboard-type "Direct link to Changing Keyboard Type") When the keyboard opens by default, you can type any text. You might want user input in a certain format, such as a phone number, email address, website URL, etc. In this situation, you can choose a predefined keyboard type to present the appropriate key selections. To change the keyboard type, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** set the **Keyboard Type** to the right one. ![keyboard-types](/assets/images/keyboard-types-e4a1beb8860678317acbb152580517c6.avif) ![changing-keyboard-type](/assets/images/changing-keyboard-type-7bcaa99496b93d0ca8b3165eb5c38544.avif) ### Masking Input[​](/resources/forms/textfield.md#masking-input "Direct link to Masking Input") You might want to allow users to provide input in a specific format. For example, if you want a date in a format like MM/DD/YYYY, where all input must be a number, and its length should not exceed eight digits. You can do so by formatting the user input using the specific mask. To mask the user input, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** set the **Mask** dropdown to the one you need. If the required format is not on the list, you can select **Custom** and specify the **Custom Mask**. The '#' sign represents the number, and 'A' represents a letter. Here are some examples of *Custom Masks*: | Input | Custom Mask | | ---------------------------------------------- | ------------------- | | Credit card number (e.g., 3424 4353 5453 3535) | #### #### #### #### | | Custom date (e.g., 12-Jan-2023) | ##-AAA-#### |
### Filtering Input[​](/resources/forms/textfield.md#filtering-input "Direct link to Filtering Input") You might want to restrict the type of characters that can be entered into a TextField. Let's say you are building an app that requires its employees to enter their employee ID when they clock in and out for their shifts. The employee ID consists of only letters and numbers, and the app should only allow these characters to be entered. You can do so by filtering the user input To filter the user input, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** set the **Filter** dropdown to the one you need. ### Validating Input[​](/resources/forms/textfield.md#validating-input "Direct link to Validating Input") You can validate the TextField value by wrapping it inside the [Form](/resources/forms/form-validation.md) widget and adding the validation criteria. tip Filtering ensures that only the allowed characters or values are entered, whereas the validation checks the entire input data against certain criteria. Both techniques can be used together or independently to ensure the correctness of user input in a TextField widget. ### Capitalization[​](/resources/forms/textfield.md#capitalization "Direct link to Capitalization") You might want to control the capitalization of text when the user is typing, and also when the text is displayed. The Capitalization property allows you to specify how the text entered in the TextField should be capitalized. This property accepts one of the following values: * **None**: This value means that no capitalization should be applied to the text. All the characters will be displayed as they are typed. * **Words**: This value capitalizes the first letter of each word in the text. * **Sentences**: This value capitalizes the first letter of each sentence in the text. * **Characters**: This value capitalizes every character in the text. To set the capitalization, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** set the **Capitalization** dropdown to the one you need. ![capitalization](/assets/images/capitalization-882a7bdbe6b787328f3798af98637803.avif) ### Submit Type[​](/resources/forms/textfield.md#submit-type "Direct link to Submit Type") Showing a particular action on a keyboard can be useful in guiding users on what to do next. For example, if you have a search bar, you can display a "Search" button on the keyboard. When tapped, instead of moving to a new line or closing the keyboard, you can execute a search function. This can improve user experience by providing more intuitive keyboard actions based on the context of the input. This property accepts one of the following values: * **Done**: This closes the keyboard. * **Next**: This moves focus to the next field. * **Previous**: This moves focus to the previous field. * **Send**: This represents the Send action. * **Search**: This represents the Search action * **Go**: This represents the Go action. To set the submit type, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** set the **Submit Type** dropdown to the one you need. ![submit-type](/assets/images/submit-type-7b60ba6675f9b8ad3b637f0b56c950d7.avif) ### Set Max Character Length[​](/resources/forms/textfield.md#set-max-character-length "Direct link to Set Max Character Length") Sometimes, you might want to specify the maximum number of characters users can enter into the TextField. When the user types or pastes text into the field and reaches the specified character limit, they won't be able to input more characters, or the TextField will visually indicate that the limit has been reached. For example, When users can leave comments or post messages (similar to 'tweet'), setting a maximum character length can help prevent spam or excessively lengthy responses. To set the max character limit, select the TextField widget, move to the **Properties Panel** **> Additional Properties >** set the **Max Length** (number of characters you want to allow), and set the **Max Length Enforcement** to one of the following values: * **Not Enforced**: This allows users to input extra characters and displays a warning when the limit is exceeded. * **Enforced**: This always truncates any additional character once the limit is reached. info You can also hide the maximum character count by enabling the **Hide Max Length Counter** option. ## Hiding Keyboard on Tap[​](/resources/forms/textfield.md#hiding-keyboard-on-tap "Direct link to Hiding Keyboard on Tap") Hiding the keyboard when the user taps outside of a TextField is a common user experience pattern that many apps use to improve usability. When the keyboard is open, it can obscure important information on the screen and make it difficult for the user to interact with other parts of the app. Adding this behavior in your app can make it easier for these users to interact with other parts of the app without interference from the keyboard. It can also make your app feel more polished and professional. To hide/close the keyboard, select the page, move the **Properties Panel >** enable the **Hide Keyboard on Tap**. ![Hide keyboard on tap](/assets/images/hide-keyboard-tap-2-845a510336fc3403e93e770c5a78ad8b.avif) ## Focus Change Event[​](/resources/forms/textfield.md#focus-change-event "Direct link to Focus Change Event") Sometimes, you may need to know whether a TextField is being used or not. For example, you can turn other parts of the app *on* or *off* depending on if the TextField is active. Also, you can start animations when someone starts or stops typing in the TextField. Let's see an example of controlling the visibility of a Text widget based on the TextField's Focus state. To do so: 1. On a Text widget, add a [Conditional Visibility](/resources/ui/widgets/widget-commonalities.md#conditional) based on the TextField's Focus state. You can access via **Set from Variable** menu **> Widget Focus State > \[TextField name]**. 2. Now, on a TextField widget, under the [On Focus Change](/resources/forms/textfield.md#trigger-action--listen-callback) callback, simply add an action to refresh the page by adding the update app state variable. ## Trigger Action / Listen Callback[​](/resources/forms/textfield.md#trigger-action--listen-callback "Direct link to Trigger Action / Listen Callback") The TextField widget provides you with two types of actions (aka callbacks): 1. **On Submit**: Actions under this will be triggered when you finish entering the text in the TextField widget. i.e., pressing a done button inside the soft keyboard. 2. **On Change**: Actions under this will trigger when you enter or delete a character in the TextField widget. 3. **On Focus Change**: Actions under this will trigger when the focus state changes on a TextField. This means when users click on it to type or click away from it. warning Be careful about adding the actions under the **On Change**. Specifically, you should avoid adding any action that will take more time. * On Submit * On Change * On Focus Change To trigger an action: 1. Select the TextField widget from the widget tree or canvas area. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Select the **Type of Action** among the **On Submit, On Change,** and **On Focus Change**. 4. Now you can add any action here. *** ## Video guide[​](/resources/forms/textfield.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: --- # Action Blocks An Action Block is a set of actions that perform a specific task and can be reused in different parts of the app. If you find yourself repeatedly performing a particular set of operations in your app, it may be helpful to create an Action Block. This allows you to break down complex actions into smaller, more manageable units, making them easier to understand and modify in the future. Action Blocks have different scopes, which determine their availability: | **Action Block Type** | **Description** | **Scope** | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | **App Level Action Blocks** | Usable across the entire app. You can create an App Level Action Block from any page or component, and it will be accessible for viewing or editing from any page or component as well. | Internally, an App Level Action Block can only access the state variables available in its scope (e.g., app state variables). | | **Page Level Action Blocks** | Restricted to the page in which they were created. These can access the state variables available in their scope, such as page state variables, as well as variables above their scope, such as [App State variables](/resources/data-representation/app-state.md). | Page Level Action Blocks can access page state variables and App State variables. | | **Component Level Action Blocks** | Restricted to the component in which they were created. These can access the state variables available in their scope, such as component state variables, as well as variables from higher scopes, like page and App State variables. | Component Level Action Blocks can access component state variables, page state variables, and App State variables. | Unsupported Actions in Action Blocks Some actions are not supported and cannot be used in an Action Block. By default, these actions are hidden in the Action Block Editor. For example, actions under the **Firebase Authentication** category, **Start Periodic Action**, **Upload Data**, and others. ## Action Blocks Structure[​](/resources/functions/action-blocks.md#action-blocks-structure "Direct link to Action Blocks Structure") When creating an Action Block, the process of defining the flow is similar to **[defining Actions](/resources/functions/action-flow-editor.md#adding-an-action-example)**. The main difference is in choosing the scope and defining the input & output values of the Action Block. ### Choosing the Scope of Action Block[​](/resources/functions/action-blocks.md#choosing-the-scope-of-action-block "Direct link to Choosing the Scope of Action Block") As discussed, Action Blocks can be **App Level, Page Level**, or **Component Level**. App Level Action Blocks can be created from any widget's action properties throughout the app. However, Page Level or Component Level Action Blocks are only available in the Page or Component where they were created. Usually, you will see a dropdown to choose between App Level, Page Level, or Component Level. Choose the scope based on your Action Block's use case. ![action-blocks.png](/assets/images/action-blocks-9d20367e001bf2a5d845ba072d3b36fc.png) ### Action Parameters[​](/resources/functions/action-blocks.md#action-parameters "Direct link to Action Parameters") Action Blocks have access to the state variables available in the same scope as the Action Block (for e.g., Page State variables can be accessed from Page Level Action Blocks). However, there will be times when you may need to input some parameters for the Action Block to perform its logic. These are called **Action Parameters**, and they can be added from the Action Flow Editor when you create a new Action. For example, here is a small demo where we create an Action Block with an input parameter. In this example, we add an item to the wishlist of an e-commerce app. Let's say our local wishlist is saved in an App State variable called `localWishlist`, and we have a reusable Action Block called `addToWishlist` that takes an input parameter called productId and performs the actions to add it to the `localWishlist` object. ### Return Values[​](/resources/functions/action-blocks.md#return-values "Direct link to Return Values") Often, your Action Block may return a value. For example, in our Product Cart Page, we have a reusable Component Level Action Block called `getTotalCost` that returns the final cost of all the products. You can define such an Action Block that returns a value (e.g., a double for this example) or a value related to your use case. You can define the return value in the Action Flow Editor. Let's see one example. --- # Actions Effectively managing user interactions is essential for developing interactive applications. Designing interactivity involves two steps: 1. Listening for Interaction (**Action Triggers**) 2. Responding to Interaction (**Actions**) **Action Triggers** represent a specific event, while **Actions** are functions executed in response to the triggered event. Common triggers are: * **On Tap**: Triggered on tapping on a widget or specifically buttons. * **On Selected:** Triggered on selecting an option from a dropdown list. * **On Page Load:** Triggered on loading a page Actions are tasks or operations that are performed in response to an event detected by a trigger. ## Action Flow Editor[​](/resources/functions/action-flow-editor.md#action-flow-editor "Direct link to Action Flow Editor") The Action Flow Editor is a visual, node-based editor used to configure the functions that run in response to a trigger. This editor simplifies the process of creating and managing business logic. ![Action Flow Editor](/assets/images/actions-e960c2b3fcd71f75014ba419ffa23dc8.avif) ### Action Triggers[​](/resources/functions/action-flow-editor.md#action-triggers "Direct link to Action Triggers") When you open the Action Flow Editor, no triggers are added by default. To add a trigger, simply search for and select the desired one from the available options. The Action Triggers bar, located at the left of the editor, displays all added triggers. info To learn more about **Action Triggers** and its types, refer [**here**](/resources/functions/action-triggers.md). Exposed by FlutterFlow Please note that Action Triggers are exposed by FlutterFlow and are not user-generated. You can only work with the ones provided in the Action Flow Editor. Each trigger has its own separate node-editor, allowing you to create distinct logic flows for different events. When you switch between triggers, the node-editor will update to display the logic specific to the selected trigger. [Switching Triggers](https://demo.arcade.software/IazHon14tfvS4UljRsqu?embed\&show_copy_link=true) info It's important to note that the logic defined in the node-editor is associated with the selected trigger. This means that the actions you set up will only be executed when that particular trigger is activated. ### Node Editor[​](/resources/functions/action-flow-editor.md#node-editor "Direct link to Node Editor") This central area of the editor is where you define and visualize the logic/actions that will execute in response to the selected trigger. The actions are laid out in a flowchart-like manner, making it easy to understand and modify the flow of actions. Actions in the Node Editor are executed synchronously. This means that if an action returns a value, it will be available to subsequent actions within the flow. Synchronous vs Asynchronous **Synchronous actions** are executed one after another, with each action waiting for the previous one to complete. **Asynchronous actions** are executed independently and can run concurrently, allowing other following tasks to proceed without waiting for them to finish. ### Creating Action[​](/resources/functions/action-flow-editor.md#creating-action "Direct link to Creating Action") If there is no initial action or if there is an action,and you want to add another one and press the plus icon, the following options will be available: 1. **Add Action**: Adds a single action node to the flow. You can add multiple synchronous actions one after another. 2. **Add Conditional Action**: Adds a conditional node with an input for a boolean expression and two action branches. The actions in each branch will be executed based on the evaluation of the boolean expression. 3. **Add Loop**: Adds a loop flow that contains an input boolean expression and an action flow. The actions within the loop will be executed repeatedly as long as the expression evaluates to true ( similar to a while loop). 4. **Add Parallel**: Adds two action flow branches that will be executed in parallel. 5. **Paste Action(s)**: Allows you to paste actions previously copied to the clipboard. After creating an action node, you need to specify the action type in the Right Panel. Creating a node is equivalent to creating an empty function, and specifying the action type is like filling out the function body with the desired logic. [Create New Action](https://demo.arcade.software/I9valjo4KqgEs8qol2Wp?embed\&show_copy_link=true) ### Right Panel[​](/resources/functions/action-flow-editor.md#right-panel "Direct link to Right Panel") The Right Panel serves two main purposes: 1. **Selecting Actions**: Choose the specific actions you want to add to your action flow. 2. **Configuring Actions**: Configure the properties, parameters, and return names of the selected action. [Arcade Flow (Fri May 10 2024)](https://demo.arcade.software/oHXsShi0Kyo5hbOIYZL5?embed\&show_copy_link=true) ### Widget Binding[​](/resources/functions/action-flow-editor.md#widget-binding "Direct link to Widget Binding") In the Action Flow Editor, the icon in the upper left corner indicates the widget to which the current action flow is bound. ![Widget Binding](/assets/images/widget-binding-5e3c31a9a5e00772c04a2fc51ad6c67a.avif) info If you rename your widget, the new name will automatically be updated and associated with this action flow. This makes it easier to keep track of the logic associated with each widget, ensuring clarity and better organization of your action flows. ### Issues[​](/resources/functions/action-flow-editor.md#issues "Direct link to Issues") The bug icon will display warnings and errors in any of the action flows bound to this widget. Note, these are neither issues in the whole project nor issues in all of the action flow but *only* issues generated from the action flows bound to *this* widget. This includes *all* the action flows on *all* the triggers and not just currently visible action flow on the selected trigger. ![Issues](/assets/images/action-errors-3bd97539688a6e9a8ae102f8e5b74cf5.avif) ### Action Blocks[​](/resources/functions/action-flow-editor.md#action-blocks "Direct link to Action Blocks") The diamond icon in the Action Flow Editor opens a menu where you can create and edit Action Blocks. **Action Blocks** are reusable action flows that can accept parameters and return values, promoting code reusability and modularity. ![action-block.avif](/assets/images/action-block-icon-7f09cb4c8a36689a1503abee4a6057db.avif) Deep Dive on Action Blocks Learn more about different types of **[Action Blocks](/resources/functions/action-blocks.md)** and their scopes. ## Adding an Action \[Example][​](/resources/functions/action-flow-editor.md#adding-an-action-example "Direct link to Adding an Action \[Example]") Here's a quick demo of how you can add an action or multiple sequential actions to a widget: --- # Action Triggers **Action Triggers** represent specific events that occur when a user interacts with the app, such as tapping a button, selecting an option from a dropdown, or loading a new page. When an Action Trigger is invoked by one of these interactions, it initiates a corresponding **Action**—a task or operation that responds to the event. In essence, Action Triggers are the '*listeners*' in your app, keeping an eye out for user interactions and signaling when it's time for your app to respond. By understanding and utilizing Action Triggers, you can craft a more dynamic and user-friendly application experience. ## Types of Action Triggers[​](/resources/functions/action-triggers.md#types-of-action-triggers "Direct link to Types of Action Triggers") ### Page & Component Root Level Triggers[​](/resources/functions/action-triggers.md#page--component-root-level-triggers "Direct link to Page & Component Root Level Triggers") FlutterFlow provides several action triggers that allow you to respond to a page or component being initialized, or things like a key press event. For more information on these triggers, see the [Page Actions & Lifecycle](/resources/ui/pages/page-lifecycle.md) and [Components Actions & Lifecycle](/resources/ui/components/component-lifecycle.md) pages. ### Basic Triggers[​](/resources/functions/action-triggers.md#basic-triggers "Direct link to Basic Triggers") FlutterFlow provides several basic action triggers that can be easily added: * **On Tap**: This trigger is activated when a user taps on a widget. For instance, you can use this trigger to display a [Snackbar message](/resources/ui/pages/scaffold.md#snackbar) when a [button](/resources/ui/widgets/button.md) is tapped. * **On Double Tap**: This trigger is activated when a user taps a widget twice quickly. A typical example might be zooming in on an image or photo when the user double-taps on it. * **On Long Press**: This trigger is activated when a user presses and holds down on a widget for an extended period. A common use case is to show additional options or a context menu, such as allowing a user to delete or rename a file when long-pressing on it. Here’s an example of showing a message on button click using the **On Tap** trigger: ### Widget Specific Triggers[​](/resources/functions/action-triggers.md#widget-specific-triggers "Direct link to Widget Specific Triggers") Certain widgets offer specific triggers that activate based on user interactions or device events. These triggers enable developers to define custom behaviors for various situations. Below are examples of widget-specific triggers: * **On Submit**: Triggered on the TextField widget when the user presses "submit" or "done," finalizing text entry. * **On Page Load**: Available on the page widget, this trigger activates as soon as the page loads, useful for tasks like data fetching or content updates. * **On Phone Shake**: Specific to the page widget, this trigger responds to physical shaking of the device, commonly used in games for actions like rolling dice. * **On Selected**: Found on widgets like Dropdowns, CheckboxGroups, Sliders, RadioButtons, ChoiceChips, and RatingBars, this trigger activates upon any change in selection. * **On Page Swipe**: Available on the PageView widget to trigger actions when the page is swiped. * **On Toggle**: Available on the ToggleIcon widget, this trigger responds each time the toggle is activated. * **On Completed, On Change**: Specific to the PinCode widget, these triggers activate when the user completes or alters a pin entry. * **On Count Changed**: Present in the CountController widget, this trigger responds to changes in the count. ## Gesture Detector Triggers[​](/resources/functions/action-triggers.md#gesture-detector-triggers "Direct link to Gesture Detector Triggers") Gesture Detector Triggers enable you to respond to user gestures, such as taps, drags, swipes, and pinches. These triggers are invoked based on specific gestures and allow you to add actions in response to user interactions. For example, the `onDoubleTap` trigger is invoked whenever a user quickly taps twice on a widget, which can be used to toggle a 'like' state or zoom in on content. These triggers are accesible when you add an action onto a `Container`. ### Lifecycle stages[​](/resources/functions/action-triggers.md#lifecycle-stages "Direct link to Lifecycle stages") The lifecycle of gesture triggers involves four key stages: **Start**, **Update**, **End/Stop**, and **Cancel**. These stages dictate how gestures are detected and handled, from the initial interaction to completion or cancellation. Understanding this lifecycle is crucial for building intuitive gesture-based interactions in your app. #### Tap Gesture Lifecycle[​](/resources/functions/action-triggers.md#tap-gesture-lifecycle "Direct link to Tap Gesture Lifecycle") Tap gestures have a simpler lifecycle, focusing primarily on detecting taps and whether they complete or get canceled. Here's how the tap lifecycle works: 1. **Down**: This stage begins when the user places their finger on the screen to initiate a tap. For example: `onTapDown` is triggered when the user touches the screen to start a tap. 2. **Up**: The tap gesture is completed when the user lifts their finger from the screen. For example: `onTapUp` is triggered when the user completes the tap by lifting their finger. 3. **Tap**: After both of the above actions are successfully completed, `onTap` is triggered indicating a full tap gesture has occurred. 4. **Cancel**: If the user moves their finger too much before lifting it, the tap is canceled, preventing the completion of the action. For example: `onTapCancel` will be called, and `onTap` will not be triggered. Here’s how the lifecycle flows for tap gestures: ![tap-gesture-lifecycle](/assets/images/tap-gesture-lifecycle-aa2aabf02f7c37fef8fb5ba2381c11e3.avif) #### Drag Gesture Lifecycle[​](/resources/functions/action-triggers.md#drag-gesture-lifecycle "Direct link to Drag Gesture Lifecycle") Drag gestures are more complex, involving continuous tracking of movement across the screen. The drag lifecycle involves the following stages: 1. **Start**: This stage occurs when the user begins dragging their finger on the screen. For example: `onHorizontalDragStart` is triggered when a horizontal drag is initiated. 2. **Update**: During the drag, the gesture’s movement is tracked, allowing you to capture real-time data like the pointer's position or delta values. For example: `onHorizontalDragUpdate` is triggered as the user drags their finger, enabling the app to track the drag’s progress. 3. **End/Stop**: The drag gesture is completed when the user lifts their finger, finalizing the interaction. For example: `onHorizontalDragEnd` is triggered when the user finishes dragging and lifts their finger off the screen. 4. **Cancel**: If the drag gesture is interrupted before it completes (for instance, by another gesture), it will be canceled. For example: `onHorizontalDragCancel` is triggered if the drag is interrupted before finishing. Here’s how the lifecycle flows for drag gestures: ![lifecycle-stage.avif](/assets/images/lifecycle-stage-fb3686571c89efb6d780be88eb80173f.avif) ##### Drag Gesture Cancellation[​](/resources/functions/action-triggers.md#drag-gesture-cancellation "Direct link to Drag Gesture Cancellation") For drag gestures, lifecycle doesn't always strictly follow the sequence of **Start**, **Update**, **End/Stop**. The **Cancel** stage can occur at any point, even before **End/Stop**, depending on the interaction. This is different from tap gestures because a drag can be canceled after it has started or even while it is being updated. For example, If a horizontal drag is interrupted before the drag completes (for example, if another gesture takes precedence), `onHorizontalDragCancel` is triggered instead of `onHorizontalDragEnd`. ![lifecycle-stage-cancel.avif](/assets/images/lifecycle-stage-cancel-0fb909588adf6847ef4ca5d046e680ed.avif) ### Available Gesture Detector Triggers[​](/resources/functions/action-triggers.md#available-gesture-detector-triggers "Direct link to Available Gesture Detector Triggers") Below is a complete list of available gesture detector triggers in FlutterFlow to enhance the capabilities of gesture-based interactions. * **onDoubleTapCancel**: Triggered when a double-tap gesture is recognized but does not complete successfully. * **onDoubleTapDown**: Triggered when the user presses down on the screen for the first tap in a double-tap sequence. * **onForcePressEnd**: Triggered when the user releases a press that exceeds a certain force threshold. * **onForcePressPeak**: Triggered when the force of a press reaches its peak. * **onForcePressStart**: Triggered when the user begins pressing with enough force to pass a defined threshold. * **onForcePressUpdate**: Triggered when the user changes the amount of pressure applied during a press. * **onHorizontalDragCancel**: Triggered when a horizontal drag gesture is interrupted or canceled. * **onHorizontalDragDown**: Triggered when the user first touches the screen and initiates a horizontal drag. * **onHorizontalDragEnd**: Triggered when the user ends a horizontal drag gesture. * **onHorizontalDragStart**: Triggered when the user begins a horizontal drag gesture. * **onHorizontalDragUpdate**: Triggered continuously as the user drags horizontally. * **onLongPressCancel**: Triggered when a long press gesture is recognized but doesn't complete. * **onLongPressDown**: Triggered when the user first presses down on the screen with the intention of a long press. * **onLongPressEnd**: Triggered when the user releases a long press. * **onLongPressMoveUpdate**: Triggered as the user moves their finger while holding down during a long press. * **onLongPressStart**: Triggered when the long press gesture starts after the user holds down for the required duration. * **onLongPressUp**: Triggered when the user releases a long press after the hold duration. * **onPanCancel**: Triggered when a pan gesture (general dragging) is interrupted or canceled. * **onPanDown**: Triggered when the user first touches the screen with the intention of panning. * **onPanEnd**: Triggered when the user ends a pan gesture. * **onPanStart**: Triggered when the user begins a pan gesture. * **onPanUpdate**: Triggered continuously as the user drags their finger across the screen. * **onScaleEnd**: Triggered when the user ends a scaling gesture, such as pinch-to-zoom. * **onScaleStart**: Triggered when the user begins a scaling gesture. * **onScaleUpdate**: Triggered continuously as the user changes the scale (e.g., zooms in or out). * **onSecondaryLongPress**: Triggered when the user presses and holds with a secondary pointer (e.g., two-finger press). * **onSecondaryLongPressCancel**: Triggered when a secondary long press gesture is recognized but does not complete. * **onSecondaryLongPressDown**: Triggered when the user first touches the screen with a secondary pointer intending to long press. * **onSecondaryLongPressEnd**: Triggered when the user releases a secondary long press. * **onSecondaryLongPressMoveUpdate**: Triggered as the user moves a secondary pointer while holding down during a long press. * **onSecondaryLongPressStart**: Triggered when the secondary long press gesture starts after holding down for the required duration. * **onSecondaryLongPressUp**: Triggered when the user releases a secondary long press after the hold duration. * **onSecondaryTap**: Triggered when the user taps with a secondary pointer (e.g., two-finger tap). * **onSecondaryTapCancel**: Triggered when a secondary tap gesture is recognized but does not complete. * **onSecondaryTapDown**: Triggered when the user first touches the screen with a secondary pointer intending to tap. * **onSecondaryTapUp**: Triggered when the user releases the screen after a secondary tap. * **onTapCancel**: Triggered when a tap gesture is recognized but does not complete successfully. * **onTapDown**: Triggered when the user first touches the screen with the intention of tapping. * **onTapUp**: Triggered when the user releases the screen after a tap. * **onTertiaryLongPress**: Triggered when the user presses and holds with a tertiary pointer (e.g., three-finger press). * **onTertiaryLongPressCancel**: Triggered when a tertiary long press gesture is recognized but does not complete. * **onTertiaryLongPressDown**: Triggered when the user first touches the screen with a tertiary pointer intending to long press. * **onTertiaryLongPressEnd**: Triggered when the user releases a tertiary long press. * **onTertiaryLongPressMoveUpdate**: Triggered as the user moves a tertiary pointer while holding down during a long press. * **onTertiaryLongPressStart**: Triggered when the tertiary long press gesture starts after holding down for the required duration. * **onTertiaryLongPressUp**: Triggered when the user releases a tertiary long press after the hold duration. * **onTertiaryTapCancel**: Triggered when a tertiary tap gesture is recognized but does not complete successfully. * **onTertiaryTapDown**: Triggered when the user first touches the screen with a tertiary pointer intending to tap. * **onTertiaryTapUp**: Triggered when the user releases the screen after a tertiary tap. * **onVerticalDragCancel**: Triggered when a vertical drag gesture is interrupted or canceled. * **onVerticalDragDown**: Triggered when the user first touches the screen and initiates a vertical drag. * **onVerticalDragEnd**: Triggered when the user ends a vertical drag gesture. * **onVerticalDragStart**: Triggered when the user begins a vertical drag gesture. * **onVerticalDragUpdate**: Triggered continuously as the user drags vertically. ### Accessing Gesture Detector Data[​](/resources/functions/action-triggers.md#accessing-gesture-detector-data "Direct link to Accessing Gesture Detector Data") Gesture detectors not only recognize types of gestures but also provide relevant data based on the trigger. For example, the exact location (XY coordinates) where a drag event occurs. Examples of using gesture data include: * **Custom Slider:** Use the coordinates to update the position of the thumb of a custom slider on its track. * **Interactive Zoom:** Used the data provided by the scale gesture to appropriately zoom in or out. * **Dynamic Interfaces:** Create effects that react to touch, like animations that start from where the user taps the screen. You can access the Gesture Detector data after adding the relevant gesture detector triggers. Once added, you can retrieve this data via the **Set from Variable** menu inside the **Action Flow Editor**. Depending on your specific needs, you can choose from the following options: * **Global Position X**: The x-coordinate of the pointer relative to the left edge of the screen when the gesture was triggered. * **Global Position Y**: The y-coordinate of the pointer relative to the top edge of the screen when the gesture was triggered. * **Local Position X**: The x-coordinate of the pointer relative to the left edge of the widget that has the action triggers applied. * **Local Position Y**: The y-coordinate of the pointer relative to the top edge of the widget that has the action triggers applied. * **Delta X**: The horizontal distance the pointer moved during the gesture. * **Delta Y**: The vertical distance the pointer moved during the gesture. ![access-xy-data](/assets/images/access-xy-data-98437951d7a2304fb0a923c07529b3a3.png) See how to effectively use gesture detector triggers and access XY data in the following example. ### Example: Swipe to delete cart items[​](/resources/functions/action-triggers.md#example-swipe-to-delete-cart-items "Direct link to Example: Swipe to delete cart items") Let's walk through an example that demonstrates how to implement a "Swipe to Delete" feature for cart items **entirely** using Gesture Detectors. Here's a preview of how it works: Here’s how you do it: 1. First, we create a variable called `offsetX` to track the horizontal drag distance of the cart item. Since the cart item is displayed in a **ListView** and is built as a reusable component, we'll define `offsetX` as a **component state variable**. This ensures that each cart item independently tracks its own drag position. ![component-state-variable.avif](/assets/images/component-state-variable-207381e4133bc82238588f7e9d023a32.avif) 2. Now, to make the item move as the user drags it, we add a **slide animation** (under **On Action Trigger**) to the Container that holds the item's layout. While configuring the animation, set the **Duration** to 0 and the **Final Position** to the `offsetX` variable. This ensures that the item follows the user's finger as they swipe. info We'll trigger this animation every time the user swipes by listening to the `onHorizontalDragUpdate` event (see how to do it in next step). ![add-animation.avif](/assets/images/add-animation-8edbaddf0a8a5d669e5638cf27824b18.avif) 3. On the main Container, we add the `onHorizontalDragUpdate` action trigger. This will called continuously as the user drags the item horizontally. On this event, we update the `offsetX` variable with the new position based on the swipe movement (using **Delta X** Data) and trigger the animation. This real-time update makes the item slide on the screen. 4. Now we need to check if the swipe meets the threshold to delete the item or reset the item's position back to its original location. For that, we add the `onHorizontalDragEnd` trigger. In the `onHorizontalDragEnd` trigger, we check if the `offsetX` value exceeds 100. If it does, we send the item index back to the page or component (via execute callback action) to delete the item from the list. If not, we reverse the slide animation. Lastly, we reset the `offsetX` value to 0 to ensure it's ready for the next interaction. --- # Conditional Logic Conditional logic is a fundamental concept in programming and software development. It involves making decisions in code based on certain conditions. This is achieved using conditional statements, which evaluate expressions to determine whether they are true or false. Depending on the result, different actions or outcomes are executed. #### How Conditional Logic Works[​](/resources/functions/conditional-logic.md#how-conditional-logic-works "Direct link to How Conditional Logic Works") * **Condition:** An expression that evaluates to either true or false. * **True Path:** The set of actions to execute if the condition is true. * **False Path:** The set of actions to execute if the condition is false. ![true-false.png](/assets/images/true-false-326377a00a7b1d4e0d107d516594f4cc.png) ## Conditional Flows[​](/resources/functions/conditional-logic.md#conditional-flows "Direct link to Conditional Flows") Conditional flows enhance basic true-false logic by handling multiple conditions and executing specific actions based on those conditions. This is achieved through more complex flows, such as single conditions, multiple conditions (using AND/OR), and conditional values with If/Then/Else logic. ### Single Condition[​](/resources/functions/conditional-logic.md#single-condition "Direct link to Single Condition") This flow allows you to define a condition based on the comparison of two values, which can be set manually or derived from variables. The condition will return **True** or **False**. **Comparison Operators:** * Equal To * Not Equal To * Less Than * Greater Than * Less Than Or Equal To * Greater Than Or Equal To * Is Set * Is Not Set ![single-condition.png](/assets/images/single-condition-fc718d2facebe1cb7c2137da8dfe8770.png) ### Multiple Conditions (AND/OR)[​](/resources/functions/conditional-logic.md#multiple-conditions-andor "Direct link to Multiple Conditions (AND/OR)") This flow lets you combine multiple single conditions using logical AND or OR operators. It is useful for more complex decision-making processes. ![multiple-condition.png](/assets/images/multiple-condition-8d10635d362ca8204fda8d0de7d56a19.png) ### Conditional Value (If/Then/Else)[​](/resources/functions/conditional-logic.md#conditional-value-ifthenelse "Direct link to Conditional Value (If/Then/Else)") Conditional Value allows you to set a dynamic variable based on different conditions. For each condition, you can specify a value that will be assigned if the condition is true. A default value can be provided if none of the conditions are met. See the example **[below](/resources/functions/conditional-logic.md#setting-widget-properties-with-conditional-logic).** ## Setting Widget Properties with Conditional Logic[​](/resources/functions/conditional-logic.md#setting-widget-properties-with-conditional-logic "Direct link to Setting Widget Properties with Conditional Logic") FlutterFlow allows you to dynamically set the properties of widgets based on conditional logic. Depending on the expected data type of the property, you can use a combination of conditional flows to achieve your desired logic. Here's an example where we use Conditional Logic to determine the value of a Text widget: If the `placePicker` widget state is set, then return the placePicker address string. Else, if the `defaultAddress` component parameter is set and not empty, then return that as a string. Otherwise, return a default address value. ## Conditional Actions[​](/resources/functions/conditional-logic.md#conditional-actions "Direct link to Conditional Actions") When you need to execute actions based on specific conditions, you can do so in the Action Flow Editor. By combining simple single conditions or multiple conditions, you can create complex logical flows. These conditions can be configured as learned in the Setting Properties section, allowing your action flows to follow **True/False** logic or **If-Else, If-Else If-Else** structures. Here's a quick demo to illustrate a simple Single Condition Action flow: You can easily convert a single condition action flow into a multiple condition action flow by enabling the Multiple Conditions toggle. Here's how: --- # Loops **Loops** in FlutterFlow allow you to perform repetitive tasks without writing complex code. This is useful when working with lists of data or when you want to repeat actions a certain number of times. There are two types of loops supported in FlutterFlow: ## While Condition Loops[​](/resources/functions/loops.md#while-condition-loops "Direct link to While Condition Loops") A **While Condition** loop requires a condition. The actions within the loop will continue to trigger as long as the condition holds true. When the condition becomes false, the loop terminates, and the next actions in the workflow will trigger. For example, you can use a While Condition loop to continuously check if a user is still within a geofenced area. As long as the condition `isUserInLocation == true` holds, the app might keep checking for updates or show a live indicator. ![loop-block.png](/assets/images/loop-block-fc90ec57a9d391e64cbcebc44df2a956.png) ## Over List[​](/resources/functions/loops.md#over-list "Direct link to Over List") This loop type lets you iterate over a list of items to perform actions for each item in the list. For example, if you have a list of items in a shopping cart and want to calculate the total price or apply a discount to each item, you can use Over List to go through each product and perform a calculation for each one. You can also customize how the loop iterates: * **Start Index**: Where the loop starts (default is `0`). * **End Index**: Where the loop ends (default is the length of the list). * **Step Size**: Interval between each iteration (e.g., set to `2` to loop through every second item). * **Reverse Order**: Enables the loop to iterate from the end of the list to the beginning (e.g., showing the latest messages first). ![loop-over-list.avif](/assets/images/loop-over-list-012eca6fedc40882eb110c201b83b898.avif) Inside a loop, you can access the current item and its index. This gives you the ability to work with each item individually, such as displaying item-specific data and making calculations. ![access-item-inside-loop.avif](/assets/images/access-item-inside-loop-e97760677c5d140e18249dacb7b27078.avif) Nested Loops You can also add a loop inside another loop to handle related data structures. For example, looping through orders and then looping through each order’s line items. ## Loop Breaks[​](/resources/functions/loops.md#loop-breaks "Direct link to Loop Breaks") AVOID an INFINITE LOOP Be careful with loop actions, as they can cause your app to enter an infinite loop if the condition never becomes false. Always ensure that the condition will be met at some point so the loop can exit. If the intended operation is completed before the condition becomes false, you must add a **Loop Break** action in your workflow to exit the loop. **Loop Breaks** are statements used to exit a loop prematurely, before the loop's normal termination condition is met. They are typically used to stop the loop when a certain condition is satisfied, preventing unnecessary iterations and allowing the program to proceed to the next section of actions. **Key Points:** * **Purpose:** Exit the loop immediately when a specific condition is met. * **Implementation:** Typically implemented with the "Add Break" node in Action Flow Editor. * **Usage:** Commonly used to avoid infinite loops or to stop looping once a desired result is achieved. ![loop-block-return.png](/assets/images/loop-block-return-fc9222466eb9481890fbb79e2f5ef4dc.png) --- # Utility Functions Utility functions are crucial for simplifying common tasks in app development, such as performing quick calculations, formatting data, and concatenating strings. In FlutterFlow, you can effortlessly integrate these utility functions when setting variables to value sources. This allows you to simplify processes like calculations, data formatting, and text manipulation directly within the visual builder. FlutterFlow has the following built-in functions: * **Combine Text:** A built-in function that lets you concatenate strings, making it easy to join multiple text elements together seamlessly. * **Inline Function:** This feature allows you to perform simple calculations and data manipulations quickly and efficiently. ## Combine Text[​](/resources/functions/utility.md#combine-text "Direct link to Combine Text") Oftentimes, you will encounter scenarios where you need to show two variables in a single String or Text widget. For example, in our [Ecommerce Demo](https://bit.ly/ff-docs-demo-v2) app, we have a price object in the following format: ``` "price": { "currency": "$", "amount": 25.50 } ``` However, when displaying the data in the UI, we should combine both the currency value and amount, as they make sense only together. In such cases, we can use the **Combine Text** built-in function available in all value sources that take a `String`. You can combine any number of *dynamic* and *static* variables together, even if they are not `Strings` themselves. In the end, the final value is always a String since it is set to a widget that only accepts `String` data types. Here is a quick demo: Combine Text vs RichText widget The **Combine Text** built-in function only allows you to combine multiple values (dynamic or static) together, with the same text style applied to all of them. If you need to combine multiple String values with different text styles for each, consider using the **[RichText](/resources/ui/widgets/text.md#richtext-widget)** widget. ## Inline Function (Code Expressions)[​](/resources/functions/utility.md#inline-function-code-expressions "Direct link to Inline Function (Code Expressions)") info **Code Expressions** was renamed to **Inline Functions** starting from FlutterFlow 6.0 version. Often times, you may need to quickly format data, convert a data type from one form to another, or perform a simple calculation before setting the variable to a data source, such as a widget value source. Inline Function is a piece of code that combines operators, variables, and/or values to produce a result. It can be used for arithmetic and logical operations, among other tasks. To add inline function, open the Set from Variable dialog wherever it's possible to set a dynamic value and choose the values that will be part of the inline function. For example, we may want to quickly calculate the discount amount of a product where the discount is 18% of the MRP of the product. The expression would be `cost - (cost * discount)`. tip Looking for more power and flexibility? Use the new [**Custom Code Expression**](/resources/functions/utility.md#custom-code-expression). It’s a more advanced version of Inline Functions that lets you access FlutterFlow generated resources without passing them as arguments. You also get real-time autocomplete and inline error checking for faster, more accurate logic. **Precedence of operations** Inline Function for math operations follow typical precedence (e.g., multiplication/division before addition/subtraction), but parentheses can change the order. In this case, the variables we need are `cost` and `discount`. So, we create two arguments in the **Inline Function** dialog where they hold the value of `cost` and `discount`, assign the data type for each of the arguments, and define the return type of the final value. In this case, the return type is a `double` since it holds the **subtotal** amount. Now you can write the inline function in the **Expression** field and click on **Check Errors** to see if the expression is valid. If it is valid, you will see the generated code for the same. The arguments in a Inline Function can take the following properties: | DataType | Supports Nullable | Supports List | | -------- | ----------------- | ------------- | | String | ✅ | ✅ | | Integer | ✅ | ✅ | | Double | ✅ | ✅ | | Boolean | ✅ | ✅ | | Colors | ✅ | ✅ | ### Common Examples[​](/resources/functions/utility.md#common-examples "Direct link to Common Examples") Here are some common expressions you can use for your business logic: | Expression | Description | Example | Return Type | | ---------------------------------- | ------------------------------------------------------- | -------------------------- | -------------- | | `contains()` | Checks if a **string** contains a particular substring. | `text1.contains(text2)` | `bool` | | `split()` | Splits a **string** into a list of substrings. | `text.split(",")` | `List` | | `toLowerCase()` or `toUpperCase()` | Converts all characters in a **string** to lowercase. | `text.toLowerCase()` | `String` | | `contains()` | Checks if a **list** contains a particular element. | `fruits.contains("apple")` | `bool` | | `max()` | Returns the larger of two numbers. | `math.max(a, b)` | `int` | | `toDouble()` | Converts the **integer** to a **Double**. | `intValue.toDouble()` | `double` | | `int.parse(s)` | Convert the **String** into an **integer.** | `int.parse(stringValue)` | `int` | ## Custom Code Expression[​](/resources/functions/utility.md#custom-code-expression "Direct link to Custom Code Expression") **Custom Code Expression** lets you write short Dart code directly in widget property fields and action flows in FlutterFlow. It’s a more powerful version of [**Inline Function**](/resources/functions/utility.md#inline-function-code-expressions), allowing you to directly access FlutterFlow generated classes, global variables, widget properties, parameters, and more without needing to manually pass them as inputs. Custom Code Expressions also support real-time autocomplete, making it easy to discover available fields as you type. For example, when you type `FFAppState().`, it will suggest all available app state variables along with their types. In addition, inline validation provides immediate feedback as you write, helping you catch syntax errors or invalid property references. info To use Custom Code Expression, you must have an active [**FlutterFlow paid plan**](https://www.flutterflow.io/pricing). tip * To explore what you can access within a Custom code expression, refer to the [**Common Examples**](/concepts/custom-code/common-examples.md) page. * Press `^ + Space` (or `Ctrl + Space`) while typing to see suggestions for what you can access in your Custom code expression. * You can access values inside custom structs. For example, you can use `FFAppState().localDeviceInfo.osVersion` if that field exists in your app state. * To use Custom code expressions better, it's helpful to understand how FlutterFlow builds your project behind the scenes. You can check the [**State Management**](/generated-code/state-management.md) page and other **Generated Code** sections to learn how everything is set up. Here are a couple of examples showing how to access App State and Page State within a Custom code expression: * **App State Access:** For example, to check if dark mode is enabled using an App State variable: ``` FFAppState().enableDarkMode ? 'Dark Mode On' : 'Light Mode Off' ``` This accesses the global `enableDarkMode` boolean stored in `FFAppState`, and returns a string based on its value. * **Page and Component State Access:** For example, to access a page or component state variable like `searchText`, you start with `_model.` and then select the variable from the autocomplete suggestions. ``` _model.searchText.isEmpty ? '' : 'Searching for "${_model.searchText}"' ``` This expression checks if the `searchText` variable (defined as a page state) is empty, and returns an appropriate message. The `_model` object refers to the current page’s generated state model. Here's an example of adding a Custom Code Expression: ### Execute Custom Code \[Action][​](/resources/functions/utility.md#execute-custom-code-action "Direct link to Execute Custom Code \[Action]") To use a Custom Code Expression when triggering actions in FlutterFlow (i.e., inside an Action Flow), you can use the **Execute Custom Code** action. This allows you to run a Dart expression when something happens, such as tapping a button or after a page loads. ![execute-custom-code.avif](/assets/images/execute-custom-code-bd07ca89bc8f62a23382545a6573949c.avif) The Execute Custom Code action can be really helpful in scenarios where the home page is removed early from the navigation stack and standard navigation using the local context may fail. To prevent this, you can [use the global navigator context](/concepts/navigation/deep-dynamic-linking.md#using-global-context-to-navigate) inside a code expression. ## Custom Functions[​](/resources/functions/utility.md#custom-functions "Direct link to Custom Functions") You can also use custom functions to handle slightly more complex calculations or to process a wider range of data types that are not supported in Inline Function. info Learn more about [**Custom Functions**](/concepts/custom-code/custom-functions.md). ## FAQS[​](/resources/functions/utility.md#faqs "Direct link to FAQS") How is a Custom Code Expression different from an Inline Function? Custom Code Expression is a more advanced and flexible version of Inline Function. With Inline Functions, you had to manually pass values as arguments. In contrast, Custom Code Expressions let you directly reference FlutterFlow generated resources (such as `FFAppState()`, `_model`, context, and more) without needing to pass them in. You can write any valid Dart expression in a Custom code expression, even multi-line logic using anonymous functions. Plus, Custom Code Expressions support real-time autocomplete and inline error validation, making it much easier to discover available variables and avoid mistakes. --- # Utility Actions Utility Actions provide essential functionalities that enhance your app's capabilities, such as data manipulation and system interactions. These actions streamline processes and improve the overall user experience. Examples include copying text to the clipboard and selecting colors or dates. ## Color Picker \[Action][​](/resources/functions/utility-actions.md#color-picker-action "Direct link to Color Picker \[Action]") Using this action, you can allow users to pick their favorite color from the palette or by entering a HEX/RGB color value. You might, for instance, utilize this to give customers the option of choosing the color of a product you offer. When this action is triggered, it opens the color picker, where users can customize the color. The color picker will close once the desired color has been selected, and the selected color will then be accessible via *Widget State > Color Picked*. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Color Picker** (under *Widget/UI Interactions*) action. 4. When the color picker is opened, by default, the primary color is selected. To change this, set the **Initially Selected Color**. 5. You can also customize the look and feel of the color picker by changing the color of the **Text**, **Background**, and **Button**. 6. By default, the color picker allows users to add opacity to the color. To allow users only select the opaque colors, disable the **Allow Opacity** toggle. 7. Recent colors help users choose any previous color they have used. Disable the **Show Recent Color** toggle if you don't want to show them. 8. The selected color is now available at **Widget State > Color Picked**. You can access it from any widget's color property or click the "**+**" button and add the following action to update the selected color in your backend or app state. info After the user has selected the desired color, the picker will close automatically, and the selected color can then be accessed via the **Widget State > Color Picked**. Here's an example of adding the color picker action and updating the selected color in an app state variable. * Adding color picker action * Customize color picker ![customize-color-picker](/assets/images/customize-color-picker-9a6db3757512a828ae5b86369eed4027.avif) ## DateTime Picker \[Action][​](/resources/functions/utility-actions.md#datetime-picker-action "Direct link to DateTime Picker \[Action]") This action allows the user to select a date and time. You could use it to schedule appointments, set a reminder for a specific date, choose travel dates and times, etc. When this action is triggered, it opens the graphical calendar and clock interface that the user can interact with to select a specific date and time. ### Types Date/Time Picker[​](/resources/functions/utility-actions.md#types-datetime-picker "Direct link to Types Date/Time Picker") You can choose to open the following types of *Date/Time* picker dialog: * **Date**: Allows you to only select a date. * **Date+Time**: Allows you to select the date followed by the time. * **Time**: Allows you to only select a time. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Container, Button, etc.) on which you want to add the action. 2. Select **Actions** from the properties panel (the right menu), If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile (inside *Action Flow Editor*) and select **Add Action**. 3. Search and select the **Date/Time Picker** (under *Widget/UI Interactions*) action. 4. Set the [Date/Time picker type](/resources/functions/utility-actions.md#types-datetime-picker). 5. By default, the picker shows the current date/time. You can change this by adjusting the **Default Date/Time**. 6. To define the range of selectable dates, use the **Minimum Date/Time** and **Maximum Date/Time** properties. Click on **Unset** to specify your dates. 7. Control whether the past and future dates/times are selectable with **Allow Past Date/Time** and **Allow Future Date/Time**. **Tip**: If you explicitly set the min or max date, this option will be disabled. 8. For an iOS-style display, activate the **Use Cupertino-style** toggle. ![cupertino-style](/assets/images/cupertino-style-6cd132faee38015163a03a82e6406a29.png) 9. For more personalized styling, turn off **Use Default Theme** and tweak the settings in the **Appearance Properties** section. ![appearance-properties](/assets/images/appearance-properties-e452f129ea064da6443fb476b9a69f92.png) info After the user has selected the desired date and time, the picker will close automatically, and the selected date/time can then be accessed via the ***Widget State > Date Picked**.* Here's an example of adding the date time picker action and displaying the value in a Text widget. ## Biometric Verification \[Action][​](/resources/functions/utility-actions.md#biometric-verification-action "Direct link to Biometric Verification \[Action]") Most modern devices come with biometric sensors to strengthen the device's security. Using this action, you can leverage on-device authentication such as fingerprint or face recognition to protect your app's privacy. When this action triggers, it checks for the enrolled biometric. If it finds any, it asks users to verify their identity. If the biometric authentication fails, it opens up the screen lock option (e.g., Pattern, PIN, Password, Swipe, etc.) as a fallback method to authenticate users. A common use case of this action is to allow only the intended user to open an app that involves financial or confidential information, such as an online payment app, stock trading app, or online storage app. Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. On the right side, search and select the **Biometric Verification** (under *Utilities*) action. 1. By default, if the biometric verification fails, it opens the on-device credentials such as Pattern and PIN. This helps in a case where the biometric sensor can't recognize a valid fingerprint or face. However, you can disable this behavior and only allow biometric verification. To do so, turn on the **Allow biometric only** toggle. 2. Enter the **Biometric Reason text**. This message is displayed inside the biometric recognition UI. 3. Provide the **Action Output Variable Name**. The status of biometric verification, True (pass) or False(fail), is stored in this variable. You can use this variable to decide the following action. For example, showing a success or failure message. 4. To show a success or failure message, **Add Conditional** action by clicking on the + button inside the already added action. 1. Click on the **UNSET**, select **Action Output**, and select the action output variable name. 2. Under the **TRUE** section, add an action to [show the snackbar](/resources/ui/pages/scaffold.md#snackbar) with a success message. 3. Similarly, add the failure message under the **FALSE** section. ## Copy to Clipboard \[Action][​](/resources/functions/utility-actions.md#copy-to-clipboard-action "Direct link to Copy to Clipboard \[Action]") Using this action, you can allow users to copy a particular text from your app. For example, copying a message or transaction ID and then pasting it into another application. When this action is triggered, the data is stored temporarily in a special part of the device's memory called the clipboard. The user can then paste the copied text into another application by using the "paste" command. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Click on the **+ Add Action**. 4. Search and select the **Copy to Clipboard** (under *Utilities*) action. 5. Most probably, this value would be dynamic; hence, you can set the **Value Source** to **From Variable** and set the **Source** accordingly. warning At present, testing this action isn't possible in Test mode, but you can use the Run mode for this purpose. ## Set Dark Mode Setting \[Action][​](/resources/functions/utility-actions.md#set-dark-mode-setting-action "Direct link to Set Dark Mode Setting \[Action]") Using this Action, you can set the app theme to Light/Dark or set it as per the system. * As Per System * Manually Setting Theme Mode ### Types of Dark Mode Setting[​](/resources/functions/utility-actions.md#types-of-dark-mode-setting "Direct link to Types of Dark Mode Setting") There are three types of the mode you can set: * **From System**: Set the Light/Dark Mode based on system preference. That means you don't need to build the Light/Dark Mode switch UI in your app. The dark mode will be set automatically if a user has set the dark mode in the Android/iOS operating system. * **Light Mode**: Set the theme mode to Light. * **Dark Mode**: Set the theme mode to Dark. Go to your project page on FlutterFlow and follow the steps below to define the Set Dark Mode Setting Action to any widget. 1. Select **Actions** from the [properties panel](/flutterflow-ui/builder.md#properties-panel) (the right menu) 2. Click **+ Add Action** button 3. Choose a gesture from the dropdown among **On Tap**, **On Double Tap**, or **On Long Press**. 4. Select the **Action Type** as **Set Dark Mode Setting**. 5. Set the **Setting Source** to **Select Setting**. 6. Set the **Dark Mode Setting** to any amongst the **From System**, **Light Mode**, **Dark Mode**. ## Send Email \[Action][​](/resources/functions/utility-actions.md#send-email-action "Direct link to Send Email \[Action]") Using this action, you can send an Email to the specified email Id. This action does not directly send an email. Instead, it redirects you to the email app and prefills the subject and message body, and you have to press the send button to send an email finally. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Send Email** (under *Share*) action. 4. Inside the **Email Address** section, provide the valid email id. Your message will be sent to this email Id. 5. Also, provide the **Subject** and **Body** of the message to be sent. ## Call Number \[Action][​](/resources/functions/utility-actions.md#call-number-action "Direct link to Call Number \[Action]") Using this action, you can make a call to the specified number. This action does not directly call a number. Instead, it redirects you to the native Calls app and prefills the specified number; you have to press the call button to make a call. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Call Number** (under *Share*) action. 4. Inside the **Phone Number** section, provide the valid phone number. The call will be made to this number. ## Send SMS \[Action][​](/resources/functions/utility-actions.md#send-sms-action "Direct link to Send SMS \[Action]") Using this action, you can send an SMS to the specified number. This action does not directly send SMS. Instead, it redirects you to the native SMS app and prefills your message, and you have to press the send button to send the message finally. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Send SMS** (under *Share*) action. 4. Inside the **Phone Number** section, provide the valid phone number. Your message will be sent to this number. 5. Inside the **SMS Body** section, provide the message you want to send. --- # What is a Project? A **Project** in FlutterFlow represents a complete Flutter application. It contains all the generated code for a Flutter app. This means that you can export your code and your app will run as a normal Flutter app without requiring FlutterFlow. A FlutterFlow project includes all the files and packages generated by the `flutter create` command, along with additional packages specifically added to support common functionalities. These include: ### UI and Styling[​](/resources/projects.md#ui-and-styling "Direct link to UI and Styling") * [**auto\_size\_text**](https://pub.dev/packages/auto_size_text): Automatically resizes text to fit within its bounds. * [**cached\_network\_image**](https://pub.dev/packages/cached_network_image): Provides a widget that displays images from the internet, caching them for performance. * [**flutter\_animate**](https://pub.dev/packages/flutter_animate): Facilitates adding animations to widgets. * [**font\_awesome\_flutter**](https://pub.dev/packages/font_awesome_flutter): Offers a comprehensive set of icons provided by FontAwesome. * [**from\_css\_color**](https://pub.dev/packages/from_css_color): Converts CSS color strings to Flutter color objects. * [**google\_fonts**](https://pub.dev/packages/google_fonts): Enables custom fonts to be used easily from the Google Fonts catalog. * [**page\_transition**](https://pub.dev/packages/page_transition): Adds customizable page transition effects. ### Navigation[​](/resources/projects.md#navigation "Direct link to Navigation") * [**go\_router**](https://pub.dev/packages/go_router): A declarative router based on URL patterns, simplifying navigation logic. ### Data Management and Storage[​](/resources/projects.md#data-management-and-storage "Direct link to Data Management and Storage") * [**collection**](https://pub.dev/packages/collection): Provides additional collection types and utilities. * [**json\_path**](https://pub.dev/packages/json_path): Allows querying JSON data structures with path expressions. * [**provider**](https://pub.dev/packages/provider): A popular state management technique to propagate changes across the app. * [**shared\_preferences**](https://pub.dev/packages/shared_preferences): Facilitates persistent storage of simple data (key-value pairs). ### Platform Specific Integrations[​](/resources/projects.md#platform-specific-integrations "Direct link to Platform Specific Integrations") * [**path\_provider**](https://pub.dev/packages/path_provider): Locates commonly used locations on the filesystem. * [**path\_provider\_android**](https://pub.dev/packages/path_provider_android), [**path\_provider\_foundation**](https://pub.dev/packages/path_provider_foundation), [**path\_provider\_platform\_interface**](https://pub.dev/packages/path_provider_platform_interface): Platform-specific implementations and interface for `path_provider`. * [**shared\_preferences\_android**](https://pub.dev/packages/shared_preferences_android), [**shared\_preferences\_foundation**](https://pub.dev/packages/shared_preferences_foundation), [**shared\_preferences\_platform\_interface**](https://pub.dev/packages/shared_preferences_platform_interface), [**shared\_preferences\_web**](https://pub.dev/packages/shared_preferences_web): Platform-specific implementations for `shared_preferences`. * [**url\_launcher**](https://pub.dev/packages/url_launcher), [**url\_launcher\_android**](https://pub.dev/packages/url_launcher_android), [**url\_launcher\_ios**](https://pub.dev/packages/url_launcher_ios), [**url\_launcher\_platform\_interface**](https://pub.dev/packages/url_launcher_platform_interface): Packages that enable launching URLs on various platforms, allowing the app to open web links, emails, and more. ### Utilities[​](/resources/projects.md#utilities "Direct link to Utilities") * [**intl**](https://pub.dev/packages/intl): Provides internationalization and localization facilities, including message translation, plurals and genders, and date/number formatting. * [**flutter\_cache\_manager**](https://pub.dev/packages/flutter_cache_manager): Manages cached files, supporting custom file retrieval strategies and cache rules. * [**timeago**](https://pub.dev/packages/timeago): A library to format dates as a relative time (e.g., "5 minutes ago"). Any elements (e.g. pages, widgets), business logic or packages that are added to the project will be included in the generated code. Generated Code FlutterFlow automatically generates a complete Flutter application for you. To dive deeper into the project structure of a Flutter app generated by FlutterFlow, explore the [**Directory Structure**](/generated-code/project-structure.md) guide. --- # Collaborate on Projects In FlutterFlow you can share projects with your entire organization (team), with individual users within your organization, or external users. ## Sharing a Project with Team[​](/resources/projects/collaboration.md#sharing-a-project-with-team "Direct link to Sharing a Project with Team") To share a project with team members, use the **Share with team** dropdown in the **Collaboration** page of your project's settings, and select how you want the project to be shared: ![share\_with\_team.png](/assets/images/share_with_team-d9300d604efe99f80fc07d46d2912eda.png) * **Team project:** A project associated with your team and automatically visible to all team members. When a project is a Team Project, team members are automatically added as Editors. You can specifically designate team members as Viewers, but you cannot remove them. * **Restricted team project:** A project associated with your team but only visible to specific team members who are added directly. After selecting this option, you’ll need to manually choose the team members you want to share the project with. * **Personal project:** A project not associated with any team, where editing capabilities depend on the type of personal plan you have. info * The Team owner always has edit access to the project, regardless of who created or shared it, and retains full team plan capabilities. * The Team owner can also selectively share the project with any number of team members. * A [**Library**](/resources/projects/libraries.md) project will not have the *Restricted Team Project* option. * Sharing a project with team members is only available on the **Growth** plan and **higher**. Check out our [**pricing**](https://www.flutterflow.io/pricing) section. ## Sharing a Project with External Collaborators[​](/resources/projects/collaboration.md#sharing-a-project-with-external-collaborators "Direct link to Sharing a Project with External Collaborators") You can invite users to your project who are not part of your organization. For instance, you might want to share your work with clients, stakeholders, or team members of the client. You can add users as Read Only users to any project regardless of your pricing plan in the **Collaborators** page of your project's settings. info * Users with read-only access will only be able to access that specific project and won't be able to access any shared *Teams* libraries (e.g., custom code, design system). * You must verify your email before inviting users. * If a user isn't already a FlutterFlow user, we will send them an invite email. Their status will be shown as Pending until they create an account. To add an external user as a collaborator as an Editor to a project, you first need to purchase a collaborator pass. To purchase a collaborator pass, go to the [My Teams](https://app.flutterflow.io/team) page and, under the **Collaborator Passes** section, click **Add Pass** and complete the checkout process. Once the pass is created, enter the user email and select the project (Team Project or Restricted Team Project) you’d like to grant them access to. info * You must be a Team Owner to purchase and assign a Collaboration Pass. * Collaborator Passes can only be assigned to users who have a paid plan (Basic, Growth, or Business). ## Transferring Project[​](/resources/projects/collaboration.md#transferring-project "Direct link to Transferring Project") danger This step can not be undone. If you want to regain project ownership, the new project owner will need to transfer ownership back to you. To transfer ownership to another user, navigate to **Settings & Integrations > Project Setup > Collaboration > Project-Level Access**, click on the current role and select **Owner**. info You can transfer a project to any FlutterFlow user (including [external collaborators](/resources/projects/collaboration.md#sharing-a-project-with-external-collaborators)) as long as they have an active paid plan. ![transfer-ownership.avif](/assets/images/transfer-ownership-38bf5147ec948ecaffe2d643eb6d8970.avif) ## Real-Time Collaboration[​](/resources/projects/collaboration.md#real-time-collaboration "Direct link to Real-Time Collaboration") Real-Time Collaboration is a powerful feature that allows multiple builders to work together on the same project or, rather same page and design system simultaneously. With this, all builders can see the changes being made to the page as they happen and can also make their own changes to the page without interfering with the work of others. This increases efficiency and productivity, as multiple builders can work on various aspects of the project or together on the same page at the same time. When multiple builders are on the same page, it looks like this: ![real-time-collaboration.gif](/assets/images/real-time-collaboration-e7b2aa92a77ba20a8d27dc59722bcbae.gif) info Real-Time collaboration is only available on the **Growth** plan and **higher**. Check out our [**pricing**](https://www.flutterflow.io/pricing) section. ## Project Activity[​](/resources/projects/collaboration.md#project-activity "Direct link to Project Activity") You can see a running history of changes made while building that helps you track progress and stay up to date on project changes. info Project Activity is only available to **Enterprise** users. Check out our [**pricing**](https://www.flutterflow.io/pricing) section. ![project-activity](/assets/images/project-activity-3d3eee4fef07cfaf66934cc1a76937e8.avif) --- # ## How to Create a Project[​](/resources/projects/how-to-create-find-organize-projects.md#how-to-create-a-project "Direct link to How to Create a Project") To create a new project, go to the Dashboard and click **+ New Project** in the upper-right corner. This opens a window where you can start with a template app or a blank project. [Create a Project](https://demo.arcade.software/s8Pwq75FDwnaLyt6pQvZ?embed\&show_copy_link=true) ## How to Find Projects[​](/resources/projects/how-to-create-find-organize-projects.md#how-to-find-projects "Direct link to How to Find Projects") Go to the Project Dashboard to view all your projects. You can search for specific projects using the search bar. [Projects - FlutterFlow](https://demo.arcade.software/GonI3mWkBe7xg98MvA0J?embed\&show_copy_link=true) Narrow your search scope with the dropdown menu next to the search bar: * **All Projects:** Shows all projects you can access. * **My Private Projects:** Shows projects accessible only to you. * **My Shared Projects:** Shows projects you own and have shared with others. * **Shared With Me:** Shows projects shared with you that you do not own. * **Team Projects:** Shows projects that belong to your teams. * **Library Projects:** Shows projects configured as libraries for reuse across other projects. * **Marketplace Listings:** Shows projects connected to your Marketplace listings. * **Archived Projects:** Shows projects you have archived. * **Beta Projects:** Shows projects using the Beta environment. * **Prod Projects:** Shows projects using the Production environment. ![filter-projects](/assets/images/filter-projects-665e7c456b47e85db1d936f2c1243a32.avif) ## Organizing Projects[​](/resources/projects/how-to-create-find-organize-projects.md#organizing-projects "Direct link to Organizing Projects") ### Create and Add Tags to Projects[​](/resources/projects/how-to-create-find-organize-projects.md#create-and-add-tags-to-projects "Direct link to Create and Add Tags to Projects") Tags help categorize and filter your projects for easier management. To create a tag, click **+ Tag** or open the three-dot menu on a project card and select **Create Tag**. Add a tag to a project by opening the three-dot menu on the project card and selecting a tag. Each project can have only one tag. [Create and Add Tags to Projects](https://demo.arcade.software/ltenHF4tRtLi4zEX0QS4?embed\&show_copy_link=true) ### Searching and Filtering by Tags[​](/resources/projects/how-to-create-find-organize-projects.md#searching-and-filtering-by-tags "Direct link to Searching and Filtering by Tags") When a tag is selected, the project list filters to show only projects associated with that tag. This filter can be combined with the search bar to refine your project search further. [Search and Filter Projects by Tag](https://demo.arcade.software/85XUoxRUK2ZxbWgBr95M?embed\&show_copy_link=true) ### Editing and Removing Tags[​](/resources/projects/how-to-create-find-organize-projects.md#editing-and-removing-tags "Direct link to Editing and Removing Tags") Modify or remove tags by clicking the gear icon within the orange Tag button. This lets you quickly update tag names and assignments. ![edit-tags](/assets/images/edit-tags-a332d8bd24063d78cd99b8397ca832fa.avif) --- # Run and Test Projects There are 4 ways to test your project in FlutterFlow. * **[Preview](/testing/run-your-app.md#preview-mode)**: This mode allows for quick testing of the user interface on a virtual device without requiring a full build. * **[Test](/testing/run-your-app.md#test-mode)**: This mode runs a web version of your app with Flutter's "Hot Reload" feature, enabling you to visualize changes immediately. * **[Run](/testing/run-your-app.md#run-mode)**: This mode allows for testing a fully functional version of your app with live data. * **[Local Run](/testing/local-run.md)**: This feature, available in the FlutterFlow Desktop App, lets you test your app on an emulator or physical mobile device. --- # Libraries Libraries enable you to share and reuse entire FlutterFlow projects as dependencies across multiple projects. This allows teams and developers to modularize their apps by creating shared libraries that include components, API calls, custom code, and more. By using libraries, development becomes more efficient and scalable. info A **Dependency** refers to an external library or resource that your project relies on to function correctly. When you create a new FlutterFlow project, certain dependencies are automatically added to support the generated code. Also, when you use a [**Custom Widget**](/concepts/custom-code/custom-widgets.md), you are essentially adding dependencies to your project. Libraries take this concept further by allowing you to add entire FlutterFlow projects as dependencies. Imagine you're building an e-commerce app, and different teams are working on various features. One team develops a complex payment system. By using the Libraries, they can publish the payment system as a reusable library and allow other teams to easily import and integrate it into multiple projects without duplicating development efforts. ![libraries.avif](/assets/images/libraries-4e9c4d418929a4bbff1bef0c0df29fae.avif) ### Importance of Libraries[​](/resources/projects/libraries.md#importance-of-libraries "Direct link to Importance of Libraries") Previously, FlutterFlow offered several methods to share resources between projects, such as team code libraries, design systems, API libraries, and by leveraging marketplace items. However, these methods had limitations, including the inability to share custom data types or custom functions alongside components or API calls and the absence of version control. With Libraries, you can publish the complete FlutterFlow project as a library and import it as a dependency into other projects. possible use cases * **Modular Development**: Build large-scale apps by separating them into smaller, independently managed projects (e.g., UI library, backend integrations, etc.). * **Team Collaboration**: Share reusable UI components, custom functions, or API integrations across multiple apps within a team. * **Community Sharing**: Publish libraries that can be imported and reused by the broader FlutterFlow community. ## Publishing a Library[​](/resources/projects/libraries.md#publishing-a-library "Direct link to Publishing a Library") To make the resources in your project available for others to use, publish your project as Library. When you publish your project as a Library, your project will become a **Library Project**, and [certain features](/resources/projects/libraries.md#disabled-features-in-a-library) will no longer be available. note When you publish your project as Library, it can not be reverted. If you want to restore your project so that it is no longer a Library, you can clone the project. However, things like your deployment and Firestore settings will be cleared. If you want to preserve the state of your project before turning it into a Library, you should clone it first and then publish. To publish a FlutterFlow project as a library, start by creating a FlutterFlow project as you normally would, then follow these steps: [Publishing a Library](https://demo.arcade.software/CTuBPgISjpRWy5TT6rRD?embed\&show_copy_link=true) info * You can only publish libraries if you have access to [**branching**](/collaboration/branching.md), which is available to users on **Growth** plan and above. * Libraries can only be published from the main branch, and each published version is linked to a specific commit, ensuring robust version control. * You must commit your changes before publishing a new version of the library. * It's recommended to include a message that tells users what has changed in the version your are publishing. warning To publish a project as a library, it must meet the following requirements: * **No Prior Store Deployment**: The project must not have been deployed to the Google Play Store or Apple App Store. * **No Failed Deployments**: The Publish button remains disabled if a deployment process was started and failed. * **No Errors or Warnings**: All project errors or warnings must be addressed beforehand. * **Main Branch Only**: You can only publish from the main branch. * [**Paid Plan**](https://www.flutterflow.io/pricing): Subscription to one of the paid plans is required to publish a project as a Library. * **Not Cloned from Marketplace**: The project cannot be a clone of a Marketplace item. ### Disabled Features in a Library[​](/resources/projects/libraries.md#disabled-features-in-a-library "Direct link to Disabled Features in a Library") When a project is converted into a library, the following features are disabled to ensure compatibility and functionality limitations: * App settings * Supabase * Development environments * Authentication * Push notifications * Mobile deployment * Web deployment * Stripe * Braintree * Razorpay * Google Analytics * OneSignal * Mux ## Importing a Library[​](/resources/projects/libraries.md#importing-a-library "Direct link to Importing a Library") To import a library project into another FlutterFlow project, you must go **Settings and Integrations** > **Project Setup** > **Project Dependencies** . Here you can specify the library project and version you are importing. [Importing a Library](https://demo.arcade.software/DrzjKuhTWZXOxBB5yGJn?embed\&show_copy_link=true) info * You can only select a library if you have at least read access on the library project. * For a library project to show in the drop down, you must be added as a collaborator on the project and the library project must have a published version. * You can import publicly accessible libraries by specifying the project ID in the text field when adding a library dependency. * By default, the latest published version of the library is imported, but you can choose to depend on an earlier version if needed. * You can also import the `current` version of the library to use the latest state of the library on the main branch - however, this is not recommended. * When importing a library into a project or another library, the library’s version must not be set to 'current' and should be less than or equal to the FlutterFlow version of the project or library it’s being imported into. Learn more about [**managing Library’s FlutterFlow version**](/resources/projects/settings/flutterflow-version-management.md#version-management-with-libraries). ### Dependency Conflicts[​](/resources/projects/libraries.md#dependency-conflicts "Direct link to Dependency Conflicts") A **Dependency Conflict** occurs when two or more libraries added by a project depend on different versions of the same dependency. This creates a situation where the project cannot resolve which version to use, leading to a project error. ![dependency-conflict.avif](/assets/images/dependency-conflict-68c1edda9693988f306551e329bff394.avif) Let's say you are building an eCommerce app that uses multiple libraries for different purposes: * **User Auth Library** is used for handling user authentication. * **Payment Gateway Library** is used for managing the payment gateway. Both library projects depend on a common library called **Components Library** but imports different versions respectively: * **User Auth Library** depends on `Components Library v1.5.0`. * **Payment Gateway Library** depends on `Components Library v2.0.0`. In this scenario, the eCommerce project will detect the dependency conflict because it can't add both `v1.5.0` and `v2.0.0` of the Components Library at the same time. #### Fixing Dependency Conflicts[​](/resources/projects/libraries.md#fixing-dependency-conflicts "Direct link to Fixing Dependency Conflicts") Follow these steps to ensure both libraries rely on the same version of Components Library: 1. **Upgrade both libraries**: If updates are available, start by upgrading both the User Auth Library and Payment Gateway Library to their latest versions. Often, newer versions of libraries are designed to use the latest version of the Components Library, which can help resolve conflicts. 2. **Modify Libraries**: If you have access to the library projects, adjust the dependencies of either User Auth Library or Payment Gateway Library (or both) to use the same version of the Components Library. 3. **Contact Library Maintainers**: If you do not own the library yourself, reach out to the maintainers of the library projects. They may provide guidance, suggest workarounds, or release a version that addresses the conflict. ## Access Library Resources[​](/resources/projects/libraries.md#access-library-resources "Direct link to Access Library Resources") Once the library is imported, following resources are accessible for use: * [Components](/resources/ui/components.md) * [Data Types & Enums](/resources/data-representation/custom-data-types.md) * [App State Variables](/resources/data-representation/app-state.md) * [Constants](/resources/data-representation/constants.md) * [API Calls](/resources/backend-logic/rest-api.md) * [Action Blocks](/resources/functions/action-blocks.md) * [Custom Functions](/concepts/custom-code/custom-functions.md), [Actions](/resources/functions/action-flow-editor.md), and [Widgets](/resources/ui/widgets.md) * [Assets](/resources/projects/settings/general-settings.md#app-assets) (Note: These are not versioned) * [Code Files](/concepts/custom-code/code-file.md) info * [**Pages**](/resources/ui/pages.md), [**Firestore Collections**](/integrations/database/cloud-firestore/creating-collections.md), and [**Cloud Functions**](/concepts/custom-code/cloud-functions.md) are still being worked on and may come in future updates. * Creation of [**AI Agents**](/integrations/ai-agents.md) is not yet supported in the Library project It's important to note that these resources show up where they are instantiated. For example: * **Components** appear in the widget palette. * **API calls** appear when making API calls in the action flow editor. * **Custom Functions** are available when setting up actions or functions within the app. * **Code Files** (Dart files containing classes or enums) become available when [creating instances](/concepts/custom-code/code-file.md#create-custom-class-instance), allowing you to access their fields and methods. They also appear in the action flow editor when adding [custom class actions](/concepts/custom-code/code-file.md#set-field-action). This ensures that only relevant resources are shown where they are needed, optimizing performance and discoverability. Access Library Components in Custom Code When your project includes a library dependency, you can use its components—such as Library App State, Library Values, Library Custom Code resources, etc.—in your custom code. Explore the **[Common Custom Code Examples](/concepts/custom-code/common-examples.md#access-library-components-in-custom-code)** directory for reference. ![access-library-resources.avif](/assets/images/access-library-resources-6cdcb108936bad7eb864b8412170abe6.avif) ## Library Versioning[​](/resources/projects/libraries.md#library-versioning "Direct link to Library Versioning") Library versioning allows you to manage different versions of a library project over time. Using versioning, library users can control which version of a library to use in a project, ensuring compatibility and reducing the risk of breaking changes. Importance of Library Versioning * **Maintain Backward Compatibility**: It ensures older versions of the library continue to work as expected while introducing new features. * **Roll Back Changes**: In case of bugs or issues in a new version, you can easily revert to a previous stable version. * **Control Updates**: Library users can decide when to upgrade to the latest version, rather than being forced into changes. ### Publish New Version[​](/resources/projects/libraries.md#publish-new-version "Direct link to Publish New Version") When you're ready to update your library, ensure that all modifications are committed to the main branch of the library project and then publish as per instructions [here](/resources/projects/libraries.md#publishing-a-library). tip * While publishing a new version, add a description to highlight what's new or changed in this version. * Each time a new version is published, the version number will automatically increment. ### Import Specific Version[​](/resources/projects/libraries.md#import-specific-version "Direct link to Import Specific Version") When importing a library into a project, you have the flexibility to choose which version of the library to use. By default, the latest version will be selected. ![import-specific-library-version.avif](/assets/images/import-specific-library-version-3c6f3149e6ac482344617db9bada7cf6.avif) ### Update to Latest Version[​](/resources/projects/libraries.md#update-to-latest-version "Direct link to Update to Latest Version") You can easily upgrade to newer versions of the libraries as they become available. tip * If a new update causes issues with your existing implementation, you also have the option to revert to a previous version. * Always test your app after upgrading to ensure that the new library version works well with your existing project. ![update-library](/assets/images/update-library-4edeaa44ed91b4f37ecce0b86b1bac00.avif) ## Library Pages[​](/resources/projects/libraries.md#library-pages "Direct link to Library Pages") When you publish a library, all the pages included in the library become available for use in the consumer project. These pages function like any regular project page in your app; they support navigation, parameters, state management, and transitions. Library Pages offers a modular approach to development, making it ideal for large teams and complex, multi-feature apps. For example, instead of recreating common flows like onboarding and payment flows, you can build them in a library once and use them wherever needed. Possible Use Cases * **Super Apps** like Gojek and Uber with distinct modules such as ride booking, shopping, and payments. Each module can be developed as a separate library and imported into a single main project. * **Enterprise Apps** with isolated user journeys for different roles, such as admin and customer. Each role-based flow can be built as its own library and integrated into the core app as needed. * **White-labeled Apps** that share common onboarding flows can benefit from libraries. The onboarding process can be built once as a library and reused across all branded versions of the app. When users import or update the library, they can override the default route names to prevent conflicts between the library and their project. Library pages then appear in navigation actions just like any regular page. ### Library Pages in NavBar[​](/resources/projects/libraries.md#library-pages-in-navbar "Direct link to Library Pages in NavBar") Library pages can also be used in the NavBar, allowing users to add reusable flows into the app’s primary navigation structure. For example, in a Super App, you can import ride booking, food delivery, or payment pages from separate libraries and add them directly to the bottom navigation, giving users quick access to each module. tip Want to learn more about building modular Super Apps using libraries? Check out our [**blog post**](https://blog.flutterflow.io/scaling-super-apps-modular-architecture-with-flutterflow-libraries/). To display a library page on the NavBar, navigate to **Project Dependencies > FlutterFlow Libraries**, then click on **Pages** for the relevant library to open its details. In the list of pages, locate the desired page and click **Nav Bar Settings**, then enable **Show on NavBar**. You can also customize additional settings, such as label and icon, as needed. To confirm, go to the **Nav Bar & App Bar** section, where you’ll see the library page listed as part of the NavBar items. info NavBar settings for regular pages are available directly within the Page Settings panel in the builder. However, for Library pages, these settings are managed through the Library Details dialog. ![NavBar-settings-for-regular-and-library-page](/assets/images/NavBar-settings-for-regular-and-library-page-967f2d0fbad3c9b44fc0eaca26d923ea.avif) ## Library Values[​](/resources/projects/libraries.md#library-values "Direct link to Library Values") **Library values** are essentially variables created and used by a library author and intended to have their values set by the library user. These values allow library author to create configurable variables that are useful in different contexts, such as public or client-side API keys, global settings, or other project-specific configurations. These values allow library users to input specific data required for the library to function properly in their project. For example, if someone builds a payment gateway library, they might define Library Values for configuration settings, such as: * Default currency: USD * Region: US * Default Payment method: Card This allows the user importing the library to provide their own payment preferences without modifying the internal code of the library. danger **Library Values should not be used to store private or sensitive data**, such as secret API keys or credentials. These values are not currently designed to securely store or handle sensitive information. The use of *client-side* or *publishable* API key is generally acceptable, because the keys often have limited permissions, rate limits, or are intended for public use. For instance, if someone creates a library that connects to a public weather API, they might define a Library Value for the API key. Users of that library can then input their own API key to make it work. tip To avoid misuse on any type credential, make sure to apply appropriate restrictions to limit its usage. For example, see how to [**restrict a Google Maps API key**](/best-practices/secure-api-keys.md#add-restrictions-to-your-api-key) in the Google Cloud Console. ### Create Library Values as Author[​](/resources/projects/libraries.md#create-library-values-as-author "Direct link to Create Library Values as Author") The library author defines the variable name, data type (e.g., string, enum), whether the variable is nullable, and an optional default value. To create library values, navigate to **Settings and Integrations > App Settings > Publish as Library > Library Values** section and click **+ Add Value**. #### Use Library Values[​](/resources/projects/libraries.md#use-library-values "Direct link to Use Library Values") After setting Library Values, they function just like any other variable in FlutterFlow. You can bind them to components, actions, API calls, or any property that allows you to configure dynamic values across your library project. You can access Library Values via the ****Set from Variable**** menu. tip Library values are used only within the library project and are not available for use in the project that imports it. The library user can only set their values. ![access-library-values](/assets/images/access-library-values-f087d40c48ba05c7809df5287da630b3.avif) ### Set Library Values as User[​](/resources/projects/libraries.md#set-library-values-as-user "Direct link to Set Library Values as User") To set library values, navigate to **Settings and Integrations > Project Setup > Project Dependencies** page. When you import a library, you'll be prompted to set values for required Library Values. If the library has already been added, click on **View Details**, which will open a dialog and then you can enter a value. tip For different [**development environments**](/testing/dev-environments.md) (e.g., development vs. production), you can bind Library Values to [**environment values**](/testing/dev-environments.md#environment-values). For instance, you could have two different Library Values for an API key, such as `DEV_OPENAI_API_KEY` and `PROD_OPENAI_API_KEY`, and bind them to the development and production environments to track API usage separately. ## Libraries with Firebase[​](/resources/projects/libraries.md#libraries-with-firebase "Direct link to Libraries with Firebase") You can create collections and enable various Firebase features in library projects without connecting a separate Firebase project. In library projects, you won’t see an option to link to a Firebase project. Instead, the project that imports the library handles the actual Firebase connection. Any indexes or security rules defined in the library are recognized by the importing project and deployed accordingly. Limitations Libraries work with Firebase but have **some limitations**. The **Firebase Auth** and **Firebase Storage** are not directly supported in library projects at this time. If you need these features in your library’s functionality, you can include an action that accomplishes this task as a [**callback**](/resources/ui/components/callbacks.md). If your team has multiple projects that share a common Firebase feature, turning it into a library is a great idea. This ensures the same logic is used and connects to the same Firestore project across all apps. Here are some examples of library projects you can build with Firebase: * **Basic Analytics or Tracking**: A library that logs events to Firestore; useful for aggregating usage data at an application level. * **Configuration or Settings**: A library that serves app-wide configurations (like feature flags, UI themes, or layout choices) is handled in Firebase Remote Config. ## FAQs[​](/resources/projects/libraries.md#faqs "Direct link to FAQs") What will happen to existing team libraries? Team code and API libraries will be migrated to library Projects. These projects will be imported as a library with the latest version specified as the version. The components within team design systems will move into their own projects, while design systems will continue to exist but only containing the theme settings. Do libraries work with Marketplace? Yes, you can add and import a Marketplace project as a library. How do libraries work with themes (design systems)? By default, the design system of the parent project takes precedence over the imported library's design system. If you want to use a library's design system, you must [**select or set the library in the Design System**](/concepts/design-system.md#adding-design-system) page. How are API keys shared? We're working on Library Values, which will allow users to set specific values when they import a library. This feature will be available soon. How does nested dependencies work? Projects can import libraries that themselves have imported other Libraries as dependencies. However, if the project and the library share the same dependency, the version must match exactly to avoid conflicts. Why do I get collision errors when importing a duplicated project as a library? When you duplicate a project and publish it as a library, the unique identifiers (keys) for components and other resources are not automatically changed. If you then import this library back into the original project, it causes key collisions between the original and duplicated resources. To help with this, FlutterFlow shows a dialog that offers to automatically delete the original resources in your base project and update all references to point to the library versions. If you prefer to resolve this manually, you can duplicate individual components within the library after importing, this will generate new keys and avoid the collision. --- # Refactor Project PLANS Refactor Project is only available on the Paid Plans. Check our [**pricing plans**](https://flutterflow.io/pricing). **Refactor Project** is a developer‑focused mode that opens your FlutterFlow project as a set of YAML files so you can perform large-scale edits in a single, consistent operation. For example, if you want to use a custom data type from a Library and update all references, you don’t have to manually edit each page or component. With this mode enabled, you can update all references at once using a single refactor pass. It makes managing large projects easier and more reliable. You can make changes across hundreds of references in just seconds, saving time and effort compared to manual edits. It also lets you preview changes and dismiss anything you don’t want to update. possible use cases * **Type Refactoring**: Rename a custom data type (e.g., `OrderDetails` → `OrderInfo`) across all bindings, forms, and logic in a single pass. * **String Replacement**: Find and replace hardcoded (magic) strings like `"admin"`, `"true"`, or `"completed"` to improve clarity and maintainability. * **Library Migration**: Replace a project-based custom data type (e.g., `UserProfile`) with its Library counterpart throughout the app without manually editing each reference. * **Key Updates**: Update outdated keys—for example, replace all instances of `old_api_key` with the new `new_api_key` value. * **Cleanup Unused Items**: Locate and remove unused fields or stale references (e.g., `oldFieldName`) from your YAML files to keep your project clean. info You can refactor the project only if you're on a [**paid plan**](https://www.flutterflow.io/pricing). To refactor a project, go to **Toolbar > Developer Menu > Refactor Project**. You’ll need to commit any unsaved changes before entering the refactor view. This opens your project in a YAML-based editor, where you can search, edit, and replace values across multiple files. You can also use **key reference** search by toggling the **key** icon—currently supported for data types, enums, pages, and components. Changes are color-coded: added lines appear in green, and removed lines appear in red. As you make changes, FlutterFlow provides inline YAML validation to help you catch and fix issues in real time. When you're done, click **Commit** to save the changes. After that, test your app to make sure all widgets, actions, and bindings still work as expected. tip You can exclude any item from the replacement by right-clicking on it and selecting **Dismiss**. --- # Pinning Projects to Stable FlutterFlow Versions FlutterFlow is constantly evolving to provide new features, address bugs, and keep up-to-date with Flutter and third-party packages. However, frequent updates can introduce unwanted changes that break existing projects—especially those that rely on custom code with external dependencies. To mitigate these issues, FlutterFlow offers a **version management** system that allows you to pin your project to a particular [*stable release*](/resources/projects/settings/flutterflow-version-management.md#stable-release) of FlutterFlow. Projects pinned to a stable release will **not automatically receive the latest FlutterFlow updates**, giving you more control over your development workflow. However, pinning to a stable release means that you will not be able to use the latest features, and there may be bugs that are not fixed until subsequent releases. **We only recommend doing this if you have a complex app with custom code dependencies.** info Currently, the ability to pin a FlutterFlow project to a stable version is only available to **Enterprise** users. ## When should you pin your project to a stable version?[​](/resources/projects/settings/flutterflow-version-management.md#when-should-you-pin-your-project-to-a-stable-version "Direct link to When should you pin your project to a stable version?") Pinning your project to a stable version of FlutterFlow offers the following benefits: * **Prevents Unexpected Breakages:** FlutterFlow updates can introduce errors into your project—particularly when you have custom code. Pinning to a stable release reduces the risk of unexpected changes to your project. * **Gives Control Over Update Timing:** FlutterFlow updates might occur at inopportune times (e.g. right before you plan to release a new version of your application). Pinning your project to a stable version allows you to choose **when** to move your project to a newer release. ## Key Concepts[​](/resources/projects/settings/flutterflow-version-management.md#key-concepts "Direct link to Key Concepts") To understand FlutterFlow's version management system, it's important to understand **Semantic Versioning**. FlutterFlow tends to release a new version of the product each week. When a new version is released, the overall version number is incremented. The version number consists of three parts: * **Major Version:** Incremented when introducing substantial changes that significantly alter the product. * **Minor Version:** Incremented for changes that notably enhance or modify the FlutterFlow development experience—such as upgrading to a new Flutter version, making substantial modifications to generated code or project structure, or introducing major new features. * **Patch Version:** Incremented with routine releases that include bug fixes and minor improvements, ensuring stability without introducing breaking changes to the generated code or project structure. ![semantic\_versioning](/assets/images/semantic-versioning-3f848e936e19cadc1ed2794d526f90a6.png) You can see what version of FlutterFlow you are using by looking at the top left hand corner of the builder. ![version-in-builder](/assets/images/version-in-builder-71d2a435efdc7c2bf6b31f4fdb98aa4c.png) #### Standard Release[​](/resources/projects/settings/flutterflow-version-management.md#standard-release "Direct link to Standard Release") A **Standard Release** of FlutterFlow is released approximately every week. However, this is subject to change based on user needs. When your project is **not pinned** to a stable release (default behavior), you will automatically use the **latest standard release.** #### Stable Release[​](/resources/projects/settings/flutterflow-version-management.md#stable-release "Direct link to Stable Release") A **Stable Release** of FlutterFlow is published monthly if any of the following conditions are met: * Significant changes have been made to project code generation. * The underlying Flutter version or Pubspec dependencies in generated projects have been updated. * Updates affecting the project structure have been introduced (e.g., the addition of a new widget type). Each stable release is assigned a unique **Major.Minor** version number. Projects that have not been edited in a FlutterFlow version with a **Major.Minor** version higher than the stable release can be pinned to that stable version. note Each stable release will be supported for **6 months** before you are forced to upgrade to the next stable version. ## Pinning Your Project[​](/resources/projects/settings/flutterflow-version-management.md#pinning-your-project "Direct link to Pinning Your Project") To pin your project, navigate to **Settings and Integrations > General > App Details >Version Pinning** section and select the stable release you want to lock into. ![pin-version](/assets/images/pin-version-cdb7d124a32829412c61bf793850b532.avif) ### Modifying the Pinned Version[​](/resources/projects/settings/flutterflow-version-management.md#modifying-the-pinned-version "Direct link to Modifying the Pinned Version") You have several options when it comes to modifying pinned version of your project: * **Upgrade to more recent Stable Version**: When a new stable version is released, you will see it as an option in the dropdown shown above. You can upgrade the pinned version to a more recent stable version whenever it becomes available. Newer stable versions will have higher numbers (i.e., 5.1 is newer than 5.0) * **Set to *Latest Version* (Unpinned):** You can unpin your project by setting it to the *Latest Version* which will use the latest [standard release](/resources/projects/settings/flutterflow-version-management.md#standard-release). * **Opt-in to the *Next Stable*:** Your project may be on a standard version that does not have a corresponding stable version (i.e., you are on 5.0.1 but the 5.0 stable will correspond to 5.0.4). In that case, you can choose to opt-in to the *Next Stable Version*. If it is already available, it will be pinned to that version immediately. Pinning and Unpinning Cannot Be Reversed Once you unpin a project or pin it to a later version, this action cannot be undone. If you're unsure whether a newer FlutterFlow version will be compatible with your project, we recommend creating a new branch and updating the pinned version within that branch first. This allows you to preview changes before applying them to your main project. ### Accessing the Proper Stable Version[​](/resources/projects/settings/flutterflow-version-management.md#accessing-the-proper-stable-version "Direct link to Accessing the Proper Stable Version") As mentioned above, once you update your project to a stable version, you can only edit the project using that version of FlutterFlow. * **For Web**: You will be automatically redirected to the URL for the stable version that your project is pinned to when you open a project from the FlutterFlow dashboard (i.e., navigating to app.flutterflow\.io or enterprise-\[region].flutterflow\.io). * **For Desktop**: You will [**install**](https://www.flutterflow.io/desktop) the dedicated desktop application for the pinned stable release. The desktop app for stable releases won’t auto-update, you will need to install a new version when you upgrade your project to a new stable version. ## Recommended FlutterFlow Version Workflow[​](/resources/projects/settings/flutterflow-version-management.md#recommended-flutterflow-version-workflow "Direct link to Recommended FlutterFlow Version Workflow") If you have a complex app with custom code that depends on specific versions of package dependencies, it may be helpful to pin your project to a specific version. This is the workflow we recommend for managing the version of your projects. 1. If you think your project should be pinned to a stable release, choose to [pin a currently available stable version (if any)](/resources/projects/settings/flutterflow-version-management.md#modifying-the-pinned-version). 2. When a new stable version is released, you can choose when you would like to upgrade based on your own release schedule and development process. For instance, you might wait until you're not actively developing a new feature, or you could check the release notes first to see if there are must-have features that would prompt you to upgrade sooner. 3. When you’re ready to upgrade, commit all your changes on main to save your progress. Create a new branch from the main branch, [update the pinned version](/resources/projects/settings/flutterflow-version-management.md#modifying-the-pinned-version), and test all functionalities to ensure compatibility. If any modifications are needed, make those changes in the new branch. 4. Run your app on the platforms you support—using a simulator, emulator, or physical device to ensure everything works as intended. See the [Local Run documentation](https://docs.flutterflow.io/testing/local-run/) for details. 5. If everything looks good, you can merge the new branch into the main branch. However, to merge branches successfully, ensure that both the main branch and the new branch are pinned to the same FlutterFlow version! If for some reason your app is not working as expected, you can choose to leave or close the branch until you are ready to make the modifications needed to support the latest FlutterFlow version (i.e. upgrade dependencies/custom code). tip See the video [**here**](https://youtu.be/8Y1uyCC_dXE) for guidance on updating [**dependencies**](/concepts/custom-code.md#manage-dependencies). ## Version Management with Libraries[​](/resources/projects/settings/flutterflow-version-management.md#version-management-with-libraries "Direct link to Version Management with Libraries") [Libraries](/resources/projects/libraries.md) have their own versions. Like projects, libraries edited in FlutterFlow can only be used in FlutterFlow versions greater than or equal to the version it was last edited in. To ensure that new versions of libraries used in a pinned project are compatible with a pinned project, we recommend pinning all libraries used in a pinned project to the same (or lower) Flutterflow version as the pinned project. Library projects can also be pinned to a specific version, ensuring that all library versions use that FlutterFlow release until the pinned version is changed. info * Pinned projects cannot add a library with the version set to 'current' or to a library version that has been edited on a later release of FlutterFlow. * Projects cannot be pinned if they contain a library with the version set to 'current' or to a library version that has been edited on a later release of FlutterFlow. tip When you import a library into a project or another library, the library’s version must be lower than or equal to the version used for the project it’s being imported into; otherwise, you will encounter an error. ## FAQs[​](/resources/projects/settings/flutterflow-version-management.md#faqs "Direct link to FAQs") Can I edit my project in multiple versions of FlutterFlow? No. If your project is not pinned to a specific version, you’ll always use the latest FlutterFlow release. If your project is pinned to a specific version of FlutterFlow, you will be prompted to edit the project in that version. How often are new stable versions released? We aim to release new stable versions of FlutterFlow approximately once a month. How can I see what's included in a new stable version? We’re currently working on displaying release notes directly in the product, so you can easily review what’s been added or changed in each new stable version. What if there are bugs in the FlutterFlow version I’m using? If critical bugs arise, we may provide hotfixes or patches for older FlutterFlow versions. However, some fixes depend on updating the underlying Flutter framework or related dependencies, which isn’t always feasible for older versions. This is a risk of staying on an older version of FlutterFlow as opposed to always using the latest. Can I change the pinned version to be different for various branches in my project? Yes, you can pin different versions for different branches. We recommend first creating a new branch, updating it to a later version, making any necessary changes, and verifying that everything works as expected before merging it into your main branch. However, to merge branches successfully, ensure that both the main branch and the new branch are pinned to the same FlutterFlow version. What happens if there is no stable version available for me to pin my project to? If your project was created and edited on a [standard release](/resources/projects/settings/flutterflow-version-management.md#standard-release) that does not correspond to a [stable version](/resources/projects/settings/flutterflow-version-management.md#stable-release), you may not see a stable version available. Instead, you can choose to opt-in to the [*next stable release*](/resources/projects/settings/flutterflow-version-management.md#pinning-your-project). If set to the next stable release, a project will immediately be pinned when opened when a new stable release becomes available. What is the recommended approach if I have multiple projects and libraries that I am working on? If you choose to pin your project to a stable version of FlutterFlow, we recommend pinning all your projects and dependencies to the same version - and trying to upgrade all projects to the next version around the same time. This makes it easier to ensure compatibilities between projects and libraries that depend on each other. Additionally, this makes it easier to have a single FlutterFlow desktop environment that you are working within. --- # General Settings General Settings serve as the control center for configuring essential aspects of your app. ## App Details[​](/resources/projects/settings/general-settings.md#app-details "Direct link to App Details") Edit the metadata and app-level settings for your project. * **Project Name**: The name of your FlutterFlow project. This is the name shown inside FlutterFlow. * **Project Description**: Optional internal notes about the project. Use this to describe the app, its purpose, or any context that helps collaborators understand the project. ### App Names[​](/resources/projects/settings/general-settings.md#app-names "Direct link to App Names") Use **App Names** to configure the package and display names for each environment. * **Current Environment**: Select the environment you want to configure, such as **Production**, **Staging**, or **Development**. * **Package Name**: The unique package or bundle identifier for your app. You can define different package names for different environments. * **Display Name**: The name shown to users on the installed app and in stores such as the App Store and Play Store. tip After changing the package name, errors may appear on the toolbar due to invalidated Firebase config files. To resolve this, generate new config files by going to **Settings & Integrations > Project Setup > Firebase > Regenerate Config Files**. ### Pinned FlutterFlow Version[​](/resources/projects/settings/general-settings.md#pinned-flutterflow-version "Direct link to Pinned FlutterFlow Version") Use this section to pin the project to a specific FlutterFlow version or keep it on the **Latest Version (Unpinned)**. Pinning can help protect complex projects from unexpected changes caused by platform updates. For more details, see [Pinning Projects to Stable FlutterFlow Versions](/resources/projects/settings/flutterflow-version-management.md). warning Once pinned, upgrading the FlutterFlow version may introduce breaking changes that could cause errors in your project. If needed, you can revert to the previous version, but any changes made to the project after upgrading will be lost. ### Initial Page[​](/resources/projects/settings/general-settings.md#initial-page "Direct link to Initial Page") You can specify your app's **Entry Page** and **Logged In Page** from this section. * **Entry Page**: The Entry Page is the first page users see when they open your app. When authentication is disabled, all users are directed to this page by default. If authentication is enabled, this page becomes the login, signup, or onboarding page for users who are not authenticated. * **Logged In Page** (*available only if auth is enabled*): This page is displayed when the app starts for authenticated users. If a user successfully signs in, they are automatically redirected to the page specified here. If the user is already authenticated, this page bypasses the Entry Page. To set the page, choose the page you want to use from the dropdown menu. ![initial-page](/assets/images/initial-page-d9f36f9a9e089c4010558d95d327adfb.avif) ### Download Settings[​](/resources/projects/settings/general-settings.md#download-settings "Direct link to Download Settings") * **Run "dart fix"**: Enabling this runs the `dart fix` command when downloading the code. This makes the generated code cleaner and potentially more performant. * **Download Unused Project Assets**: Enable this option to download all assets, including those that are not currently used in the project. This is useful when you need to access and use the assets in custom code or other parts of your project. ### Routing & Deep Linking[​](/resources/projects/settings/general-settings.md#routing--deep-linking "Direct link to Routing & Deep Linking") Configure global navigation and deep linking settings for your app. * **Override Default Transition**: Enable this to set a default page transition that applies across the app unless a page or action overrides it. * **Pages Require Authentication by Default**: Enable this to require authentication for pages by default. You can still configure page-level access as needed. * **Use Firebase Dynamic Links**: Enable this if your project still relies on Firebase Dynamic Links. If you are setting up deep links for a new project, see the [Deep & Dynamic Linking](/concepts/navigation/deep-dynamic-linking.md) guide. * **URL Scheme**: Defines the scheme and domain values used for deep links. Keep these values unique to your app and aligned with your configured domain. * **Advanced Route Settings**: Opens additional routing options for configuring app navigation behavior. ### Display Settings[​](/resources/projects/settings/general-settings.md#display-settings "Direct link to Display Settings") The **Display Settings** section allows you to configure how text scales within your app. This is particularly helpful for accessibility, ensuring that users with visual impairments can comfortably read content. * **Min Text Scaling Factor**: Defines the minimum allowable scale for text. This prevents text from shrinking below a certain threshold, helping maintain legibility for all users. For example, setting this to `1` ensures text is never rendered smaller than its base size, regardless of device settings or user preferences. * **Max Text Scaling Factor**: Defines the maximum allowable scale for text. This limits how large text can appear, which is useful for preserving layout consistency on devices with accessibility text scaling enabled. For example, setting this to `10` allows text to scale up to 10× its original size. * **Persist Text Scaling Factor**: When enabled, the current text scaling factor will be stored and applied even after the app is restarted. This ensures a consistent user experience across sessions. This setting requires both **Min** and **Max Text Scaling Factors** to be set. If either is unset, persistence will have no effect. info Once the text scaling factors are set, you can use the [**Update Text Scaling Factor**](/concepts/accessibility.md#update-text-scaling-factor-action) action to let users dynamically adjust text size. For example, suppose the Min Text Scaling Factor is set to 1.0 and the Max Text Scaling Factor is set to 5.0. If a user's device requests a scaling factor of 2.5, FlutterFlow will accept it because it falls within the allowed range. So, if the base font size is 16.0, the final rendered size would be: `2.5 × 16.0 = 40.0` If a device requests a scaling factor higher than 5.0 (such as 6.0), it will be capped at 5.0. Thus, for a base font size of 16.0, the final rendered size will be: `5.0 × 16.0 = 80.0`. Similarly, if a device requests a scaling factor below 1.0 (for example, 0.5), it will be raised to 1.0 to ensure readability. The resulting font size would remain: `1.0 × 16.0 = 16.0`. ### UI Settings[​](/resources/projects/settings/general-settings.md#ui-settings "Direct link to UI Settings") * **Show Component Preview in Palette**: Enable this to show component previews in the Widget Palette. This helps you identify reusable components visually while building. ## App Assets[​](/resources/projects/settings/general-settings.md#app-assets "Direct link to App Assets") Use App Assets to upload images for your splash screen and app launcher icon. ### Splash[​](/resources/projects/settings/general-settings.md#splash "Direct link to Splash") Splash screens are the first thing users see when your app starts up. They give the app time to get ready while showing users your branding or loading experience. This screen typically contains the image or logo of the app. To configure the splash screen: 1. Navigate to **Settings and Integrations** from the Navigation Menu > **General** section > **App Assets**. 2. Under the **Splash** section, click **Upload Image** and upload the image you would like to display on the splash screen. 3. You can try any of the **Image Fit** options to determine how the uploaded image should display on the splash screen. 4. To control the image dimensions manually, you can set the **width** and **height** properties. * To set an **exact size**, select **PX** and enter the desired values. * To set the dimensions as a **% of the screen size**, select **%** and enter the desired value. 5. The **Min Duration** property helps you set how long the splash screen will be visible. For reference, 1000 ms equals 1 second. 6. You can also set a **Background Color** to match the background of the image. 7. In mobile apps, you might occasionally notice a blank white screen briefly appearing (as the Flutter engine loads) before the splash screen is displayed. To change the color of this screen, use the **Pre-loading** Color property. 8. Typically, web apps don't use a splash screen, so if you prefer a more traditional web experience, you can choose to **Disable for Web**. ![splash-image](/assets/images/splash-image-a40ac0d29201d04fe452038c3b038623.avif) ### Launcher Icon[​](/resources/projects/settings/general-settings.md#launcher-icon "Direct link to Launcher Icon") The launcher icon, also known as the app icon, represents your application on a user's device. The image asset you upload here is used as the app launcher icon. To add the app launcher icon: 1. Click **Settings and Integrations** from the Navigation Menu. 2. Under the **General** section, select **App Assets**. 3. Under the **Launcher Icon** section, click **Upload Image**. 4. Use the **Unset** dropdown menu to select from images already uploaded to Project Media/Assets. 5. [Download the project](/flutterflow-cli/exporting.md) and run the following command in your terminal to generate the launcher icon: `flutter pub run flutter_launcher_icons:main` 6. [Run your app](/testing/run-your-app.md) on a real device or emulator to see the app launcher icon. ### Android Adaptive Icon[​](/resources/projects/settings/general-settings.md#android-adaptive-icon "Direct link to Android Adaptive Icon") [Adaptive icons](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive) let app icons adapt to different device environments. Unlike traditional launcher icons, adaptive icons are designed to scale and display well across different devices. Adaptive icons consist of two layers: 1. **Foreground layer**: This layer usually contains the logo or main visual element of the icon. 2. **Background layer**: This provides a fill (color or background image) behind the foreground, which can be manipulated by the device’s software. Here are the steps to add adaptive icons: 1. [Create an adaptive icon](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive#design-adaptive-icons). You can either use this [online tool](https://icon.kitchen/) or use these [resources](/resources/projects/settings/general-settings.md#create-adaptive-icon) to create one. 2. Return to FlutterFlow and navigate to **Settings and Integrations > General** > **App Assets > Android Adaptive Icon.** 1. Upload the **Foreground Icon**. If you use the online tool, you'll find it inside the `IconKitchen-Output > android > res > mipmap-xxxhdpi > ic_launcher_foreground.png`. 2. For **Background Type**, you can either set the **Color** or **Image**. Use a color that aligns with your app's branding for a cohesive look. 3. [Download the project](/flutterflow-cli/exporting.md) and run the following command in your terminal to generate the launcher icon: `flutter pub run flutter_launcher_icons:main` 4. [Run your app](/testing/run-your-app.md) on a real device or emulator to see the app launcher icon. ![adaptive-icons](/assets/images/adaptive-icons-12360989e7fcdd7452191243b0ad3208.avif) #### Useful Resources[​](/resources/projects/settings/general-settings.md#useful-resources "Direct link to Useful Resources") See the following resources for more information on Android adaptive icons. #### Create Adaptive Icon[​](/resources/projects/settings/general-settings.md#create-adaptive-icon "Direct link to Create Adaptive Icon") * [Create app icons in Android Studio](https://developer.android.com/studio/write/create-app-icons#create-adaptive) * [Figma template](https://material.uplabs.com/posts/adaptive-icon-sticker-sheet) (requires login) * [Affinity Designer template](https://cyrilmottier.com/2017/07/06/adaptive-icon-template/) * [Bjango templates](https://github.com/bjango/Bjango-Templates) include adaptive icons * [Adobe XD template](https://github.com/faizmalkani/adaptive-icon-template-xd) #### Adaptive Icon Fundamentals[​](/resources/projects/settings/general-settings.md#adaptive-icon-fundamentals "Direct link to Adaptive Icon Fundamentals") * [Understanding Android Adaptive Icons](https://medium.com/google-design/understanding-android-adaptive-icons-cee8a9de93e2) * [Designing Adaptive Icons](https://medium.com/google-design/designing-adaptive-icons-515af294c783) * [Implementing Adaptive Icons](https://medium.com/google-developers/implementing-adaptive-icons-1e4d1795470e) ## Nav Bar and App Bar[​](/resources/projects/settings/general-settings.md#nav-bar-and-app-bar "Direct link to Nav Bar and App Bar") See how to configure the [Nav Bar](/resources/ui/pages/scaffold.md#enable-nav-bar-in-settings) and the [App Bar](/resources/ui/pages/scaffold.md#appbar). --- # Project API The FlutterFlow **Project APIs** allow you to programmatically read, write, and validate YAML configuration files through REST endpoints. Using these APIs, you can automate project management tasks, integrate continuous integration and delivery (CI/CD) workflows, and apply bulk configuration updates without manual interactions with the FlutterFlow user interface. warning The Project API is currently in beta and may undergo changes that could affect functionality or compatibility. Prerequisites Before using the Project YAML API, make sure you have the following: * **HTTP Client**: Use a tool like `curl`, [**Postman**](https://www.postman.com/), or an HTTP library in your preferred programming language (e.g., `axios`, `requests`). * **Project Access**: You must have read access for GET/validation operations and an editor access for making updates to the project. * **Paid Plan**: You need a paid [**FlutterFlow subscription plan**](https://www.flutterflow.io/pricing). ## YAML Overview[​](/resources/projects/settings/project-apis.md#yaml-overview "Direct link to YAML Overview") ### What are FlutterFlow Project YAMLs?[​](/resources/projects/settings/project-apis.md#what-are-flutterflow-project-yamls "Direct link to What are FlutterFlow Project YAMLs?") YAML (YAML Ain't Markup Language) is a human-readable data serialization format commonly used for configuration files. In FlutterFlow, **Project YAMLs represent the complete structural definition of your app,** essentially exposing the full project schema that powers your FlutterFlow app. ### What's Included in the Project Schema?[​](/resources/projects/settings/project-apis.md#whats-included-in-the-project-schema "Direct link to What's Included in the Project Schema?") FlutterFlow's YAML files contain a comprehensive representation of your entire project, including: * **UI Components & Pages**: Widget trees, page layouts, component hierarchies, and styling configurations. * **App Configuration**: Settings like app details, authentication methods, integrations (AdMob, Firebase, etc.) * **Data Structures**: Database collections, API schemas, app state variables, and custom data types. * **Business Logic**: Actions, functions, conditional logic, and workflow definitions. * **Assets & Resources**: Custom code files, image references, fonts, and other project assets. * **Project Organization**: Folder structures, component libraries, and project metadata. ### YAML vs. FlutterFlow UI[​](/resources/projects/settings/project-apis.md#yaml-vs-flutterflow-ui "Direct link to YAML vs. FlutterFlow UI") Every change you make in the FlutterFlow visual editor — from dragging a widget onto a page to configuring a database collection, is ultimately stored as structured data in these YAML files. The FlutterFlow UI provides an intuitive visual interface for editing this underlying schema, while the Project API gives you direct programmatic access to the same data. ### File Structure[​](/resources/projects/settings/project-apis.md#file-structure "Direct link to File Structure") FlutterFlow automatically partitions your project into logical YAML files for optimal performance and organization. Each file represents a specific aspect of your project (e.g., `app-state`, `ad-mob`, individual pages, collections, etc.), making it easy to target specific updates without affecting the entire project. ## Base URL[​](/resources/projects/settings/project-apis.md#base-url "Direct link to Base URL") FlutterFlow provides different API endpoints for various environments. Use the appropriate base URL below depending on your needs: * Production * Beta/Staging * Enterprise ``` https://api.flutterflow.io/v2/ ``` ``` https://api.flutterflow.io/v2-staging/ ``` **India** ``` https://api-enterprise-india.flutterflow.io/v2/ ``` **APAC** ``` https://api-enterprise-apac.flutterflow.io/v2/ ``` **US Central** ``` https://api-enterprise-us-central.flutterflow.io/v2/ ``` **Europe** ``` https://api-enterprise-europe.flutterflow.io/v2/ ``` ## Authentication[​](/resources/projects/settings/project-apis.md#authentication "Direct link to Authentication") All API endpoints require authentication using a Bearer token. You'll need to include your FlutterFlow API token in the Authorization header of each request. See [how to get the API Token](/accounts-billing/account-management.md#how-do-i-generate-an-api-token). ``` Authorization: Bearer YOUR_API_TOKEN_HERE ``` ## API Endpoints[​](/resources/projects/settings/project-apis.md#api-endpoints "Direct link to API Endpoints") Below is a list of available API endpoints with their methods and usage descriptions. | Endpoint | Method | Purpose | | --------------------------- | ------ | ---------------------------------------------- | | `/listPartitionedFileNames` | GET | List available YAML file names for a project. | | `/l/listProjects` | POST | Retrieve metadata for all projects. | | `/projectYamls` | GET | Export/download YAML files from a project. | | `/validateProjectYaml` | POST | Validate YAML content before applying changes. | | `/updateProjectByYaml` | POST | Update project configuration via YAML. | ### List File Names[​](/resources/projects/settings/project-apis.md#list-file-names "Direct link to List File Names") Before you read or update project files, you need to know what YAML files are available. This endpoint returns a full list of file names associated with your FlutterFlow project. #### Endpoint[​](/resources/projects/settings/project-apis.md#endpoint "Direct link to Endpoint") `GET /listPartitionedFileNames` #### Query Parameters[​](/resources/projects/settings/project-apis.md#query-parameters "Direct link to Query Parameters") `projectId` (required): The ID of the FlutterFlow project #### Response[​](/resources/projects/settings/project-apis.md#response "Direct link to Response") ``` { "success":true, "reason":null, "value":{ "versionInfo": { "partitionerVersion": 6, "projectSchemaFingerprint": "abc123" }, "fileNames": [ "folders", "app-details", "collections/id-yr7z6g5a", "page/id-Scaffold_l9g6ilb6/page-widget-tree-outline/node/id-Column_174wuhc4", "custom-file/id-MAIN/custom-file-code", ... ] } } ``` The `fileNames` array lists out all the available YAML files. The `versionInfo` section provides metadata about the schema version and its unique fingerprint. If any part of `versionInfo` changes, it indicates that the API or the structure of the YAML responses has been updated. #### Example Usage[​](/resources/projects/settings/project-apis.md#example-usage "Direct link to Example Usage") ``` curl -X GET \ 'https://api.flutterflow.io/v2/listPartitionedFileNames?projectId=your-project-id' \ -H 'Authorization: Bearer YOUR_API_TOKEN' ``` ### List Projects[​](/resources/projects/settings/project-apis.md#list-projects "Direct link to List Projects") This endpoint retrieves a list of FlutterFlow projects associated with your account, including detailed metadata such as project name, owner email, team info, collaboration settings, and versioning data. #### Endpoint[​](/resources/projects/settings/project-apis.md#endpoint-1 "Direct link to Endpoint") `POST /l/listProjects` #### Request Body[​](/resources/projects/settings/project-apis.md#request-body "Direct link to Request Body") ``` { "project_type": "ALL", "deserialize_response": true } ``` * **`project_type: "ALL"`**: Use "ALL" to include personal, team, and shared projects, or "TEAM\_RESOURCE" to include only team-associated projects. * **`deserialize_response: true`**: Ensures the response is returned as human-readable JSON instead of a base64-encoded protobuf. tip It’s recommended to use the default options: `"ALL"` for `project_type` and `true` for `deserialize_response` for the most complete and readable results. #### Response[​](/resources/projects/settings/project-apis.md#response-1 "Direct link to Response") Returns a JSON object containing an array of projects under the `entries` key. Each entry contains the project ID and rich metadata, including collaborators, app icons, sessions, and branching information. ``` { "success": true, "reason": null, "value": { "entries": [ { "id": "XXXXXXXXXXXXXXX", "project": { "name": "Sample Project A", "ownerEmail": "user1@example.com", "createdAt": "2024-08-08T11:01:12.427Z", "updatedAt": "2024-08-08T11:01:18.669Z", "teamRef": { "path": "teams/TEAM_ID_1" }, "mainBranchRef": { "path": "projects/sample-project-id" }, "numBranches": 2, "otherMembers": { "USER_XYZ": { "email": "editor1@example.com", "accessLevel": "EDITOR" } }, "activeSessions": { "SESSION_ID_1": { "lastSuccessfulUpdate": "2024-08-06T18:41:56.569Z" } }, "totalNumUpdates": 2177 } } ] } } ``` #### Example Usage[​](/resources/projects/settings/project-apis.md#example-usage-1 "Direct link to Example Usage") ``` curl 'https://api.flutterflow.io/v2/l/listProjects' \ -H 'authorization: Bearer YOUR_API_TOKEN' \ --data-raw '{ "project_type": "ALL", "deserialize_response": true }' ``` ### Download Project YAML[​](/resources/projects/settings/project-apis.md#download-project-yaml "Direct link to Download Project YAML") You can download specific or all YAML configuration files from your FlutterFlow project. This helps in understanding the current structure of the file before modifying it. #### Endpoint[​](/resources/projects/settings/project-apis.md#endpoint-2 "Direct link to Endpoint") `GET /projectYamls` #### Query Parameters[​](/resources/projects/settings/project-apis.md#query-parameters-1 "Direct link to Query Parameters") * `projectId` (required): The ID of the FlutterFlow project * `fileName` (optional): Specific file to export (without extension). If not provided, all files are exported. #### Response[​](/resources/projects/settings/project-apis.md#response-2 "Direct link to Response") Returns a zip file encoded as a base64 string. You will need to manually decode this base64 data into a downloadable .zip file. To do so, copy the value of `projectYamlBytes` and then you can use online tools such as [base64.guru](https://base64.guru/converter/decode/file) or [b64encode.com](https://b64encode.com/tools/base64-to-zip/) to convert and download the files. ``` { "success":true, "reason":null, "value":{ "versionInfo": { "partitionerVersion": 6, "projectSchemaFingerprint": "abc123" }, "projectYamlBytes": "UEsDBAoAAAAAAKxV..." } } ``` #### Example Usage[​](/resources/projects/settings/project-apis.md#example-usage-2 "Direct link to Example Usage") ``` # Export all YAML files curl -X GET \ 'https://api.flutterflow.io/v2/projectYamls?projectId=your-project-id' \ -H 'Authorization: Bearer YOUR_API_TOKEN' # Export specific file curl -X GET \ 'https://api.flutterflow.io/v2/projectYamls?projectId=your-project-id&fileName=ad-mob' \ -H 'Authorization: Bearer YOUR_API_TOKEN' ``` ### Validate Project YAML[​](/resources/projects/settings/project-apis.md#validate-project-yaml "Direct link to Validate Project YAML") You must validate the YAML content before applying changes to ensure it's properly formatted and contains valid values. #### Endpoint[​](/resources/projects/settings/project-apis.md#endpoint-3 "Direct link to Endpoint") `POST /validateProjectYaml` #### Request Body[​](/resources/projects/settings/project-apis.md#request-body-1 "Direct link to Request Body") ``` { "projectId": "your-project-id", "fileKey": "ad-mob", "fileContent": "showTestAds: false\nappId: \"your-app-id\"" } ``` info * In the `fileContent` object, you must provide the **entire content** of the file. * The YAML content must be passed as a **single-line string** with correct formatting and appropriate escaping for new lines and indentation. For example, in the following `fileContent` object, you see the actual multiline YAML content, which is not allowed ❌. ``` { "projectId": "ecommerce-flow-app-ie7nl6", "fileKey": "app-state", "fileContent": "fields: - parameter: identifier: name: myAppState key: hg7j8z0y dataType: scalarType: String description: "Stores the current user session state" persisted: false" } ``` Now, here’s how the YAML content should be passed (i.e., as single line string ✅). ``` { "projectId": "ecommerce-flow-app-ie7nl6", "fileKey": "app-state", "fileContent": "fields:\n - parameter:\n identifier:\n name: myAppState\n key: hg7j8z0y\n dataType:\n scalarType: String\n description: \"Stores the current user session state\"\n persisted: false" } ``` #### Response[​](/resources/projects/settings/project-apis.md#response-3 "Direct link to Response") * **Success (200):** YAML is valid - `{"success": true, "reason": null, "value": ""}` * **Error with validation details:** ``` { "validationErrors": [ { "message": "Expected bool value", "fileKey": "ad-mob", "yamlLocation": { "line": 1, "column": 15 } } ] } ``` #### Example Usage[​](/resources/projects/settings/project-apis.md#example-usage-3 "Direct link to Example Usage") ``` curl -X POST \ 'https://api.flutterflow.io/v2/validateProjectYaml' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "projectId": "your-project-id", "fileKey": "ad-mob", "fileContent": "showTestAds: false" }' ``` ### Update Project YAML[​](/resources/projects/settings/project-apis.md#update-project-yaml "Direct link to Update Project YAML") This endpoint allows you to overwrite existing files in your FlutterFlow project by submitting updated YAML content. #### Endpoint[​](/resources/projects/settings/project-apis.md#endpoint-4 "Direct link to Endpoint") `POST /updateProjectByYaml` #### Request Body[​](/resources/projects/settings/project-apis.md#request-body-2 "Direct link to Request Body") ``` { "projectId": "your-project-id", "fileKeyToContent": { "ad-mob": "showTestAds: false", } } ``` info * In the `fileKeyToContent` object, you must provide the **entire content** of the file. * The YAML content must be passed as a **single-line string** with correct formatting and appropriate escaping for newlines and indentation. For example, in the following `fileKeyToContent` object, you see the actual multiline YAML content, which is not allowed ❌. ``` { "projectId": "ecommerce-flow-app-ie7nl6", "fileKeyToContent": { "app-state": "fields: - parameter: identifier: name: myAppState key: hg7j8z0y dataType: scalarType: String description: "Stores the current user session state" persisted: false" } } ``` Now, here’s how the YAML content should be passed (i.e., as single line string ✅). ``` { "projectId": "ecommerce-flow-app-ie7nl6", "fileKeyToContent": { "app-state": "fields:\n - parameter:\n identifier:\n name: myAppState\n key: hg7j8z0y\n dataType:\n scalarType: String\n description: \"Stores the current user session state\"\n persisted: false" } } ``` #### Response[​](/resources/projects/settings/project-apis.md#response-4 "Direct link to Response") * **Success (200):** `{"success": true, "reason": null, "value": ""}` * **Error (400):** Validation errors or malformed request. * **Error (403):** Insufficient permissions or project locked. * **Error (404):** Project or user not found. #### Example Usage[​](/resources/projects/settings/project-apis.md#example-usage-4 "Direct link to Example Usage") This example updates the `ad-mob` file and adds/updates app state variables. ``` curl -X POST \ 'https://api.flutterflow.io/v2/updateProjectByYaml' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "projectId": "your-project-id", "fileKeyToContent": { "ad-mob": "showTestAds: false", "app-state": "fields:\n - parameter:\n identifier:\n name: myAppState\n key: hg7j8z0y\n dataType:\n scalarType: String\n description: \"Stores the current user session state\"\n persisted: false\n - parameter:\n identifier:\n name: userPreferences\n key: abc123xy\n dataType:\n scalarType: JSON\n description: \"User settings and preferences\"\n persisted: true" } }' ``` ## API Usage Example[​](/resources/projects/settings/project-apis.md#api-usage-example "Direct link to API Usage Example") Let’s walk through a practical example of updating an app state variable using the Project APIs. info You can download and use [**Postman Collection**](../../../../static/jsons/FlutterFlow_APIs.postman_collection.json) to quickly test all FlutterFlow Project APIs with pre-filled headers, parameters, and sample requests. First, we use the `/listPartitionedFileNames` endpoint to check if the `app-state` file exists in the project. Once confirmed, we call the `/projectYamls` endpoint to download the YAML file. The API returns a base64-encoded string representing a zip file, which we decode and download using tools like [Base64 to ZIP](https://b64encode.com/tools/base64-to-zip/). Next, we open the `app-state.yaml` file and update the `enableDarkMode` variable by setting its `persisted` value to `true`. We then convert the updated YAML into a properly escaped single line string and validate it using the `/validateProjectYaml` endpoint. If validation succeeds, we send the final update using the `/updateProjectByYaml` endpoint. ## Error Handling[​](/resources/projects/settings/project-apis.md#error-handling "Direct link to Error Handling") This section outlines how the API handles errors, including common HTTP response codes and detailed validation feedback for YAML processing issues. ### Common Error Responses[​](/resources/projects/settings/project-apis.md#common-error-responses "Direct link to Common Error Responses") This table outlines the most common HTTP status codes and their meanings, helping you identify and resolve API issues more effectively. | Status Code | Description | Example Response | | ----------- | -------------------------------------------- | -------------------------------------------------------- | | 400 | Bad Request - Invalid JSON or malformed YAML | `"Failed to update project: ad-mob:Expected bool value"` | | 403 | Forbidden - Insufficient permissions | `"You do not have write access to this project"` | | 404 | Not Found - Project or user doesn't exist | `"Project not found"` | | 500 | Internal Server Error | `"Unknown error"` | ### Validation Errors[​](/resources/projects/settings/project-apis.md#validation-errors "Direct link to Validation Errors") When YAML validation fails, you'll receive detailed error information: ``` { "validationErrors": [ { "message": "Unknown field name 'showTestAdsasssdaf'", "fileKey": "ad-mob", "yamlLocation": { "line": 1, "column": 1 } } ] } ``` ## Best Practices[​](/resources/projects/settings/project-apis.md#best-practices "Direct link to Best Practices") * **Always Validate First**: Before updating project YAMLs, use the validation endpoint to ensure your changes are valid: ``` # 1. Validate the YAML curl -X POST 'https://api.flutterflow.io/v2/validateProjectYaml' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"projectId": "project-id", "fileKey": "ad-mob", "fileContent": "showTestAds: false"}' # 2. If validation passes, apply the changes curl -X POST 'https://api.flutterflow.io/v2/updateProjectByYaml' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"projectId": "project-id", "fileKeyToContent": {"ad-mob": "showTestAds: false"}}' ``` * **Handle Project Locks**: Projects may be temporarily locked during other operations. If you receive a 403 error mentioning `Project is locked due to ongoing changes. Please try again later.`, wait and retry. * **Batch Updates**: You can update multiple files in a single request by including multiple entries in `fileKeyToContent`: ``` { "projectId": "your-project-id", "fileKeyToContent": { "ad-mob": "showTestAds: false", "app-settings": "appName: \"Updated Name\"", "authentication": "enableEmailAuth: true" } } ``` ## Rate Limits[​](/resources/projects/settings/project-apis.md#rate-limits "Direct link to Rate Limits") Please be mindful of API rate limits. If you're making many requests, implement appropriate delays between calls to avoid being rate-limited. --- # Project Setup Setting up project in FlutterFlow ensures that your app is prepared to provide a robust and user-friendly experience across different platforms and regions. By adding necessary permissions, enabling multiple platforms, and supporting multiple languages, you can expand your app's reach and functionality while maintaining high standards of performance and user satisfaction. ## Permissions[​](/resources/projects/settings/project-setup.md#permissions "Direct link to Permissions") In your app, you must be open and get upfront consent before using the user's private information, such as location, health data, photos, or any information that reveals their identity via the camera and/or microphone. This is done by adding a permission flow just before accessing the data. We automatically add permissions whenever you add features that access the user's private data. The only thing left for you is to add the permission messages. Adding permission messages helps users clearly understand how your app will use the data it is requesting for. info * You can't show the custom permission message on Android, so the message(s) added here are displayed only on iOS devices. To write a clear permission message, please visit the help guide [**here**](https://developer.apple.com/design/human-interface-guidelines/patterns/accessing-private-data/#requesting-permission). * You can't turn off the permission (with messages) added by us to prevent issues that might come after submitting your app for review. * See how to [**request permission**](/resources/projects/settings/project-setup.md#request-permission-action). ![permissions-fi](/assets/images/permissions-fi-12cf77c30e36d15240091c31024bea2f.avif) Asking for camera permission before capturing a photo ### Adding permission message[​](/resources/projects/settings/project-setup.md#adding-permission-message "Direct link to Adding permission message") Although we add some default permission messages, you must change them to clearly mention the reason for asking the permission(s). To add permission message: 1. Select **Settings & Integrations** from the left Navigation Menu. 2. Under the **Project Setup**, select **Permissions**. 3. Here you can customize the permission message for each permission. For the permissions that are not added/enabled yet, you can turn on the toggle and enter the message. On running the app, this message will be displayed inside the standard alert dialog (between your app name and action buttons). For the already added permission (which you can't turn off), if you leave the message empty, the message displayed as a hint will be shown inside the permission dialog. ![Adding permission message](/assets/images/add-permission-6b0d8904542d74f1c2325f87ea5108ea.png) ### Adding custom permission[​](/resources/projects/settings/project-setup.md#adding-custom-permission "Direct link to Adding custom permission") Sometimes you might add a feature (probably using Custom Widget or Custom Action) that requires user permission and is not present in the list here—for example, adding a speech recognition feature to your project. In that case, you can add the required permission along with the message for the Android and/or iOS from here. To add custom permission: 1. Select **Settings & Integrations** from the left Navigation Menu. 2. Under the **Project Setup**, select **Permissions**. 3. Click on the **+ Add Permission**. 4. Inside the **iOS Permission key** enter the value (e.g. *NSSpeechRecognitionUsageDescription*, *NSMicrophoneUsageDescription* etc.). 5. Inside the **Android Permission name** enter the value (e.g., *RECORD\_AUDIO*, *CAMERA*, etc.). 6. Also, enter the **Permission Message** that describes the exact usage of data. 7. Click on the Done icon on the right. Adding translation for messages You can also add multilingual permission messages by following the instructions [**here**](/concepts/localization.md). ### Request Permission \[Action][​](/resources/projects/settings/project-setup.md#request-permission-action "Direct link to Request Permission \[Action]") Using this action, you can request permission before accessing the user's private information, such as location, voice, contacts, and photos. This action is helpful when you add a custom widget or action that accesses the user's personal information and does not have an inbuilt permission mechanism. info * Request permission only works on a mobile platform. * There won't be any dialog shown for the *Bluetooth* permission. - Allow permission - Reject permission #### Adding Request Permission action[​](/resources/projects/settings/project-setup.md#adding-request-permission-action "Direct link to Adding Request Permission action") Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to add this action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action flow Editor** in a new popup window. If it's the first action, click **+ Add Action** button. Otherwise, click the "**+**" button below the previous action tile and select **Add Action**. 1. Search and select the **Request Permissions** (under *Alerts/Notifications*) action. 2. Set the **Permission Type** to the one you need. Only the permissions for which the message is present are shown here. 3. Now you must check if the permission was granted or rejected. You can do so by adding the conditional action. To do so, click the "**+**" button below the previous action tile and select **Add Conditional**. 4. From the **Set Variable** menu, select **Permission > Permission name** (this should be the permission you requested for). 1. The **TRUE** section represents success, meaning permission was granted. Here you can add any action that informs users or access their data. 2. The **FALSE** section represents failure, meaning permission was denied. Here you can add any action that informs users about the permission they have denied. 5. Click **Close**. * Adding Request Permission action * Request permission action flow ![adding-request-permission-action-flow](/assets/images/adding-request-permission-action-flow-ffa113e047c97faddb9e18f41a63ba5a.avif) *** ## Platforms[​](/resources/projects/settings/project-setup.md#platforms "Direct link to Platforms") By default, the generated project can run on Android, iOS, and the Web without any additional effort. However, to run your app on the desktop, you need to enable a platform (e.g., MacOS, Windows, Linux) from this page. ### Advanced Android Settings[​](/resources/projects/settings/project-setup.md#advanced-android-settings "Direct link to Advanced Android Settings") * **Kotlin Version**: There are various situations where you may need to modify or configure the Kotlin version in your Android project. This could include updating to the latest version, adapting the version to accommodate a specific library or tool, or other specific requirements. To change the default version, enter the value here. * **Minimum SDK Version**: This defines the lowest version of Android that your app can run on. Setting a higher minimum SDK version ensures your app can use newer APIs and features but may limit the devices that can install it. * **Compile SDK Version**: This refers to the version of Android that your code is compiled against. It determines the APIs your app can use. To change it, enter the desired SDK version here. * **Target SDK Version**: This is the version of Android that your app is intended to run on. It helps Android ensure forward compatibility by applying certain behavior changes only if the target SDK is high enough. To adjust this, enter the desired version here. ### Advanced iOS Settings[​](/resources/projects/settings/project-setup.md#advanced-ios-settings "Direct link to Advanced iOS Settings") * **Disable iPad Support:** If the app is specifically designed for an iPhone and doesn't provide a good user experience on an iPad, you might want to trun on this setting. * **Minimum iOS Version**: This specifies the lowest version of iOS that your app can run on. ### Advanced Web Settings[​](/resources/projects/settings/project-setup.md#advanced-web-settings "Direct link to Advanced Web Settings") * **Use Original Engine Initialization**: This setting uses the original Flutter web engine initialization, which can sometimes improve loading times in deployed web apps. Enable this option if you experience performance issues with the custom initialization process. * **Use CanvasKit**: CanvasKit provides better performance and fidelity for rendering on the web by leveraging WebAssembly. This setting can improve the visual quality and performance of your app, especially for complex graphics and animations. Enable this option to use CanvasKit for rendering on the web. warning While FlutterFlow can generate project code for **macOS** and **Windows**, these platform targets are **currently in Alpha** and provided as-is. FlutterFlow does not provide infrastructure for building, debugging, or running apps on these platforms, and our **Support team is unable to assist** with issues related to macOS or Windows builds. However, the generated code can still be opened, built, and debugged using standard Flutter tooling such as Android Studio or VS Code. This applies only to the deployment platform options, not to the FlutterFlow Desktop application itself. ## Multiple Languages[​](/resources/projects/settings/project-setup.md#multiple-languages "Direct link to Multiple Languages") To support multiple languages in your app, refer [here](/concepts/localization.md). *** ## Walkthroughs[​](/resources/projects/settings/project-setup.md#walkthroughs "Direct link to Walkthroughs") A walkthrough in app development is a guided tour of the app's features and functionality, typically presented to the user when they first launch the app. It is designed to help new users understand how to use the app and navigate its various sections. For example, consider a news article app. When a new user opens the app for the first time, they might be greeted with a series of pop-ups that highlight key features such as watching article videos, subscribing to article updates, and filtering articles by tags. The steps to create and display a walkthrough in your app are as follows: 1. [Create walkthrough](/resources/projects/settings/project-setup.md#1-create-walkthrough) 2. [Start walkthrough](/resources/projects/settings/project-setup.md#2-start-walkthrough-action) 3. [Get notified on walkthrough skipped and completed](/resources/projects/settings/project-setup.md#3-get-notified-on-walkthrough-skipped-and-completed) ### 1. Create walkthrough[​](/resources/projects/settings/project-setup.md#1-create-walkthrough "Direct link to 1. Create walkthrough") To create a walkthrough: 1. Navigate to **Settings and Integrations** > **General** > **Walkthroughs >** click **Create New**. 2. Start with providing the **Name**, **Description** and then select the **Page** on which you want to show the walkthrough. The name you enter will be used to initiate the walkthrough later. 3. Now, we must add the steps for our walkthrough. Each step that we add here acts as a separate screen or popup that nicely animates to highlight the UI element. To add steps: 1. Click on the **+ Add Step**. 2. Choose the widget to highlight by clicking **Widget Unset**. In the right-side preview, select the desired widget and click **Confirm**. 3. When the widget is in focus, you may want to present information about it; this could be a simple text or a custom component (e.g., a text with an arrow). You have complete control over what you want to display via a [component](/resources/ui/components.md). Click the diamond icon to create a new component and then set it to **Content**. 4. You can also choose where the Content will be displayed by setting the **Content Alignment**. 5. Choose a **Focus Shape** for the widget—either **Circle** or **Rectangle**. 6. Pick an **Overlay Color** that you want to display when the widget is highlighted. 7. By default, we also add a skip button on the screen, and you can align it using the **Skip Alignment** option. 8. Add additional steps by repeating the process for all UI elements you wish to feature. ![Walkthrough Step](/assets/images/wt-steps-3a95f0045c4bb88dfd85422d8a8ea5d8.png) 4. To preview the walkthrough, click the **Start Preview** button and use the arrows to navigate through the steps. 5. To rearrange the steps, enable the **Reorder** option and then use the arrows to adjust their sequence. 6. Click the **Add Walkthrough** to save. ### 2. Start Walkthrough \[Action][​](/resources/projects/settings/project-setup.md#2-start-walkthrough-action "Direct link to 2. Start Walkthrough \[Action]") After creating a walkthrough, you can display it on a page using the Start Walkthrough action. Follow the steps below to add this action on a page load. 1. Walkthroughs are generally presented immediately upon page load. Therefore, open the page where you would like the walkthrough to be showcased. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 3. Ensure the **On Page Load** is selected and click on the **+ Add Action**. 4. On the right side, search and select the **Walkthrough > Start Walkthrough** (under *Widget/UI Interactions*) action. 5. Click to **Select Walkthrough** that you have created. ### 3. Get notified on walkthrough skipped and completed[​](/resources/projects/settings/project-setup.md#3-get-notified-on-walkthrough-skipped-and-completed "Direct link to 3. Get notified on walkthrough skipped and completed") Sometimes, you might want to get a callback to know whether the walkthrough is skipped or completed. For example, you could set up a callback to gather analytics or trigger a specific action once the walkthrough is finished, such as directing the user to a new page or enabling certain features of the app. When a walkthrough is added on a page, you'll see the following types of actions (aka callbacks), and you can choose any of them to add actions under it. 1. **On Walkthrough Complete**: Actions added under this will be triggered whenever the user finishes all the steps of the walkthrough. 2. **On Walkthrough Skip**: Actions added under this will be triggered whenever the user chooses to skip the walkthrough. * On Walkthrough Complete * On Walkthrough Skip Here's how you do it: 1. Open the page, select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 2. Select **On** **Walkthrough Complete** or **On Walkthrough Skip** and add actions under it. ### Video guide[​](/resources/projects/settings/project-setup.md#video-guide "Direct link to Video guide") If you prefer watching a video tutorial, here's the one for you: ### FAQs[​](/resources/projects/settings/project-setup.md#faqs "Direct link to FAQs") How do I fix when walkthrough misaligns on a widget, not focusing on the component? This issue typically arises when a widget's animation and the walkthrough start simultaneously. As the walkthrough initiates, it captures the widget's initial position before the animation completes. Consequently, after the animation concludes, the widget may have shifted to a different location, leading to misalignment. ![Misaligned focus example](/assets/images/misaligned-focus-example-6d9b679571907534c9414fbf5e9640d2.png) To resolve this, simply add a delay ([Wait](/resources/time-based-logic/wait-action.md) action) before initiating the walkthrough. **Remember,** the wait duration must be equal to or greater than the duration of the animation. My widget is not highlighting on a scrollable page. What should I do? We are aware of a limitation where widgets that are not visible on a page (i.e., you need to scroll down to see them) may not be highlighted. We are actively working to resolve this issue. As a temporary workaround, you can try placing the widget in an area that is visible without scrolling. We appreciate your patience and hope to have a fix soon! --- # Naming Variables & Functions To make your code more maintainable, readable, and consistent, it’s essential to adopt clear naming conventions for variables, functions, and components. Best practices for naming conventions in app development (especially for projects using Flutter), aim to improve code readability, maintainability, and consistency across the application. Here are some general guidelines tailored for different aspects of a Flutter project: Various naming styles (as suggested by [Dart Effective Style Guide](https://dart.dev/effective-dart/style#identifiers)): * **UpperCamelCase** (also known as PascalCase) names capitalize the first letter of each word, including the first. * **lowerCamelCase** (also known as camelCase) names capitalize the first letter of each word, except the first which is always lowercase, even if it's an acronym. * **lowercase\_with\_underscores** (also known as snake\_case) names use only lowercase letters, even for acronyms, and separate words with \_. ![various-naming-styles.png](/assets/images/various-naming-styles-1c972b9898f0f011be5caba60a9754a5.png) **General Principles** * **Be Consistent:** Whatever conventions you choose, apply them consistently across the project. * **Be Descriptive:** Names should be self-explanatory, reducing the need for additional comments to explain what a variable, function, or class does. * **Avoid Abbreviations:** Unless it's a well-known abbreviation, spell out words to avoid confusion. ## Variable Naming Convention[​](/resources/style-guide.md#variable-naming-convention "Direct link to Variable Naming Convention") This section outlines naming conventions for pages, components, state variables, custom data types, enums, and constants to ensure clarity and consistency throughout the project. ### Pages & Components[​](/resources/style-guide.md#pages--components "Direct link to Pages & Components") Use **UpperCamelCase** for all widgets, components, pages, and screen names to maintain consistency and readability. FlutterFlow ensures clarity by automatically adding "Widget" to widget names when generating code. For components, you can suffix the name with "Component" to clearly distinguish them. Similarly, for pages and screens, include "Page" or "Screen" in the name to indicate their purpose. This approach aligns with Dart conventions for class names and ensures a well-organized project structure. ![comp-style-guide.png](/assets/images/comp-style-guide-2ff5f0992fe7d38b846bdb92e807b6b1.png) Do's * **Use UpperCamelCase for Names:** Always use **UpperCamelCase** for widgets, components, pages, and screens. Examples: `CustomButton`, `UserProfilePage`, `MainViewComponent`. * **Include "Screen" or "Page" in Page Names:** Use "Screen" or "Page" in file names to identify UI screens or pages. Examples: `LoginScreen`, `SettingsPage`. * **Use Prefixes for Clarity When Necessary:** Add a prefix if it significantly improves clarity or prevents naming conflicts. Example: `AdminUserProfile` (to differentiate it from `CustomerUserProfile` or `UserProfile`). * **Be Descriptive and Clear in File Names:** Ensure names are descriptive enough to convey their purpose at a glance. Examples: `OrderConfirmationScreen`, `ProductDetailsPage`. Don'ts * **Don’t Use Unnecessary Prefixes:** Avoid prefixes that do not add clarity or are redundant. Bad Example: `AppPrimaryButton` (if `PrimaryButton` is sufficient). * **Don’t Add "Widget" Explicitly:** Avoid adding "Widget" to class or component names manually, as FlutterFlow already appends it during code generation. Bad Examples: `ButtonWidget`, `ProfileCardWidget`. * **Don’t Use LowerCamelCase for Class Names:** Reserve **lowerCamelCase** for variables and methods, not for components, or pages. Bad Examples: `loginButton`, `userProfile`. * **Don’t Mix Naming Conventions:** Maintain consistency with UpperCamelCase for all widgets, components, pages, and screens. Bad Examples: `userLogin`, `Profilecard`, `headerView`. * **Don’t Use Generic Names Without Purpose:** Avoid overly generic names that do not clearly convey the file’s intent. Bad Examples: `Main`, `View`, `Screen1`. Note that the style guidelines for Pages and Components also apply to **[Custom Widgets](/concepts/custom-code/custom-widgets.md)**, as Pages and Components created in FlutterFlow are internally generated as widgets. ### Custom Data Types & Enums[​](/resources/style-guide.md#custom-data-types--enums "Direct link to Custom Data Types & Enums") When naming custom data types and enums, use **UpperCamelCase** for consistency and clarity. Ensure that names are descriptive, providing a clear representation of the entity or purpose. ![dt-style-guide.png](/assets/images/dt-style-guide-8a04300e5d3d1f20e1dbb7d13e96c3b2.png) Do's * **Use UpperCamelCase for Custom Data Types:** Name your custom data types using **UpperCamelCase**. Ensure that names are clear, concise, and descriptive, reflecting the entity they represent. Good Examples: `UserModel`, `ProductDetails`, `OrderItem`. * **Use consistent naming for Enum Names and Values:** Use **UpperCamelCase** for the enum name such as, `Status`, `ConnectionState`, `UserRole` and **lowerCamelCase** for its values e.g., `{active, inactive, pending}`. This approach aligns with Dart's enum naming guidelines and ensures consistency. * **Use Plural Names for Lists:** If the data type represents a List, use a plural name to clarify its purpose. Good Example: `OrderItems` (to represent multiple `OrderItem` objects). Don'ts * **Don’t Use All Lowercase or Mixed Case for Custom Data Types:** Avoid using all lowercase or inconsistent casing in data model class names, as it reduces readability. Bad Example: `usermodel`, `product_details`. * **Don’t Use Vague or Non-Descriptive Names**: Avoid using generic or unclear names that do not clearly describe the data entity. Bad Example: `DataModel`, `Entity`, `Item`. * **Don’t Mix Naming Conventions for Enums:** Maintain consistent capitalization between enum names and their values. Bad Example: `enum UserRole { Admin, EDITOR, viewer }` For datatype fields, we use the same convention as [State variables](/resources/style-guide.md#variables). ### Constants[​](/resources/style-guide.md#constants "Direct link to Constants") Flutter prefers using a lowercase `k` prefix for constants to indicate their immutability, especially for project-specific constants. This approach is more concise and aligns with Dart's common practices. Use **SCREAMING\_SNAKE\_CASE** only when contributing to global or legacy projects where it is already in use. Do's * **Start Constants with a k Prefix:** Always use a lowercase `k` followed by **UpperCamelCase** for constants in FlutterFlow projects. * **Use Descriptive and Contextual Names:** Clearly describe the purpose of the constant. Avoid using abbreviations unless they are widely understood. Examples: `kDefaultPadding`, `kMaxUploadSizeMb` Don'ts * **Don’t Omit the k Prefix for Constants:** Avoid using plain names for constants in a Flutter-specific project, as they might conflict with variables or methods. Bad Examples: `padding`, `uploadSize`. * **Don’t Use Vague or Generic Names:** Avoid using names that fail to describe the purpose of the constant. Bad Examples: `VALUE`, `DATA`, `X`, `Y`. ### Variables[​](/resources/style-guide.md#variables "Direct link to Variables") State variable & Data Type field names follow the **lowerCamelCase** naming style to align with Dart's conventions. Do's * **Be Descriptive and Clear:** Use variable names that clearly describe their purpose, avoiding generic or vague terms. Examples: `isFormValid`, `errorMessage`, `availableProducts`. * **Prefix Boolean Variables with `is`, `has`, or `should`:** For readability, use prefixes that denote the variable's purpose when naming Boolean values. Examples: `isActive`, `hasErrors`, `shouldReload`. * **Use Consistent Prefixes to denote state:** When managing UI or asynchronous state, use prefixes like `current`, `selected`, or `pending` for better context. Examples: `currentTabIndex`, `selectedUserId`, `pendingAction`. Don'ts * **Don’t Use Abbreviations or Single Letters:** Avoid abbreviations or single-character names that obscure the variable's intent. Bad Examples: `usrNm`, `f`, `cnt`. * **Don’t Use Generic Names:** Avoid using generic terms that do not convey the variable’s purpose. Bad Examples: `data`, `value`, `temp`. * **Don’t Start Variables with Uppercase:** Follow Dart conventions by starting variable names with lowercase. Bad Examples: `UserName`, `IsLoading`. ## Function Naming Convention[​](/resources/style-guide.md#function-naming-convention "Direct link to Function Naming Convention") This section defines naming conventions for custom functions, actions, and action blocks to maintain consistency, readability, and ease of understanding across the codebase. ### Custom Functions & Actions[​](/resources/style-guide.md#custom-functions--actions "Direct link to Custom Functions & Actions") Custom functions and custom actions created in the Custom Code tab of FlutterFlow should follow the **lowerCamelCase** naming convention. These typically reflect an action or behavior. ![func-style-guide.png](/assets/images/func-style-guide-4884145ca292af50c547ddac71846d0b.png) Do's * **Be descriptive and concise:** Use clear, meaningful names that describe the action or purpose of the function (e.g., `validateForm` instead of `doCheck`, or `fetchUserData` instead of `userData`). * **Use action-oriented names:** Start with verbs to indicate behavior (e.g., `submitForm`, `processPayment`). Dont's * **Avoid using underscores or spaces:** Names like `fetch_user_data` do not align with **lowerCamelCase** conventions. * **Avoid redundant prefixes or suffixes:** There’s no need to prefix with `custom` or suffix with `Func` unless absolutely necessary for clarity (e.g., `customSubmitFormFunc` is redundant). * **Don’t use overly generic names:** Avoid vague terms like `doSomething` or `functionOne`, which don’t provide context. Note that **[Action Blocks](/resources/functions/action-blocks.md)** should follow the same naming convention as custom actions, as they are both technically Dart functions internally in the generated code. --- # Periodic Action Periodic execution of logic refers to running a specific block of code or a set of actions at regular, defined intervals. This is useful for tasks that need to be repeated continuously or at specific time intervals. ## Use-cases[​](/resources/time-based-logic/periodic-action.md#use-cases "Direct link to Use-cases") * For tasks that need regular updates, such as fetching data from a server, monitoring system health, or updating a user interface. * In scenarios where periodic checks or maintenance tasks are required (e.g., cleaning up temporary files, sending periodic notifications). * Implementing polling mechanisms for checking changes in state or data. ## Start Periodic Action[​](/resources/time-based-logic/periodic-action.md#start-periodic-action "Direct link to Start Periodic Action") To create a periodic action workflow, add the **Start Periodic Action** action either on the **On Page Load** action trigger of your page or on any widget that should start the periodic action. The properties of the Periodic Action look like this: ![periodic-action.png](/assets/images/periodic-action-443603587a43e6ca0016f1e208f692d4.png) ## Stop Periodic Action[​](/resources/time-based-logic/periodic-action.md#stop-periodic-action "Direct link to Stop Periodic Action") You can call the **Stop Periodic Action** action from anywhere on the page or component to stop one or multiple periodic actions. Dont forget to stop the Periodic Actions Stopping a periodic action is crucial to prevent unnecessary resource consumption and potential performance issues. It ensures that tasks do not continue running in the background when they are no longer needed, which can help maintain the efficiency and responsiveness of your application. ### Periodic Action vs Timer[​](/resources/time-based-logic/periodic-action.md#periodic-action-vs-timer "Direct link to Periodic Action vs Timer") | Feature | Timer Widget | Periodic Action | | ----------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | **Purpose** | Used for single or non-repetitive timing events, often within user interfaces. | Used for repetitive tasks that need to run at regular intervals. | | **Usage** | To set a countdown timer, start/stop actions based on user input, or trigger actions after a specific duration. | For background tasks, monitoring, regular updates, and periodic checks. | | **Example** | Countdown timer in a quiz application. | Fetching new messages from a server every 5 minutes. | ### Periodic Actions vs Loops[​](/resources/time-based-logic/periodic-action.md#periodic-actions-vs-loops "Direct link to Periodic Actions vs Loops") | Feature | Periodic Actions | Loops | | ----------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | | **Purpose** | To execute a task at regular, defined intervals. | To execute a task repeatedly until a condition is met. | | **Execution Frequency** | Executes at specified time intervals (e.g., every 60 seconds). | Executes continuously until the loop condition is false. | | **Use Case** | Suitable for tasks needing regular updates, such as fetching new data. | Suitable for tasks requiring iteration over collections or repeated checks. | | **Control** | Can be started and stopped easily, allowing for controlled execution. | Runs until a break condition is met or the loop is explicitly stopped. | | **Resource Management** | Efficient, as it allows idle time between executions. | Can be resource-intensive if not managed properly, as it runs continuously. | | **Examples** | Fetching new offers from a server every 5 minutes. | Iterating over a list of items to process them one by one. | --- # Timer \[Widget] **Timer \[Widget]** allows developers to create countdown or count-up timers within your page. It is particularly useful in scenarios where timing is crucial, such as quizzes, auctions, workout apps, and various time-sensitive activities. ## Use Cases[​](/resources/time-based-logic/timer-widget.md#use-cases "Direct link to Use Cases") * **Quizzes and Exams:** Enforcing time limits for answering questions. * **Auctions:** Displaying the remaining time for bids. * **Workouts:** Timing exercises and rest periods. * **Events:** Counting down to the start or end of an event. * **Productivity:** Using Pomodoro timers to manage work sessions and breaks. ## Timer Types[​](/resources/time-based-logic/timer-widget.md#timer-types "Direct link to Timer Types") * **Countdown Timer:** Counts down from a specified time to zero, often used in scenarios where a task or event needs to be completed within a set period. * **Count-up Timer:** Counts up from zero to a specified time or indefinitely, useful for tracking the duration of an event or activity. On adding the Timer widget to your page, you can specify the type of timer and other properties as mentioned here: ![timer-widget.png](/assets/images/timer-widget-003b5dec98bdbd979f826e8693f11640.png) ## On Timer End \[Action Trigger][​](/resources/time-based-logic/timer-widget.md#on-timer-end-action-trigger "Direct link to On Timer End \[Action Trigger]") You can also specify a flow of actions when the timer ends. You can find this Action Trigger on clicking the Action Flow Editor on the Timer widget. ![timer-widget-action.png](/assets/images/timer-widget-action-d16ca9214fae45e4005bc40e35b5dec2.png) ## Controlling the Timer[​](/resources/time-based-logic/timer-widget.md#controlling-the-timer "Direct link to Controlling the Timer") You can control the timer from anywhere on the page. Using any widget's Action Flow Editor, you can perform the following actions: * **Start Timer:** This starts the timer. If the timer is already started, triggering this type won't have any effect. * **Stop Timer:** This stops the timer. This will have effect only if the timer is started. * **Reset Timer:** This resets the timer and brings it to the initial state. ![timer-control.png](/assets/images/timer-control-61e5b36b9b03b1d9d93c08a4228b0f5d.png) ## Periodic Action vs Timer[​](/resources/time-based-logic/timer-widget.md#periodic-action-vs-timer "Direct link to Periodic Action vs Timer") | Feature | Timer Widget | Periodic Action | | ----------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | **Purpose** | Used for single or non-repetitive timing events, often within user interfaces. | Used for repetitive tasks that need to run at regular intervals. | | **Usage** | To set a countdown timer, start/stop actions based on user input, or trigger actions after a specific duration. | For background tasks, monitoring, regular updates, and periodic checks. | | **Example** | Countdown timer in a quiz application. | Fetching new messages from a server every 5 minutes. | --- # Wait \[Action] The **Wait** action is used to pause the execution of a workflow for a specific amount of time. This is helpful when you want to delay the next step in a sequence, for example, to synchronize events, allow animations to complete, or ensure a condition is met before continuing. It’s a key concept in managing time-based logic within action flows. Possible use cases * **Show Splash Screen:** Delay the transition to the next page to allow the splash screen to be visible for a few seconds. * **Step-by-Step Tutorials:** Introduce timed delays between steps to guide users through a tutorial or onboarding flow. * **Chain Animations:** Add pauses between multiple animations for a more fluid and organized visual effect. --- # Components Components in FlutterFlow are reusable widgets. You design a widget once and can reuse it throughout your app to save time, ensure consistency, and make it easier to maintain. When you add a component to a [**Page**](/resources/ui/pages.md), it becomes part of that page's **[Widget Tree](/resources/ui/widgets.md#widget-tree)**. This allows the component to interact with other widgets, inherit properties, and respond to state changes as part of the page's structure. Components help in several ways: * **Consistency:** Components provide a consistent look and behavior, reducing the likelihood of discrepancies that can occur when the same UI elements are created multiple times. * **Centralized Updates:** By creating a component once and reusing it across different parts of your app, you ensure that any design or functionality changes are made in one place. When that component is updated, all instances of that component across the app automatically reflect those changes. This significantly reduces the effort required to maintain and update the app. Classes vs. Instances Learn more about **[Classes and their Instances](/resources/ui/overview.md)** and what they mean in FlutterFlow. * **Error Reduction:** Since components reduce design duplication, the risk of errors decreases. Fixing an issue in a component means it is fixed everywhere, leading to fewer bugs and inconsistencies. * **Scalability:** As your app grows, maintaining a DRY codebase through components makes it easier to scale. Adding new features or modifying existing ones becomes more straightforward and less prone to introducing errors. DRY PRINCIPLE The **DRY (Don't Repeat Yourself)** principle is a software development concept that emphasizes the importance of reducing repetition within code and design. Leveraging components effectively helps you build a consistent, efficient, and maintainable app. ## Common Use Cases[​](/resources/ui/components.md#common-use-cases "Direct link to Common Use Cases") Components can be used in various scenarios to accelerate your app development process. Here are some common use cases: * Design a **standard button once** and reuse it across multiple screens to maintain a cohesive look. * Use components for **card designs** frequently used in your app, such as product cards, user profiles, or news articles. * **Standardize input forms** for tasks like user registration, login, or feedback collection, to ensure a consistent user experience. * Design **pop-up messages or dialogs** that match the overall theme of your app, enhancing visual consistency. * Build interactive elements such as **custom sliders, ratings, or progress bars**, and use them across various parts of your app. * Design sections of a screen that are frequently repeated, such as testimonials, image galleries, or feature highlights, and reuse them to maintain a cohesive layout. Here's an example of commonly used components in the [EcommerceFlow demo](https://bit.ly/ff-docs-demo-v2) app. ![custom-components-demo-list.png](/assets/images/custom-components-demo-list-fd33ff85bc0fec2925c8a7ffba8c15d4.png) Some of the custom components from the Ecommerce Demo App --- # Action Parameters (Callbacks) In FlutterFlow, callbacks are a way to pass down actions from parent entities (like pages or other components) to child entities (such as custom widgets or components). This allows the parent to define specific behaviors that the child entity should execute when certain events occur. Callbacks enable dynamic and interactive behavior in child components, allowing them to perform actions defined by the parent, such as navigation, data updates, or displaying dialogs. For example, if you have an *image upload component*, the parent can define what should happen after an image is successfully uploaded. Using callbacks, the *image upload component* can execute a parent-defined action, such as: * Resize and compress the image to reduce storage size. * Update the user's database record with the new image URL. * Refresh the UI to display the updated profile picture. This makes the *image upload component* reusable, as it doesn't need to know the specifics of what should happen after upload. Instead, the parent controls the behavior by passing the appropriate actions via a callback. ![action-parameters-callbacks](/assets/images/action-parameters-callbacks-93f5b42a0ea8dc2d5bf5f7ccacd430c6.avif) Benefits of Using Callbacks in FlutterFlow * **Modularity:** Separate the logic of what happens when an event occurs from the child component, making your component more modular and reusable. * **Reusability:** Use the same child component in different contexts with different behaviors, simply by passing different callbacks. ## Adding Callbacks[​](/resources/ui/components/callbacks.md#adding-callbacks "Direct link to Adding Callbacks") Let’s continue with our previous example (*image upload component*) and see how to add callbacks to it: ### Creating a Callback Parameter[​](/resources/ui/components/callbacks.md#creating-a-callback-parameter "Direct link to Creating a Callback Parameter") To create a component that will execute a callback, you must create a component with a parameter with the **Action** type. You can create an action parameter called `uploadAction`, which represents the action that will be executed after the image is uploaded. When you create an action parameter, you can also specify parameters that will be passed into the action. For this example, the action will likely need to know the uploaded image URL to process it further. So, you can specify an action parameter called `uploadedURL`. Now, the page or component that uses this button can use this parameter in its own action flow. An example of this is shown below. ### Executing a Callback[​](/resources/ui/components/callbacks.md#executing-a-callback "Direct link to Executing a Callback") You can execute the action passed into the component by using the **Execute Callback** action within the component's action flows. For example, you can execute the above callback after the image is successfully uploaded and pass the uploaded image URL into the callback. ### Passing an Action to a Component[​](/resources/ui/components/callbacks.md#passing-an-action-to-a-component "Direct link to Passing an Action to a Component") When you add a component to the widget tree of a page or another component, you can define values for its parameters, including action parameters. For instance, when you add an *image upload component*, you can specify the action flows that should run when the callback is triggered. For this example, we simply update the profile picture. info You can access the value passed to the callback by navigating to the **Set Variable** menu > **Callback Parameters**. Now that we have an *image upload component* with action parameters set up, it can be reused across different pages or contexts, because it relies on the parent to define the after-upload logic. For example, the same component can be used to upload an image while posting reviews for a product, eliminating the need to create a separate component for this functionality. ![component-action-parameters.avif](/assets/images/component-action-parameters-4af70e2016eac391b7ad452ec33669b5.avif) ## More Examples[​](/resources/ui/components/callbacks.md#more-examples "Direct link to More Examples") Let's look at a few more examples of action parameters (callbacks) in real-world scenarios. ### Example 1: Dynamic Dialog Component[​](/resources/ui/components/callbacks.md#example-1-dynamic-dialog-component "Direct link to Example 1: Dynamic Dialog Component") Let’s take another example of a reusable dialog component that uses callbacks to handle context-specific actions like confirming a deletion, logging out, or saving data. In one context, "Yes" deletes an item. In another, it logs out a user. The specific logic for each action is defined by the parent component or page using the dialog. The dialog itself does not need to know what should happen—it simply executes the callback passed to it when users click the "Yes" button. ![dialog-component-action-parameters.avif](/assets/images/dialog-component-action-parameters-c5a8e657eb4d1524754afd2dcad0d02a.avif) ### Example 2: Custom Navigation Bar in Super App[​](/resources/ui/components/callbacks.md#example-2-custom-navigation-bar-in-super-app "Direct link to Example 2: Custom Navigation Bar in Super App") Using action parameters to build a custom navigation bar in a super app is an excellent way to create a dynamic, reusable, and modular navigation solution. A **super app** typically hosts multiple mini-apps or features, each requiring specific navigation logic. Action parameters allow you to define navigation behavior dynamically, depending on the active context, making it perfect for this scenario. Here, the navigation bar doesn’t require hardcoded routes. Instead, the navigation logic can be customized for each mini-app, allowing the navigation bar to remain focused solely on its UI role. For example, in an **ecommerce mini-app**, the home button navigates to the product listing page, while the main (middle) button opens the shopping cart. In contrast, in a **cab booking mini-app**, the home button navigates to the dashboard, and the main (middle) button opens the quick booking page. ![navigation-bar-action-parameters.avif](/assets/images/navigation-bar-action-parameters-3e8f6b8fa1a943ec9121ba2126f91386.avif) --- # Child Widget Child Widget allows you to create reusable components while keeping part of the layout flexible. Instead of building multiple variations of the same component, you define a fixed structure and leave a specific area open for customization. * Its position is fixed within the component layout. * It accepts any widget, such as text, buttons, images, custom widgets, and components. * Each component instance can contain different content. * The overall structure of the component remains consistent This allows you to reuse the same component while adapting its content as needed. ![child-widgets.avif](/assets/images/child-widgets-9dc2d6e4e3b18a124a96dad258399b38.avif) Common use cases * Dashboard cards with different content (charts, stats, lists) * Settings rows with different controls (toggle, dropdown, button) * Empty state sections with different actions * Feature or onboarding cards with varying content ## Using Child Widget[​](/resources/ui/components/child-widget.md#using-child-widget "Direct link to Using Child Widget") Let’s see how to use the Child Widget by building a simple example of displaying different controls in a settings row. 1. In your Component, add a new parameter and give it a clear name (e.g., `childWidget`). 2. Set the parameter **Type** to **Child Widget**. 3. In the component layout, add a **Child Widget placeholder** where you want dynamic content to appear. 4. Go to the component instance (the place where you add this component), locate the Child Widget area, and add any widget to it. ### Child Widget vs Widget Builder Parameter[​](/resources/ui/components/child-widget.md#child-widget-vs-widget-builder-parameter "Direct link to Child Widget vs Widget Builder Parameter") Both options let you insert custom UI into a component, but they are designed for different workflows. One focuses on visual flexibility, while the other focuses on structured and scalable component design. **Child Widget**: A Child Widget allows you to drag and drop any widget directly into a component instance. It does not require setup from the component creator and is handled entirely in the visual editor. Each instance can have different content, making it ideal for quick, flexible customization. [**Widget Builder Parameter**](/resources/ui/components/widget-builder.md): A Widget Builder Parameter is defined by the component creator and lets you pass UI into a component as a parameter. It works like a function input, providing a more structured and controlled way to customize components, especially for reusable and scalable designs. ### Best Practices[​](/resources/ui/components/child-widget.md#best-practices "Direct link to Best Practices") * Place the Child Widget in a predictable area of the layout, such as a trailing section, content block, or action area. Avoid placing it in positions that affect the overall structure (e.g., between tightly coupled layout elements). * Keep the role of the Child Widget clear. It should represent a specific purpose, such as "action area" or "content area", not a random insertion point. * Avoid adding too many Child Widget placeholders in a single component, as it can make the component harder to understand and use. * Test the component with different widget types (small, large, interactive) to ensure the layout remains stable across variations. ### Limitations[​](/resources/ui/components/child-widget.md#limitations "Direct link to Limitations") * The Child Widget position is fixed inside the component. You cannot move or reposition it differently for each instance. * It does not enforce any structure on what is inserted, so inconsistent widgets across instances can lead to inconsistent UI if not carefully designed. * It is not ideal for highly dynamic, repeated layouts such as product lists or grids, where content is driven entirely by data. * It relies on manual placement per instance, which can be less efficient for larger or system-driven designs. --- # Component Actions & Lifecycle In FlutterFlow, understanding the component lifecycle is crucial for managing state and optimizing your app's performance. Let's delve into the key moments in the lifecycle of a **Component**: * **Creation**: Component instances are created dynamically when they are used within a page or another component. This means that component instances are created as needed, which helps manage resources efficiently and avoid unnecessary overhead. * **Initialization:** Actions defined in the `On Initialization` **Action Trigger** are executed during this phase. For instance, you can initialize local state variables with initial values, or start component animations in this phase. At this stage, component state variables with their default values (if any) are also created. These variables hold data specific to the component, such as form inputs or toggle states, and are essential for managing the component’s internal state. * **Updating:** While in use, the component can receive updated parameters from its parent when the parent rebuilds itself, allowing the component to adjust its behavior and appearance accordingly. When updating state variables inside a component, you can choose to rebuild only the component itself or the entire page containing the given component. This dynamic updating is crucial for maintaining a responsive and interactive user experience. * **Disposal:** When the component is no longer needed, such as when a user navigates away from the page or the component is explicitly removed, it is destroyed. In FlutterFlow, most of these lifecycle stages are handled internally by FlutterFlow's architecture. However, FlutterFlow exposes some lifecycle methods so that you, as a developer, can decide what additional configurations to load upon initialization and when to re-render the UI based on interactions. Let's look at them in the following sections: ## Initialization Action Triggers[​](/resources/ui/components/component-lifecycle.md#initialization-action-triggers "Direct link to Initialization Action Triggers") During the initialization of a **Component**, FlutterFlow exposes the `On Initialization` **Action Trigger** that assists you in loading resources or initializing data when the Component is loaded in a Page or a Component. What are Action Triggers? **Action Triggers** serve as event listeners or handlers that respond to specific events or user interactions within an application. FlutterFlow provides developers with a way to define logic that responds to various events, such as button clicks, page loads, form submissions, or data changes. To learn more, see the [**Action Flow Editor**](/resources/functions/action-flow-editor.md) section. As you open the Action Flow Editor for your Component, you can see the `On Initialization` **Action Trigger** exposed for your **Component**. ### On Initialization \[Action Trigger][​](/resources/ui/components/component-lifecycle.md#on-initialization-action-trigger "Direct link to On Initialization \[Action Trigger]") The `On Initialization` action trigger in FlutterFlow allows you to define actions that should occur when a component loads or is initialized, such as setting up necessary data, state variables, or other initialization tasks. If the component stops being shown in the UI and then becomes visible again, the actions under the **On Initialization** action trigger will run again so any setup tasks are re-executed. For dynamically generated components, such as those in a ListView with a query, each instance will trigger the actions under `On Initialization` action trigger when it is created. ### On Shortcut Press \[Action Trigger][​](/resources/ui/components/component-lifecycle.md#on-shortcut-press-action-trigger "Direct link to On Shortcut Press \[Action Trigger]") Your component can also respond to certain keypress events. For more details on setting this up, see [this section on keyboard shortcuts](/resources/ui/pages/page-lifecycle.md#on-shortcut-press-action-trigger). ### On Dispose \[Action Trigger][​](/resources/ui/components/component-lifecycle.md#on-dispose-action-trigger "Direct link to On Dispose \[Action Trigger]") The **On Dispose** action trigger for components allows you to define actions that execute when the page containing the component is navigated away or removed from memory. It is particularly useful for stopping ongoing operations. Imagine a scenario where a [periodic action](/resources/time-based-logic/periodic-action.md), such as fetching live weather updates, is started in a component when it is loaded (i.e., [On Initialization](/resources/ui/components/component-lifecycle.md#on-initialization-action-trigger)). The action runs periodically, providing real-time data updates as long as the component is active. However, when the page containing the component is navigated away, you need to stop the periodic action to conserve resources and prevent unnecessary processing. By using the **On Dispose** action trigger, you can safely stop the periodic updates and clean up any associated resources. info The **On Dispose** action trigger always runs before the [**parent page’s On Dispose**](/resources/ui/pages/page-lifecycle.md#on-dispose-action-trigger). This ensures that the component cleans up its resources first, allowing the parent to finalize its disposal without dependencies on the child. ## Component State[​](/resources/ui/components/component-lifecycle.md#component-state "Direct link to Component State") STATE VARIABLES A state variable holds information or data about your UI at any given moment. To learn more about **states and state management, [refer to this guide.](/concepts/state-management.md)** **Component state** refers to the information that a component tracks about its current condition or the data it manages internally. This can include data such as whether a button is enabled, the value of a slider, or the entries in a dynamically updated list. Component state variables are only accessible within the current component's scope. This type of variable is particularly useful for storing data that affects how the component behaves or appears, such as toggling UI elements, keeping track of user choices within the component, or caching data pertinent to the component's functionality. For example: * In a custom drop-down menu component, you might use a component state variable to keep track of which item is currently selected. * In a toggle switch component, you could use a component state variable to store the on/off state based on user interaction. This approach ensures that the state of the component is maintained as it interacts with the user or other parts of the application. When a component state variable changes, the component can be re-rendered with the updated values, displaying the latest state of the component with these updates. ### Creating a Component State[​](/resources/ui/components/component-lifecycle.md#creating-a-component-state "Direct link to Creating a Component State") To create a new **Component State variable** in your component, follow these steps: [Create Component State](https://demo.arcade.software/nEmCDqupF7YHUTi4hKvW?embed\&show_copy_link=true) When creating Component State, the following properties are included: * **Is List:** This property determines whether the variable can hold multiple values of the same data type (like a list or array) or just a single value. * **Initial Field Value:** This property sets the default value for the variable when it is first created. It's like setting the starting point or the value that the variable begins with before anything else happens. * **Nullable:** This property determines whether the variable can have a null value. When "**Nullable**" is set to true, it means the variable can be empty or have a null value. This is useful when dealing with optional data or scenarios where the absence of a value is valid. Now, apply these concepts to the `isFavourite` variable in the context of the above example: * For the `isFavourite` variable, it is a single value (boolean), so **Is List** would be set to false. * Set the **Initial Field Value** to **false**, indicating that the item is not favorited by default. * Set the **Nullable** property to false, as the variable should always have a boolean value (true or false) and never be null. note You can set the **Data Type** of your Component State variable to primitive data types such as **String, Integer, Boolean,** or **Double**, or complex built-in data types such as **Enum, Custom Data Type,** or **Document**. To learn more about the available data types, refer to the [**Data Representation section**](/resources/data-representation.md). ### Get Component State Value[​](/resources/ui/components/component-lifecycle.md#get-component-state-value "Direct link to Get Component State Value") In the following example, we demonstrate how to toggle the heart icon from an outlined to a filled icon based on the `isFavourite` state variable. We introduce a `Conditional Builder` widget that allows us to show a widget tree based on **If/Else If/Else** conditions. The goal is to visually indicate whether a product has been favorited by the user. Follow these steps: [Get Component State](https://demo.arcade.software/Y96decdgYWVll3SP9Jk8?embed\&show_copy_link=true) ### Update Component State \[Action][​](/resources/ui/components/component-lifecycle.md#update-component-state-action "Direct link to Update Component State \[Action]") **Component state** values can only be updated via actions. Whenever you want to update the component state, add an **Update Component State** action from the Action Flow Editor of the component. In the following demo, we open the Action Flow Editor on the parent widget `Conditional Builder` and call the **Update Component State** action to toggle the value of `isFavourite`. [Get Component State](https://demo.arcade.software/4tEsyMFyCxEP1tWQcPVh?embed\&show_copy_link=true) #### Rebuild on Update[​](/resources/ui/components/component-lifecycle.md#rebuild-on-update "Direct link to Rebuild on Update") When updating your component state in FlutterFlow, you'll often come across the **Update Type** property in your action properties. Here's what it means: * **Rebuild Containing Page:** This option triggers a re-rendering of the page containing this component. * **Rebuild Current Component:** This option triggers a re-rendering of the current component only. * **No Rebuild:** Choose this option when you need to update the state value without immediately reflecting the changes in the UI. tip If you want to rebuild a component without updating any state variables, use the [**Rebuild**](/concepts/state-management.md#rebuild-action) state action. Expensive Rebuilds Too many rebuilds can impact performance because rebuilding the widget tree frequently consumes resources and may lead to decreased responsiveness and increased battery usage. Therefore, it's essential to consider the trade-offs and use rebuilds judiciously to maintain optimal app performance. To learn more about what happens behind the scenes, refer to the [**Generated Code: Components**](/generated-code/component-model.md) section. --- # Components Components are reusable widgets you create to meet the specific needs of your app. This approach ensures consistency, saves time, and simplifies maintenance across your project. ## Creating a Component from Scratch[​](/resources/ui/components/creating-components.md#creating-a-component-from-scratch "Direct link to Creating a Component from Scratch") To create a component from scratch, click the **Add Button** in the **Page Selector** or **Widget Tree** tab. Then choose **Add Component > Create Blank Component**. [Create Component From Scratch](https://demo.arcade.software/shoUH86rXsdpAxtlCOKq?embed\&show_copy_link=true) ## Convert to a Component[​](/resources/ui/components/creating-components.md#convert-to-a-component "Direct link to Convert to a Component") If you have already built a complex widget in your page, you can convert that entire widget into a component and reuse it throughout your app. To convert a complex widget into a reusable component, right-click on the root widget that contains the entire widget tree you want to convert, then select **Convert to Component.** [Convert into a component](https://demo.arcade.software/if0fCrWpn6wVDdcGbW0E?embed\&show_copy_link=true) ## Creating Component from Template[​](/resources/ui/components/creating-components.md#creating-component-from-template "Direct link to Creating Component from Template") FlutterFlow offers multiple popular templates for components across various use cases that you can apply to your project in seconds, saving time. [Create from template](https://demo.arcade.software/z4aoeN7TK0Zxp6EseLuD?embed\&show_copy_link=true) ## Generate with Designer[​](/resources/ui/components/creating-components.md#generate-with-designer "Direct link to Generate with Designer") You can quickly create a component with [FlutterFlow Designer](https://designer.flutterflow.io/) by describing what you want in natural language. Designer uses your description along with your project context, to build the component with relevant widgets. [Generate with Designer](https://demo.arcade.software/VAsk3ElbFb9ehAV2e94k?embed\&show_copy_link=true) ## Import from Figma Frame[​](/resources/ui/components/creating-components.md#import-from-figma-frame "Direct link to Import from Figma Frame") You can quickly turn your Figma designs into functional FlutterFlow components using **Import from Figma**. Provide a Figma Frame URL, and FlutterFlow AI will analyze the design and generate a UI layout that closely matches your mockup. To get started, first [connect your Figma account](/concepts/design-system.md#import-figma-theme). Then, when creating a new component, select **Import from Figma** from the available options. Paste the Figma Frame URL and click **Import**. FlutterFlow will display a preview of the selected frame. Review the preview, then click **Generate** to create the component. Once completed, the component will appear in the **AI Generation History**, where you can preview and add it to your project. warning Currently, FlutterFlow doesn't support importing SVG elements from Figma frames. However, you can manually add the SVGs directly to your project [**assets**](/generated-code/project-structure.md#assets) after generation is complete, or replace them in Figma with supported image formats like PNG or JPEG. [Import from Figma](https://demo.arcade.software/V4kUtFFezchW03HIeqyY?embed\&show_copy_link=true) ## Component Properties Panel[​](/resources/ui/components/creating-components.md#component-properties-panel "Direct link to Component Properties Panel") When you select a component from the widget tree, the Properties panel opens on the right side of the interface. Use it to configure and manage the various aspects of your components. Here’s what you can typically find and modify in this panel: ![components-configurations.png](/assets/images/components-configurations-92b76049a4a278d21dfad82454a6f149.png) ## Component Parameters[​](/resources/ui/components/creating-components.md#component-parameters "Direct link to Component Parameters") Component parameters are values that a component receives from its parent entity, such as a page or another component. These parameters allow the component to be dynamic and adaptable based on the context in which it is used. By using parameters, you can customize components for different scenarios without altering the base design or functionality. ### Creating a Component Parameter[​](/resources/ui/components/creating-components.md#creating-a-component-parameter "Direct link to Creating a Component Parameter") To create a component parameter, go to the root widget in the component's widget tree. [Adding a Parameter](https://demo.arcade.software/chgEkWJpUFAIUzoB0LuG?embed\&show_copy_link=true) ### Bind the Parameter[​](/resources/ui/components/creating-components.md#bind-the-parameter "Direct link to Bind the Parameter") Once you have created a component parameter, you can link data from the parent entity to your component. Here's a small example of how we can bind the parameters created in `ProfileListItem` to their respective widgets and action triggers. [Bind Parameters in Components](https://demo.arcade.software/ixR32sxe5W97bEaS1hTt?embed\&show_copy_link=true) Aside from standard data types used throughout FlutterFlow, you can also create parameters of the following types: * **Action (callback)**: This allows you to pass actions into the component. The component can then invoke the action, usually referred to as a callback, in its own action flows. Callbacks are often used to handle events, like updating a parent's state when a button has been pressed. [You can learn more about how to use callbacks here.](/resources/ui/components/callbacks.md) * **Widget Builders**: Widget builders allows you to pass in widgets to be used within the component's widget tree. This is especially useful when you want to dynamically substitute content for part of a component, such as displaying an item in a custom dropdown, or creating a component for some consistent layout. [You can learn more about how to use Widget Builders here.](/resources/ui/components/widget-builder.md) ### Actions[​](/resources/ui/components/creating-components.md#actions "Direct link to Actions") This tab allows you to define and manage interactions or events triggered by user actions. For example, you can configure a button to navigate to another page or execute a callback action from the page using the current component. Adding an action to a component element is exactly the same experience as adding actions to any page element. Here's a quick overview: ![component-actions.png](/assets/images/component-actions-bd0cc8a019bc88f5efc73a08e46b7f6f.png) For component actions, you can establish specific behaviors or functions that are triggered by certain events related to the component's lifecycle, such as **On Initialization**. info To learn more about component lifecycle and adding **On Initialization** action to your component [**refer here.**](/resources/ui/components/component-lifecycle.md) ### State Management[​](/resources/ui/components/creating-components.md#state-management "Direct link to State Management") Components can have their own internal state variables that track information like form inputs, toggles, or other user interactions. Components can update their state in response to user actions (e.g., clicking a button) or external events (e.g., receiving new data from an API). Effective state management ensures that components dynamically update their UI to reflect changes in state, providing a responsive user experience. info Learn how to **[Create a State variable](/resources/ui/components/component-lifecycle.md#creating-a-component-state)** for your components and how to **[Update them](/resources/ui/components/component-lifecycle.md#update-component-state-action)**. --- # Using Components Components in FlutterFlow can be added to the widget tree of a page or another component. They help streamline development by allowing you to reuse design and functionality throughout your app. Components can accept parameters, making them adaptable to specific contexts. Additionally, you can use [callbacks](/resources/ui/components/callbacks.md) to pass actions from parent entities to child components, enabling dynamic and interactive behavior. You can also use [widget builders](/resources/ui/components/widget-builder.md) to substitute dynamic content into the component's widget tree. To learn more about creating components, see [Creating a Component](/resources/ui/components/creating-components.md). ## Add a Component to a Widget Tree[​](/resources/ui/components/using-components.md#add-a-component-to-a-widget-tree "Direct link to Add a Component to a Widget Tree") To add a component to the widget tree of a page or another component, choose the parent entity where you want to add the new component. Next, you can find the component in the Widget Palette, under the **Components** section. [Add component to Page](https://demo.arcade.software/EBpdB2PtNGPGzKh7O2eQ?embed\&show_copy_link=true) ### Specify Parameter Values[​](/resources/ui/components/using-components.md#specify-parameter-values "Direct link to Specify Parameter Values") In FlutterFlow, each component instance can receive unique values from its parent entity. When you add a component to the widget tree, you can set the parameter values by clicking on the instance of the component and going to the **Properties panel**. [Pass Down Values](https://demo.arcade.software/t4r4TKLGrRvdthCZYdvm?embed\&show_copy_link=true) ## Setting a Unique Key[​](/resources/ui/components/using-components.md#setting-a-unique-key "Direct link to Setting a Unique Key") When you use a component in a dynamically generated list, you can set a unique key. For example, imagine a dynamic list where items change frequently, such as a to-do list where tasks are added and removed. Think of it as giving each task a unique ID number. This is important for a few reasons: * **Tracking Changes:** The **Unique Key** helps the app recognize which tasks are new, completed, or removed, ensuring accurate updates. * **Efficiency:** With unique IDs, the app updates only the tasks that have changed instead of the entire list, improving performance. * **Retaining Details:** When you modify a task and move away from it, the **Unique Key** ensures the changes are remembered and displayed correctly when you return. tip If it's a list of documents, the unique key might be the document ID. ![component-unique-id.avif](/assets/images/component-unique-id-03e509dec30fb19e7da5eed06d683939.avif) ## Recursive Components[​](/resources/ui/components/using-components.md#recursive-components "Direct link to Recursive Components") You can create a recursive component, which means the component can include an instance of itself within its own widget tree. This is especially useful for nested content. For example, in social media applications or forums, comments can have replies, and each reply can have further replies. A recursive component can display this nested structure effectively. ![recursive-comp.png](/assets/images/recursive-comp-13c03bf3a4f9d465909348ae5e025ad1.png) --- # Widget Builder Parameters Sometimes, you want to create a component that offers some consistent design, while also allowing for customization. This is where passing widget builders as parameters becomes valuable. Widget builder parameters allow component authors to substitute dynamic content within the widget tree of the component. This means that when someone uses the component, they can dynamically pass in pieces of UI to be used within the component. For example, consider a custom dropdown component. While the overall structure of the dropdown remains the same, you might need to change the style or content of the dropdown items based on different use cases. By passing the dropdown item widget as a parameter, you can reuse the dropdown's appearance and behavior without creating new components for each variation. Possible use cases * **Custom Cards**: Imagine you need to display product cards in an e-commerce app. You can build a reusable card component with parameters for the image, header, content, and call-to-action button. This card can be reused across multiple pages but with different content. * **Dynamic Forms**: Build a form component where different fields (TextFields, Dropdowns, or Checkboxes) are passed in as parameters. This allows you to reuse the same form structure but adapt to various input fields. * **Modular Layouts**: Create a consistent layout structure with areas like headers and footers that remain the same while passing in different body content as parameters to adapt to different pages. Let’s see an example from an ecommerce app. On the shipping address page, you may want to maintain a consistent design for the various input fields (where the user can specify their name, email, etc.). However, you may want to allow customization for different inputs - for example, you want to use a `TextField` to allow the user to type their name, and a `DropDown` to allow the user to select their country. ![widget-builder-as-parameter-example.avif](/assets/images/widget-builder-as-parameter-example-1bf822ec372b4fbb9f3305d6c7d0932d.avif) ## Creating Widget Builders as Parameters[​](/resources/ui/components/widget-builder.md#creating-widget-builders-as-parameters "Direct link to Creating Widget Builders as Parameters") To create a component with a widget builder as a parameter, use the steps outlined below. ### Create a Parameter of Type Widget Builder[​](/resources/ui/components/widget-builder.md#create-a-parameter-of-type-widget-builder "Direct link to Create a Parameter of Type Widget Builder") Create a new component and add the base widgets that will be unchanged. Next, define a parameter and set its type to **Widget Builder**. To pass data from the current component to the widget builder, you can specify a parameter for the widget builder. ### Add the Widget Builder to the Widget Tree[​](/resources/ui/components/widget-builder.md#add-the-widget-builder-to-the-widget-tree "Direct link to Add the Widget Builder to the Widget Tree") Add the widget builder placeholder to the desired spot in the component’s widget tree where the dynamic element should appear. Widget builders appear in the **Components** section of the **Widget Palette** when adding a widget to the widget tree. ### Pass Parameters to the Widget Builder[​](/resources/ui/components/widget-builder.md#pass-parameters-to-the-widget-builder "Direct link to Pass Parameters to the Widget Builder") Sometimes, you need to pass data from the component to the widget builder. For example, on the shipping address page, you might want the hint text in an input field to change depending on some configuration. In this case, you can pass the hint as a parameter into the widget builder. Here’s how you do it: #### Preview the Widget Builder Using Different Components[​](/resources/ui/components/widget-builder.md#preview-the-widget-builder-using-different-components "Direct link to Preview the Widget Builder Using Different Components") You can select different components to use as a preview while building the component that has a widget builder parameter. To select a component to use in the preview, select the Widget Builder, then go to the **Widget Builder UI Properties** section of the **Properties panel**. ![preview-component.png](/assets/images/preview-component-7f90a152b97333c3d433330463decaa5.png) ## Using Components with Widget Builders as Parameters[​](/resources/ui/components/widget-builder.md#using-components-with-widget-builders-as-parameters "Direct link to Using Components with Widget Builders as Parameters") When you use a component that has a widget builder parameter, you can pass [components](/resources/ui/components.md) to customize the content according to your needs. In this example, we create two additional components for `TextField` and `Dropdown` — and pass them as widget builders. --- # UI Building Blocks When designing user interfaces in FlutterFlow, understanding the fundamental building blocks—ranging from atomic to more complex structures—is crucial. The way UI is structured in FlutterFlow closely resembles the concept of **Atomic Design**, a methodology that segments UI into distinct levels of complexity. In **Atomic Design**, we start with the smallest, indivisible components known as "atoms"—these are your basic building blocks. From there, we combine these atoms to form "molecules," which then come together to create "organisms" or larger functional units. By applying this hierarchical structure to FlutterFlow, we streamline the UI development process, making it both efficient and manageable. Now, let’s explore how this structured approach plays out in FlutterFlow, from the simplest elements to the creation of full-fledged interfaces: * **Atoms** * These are the fundamental building blocks that serve as the foundational elements of the UI. * **Example:** `TextField`, `Button`, `Icon`. * **Molecules** * These are groups of atoms bonded together and are the smallest fundamental units of a compound. These form the basic building blocks of pages but can often be used on their own. * **Example:** `EmailSignInField` (which could include an `TextField` atom and an `Icon` atom). * **Organisms** * These are groups of molecules joined together to form a relatively complex, distinct section of an interface. * Example: `LoginComponent` (which could include the `EmailSignInField` molecule, another similar `PasswordSignInField` molecule, and a `SubmitButton` atom). * **Pages** * Pages are complete screens and represent the final visible output that users interact with. They are composed of smaller units that work together to provide a full experience, including all the necessary functionality and design elements. * **Example**: `SignInPage` Now let's apply the above concepts to what we see in FlutterFlow as we create our first [project](/resources/projects.md). ## Pages[​](/resources/ui/overview.md#pages "Direct link to Pages") In FlutterFlow projects, a **Page** is essentially a new section or feature of your app that combines various UI elements to form a complete screen in the app. When you create a new project in FlutterFlow, an empty page called `HomePage` is the first thing you see on your canvas. How you define your pages defines the flow of the app and user experience for the user. For example, in our [**E-commerce Demo app**](https://bit.ly/ff-docs-demo-v2), after login, the user lands on `ProductListPage` which has a NavigationBar at the bottom that takes the user to different Pages in the app such as `ProfilePage`, etc. info Learn more about creating a new [**Page**](/resources/ui/pages.md) and using its [**Page Elements**](/resources/ui/pages/scaffold.md) like AppBar, Drawer, etc. ## Widgets[​](/resources/ui/overview.md#widgets "Direct link to Widgets") A Page usually contains a combination of widgets and components. ![everything-widget.png](/assets/images/everything-widget-e85b2e0546ad6daf4a5eb16ccff5dac3.png) Let's talk about widgets first, which are the atomic elements or building blocks of the UI structure in FlutterFlow. Each widget can be thought of as an atom or a molecule, depending on its complexity and its parent-child relationship. For example, an atomic widget (such as `TextField`) cannot hold a child element, but molecular widgets (such as `Column` or `Row`) can. info Learn more about the [**basic widgets**](/resources/ui/widgets.md) and how to [**compose widgets**](/resources/ui/widgets/composing-widgets/rows-column-stack.md) to build more complex UI. ## Components[​](/resources/ui/overview.md#components "Direct link to Components") In the idea of atomic design, components in FlutterFlow are similar to "organisms." These organisms are made up of simpler parts called *atoms* and *molecules*, or simply widgets, which together form useful and reusable parts of the user interface. These components are designed to be reusable, meaning they can be utilized across different screens and projects to provide consistent functionality and aesthetics without the need to recreate them from scratch everytime. info Learn more about [**components**](/resources/ui/components.md) and [**how to use them**](/resources/ui/components/using-components.md) in pages. ## Classes vs Instances[​](/resources/ui/overview.md#classes-vs-instances "Direct link to Classes vs Instances") When you add a UI element to your page, you are utilizing widget **classes** and creating **instances** of them. For example, `Icon` is a **widget class**. When you use it in different parts of your application, you're creating an **instance** of the `Icon` widget class and providing different values to it for each use. Think of classes as templates that outline the structure and features of something you want to create multiple times. For instance, in our demo app [EcommerceFlow](https://bit.ly/ff-docs-demo-v2), we have a reusable component called `ProductListCard` with specific characteristics such as image, product information text, and actions it should perform when clicked. Here, we've essentially created a **class**. When you place this `ProductListCard` in different Pages of your app, each one you add is an `instance`. For example, in the `ProductListPage`, we have created an **instance** called `topSellingProductCard` for use in the Top Selling section. Similarly, in the `CategoryProductListPage`, we've created an **instance** called `categoryProductCard`. ![Class-Instance.png](/assets/images/Class-Instance-92597e040b9cfb524243eb8631e8a6d1.png) You can customize each **instance** of your component to perform different actions or to fit different parts of your app, but they all start from the template you created (**the class**). This means you only need to design the `ProductListCard` once and then can reuse and adapt it as needed, simplifying your app development process and ensuring consistency across your project. --- # Introduction to Pages In FlutterFlow, a **Page** represents a single screen in your app. Under-the-hood pages use a **Scaffold**, a [foundational widget from Flutter](https://api.flutter.dev/flutter/material/Scaffold-class.html) that provides a structured layout for a screen within your app. The Scaffold offers essential elements like the AppBar and Body, allowing you to easily build screens. Pages are composed of various UI elements, or widgets. Widgets are added to a page when they are added to the page's **Widget Tree**. Widget Tree The **Widget Tree** is a structural representation of how widgets are organized within a Page. To learn more, check out the [**Widget Overview**](/resources/ui/widgets.md#widget-tree) documentation. In FlutterFlow, pages are automatically configured to handle [routing](https://docs.flutterflow.io/resources/ui/pages/properties#route-settings). Additionally, pages can have [input parameters](https://docs.flutterflow.io/resources/ui/pages/properties#page-parameters) and [state variables](https://docs.flutterflow.io/resources/ui/pages/page-lifecycle#page-state). info For more details on how to use Scaffold and the various Page Elements in FlutterFlow, see the dedicated **[Page Elements](/resources/ui/pages/scaffold.md)** guide. ## Creating a Page[​](/resources/ui/pages.md#creating-a-page "Direct link to Creating a Page") In FlutterFlow, you can craft a page tailored to your needs and design preferences. Whether you're starting from scratch, using a template, or leveraging AI tools, there are several pathways to achieve the desired functionality and aesthetic of your desired Page. Generated Code When you create a page in FlutterFlow, a `Widget` class and a corresponding `Model` class are automatically generated. You can view these in the Code Viewer. To explore the details of the generated `Model` class, take a closer [**look at the code**](/generated-code/page-model.md). FlutterFlow allows you to easily create new pages from the **Page Selector** tab in the **Navigation Menu**. ![create-new-page.avif](/assets/images/create-new-page-a5d5b49373456a2bf75da5365f3fbd77.avif) ### Create Empty Page[​](/resources/ui/pages.md#create-empty-page "Direct link to Create Empty Page") When creating your page in FlutterFlow, one option is to start with an empty page, providing you with a blank canvas. This approach allows you to build your UI from the ground up by composing widgets and components together according to your specific design vision and functional requirements. To create an empty FlutterFlow Page from scratch, follow these steps: ### Create Page from Template[​](/resources/ui/pages.md#create-page-from-template "Direct link to Create Page from Template") FlutterFlow simplifies the process of page creation by offering a variety of popular template use cases. These templates provide a basic structure for your pages, which you can quickly customize with your own styling, widgets, and text. To utilize a template from FlutterFlow, follow these steps: [Create a page from a popular template](https://demo.arcade.software/JBhxcBBPb7r1Yk6YwehS?embed\&show_copy_link=true) ### Generate with Designer[​](/resources/ui/pages.md#generate-with-designer "Direct link to Generate with Designer") You can quickly create a page with [FlutterFlow Designer](https://designer.flutterflow.io/) by describing what you want in natural language. Designer uses your description along with your project context, to build the page with relevant widgets. This is especially helpful when you're starting from scratch or prototyping ideas rapidly. [Generate with Designer](https://demo.arcade.software/oRmGZOkvdnM844VZfHLq?embed\&show_copy_link=true) ### Import from Figma Frame[​](/resources/ui/pages.md#import-from-figma-frame "Direct link to Import from Figma Frame") You can quickly turn your Figma designs into functional FlutterFlow pages using **Import from Figma**. Simply provide a Figma Frame URL, and FlutterFlow AI will analyze the design and generate a UI layout that closely matches your mockup. To get started, first [connect your Figma account](/concepts/design-system.md#import-figma-theme). Then, when creating a new page, select **Import from Figma** from the available options. Paste the Figma Frame URL and click **Import**. FlutterFlow will display a preview of the selected frame. Review the preview, then click **Generate** to create the page. Once completed, the page will appear in the **AI Generation History**, where you can preview and add it to your project. warning Currently, FlutterFlow doesn't support importing SVG elements from Figma frames. However, you can manually add the SVGs directly to your project [**assets**](/generated-code/project-structure.md#assets) after generation is complete, or replace them in Figma with supported image formats like PNG or JPEG. --- # Page Lifecycle In FlutterFlow and Flutter, understanding the page lifecycle, or the stages a page goes through from creation to disposal, is essential for managing resources and data effectively. Let's delve into the key moments in the lifecycle of a **Page**: * **Initialization**: This is the first phase where the page is set up. Here, the initial data is loaded. This might involve setting up the necessary state or defaults for the page. * **Rendering**: Here, the page is actually drawn or rendered on the screen. This includes setting up the layout, styles, and any interactive elements. The user can now see the page in its initial [state](/resources/ui/pages/page-lifecycle.md#page-state). * **Updating:** After rendering, the page becomes interactive and can respond to user inputs such as clicks, typing, or other gestures. It may re-render parts of the page or the entire page to reflect changes from user interaction or new data. * **Disposal**: When the page is no longer needed, or the user navigates away, this phase is triggered. This is where resources related to the page are released from memory. In FlutterFlow, most of these lifecycle phases are handled internally by FlutterFlow's architecture. However, FlutterFlow exposes some lifecycle methods so that you, as a developer, can decide what additional configurations to load upon initialization and when to re-render the UI based on interactions. ## Page-Level Action Triggers[​](/resources/ui/pages/page-lifecycle.md#page-level-action-triggers "Direct link to Page-Level Action Triggers") There are several **[Action Triggers](/resources/functions/action-flow-editor.md#action-triggers)** that are accessible at the root level of a page. What are Action Triggers? **Action Triggers** serve as event listeners or handlers that respond to specific events or user interactions within an application. FlutterFlow provides developers with a way to define logic that responds to various events, such as button clicks, page loads, form submissions, or data changes. To learn more, see the **[Action Flow Editor](/resources/functions/action-flow-editor.md)** section. As you open the [Action Flow Editor](/resources/functions/action-flow-editor.md) for your Page, you can see the following Action Triggers exposed for your Page. ![actions-triggers.png](/assets/images/actions-triggers-e7a59d7f7e2da33c600e2286251c6dee.png) ### On Page Load \[Action Trigger][​](/resources/ui/pages/page-lifecycle.md#on-page-load-action-trigger "Direct link to On Page Load \[Action Trigger]") This allows you to set actions when the page loads or initializes. It enables developers to perform tasks or execute logic at specific points in the page lifecycle, such as fetching data from an API, initializing variables, or updating UI elements. Possible use cases * **Initializing Data:** You can use the **On Page Load** action trigger to initiate API calls, database queries, or read from local storage, setting up the data that the page needs to display. This ensures that all necessary data is ready and available by the time the user sees the page. * **Setting State:** If your page depends on certain state conditions (like toggles, selections, or input fields), you can set these states appropriately as the page loads. * **Running Animations:** Start animations that welcome users or draw attention to certain UI elements on the page. To add an action to **On Page Load** action trigger, follow the steps: [app.flutterflow.io/authentication](https://demo.arcade.software/ii0otHqkoRtPY66n4c2y?embed\&show_copy_link=true) Generated Code When you add actions to the **on Page Load** action trigger, they are executed within a `SchedulerBinding.instance.addPostFrameCallback((_)` method. This ensures that the actions run after the widget tree is fully built. For more details, refer to the [**Page: Generated Code**](/generated-code/page-model.md#onpageload-action-generated-code) document. ### On Phone Shake \[Action Trigger][​](/resources/ui/pages/page-lifecycle.md#on-phone-shake-action-trigger "Direct link to On Phone Shake \[Action Trigger]") Actions added under this trigger run when the user shakes their phone. This is useful when you want to perform certain tasks or trigger specific actions in response to a phone shake gesture. Possible use cases * **Randomizing content:** Shake the phone to generate a random number, display a random quote, or change the background image. * **Refreshing data:** Shake the phone to trigger a data refresh, such as fetching the latest news articles or updating a live feed. * **Resetting the app state:** Shake the phone to reset the app state, clear form fields, or return to the app's home screen. ### On Shortcut Press \[Action Trigger][​](/resources/ui/pages/page-lifecycle.md#on-shortcut-press-action-trigger "Direct link to On Shortcut Press \[Action Trigger]") This action trigger lets you bind keyboard shortcuts to actions. This is incredibly helpful for improving accessibility and enhancing user experience, especially in web and desktop apps. Possible use cases * **Create New Issues in Project Management Apps:** In project management apps like Linear, users can press `C` to quickly open a form for creating a new issue or task. * **Form Submission:** Users can press a key combination (e.g., `Ctrl + Enter`) to submit a form. * **Navigating Between Pages:** Use shortcuts like `Ctrl + Right Arrow` to navigate between pages without using the mouse. important * When a keyboard shortcut is created at the page level, it won't trigger if a TextField is in focus, and you also won't be able to type the shortcut key into the TextField. * When a keyboard shortcut is created at the component level, it also won't trigger if a TextField is in focus, but you'll still be able to type the shortcut key into the TextField. * **To avoid conflicts, use shortcuts that users are unlikely to type, such as Command + S, instead of a single key like 'S'.** * There's currently a known issue with Flutter's autofocus functionality. If a TextField inside a component has autofocus enabled, and the component has a keyboard shortcut, the TextField will not autofocus as expected. Implementing keyboard shortcuts is a straightforward process in FlutterFlow. You can define as many shortcuts as you want, each mapped to specific actions that will trigger when the corresponding key combination is pressed. Let's see an example of an eCommerce web app where users can quickly access the cart page by pressing the `C` key. To create a shortcut, use the **On Shortcut Press** action trigger, then enter the keys your app should listen for. Keyboard Shortcuts & Text Fields When implementing keyboard shortcuts on a page or component with a text field, you may need to ensure the text field ignores those shortcuts. For instance, if you have a shortcut assigned to the letter "C" and a user tries to type "C" in the text field, you likely want the input to capture the keypress without triggering the shortcut. To handle this, you can enable the option on the `TextField` widget to bypass keyboard shortcuts. However, it's generally better to assign more unique combinations, like Cmd + C, which are less likely to conflict with normal typing in a text field. ### On Dispose \[Action Trigger][​](/resources/ui/pages/page-lifecycle.md#on-dispose-action-trigger "Direct link to On Dispose \[Action Trigger]") The **On Dispose** action trigger allows you to define actions that execute when a page is navigated away from or removed from memory. It is particularly useful for stopping ongoing operations. Imagine a scenario where [audio recording](/concepts/file-handling/displaying-media.md#audio-recording) is started when the page loads using the [On Page Load](/resources/ui/pages/page-lifecycle.md#on-page-load-action-trigger) action trigger. The recording process runs as long as the user remains on the page. However, when the user navigates away, you need to stop the recording to save resources and ensure the recorded audio is finalized. By using the **On Dispose** action trigger, you can safely stop the recording and save the file. Additionally, if you are using a third-party package that relies on persistent connections or listeners, you can leverage [Custom Actions](/concepts/custom-code/custom-actions.md) with the On Dispose action trigger to close streams or cancel subscriptions. Possible Use Cases * **Cleaning Up Resources:** Use this action trigger to cancel timers, close database connections, or unsubscribe from streams to prevent memory leaks and unnecessary processing. * For example, real-time applications, such as stock trading platforms, rely on WebSocket connections to fetch live updates. A homepage displaying a live ticker of stock prices would require opening the WebSocket connection on page load and closing it when **On Dispose** runs. Without an On Dispose trigger, the WebSocket connection could remain open unnecessarily, leading to wasted resources and app instability. * **Finalizing Database Transactions**: Commit or roll back database transactions if the user leaves the page before completing the process. * **Logging or Analytics:** Track user behavior or log events (e.g., page exit or time spent on a page) to monitor user engagement and improve the application experience. ![page-on-dispose.avif](/assets/images/page-on-dispose-8025fe786790802aac8cd697c25dabfa.avif) ## Page State[​](/resources/ui/pages/page-lifecycle.md#page-state "Direct link to Page State") State Variables A state variable holds information or data about your UI at any given moment. To learn more about **states and state management, [refer to this guide](/concepts/state-management.md)** **Page state** refers to the information that a page tracks about its current condition or the data it displays. This can include things like which tabs are active, the contents of a form, or any user interactions. Managing state is crucial for dynamic pages that interact with user inputs or load varying data. Page State variables are only accessible within the given Page scope. This type of variable can be useful for storing data that needs to be shared between different widgets on the page, such as form data, a search query, and filtering or sorting options. For example, * In a multistep form, you might use a **Page State** variable to store the current step number or the user's input for each step. * Or, on a search results page, you could use a **Page State** variable to store the search query entered by the user and the current filtering and sorting options applied to the results. This allows you to maintain the state of the page as the user interacts with different widgets and components. When a **Page State** variable changes, you can choose to re-render the page with the updated values, and it will display a new version of the page with these updates. ### Creating a Page State[​](/resources/ui/pages/page-lifecycle.md#creating-a-page-state "Direct link to Creating a Page State") To create a new Page State variable on your page, follow the steps: [Create Page State](https://demo.arcade.software/Qhg62nqMjhg8973XPQhb?embed\&show_copy_link=true) While creating a Page State, the following properties are included: * **Is List:** This property determines whether the variable can hold multiple values of the same data type (like a list or array) or just a single value. * **Initial Field Value:** This property sets the default value for the variable when it is first created. It's like setting the starting point or the value that the variable begins with before anything else happens. * **Nullable:** This property determines whether the variable can have a null value. When "**Nullable**" is set to true, it means the variable can be empty or have a null value. This is useful when dealing with optional data or scenarios where the absence of a value is valid. Now, let's apply these concepts to the `searchString` variable in the context of the above example: * Since `searchString` is used to store a single search query entered by the user in the search bar, "**Is List**" is set to false, therefore it can hold only one value at a time. * The default value for `searchString` is set to an empty string (""). This ensures that when the homepage loads, the search bar is initially empty, allowing users to enter their search query. * Since entering a search query is optional and the search bar can be left empty, "**Nullable**" is set to true. This allows the `searchString` variable to be null until the user enters a search query, indicating that no search has been performed yet. note You can set the Data Type of your Page State variable to primitive data types such as **String, Integer, Boolean,** or **Double**, or complex built-in data types such as **Enum, Custom Data Type,** or **Document**. To learn more about the available data types, refer to the [**Data Representation Section.**](/resources/data-representation.md) ### Get Page State value[​](/resources/ui/pages/page-lifecycle.md#get-page-state-value "Direct link to Get Page State value") You can access the **Page State** value anywhere on the current page. Any widget can hold the current value of a Page State variable, either to display it in the UI or for transactional logic. You can set the source value of the widget wherever you see the following icon. This icon indicates that you can link the widget's value to a variable. ![Page-State.png](/assets/images/page-state-5f4d542a831409759613be0d2b79a1cf.png) ### Update Page State \[Action][​](/resources/ui/pages/page-lifecycle.md#update-page-state-action "Direct link to Update Page State \[Action]") Page State values can only be updated via **Actions**. Whenever you want to update the page state, such as through a button click, user interaction, or form update, add an **Update Page State** action. [Update Page State](https://demo.arcade.software/ezZO22YHQDqTHeg0uQ8Q?embed\&show_copy_link=true) #### Rebuild on Update[​](/resources/ui/pages/page-lifecycle.md#rebuild-on-update "Direct link to Rebuild on Update") When updating page state in FlutterFlow, you'll often come across the **Update Type** property in your Action properties. Here's what it means: **Rebuild Current Page:** This option triggers a re-rendering of the page, ensuring that any changes to the state are reflected in the user interface (UI). **No Rebuild:** Choose this option when you need to update the state without immediately reflecting the changes in the UI. tip If you want to rebuild a page without updating any state variables, use the [**Rebuild**](/concepts/state-management.md#rebuild-action) state action. Expensive Rebuilds Too many rebuilds can impact performance because rebuilding the widget tree frequently consumes resources and may lead to decreased responsiveness and increased battery usage. Therefore, it's essential to consider the trade-offs and use rebuilds judiciously to maintain optimal app performance. To learn more about what happens behind the scenes, refer to the [Generated Page](/generated-code/page-model.md) section. --- # Properties Panel In FlutterFlow, the Properties panel on the right helps you set up and manage your pages. It opens when you select the root element in the [Widget Tree](/resources/ui/widgets.md#widget-tree) (on the left). The panel is organized into sections, each focusing on different settings to customize your pages. Here’s what you can typically find and modify in this panel: ![page-properties-panel.png](/assets/images/page-properties-panel-290d268c5c4a072b2ed59dd7dfbe23b2.png) ### Page Parameters[​](/resources/ui/pages/properties.md#page-parameters "Direct link to Page Parameters") This section lets you define and manage parameters that your page can receive from other pages in the app. Parameters are essentially variables that hold values that can be passed between pages. For example, you might pass a user ID from a list page to a detail page to display specific information about that user. LEARN MORE Learn more about passing data between pages [**here**](/concepts/navigation/passing-data.md). ### Route Settings[​](/resources/ui/pages/properties.md#route-settings "Direct link to Route Settings") In FlutterFlow, Route Settings are essential for defining how pages within your application are accessed and interacted with. These settings allow you to customize the URL paths for web and mobile deep linking, set meaningful Page Names as unique identifiers, integrate dynamic parameters into your routes, and set access restrictions based on user authentication. ![route-settings-configs.png](/assets/images/route-settings-configs-4f679eabdb838bc859ca009c4a24ff21.png) **Skip On Page Load When Inactive** Ensures that actions are bypassed if the Entry Page or Logged In Page is detected as inactive. This is designed specifically for entry points in the app to prevent unnecessary operations when the page is not fully active, optimizing performance and avoiding redundant executions. Generated Code When this option is enabled, the following code is added to your page’s `initState`: ``` if (RootPageContext.isInactiveRootPage(context)) { return; } // On Page Load Actions added after this ``` **Requires Authentication** When the "Requires Authentication" option is enabled for a page, it ensures that only users who are logged in can access that page. This setting is particularly useful for protecting sensitive or personalized content, as it prevents unauthorized users from viewing or interacting with the page. Generated Code When the Route object is created for this Page, setting `requireAuth: true` ensures that only authenticated users can access this page. If "Requires Authentication" is checked, the app will automatically enforce authentication checks before navigating to this page. This is automatically enabled for **Logged In Page**. ``` FFRoute( name: 'promotionPage', path: '/promotionPage', requireAuth: true, builder: (context, params) => PromotionPageWidget(), ) ``` LEARN MORE Learn more about Routing [**here**](/concepts/navigation/overview.md). ## Advanced Configurations[​](/resources/ui/pages/properties.md#advanced-configurations "Direct link to Advanced Configurations") For more advanced customization and functionality within your FlutterFlow projects, the **Properties Panel** offers various configuration settings. These settings allow for modifying appearance, greater interactivity, dynamic data handling, and more tailored user experiences. Here's an overview of these additional configurations: * [Page Properties](/resources/ui/pages/properties.md#page-scaffold-properties) * [Actions](/resources/ui/pages/properties.md#actions) * [Backend Query](/resources/ui/pages/properties.md#backend-query) * [State Management](/resources/ui/pages/properties.md#state-management) ![advanced-configs.png](/assets/images/advanced-configs-5b53cb0e973f1bde7ae667ca3542f9ba.png) ### Page (Scaffold) Properties[​](/resources/ui/pages/properties.md#page-scaffold-properties "Direct link to Page (Scaffold) Properties") This section lets you set the fundamental aspects of a page’s layout and behavior, including: * **Background Color:** This property allows you to set a background color for the entire page. You can choose a color that fits the theme and design of your app. * **Safe Area:** When this toggle is enabled, the page content will be automatically adjusted so it does not overlap with the system status bar, navigation bar, and other critical device UI elements. This ensures that all elements of the page are visible and accessible on different devices. * **Hide Keyboard on Tap:** Enabling this option makes the keyboard retract when the user taps anywhere outside the keyboard area on the screen. This is particularly useful for improving user experience by preventing the keyboard from obscuring content. * **Disable Android Back Button:** When enabled, this toggle prevents the Android back button from affecting the navigation on this particular page. This can be useful in scenarios where you don't want users to navigate back to the previous screen easily, such as in a login or payment screen. ### Actions[​](/resources/ui/pages/properties.md#actions "Direct link to Actions") This section allows you to define and manage interactions or events triggered by user actions. For example, you can configure a button to navigate to another page, submit form data, or call an API. Actions are crucial for creating interactive and functional apps. For Scaffold (Page) actions, you can establish specific behaviors or functions that are triggered by certain events related to the page's lifecycle, such as [**On Page Load**](/resources/ui/pages/page-lifecycle.md#on-page-load-action-trigger) or [**On Phone Shake**](/resources/ui/pages/page-lifecycle.md#on-phone-shake-action-trigger). LEARN MORE To learn about the page lifecycle and other methods exposed by FlutterFlow, [**refer to this resource**](/resources/ui/pages/page-lifecycle.md). ### Backend Query[​](/resources/ui/pages/properties.md#backend-query "Direct link to Backend Query") Here, you can configure the page to fetch data from a backend source or database. This is typically done through API calls or direct database queries. Setting up a backend query allows the page to display dynamic content, such as user profiles, product lists, or any other data your app needs to retrieve from a server. LEARN MORE To learn more about how to connect to a backend source, refer to our [**Database section**](/resources/backend-query.md) ### State Management[​](/resources/ui/pages/properties.md#state-management "Direct link to State Management") State management configurations are essential for maintaining the state or status of a page across user interactions or app sessions. This can include tracking user inputs, remembering user choices, or preserving the app's state during navigation between pages. LEARN MORE Learn how to create and **[manage the update lifecycle](/resources/ui/pages/page-lifecycle.md)** of Page State variables. --- # Page Elements Page elements in FlutterFlow are the key elements that define the structure and functionality of each page in your app. Understanding these elements is crucial for building intuitive and effective user interfaces. From navigational elements like the **AppBar** and Drawer to interactive components like **Floating Action Buttons (FABs)**, each element plays a specific role in shaping the user experience. Here's how the `Scaffold` contributes to page design in FlutterFlow: * **[AppBar](/resources/ui/pages/scaffold.md#appbar)**: Scaffold allows you to easily include an `AppBar` at the top of the page, which can house the title, navigation controls, and other actions. * **[Floating Action Button (FAB)](/resources/ui/pages/scaffold.md#floating-action-button-fab)**: An action button that is commonly used for primary actions on the screen, like adding a new contact or composing a message. * **[Drawer & End-Drawer](/resources/ui/pages/scaffold.md#drawers)**: A slide-out menu for app navigation, accessible from the `AppBar` or by swiping from the side. * **Body:** The main content area where you place the widgets for the page. PLEASE NOTE In FlutterFlow, you won't find a section explicitly labeled as "Body". For example, in the `ProfileSettingsPage`, the `Column` serves as the root of the widget tree for the body, with the rest of the child widgets assembled underneath. ![scaffold-elements.png](/assets/images/scaffold-elements-91f1c9529e6bb438dd460b27b59dafa5.png) ## AppBar[​](/resources/ui/pages/scaffold.md#appbar "Direct link to AppBar") **AppBar** is a widget that displays a toolbar at the top of the screen, typically used for branding, navigation, and actions related to the current screen. It supports a title and icons, and offers customization with a variety of styles and functionalities. The AppBar is divided into the following sections: * **Leading:** Typically holds a **menu** or **back icon** that provides navigation control. By default, if there is a [**drawer**](/resources/ui/pages/scaffold.md#drawers) or [**page navigation**](/concepts/navigation/page-navigation.md) with ["Allow Back Navigation" enabled](/concepts/navigation/page-navigation.md#navigate-to-action), a specific icon (such as a menu or back arrow) is displayed. However, you can override this with another [**Icon widget**](/resources/ui/widgets/icons.md) if desired, allowing for more tailored navigation options. * **Title:** Primarily serves to indicate the content of the active screen or to display the name of the application, aiding users in recognizing their context within the app. This section can also be customized with different widgets for a more tailored visual representation. * **Actions:** Hosts icon buttons for various operations like search, share, and more, situated on the right end. ### Add an AppBar[​](/resources/ui/pages/scaffold.md#add-an-appbar "Direct link to Add an AppBar") [Add AppBar](https://demo.arcade.software/Gviwe4k9svWyMBr6NLCP?embed\&show_copy_link=true) ### Enable Default Button[​](/resources/ui/pages/scaffold.md#enable-default-button "Direct link to Enable Default Button") The "Show Default Button" toggle in the **AppBar** Properties Panel controls whether the default leading icon (usually a back arrow or a menu icon) appears when the user can [navigate back](/concepts/navigation/page-navigation.md) or when a [Drawer](/resources/ui/pages/scaffold.md#drawers) is present on the page. However, it's important to note that this default icon won't appear in the FlutterFlow canvas during the design stage. It only becomes visible when the app is running, and the conditions for showing the button are met. If you wish to replace the default icon with another icon in the leading space, follow the [guide on adding an AppBar](/resources/ui/pages/scaffold.md#add-an-appbar). Generated Code In the generated code, when this toggle is enabled, [`automaticallyImplyLeading`](https://api.flutter.dev/flutter/material/AppBar/automaticallyImplyLeading.html) property in the **AppBar** widget is set to `true`. This means that the appropriate default button will be displayed if back navigation is enabled or Drawer is detected when you run the app. ## Floating Action Button (FAB)[​](/resources/ui/pages/scaffold.md#floating-action-button-fab "Direct link to Floating Action Button (FAB)") A **Floating Action Button (FAB)** is a distinctive circular button that hovers over content, commonly used for a primary action within an app, like adding a new item or composing a message. ### Extended Property[​](/resources/ui/pages/scaffold.md#extended-property "Direct link to Extended Property") This variant of the `FAB` includes both an icon and a label, making it larger than the standard circular `FAB`. It is useful when you want the action button to convey more information than just the icon can provide, such as text explaining the action ("Add Task", "Create Post", etc.). **Use cases** The **extended** `FAB` is particularly beneficial in applications where the action needs clear and immediate recognition from the user, which cannot be fully achieved by an icon alone. It is also useful in interfaces where there is ample space to accommodate a longer button without cluttering the UI. ![fab-comparison.png](/assets/images/fab-comparison-3b65acd5c5d7265223da2b2e9094fb1d.png) ### Adding a Floating Action Button to your Page[​](/resources/ui/pages/scaffold.md#adding-a-floating-action-button-to-your-page "Direct link to Adding a Floating Action Button to your Page") [Add FAB](https://demo.arcade.software/TfHpfAQYIc5iaALgbK2O?embed\&show_copy_link=true) ## Drawers[​](/resources/ui/pages/scaffold.md#drawers "Direct link to Drawers") **Drawer** is a slide-out menu that can emerge from either side of the screen, typically used for app navigation or placing additional options. It allows users to switch between different sections of an app without cluttering the main interface. ### Add a Drawer to your Page[​](/resources/ui/pages/scaffold.md#add-a-drawer-to-your-page "Direct link to Add a Drawer to your Page") [Scaffold - Add Drawer](https://demo.arcade.software/jTl8VlxxDxmhyms7YEVS?embed\&show_copy_link=true) ### End-Drawer[​](/resources/ui/pages/scaffold.md#end-drawer "Direct link to End-Drawer") Using a similar approach, you can also add an End Drawer to your page. ### Drawer \[Action][​](/resources/ui/pages/scaffold.md#drawer-action "Direct link to Drawer \[Action]") Using this action, you can open and close the drawers with a tap of a button. For example, opening the drawer from a widget placed outside the Appbar and closing it from the widget placed inside the drawer. #### Types of drawer actions[​](/resources/ui/pages/scaffold.md#types-of-drawer-actions "Direct link to Types of drawer actions") There are three types of actions you can add to the drawer. * **Open Drawer**: Opens the regular drawer. * **Open End Drawer**: Opens the end drawer. * **Close Drawers**: Closes all the open drawers. ## Nav Bar[​](/resources/ui/pages/scaffold.md#nav-bar "Direct link to Nav Bar") The NavBar (or Navigation Bar) allows you to quickly navigate between pages of your app. It is displayed at the bottom of the screen for convenient access. The items inside the NavBar are represented by an icon, optional text, or both. You can display up to three or five primary or top-level pages (pages that can be accessed from anywhere in your app) inside the NavBar. From the NavBar settings page, you can add the NavBar and make modifications such as changing the display style, reordering icons, customizing its appearance, and more. ### Enable Nav Bar in settings[​](/resources/ui/pages/scaffold.md#enable-nav-bar-in-settings "Direct link to Enable Nav Bar in settings") By default, the NavBar is disabled for any project created in FlutterFlow. Before you can add pages to the NavBar, you need to enable it from the FlutterFlow settings. Navigate to **Setting and Integrations > General > NavBar & AppBar** and enable Nav Bar. caution Initially, your NavBar will not have any pages in it. You'll see a message instructing you to add at least two pages. Before proceeding, make sure to create at least two pages. If you need help with adding a new page, you can find [**more information here**](/resources/ui/pages.md#creating-a-page). ![nav-bar.png](/assets/images/nav-bar-3e95a622b810f966bed49041ddfd96b3.png) **Responsive Visibility:** To ensure that your NavBar is visible only on certain screen sizes, you can toggle the device icons based on your design preference. ### Add Pages to your Nav Bar[​](/resources/ui/pages/scaffold.md#add-pages-to-your-nav-bar "Direct link to Add Pages to your Nav Bar") Once enabled, you need to select the pages you want to appear in the navigation bar and then add them. Here's how you can do it: [Nav Bar Add Pages](https://demo.arcade.software/ShQiuWlUfEbCT29G6nyJ?embed\&show_copy_link=true) #### Nav Bar Properties (Property Panel)[​](/resources/ui/pages/scaffold.md#nav-bar-properties-property-panel "Direct link to Nav Bar Properties (Property Panel)") * **Label:** This label will be displayed on the Nav Bar. * **Nav Bar Icon:** This icon represents the page in the Nav Bar. You can also choose its **size**. info The NavBar will only appear on the canvas if you have added at least two pages to it. #### Reordering Nav Bar Items[​](/resources/ui/pages/scaffold.md#reordering-nav-bar-items "Direct link to Reordering Nav Bar Items") To reorder the Nav Bar items: * Navigate to the **Setting and Integrations > General > NavBar & AppBar > Nav Bar**. * Under the **Re-Order Page Icons**, identify the page that you want to reorder, click on the hamburger icon (icon with three lines ) beside it and drag it in an upward or downward direction. ### Modifying NavBar Style[​](/resources/ui/pages/scaffold.md#modifying-navbar-style "Direct link to Modifying NavBar Style") When you enable the NavBar, it initially adopts the Flutter Default Nav Bar style. However, if you need more customization options, you can set the Nav Bar Style dropdown to one of the following: #### Flutter Default Nav Bar[​](/resources/ui/pages/scaffold.md#flutter-default-nav-bar "Direct link to Flutter Default Nav Bar") This is the standard material style NavBar. You have the option to show or hide labels for both selected and unselected items. ![nav-bar-default.png](/assets/images/nav-bar-default-60c4b4df61a1a36ab816fd9e384e1fad.png) **Styling Properties** | Property | Type | Description | | ---------------------------- | ----------- | ------------------------------------------------------------------------------------------ | | **Show Labels (Selected)** | Toggle | Allows you to enable or disable the display of labels for selected items in the `NavBar`. | | **Show Labels (Unselected)** | Toggle | Allows you to enable or disable the display of labels for unselected items in the `NavBar` | | **NavBar Color** | Color Wheel | Sets the background color of the `NavBar` | | **Selected Icon Color** | Color Wheel | Specifies the color of the icons when they are selected. | | **Unselected Icon Color** | Color Wheel | Specifies the color of the icons when they are not selected. | #### Google Nav Bar[​](/resources/ui/pages/scaffold.md#google-nav-bar "Direct link to Google Nav Bar") This modern Google-style NavBar features a subtle animation that reveals the item label (page name) but only displays the label for the selected item. ![nav-bar-google.png](/assets/images/nav-bar-google-335c7e5919226da7956c5f7235554cc4.png) **Styling Properties** * **Nav Bar Color:** Sets the background color of the NavBar. * **Selected Icon & Text Color:** Changes the color of the icon and text when an item is selected. * **Unselected Icon & Text Color:** Sets the color for icons and text when an item is not selected. * **Selected Background Color**: Alters the background color of the selected item. * **Show Unselected Border**: Toggles the visibility of a border around unselected items * **Border Width:** Specifies the width of the border around the NavBar item buttons. * **Border Radius:** Determines the corner roundness of the NavBar item buttons. * **Border Color:** Alters the color of the borders around NavBar item buttons. * **Nav Button Padding:** Adjusts the padding inside each nav button. * **Nav Button Margin:** Controls the margin around each nav button. * **Nav Button Alignment:** Allows customization of how nav buttons align within the NavBar. Options include center, space-between, etc., are given. * **Gap Between Icon and Text:** Specifies the spacing between the icon and text within nav buttons. * **Animation Duration (ms):** Defines how long animations take when switching between items. * **Haptic Feedback:** A toggle that enables or disables haptic feedback when interacting with NavBar items, enhancing the tactile experience. #### Floating Nav Bar[​](/resources/ui/pages/scaffold.md#floating-nav-bar "Direct link to Floating Nav Bar") This NavBar style appears as a floating element above the pages and shows labels for all items present in the NavBar. ![nav-bar-floating.png](/assets/images/nav-bar-floating-3d935739e7a9ff390c894e9e2dd54a42.png) **Styling Properties** * **Nav Bar Color:** Sets the background color of the NavBar. * **Selected Icon & Text Color:** Specifies the color of the icon and text when an item is selected. * **Unselected Icon & Text Color:** Defines the color for the icons and text when they are not selected. * **Selected Background Color:** Alters the background color of the selected item. * **Width:** Controls the width of the NavBar. * **Border Radius:** Determines the roundness of the NavBar's corners. * **Elevation:** Adjusts the shadow or elevation effect beneath the NavBar, which helps give the NavBar a floating appearance above other content. * **Button Border Radius:** Specifies the radius for the borders of each button within the NavBar. * **Nav Button Margin:** Sets the margin around each nav button * **Nav Button Padding:** Controls the padding inside each nav button. [YouTube video player](https://www.youtube.com/embed/Qhe8X5ykK54) ## SnackBar[​](/resources/ui/pages/scaffold.md#snackbar "Direct link to SnackBar") **SnackBar** is a temporary, lightweight notification that briefly appears at the bottom of the screen to provide feedback about an operation. ### When to Use Snackbar?[​](/resources/ui/pages/scaffold.md#when-to-use-snackbar "Direct link to When to Use Snackbar?") Here are some common uses of a SnackBar in an app: * **User Feedback:** Notifies users about the success or failure of actions like submitting a form or uploading a file. * **Undo Actions:** Provides a quick option to undo a recently completed action, such as deleting an email or removing an item from a list. * **Informational Alerts:** Displays brief messages about changes or updates, such as synchronization status or network issues, without requiring user interaction. * **Confirmation Messages:** Confirms the completion of tasks that don't need immediate attention, like saving settings or adding a calendar event. ### To show a SnackBar message[​](/resources/ui/pages/scaffold.md#to-show-a-snackbar-message "Direct link to To show a SnackBar message") [Show a snackbar](https://demo.arcade.software/wSnox6aBYylpdh2qx1JJ?embed\&show_copy_link=true) ### Show SnackBar \[Action][​](/resources/ui/pages/scaffold.md#show-snackbar-action "Direct link to Show SnackBar \[Action]") Material Design allows you to add an interactive element to the SnackBar notification, allowing users to respond directly from the snack message. #### Add Action Property[​](/resources/ui/pages/scaffold.md#add-action-property "Direct link to Add Action Property") Typically, a SnackBar can include a single action button. This button is used to offer users an immediate option to interact with the snack message. Common uses include undoing an action that the snack message refers to (like undoing a deletion), retrying a failed task (like reconnecting to a network), or any other quick recovery or response tasks. * **Customization:** The action within a SnackBar is customizable. You can define the button's label, appearance, and the function it executes when pressed. This allows the SnackBar to not only inform users but also engage them in meaningful ways to enhance the user experience. * **Timeouts and Visibility:** The presence of an action can affect the duration the SnackBar is displayed. By default, a SnackBar may auto-dismiss after a few seconds, but if an action button is present, users might need more time to read the message and respond, thus you might consider adjusting the display duration accordingly. ![snackbar-action-props.png](/assets/images/snackbar-action-props-deaac181811c45b593c1d699d548946d.png) Adding actions to SnackBars helps make them not just informative but also interactive, facilitating a more dynamic user interaction model where feedback and actions are closely linked. ![snackbar.png](/assets/images/snackbar-8d480ecbcad24ca94d4199fce66a182a.png) ### Hide SnackBar \[Action][​](/resources/ui/pages/scaffold.md#hide-snackbar-action "Direct link to Hide SnackBar \[Action]") Managing multiple SnackBar instances efficiently is crucial because showing them all at once can overwhelm the user interface and confuse the user. To address this, Flutter apps use a queuing system for `SnackBars`: **Snackbar Queue:** When multiple SnackBars are triggered in succession, they are queued to be displayed one after the other rather than all at once. Each `SnackBar` waits for the previous one to disappear before the next one shows up. **Hiding Previous Snackbar:** If you want to immediately replace a currently displayed SnackBar with a new one without waiting for it to auto-dismiss, you can use the **Hide Snackbar** action in FlutterFlow. The action has the following hide scope: * **Current Only:** This option hides only the currently displayed snackbar. * **All (Current and Queue):** This option hides the current snackbar as well as any snackbar in the queue. This can be useful in scenarios where an immediate update to the user feedback is necessary, such as correcting a message or providing new information. By using these methods, you can control the flow of information via SnackBars, ensuring that user feedback is timely, relevant, and not overwhelming. --- # Introduction to Widgets Widgets are the building blocks of your app's user interface in FlutterFlow. Each widget represents a fundamental UI element that contributes to the overall layout and functionality of your app. In FlutterFlow, you create your app's UI by combining basic widgets like **Text, Button** and **Container** with more complex, multi-child widgets like **Rows, Column, Lists**. Understanding the parent-child relationship between widgets is crucial, as it forms the foundation of the [**Widget Tree**](/resources/ui/widgets.md#widget-tree), which defines the structure and hierarchy of your app's UI. ## Types of Widgets in FlutterFlow[​](/resources/ui/widgets.md#types-of-widgets-in-flutterflow "Direct link to Types of Widgets in FlutterFlow") * **Built-in Widgets**: You can choose from a variety of built-in widgets in FlutterFlow. These are discussed throughout this section. * **[Components](/resources/ui/components/creating-components.md)**: You can also build your own reusable widgets, or Components by assembling multiple widgets using FlutterFlow’s drag-and-drop interface. * **[Custom Widgets](/concepts/custom-code/custom-widgets.md)**: For scenarios where more complex functionalities are required, FlutterFlow allows you to develop your own Custom Widgets using code. * **[Theme Widgets](/concepts/design-system.md#theme-widgets)**: Themed widgets can be reused across your app, making it easy to update styles universally. If you decide to change any properties, such as color schemes or fonts, you can update the theme widget instead of modifying each widget individually. ## Widget Tree[​](/resources/ui/widgets.md#widget-tree "Direct link to Widget Tree") The Widget Tree is a structural representation of how widgets—ranging from [atomic elements](/resources/ui/overview.md) like Text and Button to more [complex molecules and organisms](/resources/ui/overview.md)—organized within a Page. It outlines the parent-child relationships that define the layout and functionality of your UI. This hierarchy is similar to the concept of atomic design, where atoms and molecules combine to form more complex structures, ultimately creating a cohesive interface. WIDGET TREE BREAKDOWN ![tree.png](/assets/images/tree-dd5fad754dcf04fe9f4067413e137386.png) The above diagram illustrates a widget tree for an `ExamplePage`. The page is structured using a hierarchy of widgets that define its layout and functionality. * **ExamplePage**: The root of the widget tree, representing the entire Page. * **Column**: Directly under the root, this widget organizes its child widgets vertically. It is the main layout widget for this Page. * **Container**: A molecular widget that contains another widget, providing padding, margins, borders, or color to its child. * **Text**: An atomic widget, this displays a string of text within the `Container`. * **Row**: A molecular widget that arranges its children horizontally. It contains multiple `Icon` widgets. * **Icon**: These are atomic widgets, each representing an `Icon` image. They are repeated here twice under the `Row`. * **Image**: An atomic widget placed directly under the `Column`, used here to display an image. * **Button:** An atomic widget also under the `Column`, used for user interaction. Each widget in this tree plays a specific role in constructing the user interface, from basic elements like `Text` and `Image` to layout structures like `Row`s and `Column`s that organize these elements. Here's how this widget tree would be represented in FlutterFlow: ![widget-tree-new.png](/assets/images/widget-tree-new-083629745aefd437e890ea2f81086844.png) Understanding the widget tree is crucial for developers using FlutterFlow because it helps visualize the composition of the application's interface. It shows how individual widgets (atoms) combine and nest within each other to form more complex widgets (molecules and organisms) and ultimately complete pages. ### Widget categories[​](/resources/ui/widgets.md#widget-categories "Direct link to Widget categories") In FlutterFlow, we have the following categories of widgets: * [Layout Elements](/resources/ui/widgets.md#layout-elements) * [Base Elements](/resources/ui/widgets.md#base-elements) * [Page Elements](/resources/ui/widgets.md#page-elements) * [Form Elements](/resources/ui/widgets.md#form-elements) #### Layout Elements[​](/resources/ui/widgets.md#layout-elements "Direct link to Layout Elements") These widgets help organize the structure and layout of your app. They determine how other widgets are arranged and displayed on the screen. Common layout elements include: | Widget | Description | Example | | ------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | | **Row** | Arrange its child widgets horizontally | ![](/img/widgets/row-example.png) | | **Column** | Organizes its child widgets vertically. | ![](/img/widgets/col-example-1.png) | | **Stack** | Layers its child widgets on top of each other, allowing for overlapping elements. | ![](/img/widgets/stack-example.png) | | **Container** | Provides a box model for a single child widget, with optional padding, margins, borders, box shadow and background color. | ![](/img/widgets/cont-example.png) | Find the entire list on this [**index page**](/tags/layout-elements.md). #### Base Elements[​](/resources/ui/widgets.md#base-elements "Direct link to Base Elements") Base elements are the fundamental building blocks for creating the visual and interactive components of your app. Examples include: | Widget | Description | Example | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | **[Text](/resources/ui/widgets/text.md)** | Displays a string of text and allows you to customize fonts, sizes, and styles. | ![](/img/widgets/text-example.png) | | [**Image**](/resources/ui/widgets/image.md) | Displays image. | ![](/img/widgets/img-example.png) | | [**Icon**](/resources/ui/widgets/icons.md) | Displays icon. | ![](/img/widgets/icon-example.png) | | [**Button**](/resources/ui/widgets/button.md) | A widget meant to trigger actions and take users to another flow in the app. It can be styled with different colors, borders, and text | ![](/img/widgets/button-example.png) | Find the entire list on this [**index page**](/tags/base-elements.md). #### Page Elements[​](/resources/ui/widgets.md#page-elements "Direct link to Page Elements") In FlutterFlow, the **Page Elements** category consists of widgets like **[AppBar](/resources/ui/pages/scaffold.md#appbar)**, **[Floating Action Button (FAB)](/resources/ui/pages/scaffold.md#floating-action-button-fab)**, **[Drawer](/resources/ui/pages/scaffold.md#drawers)**, and **[End Drawer](/resources/ui/pages/scaffold.md#end-drawer)**, which are essential for structuring pages and facilitating navigation throughout the app. info Learn more about **[Page Elements](/resources/ui/pages/scaffold.md)** such as **AppBar**, **Snackbar**, **Drawers** etc and how to use them in FlutterFlow. #### Form Elements[​](/resources/ui/widgets.md#form-elements "Direct link to Form Elements") Form elements are widgets specifically used for creating forms where users can enter data. These are crucial for tasks like user registration, login, and data entry. Examples include: | Widget | Description | Example | | ---------------- | ----------------------------------------------------------------- | -------------------------------------------------- | | **Text Field** | Allows users to enter text. | ![Textfield Example](/img/widgets/txtfield-ex.png) | | **Radio Button** | Allows users to select one option from a set. | ![Radio Button Example](/img/widgets/radio-ex.png) | | **Dropdown** | Provides a menu with multiple options where users can select one. | ![Dropdown Example](/img/widgets/dropdwn-ex.png) | Find the entire list on this [**index page**](/tags/form-elements.md). Each category in FlutterFlow serves distinct purposes, helping you design both the appearance and functionality of your app more efficiently. --- # Basic Widgets FlutterFlow offers a range of basic widgets that are the building blocks of a Page or Component. In this guide, we'll cover five fundamental widgets: **Container**, **Text, Icon, Button,** and **Image**. Understanding these widgets is crucial for building any FlutterFlow app. ![basic-widgets.png](/assets/images/basic-widgets-a4c8ea362895b6f1a4472cbc0ad025ca.png) Some basic widgets include: * **[Container](/resources/ui/widgets/container.md)**: The **Container** widget is one of the most commonly used widgets in FlutterFlow. It allows you to create a rectangular or circular box that is allowed to have one single child - any other basic or advanced widget, and you can style it with various properties such as padding, margins, borders, and colors, etc. * **[Text](/resources/ui/widgets/text.md)**: The **Text** widget is used to display a string of text with single style. It’s a basic yet powerful widget that allows you to customize text appearance, alignment, and behavior. * **[Icon](/resources/ui/widgets/icons.md)**: The **Icon** widget is used to display an icon from the Material Icons, Font Awesome or your own custom icons set. Icons are essential for building user-friendly interfaces, providing visual cues to users. * **[Button](/resources/ui/widgets/button.md)**: In FlutterFlow, **Button** widgets are specialized interactive elements that come with built-in visual feedback and default hover properties. * **[Image](/resources/ui/widgets/image.md)**: The **Image** widget is used to display images in your app. FlutterFlow supports various sources for images, including assets, network URLs, and uploaded files. --- # AspectRatio The `AspectRatio` widget lets you maintain a consistent width-to-height ratio for its child widget. Instead of setting fixed pixel dimensions, you define a ratio, and the widget calculates the height automatically based on the available width. This keeps your UI proportionally consistent across all screen sizes without manual math. Use it whenever a child widget needs to maintain a predictable shape, regardless of the device — media thumbnails, video players, hero images, or uniform card layouts. In the widget tree, AspectRatio sits as a wrapper around a single child widget, typically an `Image` or `Video player`. The structure looks like this: ``` Container └── AspectRatio └── Image ``` AspectRatio controls the bounding box. Whatever child you place inside fills that box. ## Configuring the Ratio[​](/resources/ui/widgets/built-in-widgets/aspect-ratio.md#configuring-the-ratio "Direct link to Configuring the Ratio") Select the AspectRatio widget to open its properties panel. Under **Aspect Ratio**, you'll find a **Ratio** dropdown. ### Preset Ratios[​](/resources/ui/widgets/built-in-widgets/aspect-ratio.md#preset-ratios "Direct link to Preset Ratios") The **Aspect Ratio** widget ships with seven presets covering the most common layout needs: | Preset | Decimal | Best For | | ------ | ------- | ----------------------------------------------- | | 1:1 | 1.0 | Profile pictures, avatars, square thumbnails | | 4:3 | 1.333 | Standard photos, product images | | 3:2 | 1.5 | Photography, editorial cards | | 16:9 | 1.778 | Video players, YouTube thumbnails, hero banners | | 9:16 | 0.563 | Vertical video (Reels, Shorts, Stories) | | 3:4 | 0.75 | Portrait photos, book covers | | 2:3 | 0.667 | Posters, portrait cards | ### Custom Value[​](/resources/ui/widgets/built-in-widgets/aspect-ratio.md#custom-value "Direct link to Custom Value") If none of the presets fit your design, select **Custom** from the dropdown. A **Value** field appears where you enter the ratio as a decimal number. The formula is simple: divide width by height. * A 5:4 ratio → enter `1.25` * A 21:9 ultra-wide ratio → enter `2.333` * A 4:5 Instagram portrait → enter `0.8` #### Dynamic Ratio with Variable Binding[​](/resources/ui/widgets/built-in-widgets/aspect-ratio.md#dynamic-ratio-with-variable-binding "Direct link to Dynamic Ratio with Variable Binding") The **Value** field supports variable binding. This lets you drive the ratio dynamically at runtime. **Example use case:** You're building a media feed that shows both landscape videos and portrait photos. Store the ratio as an `double` field in your data model and bind the AspectRatio's value to it. When the feed loads, each card adopts the correct shape automatically — no hardcoded layouts needed. ## Constraint Warning[​](/resources/ui/widgets/built-in-widgets/aspect-ratio.md#constraint-warning "Direct link to Constraint Warning") When AspectRatio is placed inside a parent that provides tight constraints in both dimensions, meaning the parent has already fixed both the width and height, the widget displays a warning. **What it means:** AspectRatio works by taking the available width and calculating height from the ratio. If the parent has locked the height too, there is no room for AspectRatio to do its job. The ratio is ignored, and the child simply fills the parent's fixed dimensions. **Common triggers:** * Nesting it inside a `Container` that has both a fixed width and fixed height set * Placing AspectRatio inside a `Row` without wrapping it in an `Expanded` or `SizedBox` * Putting it inside a `Column` with `MainAxisSize` set in a way that squeezes available space **How to fix it:** * Remove the fixed height from the parent `Container` and let AspectRatio drive the height. * If inside a `Row`, wrap AspectRatio in an `Expanded` widget so it receives unconstrained width first. --- # Badge The Badget widget indicates the number of items that need your attention. Typically it's a medium-sized dot that floats over other widgets such as IconButton. For example, You could use the badge widget to show the number of unread notifications and items in your shopping cart. ![img\_3.png](/assets/images/img_3-cb5b5453c62028011d19f823b3e07cd9.png) ## Adding Badge widget[​](/resources/ui/widgets/built-in-widgets/badge.md#adding-badge-widget "Direct link to Adding Badge widget") Here's an example of how you can add the Badge widget to your project: 1. First, drag the **Badge** widget from the Base Elements tab and carefully drop it into the Actions section of the AppBar. 2. Now, add the **IconButton** widget inside the **Badge** widget. Customize the Icon and its color as per your requirement. 3. Select the **Badge** widget, move to the properties panel, and set the **top** side padding to 5 and **right** side padding to 15. ## Customizing[​](/resources/ui/widgets/built-in-widgets/badge.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of the badge widget using the various properties available under the **Properties Panel**. ### Setting badge text[​](/resources/ui/widgets/built-in-widgets/badge.md#setting-badge-text "Direct link to Setting badge text") You can set the badge text that appears inside the badge. Usually, it's a numeric value. To set the badge text: 1. Select the **Badge** widget from the widget or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Badge Properties** section. 3. Find the **Text** property and enter a value. You would probably set this value from the variable or field from the backend database, such as the API response variable and Firestore document field. To do so, click on the **Set from Variable**. ### Styling badge text[​](/resources/ui/widgets/built-in-widgets/badge.md#styling-badge-text "Direct link to Styling badge text") To change the badge text style: 1. Select the **Badge** widget from the widget or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Badge Properties** section. 3. Find the **Theme Text Style** property and change the style as per instructions [here](/resources/ui/widgets/text.md). ### Show/hide badge[​](/resources/ui/widgets/built-in-widgets/badge.md#showhide-badge "Direct link to Show/hide badge") You might want to hide the badge widget initially and only show it when some items need the user's attention—for example, showing the notification badge only when there are new/unread notifications. To show/hide the badge widget: 1. Select the **Badge** widget from the widget or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Badge Properties** section. 3. Find the **Show Badge** property and check/uncheck to show/hide the badge. Most probably, you would set this value from the variable such as the app state variable and variable from API response. To do so, click on the **Set from Variable**. ### Changing badge color[​](/resources/ui/widgets/built-in-widgets/badge.md#changing-badge-color "Direct link to Changing badge color") To change the badge color: 1. Select the **Badge** widget from the widget or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Badge Properties** section. 3. Find the Badge Color property, click on the box next to the already selected color, select the color, and then click **Use Color** or click on an already selected colorand enter a Hex Code directly. You can also choose the color by clicking the **Palette** and **Simple** button. ### Changing elevation[​](/resources/ui/widgets/built-in-widgets/badge.md#changing-elevation "Direct link to Changing elevation") To change the elevation (depth or Z-axis) of the badge: 1. Select the **Badge** widget from the widget or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Badge Properties** section. 3. Find the **Elevation** input box and enter the value to see the drop shadow effect below the badge. The Higher value sets the bigger size of the shadow, and the 0 value removes the shadow. ### Changing badge position[​](/resources/ui/widgets/built-in-widgets/badge.md#changing-badge-position "Direct link to Changing badge position") By default, the badge is displayed on the top right side of its child widget. You can change its position and bring it to the left side. To change the badge position: 1. Select the **Badge** widget from the widget or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Badge Properties** section. 3. Find the **Position (Start or End)** property and click on the icons to change the position. ### Allow animating badge[​](/resources/ui/widgets/built-in-widgets/badge.md#allow-animating-badge "Direct link to Allow animating badge") By default, the badge widget animates whenever the value is changed. To allow/disallows animating badge: 1. Select the **Badge** widget from the widget or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Badge Properties** section. 3. Find the **Animate** toggle, and then turn it on or off. --- # Barcode The Barcode widget is used to embed the information inside the series of lines and patterns. The data inside the barcode can be easily retried with a scanner machine, an app like [Google Lens](https://lens.google/) (Android), [Apple Camera](https://support.apple.com/en-in/HT208843) (iOS), or your [own app](/resources/ui/widgets/built-in-widgets/barcode.md#scan-barcode--qr-code-action) created using FlutterFlow. It is typically used to retrieve product information quickly and accurately. For example, you could track the inventory/books (e.g., price, description, location, etc.), share website/app URL, quick onboarding process, and so on. ![img\_4.png](/assets/images/img_4-2b518112d20901711080da59327256da.png) ## Adding Barcode widget[​](/resources/ui/widgets/built-in-widgets/barcode.md#adding-barcode-widget "Direct link to Adding Barcode widget") To add a Barcode widget to your app: 1. First, click on the **+ Add Widget**, drag the **Barcode** widget from the **Base Elements** tab, or add it directly from the widget tree. 2. By default, the barcode is displayed in a linear fashion called **1D Barcode**. (i.e., a series of lines and space of various widths). To display the barcode in a matrix form, such as QR-Code, move to the properties panel and set the **Barcode Dimensions** to the **2D Barcode**. 3. Now, you'll need to figure out the type of information you want to embed and select the **Barcode Type**. The barcode type options are available based on the *Barcode Dimensions* you selected in the previous step. For example, to label the retail products (i.e., 12 digits numeric only number), you can set it to *UPC-A* or *UPC-E*, and to embed the URL, you can set it to the *QR-Code*. If you are unsure which type to choose, [here](https://packagex.io/blog/barcode-types) is a guide to help. 4. Finally, you can provide the data/information into the **Barcode Value** property. You can also click **Set from Variable** to set it based on the value from the app state, your backend, or any other source. ## Customizing[​](/resources/ui/widgets/built-in-widgets/barcode.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the **Properties Panel**. ### Changing size[​](/resources/ui/widgets/built-in-widgets/barcode.md#changing-size "Direct link to Changing size") To change the size of the barcode widget, select the **Barcode** widget, move to the properties panel, find the **Width** and **Height** property and enter the values. ### Changing color[​](/resources/ui/widgets/built-in-widgets/barcode.md#changing-color "Direct link to Changing color") To change barcode colors, select the **Barcode** widget, move to the properties panel, and [change the colors](/resources/ui/widgets/widget-commonalities.md#change-color) for the following properties: * **Foreground Color**: This sets the line or pattern color. * **Background Color**: This sets the background color behind the line or pattern. ### Show barcode text[​](/resources/ui/widgets/built-in-widgets/barcode.md#show-barcode-text "Direct link to Show barcode text") You can also display the actual data below the barcode by enabling the **Show Text** property. Note This option is only available when using the *1D Barcode*. ## Scan Barcode / QR code \[Action][​](/resources/ui/widgets/built-in-widgets/barcode.md#scan-barcode--qr-code-action "Direct link to Scan Barcode / QR code \[Action]") Using this Action, you open a barcode or QR code interface and scan a code using the device camera. Follow the steps below to define a Scan Action to any widget. 1. Select **Actions** from the Properties panel (the right menu) 2. Click **+ Add Action** button 3. Choose a gesture from the dropdown among ***On Tap**, **On Double Tap**, or* **On Long Press** 4. Select the **Action Type** as ***Scan Barcode/QR code**.* 5. If you check the **Barcode Mode** checkbox then the UI will look like a barcode scanner. Otherwise, the UI will be like a QR code scanner. 6. **Cancel button text** would be ***Cancel*** by default, but you can specify any other text if you want. 7. In the **Output Variable Name** field, you can specify the name of the variable where the scanned text would be saved and then you can access it via the **Set from Variable menu > Action Outputs > \[Action Output Variable Name]**. --- # Blur The Blur widget is used to blur its child or parent widget. You can use this widget to create the [Frosted glass](https://en.wikipedia.org/wiki/Frosted_glass) effect, typically seen in iOS. ## Adding Blur widget[​](/resources/ui/widgets/built-in-widgets/blur.md#adding-blur-widget "Direct link to Adding Blur widget") Here's an example of how you can add the Blur widget to your project: 1. First, drag the **Blur** widget from the **Base Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. Now, add the **Image** widget inside the **Blur** widget. Customize the Image as per your requirement. ## Adding blur effect to the parent widget[​](/resources/ui/widgets/built-in-widgets/blur.md#adding-blur-effect-to-the-parent-widget "Direct link to Adding blur effect to the parent widget") By default, the Blur widget adds the blur effect on the child. For example, if you add the image widget as a child of the Blur widget, you will see the effect on the image. But sometimes, you might want to create the blur effect on the parent widget of the Blur widget. tip Adding a blur effect on the parent helps create the **Frosted Glass effect**. ![Frosted glass effect examples](/assets/images/frosted-glass-example-7270737045c7d3a75686b5d246c97fb6.png) Here is how you can create the first example: 1. First, add the **Container** widget. Move to the properties panel, set its **width** to **Inifinity** and **height** to **200**. Also, set its **Background Image**. 2. Inside the Container, add the **Blur** widget. 3. Now, add the **Text** widget inside the blur widget and bring it to the center by changing its alignment. 4. Finally, select the **Blur** widget from the widget tree or the canvas area. Move to the properties panel, scroll down to the **Blur Properties** section, and **turn on** the **Backdrop** toggle. This toggle decides whether to add a blur effect on the parent or child widget. If enabled, it will blur the parent widget, while disabling it will cast the blur effect on its child. Here are the steps to create the second example: 1. First, add the **Container** widget. Move to the properties panel, set its **width** to **Inifinity** and **height** to **200**. Also, set its **Background Image**. 2. Add the **Column** widget (inside the Container) and set its **Main Axis Alignment** to **end**. 3. Add the **Blur** widget (inside the Column). 4. Add the **Container** widget (inside the Blur) and set its **width** to **infinity** and **height** to **50**. Also, make the container's background around 40% transparent by selecting the **Fill Color** and bringing the second slider to the left. 5. Add the **Text** widget (inside the Container) and bring it to the center by changing its alignment. 6. Finally, select the **Blur** widget from the widget tree or the canvas area. Move to the properties panel, scroll down to the **Blur Properties** section and **turn on** the **Backdrop** toggle. This toggle decides whether to add a blur effect on the parent or child widget. If enabled, it will blur the parent widget, while disabling it will cast the blur effect on its child. ## Customization[​](/resources/ui/widgets/built-in-widgets/blur.md#customization "Direct link to Customization") You can customize the behavior of this widget using the various properties available under the properties panel. ### Changing blur strength[​](/resources/ui/widgets/built-in-widgets/blur.md#changing-blur-strength "Direct link to Changing blur strength") The blur strength is the blurriness added to the widget. This widget adds blur strength by utilizing the Sigma X and Sigma Y property. Sigma X sets the blur strength in the horizontal direction, while Sigma Y sets the blur strength in the vertical direction. The higher Sigma X and Y values increase the blurriness, whereas setting them to 0 completely removes the blurriness. To change the blur strength: 1. Select the **Blur** widget from the widget tree or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Blur Properties** section. 3. Change the values in the **Sigma X** and **Sigma Y** input boxes. ### Show or hide blur effect[​](/resources/ui/widgets/built-in-widgets/blur.md#show-or-hide-blur-effect "Direct link to Show or hide blur effect") To show or hide the blur effect: 1. Select the **Blur** widget from the widget tree or the canvas area. 2. Move to the properties panel (on the right side of your screen), and scroll down to the **Blur Properties** section. 3. **Check**/**Uncheck** the **Should Apply Blur** property to show/hide the blur effect. You can also set this value from a variable such as the App State variable, API response variable, or Firestore document by clicking on the **Set from Variable**. --- # Calendar The Calendar widget shows days in a month and a week. You can use the Calendar widget to filter the event list by date. For example, showing appointments on a specific date. ## Adding Calendar to your project[​](/resources/ui/widgets/built-in-widgets/calendar.md#adding-calendar-to-your-project "Direct link to Adding Calendar to your project") To add the Calendar widget to your project: 1. Drag the **Calendar** widget from the **Base Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. On running the app, the calendar widget shows today's date by default. To set a different date, follow the instructions as below. 3. Move to the Properties Panel and scroll down to the **Calendar** section. 4. Find the **Initial Date** property, click **Unset,** and set the date from the variable (app state, API, etc.). ## Show/save the selected date[​](/resources/ui/widgets/built-in-widgets/calendar.md#showsave-the-selected-date "Direct link to Show/save the selected date") When you select/change any date on the calendar, you can display it on the page or save it in a variable/Field (as Timestamp datatype) for later access. Let's build an example of showing the selected date in a Text widget that looks like the one below: The steps to show the selected date in the Text widget are as follows: ### 1. Create an app state variable[​](/resources/ui/widgets/built-in-widgets/calendar.md#1-create-an-app-state-variable "Direct link to 1. Create an app state variable") Changing the date on the calendar widget emits the selected date in a variable called *calendarSelectedDay*. You can't use this value directly in the Text widget because the Text widget can only accept String values. Hence it would help if you created an app state variable that will store the *calendarSelectedDay* value and then display the selected date in a Text widget (using Date Format Options). To create the app state variable, please find the instructions [here](/resources/data-representation/app-state.md#create-app-state-variable). It should look something like this: ![app-state-variable-calendar.avif](/assets/images/app-state-variable-calendar-defc3da58615929db3ea58265d5d8e26.avif) ### 2. Saving selected date in app state variable[​](/resources/ui/widgets/built-in-widgets/calendar.md#2-saving-selected-date-in-app-state-variable "Direct link to 2. Saving selected date in app state variable") To save the selected date in an app state variable, you can utilize the ***On Date Selected*** event and then add actions to update the app state variable: Here are the steps in detail: 1. Select the **Calendar** widget from the widget tree or canvas area. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action flow Editor** in a new popup window. 1. Click on the **+ Add Action**. 2. On the right side, search and select the [**Update App State**](/resources/data-representation/app-state.md#update-app-state-action) action. 3. Set the **Select field to update** to the App State variable **name**. 4. Choose the **Select Update Type** to **Set Value**. 5. Set the **Value Source** to **From Variable**. 6. Set the **Source** to **Widget State**. 7. Set the **Available Options** to the **calendarSelectedDay**. 8. If there is a multiple date selection (date range selection), you can choose which date to pick up. You can choose to set the start or end date by setting the **Range Part** to **Start** or **End**. For a single date selection (which is by default), the start and end date would be the same. ### 3. Showing date in Text widget from an app state variable[​](/resources/ui/widgets/built-in-widgets/calendar.md#3-showing-date-in-text-widget-from-an-app-state-variable "Direct link to 3. Showing date in Text widget from an app state variable") To show the selected date in the Text widget: 1. Select the **Text**, move to the properties panel, and click **Set from Variable**. 2. Select **Source** as **App State** and **Available Options** to the App State Variable **name**. 3. (Optional) Set the **Timestamp Format** to display the date in a specific format. 4. (Optional) Set the default value if you wish to. 5. Click **Confirm**. ## Using a calendar to filter the list[​](/resources/ui/widgets/built-in-widgets/calendar.md#using-a-calendar-to-filter-the-list "Direct link to Using a calendar to filter the list") You might need to use the calendar widget to filter the list of events (appointments, meetings, tickets, etc.). You can do so by applying the filter on the backend query and passing the selected date as a parameter. Let's build an example that shows the Todos list (from the Firestore collection) based on date. Here's how it looks: The steps to use the calendar to filter the list are as follows: ### 1. Prepare data[​](/resources/ui/widgets/built-in-widgets/calendar.md#1-prepare-data "Direct link to 1. Prepare data") Before you use the calendar to filter the list, you need to have a list of items with at least one field that holds the date. This date will be used to match against the date selected from the calendar. Skip if you already have data in such a format. You can create a Firestore collection with a date field like the one below: ![calendar-prepare-data.avif](/assets/images/calendar-prepare-data-9d7b9823e8105bcf236c8ea82005ce28.avif) ### 2. Building UI[​](/resources/ui/widgets/built-in-widgets/calendar.md#2-building-ui "Direct link to 2. Building UI") Your UI must include at least two calendars and ListView widgets. Here's how you add it: 1. Add the **Calendar** widget. To provide a better user experience, you can switch to the week view. 2. Add the **ListView** and show the data from the Firestore collection. ### 3. Apply date filter on backend query[​](/resources/ui/widgets/built-in-widgets/calendar.md#3-apply-date-filter-on-backend-query "Direct link to 3. Apply date filter on backend query") Finally, you can add a filter on the existing backend query or a new one and provide the selected date from the calendar. To apply filter by date: 1. Select **ListView** from the widget tree or the canvas area. 2. Click on the **Backend Query** tab (on the right side of your screen). 3. Query a collection. Skip if you have already done so. 4. Scroll down and click on the **+ Filter** button at the bottom 5. Find the **Field Name**, click on the Unset, and select the field on which you would like to apply the filter. 6. Find the **Relation** dropdown, click on the **Unset** and choose the relation as **Equal To**. 7. Set the **Value Source** to **From Variable**. 8. Set the **Source** to **Widget State**. 9. Set the **Available Options** to the **calendarSelectedDay**. 10. If there is a multiple date selection (date range selection), you can choose which date to pick up. You can choose to include the start or end date by setting the **Range Part** to **Start** or **End**. For a single date selection (by default), the start and end date would be the same. 11. Click **Confirm**. 12. After this, you can display the actual data in UI elements. ## Customizing calendar[​](/resources/ui/widgets/built-in-widgets/calendar.md#customizing-calendar "Direct link to Customizing calendar") The Properties Panel can be used to customize the appearance and behavior of your widget. ### Changing icon color[​](/resources/ui/widgets/built-in-widgets/calendar.md#changing-icon-color "Direct link to Changing icon color") You can change the color of the icons displayed on the top right side of the calendar. to do that: 1. Select **Calendar** from the widget tree or the canvas area. 2. Move to the Properties panel and scroll down to the **Calendar** section. 3. Find the **Icon Colors** property, click on the box next to **Unset**, select the color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. You can also choose the color by clicking the Palette and Simple button. ### Separate title and icons[​](/resources/ui/widgets/built-in-widgets/calendar.md#separate-title-and-icons "Direct link to Separate title and icons") By default, the calendar title (displaying the current month-year) and the icon for changing the month are positioned on the same row. If you wish to place them in separate rows, navigate to the **Properties Panel > Calendar >** and **enable the Two-row Header** option. ### Changing row height[​](/resources/ui/widgets/built-in-widgets/calendar.md#changing-row-height "Direct link to Changing row height") Changing the row height allows you to adjust the calendar height as per your design. To change the row height: 1. Select **Calendar** from the widget tree or the canvas area. 2. Move to the Properties panel and scroll down to the **Calendar** section. 3. Find the **Row Height** property and enter the value. --- # Card The [Card](https://api.flutter.dev/flutter/material/Card-class.html) widget is used to represent some related information in a box with rounded corners and a slight shadow for a 3D effect. For example, you can use a Card widget to show a Business card, restaurant information, movie details, etc. The Card widget is often used with a List to display the item information for a specific record. ![img.png](/assets/images/img-fe542d54ca6413fb02dc2ef49a03ef09.png) ## Adding Card Widget[​](/resources/ui/widgets/built-in-widgets/card.md#adding-card-widget "Direct link to Adding Card Widget") Here's an example of how you can use a Card widget in your project: 1. Open the [Widget Palette](/flutterflow-ui/widget-palette.md) and locate the **Card** widget under the **Layout Elements** tab. You can drag it into your desired location or add it directly from the widget tree or canvas area. 2. Start with adding a `Row` or `Column` widget inside the Card and build the UI as per your requirements. ## Customizing[​](/resources/ui/widgets/built-in-widgets/card.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the Properties Panel. ### Styling the Card[​](/resources/ui/widgets/built-in-widgets/card.md#styling-the-card "Direct link to Styling the Card") Styling helps you customize a widget that matches your design. The Card widget allows you to customize the background color, elevation, and rounded corners. Here's how you stylize the Card widget: 1. Select the **Card** widget and move to the **Properties Panel > Card Properties**. 2. To change the background color, [modify the Color](/resources/ui/widgets/widget-commonalities.md#change-color) property. 3. To change the elevation (depth or Z-axis), enter the value in the **Elevation** property. 4. To create the rounded border, use the **Border Radius** property. For uniform curvature on all sides, use the **Uniform Radius** option by sliding the adjustment bar or inputting your preferred value directly. --- # Carousel The Carousel widget, often called an image slider, is a popular design element used to display a series of images or content in a horizontal or sometimes vertical format. The primary purpose of a carousel slider is to showcase multiple pieces of information, such as images, product features, news articles, or testimonials, within limited screen space. ## Adding Carousel widget[​](/resources/ui/widgets/built-in-widgets/carousel.md#adding-carousel-widget "Direct link to Adding Carousel widget") To add the Carousel widget to your app: 1. Add the **Carousel** widget from the **Layout Elements** tab. 2. By default, it adds four slides and shows the first one in the canvas. In the widget tree, it is represented as **Carousel Page**. To see another slide in the canvas, move to the **Properties Panel >** set the **Active Page** to the slide you want to see. 3. To add a new slide, move to the **Properties Panel > Active Page >** click **+ Add Page**. 4. To delete any slide, select the **Carousel Page** from the widget tree or the canvas area and press the **Delete** key on the keyboard. 5. By default, Carousel Page contains an Image widget; however, you can customize it as per your requirements. ## Customizing[​](/resources/ui/widgets/built-in-widgets/carousel.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Changing the scroll direction[​](/resources/ui/widgets/built-in-widgets/carousel.md#changing-the-scroll-direction "Direct link to Changing the scroll direction") By default, the Carousel comes with a horizontal scroll for the slides. To change the scroll direction to vertical, move to the **Properties Panel > Carousel Properties >** set the **Axis** to **Vertical**. ### Trigger action on slide chang[​](/resources/ui/widgets/built-in-widgets/carousel.md#trigger-action-on-slide-chang "Direct link to Trigger action on slide chang") You might want to trigger an action when the slide is swiped. For example, If your carousel has an auto-play feature, you can listen for slide change events to pause or resume auto-play. You could also have a custom indicator below the Carousel and have it synchronize with the current slide to provide users with clear feedback about their position within the carousel. To trigger action on page or slide change: 1. Select the widget from the widget tree or canvas area. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. You will notice that the **Type of Action** (aka callback) is already set to **On Page Change**. That means actions added under this will be called whenever the slide is swiped. 4. Now you can add any action here. Here is an example showing the snackbar message whenever the slide is swiped. ### Setting initial page index[​](/resources/ui/widgets/built-in-widgets/carousel.md#setting-initial-page-index "Direct link to Setting initial page index") You might want to display a specific slide as soon as it is loaded. To do so, move to the **Properties Panel > Carousel Properties >** enter the **Initial Page Index** value. Please **note** that the slide index starts from 0. So, if you want to set slide 1, you should enter 0. If you want to set slide 2, you should enter 1, and so on. ![set-initial-index](/assets/images/set-initial-index-f1c89cccdedfe9ca381f411e549502f5.png) ### Loop carousel contents[​](/resources/ui/widgets/built-in-widgets/carousel.md#loop-carousel-contents "Direct link to Loop carousel contents") By default, the content of the carousel loops continuously. To stop this behavior, move to the **properties panel > Carousel Properties >** disable **Loop carousel contents**. ### Wrap items in a center widget[​](/resources/ui/widgets/built-in-widgets/carousel.md#wrap-items-in-a-center-widget "Direct link to Wrap items in a center widget") If you want all items in a center position, move to the **properties panel > Carousel Properties >** enable **Wrap items in Center Widget**. ![wrap-items-in-center-widget](/assets/images/wrap-items-in-center-widget-c49c2c41d38f8fde5e18b1922ffe9c11.png) ### Changing Viewport and Shrink factor[​](/resources/ui/widgets/built-in-widgets/carousel.md#changing-viewport-and-shrink-factor "Direct link to Changing Viewport and Shrink factor") You can use the **Viewport Fraction** to change the size of a single item, i.e., the item in the center. The **Shrink Factor** lets you adjust the size of other items, i.e., items that are not in focus. Both the properties accept the value between 0 and 1. where 1 is full size, and 0.5 is half of the actual size. ### Enabling autoplay[​](/resources/ui/widgets/built-in-widgets/carousel.md#enabling-autoplay "Direct link to Enabling autoplay") When autoplay is enabled, the carousel will automatically transition from one slide to the next at regular intervals, determined by the following options: * **Duration**: The amount of time (in milliseconds) that it takes to transition from the current slide to the next. * **Delay**: The amount of time (in milliseconds) that the item remains in the center before moving to the next one. ### Change slide on button press[​](/resources/ui/widgets/built-in-widgets/carousel.md#change-slide-on-button-press "Direct link to Change slide on button press") You might want to allow users to change the slide on button press (e.g., next, previous, and skip buttons) in addition to the swipe. You can do so by adding the **Control Carousel** action on the Tap of a Button widget. Here's how you do it: 1. First, [add the Carousel](/resources/ui/widgets/built-in-widgets/carousel.md#adding-carousel-widget) widget. 2. Add buttons to go to the previous and next pages. 3. Now select any button and define the [Control Carousel](/resources/ui/widgets/built-in-widgets/carousel.md#control-carousel-action) action. *** ## Control Carousel \[Action][​](/resources/ui/widgets/built-in-widgets/carousel.md#control-carousel-action "Direct link to Control Carousel \[Action]") By using this action, you can gain more control over the scrolling behavior of the Carousel widget. For instance, you can enable your users to move to the next or previous slide with a single tap of a button. ### Types of action[​](/resources/ui/widgets/built-in-widgets/carousel.md#types-of-action "Direct link to Types of action") These are the types of actions you can add on the Carousel widget. * **Previous**: Scroll to the previous slide. * **Next**: Scroll to the next slide. * **First**: Scroll to the first slide. * **Last**: Scroll to the last slide. * **Jump to**: Scroll to a specific slide in the Carousel widget. Please note that the slide index starts from 0. So, if you want to jump to slide 1, you should enter 0. If you want to jump to slide 2, you should enter 1, and so on. --- # Bar Chart The Bar Chart shows the rectangular bars on a graph whose height varies as per its numeric value and has equal width. This can be used to display categorical information. For example, you could use the Bar chart to display each year's income and expense value together. ## Adding bar chart[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#adding-bar-chart "Direct link to Adding bar chart") Adding a chart comprises of following steps: 1. [Preparing data](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#1-preparing-data) 2. [Adding bar chart widget](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#2-adding-bar-chart-widget) ### 1. Preparing data[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#1-preparing-data "Direct link to 1. Preparing data") Before adding the chart widget, you need to prepare the data in the format that the chart widget accepts. The bar chart widget requires label values (runs horizontally from left to right) and Y coordinate values (runs vertically from bottom to top). Together these values (labels and Y coordinate) are used to draw bars on a chart. You can store and retrieve these values in the following ways: 1. [Firestore Documents](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#11-firestore-documents) 2. [Numbers Lists](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#12-numbers-lists) #### 1.1 Firestore Documents[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#11-firestore-documents "Direct link to 1.1 Firestore Documents") If you use Firebase as the backend, you can create a collection and add the list of documents. Each document entry can be used to draw bars on the chart. Hence you must add at least two fields (one with DataType String and another with DataType Double) in a document that will act as the labels, and the Y coordinates value to draw a bar. The figure below illustrates the sample collection that draws the two bars (income and expense) for each year. ![bar-collection-to-document.avif](/assets/images/bar-collection-to-document-d00625c51027b8e8a06a6edb1023390f.avif) warning The above collection schema is used for simplification. You are free to have your own schema that works best for you. Here's how the data is used to draw bars on a chart: ![firestore-data.avif](/assets/images/firestore-data-d5adcaee1b95538e3c6a04aa5c2523e6.avif) #### 1.2 Numbers Lists[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#12-numbers-lists "Direct link to 1.2 Numbers Lists") The bar chart widget can draw a bar using a list of labels and numbers. You need at least two separate lists with DataType String and Double. One list stores a list of labels to be displayed on the X-axis, whereas the other stores a list of values on the Y-axis. The chart widget uses both variables to draw the bar. info The variable can be an app state variable or the action output variable of an API call. The figure below illustrates the sample app state variables that draw the two bars (income and expense) for each year. ![app-state-variable.avif](/assets/images/app-state-variable-c1ba443e4de61ab35664680f5bd7900b.avif) warning A number of values in the Y-axis variable should match the number of labels in the X-axis variable. Here's how the number list is used to draw bars on a chart: ![app-state-variable-2.avif](/assets/images/app-state-variable-2-4d34ce331379057fe557b186e42b310d.avif) To create the app state variable, please find the instructions [here](/resources/data-representation/app-state.md#create-app-state-variable). ### 2. Adding bar chart widget[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#2-adding-bar-chart-widget "Direct link to 2. Adding bar chart widget") To add the bar chart widget to your project: 1. Drag the **Chart** widget from the **Base Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. Move to the property panel and set the **Chart Type** to **Bar**. 3. For the Bar Chart, a single **Chart Data** is a **Bar** drawn on the chart. The bar is drawn by providing the data to this. To show the first bar, open the **Chart Data 1** section, and set the **Data Source** to [Firestore Documents](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#11-firestore-documents) or [Numbers List](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#12-numbers-lists). 4. If you select **Firestore Documents**: 1. Make sure you have access to a list of documents. The list of documents can be retrieved by querying a collection at any top-level widget, such as the **Page** or **Column** widget. You can also query a collection on the Chart widget itself. To query collection on a page: 1. Select the **page** and then click on the **Backend Query** tab (on the right side of your screen). 2. Set the **Query Type** to **Query Collection**. 3. Scroll down to find the **Collection** dropdown and set it to your collection. 4. Set the **Query Type** to **List of Documents**. 5. To order the labels, you can perform Ordering on a query. 6. Click **Save**. 2. Set the Source to the **collection\_name Documents > Documents (List/)** and click **Confirm** (e.g. *transactions Documents > Documents (List/)*). 3. Set the **Bar Labels Field,** whose values will be used as labels, and lay out horizontally from left to right (e.g., day, week, month, year). 4. Set the **Bar Values Field,** whose values will be used to draw bars on a chart. This will draw bars for the first chart data (e.g., income data). 5. If you select **Numbers Lists**: 1. Under the **Bar Labels**, click on the **UNSET** and set it to a variable whose values will be used as labels and lay out horizontally from left to right (e.g., day, week, month, year). 2. Further options are displayed as per the selected source. For example, if you choose **App State**, The **Available Option** field is displayed that allows you to select the actual variable. 3. Under the **Bar Values**, click on the **UNSET** and set it to a variable whose values will be used to draw bars on a chart. This will draw bars for the first chart data (e.g., income data). 6. Click **Add Data** to show bars for multiple categories (e.g., income and expense). The bars for each new category are displayed next to the previous one. **Note**: When you click **Add Data**, you can only set **Bar Values Field** since the **Bar Labels Field** is already provided in the first **Chart Data**. 7. Scroll down to the **Chart Properties** section and adjust the **Width** and **Height** properties. * Using Firestore Documents * Using Numbers Lists ## Customizing bars[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#customizing-bars "Direct link to Customizing bars") You can customize the look and feel of bars to match your design. ### Customize bar for an individual chart data[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#customize-bar-for-an-individual-chart-data "Direct link to Customize bar for an individual chart data") You can customize the bar for each specific chart data to help users easily identify the information. To customize the bar for each chart data: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and open the **Chart Data** > **Bar Properties**. 3. To change the **Bar Color**, click on the box next to the already selected color, select any dark/light color, and then click **Use Color** or click on an already selected color and enter a Hex Code directly. 4. To add a border around the bar, enter the **Border Width** value and change its **Border Color**. ### Customize all bars[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#customize-all-bars "Direct link to Customize all bars") To customize all bars together: 1. To change the bar width, scroll down the **Bar Styling properties > Bar Width** and enter the value. 2. To add space between two bars or two bars category (if you have multiple chart data), enter the value in the **Group Spacing** property. 3. If you have multiple chart data and want to add space between two adjacent bars, you can enter a value in the **Bar Spacing** property. 4. To combine multiple chart data and display it as a single bar, enable the **Stack Bars**. 5. To change how the bars should be distributed horizontal direction, choose from the **Main Axis Alignment** options. ## Customizing chart[​](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md#customizing-chart "Direct link to Customizing chart") You can [customize the chart](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-chart) to match your design by changing the background color, setting axis bounds, showing grids, displaying borders, and more. --- # Chart The chart widget is used to represent the information in a graphical format. You can use it to display complex information in an easily understandable format. ## Types of chart[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#types-of-chart "Direct link to Types of chart") You can add the following types of charts: 1. [Line Chart](/resources/ui/widgets/built-in-widgets/chart/line-chart.md) 2. [Bar Chart](/resources/ui/widgets/built-in-widgets/chart/bar-chart.md) 3. [Pie Chart](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md) ## Customizing chart[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-chart "Direct link to Customizing chart") Using *Chart* Properties (inside the properties panel), you can customize the appearance and behavior of the widget. info The following instructions will have a similar effect on the Bar chart. ### Showing legend[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#showing-legend "Direct link to Showing legend") Legend helps users identify the data drawn over the chart. It's a small box that shows the chart data name/text next to its color (a color used to draw a line or bar). ![legend.webp](/assets/images/legend-21e11cdd278ca1c7777961e548dfebcc.webp) To show legend: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel and open **Chart Data 1**. 3. Enter the **Legend** name. This will be displayed as the name of the line or bar. 4. If you have multiple chart data (e.g., Chart Data 1, Chart Data 2, and so on), set the legend for them as well. 5. Scroll down to **Chart Properties** and enable the **Show Legend** property. ### Customizing legend box[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-legend-box "Direct link to Customizing legend box") You can change the appearance of the legend box by following the instructions below: 1. First, [enable the legend](/resources/ui/widgets/built-in-widgets/chart/chart.md#showing-legend). 2. Scroll down to the **Legend Properties** section. 3. To change the dimension of the legend box, enter the **Width** and **Height** values. 4. The legend box typically appears over the chart on the bottom right side. To change its position, use the **Horizontal** and **Vertical** **Alignment** slider. 5. To change the background color, find the **Background Color** property and click on the box next to **Unset**, select the color, then click **Use Color** or click on **Unset** and enter a Hex Code directly. 6. To customize the border, use the **Border Color**, **Border Width,** and **Border Radius**. 7. To add space between legend text and its box border, adjust **Padding** property. ### Customizing legend text and indicator[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-legend-text-and-indicator "Direct link to Customizing legend text and indicator") To customize the legend text and indicator: 1. First, [enable the legend](/resources/ui/widgets/built-in-widgets/chart/chart.md#showing-legend). 2. To style the legend text, scroll down to the **Legend Properties** > **Legend Text Properties** and change the style as per [here](/resources/ui/widgets/text.md#common-text-styling-properties). 3. To add space between the indicator and the text, adjust the **Text Padding** property. 4. You can change the indicator size by entering a value inside the **Indicator Size** property. 5. To create rounded corners around the indicator, you can use the **Indicator Border Radius** property. ### Changing background color[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#changing-background-color "Direct link to Changing background color") The default background color for the chart widget is white. To change the background color: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Chart Properties** section. 3. Find the **Background Color** property, click on the box next to **Unset**, select any dark/light color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. You can also choose the color by clicking on the Palette and Simple buttons. ### Set axis bounds[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#set-axis-bounds "Direct link to Set axis bounds") Axis Bounds specify limits on the axis. You can set the minimum and maximum limits on the X and Y axes. You can set four types of bounds on a chart: 1. **Min X** (only applicable in Line Chart): Specifies a number at which the X-axis should start. 2. **Min Y**: Specifies a number at which the Y-axis should start. 3. **Max X** (only applicable in Line Chart): Specifies a number at which the X-axis should end. 4. **Max Y**: Specifies a number at which the Y-axis should end. info If you don't specify the axis bounds, the start and end numbers for the X and Y axis are set as per the min and max of the actual data. To set the axis bounds: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Chart Properties** section. 3. Find the **Axis Bounds** section and enter the **Min X**, **Min Y**, **Max X**, and **Max Y** values. * Chart without axis bounds * Chart with axis bounds ![chart-without-axis-bound.png](/assets/images/chart-without-axis-bound-8d6081e86da1e26e8adac134dfea2a4a.png) The line chart with bounds set to **Min X:0 ,Min Y:0, Max X:7 and Max Y:100** looks like this: ![chart-with-axis-bound.avif](/assets/images/chart-with-axis-bound-fa38946dc2d501e275ed529634a91f12.avif) ### Showing grid[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#showing-grid "Direct link to Showing grid") To display the grid on the chart background: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Chart Properties** section. 3. Find the **Show Grid** toggle and **enable** it. ### Showing border[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#showing-border "Direct link to Showing border") To display a border around the chart: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Chart Properties** section. 3. Find the **Show Border** toggle and **enable** it. 4. Find the **Border Color** property, click on the box next to **Black**, select the color, and then click **Use Color** or click on **Black** and enter a Hex Code directly. 5. Now, find the **Border Width** property below and enter the value. (e.g. 2,5,10) ### Showing tooltip[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#showing-tooltip "Direct link to Showing tooltip") Sometimes it becomes difficult to identify the exact Y value. To overcome this, you can enable the tooltip. Enabling the tooltip will display the Y value when you interact with the chart. You can also add background color to the tooltip. To enable tooltip: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Chart Properties** section. 3. Find the **Show Border** toggle and **enable** it. 4. To change the background color, find the **Tooltip Background Color** property, click on the box next to **Unset**, select any dark/light color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. ### Customizing X axis (Show name, number, and labels)[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-x-axis-show-name-number-and-labels "Direct link to Customizing X axis (Show name, number, and labels)") You can customize the X axis to display names and numbers on it. ### Displaying name on X-Axis[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#displaying-name-on-x-axis "Direct link to Displaying name on X-Axis") To show the name on the axis, such as day, week, and month: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Chart Properties** section. 3. Scroll down to the **X Axis Properties** and enter the value in the **Text** input box. You can also set the name from a variable by clicking on the **Set from Variable text**. 4. You can also customize the appearance of the name text. ### Displaying numbers or labels on the X axis[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#displaying-numbers-or-labels-on-the-x-axis "Direct link to Displaying numbers or labels on the X axis") Displaying numbers or labels on the axis helps you quickly understand the graph. **For Line Chart** If you have set the [Axis bounds](/resources/ui/widgets/built-in-widgets/chart/chart.md#set-axis-bounds), the start and end numbers are displayed as per the value set in **Min X** and **Max X**. Otherwise, they are shown as per the min and max values of the actual data. You can also specify the intervals between the numbers. To display numbers on the X-axis: 1. Select the **Chart** widget, head over to the properties panel, and scroll down to the **Chart Properties** section. 2. Scroll down to the **X Axis Properties** and enable the **Show Label** option. 3. When it comes to displaying numbers, it's usually acceptable to show up to two digits as is. However, if the number exceeds that limit, it's recommended to set the **Label Format Type** to **Number** and configure the appropriate **Number Format Options**. 4. Enter the value in the **Label Interval** input box. 5. You can also customize the appearance of the numbers. * Displaying numbers on the X axis * Displaying numbers (with formatting) on the X axis info For the bar chart, you can only display labels on X-axis. ### Customizing Y axis (Show name and numbers)[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-y-axis-show-name-and-numbers "Direct link to Customizing Y axis (Show name and numbers)") You can customize the Y axis to display names and numbers on it. ### Displaying name on Y-axis[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#displaying-name-on-y-axis "Direct link to Displaying name on Y-axis") To show the name on the axis, such as progress, number of users, and sales: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Chart Properties** section. 3. Scroll down to the **Y-Axis Properties** and enter the value in the **Text** input box. You can also set the name from a variable by clicking on the **Set from Variable text**. 4. You can also customize the appearance of the name text. ### Displaying numbers on the Y axis[​](/resources/ui/widgets/built-in-widgets/chart/chart.md#displaying-numbers-on-the-y-axis "Direct link to Displaying numbers on the Y axis") Just like X-axis, If you have set the [Axis bounds](/resources/ui/widgets/built-in-widgets/chart/chart.md#set-axis-bounds), the start and end numbers are displayed as per the value set in **Min Y** and **Max Y**. Otherwise, they are shown as per the min and max value of the actual data. You can also specify the intervals between the numbers. To display numbers on the Y axis: 1. Select the **Chart** widget, head over to the properties panel, and scroll down to the **Chart Properties** section. 2. Scroll down to the **Y Axis Properties** and enable the **Show Label** option. 3. When it comes to displaying numbers, it's usually acceptable to show up to two digits as is. However, if the number exceeds that limit, it's recommended to set the **Label Format Type** to **Number** and configure the appropriate **Number Format Options**. 4. Enter the value in the **Label Interval** input box. 5. You can also customize the appearance of the numbers. * Displaying numbers on the Y axis * Displaying numbers (with formatting) on the Y axis --- # Line Chart The Line Chart connects the data points on a graph with a line. This is typically used to display information that evolves over time. For example, you could use this widget to show progress over some time. This will plot the progress value on a chart that becomes easily digestible for the users instead of just showing numbers in a tabular format. ## Adding line chart[​](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#adding-line-chart "Direct link to Adding line chart") Adding a chart comprises of following steps: 1. [Preparing Data](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#1-preparing-data) 2. [Adding Chart widget](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#2-adding-chart-widget) ### 1. Preparing Data[​](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#1-preparing-data "Direct link to 1. Preparing Data") Before adding the chart widget, you need to prepare the data in the format that the chart widget accepts. The line chart widget requires an X coordinate (runs horizontally from left to right) and a Y coordinate (runs vertically from bottom to top) value. Together these values (x,y) are used to mark a point in the chart. You can store and retrieve these values in the following ways: 1. [Firestore Documents](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#11-firestore-documents) 2. [Numbers Lists](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#12-numbers-lists) #### 1.1 Firestore Documents[​](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#11-firestore-documents "Direct link to 1.1 Firestore Documents") If you use Firebase as the backend, you can create a collection and add the list of documents. Each document entry is used to plot a single point on the chart. Hence you must add at least two fields (with DataType Integer or Double) in a document that acts as the X and Y coordinates to plot the point. The figure below illustrates the sample collection that will draw a single line on the chart: ![collection-to-document.avif](/assets/images/collection-to-document-48ad2e91983c8635ce6a1a030f46df6a.avif) warning The above collection schema is used for simplification. You are free to have your own schema that works best for you. Here's how the data is used to mark a point in a chart: ![firestore-data-to-chart.avif](/assets/images/firestore-data-to-chart-07e36e7338130cd653b6eed0628b42e5.avif) #### 1.2 Numbers Lists[​](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#12-numbers-lists "Direct link to 1.2 Numbers Lists") The chart widget can plot a point using a list of numbers. You must create at least two separate lists with DataType Integer or Double. One list stores all X-axis values, whereas the other stores a list of all Y-axis values. The chart widget uses both variables to create pair of (x,y), which are then used to mark a point in the chart. info The variable can be an app state variable or the action output variable of an API call. warning You must have at least two variables to draw a single line. The figure below illustrates what the app state variables should look like: ![app-state-variables.avif](/assets/images/app-state-variables-d36db8be8677f9c3e9700215bbd341d2.avif) Here's how the number list is used to mark a point in a chart: ![numbers-to-chart.avif](/assets/images/numbers-to-chart-15ec524e67beb7f6c6393230d8f4cc04.avif) To create the app state variable, please find the instructions [here](/resources/data-representation/app-state.md#create-app-state-variable). ### 2. Adding Chart widget[​](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#2-adding-chart-widget "Direct link to 2. Adding Chart widget") To add the chart widget to your project: 1. Drag the **Chart** widget from the **Base Elements** tab (in the Widget Panel) or add it directly from the widget tree. **Note**: The Line Chart is the default chart type. 2. Move to the property panel and scroll down to the **Chart Data** section. 3. For the Line Chart, **Chart Data** is a **line** drawn on the chart. The line is drawn by providing data to this. To show the first line, open the **Chart Data 1** section, and set the **Data Source** to [Firestore Documents](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#11-firestore-documents) or [Number List](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#12-numbers-lists). 4. If you select **Firestore Documents**: 1. Make sure you have access to a list of documents. The list of documents can be retrieved by querying a collection at any top-level widget, such as **Page** or **Column** widget. You can also query a collection on the Chart widget itself. To query collection on a page: 1. Select the **page** and then click on the **Backend Query** tab (on the right side of your screen). 2. Set the **Query Type** to **Query Collection**. 3. Scroll down to find the **Collection** dropdown and set it to your collection. 4. Set the **Query Type** to **List of Documents**. 5. Click **Save**. 2. Set the Source to the **collection\_name Documents > Documents (List\)** and click **Confirm** (e.g. *progress Documents > Documents (List\)*). 3. Set the **X Value Field,** whose values will lay out horizontally from left to right (e.g., day, week, month). 4. Set the **Y Value Field,** whose values will lay out vertically from bottom to top (e.g., progress, number of users, sales). 5. If you select **Numbers Lists**: * Under the **X Data**, click on the **UNSET** and set it to a variable whose values will lay horizontally from left to right (e.g., day, week, month). * Further options are displayed as per the selected Source. For example, if you choose **App State**, The **Available Option** field is displayed that allows you to select the actual variable. * Under the **Y Data**, click on the **UNSET** and set it to a variable whose values will lay out vertically from bottom to top (e.g., progress, number of users, sales). 6. Click **Add Data** to show multiple lines on a chart. Each new line is stacked on top of the previous line. 7. Scroll down to the **Chart Properties** section and adjust the **Width** and **Height** properties. * Using Firestore Documents * Using Numbers Lists ## Customizing line[​](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#customizing-line "Direct link to Customizing line") You can customize the look and feel of each line drawn on a chart widget to match your design. The Line Properties (inside the Chart Data) section is used the customize the line. To customize the line: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, open the **Chart Data** section and then open the **Line Properties** section. 3. To change the **Line Color**, click on the box next to the already selected color, select any dark/light color, and then click **Use Color** or click on an already selected color and enter a Hex Code directly. 4. To change the thickness of the line, change the value in the **Line Thickness** input box. 5. By default, all the data points are connected with a smooth curve line; to disable this, simply **turn off** the **Curved Lines** property. This will draw a straight line between two points. 1. If you keep this property enabled, you may notice that for some data points the curve goes beyond/above the actual value. To prevent this, you can **enable** the **Prevent curve from overshooting**. 6. To see the point at the exact location on the chart, you can turn on the **Show Dots** property. 7. To fill the area below the line with a custom color, turn on the **Fill Below Line** property and set the **Fill Color** by clicking on the box next to **Unset**, select any dark/light color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. ## Customizing chart[​](/resources/ui/widgets/built-in-widgets/chart/line-chart.md#customizing-chart "Direct link to Customizing chart") You can [customize the chart](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-chart) to match your design such as changing the background color, setting axis bounds, show grids, displaying borders, and more. --- # Pie Chart The Pie Chart divides the circle (aka Donut) into slices/sections representing different categories. Each section shows the size of the data. It is typically used to display how a total amount is distributed between sections. For example, you could use the Pie Chart to show which animal dominates the pet world. ## Adding pie chart[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#adding-pie-chart "Direct link to Adding pie chart") Adding a pie chart comprises of the following steps: 1. [Preparing data](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#1-preparing-data) 2. [Adding pie chart widget](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#2-adding-pie-chart-widget) ### 1. Preparing data[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#1-preparing-data "Direct link to 1. Preparing data") Before adding the chart widget, you need to prepare the data in the format that the chart widget accepts. The pie chart widget requires labels and section values. Together these values are used to draw slices on a chart. You can store and retrieve these values in the following ways: 1. [Firestore Documents](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#11-firestore-documents) 2. [Numbers Lists](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#12-numbers-lists) 3. [Single Value](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#13-single-value) #### 1.1 Firestore Documents[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#11-firestore-documents "Direct link to 1.1 Firestore Documents") If you use Firebase as the backend, you can create a collection and add the list of documents. Each document entry can be used to draw sections on the chart. Hence you must add at least two fields (one with DataType String and another with DataType Integer or Double) in a document. The field with String DataType will be used as labels, whereas the field with Integer or Double DataType will be used as section values. The figure below illustrates the sample collection that draws three sections on the pie chart. ![pie-collection-document.avif](/assets/images/pie-collection-document-b527fe46d6e4adfee4e144c96206def9.avif) warning The above collection schema is used for simplification. You are free to have your own schema that works best for you. Here's how the data is used to draw sections on a pie chart: ![pie-firestored-data.avif](/assets/images/pie-firestored-data-77f4b01376f3946949fa7cef7c716f72.avif) #### 1.2 Numbers Lists[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#12-numbers-lists "Direct link to 1.2 Numbers Lists") The pie chart widget can draw sections using a list of labels and numbers. You need at least two different lists with DataType String and Integer or Double. One list stores a list of labels, whereas the other stores a list of section values. info The variable can be an app state variable or the action output variable of an API call. The figure below illustrates the sample app state variables that draw three sections on the pie chart. ![pie-app-state-variable.avif](/assets/images/pie-app-state-variable-f8fbd7bb5de5988c355a569ab20d847a.avif) warning The number of section values should match the number of labels. Here's how the number list is used to draw sections on a chart: ![pie-app-state-variable-2.avif](/assets/images/pie-app-state-variable-2-4f157cb01320088e4cbf59af5127cc6b.avif) To create the app state variable, please find the instructions [here](/resources/data-representation/app-state.md#create-app-state-variable). #### 1.3 Single Value[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#13-single-value "Direct link to 1.3 Single Value") When you have a fixed number of labels (aka static labels, which won't change over time), you can use this option. This option allows you to define labels and their section value from a variable. info The variable can be an app state variable or the action output variable of an API call. Here's how the three separate app state variables are used to draw sections on a chart: ![pie-single-value.avif](/assets/images/pie-single-value-5319c32f46c024aa5dd2b764fc502a5c.avif) ### 2. Adding pie chart widget[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#2-adding-pie-chart-widget "Direct link to 2. Adding pie chart widget") To add the pie chart widget to your project: 1. Drag the **Chart** widget from the **Base Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. Move to the property panel and set the **Chart Type** to **Pie**. 3. For the Pie Chart, a single **Chart Data** is a **Section** drawn on the chart. The section is drawn by providing the data to this. Open the **Chart Data 1** section, and set the **Data Source** among the [Firestore Documents](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#11-firestore-documents), [Numbers List](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#12-numbers-lists), and [Single Value](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#13-single-value). 4. If you select **Firestore Documents**: 1. Make sure you have access to a list of documents. The list of documents can be retrieved by querying a collection at any top-level widget, such as the **Page** or **Column** widget. You can also query a collection on the Chart widget itself. To query collection on a page: 1. Select the **page** and then click on the **Backend Query** tab (on the right side of your screen). 2. Set the **Query Type** to **Query Collection**. 3. Scroll down to find the **Collection** dropdown and set it to your collection. 4. Set the **Query Type** to **List of Documents**. 5. Click **Save**. 2. Under the **Data**, click on the **UNSET** and set the source to the **collection\_name Documents > Documents (List/)** and click **Confirm** (e.g., *pets Documents > Documents (List/)*). 3. Set the **Legend Labels Field,** whose values will be used as labels. 4. Set the **Section Values Field,** whose values will be used to draw sections on a chart. 5. To set the section color, scroll down to **Pie Chart Properties > Pie Chart Color** and click on **Add Color**. **Note**: Make sure the number of colors you have must be equal to or greater than the number of labels. Otherwise, all sections would have the same colors. 5. If you select **Numbers Lists**: 1. Under the **Legend Labels**, click on the **UNSET** and set it to a variable whose values will be used as labels. 2. Further options are displayed as per the selected source. For example, if you choose **App State**, The **Available Option** field is displayed allowing you to select the actual variable. 3. Under **Section Values**, click on the **UNSET** and set it to a variable whose values will be used to draw sections on a chart. 4. To set the section color, scroll down to **Pie Chart Properties > Pie Chart Color** and click on **Add Color**. **Note**: Make sure the number of colors you have must be equal to or greater than the number of labels. Otherwise, all sections would have the same colors. 6. If you select **Single Value**: 1. Under **Section Value**, click on the **UNSET** and set it to a variable whose value will be used to draw the first section. 2. Further options are displayed as per the selected source. For example, if you choose **App State**, The **Available Option** field is displayed allowing you to select the actual variable. 3. Click **Add Data** to show multiple sections (e.g., Dogs, Cats, Birds). **Note**: This option is only available when using Single Value. 7. Scroll down to the **Chart Properties** section and adjust the **Width** and **Height** properties. * Using Firestore Documents * Using Numbers Lists * Using Single Value ## Customizing section[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#customizing-section "Direct link to Customizing section") You can customize the look and feel of each section to match your design by following the instructions below: 1. Select the **Chart** widget from the widget tree or the canvas area. 2. Move to the properties panel, and open the **Chart Data** > **Pie Chart Properties**. 3. To change the size of the circle, enter the value in the **Pie Chart Radius** property. 4. To add a border around the section, enter the **Border Width** value and change its **Border Color**. 5. To create an inner circle (hole) inside the main circle(Donut), enter the size into the **Donut Hole Radius** property. 1. To change the **Donus Hole Color**, click on the box next to the already selected color, select any dark/light color, and then click **Use Color** or click on an already selected color enter a Hex Code directly. 6. To display the section value or its percentage, set the **Section Lable Type** to **Value** or **Percent** respectively. ## Showing legend[​](/resources/ui/widgets/built-in-widgets/chart/pie-chart.md#showing-legend "Direct link to Showing legend") Legend helps users identify the data drawn over the chart. It's a small box that shows the chart data name/text (label) next to its color (a color used to draw a section). To show and customize the legend follow the instructions [here](/resources/ui/widgets/built-in-widgets/chart/chart.md#customizing-chart). --- # CountController The CountController widget is used to increment and decrement the count or number. You could use the CountController widget to set the quantity of any product when buying in an e-commerce app. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding CountController to your project[​](/resources/ui/widgets/built-in-widgets/count-controller.md#adding-countcontroller-to-your-project "Direct link to Adding CountController to your project") Here's an example of how you can use a CountController widget in your project: 1. First, drag the **CountController** widget from the **Form Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. Move to the properties panel (in the right) and scroll down to the **Count Controller Properties**. 3. The number on CountController appears as soon as it is loaded, called the Initial Count, 0 by default. To change this initial count, enter the value in the **Initial Count** input box. You can also set this value dynamically by having it **Set from Variable**. This can be used to display the default quantity of a product in an E-commerce app. 4. The Step Size property sets the value by which the count should be increased or decreased. The default value is 1. To change this, enter the value in the **Step Size** input box. 5. To allow users to set the valid count or quantity, you can limit the CountController range (min and max count) by specifying the value in the **Minimum** and **Maximum** input boxes. ## Trigger action on count change[​](/resources/ui/widgets/built-in-widgets/count-controller.md#trigger-action-on-count-change "Direct link to Trigger action on count change") Let's see how to trigger an action when the count changes on this widget. This is helpful when you want to update the latest count in your backend (make API call, create/update Firestore document) as the count changes. To do so: 1. Select **CountController**, select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 2. You will notice that the **Type of Action** (aka callback) is already set to **On Count Changed**. That means actions added under this will be called whenever the count changes. 3. Now you can add any action here. Here is an example of updating the count in an [app state variable](/resources/data-representation/app-state.md). ## Customizing CountController[​](/resources/ui/widgets/built-in-widgets/count-controller.md#customizing-countcontroller "Direct link to Customizing CountController") The Properties Panel can be used to customize the appearance and behavior of your widget. ### Customizing icon[​](/resources/ui/widgets/built-in-widgets/count-controller.md#customizing-icon "Direct link to Customizing icon") To customize the decrement icon: 1. Select the **CountController** widget from the widget tree or the canvas area. 2. Move to the properties panel, and find the **Style Properties** section. 3. To change the icon, click on the already selected icon and then search and select the new icon. 4. To change the icon size, enter the value in the **Icon Size** property. 5. To change the icon color, find the **Icon Color** property, click on the box next to the selected color, select the color, and click **Use Color** or click on **Unset** and enter a Hex Code directly. --- # CreditCardForm The CreditCardForm widget allows users to enter their credit card details such as card number, expiry date, and CVV. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding CreditCardForm widget[​](/resources/ui/widgets/built-in-widgets/credit-card-form.md#adding-creditcardform-widget "Direct link to Adding CreditCardForm widget") Here's an example of how you can add the CreditCardForm widget to your project: 1. First, drag the **CreditCardForm** widget from the **Form Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. When you type in, the card number gets obscured (number becomes •, i.e., dot). To disable this feature and allows users to see the full number, move to the properties panel, find the **Obscure Card Number** toggle and turn it off. ## Customizing[​](/resources/ui/widgets/built-in-widgets/credit-card-form.md#customizing "Direct link to Customizing") You can customize the behavior and appearance of this widget using the various properties available under the properties panel. ### Obscuring CVV[​](/resources/ui/widgets/built-in-widgets/credit-card-form.md#obscuring-cvv "Direct link to Obscuring CVV") By default, the CVV number is visible when you type in. It's essential that you obscure (number becomes •, i.e. dot) it. To obscure the CVV: 1. Select the **CreditCardForm** widget from the widget tree or the canvas area. 2. Move to the properties panel, find the **Obscure CVV** toggle and turn it on. ### Adding background color[​](/resources/ui/widgets/built-in-widgets/credit-card-form.md#adding-background-color "Direct link to Adding background color") To change the background color of the fields: 1. Select **CreditCardForm** from the widget tree or the canvas area. 2. Move to the Properties panel and scroll down to the **Input Decoration Properties** section. 3. Find the **Fill** toggle and turn it on. 4. Now find the **Fill Color** property, click on the box next to **Unset**, select the color, and then click **Use Color** or click on **Unset** and enter a Hex Code directly. You can also choose the color by clicking on the Palette and Simple buttons. ### Customizing border[​](/resources/ui/widgets/built-in-widgets/credit-card-form.md#customizing-border "Direct link to Customizing border") To customize the border around the credit card fields: 1. Select **CreditCardForm** from the widget tree or the canvas area. 2. Move to the Properties panel and scroll down to the **Input Decoration Properties** section. 3. Select from the **Input Border Type** dropdown. 1. Choose **Outline** to place a border around the entire field. 2. Choose **Underline** to place a border only on the bottom of the field. 3. Choose **None** to eradicate the border. 4. Scroll down a bit to find the **Border Color** property, click on the box next to the already selected color, select the color, and then click **Use Color** or click on an already selected color and enter a Hex Code directly. You can also choose the color by clicking on the Palette and Simple buttons. 5. Find the **Border Width** property below, and enter the desired value. 6. Now, Enter the **Border Radius** property and enter the value as 50. By default, the value 50 will be set for all corners, which are TL (Top left), TR (top right), BL (bottom left), and BR (bottom right). Click on the lock icon to change each corner separately. ### Add content padding[​](/resources/ui/widgets/built-in-widgets/credit-card-form.md#add-content-padding "Direct link to Add content padding") Content padding adds space between the field text and the border. To add the content padding: 1. Select **CreditCardForm** from the widget tree or the canvas area. 2. Move to the Properties panel (on the right side of your screen) and scroll down to the **Input Decoration Properties** section. 3. Find the **Content Padding** property and enter the values for L(left), T(top), R(right), and B(bottom) input boxes. ### Reducing field height[​](/resources/ui/widgets/built-in-widgets/credit-card-form.md#reducing-field-height "Direct link to Reducing field height") You might want to reduce the field height to match your design. Using the dense property, you can reduce the field height to a predefined size. To reduce the field height: 1. Select **CreditCardForm** from the widget tree or the canvas area. 2. Move to the Properties panel (on the right side of your screen) and scroll down to the **Input Decoration Properties** section. 3. Find the **Dense** toggle and turn it on. --- # DataTable (Paginated) The DataTable is a widget used to display data in a table format. It organizes information into rows and columns, similar to a spreadsheet, making it easier to read and understand large amounts of data. For example, you could use it to display a list of employees in a company, with each row representing an individual employee and the columns showing the employee's name, age, department, and salary. Additionally, this widget supports pagination, which can handle large datasets by displaying them in manageable chunks. ![paginated-data-table-fi](/assets/images/paginated-data-table-fi-5210ba954854e542064291691155495e.avif) ## Adding DataTable widget[​](/resources/ui/widgets/built-in-widgets/datatable.md#adding-datatable-widget "Direct link to Adding DataTable widget") Let's see how to add a DataTable widget by building an example that shows a list of all employees in a company. Here's how it looks: The steps to add DataTable and display the employees' details are: 1. Open the [Widget Palette](/flutterflow-ui/widget-palette.md) and locate the **DataTable** widget under the **Layout Elements** tab. You can drag it into your desired location or add it directly from the widget tree or canvas area. 2. It adds two types of predefined widgets: 1. **DataTableHeader**: This refers to the top row of the table, which displays the names of the columns. To change its text, click on the **DataTableHeader > Text** widget, move to the properties panel and give it a name. 2. **DataTableCell**: This displays the actual data. By default, it comes with the Text widget. However, you can replace it with any other widget based on your requirements. ![data-table-header](/assets/images/data-table-header-5386eefbd8baa55687bfd6095b2ac4f1.avif) 3. By default, it shows three columns. To show more, select the **DataTable** widget, move to the **properties panel > Paginated Data Table Properties >** enter the **Number of Columns** you want. 4. For the demonstration purpose, let's display data from Firestore: 1. First, ensure you have created a collection. 2. *It's **important to note** that, unlike other widgets, you cannot directly have a backend query on the DataTable widget. Because if you do so, you won't have access to the query result (list of employees) for further use, such as sorting and searching. Hence, getting the backend query result on a parent widget and then using that result to populate DataTable is advisable.* 3. For this example, on page load, we'll add a Query Collection action and save the result in a page state variable. 4. On the **DataTable** widget, generate dynamic children using the page state variable (which holds a list of employees). 5. Display data in the **DataTableCell > Text**. ## Sorting[​](/resources/ui/widgets/built-in-widgets/datatable.md#sorting "Direct link to Sorting") The way sorting works in a DataTable is as follows: first, you mark the column to sort. Then, whenever a user clicks on a header, you receive an *OnSortChanged* callback with two properties: `Sorted Column Index` and `Is Ascending`. You consume both properties in a custom function to write a sorting logic. * **`Sorted Column Index`** specifies the column by which the data should be sorted (0 for first column, 1 for the second column and so on). * **`Is Ascending`** determines the sort direction (true for ascending order, false for descending order). info **Remember**, sorting is not performed automatically by the DataTable widget. It provides you the flexibility to implement your own sorting logic through a Custom Function. Let's extend the previous example and see how you can enable sorting on columns. Here's how it looks: To enable sorting: 1. Select the **DataTableHeader**, move to the **Properties Panel**, and turn on the **Sortable** toggle. Apply this to each column you want to sort 2. Select the DataTable widget, select **Actions** from the Properties panel, and open **Action Flow Editor**. 3. Select the **On Sort Changed**. Actions added under this will be triggered whenever the user clicks on any column header that has sorting enabled. 4. For this example, we update the same page state variable (that populates the DataTable) with the sorted data using the following custom function. ``` List sortMyData( List listToSort, bool isAsc, int sortColumIndex, ) { /// MODIFY CODE ONLY BELOW THIS LINE // Sort by 'name' for 0, 'age' for 1, 'position' for 2 in code. switch (sortColumIndex) { case 0: listToSort.sort((a, b) => a.name.compareTo(b.name)); break; case 1: listToSort.sort((a, b) => a.age.compareTo(b.age)); break; case 2: listToSort.sort((a, b) => a.position.compareTo(b.position)); break; default: break; } if (!isAsc) { listToSort = listToSort.reversed.toList(); } return listToSort; /// MODIFY CODE ONLY ABOVE THIS LINE } ``` ## Searching[​](/resources/ui/widgets/built-in-widgets/datatable.md#searching "Direct link to Searching") You can add search functionality to the DataTable widget using our Simple Search feature. However, for this specific widget, instead of using a [Conditional Builder](/concepts/layouts/conditional-builder.md) widget, you can directly utilize the [Conditional Value](/resources/functions/conditional-logic.md#conditional-value-ifthenelse) to determine which result to display based on the `IsShowFullList` variable. ![searching-through-table](/assets/images/searching-through-table-7eaa66ef377e289c012db89a9600069d.avif) ## Selecting rows[​](/resources/ui/widgets/built-in-widgets/datatable.md#selecting-rows "Direct link to Selecting rows") You might want to allow users to select one or more of its rows for tasks like editing, deleting, or performing specific actions on the selected data. For example, preparing a list of promoted employees from the main employee listing. To achieve this, create a page state variable to store the selected list. Upon button click, update this variable with the chosen selections from the DataTable. **Note that** the DataTable provides a list of selected row indices; you'll need a [custom function](/concepts/custom-code/cloud-functions.md) to retrieve the actual rows corresponding to these indices. Here are the exact steps: 1. First, create a [page state](/resources/ui/pages/page-lifecycle.md#creating-a-page-state) variable that will hold the list of selected rows. 2. Select the **DataTable**, move to the **Properties Panel > Paginated Data Table Properties >** turn on the **Selectable** toggle. 3. On button click, [update the page state](/resources/ui/pages/page-lifecycle.md#update-page-state-action) variable with the selected rows. While adding this action, use the following custom function to retrieve the selected items based on the indices. You can get the list of selected rows indices via **Widget State > DataTable Selected Rows**. 4. Optionally, you could pass this variable to a new page to display the selection. Custom function: ``` List findPromotedEmps( List allEmps, List selecteEmpsIndex, ) { // MODIFY CODE ONLY BELOW THIS LINE // return allEmps based on selecteEmpsIndex List promotedEmps = []; for (int i = 0; i < selecteEmpsIndex.length; i++) { int index = selecteEmpsIndex[i]; if (index >= 0 && index < allEmps.length) { EmployeesRecord emp = allEmps[index]; promotedEmps.add(emp); } } return promotedEmps; /// MODIFY CODE ONLY ABOVE THIS LINE } ``` ## Get notified on page changed[​](/resources/ui/widgets/built-in-widgets/datatable.md#get-notified-on-page-changed "Direct link to Get notified on page changed") You might want to get a callback whenever a user taps on the next page of the DataTable. For example, to make an API call to retrieve the data for the next page. To do so: 1. Select the **DataTable** widget. 2. Select **Actions** from the Properties panel and open **Action Flow Editor**. 3. Select **On Page Changed**. This callback gives you the **Current Row Index**, which is the index of the first row of a new page. For example, if you have 25 items (0-24) on the current page, the **Current Row Index** value will be 25. This is helpful in APIs that fetch a fixed set of data by specifying a starting position ([offset](https://developer.box.com/guides/api-calls/pagination/offset-based/)). 4. Now, add an action to call the paginated API (that returns the result in chunks). See [how to add the paginated API](/resources/backend-logic/rest-api.md#query-parameters) call by adding query parameters. For this example, we use this API: . **Note**: this API uses page-based rather than offset-based pagination, requiring manual adjustment of the page variable. 5. On the success of the API call, you can add an action to append the new data in the current list. For this, you can add the following custom function to add new results to existing data. ``` List addAlldatatoList( List currentUsersList, List newUsersList, ) { /// MODIFY CODE ONLY BELOW THIS LINE // add all newUsersList to currentUsersList currentUsersList.addAll(newUsersList); return currentUsersList; /// MODIFY CODE ONLY ABOVE THIS LINE } ``` ## Get notified on rows per page changed[​](/resources/ui/widgets/built-in-widgets/datatable.md#get-notified-on-rows-per-page-changed "Direct link to Get notified on rows per page changed") Sometimes, you might want to get a callback when a user changes the number of rows to display on a page. This is helpful for dynamically adjusting data fetch requests based on user preferences. This is how you do it: 1. Select the **DataTable** widget. 2. Select **Actions** from the **Properties panel** and open **Action Flow Editor**. 3. Select **On Rows Per Page Changed**. Any actions added under this will be triggered when the number of displayed rows is changed. 4. Now, you can add any action here. ![get-notified-on-row-changed-per-page](/assets/images/get-notified-on-row-changed-per-page-68dbbe36c595912ea9c093d9a8f999d9.avif) ## Customizing[​](/resources/ui/widgets/built-in-widgets/datatable.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Configure paginated DataTable[​](/resources/ui/widgets/built-in-widgets/datatable.md#configure-paginated-datatable "Direct link to Configure paginated DataTable") To configure the paginated DataTable, move to the **Properties Panel > Paginated Data Table Properties** and then: * To hide the pagination, turn on the **Hide Paginator** toggle. * To display buttons to navigate to the first and last page of the DataTable, turn on the **Show First And Last Buttons**. * To have a normal DataTable without pagination, turn off the **Paginated** toggle. info Typically, setting the size explicitly isn't necessary for a DataTable, as it's designed to showcase large datasets and should utilize all available space. However, to enable horizontal scrolling in the DataTable (when content exceeds screen width), you must specify the **Min Width**. ### Adjust row and column spacing[​](/resources/ui/widgets/built-in-widgets/datatable.md#adjust-row-and-column-spacing "Direct link to Adjust row and column spacing") To modify the row and column spacing, move to the **Properties Panel > Layout Properties** and then tweak the following properties: * **Header Row Height**: This changes the height of the header. * **Data Row Height**: This changes the height of all the rows. * **Column Spacing**: This changes the distance between columns. ### Customize DataTable color[​](/resources/ui/widgets/built-in-widgets/datatable.md#customize-datatable-color "Direct link to Customize DataTable color") To modify the DataTable color, navigate to the **Properties Panel > Style Properties**, where you can set colors for various elements: * **Header Row Color**: This changes the background color of the header row. * **Row Color**: This sets the background color for all rows. * **Alternate Row Color**: This allows for a different background color for alternate rows. * **Sort Icon Color**: This alters the color of the sort icon used in sortable columns. ### Adjust border radius[​](/resources/ui/widgets/built-in-widgets/datatable.md#adjust-border-radius "Direct link to Adjust border radius") To add the rounded corner to the DataTable, navigate to the **Properties Panel > Style Properties > Border Radius** and then: 1. Enter values for TL (Top left), TR (top right), BL (bottom left), and BR (bottom right). 2. To apply the same radius on all sides, switch to the **Uniform Radius** option. You can then adjust the radius by either moving the slider or entering the desired value directly. ![adjust-row-border](/assets/images/adjust-row-border-36196b67df9ddebc447abe1159bb5fec.avif) ### Add dividers[​](/resources/ui/widgets/built-in-widgets/datatable.md#add-dividers "Direct link to Add dividers") To add horizontal and vertical dividers inside the DataTable, navigate to the **Properties Panel > Style Properties >** turn on the **Horizontal** and **Vertical Dividers**. After enabling, you can also change its **Color** and **Thickness**. ![add-dividers](/assets/images/add-dividers-44a337a79fddd01973f65573ada53ef2.avif) ### Customize checkbox colors[​](/resources/ui/widgets/built-in-widgets/datatable.md#customize-checkbox-colors "Direct link to Customize checkbox colors") When rows are selectable, you can customize the appearance of the checkbox by adjusting the following color properties: * **Selected Fill Color**: Sets the background color of the checkbox when it is selected. * **Unselected Fill Color**: Sets the background color of the checkbox when it is not selected. * **Unselected Border Color**: Changes the border color of the checkbox when it is not selected. * **Selected Border Color**: Changes the border color of the checkbox when it is selected. * **Check Color**: Alters the color of the checkbox mark itself when selected, providing visual feedback to users about their selection status. --- # Dividers Add a thin horizontal or vertical line, with padding on either side. Customize the color, width or height, and style of the divider from the Properties Panel. ## Divider Properties[​](/resources/ui/widgets/built-in-widgets/dividers.md#divider-properties "Direct link to Divider Properties") Here are the properties in detail: ![divider.png](/assets/images/divider-7b4c775b67dbc4c100dbe304e2900405.png) Divider (Horizontal) Properties ![v-divider.png](/assets/images/v-divider-1494face8c703ce035468b4a8d3bf78c.png) Vertical Divider Properties * **Line Style**: This property determines the visual pattern of the divider line. Options typically include: * **Solid**: A continuous line. * **Dotted**: A series of dots. * **Dashed**: A series of dashes. * **Dashdotted:** A combination of dashes and dots. * **Color**: Defines the color of the divider line. This can be set using predefined theme colors or custom values to match or contrast with the application's design scheme. * **Thickness**: Specifies the thickness of the divider line, influencing its visual prominence. Thicker lines are more noticeable and can be used to make a bold statement, while thinner lines are subtler. * **Width**: This property sets the horizontal length of the divider. It can be specified in absolute terms (e.g., pixels). * **Height**: For vertical dividers, this property sets the vertical length. Like width, it can also be defined in pixels. * **Indent and End-Indent**: These properties control the spacing from the edges of the container to the start and end points of the divider line, respectively. Indents can be used to fine-tune the placement of the divider within a layout, helping to achieve a balanced or desired aesthetic effect. --- # Draggable + DragTarget The Draggable widget is used to make a widget that can be dragged and dropped to a different location within the app. It allows users to interact with the app by moving an item using touch gestures or a mouse. The DragTarget widget is used in conjunction with the Draggable widget to specify where a dragged item can be dropped. It creates a region that can accept the data carried by the Draggable widget. When an item is dragged over a DragTarget, the DragTarget has the opportunity to determine whether it can accept the item. If it accepts, it can then trigger actions such as updating the app's state to reflect the change. For example, in a shopping cart app, you could use these widgets together to allow users to add items to their cart by dragging and dropping them onto a cart icon. ## Adding Draggable and DragTarget Widgets[​](/resources/ui/widgets/built-in-widgets/draggable.md#adding-draggable-and-dragtarget-widgets "Direct link to Adding Draggable and DragTarget Widgets") Let's see how to add a drag-and-drop functionality by building an example that allows users to put only plants on the shelf. Here's how it looks: The steps to build such an example are as follows: ### 1. Create page state variable[​](/resources/ui/widgets/built-in-widgets/draggable.md#1-create-page-state-variable "Direct link to 1. Create page state variable") In this example, we have two images of a shelf: one with empty space for one plant and another with all plants on the shelf. To control which image to show based on whether the correct item is dropped on the shelf, we need a [page state variable](/resources/ui/pages/page-lifecycle.md#page-state). Therefore, [create a page state variable](/resources/ui/pages/page-lifecycle.md#creating-a-page-state) named `isShelfFull` with the datatype *Boolean* and set its default value to *False*. ![img\_1.png](/assets/images/img_1-f80cacf0889a64d442e5907793c4005c.png) Control image display based on page state variable ### 2. Add Draggable widgets[​](/resources/ui/widgets/built-in-widgets/draggable.md#2-add-draggable-widgets "Direct link to 2. Add Draggable widgets") Let's add the draggable widgets and specify the data for each widget. This data will later be used to determine if the correct item is being dropped on the shelf. For instance, you can assign a unique identifier or a type attribute (e.g., plant, spoon, toy) to each draggable widget. note As we proceed in this section, you'll learn how this information is crucial for the DragTarget widget to evaluate whether the item being dropped matches the expected type for the shelf. In this example, the draggable items are a plant, a spoon, and a football. Let's see how to add them: 1. Inside the **Row** widget, add **Draggable** widgets directly from the widget tree or canvas area. 2. Inside the **Draggable** widget, you can add any widget as a child widget. For this example, we use the **Image** widget. 3. To add data to draggable widgets, select the **Draggable widget > Properties Panel > Draggable Properties >** specify the **Type** of the data and its **Value**. info The Draggable widget also provides you with various drag events (as [**Action Triggers**](/resources/functions/action-triggers.md)) that you might want to use to customize the drag experience. These include: * **On Drag Started**: Gets triggered when the user initiates a drag operation. * **On Drag Update**: Gets triggered when the drag is currently in progress, allowing you to track its movement or update other UI elements accordingly. * **On Drag Completed**: Gets triggered when the user successfully drags and drops the widget into [**DragTarget**](/resources/ui/widgets/built-in-widgets/draggable.md#3-add-dragtarget-widget) widget. * **On Drag Cancelled**: Gets triggered when the drag operation is aborted, such as when the user releases the widget outside a **DragTarget** or the DragTarget rejects the widget. * **On Drag End**: Gets triggered when the drag operation finishes, regardless of whether it was completed or cancelled. ### 3. Add DragTarget widget[​](/resources/ui/widgets/built-in-widgets/draggable.md#3-add-dragtarget-widget "Direct link to 3. Add DragTarget widget") The DragTarget widget in this example allows users to drop items onto the shelf. We utilize the Stack widget to layer the DragTarget widget over the shelf image. Moreover, the display of the shelf image is controlled by the [ConditionalBuilder](/concepts/layouts/conditional-builder.md) widget, which uses the `isShelfFull` variable to determine which image to show. This widget arrangement ensures that the shelf image updates dynamically based on whether the shelf is full or not. Let's see how to add DragTarget widget: 1. Open the [Widget Palette](/flutterflow-ui/widget-palette.md) and locate the **DragTarget** widget under the **Base Elements** tab. You can drag it into your desired location or add it directly from the widget tree. 2. Inside the **DragTarget** widget, add a [**Container**](/resources/ui/widgets/container.md) widget, preferably of the same size as the image, and set its background color to transparent. This will serve as the drop zone for draggable items. 3. Now, you need to specify the type of data this target will receive. To do so select the **DragTarget widget > Properties Panel > Draggable Properties >** specify the **Type** of the data. This is crucial for ensuring that only the correct items can be dropped on the target. ### 4. Get notified on drag events[​](/resources/ui/widgets/built-in-widgets/draggable.md#4-get-notified-on-drag-events "Direct link to 4. Get notified on drag events") The DragTarget widget provides you with the various drag events (aka callbacks) which are essential in building drag and drop functionalities. Here are they: * **On Drag Accept:** Actions under this are triggered when the data is dropped over the DragTarget. * **On Drag Enter:** Actions under this are triggered when the data is being dragged over DragTarget. * **On Drag Exit:** Actions under this are triggered when a draggable item that was previously over the DragTarget leaves its area. For example, In the shopping app, if the user decides not to drop the item into the cart and moves it away, this event callback can be used to remove the highlight from the shopping cart. tip You can use On Drag Accept or On Drag Enter to determine if DragTarget can receive the data and accordingly update the app state. It's crucial to think about the user experience you wish to create. For instance, if you aim to trigger an action as soon as an item enters the drop area, utilize On Drag Enter along with On Drag Exit. Conversely, if your action should occur only after the item has been dropped, then On Drag Accept, paired with On Drag Exit, is your go-to option. Let's see how to add drag events for this example: 1. Select **DragTarget** widget, select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 2. To ensure that only a plant item is being dropped: 1. Select the **On Drag Accept** and select **+ Add Conditional Action**. 2. From the **set variable** menu, select **Drag Target > Dragged Data**. This captures the data of the draggable item that we added in [step 2](/resources/ui/widgets/built-in-widgets/draggable.md#2-add-draggable-widgets). 3. Check if the captured data matches the expected item, i.e., plant. 4. In the **TRUE** branch, you can add a [snackbar message](/resources/ui/pages/scaffold.md#snackbar) and [update](/resources/ui/pages/page-lifecycle.md#page-state) the `isShelfFull` variable to True. This will create an effect like the user has actually dragged and dropped the item onto the shelf. 3) Now, select the **On Drag Exit** andadd an action to [update](/resources/ui/pages/page-lifecycle.md#page-state) the `isShelfFull` variable to False. This ensures that if the user decides not to drop the item and moves it away, the shelf image reverts to the empty one. ![img\_2.png](/assets/images/img_2-c110529846ac9814ffd79fbdfffc630f.png) --- # Expandable An Expandable widget is a user interface component used to show or hide content dynamically. It consists of a header that can be tapped to reveal or collapse additional content. This functionality is particularly useful in interfaces where space is at a premium, such as in mobile applications or complex forms, enabling users to access information on demand without overwhelming the screen with too much content all at once. **Default Widget Tree for Expandable Widget** When you add an **Expandable** widget, the default widget tree typically includes: * **Header:** The visible part of the widget when it is both collapsed and expanded. This usually contains a label or icon indicating what the expandable content relates to. * **Collapsed View:** The default state showing minimal content or summarization. * **Expanded View:** Contains more detailed information or additional controls that are visible when the widget is expanded. ![expandable-widget-tree.avif](/assets/images/expandable-widget-tree-7f0f06e1450e64f33575ea6e218275cf.avif) ## Expandable Widget Properties[​](/resources/ui/widgets/built-in-widgets/expandable.md#expandable-widget-properties "Direct link to Expandable Widget Properties") * **Icon Properties:** For Icon Properties, check out the **[Icon](/resources/ui/widgets/icons.md)** guide. * **Expandable Properties:** * **Active View:** Specifies whether the widget is currently in the collapsed or expanded state. * **Initially Expanded:** Determines if the widget should be expanded by default when the view is first loaded. * **Tap Header to Toggle:** Allows the user to expand or collapse the content by tapping the header. * **Tap Body to Expand/Collapse:** Defines whether tapping on the body of the expanded content can toggle its state. * **Style Properties:** * **Width & Height:** Dimensions of the widget, which can be set to infinity to take full width or height. * **Background Color:** The color behind the expandable content. * **Header Alignment:** Aligns the header content such as left, center, or right. ### Practical Use of Expanded[​](/resources/ui/widgets/built-in-widgets/expandable.md#practical-use-of-expanded "Direct link to Practical Use of Expanded") This setup allows for a highly customizable Expandable widget, making it suitable for FAQs, forms, lists, or other content that benefits from a clean, compact initial appearance with options for more detailed information. The ability to fine-tune how and where icons appear, along with the behavior of the widget's expandability, gives developers significant control over user experience and interface design. --- # FlippableCard The FlippableCard widget provides the visual interaction called 'Flip card animation'. Initially, it shows the front side of the card, and when you tap on it, it shows the back side. You could use this widget to show and hide details of an item (e.g., credit card, online course card, coupon card, etc.) ## Adding FlippableCard widget[​](/resources/ui/widgets/built-in-widgets/flippable-card.md#adding-flippablecard-widget "Direct link to Adding FlippableCard widget") To add the FlippableCard widget: 1. First, click on the **+ Add Widget** and drag the **FlippableCard** widget from the **Layout Elements** tab or add it directly from the widget tree. 2. Select the **Card Front** from the widget tree and customize or replace the **Container** with the widget of your choice. For example, replacing it with a **Credit Card** widget (under the Templates > Card Views). 3. To edit the back side of the card, select the **FlippableCard**, move to the properties panel, scroll down to the **Flippable Card Propertie**s and enable the **Edit Back of Card**. 4. Now select the **Card Back** from the widget tree and customize or replace the **Container** with the widget of your choice. For example, again, add the Credit Card widget and customize it to show the details. ## Customizing[​](/resources/ui/widgets/built-in-widgets/flippable-card.md#customizing "Direct link to Customizing") You can customize the appearance of this widget using the various properties available under the properties panel. ### Changing flip direction[​](/resources/ui/widgets/built-in-widgets/flippable-card.md#changing-flip-direction "Direct link to Changing flip direction") By default, this widget flips the card in the horizontal direction (i.e., from left to right and right to left). To change the flip direction: 1. Select the **FlippableCard** widget from the widget tree or canvas area. 2. Move to the properties panel, and scroll down to the **Flippable Card Properties** section. 3. Find the **Flip Direction** dropdown and change it to **Horizontal** or **Vertical**. ### Changing flip animation duration[​](/resources/ui/widgets/built-in-widgets/flippable-card.md#changing-flip-animation-duration "Direct link to Changing flip animation duration") When you tap on this widget, the flip animation completes in 400ms (milliseconds). You can change this duration if you wish to make it a little faster or slower. To change the flip animation duration: 1. Select the **FlippableCard** widget from the widget tree or canvas area. 2. Move to the properties panel, and scroll down to the **Flippable Card Properties** section. 3. Find the **Flip Animation Duration** property and change the value. Note: The value should be in milliseconds (e.g., 1000ms = 1 second). ### Disable flip on tap[​](/resources/ui/widgets/built-in-widgets/flippable-card.md#disable-flip-on-tap "Direct link to Disable flip on tap") By default, the card flips when you tap on it. To disable this behavior, move to the **properties panel > Flippable Card Properties** > disable **Flip on Tap** toggle. --- # Markdown The Markdown widget is used to input and display text using [Markdown syntax](https://www.markdownguide.org/basic-syntax/). It allows you to format text easily, without the complexity of a full-fledged WYSIWYG (What You See Is What You Get) editor or the need to write HTML code. You could use this widget in various applications like note-taking apps, forums, and blogging platforms. They are particularly popular in technical and coding communities for their ease of formatting code snippets and descriptions. ![img.png](/assets/images/img-fe542d54ca6413fb02dc2ef49a03ef09.png) ## Adding Markdown widget[​](/resources/ui/widgets/built-in-widgets/markdown.md#adding-markdown-widget "Direct link to Adding Markdown widget") To add a Markdown widget: 1. Open the [Widget Palette](/flutterflow-ui/widget-palette.md) and locate the **Markdown** widget under the **Base Elements** tab. You can either drag it into your desired location or add it directly from the widget tree. 2. To display the markdown content, move to the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) and enter the text inside the **Data** section. 3. Optionally, you have the choice to make your Markdown content selectable. This can be adjusted using the **Selectable** property. --- # MediaDisplay The **MediaDisplay** widget in FlutterFlow automatically detects the type of media fetched from a URL and adjusts the widget accordingly. For instance, if the URL returns an image, the widget will behave as an Image widget. This versatility allows you to easily present various types of media within your app. For example, it can be integrated into scrollable widgets like [ListView](/resources/ui/widgets/composing-widgets/list-grid.md#listview-widget) for displaying activity feeds or [GridView](/resources/ui/widgets/composing-widgets/list-grid.md#gridview-widget) for presenting photos and videos together. ## Adding MediaDisplay widget[​](/resources/ui/widgets/built-in-widgets/media-display.md#adding-mediadisplay-widget "Direct link to Adding MediaDisplay widget") Let's build an example of using the MediaDisplay widget inside the ListView and display the photos and videos from the Firestore database. The steps to add and use the MediaDisplay are as follows: 1. Add the **MediaDisplay** widget from the **Base Elements** tab and drop it inside the **ListView**. 2) Create a collection and add data with some image and video URLs. 3) Query a collection to get a list of documents from the Firestore collection and show them in the ListView. 4) To display media inside the widget, move to the properties panel > **Media Path** > Set from Variable menu. Select the source as **\[collection\_name] Document** and select the field that holds the URL path from the **Available Options** list. ## Customizing[​](/resources/ui/widgets/built-in-widgets/media-display.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of the widget using the various properties available under the properties panel. ### Customizing Image[​](/resources/ui/widgets/built-in-widgets/media-display.md#customizing-image "Direct link to Customizing Image") To customize the widget when image is displayed, refer [here](/resources/ui/widgets/image.md#common-image-properties). ### Customizing Video[​](/resources/ui/widgets/built-in-widgets/media-display.md#customizing-video "Direct link to Customizing Video") To customize the widget when video is displayed, refer [here](/concepts/file-handling/displaying-media.md#videoplayer). --- # MouseRegion The `MouseRegion` widget lets you know whenever the mouse pointer enters or exits from a widget. You could use it to build a user experience (UX), such as animating buttons when a user hovers over them and revealing or hiding menu items when a user hovers over the menu icon. On this page, you will learn how to [add the MouseRegion widget](/resources/ui/widgets/built-in-widgets/mouse-region.md#adding-mouseregion-widget), use it to [show/hide elements](/resources/ui/widgets/built-in-widgets/mouse-region.md#showhide-elements-using-mouseregion), and [customize](/resources/ui/widgets/built-in-widgets/mouse-region.md#customizing) it. ## Adding MouseRegion widget[​](/resources/ui/widgets/built-in-widgets/mouse-region.md#adding-mouseregion-widget "Direct link to Adding MouseRegion widget") Here are the step-by-step instructions to build such an example: 1. First, click on the **+ Add Widget** and drag the **MouseRegion** widget from the **Base Elements** tab or add it directly from the widget tree. 2. Add a [**Button**](/resources/ui/widgets/button.md) (inside MouseRegion) with [**On Action Trigger**](/concepts/animations/widget-animations.md#animation-on-action-trigger) animation. 3. Select the **MouseRegion** widget, select **Actions** from the Properties Panel (the right menu), and click **Open**. This will open an **Action flow Editor** in a new popup window. 4. Select the **On Mouse Enter** tab. Actions added under this will be triggered whenever the mouse enters the MouseRegion widget. 1. Add the [Widget Animation](/concepts/animations/widget-animations.md) action to start the animation on a Button. 5. Select the **On Mouse Exit** tab. Actions added under this will be triggered whenever the mouse leaves the MouseRegion widget. 1. Add the [Widget Animation](/concepts/animations/widget-animations.md) action to stop the animation on a Button. ## Show/hide elements using MouseRegion[​](/resources/ui/widgets/built-in-widgets/mouse-region.md#showhide-elements-using-mouseregion "Direct link to Show/hide elements using MouseRegion") Using the callbacks provided by the MouseRgion widget, you can show or hide a widget. The idea is to update the *App State* variable when the mouse pointer enters or exits the widget. And then use the same app state variable to add *Conditional Visibility* on a widget. Let's see how to build the following example: Here are the step-by-step instructions: 1. First, add the Stack **>** **Container** **> MouseRegion >** **IconButton** to display the menu icon. 2. Add the **Container > MouseRegion >** **Column** (with some menu items/options) inside the same Stack widget. Note Note that we wrapped the menu icon and its options inside the MouseRegion widget. In the next step, we will add the same actions for both MouseRegion widgets so that the menu options stay visible as long as you hover over them. ![img\_9.png](/assets/images/img_9-8b6a979e7ead3b7291192c7f05eb1a2a.png) 3. Create a boolean [App State variable](/resources/data-representation/app-state.md) and use it to [add conditional visibility](/resources/ui/widgets/widget-commonalities.md#conditional) on menu options. 4. On both MouseRegion widgets, add an [update app state variable](/resources/data-representation/app-state.md#update-app-state-action) action to set **True** when the mouse enters and **False** when the mouse exit. Use app state variable and MouseRegion to show/hide a widget ## Customizing[​](/resources/ui/widgets/built-in-widgets/mouse-region.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the **Properties Panel**. ### Customize mouse cursor[​](/resources/ui/widgets/built-in-widgets/mouse-region.md#customize-mouse-cursor "Direct link to Customize mouse cursor") When a mouse enters the widget, its cursor will change to the appropriate one by default. However, you can also set it to a custom one if you wish to. To customize the mouse cursor, select the **MouseRegion** widget, move to the properties panel, find the **Mouse Cursor** dropdown select the one you think fits best. --- # PinCode The PinCode widget allows you to enter the PIN or OTP. You could use this widget to verify the user identity or a transaction before making payments in fintech apps. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding PinCode widget[​](/resources/ui/widgets/built-in-widgets/pincode.md#adding-pincode-widget "Direct link to Adding PinCode widget") To add a PinCode widget: 1. Open the [Widget Palette](/flutterflow-ui/widget-palette.md) and locate the **PinCode** widget under the **Base Elements** tab. You can drag it into your desired location or add it directly from the widget tree or canvas area. 2. To increase the pin length (number of values users can enter), move to the properties panel, see the **Pin Length** property, and enter the value. **Note**: You can only set this value up to 8. 3. If you are using this widget to get a secret PIN from users, you can obscure it with a special character. To do so, enable the **Obscure Text** toggle and select the **Obscuring Character** among the \*,-,?, and •. 4. You can also enable/disable the **Hint Text** toggle and select the **Hint Character** displayed when you haven't entered anything. ## Trigger Action On Completed[​](/resources/ui/widgets/built-in-widgets/pincode.md#trigger-action-on-completed "Direct link to Trigger Action On Completed") Let's see how to trigger an action when you are done entering the value in this widget. This is helpful when you want to compare the entered value with the one stored in your backend. To do so: 1. Select the **PinCode** widget, select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 2. Set the **Type of Action** (aka callback) to **On Completed**. That means actions added under this will be called after the user has entered all PIN field values. 3. Now you can add any action here. Here is an example of displaying a snackbar message that shows the entered value in the PinCode widget. ## Trigger Action On Change[​](/resources/ui/widgets/built-in-widgets/pincode.md#trigger-action-on-change "Direct link to Trigger Action On Change") You may want to trigger an action whenever users enter or delete the value in each field of this widget. For instance, you can check the validity of the entered digit as soon as the user types it in and show a message that it is not valid. To do this, [add an action using the trigger](/resources/forms/form-triggers.md#on-change) that responds to changes in this widget. ## Trigger Action On Focus Change[​](/resources/ui/widgets/built-in-widgets/pincode.md#trigger-action-on-focus-change "Direct link to Trigger Action On Focus Change") You may want to trigger an action when the user taps into or exits the Pincode field. For example, you can run a validation check once the user finishes entering the code and moves focus away from the field. To do this, [add an action using the trigger](/resources/forms/form-triggers.md#on-focus-change) that responds to focus changes in this widget. ## Validation[​](/resources/ui/widgets/built-in-widgets/pincode.md#validation "Direct link to Validation") You can validate the Pincode widget to see if a user has entered any value. To do so, wrap the Pincode widget inside the [**Form**](/resources/forms/form-validation.md#adding-form-widget) widget, In the *Form* widget, enter the error message you want to display and then trigger the [**Validate Form**](/resources/forms/form-validation.md#3-adding-validate-action) action. This will display an error message when a user tries to submit the form without a pincode value. You can also adjust the height to the error text from **Properties Panel > Error text height**. ![Set error text height](/assets/images/set-error-text-height-33fb804125c167aebbf767935c094286.png) ## Customizing[​](/resources/ui/widgets/built-in-widgets/pincode.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Changing keyboard type[​](/resources/ui/widgets/built-in-widgets/pincode.md#changing-keyboard-type "Direct link to Changing keyboard type") When the keyboard opens by default, you can enter only numbers. But you might want to allow users to enter both letters and numbers. To do so, select the **PinCode** widget, move to the **Properties Panel** **> PinCode Properties >** set the **Keyboard Type** to the **Visible Password**. ![Keyboard Type](/assets/images/keyboard-type-5cfaa2613a7498d4a87b7703e1897bbc.webp) ![Keyboard type: Visible Password](/assets/images/keyboard-type-visible-password-aa5bfba910b56312073b4646db7b61a9.png) ### Using PinCode for secret pin[​](/resources/ui/widgets/built-in-widgets/pincode.md#using-pincode-for-secret-pin "Direct link to Using PinCode for secret pin") To make a *PinCode* a secret pin field, move to the **Properties Panel > Pin Code Properties >** enable the **Obscure Text**. Now, when you enter a value, it will be obscured with the star (\*). You can change this symbol using the **Obscuring Character** dropdown. ### Setting hint character[​](/resources/ui/widgets/built-in-widgets/pincode.md#setting-hint-character "Direct link to Setting hint character") A hint character refers to a special character or symbol that is displayed in each input field of the PinCode Widget to give users a visual clue about the expected input format. Hint characters are often used in combination with the actual input characters to guide users when entering a PIN or password. To set the hint text, move to the **Properties Panel > Pin Code Properties > enable the Hint Text > set the Hint Character**. ### Auto focus[​](/resources/ui/widgets/built-in-widgets/pincode.md#auto-focus "Direct link to Auto focus") When enabled, it mimics the tap event and immediately shows the keyboard. This makes *PinCode* widget ready to receive input from users without having to click on it. In case, you want to disable this behaviour, move to the **Properties Panel** **> Pin Code Properties >** disable the **Auto Focus** property. ### Auto Fill[​](/resources/ui/widgets/built-in-widgets/pincode.md#auto-fill "Direct link to Auto Fill") When this is enabled, it can read and auto fill the code from your messages app. ![Auto Fill enabled](/assets/images/auto-fill-enabled-7c3e5da02c15b11362b97970d64f3f22.png) ### Aligning pin code fields[​](/resources/ui/widgets/built-in-widgets/pincode.md#aligning-pin-code-fields "Direct link to Aligning pin code fields") By default, all the pin fields are aligned to *Space Evenly*. Meaning there will be equal space between each pin field. The following options help you align the pin code fields: * **Start**: Place pin code fields as close to the beginning as possible. * **Center**: Place pin code fields as close to the middle as possible. * **End**: Place pin code fields as close to the end as possible. * **Space Evenly**: Evenly space pin code fields. * **Space Around**: Place the free space evenly between the pin code fields with some extra space at the beginning and end. * **Space Between**: Place the free space evenly between the pin code fields. To configure the space between and around the pin fields, select the **PinCode** widget, move to the properties panel, find the **Pin Code Alignment** property and select among the above options. ### Changing pin field shape and size[​](/resources/ui/widgets/built-in-widgets/pincode.md#changing-pin-field-shape-and-size "Direct link to Changing pin field shape and size") To change the pin field shape and size: 1. Select the **PinCode** widget, move to the properties panel, find the **Pin Field Shape** property, and here you can set the shape to **Box**, **Circle**, and **Underline**. 2. To change the height and width, enter the value in **Field Height**, and **Field Width** boxes. 3. To create a rounded border when the shape is set to *Box*, use the **Border Radius** and **Border Width** properties. ### Change colors[​](/resources/ui/widgets/built-in-widgets/pincode.md#change-colors "Direct link to Change colors") You can change colors for the different states of the pin fields. To do so: 1. Select the **PinCode** widget, move to the properties panel, and change the colors for the following properties: * **Active Color**: This sets the border color when the value is entered. * **Inactive Color**: This sets the border color when there is no value. * **Selected Color**: This sets the border color when the cursor is inside the pin field and the user is about to enter the value. 2. To change the background color instead of only the border color, **Enable Active Fill**. ### Customizing cursor[​](/resources/ui/widgets/built-in-widgets/pincode.md#customizing-cursor "Direct link to Customizing cursor") You can show/hide the cursor using the **Show Cursor** toggle and change the color using the **Cursor Color** property. Clear pin code value See how to [**reset the pin code value**](/resources/forms/reset-form-field.md). --- # ProgressBar The ProgressBar widget is used to represent the progress of any task. You can use the ProgressBar widget to build a UI that shows the downloading or uploading of files, sales this week, hours spent, overall score, etc. ## Adding ProgressBar[​](/resources/ui/widgets/built-in-widgets/progressbar.md#adding-progressbar "Direct link to Adding ProgressBar") Here's how you can add the ProgressBar widget to your project: 1. Add the **ProgressBar** widget by dragging it from the **Base Elements** tab or directly from the widget tree and align it in the center. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Progress Bar Properties** section. 3. Find the **Progress Bar Shape** dropdown and set it to either **Circular** or **Linear**. * **Circular**: The ProgressBar is displayed in a Circle shape. This is the default shape set to the ProgressBar. * **Linear**: The ProgressBar is displayed in a rectangular shape and laid out horizontally on the screen. 4. To set the progress, find the **Progress Value** input box and enter the value between 0 and 1.0. For example, a value of 0.3 will fill 30% of the portion on the ProgressBar. 5. To change the progress text (displayed in the center), scroll down to the **Text** section, find the Text property, and enter the value. ## Customizing circular progress bar[​](/resources/ui/widgets/built-in-widgets/progressbar.md#customizing-circular-progress-bar "Direct link to Customizing circular progress bar") The Properties Panel can be used to customize the appearance and behavior of the Circular Progress Bar. ### Changing size[​](/resources/ui/widgets/built-in-widgets/progressbar.md#changing-size "Direct link to Changing size") You may want to change the default size of the Circular ProgressBar to match your design. You can do so using the *Diameter* property. To change the size of the Circular progress bar: 1. Select **ProgressBar** from the widget tree or the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Progress Bar Properties** section. 3. Find the **Diameter** property. Now, there are two ways to change the size: * To set to an **exact size,** select **PX** and enter the desired values. * To set the size as a **% of the screen size**, select **%** and enter the desired value. ### Changing thickness[​](/resources/ui/widgets/built-in-widgets/progressbar.md#changing-thickness "Direct link to Changing thickness") Changing the thickness property allows you to change the size of the progress bar belt. 1. Select **ProgressBar** from the widget tree or the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Progress Bar Properties** section. 3. Find the **Thickness** property and enter the value. ### Changing start angle[​](/resources/ui/widgets/built-in-widgets/progressbar.md#changing-start-angle "Direct link to Changing start angle") By default, the progress bar starts filling the progress from the top-center position (i.e., 0 degree). However, you can set it to start the progress bar from a specific angle using the *Start Angle* property. To change the start angle: 1. Select **ProgressBar** from the widget tree or the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Progress Bar Properties** section. 3. Find the **Start Angle (degree)** property and enter the value in degree. For example, entering a value of 90 fills the progress bar from the right. Whereas the value of 180 fills the progress bar from the bottom. ## Customizing linear progress bar[​](/resources/ui/widgets/built-in-widgets/progressbar.md#customizing-linear-progress-bar "Direct link to Customizing linear progress bar") The Properties Panel can be used to customize the appearance and behavior of the Linear Progress Bar. ### Changing size[​](/resources/ui/widgets/built-in-widgets/progressbar.md#changing-size-1 "Direct link to Changing size") You can change the default size using the *Width* property. To change the size of the Linear Progress Bar: 1. Select **ProgressBar** from the widget tree or the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Progress Bar Properties** section. 3. Find the **Width** property. Now, there are two ways to change the size: * To set to an **exact size,** select **PX** and enter the desired values. * To set the size as a **% of the screen size**, select **%** and enter the desired value. ### Changing thickness[​](/resources/ui/widgets/built-in-widgets/progressbar.md#changing-thickness-1 "Direct link to Changing thickness") Changing the thickness property allows you to change the height of the progress bar. 1. Select **ProgressBar** from the widget tree or the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Progress Bar Properties** section. 3. Find the **Thickness** property and enter the value. ### Changing end radius[​](/resources/ui/widgets/built-in-widgets/progressbar.md#changing-end-radius "Direct link to Changing end radius") By default, the progress bar appears in a rectangular shape. However, you can make it rounded rectangular using the *End Radius* property. To change the end radius: 1. Select **ProgressBar** from the widget tree or the canvas area. 2. Move to the Property Editor (on the right side of your screen) and scroll down to the **Progress Bar Properties** section. 3. Find the **End Radius** property and enter the value. --- # RatingBar The RatingBar widget is used to show a rating or collect ratings from users (this is an interactive RatingBar). For example, you can use the RatingBar widget inside an e-commerce app to show ratings for a product. ## Adding a RatingBar to Your Project[​](/resources/ui/widgets/built-in-widgets/ratingbar.md#adding-a-ratingbar-to-your-project "Direct link to Adding a RatingBar to Your Project") Here's an example of how you can use the RatingBar widget in your project: 1. First, drag the **Column** widget from the **Layout Elements** tab (in the Widget Panel) or add it directly from the widget tree. Set its **Cross Axis Alignment** to **Start**. 2. Now add one **Image** widget inside the column and set its **Width** property to **inf** and **Height** property to 200. 3. Add a **Text** widget (Inside the Column). Change the **name** to **Item Name** and the **Theme Style** to **Title 1.** Set the **Left Padding** to 10. 4. Add another **Text** widget. Change the **name** to **Item Description** and the **Theme Style** to **Subtitle 2.** Set the **Left Padding** to 10. 5. Finally, add the **RatingBar** widget from the **Form Elements** tab or add it directly from the widget tree. ### Collectings Ratings from Users (Interactive RatingBar)[​](/resources/ui/widgets/built-in-widgets/ratingbar.md#collectings-ratings-from-users-interactive-ratingbar "Direct link to Collectings Ratings from Users (Interactive RatingBar)") To collect ratings from users: 1. Select **RatingBar** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Rating Bar Properties** section. 3. Find the **Interactive** property and checkmark it (click on it). ### Setting The Rating Value[​](/resources/ui/widgets/built-in-widgets/ratingbar.md#setting-the-rating-value "Direct link to Setting The Rating Value") The Rating can be set by inputting an amount or set from a variable. This is only for a RatingBar that is not interactive. To manually set the Rating value for the RatingBar: 1. Select **RatingBar** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Rating Bar Properties** section. 3. Find the **Rating** property and change the default value. info You can also enter the value in decimal such as 1.5. When a decimal is used, a portion of the icon will be colored. ### Customize the Icon[​](/resources/ui/widgets/built-in-widgets/ratingbar.md#customize-the-icon "Direct link to Customize the Icon") Here's an example of how you can customize the icons appearing in the RatingBar: 1. Select **RatingBar** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Rating Bar Properties** section. 3. Find the **Icon Count** property and change the value to 10. 4. Set the **Icon Size** property to 30. 5. Find the **Icon Selector** property below, Click on the **Start Rounded** button, then search and select the icon name with **FontAwesome.smile**. ### Changing the Rated/Unrated Color[​](/resources/ui/widgets/built-in-widgets/ratingbar.md#changing-the-ratedunrated-color "Direct link to Changing the Rated/Unrated Color") To change the rated and unrated color (color for icons that are not filled in) for the RatingBar: 1. Select **RatingBar** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Rating Bar Properties** section. 3. Now, find the **Rated Color** property, Click on the box next to **Secondary**, select the color, and then click **Use Selected Color** or click on **Secondary** and enter a Hex Code directly. You can also choose the color by clicking on the Palette and Simple button. 4. Similarly, set the **Unrated** **Color** as well. ### Add Padding between Icons[​](/resources/ui/widgets/built-in-widgets/ratingbar.md#add-padding-between-icons "Direct link to Add Padding between Icons") To add padding between icons: 1. Select **RatingBar** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Rating Bar Properties** section. 3. Find the **Icon Padding** property and enter the values. info Use the Lock button to change the Left, Top, Right and Bottom padding all at the same time. Unlocking will allow you to modify each value separately. ### Changing the Axis[​](/resources/ui/widgets/built-in-widgets/ratingbar.md#changing-the-axis "Direct link to Changing the Axis") In a very rare case, you may want to make all icons (inside the RatingBar) appear vertically. This can be done using the Axis property. To change the Axis: 1. Select **RatingBar** from the widget tree or from the canvas area. 2. Move to the Property Editor and scroll down to the **Rating Bar Properties** section. 3. Find the **Axis** dropdown and change it to **Vertical**. --- # Signature The signature widget allows you to capture a signature. This widget tracks your finger or mouse pointer on a screen and draws the line accordingly on a signature pad. You can use this widget to get the user consent on an agreement or contract in digital form. ## Adding Signature widget[​](/resources/ui/widgets/built-in-widgets/signature.md#adding-signature-widget "Direct link to Adding Signature widget") Here's an example of how you can add the Signature widget to your project: 1. First, drag the **Signature** widget from the **Form Elements** tab (in the Widget Panel) or add it directly from the widget tree. 2. Move to the properties panel, scroll down to the **Signature** section and adjust the **width** and **height** of the widget. ## Saving signature to Firestore document[​](/resources/ui/widgets/built-in-widgets/signature.md#saving-signature-to-firestore-document "Direct link to Saving signature to Firestore document") You might be using the Firestore database to store your app data in the collection-document model. Let's see how you can save the signature into the Firestore document. The drawn signature is first uploaded and stored as an image into the [Firebase Storage](https://firebase.google.com/docs/storage) using the *Upload Signature* action. This returns the uploaded URL, which can be stored inside the Firestore document for later access. Prerequisites Ensure you incorporate all the mentioned prerequisites. * Be familiar with [**Structuring the Firebase Database**](/integrations/database/cloud-firestore/getting-started.md#structuring-the-database). * Complete all steps in the [**Firebase Setup**](/integrations/firebase/connect-to-firebase.md) section for your project. * [**Firebase Authentication**](/integrations/authentication/firebase/initial-setup.md) must be properly configured. * [**Firebase Storage**](/integrations/firebase-storage/storage-rules.md) rules must be deployed. Saving signature to Firestore document comprises the following steps: ### 1. Create Image Path field[​](/resources/ui/widgets/built-in-widgets/signature.md#1-create-image-path-field "Direct link to 1. Create Image Path field") Create a Firestore Collection with the schema that contains a field with an Image Path data type. ![image-path-field](/assets/images/image-path-field-0b5a207a3ecbad66e7606284bace3a46.avif) ### 2. Upload signature \[Action][​](/resources/ui/widgets/built-in-widgets/signature.md#2-upload-signature-action "Direct link to 2. Upload signature \[Action]") Using this action, you can upload the drawn signature to [Firebase Storage](https://firebase.google.com/docs/storage). This action returns the Uploaded URL, which you can use to show its content or store in a database to access it later. Follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., Button) on which you want to define the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Click on the **+ Add Action**. 2. On the right side, search and select **Upload Signature**. 3. Set the **Signature to Upload** to the name of the signature widget. (i.e., Signature by default). 3. Click **Close**. ### 3. Passing signature image URL into document field[​](/resources/ui/widgets/built-in-widgets/signature.md#3-passing-signature-image-url-into-document-field "Direct link to 3. Passing signature image URL into document field") The *Upload Signature* action (added in the previous step) returns the URL of the signature image. You can use it to pass into the document field by adding the action that creates or updates the document, such as [Create Document](/integrations/database/cloud-firestore/firestore-actions.md#create-document-action) or [Update Document](/integrations/database/cloud-firestore/firestore-actions.md#update-document-action). Here are the steps in detail: 1. Select the **Widget** (e.g., Button) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **Open**. This will open an **Action Flow Editor** in a new popup window. 1. Select the already added **Upload Signature Action**, click on the **+** button at the bottom of the box and select **Add Action**. 2. On the right side, search and select **Create Document** or **Update Document**. 3. If you select **Create Document**. 1. Set the **Collection** to your collection name (e.g., todo). 4. If you select **Update Document**, set the document reference to update. 1. If you have access to the document, set the **Source** to the **actual document** and **Available Options** to **reference**. 5. Under the **Set Fields** section, click on the **+ Field** button. 6. Click on the Field name until you see the fields that store the slider value. 1. Set the **Value Source** to **From Variable**. 2. Click on the **UNSET** (this will open a popup on the left side). 3. Select the **Widget State** and then select **Uploaded Signature URL**. 7. **Close** the action flow editor. ## Clear signature \[Action][​](/resources/ui/widgets/built-in-widgets/signature.md#clear-signature-action "Direct link to Clear signature \[Action]") You can allow users to delete the signature if they make a mistake or want to get the perfect signature. You can do this by adding the *Clear Signature* action. Follow the steps below to define the Action to any widget. 1. Select the **Widget** (e.g., IconButton with canceling or delete icon) on which you want to define the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 1. Search and select **Clear Signatures**. 2. Select the **Signature Fields** from the list below. This helps when you have multiple signature widgets on a page and want to clear only selected one(s). 3. Click **Close**. ## Customization[​](/resources/ui/widgets/built-in-widgets/signature.md#customization "Direct link to Customization") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Customizing pen[​](/resources/ui/widgets/built-in-widgets/signature.md#customizing-pen "Direct link to Customizing pen") To change the pen color and stroke width: 1. Select the **Signature** widget from the widget tree or the canvas area. 2. Move to the properties panel, and scroll down to the **Signature** section. 3. Find the **Pen Color** property and click on the box next to the already selected color, select the color, then click **Use Color** or click on an already selected color and enter a Hex Code directly. 4. Find the **Pen Stroke Width** property and enter the value. The higher value increases the thickness of the stroke. --- # Slider The Slider widget is used to select a single value from a range of values. You define the min and max value for the slider, and users can choose the value between the specified range by dragging the slider thumb (sliding circle). For example, you can use the **Slider** widget to allow users to set the volume, set the donation amount, etc. Widget State Before diving into form widgets, check out our guide on [**Widget States**](/concepts/state-management/widget-state.md) to efficiently manage the state and behavior of your form elements. ## Adding Slider[​](/resources/ui/widgets/built-in-widgets/slider.md#adding-slider "Direct link to Adding Slider") Let's build an example of using the Slider widget and retrieve its value in a Text widget. The steps to build the example are as follows: 1. First, add the **Slider** widget from the **Form Elements** tab or add it directly from the widget tree. 2. Now, add the **Text** widget to display the slider value. 3. Keep the **Text** widget selected, Move to the properties panel, and click on the **Set from Variable**. This will open a new panel. 1. Set **Source** to **Widget State**. 2. Set the **Available Options** to **Slider**. If you add multiple sliders, the names would be like Slider1, Slider2, and so on. 3. Set the **Number Format Option** if you wish to. 4. Click **Confirm**. ## Trigger Action on Change[​](/resources/ui/widgets/built-in-widgets/slider.md#trigger-action-on-change "Direct link to Trigger Action on Change") See how to [trigger an action when a selection changes](/resources/forms/form-triggers.md#on-selected) on this widget. ## Setting initial value[​](/resources/ui/widgets/built-in-widgets/slider.md#setting-initial-value "Direct link to Setting initial value") Sometimes you might want to display the slider with the default value. For example, showing the volume slider with the audible volume value. You can do so by setting the initial value for the Slider. ## Customization[​](/resources/ui/widgets/built-in-widgets/slider.md#customization "Direct link to Customization") You can customize the appearance and behavior of the widget using the various properties available under the properties panel. ### Setting platform type[​](/resources/ui/widgets/built-in-widgets/slider.md#setting-platform-type "Direct link to Setting platform type") You can set the platform type to *Adaptive or Android* for this widget. Selecting the Adaptive type will display the widget in its native style. That means the widget will show iOS-style rendering when running on iOS devices and Android-style rendering when running on Android devices. To set the platform type: 1. Select the **Slider** widget from the widget tree or the canvas area. 2. Move to the properties panel and open the **Platform** section. 3. Set the **Platform Type** among the **Android** or **Adaptive**. ### Defining slider range[​](/resources/ui/widgets/built-in-widgets/slider.md#defining-slider-range "Direct link to Defining slider range") You can define the slider range by setting the min and max values. To set the min and max values: 1. Select the **Slider** widget from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Slider Properties** section. 3. Find the **Min** property and enter the value. This will be the start value of the range. 4. Find the **Max** property and enter the value. This will be the end value of the range. ### Setting step size[​](/resources/ui/widgets/built-in-widgets/slider.md#setting-step-size "Direct link to Setting step size") By default, you can move and stop the slider thumb at any place on the slider track. To make the slider thumb stop at a specific interval, you can set the step size value. info If the range is not evenly divisible by the step size, the slider thumb will stop at the closest value in the range. To set the step size: 1. Select the **Slider** widget from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Slider Properties** section. 3. Find the **Step Size** property and enter the value. ### Changing color[​](/resources/ui/widgets/built-in-widgets/slider.md#changing-color "Direct link to Changing color") To change the slider colors: 1. Select the **Slider** widget from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Slider Properties** section. 3. To change the active color, find the **Active Color** property, click on the box next to the already selected color, select the color, and then click **Use Color** or click on an already selected color and enter a Hex Code directly. You can also choose the color by clicking the **Palette** and **Simple** button. 4. To change the inactive color, find the **Inactive Color** property, click on the box next to the already selected color, select the color, and then click **Use Color** or click on an already selected color and enter a Hex Code directly. You can also choose the color by clicking the **Palette** and **Simple** button. ### Showing slider value[​](/resources/ui/widgets/built-in-widgets/slider.md#showing-slider-value "Direct link to Showing slider value") You can show the slider value while moving the slider thumb on the track. The value appears as a tooltip above the slider thumb. To show the slider value: 1. Select the **Slider** widget from the widget tree or the canvas area. 2. Move to the properties panel and scroll down to the **Slider Properties** section. 3. Find the **Show Value** property and turn on the toggle. --- # Spacer The [Spacer widget](https://www.youtube.com/watch?v=7FJgd7QN1zI) is used to insert a flexible empty space between the children of the Column and Row widget. ![img.png](/assets/images/spacer-0a2253d3f9f18a42a86c74ea3d76474c.png) If you want even space between your child widgets, you can add space by setting the **Main Axis Alignment** to **Space Around**, **Space Evenly,** and **Space Between.** If you want a more customized space between your child widgets (example below), you should use the Spacer Widget. info The Spacer widget takes all of the available space so the Spacer Widget will have no effect on a Column or Row where the **Main Axis Alignment** is set to **Space Around**, **Space Evenly,** and **Space Between.** To use the Spacer widget, add it between the children of your Row or Column wherever you like, and set the flex value to a positive whole number. By default, it is set to 1. ![spacer-widget.png](/assets/images/spacer-widget-457ac9a558b9e8844c4cb3e46122937a.png) Spacer Example In the example above, we have added two Spacer widgets between the Row children. One is set to 3, therefore taking up three times more space than the other Spacer widget, which is set to 1. --- # StickyHeader The StickyHeader widget is a special type of widget that allows the top part of a scrollable list to "stick" or remain visible at the top of a viewport while the rest of the content can be scrolled. As users scroll down, the sticky header remains fixed at the top, providing consistent context or navigation cues. For instance, In data-heavy applications where users scroll through large data tables, sticky headers ensure that the column titles are always visible, enhancing usability and readability. StickyHeader widget in action The StickyHeader widget consists of two primary sections: the *StickyHeader Header* and the *StickyHeader Content*. * **StickyHeader Header**: This section contains the widget that remains fixed at the top while scrolling. It is typically used to display headers, titles, or important information that should stay visible at all times. * **StickyHeader Content**: This section contains the scrollable widget, such as ListView or GridView, that holds the main content. It allows users to scroll through the content while the header remains in place. Please note For the StickyHeader widget to work, you must add it inside the scrollable widget, such as Column and ListView, and make them the **Primary** scrollable widget. **Note**: When you add it inside the Column, make sure you make the column **scrollable**. This enables the desired behavior of the header to stick at the top while the content scrolls. ![img\_1.png](/assets/images/img_1-f80cacf0889a64d442e5907793c4005c.png) StickyHeader sections ## Adding StickyHeader widget[​](/resources/ui/widgets/built-in-widgets/sticky-header.md#adding-stickyheader-widget "Direct link to Adding StickyHeader widget") Let's see how you can use the StickyHeader widget as a replacement for the **AppBar** by building an example that contains a search bar as a sticky header. Here's how it looks: Using a search bar as a sticky header widget Here are the steps to build such an example: 1. First, ensure you have a Column widget on a page. if not, add it. Also, make the Column widget **scrollable** and **Primary**. 2. Add the **StickyHeader** widget from the **Base Elements** tab. 3. Inside the **StickyHeader Header**, add a widget that you want to stay at the top when scrolling. For this example, it's the search bar. 4. Inside the **StickyHeader Content**, add the **ListView > Container** widgets to display a list of users. 5. Query and display a list of users in a ListView. ## Another example[​](/resources/ui/widgets/built-in-widgets/sticky-header.md#another-example "Direct link to Another example") When displaying a long list with categorized sections, such as a contacts list with alphabetical sections (A, B, C...), you can use the `StickyHeader` widget to keep the section headers (e.g., letters) visible as users scroll through the contact list. The aim is to generate StickyHeader widgets corresponding to each letter. Inside each StickyHeader, display contacts matching its starting letter. By dynamically generating StickyHeader widgets per letter, we can provide a structured view with grouped contacts. Here's how it looks when completed: Contact list page using StickyHeader widget Here are the steps to build such an example: 1. Prepare a list of letters starting from A-Z. You can use the `AppState` variable for this. ![img\_2.png](/assets/images/img_2-c110529846ac9814ffd79fbdfffc630f.png) 2. Prepare a list of contacts. ![img\_3.png](/assets/images/img_3-cb5b5453c62028011d19f823b3e07cd9.png) 3. Add the **ListView > StickyHeader** widgets. 1. In ListView, generate dynamic children from a variable that holds the letters. 2. Inside the `StickyHeader` section, add a widget to display the current letter. 4) Now, inside the *StickyHeader* *Content* section, add the **ListView** with a **Container** inside to display the list of matching contacts. 1. On this ListView, generate dynamic children from a variable that holds all the contacts. But while doing so, filter the list and extract only matching contacts using [Inline Function](/resources/functions/utility.md#inline-function-code-expressions). 2. Now you can display the contact's details, such as name, inside the UI. --- # SwipeableStack The SwipeableStack is a widget designed to stack cards or content layers that users can swipe in any direction. It is commonly used in dating apps like Tinder for profile browsing. ## Adding SwipeableStack widget[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#adding-swipeablestack-widget "Direct link to Adding SwipeableStack widget") To add a Stack widget: 1. Open the [Widget Palette](/flutterflow-ui/widget-palette.md) and locate the **SwipeableStack** widget under the **Layout Elements** tab. You can drag it into your desired location or add it directly from the widget tree or canvas area. 2. By default, it adds four cards and is represented as **SwipeableStack Page**. To see another page in the canvas, move to the **Properties Panel >** set the **Active Page** to the card you want to see. 3. To add a new card, move to the **Properties Panel > Active Page >** click **+ Add Page**. 4. To delete any card, select the **SwipeableStack Page** (which you want to delete) from the widget tree or the canvas area and press the **Delete** key on the keyboard. 5. By default, SwipeableStack Page contains an Image widget; however, you can customize it as per your requirement. For example, if you want to create a Tinder like user experience, you could wrap (`⌘` + B) the default image widget inside the Stack widget and then add some more widgets. ## Swipe card on the button press[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#swipe-card-on-the-button-press "Direct link to Swipe card on the button press") You might want to allow users to swipe the cards with a button press—for instance, swiping a card left through an 'unlike' or 'reject' button, and right with a 'like' or 'accept' button. Here's how you can swipe the card with a button press: 1. First add the [SwipeableStackwidget](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#adding-swipeablestack-widget). 2. Add a couple of buttons inside. 3. Now, [add the Control SwipeableStack action](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#control-swipeable-stack-action). ## Get notified on swipe[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#get-notified-on-swipe "Direct link to Get notified on swipe") You might want to get a callback when the child widget (e.g., card) gets swiped and then add further actions. For example, updating the item (like or unlike flag) in the backend based on the swipe type (left or right). Here is how you can get a callback when the child widgets get swiped: 1. Select the **SwipeableStack** widget. 2. Select **Actions** from the Properties panel and open **Action Flow Editor**. 3. Select the swipe type (among the **OnWidgetSwipe, OnLeftSwipe, OnRightSwipe, OnUpSwipe, On Down Swipe**) on which you would like to get a callback. If the swipe direction is not important to you, select **On Widget Swipe**. 4. Now you can add any action that will be triggered upon receiving the selected callback—for example, showing the Snackbar message on swipe. ## Customizing[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the properties panel. ### Loop cards[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#loop-cards "Direct link to Loop cards") To loop the cards in SwipeableStack, move to the **Properties Panel > SwipeableStack Properties >** turn on the **Loop** toggle. ![loopcard](/assets/images/loopcard-c521cf860905090a85c97a048b081aa5.avif) ### Allowed Swipe Direction[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#allowed-swipe-direction "Direct link to Allowed Swipe Direction") You can control the directions in which users can swipe cards by adjusting the **Allowed Swipe Direction** property. It enables you to customize how users interact with the SwipeableStack, letting you limit swipes to certain directions or enable swiping in any direction. To do so, navigate to the **Properties Panel > SwipeableStack Properties > Allowed Swipe Direction**, and select one of the following options: * **All**: Users can swipe in all directions. * **Left**: Swipe only to the left. * **Right**: Swipe only to the right. * **Down**: Swipe only downward. * **Up**: Swipe only upward. * **Vertical**: Swipe up or down. * **Horizontal**: Swipe left or right. For example, in Tinder-like Swipeable Cards layout, you can set the **Allowed Swipe Direction** to **Horizontal**, enabling users to swipe left to "dislike" and right to "like" a profile. ![allowed-swipe-direction.png](/assets/images/allowed-swipe-direction-fa49e6decdd522be89636cc3116b25cf.png) ### Customize card display count and scale[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#customize-card-display-count-and-scale "Direct link to Customize card display count and scale") You can adjust how many cards are visible in the stack at one time and how they are scaled. This customization enhances the UX by letting you create a more engaging and visually appealing card stack, where the depth and hierarchy of cards can be easily perceived by users. To do so, move to the **Properties Panel > SwipeableStack Properties >** enter the value in **Card Display Count** and **Next Card Scale**. For *Next Card Scale,* experiment with values ranging from 0.9 to 0.99 to achieve the desired visual effect. ### Change swipe threshold[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#change-swipe-threshold "Direct link to Change swipe threshold") A "threshold" typically refers to the sensitivity of swipe gestures. It determines how much a user needs to swipe a card for it to be considered a complete swipe action. It accepts value between 0 and 1; the threshold set closer to 1 requires the user to swipe or drag the card further across the screen to trigger a swipe action. To do so, move to the **Properties Panel > SwipeableStack Properties >** enter the value in **Swipe Threshold** property. ### Set card swiping angle[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#set-card-swiping-angle "Direct link to Set card swiping angle") You can control the tilt or rotation effect of cards as they are swiped. The *Max Angle* property allows you to set the maximum rotation angle a card can reach during a swipe gesture. To do so, move to the **Properties Panel > SwipeableStack Properties >** enter the value (0-360) in **Max Angle** property. ### Change back card offset[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#change-back-card-offset "Direct link to Change back card offset") You can control how the subsequent cards are visually offset relative to the top card, creating a layered effect. This enhances the visual depth and appeal of the card stack within the app. To change the offset of the back cards move to the **Properties Panel > SwipeableStack Properties > Back Card Offset >** enter the values in **Horizontal** and **Vertical** boxes. *** ## Control Swipeable Stack \[Action][​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#control-swipeable-stack-action "Direct link to Control Swipeable Stack \[Action]") Using this action, you can swipe the widgets inside the SwipeableStack widget. For example, swiping the card left or right with the tap of a button. ### Types of card swipe[​](/resources/ui/widgets/built-in-widgets/swipeable-stack.md#types-of-card-swipe "Direct link to Types of card swipe") There are the following types of card swipes you can add: * **Trigger Left Swipe**: Moves the current card from right to left. * **Trigger Right Swipe**: Moves the current card from left to right. * **Trigger Up Swipe**: Moves the current card upwards from bottom to top. * **Trigger Down Swipe**: Moves the current card downwards from top to bottom. --- # Tooltip The Tooltip widget provides additional information or visual cues of a widget in a small popup box. It appears when the user taps or long-presses the widget or hovers over it. It's typically used to provide an explanation about the function of a widget. info It is not frequently used on touch devices where tapping or long-pressing can initiate other actions. But they can be incredibly useful in the desktop environment where hover functionality is available. ![tooltip.png](/assets/images/tooltip-0ef2d763bc6f64243f713d5c1c530220.png) ## Adding Tooltip widget[​](/resources/ui/widgets/built-in-widgets/tooltip.md#adding-tooltip-widget "Direct link to Adding Tooltip widget") To add the *Tooltip* widget to your app: 1. Identify the widget you want to provide a description for and right-click on it. Select **Wrap Widget** and then select **Tooltip** widget. 2. Now select the **Tooltip** widget, move to the **Properties Panel > Message > Text**, and enter the message you want to display. ## Customizing[​](/resources/ui/widgets/built-in-widgets/tooltip.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the Properties Panel. ### Component as Tooltip[​](/resources/ui/widgets/built-in-widgets/tooltip.md#component-as-tooltip "Direct link to Component as Tooltip") Sometimes, you may want to display more than just text in a tooltip—such as images, icons, buttons, or other custom components. For example, in an e-commerce app, a tooltip could show a detailed breakdown of customer reviews when users hover over the overall rating. To achieve this, simply set the **Tooltip Type** to **Component** and select the custom component you'd like to display. To display dynamic content in tooltips, you can create a wrapper component that accepts a [**WidgetBuilder**](/resources/ui/components/widget-builder.md) as a parameter and use this component within the tooltip. Here’s exactly how you do it: ### Change trigger mode[​](/resources/ui/widgets/built-in-widgets/tooltip.md#change-trigger-mode "Direct link to Change trigger mode") On touch devices, the *Tooltip* opens on tap. To make it open on long press instead, use the **Trigger Mode** property. ### Show Tooltip on Focus[​](/resources/ui/widgets/built-in-widgets/tooltip.md#show-tooltip-on-focus "Direct link to Show Tooltip on Focus") The **Show Tooltip on Focus** toggle controls whether the tooltip is displayed when the child widget receives keyboard focus. This is particularly useful for improving accessibility and keyboard navigation, as it ensures users see helpful information when they tab through form fields, interactive elements or any important information. ![tooltip-on-focus](/assets/images/tooltip-on-focus-1e2b4249df8108342df62a4ea8d69523.avif) ### Change tooltip alignment[​](/resources/ui/widgets/built-in-widgets/tooltip.md#change-tooltip-alignment "Direct link to Change tooltip alignment") By default, the *Tooltip* appears below the target widget. You can change this setting using the **Preferred Direction** property. This allows you to open the Tooltip **Above**, **Left,** and **Right** directions in addition to the **Below**. ### Customize tail size[​](/resources/ui/widgets/built-in-widgets/tooltip.md#customize-tail-size "Direct link to Customize tail size") To change the tail's size, you can use the **Tail Width** and **Tail Length** properties. ### Changing background color[​](/resources/ui/widgets/built-in-widgets/tooltip.md#changing-background-color "Direct link to Changing background color") You can change the Tooltip's background color using the **Background Color** property. ![tooltip-bckgrnd.png](/assets/images/tooltip-bckgrnd-2b518112d20901711080da59327256da.png) ### Set tooltip offset[​](/resources/ui/widgets/built-in-widgets/tooltip.md#set-tooltip-offset "Direct link to Set tooltip offset") By setting the tooltip offset, you can adjust the space between the tooltip and the target widget. To do so, move to the **Properties Panel >** set the **Offset** value. ![tooltip-offset.png](/assets/images/tooltip-offset-529df1e8b721d43198f546967572f525.png) ### Customize border radius[​](/resources/ui/widgets/built-in-widgets/tooltip.md#customize-border-radius "Direct link to Customize border radius") To change the rounded corner of the Tooltip widget, move to the **Properties Panel >** set the **Border Radius** property. ![radius.png](/assets/images/radius-e3cc8f869e0ad07b580e37adc4592767.png) ### Elevate tooltip[​](/resources/ui/widgets/built-in-widgets/tooltip.md#elevate-tooltip "Direct link to Elevate tooltip") To add a shadow or to create a sense of depth on this widget, you can use the **Elevation** property. It allows a widget to stand out, making it appear like it's floating above the surface of the UI, ultimately making the tooltip more noticeable. ![elevate-tooltip.png](/assets/images/elevate-tooltip-3fffcfff953b0324ad25a7c1259ba90a.png) toolt ### Set internal padding[​](/resources/ui/widgets/built-in-widgets/tooltip.md#set-internal-padding "Direct link to Set internal padding") In case you want to add some space around the tooltip message, navigate to the **Properties Panel >** set the **Padding** property. ![internal-padding.png](/assets/images/internal-padding-2a4ac4b4e9f7886f70bb660b07701921.png) ### Change wait duration[​](/resources/ui/widgets/built-in-widgets/tooltip.md#change-wait-duration "Direct link to Change wait duration") The wait duration specifies the amount of time that the Tooltip widget waits before it displays. To change this setting, move to the **Properties Panel >** set the **Wait Duration** value. ### Change show duration[​](/resources/ui/widgets/built-in-widgets/tooltip.md#change-show-duration "Direct link to Change show duration") The show duration specifies the duration for which the Tooltip widget continues to be displayed on the screen, even after the user has navigated away from it. As a best practice, it's often recommended to set this value to zero. This ensures that the tooltip disappears instantly once the user navigates away. To change the default duration, move to the **Properties Panel >** set the **Show Duration** value. --- # Transform The `Transform` widget applies graphic transformations such as skew (or tilt), rotate, scale, and translate (or slide) to its child widget. You could use this widget in combination with animations to build visually engaging apps. ![transform.png](/assets/images/transform-aabf5d89972f979388a475a22f597383.png) ## Adding Transform widget[​](/resources/ui/widgets/built-in-widgets/transform.md#adding-transform-widget "Direct link to Adding Transform widget") To add a Transform widget to your app: 1. First, click on the **+ Add Widget**, drag the **Transform** widget from the **Base Elements** tab, or add it directly from the widget tree. 2. Add a child widget inside the Transform widget that you want to transform. 3. By default, the transformation applied to a child widget is the **Skew** transformation. This type of transformation allows you to tilt the child widget, i.e., top and bottom or the left and right sides no longer remain to be parallel. To add/customize tilt to the child widget: 1. Select the **Transform** widget and move to the properties panel. 2. To add tilt in the horizontal direction, find the **Skew X** property and use the slider or directly enter the value into the box. The positive value will move the top side to the left and the bottom side to the right. 3. To add tilt in the vertical direction, use the **Skew Y** property. The positive value will move the left side in an upward direction and the right side in a downward direction. 4. The negative value will move the sides in the opposite direction. 4. Optional: To change the position of the origin (a center of the transform widget), you can use the **Transform Orgin and Alignment** options. ## Customizing[​](/resources/ui/widgets/built-in-widgets/transform.md#customizing "Direct link to Customizing") You can customize the appearance and behavior of this widget using the various properties available under the [Properties Panel](/flutterflow-ui/builder.md#properties-panel). ### Changing transform type[​](/resources/ui/widgets/built-in-widgets/transform.md#changing-transform-type "Direct link to Changing transform type") To change the transform type, select the **Transform** widget, move to the properties panel, find the **Transform Type** dropdown and choose the desired one. * For **Scale** type, use the **Scale X** property to increase or decrease the size in the horizontal direction. Use the **Scale Y** property to change the size in the vertical direction. For example, If you enter 0.5, it will make the widget half the size, whereas value two will make the widget twice its size. - For **Rotate** type, use the **Rotate (degree)** property to turn the widget. The value must be in degrees (i.e., 0 to 360). By default, the widget rotates in a clockwise direction. To turn the widget anticlockwise, enter the negative value. * For **Translate** type: * Set the **Translate X** property to slide the widget in horizontal direction. The positive value will move the widget in the right direction, whereas the negative value will move in the left direction. * Set the **Translate Y** property to slide the widget in the vertical direction. The positive value will move the widget in a downward direction, whereas the negative value will move in an upward direction. --- # Button The Button widget is a fundamental component in user interface design, utilized extensively across web and mobile applications. It serves as a primary means of user interaction, allowing users to execute actions or commands within an application. Buttons are essential for: * **Initiating Actions:** Whether it's submitting a form, opening a new page, or performing any operational task, buttons trigger these functionalities. * **User Feedback:** Buttons often change visually in response to user actions—like hover effects, changes in color on click, or disabled states—providing immediate visual feedback that confirms an action has been recognized. * **Navigational Purposes:** Buttons can guide users through a site or application, such as moving to the next page of a form or returning to the home page. * **Enhancing User Experience:** Well-designed buttons are crucial for creating a smooth and intuitive user experience. They are designed to be easily recognizable and accessible, facilitating a seamless interaction by clearly communicating their function. When you add a Button widget to your Page or Component and select it, the Properties Panel on the right displays various properties and functionalities: Some significant properties are illustrated below: ### Button Default Styling Settings[​](/resources/ui/widgets/button.md#button-default-styling-settings "Direct link to Button Default Styling Settings") Define the initial appearance of your button, including its size, color, border, and padding. These settings determine how the button looks under default conditions. ![button.png](/assets/images/button-1fe4d0fc73df7d0734e49fd89437a0d2.png) ### Button Disabled & Hover Settings[​](/resources/ui/widgets/button.md#button-disabled--hover-settings "Direct link to Button Disabled & Hover Settings") Customize how your button appears when disabled or when a user hovers over it. These settings allow you to alter the button's color, border, and elevation to indicate its state visually. ![button-disabled.png](/assets/images/button-disabled-799361e254ccd5c50136ed53437068f4.png) Additionally, you can define the style of the text inside the Button and, if enabled, the style of the Icon within the Button. --- # Composing Widgets In FlutterFlow, creating a complex user interface often involves combining simpler widgets into more intricate layouts. While atomic widgets like **Text, Button, Image**, and **Icon** form the building blocks of your UI, you’ll use molecular widgets like **Row**, **Column**, and **Stack** to arrange these atomic widgets into a structured layout. As you grow more comfortable with these, you can advance to using **Lists** and **Grids** for even more dynamic and complex compositions. ## Molecular Widgets: Row, Column, and Stack[​](/resources/ui/widgets/composing-widgets/.md#molecular-widgets-row-column-and-stack "Direct link to Molecular Widgets: Row, Column, and Stack") To start composing more sophisticated interfaces, FlutterFlow provides essential molecular widgets like **Row, Column**, and **Stack**. These widgets allow you to control the arrangement of atomic widgets within your app. * **Row:** This widget aligns its children horizontally in a single line, from left to right. It's useful for creating layouts where elements need to be placed side by side, such as icons with labels or buttons in a toolbar. * **Column:** This widget aligns its children vertically, from top to bottom. It's perfect for creating lists of items or laying out sections of a page vertically. * **Stack:** This widget allows for overlapping widgets by placing them on top of each other. It’s ideal for creating layered effects, like placing text over an image or adding a badge to an icon. ![row-col-stack.png](/assets/images/row-col-stack-43692a7d10f09d07ddb08295cc2b1055.png) info Learn more about how to compose widgets with **[Row, Column & Stack](/resources/ui/widgets/composing-widgets/rows-column-stack.md)**. ## Advanced Composition: Lists & Grids[​](/resources/ui/widgets/composing-widgets/.md#advanced-composition-lists--grids "Direct link to Advanced Composition: Lists & Grids") As you become more familiar with using molecular widgets like **Row**, **Column**, and **Stack**, you can begin working with **Lists** and **Grids**. These widgets are specifically designed to handle large sets of data or dynamic content, making them essential for more complex layouts. * **Lists:** While a `Column` is useful for stacking a few items vertically, a `ListView` is designed to handle potentially infinite items by allowing the content to scroll. This makes it ideal for things like a chat app, news feed, or any list that can grow beyond the screen size. One of the key advantages of using a ListView is also its built-in support for **lazy loading**. Lazy Loading Lazy loading means that the `ListView` only builds and renders the items that are currently visible on the screen. As the user scrolls, `ListView` dynamically loads additional items just in time. This significantly improves performance, especially when dealing with long lists of data, by conserving memory and processing resources. * **Grids:** A GridView organizes items into a two-dimensional grid. It's perfect for displaying items like photos, products, or any other type of content that benefits from being presented in a grid format, making it visually appealing and easy to navigate. List & Grids Learn about the advanced properties of **[Lists & Grids](/resources/ui/widgets/composing-widgets/list-grid.md)**. --- # Generate Dynamic Children Widgets capable of handling multiple child widgets have an additional functionality called Generate Dynamic Children that helps you generate multiple child widgets from a `List` variable. This is particularly useful when you are retrieving data from an API call, Firebase Query, or a State variable that holds a List of items. Some of the widgets that can handle multiple children include **[Column, Row, Stack](/resources/ui/widgets/composing-widgets/rows-column-stack.md), [ListView, GridView](/resources/ui/widgets/composing-widgets/list-grid.md),** and **[PageView](/concepts/navigation/pageview.md)**. In the following example, we will use an `AppState` called `categoryList` that holds a List of Product Categories and set the variable to the categoryList widget that is a ListView. note In the demo app, we have predefined custom `DataTypes`. One such DataType is "**Category**," which includes the fields `categoryImg` and `categoryName`. In our App State, **categoryList** is a `List` that holds multiple Category objects. We use this list variable as the value source for our `ListView` widget. The value is stored in a variable (in this example, `allCategoriesList`) and can be used to populate any scrollable view. In our example, we populate the `ListView` widget, which creates multiple instances, each holding a Column with a circular Container and Text. What are Instances? Learn about **[Instances](/resources/ui/overview.md#classes-vs-instances)** and how it compares with **Classes** in this [**document**](/resources/ui/overview.md#classes-vs-instances). To make changes, you need to **modify only the first child** and set the variable sources to the first child widgets. These changes will be applied to all children widgets in the `ListView`. The number of children will match the length of the List variable unless you have set a limit in the **Max Items** option under the **Generating Dynamic Children** tab. Let's see a quick demo to set the variable source of the first child widgets: --- # Lists & Grids In FlutterFlow, `ListView` and `GridView` are versatile widgets designed for displaying lists and grids of elements, respectively. Both are highly customizable and optimized for dynamic content displays, making them essential for any app that requires scrolling through a collection of items such as images, text, or interactive elements. ## ListView Widget[​](/resources/ui/widgets/composing-widgets/list-grid.md#listview-widget "Direct link to ListView Widget") ListView is a scrollable list of widgets arranged linearly. It is ideal for scenarios where items need to be displayed one after another, either **vertically or horizontally**. It is particularly useful for long lists that need to be efficient; only the items visible on the screen are rendered, enhancing performance for lists with a large number of elements. You can customize the ListView properties and functionalities, some are as follows: ### Axis[​](/resources/ui/widgets/composing-widgets/list-grid.md#axis "Direct link to Axis") Axis sets the orientation of the ListView. You can select either "Vertical" or "Horizontal" depending on whether you want the list to scroll vertically or horizontally. ![listview-axis.png](/assets/images/listview-axis-9b98370a7ac7fe23dc7df200f0a8c10c.png) ### Spacing[​](/resources/ui/widgets/composing-widgets/list-grid.md#spacing "Direct link to Spacing") * **Items Spacing:** This defines the space between individual items in the ListView. You can specify the spacing in pixels. Items Spacing vs Padding Prefer “Items Spacing” set on the parent row or column instead of padding on individual elements. This ensures consistency, especially on non-dynamically generated lists. * **Apply to Start & End:** When enabled, the item spacing will also be applied to the start and the end of the ListView, adding a margin at the beginning and end of the list. This effectively adds padding at the start and end of the layout in addition to between the items. * **Start Spacing and End Spacing:** These properties allow you to set additional spacing at the start and end of the ListView, respectively. This can be used to create padding around the list items that is separate from the spacing between the items. ### Advanced Functionalities[​](/resources/ui/widgets/composing-widgets/list-grid.md#advanced-functionalities "Direct link to Advanced Functionalities") * **Shrink Wrap:** When this property is enabled, the ListView will size itself to the total size of its children, meaning it won’t take more space than necessary. This is useful for lists that do not need to be scrollable because they fit within their constraints. * **Primary:** If set to true, the ListView will act as the primary scrolling view in the context. This usually affects how the view interacts with other scrolling views and whether it stretches to fill the viewport. [**See more info here**](/resources/ui/widgets/composing-widgets/list-grid.md#primary-property). * **Reverse:** In lists, when the reverse property is enabled, it reverses the order in which items appear in the ListView. For a vertical list, this means starting from the bottom and for a horizontal list, starting from the right. ![listview-reverse.png](/assets/images/listview-reverse-87c139dec023d5c05aec91c5c21e0735.png) ### Reorderable List[​](/resources/ui/widgets/composing-widgets/list-grid.md#reorderable-list "Direct link to Reorderable List") Whether to allow reordering of items in the list. On Web or Desktop this will add drag handles, but on mobile the reorder is triggerred by long pressing an item. Note This will not automatically persist the order of items in the list, but instead lets you define an action under **"On Reorder**" action trigger to make any necessary changes yourself. CONTENTs of a Reorderable List **Reorderable ListView** must have dynamic children otherwise enabling this will throw an error. Here's a quick tutorial to set up your Reorderable ListView: #### Using App State variable[​](/resources/ui/widgets/composing-widgets/list-grid.md#using-app-state-variable "Direct link to Using App State variable") 1. First, create an app state variable with a few items of type String and display them on the ListView widget. 2. Then, select the ListView, head over to the **Properties Panel > ListView Properties**, and enable the **Reorderable** property. 3. Select Actions from the properties panel (the right menu), and open the **Action Flow Editor.** 4. You'll see an **On Reorder** action trigger. Actions under this are triggered when a user completes repositioning an item in the UI. But, we also need to update the item position in the actual list as well. To do so, we can create a custom action that will modify the item index in the list. 1. Create a custom action with three arguments that accept the actual list, old index, and new index. Tip: You'll get the old and new index from Set Variable menu > Reorderable ListView. 2. Here's the custom code with explanation ``` // Define a function called reorderItems that returns a Future of a list of strings. // It takes in a list of strings, an old index, and a new index as parameters. Future> reorderItems( List list, int oldIndex, int newIndex, ) async { // If the item is being moved to a position further down the list // (i.e., to a higher index), decrement the newIndex by 1. // This adjustment is needed because removing an item from its original // position will shift the indices of all subsequent items. if (oldIndex < newIndex) { newIndex -= 1; } // Remove the item from its original position in the list and store // it in the 'item' variable. final item = list.removeAt(oldIndex); // Insert the removed item into its new position in the list. list.insert(newIndex, item); // Return the modified list. return list; } ``` 5. The custom action returns the modified list, which you can use to update the actual list using the update app state variable action. #### Reordering Items in a Firebase Query[​](/resources/ui/widgets/composing-widgets/list-grid.md#reordering-items-in-a-firebase-query "Direct link to Reordering Items in a Firebase Query") If you want to reorder the list items retrieved via Firebase query collection, the steps are almost similar except for the following changes. Caution Reordering items in a Firebase query is only suited for smaller lists. For larger datasets, this method can be inefficient and might lead to performance issues. Additionally, frequent writes and updates to Firebase can increase costs significantly. 1. Create 'order' field in the collection. 2. Query collection order by 'order' field. 3. Ensure that the Infinite scroll is disabled. 4. Replace the custom action code with the below one: ``` Future reorderFirebaseItems( List list, int oldIndex, int newIndex, ) async { // If the item is being moved down the list, we adjust the newIndex. if (oldIndex < newIndex) { newIndex -= 1; } // Remove the item from its current position in the list. final PlaylistRecord item = list.removeAt(oldIndex); // Insert the item into its new position. list.insert(newIndex, item); // Create a batch to combine multiple Firestore operations into one. final batch = FirebaseFirestore.instance.batch(); // Iterate through the list and update the order field for each document in Firestore. for (int i = 0; i < list.length; i++) { final PlaylistRecord doc = list[i]; // Update the 'order' field of the document with its new index. // This assumes that you have an 'order' field in Firestore where you store the order of the items. batch.update(doc.reference, { 'order': i }); } // Commit all the batched operations to Firestore. return await batch.commit(); } ``` ## ListTile widget[​](/resources/ui/widgets/composing-widgets/list-grid.md#listtile-widget "Direct link to ListTile widget") The `ListTile` widget is a versatile component designed for displaying rows in a list, commonly used for menus, drawers, and lists where each row consists of multiple elements aligned horizontally. `ListTile` is particularly useful when you need a standardized row layout that includes elements a main title, a subtitle, and interactive icons at the start or end of the row. It saves time compared to constructing custom row layouts from scratch while ensuring visual consistency. When to Use ListTile Over Custom Components ListTile should be used when you require a simple, effective layout with standard elements and interactions. It is ideal for: * Lists where items have a uniform structure. * Quick assembly of functional interfaces without needing complex customization. * Scenarios requiring integrated touch feedback and accessibility features which ListTile provides by default. You can customize the Title (Text), Subtitle (Text) and Icon properties from the Properties Panel ![list-tile.png](/assets/images/list-tile-20d6ffe0e8f7e0dbcc14be9a912a365d.png) info To learn about how to customize the Text widgets in this component, refer the [**Text widget**](/resources/ui/widgets/text.md). ### Convert into SlidableListTile[​](/resources/ui/widgets/composing-widgets/list-grid.md#convert-into-slidablelisttile "Direct link to Convert into SlidableListTile") The ListTile in FlutterFlow offers an additional functionality—it can easily be transformed into a slidable version. This enhanced ListTile allows you to embed actions that users can access by sliding the tile to the left, adding a layer of interactivity and utility to the standard list item. Here's how you can enable the Slidable functionality of a ListTile and modify the properties of the Actions: ## GridView Widget[​](/resources/ui/widgets/composing-widgets/list-grid.md#gridview-widget "Direct link to GridView Widget") GridView provides a two-dimensional array of children. It is the widget of choice when you need to display items in a grid pattern, like a photo gallery or a board game layout. Like [ListView](/resources/ui/widgets/composing-widgets/list-grid.md#listview-widget), GridView only renders the visible items, making it efficient for displaying large collections of elements. GridView supports multiple configurations for column count, spacing, aspect ratio, and scroll directions, offering robust customization options for diverse layout needs. ![gridview.png](/assets/images/gridview-4dd3fbd31bc4dba1a1cca05e4992c86d.png) Here's a quick demo to show how to add a GridView widget and modify its properties: ### Staggered View[​](/resources/ui/widgets/composing-widgets/list-grid.md#staggered-view "Direct link to Staggered View") Grid View vs Staggered View **GridView** and **StaggeredView** are similar widgets in FlutterFlow, with the main difference being the layout and sizing of their children. GridView arranges its children in a fixed-size grid, while StaggeredView allows for variable-sized children, creating a more flexible and dynamic layout. StaggeredView is ideal for layouts with items of varying sizes. For example, it can be used to create a layout similar to the Pinterest app. ![staggeredView](/assets/images/staggeredView-bda380985a09f3a676211368bdb80f0c.png) ### Advanced Functionalities[​](/resources/ui/widgets/composing-widgets/list-grid.md#advanced-functionalities-1 "Direct link to Advanced Functionalities") * **Shrink Wrap:** By default, the GridView widget takes up all the available space in its main axis. That means if the Axis property is set to Vertical, GridView will occupy all vertical space on the screen. Similarly, if the Axis is set to Horizontal, then GridView will reserve all the horizontal space. * **Primary:** When set, this indicates whether the GridView is the primary scrollable widget in the layout. A primary GridView handles the scroll interactions, usually necessary when there's only one scrolling view in the viewport. [**See more info here**](/resources/ui/widgets/composing-widgets/list-grid.md#primary-property). Video Tutorial If you prefer watching a video tutorial, here's the one for you: ## Adding infinite scroll[​](/resources/ui/widgets/composing-widgets/list-grid.md#adding-infinite-scroll "Direct link to Adding infinite scroll") The infinite scroll automatically loads the new items as you scroll down the list. It works by showing only a limited number of items (e.g., 15, 25) at first and loads subsequent items before the user reaches the end of the list. At the end of the list, a circular progress bar is visible as the new items are loaded. ![Infinite scroll behind the scene](/assets/images/infinite-scroll-behind-scene-fa69e91aa71918d1aa8713d475819c50.avif) Adding infinite scroll helps you improve the user experience by reducing the initial waiting time (as without infinite scroll, it would take more time to load the long list) and loading new items only when required. The infinite scroll can be added to the list of items retrieved from two sources: * [Infinite scroll on a list from the Firestore collection](/resources/ui/widgets/composing-widgets/list-grid.md#infinite-scroll-on-a-list-from-the-firestore-collection) * [Infinite scroll on a list from API call](/resources/ui/widgets/composing-widgets/list-grid.md#infinite-scroll-on-a-list-from-api-call) ### Infinite scroll on a list from the Firestore collection[​](/resources/ui/widgets/composing-widgets/list-grid.md#infinite-scroll-on-a-list-from-the-firestore-collection "Direct link to Infinite scroll on a list from the Firestore collection") In FlutterFlow, you can directly enable the infinite scroll on a list of items received from the Firestore collection. To enable the infinite scroll: 1. [Query a collection](/resources/backend-query/query-collection.md) on a ListView (skip if you have already done so). 2. Select the ListView, move to the properties panel and, select the **Backend Query** section. 3. Scroll down the already added query and **turn on** the **Enable Infinite Scroll**. 4. On enabling the infinite scroll, the **Listen For Changes** property also gets enabled. That means the list automatically updates if changes are made to the item. This is done to keep all the items up to date on the screen. However, it does not update the list if any new item is added or deleted. In rare cases, you would need to disable this feature. To do so, turn off this property. 5. In infinite scroll, the items are loaded in chunks called pages. The number of items to load on a single page is determined by the **Page Size** property. By default, the value is set to 25 (i.e., load 25 items per page). The ListView loads the first page as soon as it is visible on the screen, and the subsequent pages (with the number of items defined in the Page Size property) are loaded as you scroll down the screen. You can adjust this value according to your design and requirements. 6. Click **Save**. ### Infinite scroll on a list from API call[​](/resources/ui/widgets/composing-widgets/list-grid.md#infinite-scroll-on-a-list-from-api-call "Direct link to Infinite scroll on a list from API call") To add an infinite scroll on the API call, you must have an endpoint that supports pagination with at least one query parameter that accepts a page number like page, offset, etc. #### Pagination Variables[​](/resources/ui/widgets/composing-widgets/list-grid.md#pagination-variables "Direct link to Pagination Variables") When you add the paginated API call in the builder and enable the infinite scroll, we provide you the following pagination variables that you can pass to your API variables. These will be available inside the **Set Variable** menu. ![Pagination Variables](/assets/images/pagination-variable-74b3bf4532bd715de1bc806ab48a6a57.png) 1. **Next Page Index**: You can pass this variable for the query parameter that accepts the page number. The default value is 0 and keeps increasing by one as you scroll down the list until it reaches the end. 2. **#(Number of) Loaded Items**: This equals the number of items returned by the paginated API call. 3. **Last Response**: This is useful if you want to get anything from the last response that might help you retrieve the next set of data. tip When passing the *Number of Loaded Items* for query parameters like *limit*, *per\_page*, *size,* etc., use a *Specific Value,* such as 15,20. Adding infinite scroll includes: 1. [Add paginated API call](/resources/ui/widgets/composing-widgets/list-grid.md#1-add-paginated-api-call) 2. [Passing pagination variable in API call query](/resources/ui/widgets/composing-widgets/list-grid.md#2-passing-pagination-variable-in-api-call-query) #### 1. Add paginated API call[​](/resources/ui/widgets/composing-widgets/list-grid.md#1-add-paginated-api-call "Direct link to 1. Add paginated API call") The paginated API is the API that returns the result in chunks. Most of the paginated API requires you to add the query parameters to know how many items to retrieve and from where to start. For example, this API call requires `per_page` parameter that specifies 20 items to load per page, and `page` parameter tells to start from the first page. This is called page-based pagination. See [how to add the paginated API](/resources/backend-logic/rest-api.md#passing-query-parameters) call by adding query parameters. #### 2. Passing pagination variable in API call query[​](/resources/ui/widgets/composing-widgets/list-grid.md#2-passing-pagination-variable-in-api-call-query "Direct link to 2. Passing pagination variable in API call query") This step includes adding the ListView -> ListTile widget and querying the paginated API call. 1. First, query and show data from API calls. 2. While querying the API call, pass the query parameter value from the pagination variable. ## Primary property[​](/resources/ui/widgets/composing-widgets/list-grid.md#primary-property "Direct link to Primary property") When this property is true and even if the content inside the scrollable widget, such as ListView, or GridView, doesn't overflow the visible area, the user can still attempt to scroll it. The content might move slightly and then bounce back, especially noticeable on iOS with the bounce effect. tip In situations where you have multiple scrollable widgets nested within each other, only one should typically be set as primary. In most cases, the outermost scrollable widget (usually the one that takes up the most space or the full screen) is set as primary, while inner scrollables are not. For example, when you have a widget structure like this Column > ListView, you should keep the Column as primary and ListView as non-primary. ![img\_2.png](/assets/images/img_2-c110529846ac9814ffd79fbdfffc630f.png) ## Pull to Refresh on ListView or GridView[​](/resources/ui/widgets/composing-widgets/list-grid.md#pull-to-refresh-on-listview-or-gridview "Direct link to Pull to Refresh on ListView or GridView") If you've enabled the Single Time Query for a Backend Query in a scrollable widget, it won't refresh the list when items are updated in the backend. To address this, add a pull-to-refresh feature. This user interface pattern allows users to manually refresh content by pulling down the content area, such as a list. When pulled down sufficiently and released, the app will refresh, fetching the latest data or updates. To enable pull to refresh: 1. Select your scrollable widget, such as `ListView`, `GridView`, or `StaggeredView`. 2. Move to the properties panel and select the **Backend Query**. 3. Open the already added query (e.g., Query collection or API call) and make sure the **Single Time Query** is enabled. 4. Switch on the **Enable Pull to Refresh** toggle. This will automatically add the **Refresh Database Request** action on a pull to refresh gesture. ## Scroll To \[Action][​](/resources/ui/widgets/composing-widgets/list-grid.md#scroll-to-action "Direct link to Scroll To \[Action]") Using this action, you scroll the scrollable widget to the beginning or end. info Before adding this action, make sure you have a scrollable widget, such as a **ListView, StaggeredView**, or **GridView**, with enough items to enable scrolling. Follow the steps below to add this action to any widget. 1. Select the **Widget** (e.g., FloatingActionButton) on which you want to add the action. 2. Select **Actions** from the Properties panel (the right menu), and click **+ Add Action**. 3. Search and select the **Scroll To** (under *Widget/UI Interactions*) action. 4. Set the **Scrollable Widget to Control** to the **name** of the scrollable widget (e.g., ListView) added to your page. 5. Set the **Scroll To** either **Beginning** (to scroll to the start) or **End** (to scroll to the end) of the list. 6. Specify the **Duration** in milliseconds (i.e., 1000ms = 1 second). This determines how long the scroll animation will take to complete. **Tip:** If you expect the list to be extensive, consider setting a shorter duration. --- # Rows, Column & Stack In Flutter, `Rows`, `Columns`, and `Stacks` are fundamental layout widgets that help you structure the UI by organizing other widgets in different visual arrangements. Here's how each one works: * **Row**: A `Row` arranges its child widgets in a horizontal line. This is useful when you want to place elements side by side across the screen. * **Column**: A `Column` organizes its child widgets vertically, stacking them from top to bottom. This is ideal for placing elements that need to appear in a vertical sequence, such as a list of messages in a chat app or entries in a form. * **Stack**: A `Stack` layers its child widgets on top of each other, allowing for overlapping elements. In a `Stack`, widgets can be positioned absolutely relative to the edges of the `Stack`, giving you control over the exact location of each element. Each of these widgets serves different purposes and choosing between them depends on how you need to arrange your UI components: ![row-col-stack.png](/assets/images/row-col-stack-43692a7d10f09d07ddb08295cc2b1055.png) Minimum Layout Nesting Use the minimum amount of rows/columns necessary to achieve your layout to avoid unnecessary complexity. No page or component should nest more than 10 levels deep. Reaching this limit likely signals the need for **[converting a part of the widget tree into components](/resources/ui/components/creating-components.md#convert-to-a-component)**. ## Common Property: Alignment[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#common-property-alignment "Direct link to Common Property: Alignment") ### Main Axis[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#main-axis "Direct link to Main Axis") The main axis is the primary direction in which child widgets are laid out in a `Row` or `Column`. **Row:** The main axis runs **horizontally**. Child widgets are arranged from left to right. FlutterFlow allows you to set Row's Main Axis property to the following types: ![row-main-axis.png](/assets/images/row-main-axis-51a849d58cd39f3daaf64557e0845bcb.png) Row's Main Axis property has the following types: Start, End, Center, SpaceEvenly, SpaceAround, SpaceBetween **Column:** The main axis runs **vertically**. Child widgets are laid out from top to bottom. FlutterFlow allows you to set Column's Main Axis property to the following types: ![column-main-axis.png](/assets/images/column-main-axis-cd42d005444cf97750bc8e10eba404aa.png) Column's Main Axis property has the following types: Start, End, Center, SpaceEvenly, SpaceAround, SpaceBetween Manipulating the main axis allows you to control how widgets are spaced and how they should expand or align in relation to each other along this primary direction. ### Cross Axis[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#cross-axis "Direct link to Cross Axis") The cross axis is **perpendicular to the main axis** and controls the alignment and spacing of widgets across this secondary direction. It has the following types: Start, Center, End. **Row:** The cross axis runs **vertically**. It determines how child widgets are aligned from top to bottom within the row. ![row-cross.png](/assets/images/row-cross-9645c4fc85c933db44b7be3eca57c44b.png) Cross Axis types for Row. Main Axis of Row is set to Center. **Column:** The cross axis runs **horizontally**. It controls how child widgets align from left to right within the column. ![column-cross.png](/assets/images/column-cross-b398b7ef4db1839e86e06f7c581e8c0b.png) Cross Axis types for Column. Main Axis of Column is set to Center ### Stack Alignment[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#stack-alignment "Direct link to Stack Alignment") For `Stacks`, the concept of main and cross axes is less applicable because widgets are aligned relative to the entire area of the `Stack`. In FlutterFlow you can control the `Stack` children's alignment using the `Stack` property called *Default Child Alignment* which positions the children using `X` and `Y` coordinates. ![stack-align.png](/assets/images/stack-align-691158330abdc58c1e7f1b408fccd82b.png) Understanding these axes and their properties is essential for effectively designing layouts that behave as expected on different screen sizes and orientations, ensuring a robust and flexible UI. ## Expansion & Flex (for Row & Column)[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#expansion--flex-for-row--column "Direct link to Expansion & Flex (for Row & Column)") When widgets are placed inside a Row or Column in a layout, they gain access to an additional property called **Expansion** & **Flex**. This property controls how a widget behaves in terms of taking up available space within its parent Row or Column. #### Expanded[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#expanded "Direct link to Expanded") The Expansion properties are as follows: * **Default:** Make the widget NOT fill space along the main axis (horizontal for Row, vertical for Column), therefore taking the minimum space required by its contents. * **Flexible:** Allow the widget to take up to the available space along the main axis (horizontal for Row, vertical for Column). You can think of this as giving it a "Max Width" equal to the amount of available space. The widget can take up less space if it is smaller, but otherwise will be constrained to the available width. Understanding Layouts Flexible will be **disabled** if the child widget is in a Row with unbounded width or Column with unbounded height. * **Expanded:** Make the widget fill the space along the main axis (horizontal for Row, vertical for Column). Using Expanded & Flexible in an Example ![expanded.png](/assets/images/expanded-eda87a69753adc7cf7fb3649c71c4105.png) 1. **Default Behavior:** Here, you see two child widgets displayed next to each other, each occupying only the necessary space to show its content without any expansion. 2. **Expanded Widget Usage:** The first child widget (highlighted in red) is wrapped with an **Expanded** widget. This causes it to take up all the remaining space in the container after accounting for the space required by the other widgets. Here, the first child stretches to fill all the extra space, pushing the other widgets to the side or shrinking them to their minimum size. 3. **All Expanded Widgets:** In this example, all child widgets are set to **Expanded**. This configuration divides the container's space equally among all child widgets, regardless of their intrinsic size. Each widget stretches to fill an equal portion of the container. 4. **All Flexible Widgets:** In the last example, each child widget is wrapped with a **Flexible** widget. This allows the widgets to expand to fill the available space but unlike **Expanded**, they can also shrink below their allocated space if necessary, based on the flex factors and the minimum space required by each widget. If all have the same flex factor, they will divide the space equally but are able to shrink if the content size demands less space. Let's understand Flexible concept with another example: Flexible Concept ![flexible.png](/assets/images/flexible-5eeaab35dab266c5eb63c3af8b47162d.png) * In the left image, Child 2 (in purple) and Child 3 (in green) retain their intrinsic sizes due to **default settings**, causing their content to appear cut off when the container's width is limited. They cannot adapt to smaller spaces, leading to potential content clipping. This highlights the limitations of default settings in confined spaces where dynamic resizing would improve content visibility. * In contrast, the right image uses the **Flexible widget** for Child 2 and Child 3, allowing them to adjust dynamically to the container's width constraints. Instead of sticking to their original sizes, these widgets can shrink or expand, making the layout responsive and ensuring content remains visible and well-aligned, regardless of screen size changes. This adaptability is crucial for maintaining accessibility and visual coherence in diverse display environments. #### Flex[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#flex "Direct link to Flex") Additionally, you can utilize Flex factors to determine the flexibility of a widget within its parent container. A Flex factor is an integer assigned to a child widget, indicating its proportional size compared to other children in the same parent. The space a child occupies is determined by its Flex factor in relation to the total Flex factors of all siblings in the layout. Default Behavior If no flex factor is provided, the child will not expand to fill extra space in the parent container. It will occupy only the space required for its content unless styled otherwise. When you assign a flex factor, the widget can expand to fill any available space in the parent container. For instance, in a Row or Column, if one widget has a flex factor of 1 and another has a flex factor of 2, the second widget will take up twice as much space as the first. Flex Example ![flex.png](/assets/images/flex-686dc14f7db4be8620279b395f21bb97.png) * Child 2 (purple) with a higher Flex factor (8) consistently occupies a larger portion of space, showing how a higher number increases the space allocation relative to other widgets. * Child 3 (green) has varying Flex factors (1 and 4), illustrating how increasing the Flex factor allows the widget to occupy more space, albeit still less than Child 2 due to its lower Flex factor. Find a video tutorial about Expanded & Flexible: ## Scrollability[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#scrollability "Direct link to Scrollability") Scrollability for **Row or Column** widgets in FlutterFlow determines whether the content within these layouts can extend beyond the visible boundaries of the screen or container, enabling horizontal or vertical scrolling: * **Allow Scrolling:** When enabled, this allows the content to exceed the device or parent container’s screen limits, making the overflow content accessible through scrolling. * **Do Not Allow Scrolling:** If disabled, the content that exceeds the boundaries of the screen or its parent container will not be accessible through scrolling. This setting forces the content to fit within the available visible space, hiding overflow content or potentially causing layout issues. Generated Code In the generated Flutter code, enabling scrollability simply involves wrapping the Row or Column in a `SingleChildScrollView()`. This widget adjusts its child's size and position based on the incoming constraints and the scrolling movement, effectively managing overflow by introducing scrollable behavior. ## Spacing[​](/resources/ui/widgets/composing-widgets/rows-column-stack.md#spacing "Direct link to Spacing") * **Items Spacing:** This field sets the space between each child widget within the Row or Column. You can specify a static numerical value that determines the pixel spacing between adjacent children or set it from a variable. Items Spacing vs Padding Prefer “Items Spacing” set on the parent row or column instead of padding on individual elements. This ensures consistency, especially on non-dynamically generated lists. * **Apply to Start & End:** When toggled on, this applies the specified item spacing to the beginning and the end of the Row or Column. This effectively adds padding at the start and end of the layout in addition to between the items. * **Start Spacing and End Spacing:** These properties allow for additional specific spacing at the start and end of the Row or Column, respectively. This is useful for fine-tuning the layout to ensure content is visually balanced within the container or to provide clear margins. --- # Container A Container is a highly versatile widget that functions much like a multi-purpose box in your app's interface. It is primarily used to decorate, position, and arrange child widgets—smaller components within your app. Containers are useful for dividing the screen into smaller, logical parts, and styling or positioning these parts effectively. For instance, you can use a Container to assign a background color, shape, or specific size to elements like text or buttons. Think of it as placing an item inside a box and then customizing the appearance and placement of that box within the screen layout. ## Container Properties[​](/resources/ui/widgets/container.md#container-properties "Direct link to Container Properties") The Container properties can be adjusted to customize the appearance and layout of a Container widget. Here's a brief explanation of each: ![container-props.png](/assets/images/container-props-b14a355ea5884fb8ea6cccce91b24908.png) ### Limiting Size[​](/resources/ui/widgets/container.md#limiting-size "Direct link to Limiting Size") Sometimes, you don't set the height and width of the container explicitly and allow it to be the size of its child widget. If you do so, you may find layout issues where widgets may become too large or too small on different devices, leading to a poor user experience. To overcome this, you can limit the size of the container by specifying the Min W, Min H, Max W, and Max H. For example, in a responsive design, you might want a button to grow with the screen size but not exceed a certain width. By setting these properties, you can ensure the button is at least a certain size for usability but doesn't become too large on bigger screens. * **Min W (Minimum Width) & Min H (Minimum Height):** These set the minimum dimensions the Container can shrink to, in pixels or percentage. * **Max W (Maximum Width) & Max H (Maximum Height):** These set the maximum dimensions the Container can expand to, in pixels or percentage. ### Clip Content[​](/resources/ui/widgets/container.md#clip-content "Direct link to Clip Content") Determines whether the content inside the Container should be clipped if it exceeds the boundaries of the Container. When enabled, anything outside the Container's bounds will not be visible. ## Box Shadow Properties[​](/resources/ui/widgets/container.md#box-shadow-properties "Direct link to Box Shadow Properties") The Box Shadow properties allow you to add and customize a shadow effect for your Container widget. Here's a brief explanation of each property: * **Shadow Color:** The color of the shadow, typically specified in a hex format including an alpha value for transparency, like `#33000000.` You can select from Theme Colors, use a color picker, or input a hex code. * **Blur:** Determines the blur radius of the shadow. A higher value produces a more diffused shadow, while a lower value makes the shadow sharper and more defined. * **Spread:** Controls the **spread radius of the shadow**. **Increasing** this value will **expand** the area that the shadow covers, making it appear larger. * **Offset X & Offset Y:** These properties set the horizontal (X) and vertical (Y) displacement of the shadow relative to the widget. **Offset X** shifts the shadow horizontally, and **Offset Y** moves it vertically. Positive values move the shadow right and down, respectively, while negative values move it left and up. Here's a quick demo to show the box shadow property in Container: ## Gradient Properties[​](/resources/ui/widgets/container.md#gradient-properties "Direct link to Gradient Properties") The Gradient properties allow you to create and customize a gradient effect for a Container widget. Here's an overview of each property: * **Angle (Degrees):** Sets the orientation of the gradient by specifying the angle in degrees. An angle of **0 degrees** creates a **horizontal** gradient, and **90 degrees** would make it **vertical**. * **Colors**: These are the colors used in the gradient. You can set these colors using Theme Colors, a color picker, or hex codes. Two color values are added by default. * **Add Color:** This option allows you to add additional colors to the gradient, further customizing the effect by adjusting their transition points and choosing from Theme Colors, a color picker, or hex codes. * **Transition Point:** These values determine where each color starts transitioning within the gradient. Transition points are set as a fraction of the total gradient distance: ![gradient-cont.png](/assets/images/gradient-cont-0e1fe8041e4c52c057d37f52b40b072d.png) In the above example, * The Transition Point for Color 1 is set at 0, meaning it starts at the very beginning of the gradient. * The Transition Point for Color 2 is 0.5, indicating that this color starts transitioning at the halfway point. * The Transition Point for Color 3 is 1, which places the start of this color's transition at the end of the gradient. ## Background Image Properties[​](/resources/ui/widgets/container.md#background-image-properties "Direct link to Background Image Properties") The Background Image properties provide options for setting up an image as the background of a Container widget. info For a detailed guide on configuring **common Image properties**, please refer to the relevant section [**here**](/resources/ui/widgets/image.md#common-image-properties). ## Child Properties[​](/resources/ui/widgets/container.md#child-properties "Direct link to Child Properties") * **Child Alignment:** This allows you to specify the alignment of child widgets within the Container. The grid indicates possible positions (center, top, bottom, left, right, and etc), and you can adjust the alignment precisely using the X and Y values, which shift the child widget horizontally and vertically within the Container. ## Implicit Animated[​](/resources/ui/widgets/container.md#implicit-animated "Direct link to Implicit Animated") This property enables the use of implicit animations for changes in the Container’s properties (like size or color). This makes transitions between property changes smoother and visually appealing. Here's an example of Container's width and color changing without the use of Implicit Animation. Now we enable **Implicit Animation** for this Container and see the difference: The properties of Implicit Animation are as follows: * **Animation Curve:** Specifies how the animation progresses over time. The options are Ease In, Ease in Out, Ease Out, Bounce, Linear, Elastic. * **Duration (ms):** Sets the duration of the animation in milliseconds. A shorter duration makes the animation faster, while a longer duration slows it down. ## Safe Area[​](/resources/ui/widgets/container.md#safe-area "Direct link to Safe Area") This toggle ensures that the Container and its contents are positioned within the safe area of the device’s screen, avoiding obscured areas like notches or rounded corners. This is particularly useful for ensuring good visibility and interactivity across different devices. To enable the safe area, navigate to the properties panel and turn on the Safe Area toggle. ![safe-area.png](/assets/images/safe-area-f1ada35c9f8ace795a889f0e27999a84.png) Watch the video tutorial If you prefer watching a video tutorial, here is the guide for you: [Containers](https://www.youtube.com/embed/EQgUvPEMd2E) --- # Icons Icons are integral elements in user interfaces, providing visual cues that enhance user interaction and aesthetic appeal. They communicate action, represent functionality, and improve navigation efficiency within applications. ![icon.png](/assets/images/icon-7d7bad4d005740306b979eabaab82ac2.png) ## Types of Icon widgets[​](/resources/ui/widgets/icons.md#types-of-icon-widgets "Direct link to Types of Icon widgets") FlutterFlow allows a bunch of widgets and components: * **Icon Widget**: The **Icon** widget in FlutterFlow is used for displaying symbols from a variety of available icon packs like Material Icons. It's straightforward to use, allowing for quick integration of visual symbols into your app. * **Icon Button Widget**: The **IconButton** widget combines the functionality of an icon with the capabilities of a button, making it a clickable icon. It's commonly used for actions like opening a menu, submitting a form, or any other interactive task. * **Toggle Icon Widget**: The **ToggleIcon** widget offers a specific functionality where the icon toggles between two states based on a boolean condition. This widget is ideal for "favorite" or "like" buttons, where the icon state changes to represent an active or inactive state. The ToggleIcon reacts to user taps, changing its appearance and also allowing for callback functionality to handle the state change. ## Common Icon Properties[​](/resources/ui/widgets/icons.md#common-icon-properties "Direct link to Common Icon Properties") Upon selecting the Icon, you can modify properties such as **Icon color** and **Icon size** from the Properties Panel on the right. Additionally, you can set the Icon value by selecting from a vast catalog of **Material Icons** and **FontAwesome** Icons provided by FlutterFlow. Custom Icons You can also upload your own licensed Custom Icons. Check out [**this video**](https://youtu.be/rlGkbnhP75g) to learn more. ## Icon Button Properties[​](/resources/ui/widgets/icons.md#icon-button-properties "Direct link to Icon Button Properties") The Properties Panel for your IconButton allows you to modify the Icon Properties, Button Styling, Disabled state, and Hovered state properties. It also lets you determine if you want a loading indicator when the icon button is clicked. To get a quick demo of the styling changes, check this out: ## Toggle Icon Properties[​](/resources/ui/widgets/icons.md#toggle-icon-properties "Direct link to Toggle Icon Properties") ToggleIcon is a special component created for you that lets you add a toggle on and toggle off icon, and define a State variable that determines the state of the Toggle icon. The properties are straightforward and include the following: ![toggle.png](/assets/images/toggle-68199f1a71fa7398fa0dbf1febb71a11.png) ### On Toggle \[Action][​](/resources/ui/widgets/icons.md#on-toggle-action "Direct link to On Toggle \[Action]") By default, FlutterFlow handles the toggling of the State variable from true to false and vice versa when the button is clicked. However, you can also add another action under the On Toggle action trigger to perform extra tasks. --- # Image Images are a fundamental part of modern user interfaces, enhancing visual appeal and user engagement. In app design, images can provide context, support content, and guide user interactions. Different types of image widgets cater to various design requirements, ensuring flexibility and aesthetic integration across platforms. * **Image Widget**: The Image Widget is a versatile component used to display images from a variety of sources, including local assets and the internet. It's essential for adding visual elements to your applications, such as logos, icons, and photographs. * **CircleImage Widget**: The CircleImage Widget specifically caters to scenarios where you need to display images in a circular format, commonly used for profile pictures or branding elements. The properties for the Image widget provide various customization options, from sizing and fitting to advanced animations. ## Common Image Properties[​](/resources/ui/widgets/image.md#common-image-properties "Direct link to Common Image Properties") * **Width & Height:** Specify the dimensions of the image. Values can be in pixels (px) or as a percentage (%) of the parent container's size, allowing for responsive design. * In case of `CircleImage` widget, you can define the **diameter** of the widget instead. * **Border Radius:** Adjusts how rounded the corners of the image are. You can define border radius for TL (Top left), TR (top right), BL (bottom left), and BR (bottom right) separately or for all corners together. A higher value results in more rounded corners. CIRCLEIMAGE This option is not available for `CircleImage` widget since it is circular in shape. ### Image Type[​](/resources/ui/widgets/image.md#image-type "Direct link to Image Type") Specifies the source of the image. Options include: * **Network:** Enter the URL of the image in the Path input field. This is used for images hosted online. * **Cached:** Determines whether the image should be cached for performance optimization. When toggled on, it stores the image locally to speed up load times on subsequent views. * When cached is enabled for `Image` widget & `CircleImage` widget, you can also define the **Fade in/out duration** (when blur hash is not enabled). This setting is not available for Background Image of Container. * **Asset:** Click the Upload Image + button to upload an image from your computer or select from previously uploaded assets. When this option is selected, you can enable the **Set Dark Mode** toggle to specify a separate background image for dark mode environments, enhancing the visual experience under different lighting conditions. * **Uploaded File:** Selecting this option allows for dynamic handling of image data within your app, accommodating images that users upload during app usage. This makes it suitable for applications requiring user-specific or user-generated content. Set this to use **Widget State > Uploaded File** to manage the image as part of the app's state. ### Box Fit[​](/resources/ui/widgets/image.md#box-fit "Direct link to Box Fit") Determines how this widget should take up the available space. The options are: ![image-boxfit.png](/assets/images/image-boxfit-74c8424b34088f4d083e1b7bc4483797.png) Example of a horizontal & vertical image in different BoxFit options * **Fill:** Scale the image to completely fill the container, which might distort the image. * **Contain:** Scale the image to fit within the container without distorting it, which might leave some empty space. * **Cover:** Scale the image to completely cover the container without distorting it, potentially cropping some parts of the image. * **Fit Width:** Scale the image to fit the width of the container, possibly leaving empty space vertically. * **Fit Height:** Scale the image to fit the height of the container, possibly leaving empty space horizontally. * **None:** No scaling or adjustment, showing the image in its original size. * **Scale Down:** Center the widget and scale it down until it fits within the available space. ### Image Alignment[​](/resources/ui/widgets/image.md#image-alignment "Direct link to Image Alignment") Controls the alignment of the image within the container. This grid allows you to position the image precisely within the container, with options to align it to the center, top, bottom, left, right, and combinations of these. * **X & Y:** Adjusts the fine positioning of the background image along the X (horizontal) and Y (vertical) axes. This is useful for making precise adjustments to the image placement. ## Advanced Image Functionalities[​](/resources/ui/widgets/image.md#advanced-image-functionalities "Direct link to Advanced Image Functionalities") * **Show Error Image on Failure:** When enabled, displays an error image if the main image fails to load. This helps maintain a good user experience even when image retrieval issues occur. * **Use Blur Hash:** When enabled, displays a blurred placeholder image while the main image is loading, based on a hash value representing the original image. This can enhance the perceived performance of image loading. * **Make Expandable:** When enabled, the image can be expanded, usually to a larger view or a full-screen mode, upon user interaction. * **Use Hero Animation:** Enables a hero animation effect when transitioning between screens. This can make the image appear to "fly" between screens for a smoother visual transition. --- # Properties Panel In FlutterFlow, the Properties Panel on the right helps you configure and manage your widgets. It opens when you click on a widget or [component](/resources/ui/components.md) in the [**Widget Tree**](/resources/ui/widgets.md#widget-tree). Here's a quick demo showing how to add a widget to the canvas, which opens the widget properties panel on the right, allowing you to update the widget's properties: The panel is divided into sections, each focusing on settings specific to the selected widget. The available options may vary depending on the widget type, with additional advanced configurations available for further customization. ![advanced-configs-widgets.png](/assets/images/advanced-configs-widgets-4fcf0fbd6b6c7ed7551a6df8262671bb.png) ### Widget name[​](/resources/ui/widgets/properties.md#widget-name "Direct link to Widget name") When you select any widget, its name appears on the properties panel. The default name for any widget is its type. For example, if you select the Container widget, the name appears as '**Container**'. However, you can use the edit icon on the right to change its name. ![widget-properties.png](/assets/images/widget-properties-5052050595add7f5def91601388644b3.png) ## Actions[​](/resources/ui/widgets/properties.md#actions "Direct link to Actions") This section allows you to define and manage interactions or events triggered by user actions. For example, you can configure a button to navigate to another page, submit form data, or call an API. Actions are crucial for creating interactive and functional apps. In the case of widgets, you can add user interactions on action triggers such as **On Tap** or **On Long Press**. The availability of these actions may vary depending on the widget. Actions differ according to the widget selected; on some widgets, you can't apply any actions. ## Backend Query[​](/resources/ui/widgets/properties.md#backend-query "Direct link to Backend Query") Here, you can configure the page to fetch data from a backend source or database. This is typically done through API calls or direct database queries. Setting up a backend query allows the widget to display dynamic content, such as user profiles, product lists, or any other data your app needs to retrieve from a server. ## Generate Dynamic Children[​](/resources/ui/widgets/properties.md#generate-dynamic-children "Direct link to Generate Dynamic Children") Widgets capable of handling multiple child widgets have an additional tab called **Generate Dynamic Children**. This feature helps you generate multiple child widgets from a list variable. This is particularly useful when you are retrieving data from an API call. Some of the widgets that can handle multiple children include **Column, Row, Stack, ListView, GridView, and PageView**. info To learn more about [**Generating Dynamic Children**](/resources/ui/widgets/composing-widgets/generate-dynamic-children.md), refer here. ## Animations[​](/resources/ui/widgets/properties.md#animations "Direct link to Animations") You can apply animations to a widget to enhance the visual appeal and user experience. Animations can be used to draw attention to important elements, provide feedback on user interactions, or create visually engaging transitions between states. info Learn more about adding **[animations](/concepts/animations.md)** here ## Documentation and Semantic Labels[​](/resources/ui/widgets/properties.md#documentation-and-semantic-labels "Direct link to Documentation and Semantic Labels") **Documentation** helps developers understand the purpose and function of a widget within the app, making maintenance and future updates easier. **Semantic labels** are crucial for accessibility, allowing screen readers to accurately describe the widget's function to users with visual impairments. --- # Text Text is a fundamental element in any user interface, used to convey information and interact with users. In app development, effectively presenting text can significantly enhance the user experience, making information accessible and interactions intuitive. Two common widgets used for displaying text in FlutterFlow are the Text widget and the RichText widget. Each serves a distinct purpose and offers different capabilities for integrating text into an application. ## Text Widget[​](/resources/ui/widgets/text.md#text-widget "Direct link to Text Widget") The Text widget is used to display a piece of text on the screen. It's one of the most commonly used widgets in app development. ![text-example.png](/assets/images/text-example-5dbb41a06e4e1625e516f25bc8b51682.png) For example, in this screen, the Text widget is used to present different pieces of information clearly and effectively. The Text widgets display the product name, "Men's Harrington Jacket," its price, "$148," and a detailed description of the product. These Text widgets are styled differently to emphasize specific pieces of information. The Text widget can be found under the **Base Elements** tab in the **Widget Palette**. You can either drag it to your desired location on the screen or insert it directly via the widget tree. Once the Text widget is selected, the Properties Panel on the right side becomes active, allowing you to customize the styling of your Text widget. Here, you can adjust various attributes such as font size, color, alignment, and more to tailor the appearance to fit your design needs. ## RichText Widget[​](/resources/ui/widgets/text.md#richtext-widget "Direct link to RichText Widget") The **RichText** widget offers more elaborate formatting capabilities compared to the basic Text widget. It allows for the mixing of multiple styles within a single text sequence, enabling the creation of stylized textual content. This widget uses a tree of **TextSpan** objects to define the rich formatting options, including different fonts, sizes, and colors for various parts of the text. RichText is particularly useful for text-heavy applications that need inline styling and linking, like in a formatted article or a document viewer. The RichText widget can be found under the **Base Elements** tab in the **Widget Palette**. You can either drag it to your desired location on the screen or insert it directly via the widget tree. ![richtext-eg.png](/assets/images/richtext-eg-fb12340204efef65ec066cab09b82894.png) When the RichText widget is added to your widget tree, FlutterFlow automatically creates two RichTextSpan child objects. You can modify the text value and styling of each object to create multiple styles within your paragraph. To modify the RichTextSpan objects, see the quick demo below: ## Common Text Styling Properties[​](/resources/ui/widgets/text.md#common-text-styling-properties "Direct link to Common Text Styling Properties") ![text-props.png](/assets/images/text-props-6a4106562a78bb5e4db8d8c6fef14e1b.png) tip For consistency, we recommend defining your Typography and custom text styles from **Theme Settings > Typography & Icons** before creating any screens. Few things to note: * **Line Height:** Sets the height of the text (e.g. a value of 1.5 would make the line height 50% larger than the font size. * **Text Align:** Define how text is positioned within a container, typically as left-aligned, right-aligned, centered, or justified ## Advanced Properties for Text Widget[​](/resources/ui/widgets/text.md#advanced-properties-for-text-widget "Direct link to Advanced Properties for Text Widget") * **Max Lines:** This property specifies the maximum number of lines that the text can occupy. If the content exceeds the set number of lines, it will be truncated or end with an ellipsis, depending on the configuration. This is useful for maintaining a clean and consistent layout where text space is limited. - **Auto Size** The `Auto Size` option allows the `Text` widget to automatically reduce its font size to fit within its parent widget. This ensures that the text remains legible without overflowing its container, making it especially handy for responsive designs where the display may vary across different devices. * **Configure Parent Widget Dimensions** To enable `Auto Size`, the `Text` widget must be inside a widget that has both defined width and height. Without these constraints, the font size cannot be adjusted automatically. 1. Select the `Text` widget. 2. Check its parent widget. 3. Ensure both width and height are explicitly defined. warning Without defined dimensions, the `Auto Size` feature may not behave as expected. * **Behavior Scenarios** The following examples illustrate how `Auto Size` behaves under different container configurations: * Container with width set to `infinity` and height set to `100px`, `Auto Size` disabled. The text may overflow beyond the container. * Container with width set to `infinity` and height set to `100px`, `Auto Size` enabled. The font size adjusts to fit the defined height. * Container with width set to `30%` and no height defined, `Auto Size` enabled. The feature has no visible effect due to missing height constraint. * Container with width set to `70%` and height set to `50px`, `Auto Size` enabled. The text is resized to the minimum allowed font size to remain within the container. ![](/assets/images/20250430121459696014-760e4e8b93b65d720b5f8c3af1d34a4d.png) tip Use `Auto Size` with percentage-based dimensions for better responsiveness. For example, set the container width to `30%` and enable `Auto Size` to allow the text size to adjust as the screen size changes. note The `Auto Size` feature has a minimum font size threshold. If the container becomes too small, text may clip or overflow when resizing is no longer possible. ### Setting Text Overflow replacement[​](/resources/ui/widgets/text.md#setting-text-overflow-replacement "Direct link to Setting Text Overflow replacement") You may want to limit the number of characters shown inside the Text widget and replace the extra characters with the ellipsis or completely hide them. Important This option is only available if the value is set from the variable. To set the text overflow replacement: 1. Select the **Text** widget, navigate to the **Properties Panel > Text Properties >** enter the value for **Max character** to limit the number of characters. 2. Set the **Text Overflow Replacement** to either **Clip/Cutoff** or **Ellipsis (...)** ![text-overflow.png](/assets/images/text-overflow-acb011560d030927aadf17f5993feb5f.png) ### Adding Gradient color[​](/resources/ui/widgets/text.md#adding-gradient-color "Direct link to Adding Gradient color") Conditional Properties Note that enabling the Gradient option disables AutoSize and setting Max Lines for your Text. Adding a gradient color to the text gives it a modern look and feel. You can either use our ready-made templates or create it from scratch. Here's how you do it: 1. Select the **Text** widget, navigate to the **Properties Panel > Text Properties >** enable the **Gradient** toggle. 2. To add your own colors: 1. Select the **Type** among the **Linear** and **Radial**. The *Linear* distributes the colors horizontally, whereas the *Radial* circularly spreads the color. 2. If you choose *Linear*, specify the **Direction,** and for *Radial*, specify the **Radius**. 3. Add/Remove or customize the existing colors. info You can also add gradient colors from a preset template as shown in the video demo. ## Formatting numbers[​](/resources/ui/widgets/text.md#formatting-numbers "Direct link to Formatting numbers") You may want to format large numbers for better readability. Displaying a number like 2,354,000 or 4,356,634,444 instead of 2354000 or 4356634444 enhances the user experience. For instance, it's clearer to show the population as 1,200,000 rather than 1200000 and currency values like $2K or $5M instead of $2000 or $5000000. ### Types of formatting[​](/resources/ui/widgets/text.md#types-of-formatting "Direct link to Types of formatting") Below are the types of formatting that we support: * **Decimal**: Shows numbers in decimal format (e.g., 1,200,000 and 1.200.000). * **Percent**: Shows numbers in percentage format (e.g., 28%, 99.99%). * **Scientific**: Shows numbers in scientific format (e.g., 1e3, 1E6). * **Compact**: Shows numbers in compact format (e.g., 2.1K, 2.3M, 5B). * **Compact Long**: Shows numbers in compact long format (e.g., 2.1 thousand, 2.3 million, 5 billion). * **Custom**: If the given formatting options do not fit your requirement, you can use specify a custom format. ### Format a number[​](/resources/ui/widgets/text.md#format-a-number "Direct link to Format a number") Use the instructions below to format a number: 1. Select the **Text** widget, move to the [Properties Panel](/flutterflow-ui/builder.md#properties-panel) > **Set from Variable >** display the value from a variable of type **Integer** or **Double**. (e.g., **App State > App State Variable Name**). 2. After selecting a variable, set the **Available Options** to **Number Format** and **Number Format Options** to the required [type](/resources/ui/widgets/text.md#types-of-formatting). 1. If you choose **Decimal**, you must set the **Decimal Type** as well. The decimal values can be shown in two ways, i.e., 1,200 (with a comma) and 1.200 (with a period). 1. Select **Automatic** to show decimal value based on the user's country. 2. Select **Period for Decimal** to show decimal value with a period (e.g., 1.200). 3. Select **Comma for Decimal** to show decimal value with a comma (e.g., 1,200). 2. If you choose **Custom**: 1. Find the **Custom Format** box, and enter your format. For example, entering `###,###.###` will convert the number 123456.789 into 123,456.789, and 000.00 will convert the number 12.786 into 012.79. 2. In the **Locale** input box, enter the locale in which you want to display the number. (If you leave this property empty, the locale is automatically set as per the user's location). Learn more about how to format a number [here](https://pub.dev/documentation/intl/latest/intl/NumberFormat-class.html). 3. To display this number as currency, enable the **Display as Currency** toggle and specify the **Currency Symbol**. 4. Click **Confirm**. --- # Common Widget Properties When working with widgets in FlutterFlow, you'll encounter properties and features that are common across multiple widget types. Below is a detailed overview of such properties. ## Visibility[​](/resources/ui/widgets/widget-commonalities.md#visibility "Direct link to Visibility") Visibility settings in FlutterFlow allow you to dynamically control when and how widgets appear in your app. ### Conditional[​](/resources/ui/widgets/widget-commonalities.md#conditional "Direct link to Conditional") **Conditional** visibility allows you to control the display of UI elements (widgets) based on specific conditions or criteria. It helps you create dynamic, personalized experiences by showing or hiding certain content. For example, you could display specific features or actions only to users with particular roles, such as showing admin controls exclusively to administrators. info The **Show in UI Builder** toggle only affects visibility within the design canvas, giving you a quick preview of how the layout will adjust when this widget is shown or hidden. ![conditional-visibility.avif](/assets/images/conditional-visibility-23ea7b86289e9551c4468ed2b5a872d0.avif) ### Responsive[​](/resources/ui/widgets/widget-commonalities.md#responsive "Direct link to Responsive") The **Responsive visibility** property allows you to show or hide widgets based on device screen size, such as mobile, tablet, or desktop. By toggling each icon, you can show or hide the widget according to your design needs. For example, you might create two separate navigation menus: * **Desktop Menu**: A wider, left-aligned menu only visible on large screens by enabling the desktop icon and disabling all other screen size icons. * **Mobile Menu**: A compact drawer menu only visible on smaller screens by enabling the phone icon and disabling all other screen size icons. ![responsive-visibility.avif](/assets/images/responsive-visibility-af9fb868f82411dafd2dcf3cf3485c90.avif) ### Opacity[​](/resources/ui/widgets/widget-commonalities.md#opacity "Direct link to Opacity") The **Opacity** property controls how transparent or visible a widget appears. It accepts a value between 0 and 1, where 0 means fully transparent, 1 is fully opaque, and 0.5 results in semi-transparency. This property enables a wide range of creative UI effects, such as translucent buttons, overlay highlights, or smooth theme transitions. When **Animated Opacity** is enabled, any changes to the opacity value are smoothly animated based on the specified duration and curve, enhancing visual appeal and user experience. ![Opacity.avif](/assets/images/Opacity-ef06ded87e55f159dad15135fdb2aa96.avif) ## Padding[​](/resources/ui/widgets/widget-commonalities.md#padding "Direct link to Padding") **Padding** is the space added inside a widget, between its content and its border (or edge). It ensures the content doesn't touch the borders, creating visual breathing room and contributing to a cleaner, more responsive layout across different screen sizes. To set padding, select the widget, go to the **Padding & Alignment** > **Padding** section in the **Properties Panel**, and enter the values in **pixels (px)**, which represent logical pixels. You can choose from two options: * **Uniform Padding**: Apply the same value to all four sides. * **Independent Padding**: Set different padding values for top, bottom, left, and right. If you prefer watching a video tutorial, here is the guide for you: ## Alignment[​](/resources/ui/widgets/widget-commonalities.md#alignment "Direct link to Alignment") **Alignment** determines how a widget is positioned within its parent container. It helps you control where your widget appears—left, right, center, top, bottom, or any point in between. To set alignment, select the widget and go to the **Padding & Alignment** > **Alignment** section in the **Properties Panel**. You'll see a 3×3 grid representing all nine positions: * Top Left * Top Center * Top Right * Center Left * Center (Default) * Center Right * Bottom Left * Bottom Center * Bottom Right Simply click the dot representing where you'd like the widget to be positioned. Alternatively, you can input a specific value (between -1 to 1) for the precise horizontal and vertical alignment. * **X (Horizontal Alignment)** controls the widget’s position along the horizontal axis within its parent. A value of `-1` aligns it to the left, `0` centers it, and `1` aligns it to the right. * **Y (Vertical Alignment)** controls the widget’s position along the vertical axis. A value of `-1` places it at the top, `0` centers it vertically, and `1` places it at the bottom. info Values beyond this range will push the widget outside the visible screen area. ## Add Testing Value Key[​](/resources/ui/widgets/widget-commonalities.md#add-testing-value-key "Direct link to Add Testing Value Key") A **Value Key** is used to uniquely identify widgets during [**Automated Testing**](/testing/automated-tests.md) in FlutterFlow. For example, on a Create Account page, you might use descriptive keys like `signupFirstNameField`, `signupEmailField`, `signupPasswordField`, and `signupSubmitButton`. This helps testing tools reliably locate and interact with the correct widgets. For more details, refer to the [complete guide here](/testing/automated-tests.md). ![test-value-keys.avif](/assets/images/test-value-keys-e38ee305c4a9fb82b6145cb18cd7697c.avif) ## Set Width & Height[​](/resources/ui/widgets/widget-commonalities.md#set-width--height "Direct link to Set Width & Height") To adjust a widget's size, click on the widget you wish to resize and navigate to the right-side Properties Panel. There, you can set the size in the following ways: * **PX (Pixels):** Enter a fixed size in pixels for a consistent dimension. * **% (Percentage):** Set the size relative to the screen or parent container. * **∞ (Infinity):** Make the widget expand to fill the available width or height. You can also drag the handle bars on the right and bottom sides of a selected widget to resize. The measurements appear while resizing to show the current pixel values. ![use-handle-bars-to-resize.avif](/assets/images/use-handle-bars-to-resize-eb13939a7ac53272a5d6fa13aec92187.avif) Responsive Width & Height You can also use a **Responsive Value** to apply different width or height values based on screen size. To set it up, open the **Set from Variable** menu and select **Responsive Value**. Then, assign specific size values for each screen size category, such as mobile (Screen Width < Breakpoint Small), tablet (Screen Width < Breakpoint Medium), and desktop (Screen Width < Breakpoint Large). ## Use Keyboard to Adjust Property Values[​](/resources/ui/widgets/widget-commonalities.md#use-keyboard-to-adjust-property-values "Direct link to Use Keyboard to Adjust Property Values") You can quickly increase or decrease the property value using your keyboard's up and down arrow keys. This allows for precise control without needing to type in new values each time. tip Hold down the **Shift** key while pressing the arrow keys to change the value by 10 units at a time. ## Change Color[​](/resources/ui/widgets/widget-commonalities.md#change-color "Direct link to Change Color") To change the color, navigate to a widget property that allows you to set a color, and then click on the currently selected color. This opens the **Color Picker**, where you have multiple ways to set the desired color: * **Custom Color**: Use the gradient area to select any shade and fine-tune it using: * The **hue slider** (rainbow bar) to adjust the base color. * The **transparency slider** (checkered bar) to control opacity (alpha value). * **Use RGB or HEX**: Manually input a **HEX code** (e.g., `#A489F5`) or set the **RGB values** directly for precise color control. The **Alpha (A)** value defines transparency (e.g., 100% = fully opaque). * **Theme Colors**: Below the picker, you’ll find a list of your app’s predefined **Theme Colors** like Primary, Secondary, and Background. Using theme colors ensures design consistency across your app and makes global updates easier. * **Set from Variable**: You can also dynamically assign a color based on your app logic. For example, changing the background color based on the selected item or theme. tip You can also assign a color using a **String variable** that contains a **CSS-style color value** (e.g., `"#FF5733"`, `"rgba(255, 87, 51, 1)"`, or `"red"`). This is especially useful when colors are stored in a database or returned from an API. Make sure the string format follows valid CSS color syntax, as FlutterFlow uses the [**`from_css_color`**](https://pub.dev/packages/from_css_color) package under the hood to parse these values. This allows you to dynamically theme parts of your app based on user preferences or remote configurations. ![color-from-string.avif](/assets/images/color-from-string-d25dcbcd05a58f64d8dd0bbf5b5add9f.avif) ## Copy Variable[​](/resources/ui/widgets/widget-commonalities.md#copy-variable "Direct link to Copy Variable") If you’ve created a complex variable value (e.g., using Conditional Logic) and want to reuse the same logic elsewhere, you can easily do so by copying the variable. To copy and paste a variable, open the **Set from Variable** menu, click the **three dots**, and select **Copy Variable**. Then go to the target location, open the same menu, click **Paste Variable**, and confirm. ## Bulk Edits Properties[​](/resources/ui/widgets/widget-commonalities.md#bulk-edits-properties "Direct link to Bulk Edits Properties") You can easily modify the properties of multiple widgets at once. For example, if you want to change the background color of several buttons from blue to green, there's no need to edit each one individually. Simply select all the buttons and update their fill color in one go. To do this, hold down the **Shift** key and click on each widget you want to edit. Once selected, their shared properties will appear in the **Properties Panel**, where you can apply changes. ## Use Images from Unsplash[​](/resources/ui/widgets/widget-commonalities.md#use-images-from-unsplash "Direct link to Use Images from Unsplash") You can easily display high-quality images directly from [Unsplash](https://unsplash.com/) using the Properties Panel. Just click the **search icon**, type in your desired keyword, and select an image from the results. tip You can also choose the image size (i.e., Small, Regular, or Full) before adding it, depending on your layout. ## UI Builder Display Value[​](/resources/ui/widgets/widget-commonalities.md#ui-builder-display-value "Direct link to UI Builder Display Value") For widgets like `Text` and `RichText`, if the content is set from a variable, you can add a placeholder value that appears only in the FlutterFlow builder. This placeholder helps you visualize how the text will look on the canvas, but it won’t appear in the live app, it's replaced by the actual variable at runtime. This is especially helpful for previewing layout, spacing, and alignment without removing or disrupting your variable bindings. ![ui-builder-display-value.avif](/assets/images/ui-builder-display-value-1efc84d1dd78a1725f8ff7e132a509be.avif) ## Adding Border[​](/resources/ui/widgets/widget-commonalities.md#adding-border "Direct link to Adding Border") You can add a border to any widget using the following properties: * **Border Color**: Choose a color manually or bind it to a variable. You can select from your theme colors (like `Primary`) or use the color picker. * **Border Width**: Set the thickness of the border in pixels. * **Border Radius**: Adjust how rounded the corners should be using the options below: * **Independent Radius**: Set different radius values for top, bottom, left, and right. * **Uniform Radius**: Apply the same value to all four sides. The slider and numeric input allow you to have precise control. * **Button Padding**: Controls the space inside the widget (between the content and the border). tip Use consistent border and padding styles for buttons, cards, and containers to maintain a clean and cohesive UI. --- # Roadmap This roadmap guides you through the key layers of app development: the **UI Layer, Logic Layer,** and **Data Layer**. Understanding these layers is essential for creating apps that are visually appealing, functionally robust, and secure. ![layers.avif](/assets/images/layers-aea5e7fd1325b59a7152ec28570b56bc.avif) ## UI Layer[​](/roadmap.md#ui-layer "Direct link to UI Layer") The UI Layer is all about the visual elements and interactions in your app. It includes widgets for buttons, forms, navigation, and layouts. In FlutterFlow, this layer also covers customization options like themes and responsive design, ensuring your app looks great and is easy to use. * **FlutterFlow Widgets:** * [Atomic Design](/resources/ui/overview.md) * [Pages](/resources/ui/pages.md), [Widget](/resources/ui/widgets.md) & [Components](/resources/ui/components.md) * [TextFields](/resources/forms/textfield.md) & [Other Form Widgets](/resources/forms.md) * **Navigation Elements:** * [Page Transitions (Slide, Fade, etc.)](/concepts/animations/page-transition.md) * [AppBar and other Page Elements](/resources/ui/pages/scaffold.md) * [Bottom Sheets](/concepts/navigation/bottom-sheet.md) * [Webviews](/concepts/navigation/webview.md) * **User Experience (UX):** * [Design System](/concepts/design-system.md) * [Responsiveness](/concepts/layouts/responsive.md) * Interaction Feedback * [Animations](/concepts/animations.md) * [Haptic Feedback](/concepts/alerts/haptic-feedback.md) ## Logic Layer[​](/roadmap.md#logic-layer "Direct link to Logic Layer") The Logic Layer handles your app's business logic and decision-making. This includes state management, conditional actions, and navigation logic. * **State Management:** * Representing Data * [Variables](/resources/data-representation/variables.md) * [Datatypes](/resources/data-representation/data-types.md) & [Custom Data Types](/resources/data-representation/custom-data-types.md) * [Enums](/resources/data-representation/enums.md) * [Constants](/resources/data-representation/constants.md) * [State Variables](/concepts/state-management.md) * [Managing Widget States](/concepts/state-management/widget-state.md) * Dynamic Lists [(Generating Dynamic Children)](/resources/ui/widgets/composing-widgets/generate-dynamic-children.md) * **Actions & Business Logic:** * [Actions](/resources/functions/action-flow-editor.md) * [Conditional Actions](/resources/functions/conditional-logic.md) * [Custom Code](/concepts/custom-code.md) * [Form Validation Logic](/resources/forms/form-validation.md) * **Navigation Logic:** * [Navigation & Routing](/concepts/navigation/overview.md) * [Deep & Dynamic Linking](/concepts/navigation/deep-dynamic-linking.md) * **Notification Systems**: * [Triggering Push Notifications](/concepts/notifications/push-notifications.md) * [Alert Dialogs](/concepts/alerts/alert-dialog.md) ## Data Layer[​](/roadmap.md#data-layer "Direct link to Data Layer") The Data Layer manages data storage, retrieval, and integration with external sources like APIs and databases. * **Authentication:** * [Auth Methods Overview](/integrations/authentication-methods.md) * [Firebase or Supabase or Custom Authentication](/integrations/authentication-types.md) * **Database Integration:** * [Firebase](/integrations/database/cloud-firestore/getting-started.md) or [Supabase](/integrations/database/supabase/database-actions.md) integration. * Local Storage with [AppState](/resources/data-representation/app-state.md) or [SQLite DB](/integrations/database/sqlite.md) * **API Integration:** * Working with [REST APIs](/resources/backend-logic/create-test-api.md) * [Streaming APIs](/resources/backend-logic/streaming-api.md) --- # Automated Tests Automated Tests allow you to test the behavior and appearance of your app to ensure all features are working as expected. It’s essentially like testing a real application without human intervention. Internally, when you write tests, FlutterFlow generates code for the [Flutter integration testing framework](https://docs.flutter.dev/testing/integration-tests), which you can download and test locally or through services like [Firebase Test Lab](https://firebase.google.com/docs/test-lab). Legacy testing Automated Tests are now considered a legacy testing option in FlutterFlow. For new testing workflows, we recommend using [**Test Pilot**](/testing/test-pilot.md), which lets you create and run AI-powered QA tests using natural-language instructions. Pricing Details * **Free and Basic plans:** Automated testing is not available. * **Growth plan:** Includes **1 test per project**. * **Business plan:** Allows **up to 3 tests per project**. * **Enterprise plan:** Supports **unlimited automated tests**. ## Basics[​](/testing/automated-tests.md#basics "Direct link to Basics") Before you add and run any tests, it's crucial to understand the workflow. When creating a test, you essentially map out a series of steps that dictate how the test will engage with the app. Each step can serve a distinct purpose and can be categorized as: ### Step Type[​](/testing/automated-tests.md#step-type "Direct link to Step Type") 1. [Interact with Widget](/testing/automated-tests.md#1-interact-with-widget) 2. [Wait to Load (Pump & Settle)](/testing/automated-tests.md#2-wait-to-load-pump--settle) 3. [Expect Result](/testing/automated-tests.md#3-expect-result) #### 1. Interact with Widget[​](/testing/automated-tests.md#1-interact-with-widget "Direct link to 1. Interact with Widget") This step simulates user interactions with your app, such as tapping on a button or entering text into a field. When you add this step, you can specify what kind of [action type](/testing/automated-tests.md#action-type) you would like to simulate and on [which widget](/testing/automated-tests.md#selection-method). ##### Action Type[​](/testing/automated-tests.md#action-type "Direct link to Action Type") * **Tap**: Acts like a single tap or click. * **Double Tap**: Imitates tapping twice quickly. * **Long Press:** Imitates pressing and holding for a moment. * **Enter** **Text**: Input the exact text you want to simulate entering. * **Scroll Until Visible**: When this is selected, you can specify the **Delta** 'number of pixels' you want to repeatedly scroll until the widget is visible. If you have more than one scrollable widget, select which one you want to scroll using the **Scrollable** property. #### 2. Wait to Load (Pump & Settle):[​](/testing/automated-tests.md#2-wait-to-load-pump--settle "Direct link to 2. Wait to Load (Pump & Settle):") After an interaction, your test might need to pause momentarily, allowing the app to process the interaction, load something, or update its state. This is where the 'Wait to Load' mechanism comes into play, ensuring the app has had enough time to reflect any changes. When this is selected, you have options to adjust: * **Duration**: How long do you want to wait? The default value is 100ms. * **Timeouts**: Maximum amount of time to wait, after which the test will fail. Best practices * Start your test with this step for about 3 seconds (i.e., 3000ms). * After every "Interact with Widget" step, it's usually wise to add another "Wait to Load". #### 3. Expect Result[​](/testing/automated-tests.md#3-expect-result "Direct link to 3. Expect Result") After performing an action in your app, it's important to verify that the result matches your expectations. This is the verification step where you confirm that the app behaves as expected after the interaction. Here, you confirm whether a particular widget is present on the screen. When this is selected, you have to [locate a widget](/testing/automated-tests.md#selection-method) that you want to verify and set what you expect to find using any of the below options: * **Find Nothing:** Ensures that the specified widget is not present on the screen. * **Finds Num Widgets:** Expect a certain number of widgets to be present. * **Finds One Widget:** Confirms that exactly one widget is present. * **Finds Widgets:** Expect multiple widgets to be found. * **Is Enabled**: Verifies that the widget is not only visible but also functional. * **Is Disabled**: Verifies that the widget is in a disabled state, meaning it is inactive and will not respond to user interactions. * **Has State**: Confirms that a widget is in a specific state, such as *True* or *False*. For example, verify whether a checkbox is checked. ### Selection Method[​](/testing/automated-tests.md#selection-method "Direct link to Selection Method") This is the method by which you locate the widget you want to select or verify. FlutterFlow offers the following ways to identify widgets: * **Select from UI Builder:** Use the UI Builder's interface to visually select the widget you want to verify. * **Find By ValueKey:** Locates the widget by its unique ValueKey. **Tip**: To add a ValueKey to a widget, use the 'Value Key' property located under the 'Testing' section on the widget properties panel. * **Find By Type:** Search for a widget based on its type, like `Text` or `Button`. * **Find By Semantics Label:** Useful for locating widgets that have a specific semantics label. * **Find By Text:** Locate a widget that displays specific text. * **Find By Descendent:** Search for a widget that has a specific child or ancestor. ## Add Tests[​](/testing/automated-tests.md#add-tests "Direct link to Add Tests") Let's see how to add tests with an example that will ensure that users can add and remove items from their favorites list. Here are the step-by-step instructions on adding tests: 1. Create a test to verify if the page is visible on the screen. [Sharing a Project with a User](https://demo.arcade.software/RjJPy7zOBCu1QAVi8h0p?embed\&show_copy_link=true) 2. Next, find and simulate on tap event on the favorite button with the 'ValueKey' as the product id. **Important**: By using the 'ValueKey', we precisely target the favorite button for a specific product. Without this specificity, the test will encounter multiple favorite buttons and become uncertain about which one to tap, leading to a failed test. [Sharing a Project with a User](https://demo.arcade.software/GF5My9t7gjEGfSEdgSXR?embed\&show_copy_link=true) 3. Similarly, you can now duplicate the test and make changes for the 'RemoveFromFavorites' test. **Tip**: While doing so, ensure that in the last step (i.e., **Expect Result**), you set the **Expectations** to **Finds Nothing**. This ensures that the removed item is not visible on the favorites list. ![remove-from-favorites](/assets/images/remove-from-favorites-40571fef2eabad43eb41466b20bbfdb9.avif) ## Run Tests[​](/testing/automated-tests.md#run-tests "Direct link to Run Tests") You can run tests on local devices or use the services like [Firebase Test Lab](https://firebase.google.com/docs/test-lab). To run the tests locally: 1. [Download the project code](/flutterflow-cli/exporting.md). 2. Go to `your_project/integration_test/test.dart`. 3. To run a specific test, click the play button next to it. To execute all tests at once, double-click the play button next to `void main`. 4. Alternatively, you can use the terminal and enter the command: `flutter test integration_test/test.dart`." info To run the tests on Firebase Test Lab, you can follow the instructions [**here**](https://docs.flutter.dev/testing/integration-tests#test-using-the-firebase-test-lab). --- # Development Environments Development Environments in FlutterFlow allow you to set up multiple environments for your apps, such as `Development`, `Staging`, and `Production`. For each environment, you can create environment-specific values and databases. This allows you to easily point to different backends depending on where you are in your development lifecycle. note By default, every FlutterFlow project starts with a `Production` environment. When to Use Dev vs. Staging Environments * **Dev Environment**: Use for testing and developing new features without affecting production data. * **Staging Environment**: Use to simulate the production environment before launching, and is isolated from the actual production data. *This is a common best practice, but you can create custom environments with different names for your own workflow.* ### Create and Switch Development Environments[​](/testing/dev-environments.md#create-and-switch-development-environments "Direct link to Create and Switch Development Environments") You can create and switch environments in the **Dev Environments** page in **App Settings**. You can always see the current environment that is selected by looking in the top left hand corner of the project. [Creating and Switching Development Environments](https://demo.arcade.software/yR8P5pFPOKtuQ0jFSOJ7?embed\&show_copy_link=true) The selected environment is used to generate the proper app code when you run, test, deploy or export your app. The only things that change between environment are the [Firebase Project](/testing/dev-environments.md#configuring-firebase-or-supabase-for-each-environment) or variables that are tied to [Environment Values](/testing/dev-environments.md#environment-values) ### Environment Values[​](/testing/dev-environments.md#environment-values "Direct link to Environment Values") Environment Values can be used to dynamically change parts of your app's code based on the environment that is being used. For example, in an e-commerce app, you might define an `apiUrl` Environment Value that points to different API URLs for Development, Staging, and Production. This allows you to test new features without affecting the live production environment, where real customer orders are processed. #### Use Environment Value[​](/testing/dev-environments.md#use-environment-value "Direct link to Use Environment Value") Let's see an example of creating and using `apiUrl`: [Creating and Using Environment Values](https://demo.arcade.software/bAVpkNAanVDlBTyeRwJy?embed\&show_copy_link=true) Generated Code When you switch to an environment, FlutterFlow generates code specific to that environment, for any of the following interactions: * Test / Run mode sessions * Local Run * Code export * Deployment You may also encounter different project errors depending on the selected environment. In the generated code, FlutterFlow creates two files: * `environment.json` – Stores the environment values defined by the user in FlutterFlow. * `FFDevEnvironmentValues` class – A singleton class that holds a single instance of the `FFDevEnvironmentValues` object. It includes initialization logic and getters for accessing these environment values. They can also be referenced in your custom code resources. See **[Common Custom Code Examples](/concepts/custom-code/common-examples.md#get-dev-environment-values-in-custom-code)**. #### Private Environment Values[​](/testing/dev-environments.md#private-environment-values "Direct link to Private Environment Values") You can mark environment values as private when they contain sensitive information that should not be exposed in the client-side code. warning Private environment values are not included in the compiled application code and are never exposed to end users. However, if a private environment value is used in a private API call that runs through a generated cloud function, the value may appear in the cloud function’s code. When exporting or pushing your project to GitHub, you must review and manage these cloud function files—for example, excluding them with `.gitignore` if they contain sensitive information. Currently, the only way to use a private environment value is as a variable in a private API call. Since private API calls are routed through a Cloud Function, the variable value remains hidden from any client-side requests made by the app. Generated Code For private environment values, the generated code does not include these values in the `environment.json` file, and no getter logic is created in the `FFDevEnvironmentValues` class. ### Configuring Firebase or Supabase for each Environment[​](/testing/dev-environments.md#configuring-firebase-or-supabase-for-each-environment "Direct link to Configuring Firebase or Supabase for each Environment") A single FlutterFlow project can have **multiple environments**, each mapped to its **own Firebase or Supabase project**. This ensures that environments like `Development`, `Staging`, and `Production` remain independent, giving you better control over your app's data and behavior throughout different stages of development. ![flutterflow-environment](/assets/images/flutterflow-environment-update-ebd402503ef403cb632e068bfe98d81f.avif) You must complete the Firebase or Supabase setup for an environment before you can test your app using that environment. However, this doesn't stop you from continuing to run and test your app in other environments. Just switch back to Production, and you can keep testing while finishing the setup for the new environment. #### Configuring Firebase[​](/testing/dev-environments.md#configuring-firebase "Direct link to Configuring Firebase") If your project uses Firebase, you'll need to create a separate Firebase project in the Firebase Console for each environment. Then, you can change the selected environment in the Firebase settings page (see below), and follow the steps to [**manually configure the Firebase project**](/integrations/firebase/connect-to-firebase.md#connect-an-existing-firebase-project-manually) for each one. ![firebase-dev-env-config.png](/assets/images/firebase-dev-env-config-e6341ee4a2459cbd8b1dd84cea224c07.png) Additionally, you must manually set up [**Firestore rules**](/integrations/database/cloud-firestore/firestore-rules.md) and [**collections**](/integrations/database/cloud-firestore/creating-collections.md) for the new environment. info The data that you add to Firebase through the Content Manager is specific to the Firebase project, and environment, that you have selected. #### Configuring Supabase[​](/testing/dev-environments.md#configuring-supabase "Direct link to Configuring Supabase") If your project uses Supabase, you'll need to [**set up a new Supabase project**](/integrations/supabase/setup.md) for each environment. Create environment-specific values like `SupabaseAPIURL` and `SupabaseAnonKey`, and then configure the Supabase properties to point to these newly created values. Below is an example of how it would look like. note It's recommended that you keep schemas consistent between the different Supabase environments. It's also recommended that you **Get Schema** from the Production environment and build from there. ### FAQ[​](/testing/dev-environments.md#faq "Direct link to FAQ") How can you push code from one environment to another? It’s important to note that the **Development Environments** feature in FlutterFlow is primarily designed to configure different backends for testing If you are building new features, you should consider using [**Branching**](/collaboration/branching.md). You can develop and test new features on a new branch by selecting a development environment. Once tested, you can merge the branch into `main` and switch to the `Production` Environment to go live. Are you using Flutter flavors under the hood? No, FlutterFlow does not use Flutter flavors. Instead, it generates code based on the environment selected in FlutterFlow. The environment-specific code is generated and applied for the following actions: * Test / Run mode sessions * Local Run * Code export * Deployment How to deploy apps for different environments? You can configure deployment settings for each environment using the dropdown interface on the deployment page. For mobile, set a new package name, and for web, set a new site URL. Once done, deploy your app as usual. See how to do it in [**detail here**](/deployment/deploy-for-environments.md). --- # Local Run You can test your app on a real device using the Local Run feature, which is available in the FlutterFlow Desktop App. Local Run automatically tracks changes in your FlutterFlow project, downloads the code locally, and gives you the option to use Flutter's Hot Reload or Hot Restart to see your changes instantly on a device. **Prerequisites** Testing on mobile devices requires downloading code, for which you must be on [**paid plans**](https://flutterflow.io/pricing). ### iOS Setup[​](/testing/local-run.md#ios-setup "Direct link to iOS Setup") For iOS app testing on a device or simulator, you need a Mac with Xcode. Follow [**these instructions**](https://docs.flutter.dev/get-started/install/macos/mobile-ios?tab=download#configure-ios-development) to set up your Mac, which includes [**setting up your device for testing**](https://docs.flutter.dev/get-started/install/macos/mobile-ios?tab=download#configure-your-target-ios-device). ### Android Setup[​](/testing/local-run.md#android-setup "Direct link to Android Setup") For Android app testing on a device or emulator, configure your machine ([**Windows**](https://docs.flutter.dev/get-started/install/windows/mobile?tab=virtual), [**Mac**](https://docs.flutter.dev/get-started/install/macos/mobile-android?tab=virtual), [**Linux**](https://docs.flutter.dev/get-started/install/linux#android-setup)) by following [**these instructions**](https://docs.flutter.dev/get-started/install/macos/mobile-android?tab=virtual#configure-android-development), which include [**setting up your device for testing**](https://docs.flutter.dev/get-started/install/macos/mobile-android?tab=virtual#configure-your-target-android-device). ## Using Local Run[​](/testing/local-run.md#using-local-run "Direct link to Using Local Run") Here are the steps to use local run: 1. Download the [desktop](https://flutterflow.io/desktop) app and open your project. 2. In the [Toolbar](/flutterflow-ui/toolbar.md), click on the **dropdown** next to the *Test Mode* button and click **Setup Local Run**. This will open the setup wizard. ![setup-local-run](/assets/images/setup-local-run-ccd7811b7cb820f9f949185b93e1ca53.avif) 3. To run the app locally, you'll need the Flutter SDK. Click the **Download** button to download it. **Note** that for iOS, ensure you have *Xcode* and *CocoaPods* installed, select the checkmark, and then click **Download**. ![download-flutter-sdk](/assets/images/download-flutter-sdk-5dcbdc286c65082b012c91321e36e39f.avif) 4. Once it's ready to use, click the **Continue** button. This will run the **`Flutter Doctor`** command to check your environment for any issues that might prevent you from running the applications. It performs a series of checks to verify that the necessary tools and dependencies are correctly installed and configured on your system. ![doctor-output](/assets/images/doctor-output-da2d64e4465ae9aefe070cea216c5f32.avif) 5. Optional: You can set up your preferred IDE to open the project code directly from the local run. To do this, select your IDE, **Select Path**, and click **Save**. This feature is useful for debugging and understanding your project code. For this step, ensure you have setup [Flutter SDK](/testing/local-run.md#2-setup-flutter-sdk) and [IDE](/testing/local-run.md#3-installing-ide-and-plugins). info * The local run uses its own isolated Flutter SDK to ensure consistency and compatibility. The SDK is stored separately from any existing Flutter installations on your system and is automatically used to run your app and open projects in VS Code. For other IDEs like Android Studio, you need to set the SDK path to FlutterFlow's version manually. * **Please note** that any changes made in the IDE will not sync with the FlutterFlow project and will be overwritten when you hot reload or restart the app. * The path is the location of the IDE on your computer. On macOS, it's typically in "Applications," and on Windows, it's usually in "Program Files." * Also, see how to [**access the project code**](/testing/local-run.md#access-project-code). ![config-IDE](/assets/images/config-IDE-3cd6d998b47f2e689b71921d080a1806.avif) 6. In the **Code Export** section, you can configure how Local Run exports and updates your FlutterFlow project code. * **Experimental Speed Up**: Uses an optimized export pipeline to significantly reduce export times. When enabled, Local Run can also work offline for faster iteration. If you experience export-related issues, you can disable this option. * **Format Exported Code**: Controls whether the exported code should be automatically formatted. Disabling formatting improves export speed, which helps during rapid iteration. However, if you plan to inspect or modify the generated code, it’s recommended to keep this enabled. * **Enable Debug Logging**: Includes logging support in the exported app. Keeping this enabled allows you to use the FlutterFlow Debug Panel inside DevTools for debugging and inspection. * **Auto Hot Reload**: Automatically triggers a hot reload whenever changes are made in FlutterFlow. This removes the need to manually trigger hot reload after every update. * **Auto Hot Restart**: Automatically performs a full app restart when changes require more than a hot reload, such as new dependencies or state model updates. This is disabled by default because full restarts are slower than hot reloads. ![local-run-code-export](/assets/images/local-run-code-export-6b12621e787e585534fcd0ba8f76b6a6.avif) 7. From the test menu, click on the **Get Devices** button. This will list devices connected to your system. You can add or remove devices from the list by clicking on the **+** and **-** buttons, respectively. Once you've finalized your selection, simply click on the **Test** button to see your app running on selected devices. **Tip**: In the Mac OS desktop app, you can directly open the simulator by clicking on the **Launch iOS Simulator** text. To test app on a real device, see how to [setup a physical device](/testing/local-run.md#setup-physical-device). [Sharing a Project with a User](https://demo.arcade.software/PdTDtCPA6dmY2N4ziJ1A?embed\&show_copy_link=true) 8. After you make a change in your app, open the test menu to access options like **hot reload**, **hot restart**, and **stopping** your app. You'll notice that the test mode button has now changed to the **Hot Reload** button, which you can click anytime to instantly see your changes reflected on your device. **Hot Reload** updates UI instantly without losing its state, while **Hot Restart** recompiles and reloads the entire app, resetting its state. For more info, you can visit [Flutter's Hot Reload documentation](https://docs.flutter.dev/tools/hot-reload). ![run-controls](/assets/images/run-controls-af8bd39b1b2838b4f99b6d9c8e2caa41.avif) ## Setup Physical Device[​](/testing/local-run.md#setup-physical-device "Direct link to Setup Physical Device") Testing your app on physical devices is essential to ensure it performs as expected in real-world scenarios. To set up a physical device, first, launch the project in **Android Studio** or **Xcode**, depending on the platform you are targeting. You can easily access these options by clicking on the **code icon** in the **Local Run** menu. ![access-project-code.avif](/assets/images/access-project-code-b77d440078de6a10c3cf93798c5eb5f5.avif) ### Setup Android Device[​](/testing/local-run.md#setup-android-device "Direct link to Setup Android Device") To setup Android physical device, first enable Developer Options and USB Debugging in your Android device. Navigate to **Settings > About phone**, tap **Build number** seven times to activate Developer Options, then go to **Settings > System > Developer options** and enable **USB debugging**. Connect your device to your computer via USB, authorizing the connection if prompted. Verify the setup by running `flutter devices` in Android Studio’s terminal; your device should appear in the list of connected devices. info For more detailed guidance, refer to the [**Android Flutter documentation**](https://docs.flutter.dev/get-started/install/macos/mobile-android#configure-your-target-android-device). ### Setup iOS Device[​](/testing/local-run.md#setup-ios-device "Direct link to Setup iOS Device") To setup iOS physical device, you must configure your **Apple Developer account** and set up **code signing** in Xcode. First, add your **Apple ID** by opening **Xcode > Preferences > Accounts**, clicking **"+"**, selecting **Apple ID**, and signing in. Next, assign your project to a development team. Open your project in Xcode, select the **Runner** project, go to **Signing & Capabilities**, and choose your **Apple Developer team** in the **Team** dropdown. If your team is not listed, ensure that your Apple ID has been properly added to Xcode. Finally, configure code signing to allow your app to run on a real device. Ensure **"Automatically manage signing"** is enabled. Xcode will attempt to create and download a **provisioning profile** for your project. If issues arise, you may need to manually create a provisioning profile in the **Apple Developer Certificates, Identifiers & Profiles** section. Once created, download and double-click the provisioning profile to install it in Xcode. info For more detailed guidance, refer to the [**iOS Flutter documentation**](https://docs.flutter.dev/get-started/install/macos/mobile-ios#configure-your-target-ios-device). ## Access Device Logs in Local Run[​](/testing/local-run.md#access-device-logs-in-local-run "Direct link to Access Device Logs in Local Run") Device logs provide a way to access and view the logs generated by your app while it's running on a device or simulator. They are invaluable for understanding the inner workings of your app. If something isn't functioning as expected, the device logs can reveal the reasons behind it. To access the device logs, first run your app using the local run. Then, open the test menu and click on **Logs** icon. This will display a floating window with detailed logs of the app while it's running. ![access-device-logs](/assets/images/access-device-logs-2-409187df261c87da7bcf5c2122b13008.avif) ### Console Input[​](/testing/local-run.md#console-input "Direct link to Console Input") The console input in local run is particularly useful for performing hot reload and hot restart directly from the device logs. To initiate a hot reload, press `r` followed by `Enter`, and for a hot restart, press `R` followed by `Enter`. Additionally, any terminal commands commonly used with Flutter while running an app should work with the console input. [Sharing a Project with a User](https://demo.arcade.software/fraMoCbFDhzunNgBN852?embed\&show_copy_link=true) ### Checking Errors[​](/testing/local-run.md#checking-errors "Direct link to Checking Errors") Any errors displayed in the red box on your screen are also recorded in the Device logs, where you can find detailed information about the app's state and the events leading up to the issue. ## Reconfigure Local Run Setup[​](/testing/local-run.md#reconfigure-local-run-setup "Direct link to Reconfigure Local Run Setup") If you need to update the Flutter SDK version, run Flutter Doctor, or start the simulator again, simply open the test menu and click **Configure**. ![reconfigure-local-run.avif](/assets/images/reconfigure-local-run-2-73aba54c54ec4217e046f342cd5a0491.avif) ## Access Project Code[​](/testing/local-run.md#access-project-code "Direct link to Access Project Code") To access the project code, open the test menu and ensure the project is not running. Click on the **code icon**, and you'll be presented with options to either open the project folder, project in your preferred IDE or directly launch the project in Xcode (for macOS users). ![access-project-code.avif](/assets/images/access-project-code-b77d440078de6a10c3cf93798c5eb5f5.avif) ## Manually Download Code and Run[​](/testing/local-run.md#manually-download-code-and-run "Direct link to Manually Download Code and Run") There may be certain situations where you, as a developer, may prefer not to have local runs overwrite any changes that have been made in the code. In such cases, you can manually download the code onto your local system and then make any modifications as needed. Here’s how you do it: 1. [Download code](/testing/local-run.md#1-download-code) 2. [Setup Flutter SDK](/testing/local-run.md#2-setup-flutter-sdk) 3. [Installing IDE and Plugins](/testing/local-run.md#3-installing-ide-and-plugins) 4. [Running app on device](/testing/local-run.md#4-running-app-on-device) ### 1. Download Code[​](/testing/local-run.md#1-download-code "Direct link to 1. Download Code") warning * Project code download is available only on the paid plans. * Make sure to address any project issues before downloading the code. To download your app code, you have two options: * Use the [FlutterFlow CLI](/flutterflow-cli/exporting.md). (Recommended) * Alternatively, from the **Toolbar**, click on the **Developer Menu** > **Download Code**. This will download the *.zip* file. Extract the *.zip* file to view the contents of the project. ### 2. Setup Flutter SDK[​](/testing/local-run.md#2-setup-flutter-sdk "Direct link to 2. Setup Flutter SDK") You can download the latest Flutter SDK from [here](https://docs.flutter.dev/get-started/install). However, we recommend using the Flutter SDK downloaded by the [local run](/testing/local-run.md#using-local-run), whether you have already downloaded the Flutter SDK or not. This approach ensures compatibility with FlutterFlow projects and helps you avoid issues arising from version differences. To do this, copy the Flutter SDK path (click 'this path' button) from the local run and [add it to your system path](/testing/local-run.md#troubleshooting). ![setup-flutter-SDK](/assets/images/setup-flutter-SDK-57c8c80b82e6d2998cc36b930153bc01.avif) If you prefer to use your existing Flutter SDK, you can follow the steps below to avoid any versioning issues: 1. Take note of your FlutterFlow project version. ![check-flutter-version.avif](/assets/images/check-flutter-version-2-ae7065141a80cc79c9eec976e7dfc296.avif) 1. Check your current Flutter SDK version by entering the following command in the terminal. `flutter --version` 2. If that is different from what FlutterFlow uses, you may need to switch to the supported version. 3. To install a specific version of Flutter, use the following command: 1. To **downgrade** flutter version: ``` flutter downgrade ``` 2. To **upgrade** flutter version: ``` flutter upgrade --force ``` Replace `` with the version supported by FlutterFlow. ### 3. Installing IDE and Plugins[​](/testing/local-run.md#3-installing-ide-and-plugins "Direct link to 3. Installing IDE and Plugins") You can choose to install either [Visual Studio Code](https://code.visualstudio.com/) or [Android Studio](https://developer.android.com/studio) as the IDE for your project. With either IDE, you also need the official Flutter and Dart plugins that provide you with code completion, syntax highlighting, widget editing assistance, run & debug support, and more. * To install Visual Code with Flutter and Dart plugins, check out [this link](https://flutter.dev/docs/get-started/editor?tab=vscode). * To install Android Studio with Flutter and Dart plugins, check out [this link](https://flutter.dev/docs/get-started/editor?tab=androidstudio). ### 4. Running App on Device[​](/testing/local-run.md#4-running-app-on-device "Direct link to 4. Running App on Device") You can choose to run your app on a real device or an emulator. tip To test app on a real device, see how to [**setup a physical device**](/testing/local-run.md#setup-physical-device). To run your app on a device: 1. First open the downloaded project in your preferred IDE. 2. For **VS Code**: 1. Go to the "View" menu -> select "Terminal" from the dropdown. 2. Run the command `flutter pub get`. 3. Now, enter the command `flutter run`. VS Code will build and run your app. You'll see the output in the terminal, and the app should launch in the selected emulator or physical device. 3. For **Android Studio**: 1. Open the terminal within Android Studio by clicking **"View" -> "Tool Windows" -> "Terminal"**. 2. Run the command `flutter pub get`. 3. Click the green "Run" button (a right-facing triangle) located in the top toolbar. Choose the target device (emulator or physical device) where you want to run the app. Android Studio will build and run your app. You'll see the output in the "Run" panel at the bottom, and the app should launch in the selected emulator or device. info * If your device is not listed in the **Flutter Device Selection** dropdown, make sure you have properly completed the Android and iOS setup. * If you encounter a version compatibility issue with Flutter, you can resolve it by upgrading to the latest version. Simply execute the `flutter upgrade` command in your terminal. To verify your current Flutter version, use the `flutter --version` command. ## Run on Desktop[​](/testing/local-run.md#run-on-desktop "Direct link to Run on Desktop") Running your app on a Desktop involves: 1. **Adding platforms**: Navigate to **Setting and Integrations** from the Navigation Menu > **Project Setup** > **Platforms** and enable your desired platform. 2. **Make design adjustments (optional)**: If you plan to target both mobile and desktop users, some design adjustments may be necessary to ensure that the UI is optimized for both platforms. You can create separate widgets for different platforms and control their visibility using [Responsive Visibility](/concepts/layouts/responsive.md#responsive-visibility). 3. **Run the app on a desktop**: Use the Local Run feature in the FlutterFlow Desktop app or manually download and run the code, choosing your target device (e.g., macOS) before running. ## Video Guide[​](/testing/local-run.md#video-guide "Direct link to Video Guide") If you prefer watching a video tutorial, here's the one for you: [Local Run | New Feature Tutorial](https://www.youtube.com/embed/k9NpYncXC_U) *** ## Troubleshooting[​](/testing/local-run.md#troubleshooting "Direct link to Troubleshooting") Command not found: flutter (add Flutter to system's path) If you downloaded Flutter via local run, it might not be added to your system's path. You'll need to get the Flutter SDK directory and add it to your path manually. * For Mac * For Windows 1. From the [local run](/testing/local-run.md#using-local-run) wizard, open the **Configure IDE** step and click on **this path** to get the Flutter SDK path. ![get path](/assets/images/get-path-5180adef7967b16994e39ff537e3fb09.avif) 2. Open the Terminal and run the following command to open your `.zshrc` file (or `.bash_profile` if you're using Bash): ``` open -e ~/.zshrc ``` 3. Add path at the end of the file. It should look something like this: ``` export PATH="$PATH:$HOME/Library/Application Support/io.flutterflow.prod.mac/flutter/bin" ``` 4. Save and close the file. 5. Run the following command to apply the changes: ``` source ~/.zshrc ``` 6. Restart your terminal and try running the `flutter` command again. 1) From the [local run](/testing/local-run.md#using-local-run) wizard, open the **Configure IDE** step and click on **this path** to get the Flutter SDK path. ![get path](/assets/images/get-path-5180adef7967b16994e39ff537e3fb09.avif) 2) Right-click on the Start menu and select "System". 3) Click on "Advanced system settings" and then "Environment Variables". 4) Under "System variables", find the "Path" variable and click "Edit". 5) Click "New" and add the path to your Flutter SDK. 6) Click "OK" to save your changes. 7) Restart your command prompt and try running the `flutter` command again. Device not showing in the list If you don't see your device in the list after refreshing, follow these steps: 1. Ensure you have added Flutter to your path. 2. Open the Terminal and run the following command: ``` flutter devices ``` This will list all connected devices that the Local Run recognizes. 3. If you still don't see your device, try restarting it. 1. **For iOS**: Open Xcode, go to the "Window" menu, select "Devices and Simulators," choose your simulator, and click "Restart." 2. **For Android**: Open the Android Studio > Device Manager, choose your emulator, and click the "Play" button. 3. You can also restart the emulator directly from the command line using Flutter: ``` flutter emulators --launch ``` **Note** that replace `` with the ID of your emulator. You can find the ID by running `flutter emulators`. 4. Try running `flutter devices` again. Xcode warning "Runner.xcworkspace modified" If you encounter a warning from Xcode stating: > "The file 'Runner.xcworkspace' has been modified by another application." This warning can usually be safely ignored. It typically occurs when multiple tools or processes (such as FlutterFlow local run and Xcode) modify the project files simultaneously. Here's what you can do: 1. **Save Your Work**: Ensure that you've saved any changes you've made in Xcode. 2. **Close and Reopen**: Close the warning prompt and, if necessary, close and reopen Xcode to refresh the project files. 3. **Clean the Build**: If the warning persists, try cleaning the build folder in Xcode by going to "Product" > "Clean Build Folder." 4. **Flutter Clean**: You can also run `flutter clean` in your terminal to clean the build cache for your project, which can sometimes resolve issues related to outdated or conflicting files. *** ## FAQs[​](/testing/local-run.md#faqs "Direct link to FAQs") Can I export the project as a Flutter Module? Yes, you can export your project as a Flutter module. Here's how: 1. Activate the FlutterFlow CLI by entering `dart pub global activate flutterflow_cli` in your terminal. 2. Use the command below to export your project and substitute ``, ``, and `` with your specific project details: ``` flutterflow export-code --project --dest --include-assets --token --as-module ``` If you wish to exclude assets from the export, use `--no-include-assets` in your command. This will export the project code without the assets. For example: `flutterflow export-code --project your_project_id --dest path_to_output_folder --no-include-assets --token your_token --as-module` You can then follow the instructions for [Android](https://docs.flutter.dev/add-to-app/android/project-setup) and [iOS](https://docs.flutter.dev/add-to-app/ios/project-setup) to add the module to your main app. --- # Run your App Running and testing your app is a crucial part of the app development process. This page provides a comprehensive guide on how to run and test your FlutterFlow app. It covers various modes of testing, including [Preview](/testing/run-your-app.md#preview-mode), [Test](/testing/run-your-app.md#test-mode), [Run](/testing/run-your-app.md#run-mode), and [Local Run](/testing/run-your-app.md#local-run) modes, with detailed steps and indications of when to use each mode. info You can access various modes of running your app from the [**Toolbar**](/flutterflow-ui/toolbar.md). ![run your app](/assets/images/run-your-app-61003863bd89e585f73f68fc26c93b83.avif) ## Preview Mode[​](/testing/run-your-app.md#preview-mode "Direct link to Preview Mode") You can use the Preview Mode to quickly try out your app on a virtual device without waiting for it to build. This is helpful primarily for navigation and animations. You can also preview your app in the Dark/Light mode and visualize it on various mobile, tablet, and desktop devices. ### When to use Preview Mode[​](/testing/run-your-app.md#when-to-use-preview-mode "Direct link to When to use Preview Mode") The primary benefit of **Preview Mode** is that it allows your app to load instantly, making it ideal for UI testing. However, most business logic is not included in this mode. As a result, this mode is used less frequently than other testing modes, which provide a more comprehensive evaluation of the app's functionality. Preview Mode Limitations * Actions may not trigger or work properly. * FontAwesome icons jump around when mouse hovers over certain material widgets. * Firestore data is not loaded from Firebase. * Firebase auth flow can't be tested. We always allow log in. * API Calls can't be run or tested here. * Refresh if animation actions are not working. * Refresh if Clear TextFields actions are not working. * RevenueCat data is not loaded. * Paywall actions execute as if the entitlement is active. * Hero Animation may not work on dynamically generated widgets. * Dropdown disabling does not work in Preview Mode. * Tooltip does not work for some screen sizes in Preview Mode. ## Test Mode[​](/testing/run-your-app.md#test-mode "Direct link to Test Mode") The **Test Mode** runs a web version of your FlutterFlow app and uses Flutter's Hot Reload feature, which lets you immediately see any changes made to code in an emulator or on-device. Running your app in Test Mode helps you experiment, test UIs, and fix bugs faster. To run your app in Test Mode: 1. Select **Test Mode** from the left-side menu. The test environment will launch and be ready to use within a few minutes. 2. Once Test Mode is running, make changes in the FlutterFlow builder, such as updating colors, layouts, or widgets. 3. In Test Mode, **Sync changes automatically** is enabled by default, so changes made in FlutterFlow are automatically synced to the running app. 4. If you disable auto-sync, click **Hot Reload** or press `Cmd/Ctrl + J` whenever you want to manually sync and preview the latest changes. 5. Use **Hot Restart** when changes require a full restart, such as dependency updates or certain state model changes. note **For users on a paid plan**, Test Mode sessions do not expire and can remain active indefinitely until manually stopped. **For users on the Free plan**, Test Mode sessions expire after 20 minutes. Once a session expires, you can start a new one by clicking the **New Session** button. ![new-session](/assets/images/new-session-586a87a3ac25f5881e09126311c15b86.avif) ### Floating Window[​](/testing/run-your-app.md#floating-window "Direct link to Floating Window") A Floating Window displays the running app on top of the builder, allowing you to design and test at the same time without switching between tabs. The Floating Window makes iteration much faster because you can immediately see the impact of your changes while working in the builder. It makes it easier to fine-tune layouts, styling, and interactions. To open the Floating Window, start a Test Mode session and click the **Floating Window** icon in the Test Mode toolbar. A movable preview of your app will appear over the builder, allowing you to keep the live app and editor visible at the same time. You can drag the window anywhere on the screen and resize it as needed while continuing to edit your app. ### Inspect Mode[​](/testing/run-your-app.md#inspect-mode "Direct link to Inspect Mode") Inspect Mode helps you quickly locate widgets in the FlutterFlow builder while testing your app. This is especially useful when working with large pages or deeply nested layouts. Instead of manually searching through the Widget Tree to find a specific button, image, text, or container, you can simply click it in the running app and jump directly to its location in the builder. To use Inspect Mode, click the **Inspect Mode** icon in the Test Mode toolbar. Once enabled, select any widget in the running app preview. FlutterFlow will automatically navigate to and highlight the corresponding widget in the builder, allowing you to inspect or edit it immediately. When you're finished, click the Inspect Mode icon again to exit inspection mode and continue interacting with the app normally. ### Test Mode on Mobile[​](/testing/run-your-app.md#test-mode-on-mobile "Direct link to Test Mode on Mobile") You can open the current Test Mode session directly on a physical mobile device. This allows you to test your app on actual hardware and verify touch interactions, layouts, scrolling behavior, and overall user experience. To open the session on your phone, click the **QR Code** icon in the Test Mode toolbar. FlutterFlow will generate a QR code and a unique session link. Scan the QR code using your phone's camera or open the generated link on your mobile device. The app will load the same active Test Mode session that is running in your browser. warning The generated link is tied to the current Test Mode session and will stop working when the session ends. ![test-mode-in-phone](/assets/images/test-mode-in-phone-2c85818cfd1323b0164376ca0ffc0c19.avif) ### Debug info[​](/testing/run-your-app.md#debug-info "Direct link to Debug info") Test mode also includes a **Debug Info** panel, which provides a real-time view of all variables with their current values. It includes search and filter options, allowing you to find variables based on type or nullability. This is particularly useful for developers who need to track the state of the app and diagnose issues efficiently. ![deubg-info](/assets/images/deubg-info-f3da771189b805d1e3c99110a4dbccbd.avif) Test Mode Limitations **Test Mode** has certain limitations because some packages are not supported on the web and because of the way FlutterFlow configures your project to run in the cloud. * If you see a grey "broken" screen with a sad face, it may be a DNS server issue with your network provider. We recommend using CloudFlare's 1.1.1.1 DNS server. [**Click here**](https://developers.cloudflare.com/1.1.1.1/setup/) to see instructions. * Lottie animation may not load if you provide a variable path. * Cookies need to be enabled for Test Mode to function properly. They are only used for functional purposes. * If you see a progress bar where the phone outline should be that lasts longer than 15 seconds, try refreshing the page. * The device screen can not be wider than the page's width. * Copy to Clipboard Action is not supported in Test Mode. Use [**Run Mode**](/testing/run-your-app.md#run-mode) to avoid this issue. * Widgets with Shimmer or Tint animation might not appear properly. * Assets used within Custom Code might not appear properly. * Audio Recording actions do not work in Test Mode; use web publishing in Settings to test recording audio or test it on emulator via Local Run. ## Run Mode[​](/testing/run-your-app.md#run-mode "Direct link to Run Mode") You can test a fully functional version of your app using the **Run Mode**, including live data. It will build the app, which typically requires around 2-4 minutes - but can be longer for larger projects. You can then interact with your app through your web browser. This is a web version of the app, identical to the version that is run on *Test Mode*. To run the app in Run Mode, click on the **dropdown** next to the Test Mode button and click the play button or press **Cmd/Ctrl + E** (keyboard shortcut). This will run your app in a new browser window. ### When to use Run Mode[​](/testing/run-your-app.md#when-to-use-run-mode "Direct link to When to use Run Mode") The main benefit of Run Mode is the ability to share a running app within your team via a link. Please note that, **Run Mode links are not public**; they are only accessible to project members. Even if the project is made public (allowing others to view and clone the project), the visibility of Run Mode links remains restricted to project members. All Run Mode sessions will persist and can be accessed from the dropdown menu next to the lightning bolt icon in the upper right of the FlutterFlow builder. ![run-project-versions](/assets/images/run-project-versions-4a8611a8a0971f33edc403b661e723e8.avif) Run Mode Limitations Run Mode does not support Hot Reloading, so any changes you make to your app will not be reflected in the Run Mode. In order to see the changes, you would have to create another Run Mode. ## Local Run[​](/testing/run-your-app.md#local-run "Direct link to Local Run") Local Run downloads the code locally and gives you the option to use [Flutter's Hot Reload](https://docs.flutter.dev/tools/hot-reload) or Hot Restart to see your changes instantly on a device. See how to setup Local Run [here](/testing/local-run.md). info Please note that Local Run is currently available only on the [**Paid Plans**](https://flutterflow.io/pricing). ## FAQ[​](/testing/run-your-app.md#faq "Direct link to FAQ") I don't see the new Test Mode option in the left sidebar. If the new Test Mode option is not visible in the left sidebar, open the test menu and enable the **Use new test mode** option. Once enabled, the new Test Mode option will appear in the navigation menu. --- # Test Pilot Test Pilot in FlutterFlow allows you to run AI-powered tests for your app. Instead of building step-by-step integration tests manually, you create natural-language tests, group related tests together, and ask Test Pilot to interact with your app like a QA tester. This is useful when you want to validate important user journeys before publishing or sharing a new build. For example, you can ask Test Pilot to sign in with test credentials, add an item to a cart, open the profile page, or confirm that a checkout flow lands on the expected success screen. At a high level, Test Pilot creates a web build snapshot of your project, launches the app in a browser-based QA environment, and uses an AI agent to follow each test's instructions. After the run completes, you can review pass or fail status, per-test summaries, screenshots, playback, actions taken, and credit usage. ## Create Test[​](/testing/test-pilot.md#create-test "Direct link to Create Test") To get started, first create a **Test Group**. Use test groups to organize related tests, such as authentication, checkout, profile settings, or onboarding. After you create or select a test group, add tests that describe the exact journey Test Pilot should perform. Each test has: * **Test Name**: The label shown in the test group and run results. * **Entry Page**: Optional initial route for the test. Use **App default** when the app should start from its default entry page. * **Enabled or Disabled state**: A toggle to enable or disable a test. Only enabled tests are included when the group runs. * **Instructions**: Natural-language steps for the AI agent to follow. * **Expected Outcome**: Optional success criteria that Test Pilot should verify after completing the instructions. * **Restart before test**: Optional behavior that starts the app fresh before that test. This is **turned off by default, so tests are executed sequentially** one by one and can continue from the state left by the previous test. Turn it on when a test should start from a clean app state and not depend on earlier tests. Write instructions the way you would brief a QA teammate. Mention the screen, the UI element, the action to take, and the expected result. For example, a login test might use: * **Instructions**: `Wait for the screen to load, then enter $email and $password into the text fields and click the continue button.` * **Expected Outcome**: `The app should land on the home page.` tip `$email` and `$password` are [**Test Parameters**](/testing/test-pilot.md#test-parameters). After you create a parameter, you can reference it directly in test instructions by adding `$` before the parameter name. Here's how to create tests: ### Test Parameters[​](/testing/test-pilot.md#test-parameters "Direct link to Test Parameters") Test Parameters let you store reusable values that Test Pilot can substitute into test instructions. This is especially helpful for credentials, environment-specific values, or other data that you do not want to type into every test. Test Parameters are managed per [FlutterFlow Environment](/testing/dev-environments.md). Use the **Environment** dropdown to switch between environments, then add the parameter values for the selected environment. Each parameter includes: * **Name**: The variable name used in instructions. * **Value**: The value Test Pilot substitutes when running the test. * **Description**: A clear note describing what the value is used for. * **Encryption toggle**: Marks sensitive values so they are encrypted. To use a parameter, create it from **Test Parameters**, then reference it in instructions with a dollar sign, such as `$email` or `$password`. caution For login tests, use a separate test account instead of a real user account. Secret parameters help hide sensitive values, but you should avoid using production credentials in Test Pilot runs. ## Run Test[​](/testing/test-pilot.md#run-test "Direct link to Run Test") To run a test group, select the test group you want to run, click **Run Test Group**, choose the run configuration, and then click **Run Tests**. Run configuration options include: * **Device Sizes**: Uses the current canvas size by default. You can add predefined devices or custom width and height values. The run dialog supports up to 3 selected devices. * **Brightness**: Choose light or dark mode. Dark mode is available only when dark mode is enabled in the project design system. info Before starting a run, make sure the selected group has at least one enabled test, the project has no blocking errors, the selected environment is valid, and no other Test Pilot run is active for the same project or group. ## Review Test Results[​](/testing/test-pilot.md#review-test-results "Direct link to Review Test Results") The **Run History** section shows the previous and active Test Pilot runs for the selected group. Click **View Results** to open the run details page. From there, you can review every test in the run and inspect the AI agent's explanation for each result. Each test result can include: * **View Test Playback**: Opens a step-by-step playback of the agent's interaction with the app. When you open Test Playback, you can use **Previous** and **Next** to move through each step, select a screenshot from the thumbnail strip, and review the agent's reasoning for that step. The playback side panel includes: * **Goal**: What the agent was trying to do in the selected step. * **Observation**: What the agent saw on the screen. * **Actions**: The action taken in that step and whether it succeeded. * **Config**: Displays the test run duration, screen size, and options to show or hide the device frame and overlay. It also includes a **View JSON** option to inspect the raw step data. * **Screenshots**: Captured screens from the run. Use these to quickly see what the agent saw at key moments. * **Actions Taken**: The actions the agent performed while executing the test, including clicks, text entry, key presses, completion steps, and whether each action succeeded. * **Open Snapshot**: Opens the build snapshot used for the run. ## Best Practices[​](/testing/test-pilot.md#best-practices "Direct link to Best Practices") * Keep each test focused on one user journey, such as login, checkout, or opening profile settings. * Start instructions with any setup the agent needs, such as waiting for the screen to load. * Use clear UI descriptions, such as "click the profile icon on the top left side" instead of "click the icon." * Add an expected outcome when the result matters, such as "the app should land on the home page." * Store separate parameter values for each environment. * Review screenshots, playback, and actions taken when a test fails, because the summary may point to a build, startup, UI, or instruction issue. * Turn on **Restart before test** when a test should start from a clean app state. Leave it off when tests are intentionally designed to run sequentially. ## Test Pilot Credits[​](/testing/test-pilot.md#test-pilot-credits "Direct link to Test Pilot Credits") Test Pilot uses Test Pilot credits. Each project gets **5 free credits**, which equals **5 single-test runs**. After the free credits are used, runs use credits from an assigned Test Pilot Credits Pass. A Test Pilot Credits Pass is a paid add-on. Passes start at **$5/month for 100 credits**. One credit represents one single test being run. Credit usage is based on the number of tests, devices, and brightness modes included in the run. For example, if a test run includes **3 tests**, runs on **2 devices**, and uses both **light and dark mode**, it uses `3 * 2 * 2 = 12` credits. Before you start a run, FlutterFlow shows how many credits the run will use. This lets you review the cost of running the selected test group before you click **Run Tests**. Pricing: * **USD**: `$5/month` per pass unit. Each unit grants `100` credits per reset cycle. You can buy multiple units. For example, `10` units cost `$50/month` and grants `1000` credits. * **INR**: The same pricing applies. * **Regional discounts**: None. * **Annual discount**: Approximately 25%. Pass types: * **Personal pass**: Purchased from Account billing and assignable to one personal project owned by that user. * **Team pass**: Purchased from Teams billing and assignable to one project belonging to the same team. Pass assignment is permanent. You cannot manually clear, update or transfer the pass while the project still exists. If the assigned project is deleted, the pass becomes unassigned. If a project changes owner or team scope and the assigned pass no longer matches that scope, the pass should be unassigned automatically. For example, if a team project with an assigned team pass is moved to become a personal project, the team pass will be unassigned. Deleting or canceling a purchased pass is a billing action. Like other subscriptions and add-ons, when you delete a Test Pilot pass, you can continue to use it until the end of your current billing cycle. ## FAQs[​](/testing/test-pilot.md#faqs "Direct link to FAQs") Why can't I run tests? Check that the project has available Test Pilot credits or free runs, at least one enabled test, editor access, no active Test Pilot run, a valid environment, and no blocking project errors. Why do I see 0 credits? This can happen when no Test Pilot Credits Pass is assigned to the project, the assigned pass has used all credits for the cycle, the assigned pass no longer matches the project scope, or credit status failed to load. Why can't I assign my pass? A pass may already be assigned, the project may already have a pass, the pass may not match the project scope, or you may not have the required billing permissions. Personal passes can only be assigned to matching personal projects, and team passes can only be assigned to matching team projects. Can support move my pass? Pass assignment is intended to be permanent. Contact support if you suspect a data issue, such as a stale assignment after a project was deleted or moved. How many free credits do projects get? Each project gets 5 free Test Pilot credits. Since 1 credit equals 1 single test run, this gives each project 5 free tests. After those credits are used, the project needs an assigned Test Pilot Credits Pass. Why doesn't my pass have a regional discount? Test Pilot Credits Passes do not use regional discounts. The billing page shows the price that applies to your account. Do I get free credits if I have an educational account? Educational accounts get 5 free tests per project, and are able to purchase Test Pilot passes for further credits. --- # API Charset and Encoding Fix Guide When working with API calls in FlutterFlow, you might encounter issues where the response returns with strange characters, incorrect formatting, or unreadable content. These problems are often caused by improper charset or encoding settings either in the API request or the server response. This guide shows you how to resolve such issues and ensure your API outputs are correctly displayed in your FlutterFlow project. Follow the steps below: 1. **Set Proper Request Headers** Make sure your API call includes the appropriate headers to instruct the server on how to format the response. Add the following headers to your API configuration: * `Content-Type: application/json` * `Charset: utf-8`​ These headers tell the server to return the data in JSON format using UTF-8 encoding, which is compatible with FlutterFlow. ![Setting Content-Type and Charset headers](/assets/images/20250430121409119593-f9abb5e3b9054634e35b7478b2c4ae30.png) 2. **Enable UTF-8 Decoding in FlutterFlow** If the server does not specify encoding—or if you're still getting corrupted text—you can configure FlutterFlow to decode the API response as UTF-8 manually. To do this: 1. Go to your API call setup in FlutterFlow. 2. Scroll to **Advanced Settings**. 3. Enable **Force response decoding as UTF-8**. This setting helps FlutterFlow correctly interpret the API response, especially from servers that don’t return standard headers. ![Force decode response as UTF-8](/assets/images/20250430121409391507-ffa5d343b7e960f80dc91eb8e3d9af2a.png) Final Tips * Always test your API calls in FlutterFlow’s API Test tab to ensure the response is properly formatted. * Confirm that the external API supports UTF-8 and returns a valid JSON response. * Review your server settings if you control the backend, to ensure it sends the correct headers. note Incorrect API call outputs due to charset or encoding can be quickly resolved by: * Adding proper headers like `Content-Type: application/json` and `Charset: utf-8`. * Enabling **Force response decoding as UTF-8** in FlutterFlow’s API advanced settings. These simple steps will help you get accurate and readable data from your APIs, resulting in a smoother app development experience. If you still face challenges, don't hesitate to reach out to our support team through Live chat or by emailing --- # Client-Server Errors During the API Call When calling an API in FlutterFlow, you may run into client-server errors. These typically come as status codes that indicate what went wrong, either on your end (the client) or on the server you're requesting data from. This guide will help you understand the most common API error codes and how to fix them. To learn more about APIs, check out our **[API documentation guide](/resources/backend-logic/rest-api.md)**. ## Common Client-Side Status Codes[​](/troubleshooting/api/client-server-errors-during-the-api-call.md#common-client-side-status-codes "Direct link to Common Client-Side Status Codes") These errors are usually caused by incorrect requests from the client side. * **400 – Bad Request** The 400 error is a generic response indicating that the server could not understand the request due to malformed syntax. Common causes include incorrect query parameters or missing fields in the request body. Ensure your request is correctly formatted and all required information is included. tip Check the API's own documentation to ensure you're including the correct fields and headers. ![400 Example](/assets/images/20250430121351345482-673e942c94c6b33263d5ed75e5c7833b.png) * **401 – Unauthorized** This status code appears when authentication has not yet been provided. To resolve this, ensure you have signed up for the API and included your API key in the HTTP header of your request. ![401 Example](/assets/images/20250430121350799148-811fa6f6d26ee9693c3520006c344d9a.png) * **403 – Forbidden** Receiving a 403 error means you're authenticated but do not have permission to access the requested resource. This could be due to using the wrong API key or attempting to access features not available in your subscription plan. ![403 Example](/assets/images/20250430121351077308-5530372a8a17f5b9c34c4a2cac813698.png) * **404 – Not Found** The 404 error indicates that the requested URL does not exist on the server. This could be due to a typo in the URL or changes in the API endpoints. Always verify the URL and check for any recent API updates. tip Always double-check your request URL before troubleshooting further. ![404 Example](/assets/images/20250430121350517804-696d4bb632fe7720f7e469260ce90792.png) * **407 – Proxy Authentication Required** You haven't authenticated with the proxy server. This is less common but can happen in restricted network environments. * **422 – Unprocessable Entity** Your request was well-formed but couldn’t be processed. For example, passing a `latlng` without a comma. * **429 – Too Many Requests** This error occurs when too many requests are sent in a short period, exceeding the API's rate limits. To avoid this, implement request throttling or review your API subscription plan to ensure it meets your needs. tip Check your API plan limits and consider throttling requests from your app. ## Common Server-Side Status Codes[​](/troubleshooting/api/client-server-errors-during-the-api-call.md#common-server-side-status-codes "Direct link to Common Server-Side Status Codes") These errors occur on the API server side. * **500 – Internal Server Error** A 500 error can occur for various reasons, often indicating that the API server has crashed. Check your request for accuracy and consult the API documentation for any known issues. * **501 – Not Implemented** This error occurs when the HTTP method used in the request is not supported by the server. Trying a different HTTP method or checking the API documentation for supported methods can resolve this issue. * **502 – Bad Gateway** This error means that the server, acting as a gateway or proxy, received an invalid response from the upstream server. It's usually a temporary issue that should be resolved by the API provider. * **503 – Service Unavailable** The 503 status code indicates that the server is temporarily unable to handle the request due to overload or maintenance. Waiting before sending another request is often the best approach. * **504 – Gateway Timeout** A 504 error suggests that the server, acting as a gateway, did not receive a timely response from the upstream server. This could be due to network latency or the API server processing the request too slowly. **Troubleshooting Steps** * **Clear Browser Cache and Cookies** If you're encountering a 400 Bad Request error, clearing your browser's cache and cookies can resolve issues related to expired or invalid data. * **Verify the Requested URL** Ensure the URL or endpoint is correct. Remember, domain names are case-sensitive. * **Adjust Request Parameters** For 400 errors, check if the file size is too large (for POST requests) or if there are any other incorrect parameters. * **Consult API Documentation** Always refer to the API's official documentation for specific requirements and troubleshooting tips. * **Contact API Support** If you continue to face issues, reaching out to the API's support team can provide further assistance and insights into resolving the problem. Understanding these common API error status codes and their solutions can significantly smooth the development process, ensuring more efficient and effective communication between your application and the APIs you rely on. Final tips * Always check the API's own documentation, inspect your request, and look up error messages. If the issue persists, contact the API provider. * Once you fix the issue, your calls should return a `200 OK`, which means everything is working as expected! --- # Securing Your API Keys in Private API Calls Ensuring the security of API keys is a critical aspect of building and maintaining a safe and reliable application. In the realm of private API calls, it's especially important to make sure your API keys are not exposed. This article aims to provide a best-practices guide on where to place your API keys to increase security in a FlutterFlow environment.​ **The Misconception: Private API Calls Secure Everything** Many users assume that simply marking an API call as 'private' is enough to protect all associated data. However, this is not the case. Private API calls run in a Cloud Function, which means any keys or sensitive data in the body will be secure, as long as they're not passed in from the frontend. Even in private API calls, if you're loading an API key from the frontend (like from Firebase remote configs), then you're still exposing it.​ ## Secure Placement of API Keys in Your Project[​](/troubleshooting/api/securing-your-api-keys-in-private-api-calls.md#secure-placement-of-api-keys-in-your-project "Direct link to Secure Placement of API Keys in Your Project") The ideal way to secure an API key is to include it in a request header or directly within the API endpoint URL. This ensures that it is never passed in from the client, thereby maintaining its confidentiality.​ For example, you can hard-code the key directly into your API call header like this:​ ``` { "Authorization": "Bearer YOUR_API_KEY_HERE" } ``` Or directly within the API endpoint URL:​ ``` https://api.example.com/resource?api_key=YOUR_API_KEY_HERE ``` The key should never be a variable that gets passed in from the frontend, as that would make it accessible via the client-side code, defeating the purpose of using private API calls for secure operations. ## Verifying the Security of Your API Key[​](/troubleshooting/api/securing-your-api-keys-in-private-api-calls.md#verifying-the-security-of-your-api-key "Direct link to Verifying the Security of Your API Key") After implementing these changes, a straightforward way to verify that your key is secured is by downloading your application code and checking to make sure the API key doesn’t appear in any frontend files.​ Example: Not Secure ![](/assets/images/20250430121157297846-558fb203f7daca2d47d53c7c2ee5fd77.png) Example: More Secure ![](/assets/images/20250430121157601185-d840701955c3ab46620abe83a9ff66df.png) By adhering to these best practices, you can increase the safety of your API keys even while making private API calls. info The goal is to keep all sensitive data, including API keys, away from the client side of the application to ensure optimal security. ​ --- # Custom Domain Connection Error If you encounter the error shown below after clicking **Connect**, follow these steps to resolve it: ![](/assets/images/20250430121243410633-49bbbf9e371e3a93e2d327ea974c0f87.png) Prerequisites * Access to your domain registrar or DNS provider dashboard. * DNS management permissions to add or modify DNS records. **Steps to Resolve the Error:** 1. **Verify DNS Records** * Ensure that you have correctly configured the DNS records required for your custom domain connection. * Add the keys provided by FlutterFlow to your domain’s DNS settings. note For A records, if your DNS provider requires a name, you can use `"@"`. When you see an empty value, it typically refers to `"@"`. ![](/assets/images/20250430121243684493-7be68a61e4a14bfdd10b65c006f2def2.png) 2. **Check for Conflicting Records** * Review your DNS configuration to ensure there are no extra or unnecessary records that conflict with the FlutterFlow-provided keys. * For example, if you already have an A record using `"@"`, remove it to avoid conflicts. note Before removing any existing DNS records, take screenshots and save them for reference. Below are examples of correct configurations in FlutterFlow and your DNS provider: ![](/assets/images/20250430121243982678-b87737146da4c860ffe03a1fb4672195.png) ![](/assets/images/20250430121244255037-0b78e57dc8fce0a8ff528ae03e8fd28b.png) By following these steps, you can ensure your custom domain is connected correctly. --- # Custom Domain Connection Issues This article provides solutions for common problems encountered when connecting custom domains. Prerequisites * Access to your domain registrar or DNS provider dashboard. * DNS management permissions to add or modify DNS records. * Familiarity with DNS record types (A, CNAME, CAA). **Steps to Resolve DNS Record Errors:** 1. **Verify DNS Records** * Use tools like **[nslookup.io](https://www.nslookup.io)** to verify that your DNS A and CNAME records match the configuration provided in FlutterFlow. * Ensure no conflicting A, AAAA, or CNAME records exist. ![](/assets/images/20250430121150651702-795c4d0a85619abadc9fcd060e3c2771.png) 2. **Allow Time for DNS Propagation** * DNS updates may take up to 24 hours. * Wait at least one hour after making changes before attempting to reconnect your domain. 3. **Retry Connection** * After verifying DNS settings and allowing propagation, attempt to reconnect your domain. 4. **Contact Registrar Support If Necessary** * If settings are correct and the issue persists after 48 hours, contact your domain registrar to confirm DNS configuration. **Handling Difficulty Creating DNS Records:** * Different registrars require different formats for DNS record names: * For root domains (e.g., `example.com`), some require an empty name, others `"@"`, or the full domain name. * For subdomains (e.g., `test.example.com`), some require just `"test"`, others `"test.example.com"`. * Consult your registrar’s documentation for exact instructions. **Resolving 404 Errors After Domain Connection:** * Publish the project again after connecting the domain. * This usually resolves most 404 errors related to domain connections. **Fixing DNS Restrictions for SSL Certificates:** 1. **Check for CAA Records** * Use **[nslookup.io](https://www.nslookup.io/domains/your-site-name/dns-records/caa/)** (replace `your-site-name` with your domain) to check CAA records. 2. **Adjust CAA Records** * Add `"letsencrypt.org"` to your allowed certificate authorities. * Remove any conflicting CAA records. note Once CAA records allow `"letsencrypt.org"`, FlutterFlow will be able to generate SSL certificates and complete the domain connection. If issues persist after following these steps, contact FlutterFlow support via Live Chat or email at . --- # Web Publishing FAQs This article provides answers to frequently asked questions related to web publishing. Prerequisites * Basic understanding of FlutterFlow and Flutter web projects. * Access to FlutterFlow exported web project files. * Familiarity with web hosting concepts. - **What certifications does FlutterFlow web hosting comply with?** FlutterFlow web hosting runs on Google Compute Engine. For detailed information about compliance and certifications, see **[Google Cloud Compliance](https://cloud.google.com/security/compliance)**. - **What are the system requirements for self-hosting a FlutterFlow web project?** FlutterFlow exports standard Flutter code. To compile and host Flutter web apps yourself, review the **[Flutter Web Deployment Guide](https://docs.flutter.dev/deployment/web)**. Compiled Flutter projects produce static files that can be hosted on most web servers without backend technology like Node.js or PHP. - **Do I need backend technologies to host my FlutterFlow web project?** No, compiled Flutter web projects are static content. You can host them on any server capable of serving static files with proper MIME types. - **What should I consider when hosting on a custom domain?** You need to configure DNS settings correctly and ensure SSL certificates are in place for HTTPS. See domain connection guides for more information. For further questions, contact FlutterFlow support via in-app messenger or email at --- # Codemagic Install Pods Failure During Codemagic deployment, errors may occur at the **Install Pods** step due to iOS dependency conflicts, unstable code branches, or pod version mismatches. This guide outlines steps to identify and resolve these issues effectively. Prerequisites * You are deploying an iOS app using Codemagic. * Your project includes custom code or third-party packages. ## Fix Dependency Conflicts from Custom Code[​](/troubleshooting/apple-store-deployment/codemagic-install-pods-failure.md#fix-dependency-conflicts-from-custom-code "Direct link to Fix Dependency Conflicts from Custom Code") Custom code or third-party packages may introduce conflicting versions of dependencies that prevent CocoaPods from resolving successfully. **Steps to Resolve Install Pods Failure:** * **Check for Dependency Conflicts from Custom Code**
Custom or third-party packages may cause version mismatches with FlutterFlow-supported dependencies. * Review documentation to ensure package compatibility. * Adjust versions in your `pubspec.yaml` file accordingly. * Run: ``` flutter pub get ``` ![](/assets/images/20250430121132533922-3b7325e726f04e089085f7f88e024042.png) * **Use a Stable GitHub Branch for Deployment**
Deploying from unstable branches can introduce unexpected errors during pod installation. * Ensure you're using a branch that passed previous Codemagic deployments. * Remove untested or experimental code. * Revert or refactor recent commits that might break dependencies. ![](/assets/images/20250430121132883140-a6f00f21f57ef2ada91b0b1126ba9db3.png) * **Fix Pod Version Compatibility Issues**
CocoaPods may fail to resolve dependencies due to incompatible versions or incorrect iOS deployment targets. * Update packages like `app_settings` in `pubspec.yaml` to versions compatible with your Flutter version. * Raise the iOS minimum deployment target in Xcode if necessary. ![](/assets/images/20250430121133219967-a817d6dd70abcb7fc46406c95445af11.png) Deployment Best Practices * Confirm dependency compatibility before pushing changes. * Always deploy from tested GitHub branches. * Verify your deployment target supports all pods used. --- # Codemagic Signing Certificate Limit During iOS deployment, Codemagic attempts to create distribution certificates in your Apple Developer Account. If the maximum number of certificates has already been reached, the build will fail with a certificate creation error. ## Error Message[​](/troubleshooting/apple-store-deployment/codemagic-signing-certificate-limit.md#error-message "Direct link to Error Message") ``` Build failed :|Step 3 script `Fetch signing files` exited with status code 1 Returned 409: There is a problem with the request entity - You already have a current Distribution certificate or a pending certificate request. ``` This message indicates that Codemagic cannot proceed because no additional distribution certificates can be created. Prerequisites * You are deploying an iOS app using Codemagic. * Your Apple Developer Program account is active and linked. **Steps to Resolve Certificate Limit Error:** 1. **Access Your Apple Developer Account**
Log into your Apple Developer account to manage certificates: * Go to the **[Apple Developer Certificates List](https://developer.apple.com/account/resources/certificates/list)**. 2. **Navigate to the Certificates Section**
In the **Certificates, Identifiers & Profiles** section: * Click on **Certificates**. * Locate all existing **Distribution Certificates**. 3. **Remove Unused or Expired Certificates**
Review and delete any unused, expired, or redundant distribution certificates to free up space. 4. **Re-run Deployment**
After deleting the certificates, initiate the build process again in FlutterFlow. Codemagic will automatically generate a new certificate as needed. note The deleted distribution certificates will be recreated automatically by Codemagic during the next build. --- # Download dSYM File from App Store Connect To download the dSYM file from the App Store Connect Developer Console, follow these steps. Prerequisites * Access to your Apple Developer account. * Your app has at least one build uploaded to App Store Connect. **Steps to Download the dSYM File:** 1. **Sign in** to **[App Store Connect](https://appstoreconnect.apple.com/)** with your Apple Developer account. 2. Open your app. 3. Select a build from the **TestFlight** tab on your project page. 4. Open the **Build Metadata** tab. 5. Under **Include Symbols**, download the dSYM file. ![](/assets/images/20250430121257965718-1878d33e7c1b9d3378179fd47de1e14c.png) note The dSYM file is only available for builds that have been successfully uploaded to App Store Connect and are in a "processing" or "ready for submission" state. If the **Download dSYM file** link is not visible, it indicates that the build submission did not complete successfully. In this case: 1. Redeploy the build to the App Store. 2. After successful processing, return to the **Build Metadata** tab and download the dSYM file. ![](/assets/images/20250430121258232331-c39d5ac905c1a85d35b4487febae227c.png) --- # ImageNotification Development Team Error This error occurs when the **ImageNotification** entitlement is missing in your Apple Developer account. To resolve it, create a new Identifier for `ImageNotification` in your Apple Developer account. Prerequisites * Access to your **Apple Developer account**. * Permission to manage **Certificates, Identifiers & Profiles**. **Steps to Create the Identifier:** 1. Sign in to your **[Apple Developer account](https://developer.apple.com/)**. 2. Navigate to **Certificates, Identifiers & Profiles**. 3. Select **Identifiers**. 4. Click the **Add (+)** button. 5. Choose **App IDs** and click **Continue**. 6. Under **Type**, select **App** and click **Continue**. 7. In the **Description** field, enter `ImageNotification` (case-sensitive). 8. In the **Bundle ID** field, enter your full bundle ID followed by `.ImageNotification` (for example: `com.example.app.ImageNotification`). 9. Click **Continue** and then **Register** to complete the setup. Once this Identifier is added, the signing process should proceed without requiring a development team selection. --- # iOS Deployment Authentication Error During iOS deployment using Codemagic, an authentication credentials error can occur due to misconfigured or expired API tokens for App Store deployment. The API token used for App Store Connect may be invalid or expired. info For details on generating valid tokens, see the **[Apple API Token Documentation](https://developer.apple.com/go/?id=api-generating-tokens)**. Here is the error message: ``` Failed Step: Fetch signing files GET https://api.appstoreconnect.apple.com/v1/bundleIds?limit=100&sort=name&filter%5Bidentifier%5D=appname.com&filter%5Bplatform%5D=IOS returned 401: Authentication credentials are missing or invalid. Provide a properly configured and signed bearer token, and make sure that it has not expired. Learn more about Generating Tokens for API Requests https://developer.apple.com/go/?id=api-generating-tokens ``` Prerequisites * Access to your Apple Developer App Store Connect account. * Permission to manage API keys under **Users and Access**. **Steps to Resolve the Authentication Error:** 1. Open **App Store Connect** and navigate to **Users and Access → Keys**. 2. If prompted, click **Request Access**. 3. Select **Generate API Key** or click the **Add (+)** button. 4. In the popup, provide the following details: * **Name**: Enter a descriptive name for the API Key. * **Access**: Choose the appropriate access level for the key. 5. Click **Generate** to create the API Key. 6. Download the newly created API Key by selecting **Download API Key**. note If the download option does not appear immediately, refresh the page. 7. In **FlutterFlow**, go to **Settings & Integrations → Deployment**. 8. Under **Private Key**, click **Upload Private Key**, select the downloaded API Key file, and click **Open**. 9. Retry your iOS deployment. ![](/assets/images/20250430121336383410-4e20c9a876b7745e7dc870012bb098c6.gif) note If the error persists after completing these steps, contact FlutterFlow support via in-app messenger or email at . --- # App Starts from HomePage in Run Mode If your app always redirects to the **HomePage** in **Run Mode**, even after a previous login, it's likely caused by **retained authentication state** or **cached session data** in your browser. ## Troubleshooting Steps[​](/troubleshooting/authentication/app-starts-from-homepage-in-run-mode.md#troubleshooting-steps "Direct link to Troubleshooting Steps") * Clear your browser cache and history. ![How to clear browser cache](/assets/images/20250430121300291232-3cadce68e528afe990e06d47b2558512.png) * Try a different browser or use incognito/private browsing mode to see if the issue persists. If the problem continues, consider checking your authentication flow and session management in your app settings. Reset Authentication State in Run Mode When using **Run Mode**, FlutterFlow preserves your **authentication state** across sessions. To test your app from a clean state, add a **"Log Out"** button on your HomePage that triggers the `Sign Out` action. This ensures the app starts from the login screen during your next test. --- # Check Firebase Login Method Understanding which authentication method a user has used can be useful for several reasons. For example, it can be leveraged for analytics, user support, and to customize the user's experience based on their login method. This method, however, is specific to Firebase Authentication.​ In our Flutter app, we can find out which method a user used to authenticate by leveraging Firebase's `User.providerData` property. Let's take a closer look at how this works in the code: ``` import 'package:firebase_auth/firebase_auth.dart'; String getUserSignInMethod() { final user = FirebaseAuth.instance.currentUser; String signInMethod; for (var info in user!.providerData) { signInMethod = info.providerId; } return signInMethod; } ``` Here's a breakdown of the code: * We first import the [Firebase Auth](https://pub.dev/packages/firebase_auth) package which gives us access to Firebase's authentication methods. * Next, we define a function `getUserSignInMethod`. This function will return a string indicating the sign-in method the user used. * Inside the function, we obtain the current user from FirebaseAuth using `FirebaseAuth.instance.currentUser`. * We then declare a string `signInMethod` that will store the name of the provider used for sign-in. * `user.providerData` is an iterable that provides UserInfo for each sign-in method used by the user. We loop over this iterable using a `for` loop. * In each iteration, we assign the `providerId` to our `signInMethod` string. The `providerId` can be 'google.com' for Google, 'facebook.com' for Facebook, and 'password' for email and password. * After the loop is done, the function returns `signInMethod` string which indicates the sign-in method the user used. * The function `getUserSignInMethod()` returns a String value which corresponds to the providerId of the user's sign-in method. Here are examples of how the return value might look like: * If the user has signed in using Google, the function will return: **`'google.com'`** * If the user has signed in using Facebook, the function will return: **`'facebook.com'`** * If the user has signed in using Email and Password, the function will return: **`'password'`** These are the identifiers used by Firebase to represent different sign-in methods. Please thoroughly test this function to ensure it fits your specific requirements Use Sign-In Method to Drive Dynamic UI in FlutterFlow In FlutterFlow, if you want to display or use the user's sign-in method in your UI logic (example, showing different UIs for Google vs. email login), you can create a custom function using the `providerId` approach shown in the article and **connect it to a custom action**. This allows you to make dynamic decisions inside your app based on how the user authenticated. Remember to return the result from the custom function and store it in an App State variable for easy access throughout your app. --- # Deleting Firebase Users and Related Data ![](/assets/images/20250430121300815719-93473f2543e01b1e4fb75bce9e5dd145.png "Screenshot showing the delete user action") ## Understanding the Delete Action[​](/troubleshooting/authentication/deleting-firebase-users-and-related-data.md#understanding-the-delete-action "Direct link to Understanding the Delete Action") The delete action in Firebase is designed to remove the user from the authentication table only. This means the user's document in the database will not be affected. If you want to delete the user's document from the database as well, you'll need to create a custom action with some custom code. ### Logging Out After Deletion[​](/troubleshooting/authentication/deleting-firebase-users-and-related-data.md#logging-out-after-deletion "Direct link to Logging Out After Deletion") After completing the delete action, it is important to log out the user. Since the user no longer exists in the authentication system, logging out ensures the app routes the user back to the login page, which is typically the initial page of your project. ## Steps for Proper User Deletion[​](/troubleshooting/authentication/deleting-firebase-users-and-related-data.md#steps-for-proper-user-deletion "Direct link to Steps for Proper User Deletion") 1. **Delete related data first:**
Before calling the delete user action, delete any related data such as Firestore documents or Storage files associated with the user. Once the user is deleted from Firebase Auth, their UID will no longer be accessible in the app session, making it difficult to reference their data afterward. 2. **Handle re-login behavior:**
Keep in mind that if the same user signs in again using the same signup method, Firebase will create a new document in the database for them. This happens because Firebase links the new login information to the old user document. Important Tips for Deleting Users * Always delete associated user data from Firestore or Storage **before** deleting the user from Firebase Auth. This prevents orphaned data and issues with data referencing. * Remember that after deletion, the user will need to be logged out to avoid session errors. * If the user signs in again with the same signup method, Firebase creates a new document for them, reconnecting the new login to the old user document. ![](/assets/images/20250430121301101693-de232395e419de0c7398095f4c145d23.png "Screenshot illustrating user deletion flow") note The delete user action in FlutterFlow performs the same operation as manually deleting a user from the Firebase Authentication table. --- # Fix Google Sign-In Issues If Google Sign-In isn’t working after exporting your FlutterFlow app, follow these steps based on how you’re deploying your app. 1. **If Deployed to the Play Store via CodeMagic** If you published your app to the Play Store using FlutterFlow's CodeMagic integration: * In the **Google Play Console**, open your app from the **All apps** list. * Go to **Setup → App Integrity**. * Under the **App Signing** tab, copy the **SHA-1 certificate fingerprint**. ![](/assets/images/20250430121440426479-9f2c4340a46f05d29580a4763c4ba7f3.png) * In the **Firebase console**, open the same project, scroll to **Your Apps**, and select your Android app. * Click **Add fingerprint**, paste the SHA-1, then click **Save**. ![](/assets/images/20250430121441325585-013b7c6532c00e98fee3f90c4d1f4fbd.png) * In FlutterFlow, go to **Settings → Firebase** and click: * **Regenerate Config Files** * **Generate Files** ![](/assets/images/20250430121442125737-992a83c99d1aaac98c6db7838fa1782e.png) Re-test your app. Google Sign-In should now work correctly. 2. **If Not Yet Published or Using Manual Signing** If you’re not using Play Store App Signing: * Use **Keytool** or **Gradle's Signing Report** to generate your SHA-1. * In **Firebase**, open your project settings. * Under **Your Apps**, select the Android app and add the SHA-1 fingerprint. ![](/assets/images/20250430121442863891-013b7c6532c00e98fee3f90c4d1f4fbd.png) * In FlutterFlow, go to **Settings → Firebase**, then: * **Regenerate Config Files** * **Generate Files** ![](/assets/images/20250430121443525154-992a83c99d1aaac98c6db7838fa1782e.png) Test the app again to confirm Google Sign-In works. *Refer to the [Google Play Services documentation](https://developers.google.com/android/guides/overview) for more information.* Add Debug SHA-1 for Local Testing * When testing Google Sign-In in FlutterFlow before publishing, add your **debug SHA-1** in Firebase. * Then go to `Settings → Firebase` in FlutterFlow and regenerate your config files. --- # Permission Denied: Code 403 This error typically occurs when your application or service account does not have the required permissions to access a resource in Google Cloud or Firebase. ## Code 403 Error Message[​](/troubleshooting/authentication/permission-denied-code-403.md#code-403-error-message "Direct link to Code 403 Error Message") You may encounter this error due to one or more of the following reasons: * **Invalid or misconfigured service account JSON file** * **Insufficient permissions** assigned to the service account * **Missing or incorrect IAM roles** for the service account * **API not enabled** in the Google Cloud project Do the following to fix this error: * **Check Your Service Account JSON File** Ensure you are using the correct `service-account.json` file and that it is not corrupted or expired. * **Verify IAM Roles and Permissions** Make sure the service account has the necessary roles like `Editor`, `Owner`, or other specific roles required for your use case. * **Enable Required APIs** Go to the [Google Cloud Console](https://console.cloud.google.com/apis/library) and ensure all necessary APIs are enabled for your project. * **Regenerate the Service Account Key if Needed** If you suspect the key is invalid, generate a new one and update your application configuration accordingly. Always Use Least Privilege Principle When assigning IAM roles to your service account, follow the **principle of least privilege**—only grant the minimum permissions necessary for the task. This not only reduces the risk of misconfiguration but also enhances the overall security posture of your app. If you continue to experience issues, consult the [Google Cloud IAM documentation](https://cloud.google.com/iam/docs/troubleshooting-access) or contact [FlutterFlow Support](mailto:support@flutterflow.io) for further assistance. --- # SafetyNet Phone Sign-In Issue on Android Devices If you're experiencing issues with Firebase Phone Authentication on Android devices, especially when using emulators or testing in release mode, this guide will help you identify and resolve common problems. Firebase uses either **SafetyNet** or **reCAPTCHA** to verify that phone number sign-in requests originate from your app. Issues typically arise when one of these verification methods is not correctly configured. ## Troubleshooting Checklist[​](/troubleshooting/authentication/safetynet-phone-sign-in-issue-on-android-devices.md#troubleshooting-checklist "Direct link to Troubleshooting Checklist") Ensure the following configurations are in place: * **Firebase Setup** * Your project is correctly set up in the [Firebase Console](https://console.firebase.google.com/). * Firebase Authentication is enabled. * The Phone Sign-In method is activated. * **Phone Authentication Flow** * Prompt the user to enter their phone number. * Send a verification code to the user's phone. * Accept and verify the code entered by the user. * **SafetyNet / reCAPTCHA Configuration** * Your app includes the required Firebase and Play Services dependencies. * SHA-1 and SHA-256 fingerprints are added to your Firebase project settings. * Your API key is either unrestricted or allowlisted. * **Testing Environment** * If you're using an emulator, test on a physical device instead. Emulators may bypass or fail certain integrity checks. ## Firebase Verification Methods[​](/troubleshooting/authentication/safetynet-phone-sign-in-issue-on-android-devices.md#firebase-verification-methods "Direct link to Firebase Verification Methods") Firebase uses one of the following methods to confirm the authenticity of phone sign-in requests: 1. **SafetyNet (Deprecated)** If the device supports Google Play Services, Firebase uses **SafetyNet Attestation** to confirm the device’s legitimacy. Deprecated API The SafetyNet Attestation API is deprecated and has been replaced by the [Play Integrity API](https://developer.android.com/google/play/integrity). After **January 31, 2023**, you can no longer enable the SafetyNet API for new projects in the Google Cloud Console. To use SafetyNet (if still active for your project): * Enable **Android Device Verification (Deprecated)** in the [Google Cloud Console](https://console.cloud.google.com/). * Ensure your app's **SHA-256** is added in the Firebase Console under **Project Settings > General > Your Apps**. * Use the default Firebase API key or request onboarding for SafetyNet if needed. * Monitor your quota [here](https://developer.android.com/google/play/safetynet/quotas). ![](/assets/images/20250430121259958091-c6869a1ed27cc114df371bef037c90f6.png) 2. **reCAPTCHA Verification** If SafetyNet is unavailable (e.g. device without Google Play Services or running on an emulator), Firebase falls back to **reCAPTCHA verification**.The reCAPTCHA challenge usually completes without user interaction. This flow requires: * A valid **SHA-1** fingerprint added to your Firebase project. * An **unrestricted** or **domain-allowlisted** API key (e.g. `your-project-name.firebaseapp.com`). * Ensure both SafetyNet and reCAPTCHA flows are working to support a wider range of Android devices. Release Mode Configuration When releasing your app to the Google Play Store, ensure you include the **SHA-1** and **SHA-256** keys from your **Play Console**. Here is how to do that: * Navigate to **Play Console → Your App → Release → Setup → App Signing** * Then copy both **SHA-1** and **SHA-256** fingerprints and add them to Firebase Console under **Project Settings > General > Your Apps**. ![](/assets/images/20250430121300291238-1e1512108bd55771c4c7bc2f003507bd.png) Learn more * [Firebase Phone Authentication (FlutterFire)](https://firebase.flutter.dev/docs/auth/phone/) * [Using Firebase Auth in FlutterFlow](https://docs.flutterflow.io/authentication) * [Play Integrity API Migration](https://developer.android.com/google/play/integrity) Still stuck? Check Firebase logs, test on a physical device, and ensure your API keys and fingerprints are correctly added. Proper configuration of SafetyNet or reCAPTCHA is critical to ensuring phone number sign-in works reliably across devices. --- # Sign in With Apple (for Web) To enable **Sign in with Apple** on the web, you must complete additional steps in both your **Apple Developer Account** and **Firebase Console**. These steps allow Apple to identify your website and authorize the use of Apple login on web platforms. warning The **Sign in with Apple (Web)** functionality cannot be tested in Test/Run Mode. You must **deploy** your app to a live domain before testing. Take the following steps to set up Sign in with App (for Web): 1. **Configure Apple Developer Account** Follow these steps in your [Apple Developer Account](https://developer.apple.com/account/): 1. **Register a New Identifier** * Select **App IDs** and fill in the required details. * Enable the **Sign in with Apple** capability. 2. **Create a New Service ID** * Provide a name and a unique identifier. * This will be used as the **Service ID** in Firebase. 3. **Configure Sign in With Apple** * Add your domain and return URL (from Firebase). * Save the configuration. 4. **Create a New Key** * Enable **Sign in with Apple**. * Download the generated private key (`.p8` file). 2. **Set Up in Firebase Console** After downloading the private key, configure your Firebase app by doing the following: 1. Go to **Authentication → Sign-in method → Apple**. 2. Enter the following details: * **Apple Team ID** * **Key ID** * **Private Key** (from the `.p8` file) 3. Set the **Service ID** to match the one created in the Apple Developer account. Once these steps are completed, your **Sign in with Apple (Web)** setup should be active. Still Not Working? If the sign-in process fails after completing these steps, please contact [FlutterFlow Support](mailto:support@flutterflow.io) via Chat or Email. Helpful Resources * [Apple Developer - Sign in with Apple](https://developer.apple.com/sign-in-with-apple/) * [Firebase Authentication - Apple Provider](https://firebase.google.com/docs/auth/web/apple) * [FlutterFlow Authentication Docs](https://docs.flutterflow.io/authentication) --- # Troubleshooting Custom Authentication Prerequisites * Ensure you have a **custom server** with login and sign-up endpoints that return a JWT token upon success. * **Custom authentication** must be enabled in FlutterFlow, with entry and logged-in pages correctly set. Here's an example: ![](/assets/images/20250430121149388590-eb05f8e20642cd740ae49b917fb2a8ab.png) ## How to Fix Custom Authentication Issues[​](/troubleshooting/authentication/troubleshooting-authentication.md#how-to-fix-custom-authentication-issues "Direct link to How to Fix Custom Authentication Issues") 1. **Verify Server and API Endpoints** * Confirm that your server correctly returns JWT tokens for login and sign-up requests. The server's response should include the **authentication token**, **refresh token**, **expiration time**, and **user ID (UID)**. * Double-check the API endpoint configurations in FlutterFlow to ensure they match your server’s requirements. 2. **FlutterFlow Configuration** * Make sure **Custom Authentication** is enabled in your project settings. * Verify that the **Entry Page** and **Logged In Page** are correctly set. 3. **UI Configuration** * Ensure your app includes the essential pages for the authentication flow: **Login**, **Sign Up**, and **Home Page** (the page shown when a user is authenticated). 4. **API Integration and Authentication Flow** * Test API calls from FlutterFlow to your custom server to confirm responses are working as expected. * Use the **Backend Call** action to trigger login/signup, then handle the **Custom Login** action using the response data. 5. **Handling Tokens and User Data** * Parse the API response properly to extract and store: * `auth token` * `refresh token` * `expiration time` * `user ID (UID)` * Store these values in local state or secure app storage. ![](/assets/images/20250430121149749937-2f080ea6b813107d114451f2edd8ffa4.png) 6. **Navigation** * If navigation does not occur automatically after login/signup: * Disable automatic navigation. * Use a **manual navigation** action to route users to the appropriate page. General Tips * Test your flow with **dummy credentials** before using real user data. This helps debug token handling, API responses, and navigation. * Add **logging** on both the server and in FlutterFlow (example, using snack bars or alerts) to monitor each step of the flow. * Verify the full flow—from login to protected pages—to ensure everything works as expected. More Resources * [FlutterFlow Custom Authentication Video](https://www.youtube.com/watch?v=hnX3CvBtGvI) * **Sample project:** [Custom Auth Checklist](https://app.flutterflow.io/project/custom-auth-checklist-fdjkno) * [FlutterFlow Custom Authentication Documentation](https://docs.flutterflow.io/data-and-backend/custom-authentication) --- # ListView Gray Box and Red Screen Errors When loading a list of items from the database, you might encounter a gray box or red error screen. This article explains the possible causes and how to resolve them. Prerequisites * Ensure your query is correctly connected to a Firestore collection or CMS. * Confirm that your app builds and runs correctly in **Run** and **Test** modes. **Understanding the Error:** A **gray box** usually indicates that the backend query failed to return results. A **red screen** in Test mode suggests a runtime error caused by invalid data or query failure. **Step-by-Step Troubleshooting:** 1. **Verify Query Results** * If the query is successful and returns items, the list will populate as expected. * If there are no records matching the query, you will see the **empty state** you configured. * If the query fails, a gray box (in Run mode) or a red error screen (in Test mode) will appear. ![Empty State](/assets/images/20250430121239249713-c31c0a76d2143af7fdcaeccc648d63b0.png) tip Always configure an empty state for lists. This helps distinguish between a failed query and an empty dataset. 2. **Behavior by Mode** * **Run mode**: Displays a gray box when the query fails. * **Test mode**: Shows a red screen with a specific error message. **Example: Working Query with No Results**
![Working Query](/assets/images/20250430121239492027-aa83575a453036ca7dec97a5c5d07c0f.png) **Example: Failed Query**
![Failed Query](/assets/images/20250430121239708989-9f07c45b1dd9e9906e2fe6f44ce93fa5.png) 3. **Check for Null Values in the Data** Null values in critical fields may cause queries or widgets to fail. Here is how to check for null values: 1. Inspect your data in **Firebase** or **CMS** for any fields with `null` values. 2. Pay attention to fields used in filters, formatting, or conditional visibility. 3. For example, if `created_time` is null and you are formatting a date from this field, the query may fail. **Example: Null Field Causing Error** ![Null Field Example](/assets/images/20250430121240227391-6af12dfe0048a0ac5e35266444292525.png)
![Date Formatting Error](/assets/images/20250430121240508011-5f32e3b00fe8ffad5396024a01edb6e4.png) note Use **visibility rules** to hide widgets that depend on potentially null values. 4. **Handle Document-From-Reference Queries Safely** If you use document references inside a list item widget, and the reference is null or missing, it will break the query. ![Broken Reference Example](/assets/images/20250430121240818334-9d09c42d5d3bc820c9cbae3b3bdb69d9.png) note Always add a visibility rule to any widget performing document-from-reference queries. This ensures the widget is only visible when the reference is valid. Summary * A **gray box** means the backend query failed. * A **red screen** indicates a runtime error in **Test mode**. * **Null values** in your database are a common cause of failure. * Always configure **empty states** and apply **visibility rules** to handle null or missing data gracefully. --- # Fix ListView Only Returning One Item If your **ListView** is only showing one item, this guide will walk you through the common reasons and how to resolve the issue. Prerequisites * A working Firebase or CMS integration. * A dynamic layout widget such as `ListView`, `GridView`, or `Column`. * At least two documents in your Firestore collection for testing. Follow the steps below to resolve the issue: 1. **Use a Dynamic Widget**
Make sure you're using a widget like `ListView`, `GridView`, or `Column` that supports dynamic content. 2. **Confirm the Query Type**
Ensure the query is set to return a **list of documents**, not a single document. 3. **Review Applied Filters**
If you are using filters, check that multiple records in your database satisfy those filter conditions. 4. **Check Firestore Data**
Open your Firestore collection and verify that it contains **multiple records**. 5. **Verify List Type Fields**
If querying a single field, confirm it's defined as a **List** in both Firebase and FlutterFlow. tip To test your setup, remove all filters temporarily and use a basic list query. This helps isolate whether the issue is with filtering or the query type. --- # Resolving Firebase Configuration Issues If you're experiencing backend errors, failed schema validation, or data sync issues, this guide will help you verify and fix your Firebase setup in FlutterFlow. Prerequisites * You must have already connected your Firebase project to FlutterFlow. * You should have access to your Firebase console with admin rights. Follow the steps below to fix firebase configuration: 1. **Grant Required Permissions** Assign the following permissions to `firebase@flutterflow.io` in your Firebase project: * Editor * Cloud Functions Admin * Service Account User Learn how to **[assign Firebase permissions](/integrations/firebase/connect-to-firebase.md#allow-flutterflow-to-access-your-project)**. 2. **Update Firestore Rules** Update your Firestore security rules to allow access for FlutterFlow. After making changes: * Remove `firebase@flutterflow.io` from your authenticated users. * Redeploy your Firestore rules. * Validate your schema again. ![](/assets/images/20250430121532523511-793bd6baac529fb45002bc73df1636a0.png) 3. **Match Field Types and Names** Check that data field types and names match between Firestore and FlutterFlow exactly. Mismatches will cause query errors. 4. **Validate Firestore Schema in FlutterFlow** Use the **Validate** button under **Firestore → Settings** in FlutterFlow to confirm that your collection schema matches your Firestore structure. ![](/assets/images/20250430121532793176-81306c33ba320ea521aa4a3b4a8d6803.png) 5. **Reset Firebase Setup (If Needed)** If issues persist after following the steps above: * Revoke the current setup. * Reconnect your Firebase project using the **[Firebase setup instructions](/integrations/firebase/connect-to-firebase.md)**. 6. **Add Authorized Domains** In the Firebase console, go to **Authentication → Sign-in Method → Authorized Domains** and add: `app.flutterflow.io` 7. **Refresh FlutterFlow** Make sure you're using the latest version of the platform: * Press `Ctrl`/`Cmd + Shift + R` * Clear your browser cache * Log out and back in to FlutterFlow 8. **Upgrade to Blaze Plan (If Using Cloud Functions)** Cloud Functions such as Push Notifications and Payments require a billing-enabled Firebase project. Make sure you’re on the **Blaze Plan**. tip After updating Firestore rules, always validate the schema using the **Validate** button before proceeding with other fixes. --- # Update Document Action Fails During Backend Call When performing the **Update Document** action, you may encounter a situation where the loading indicator appears but then stops without completing the action. This indicates that the update was unsuccessful. If the update succeeds, the next steps in your action flow, such as displaying an alert dialog, should execute automatically. ![](/assets/images/20250430121241690449-72728d8ac64b57f905a10b2867d628dc.gif) ![](/assets/images/20250430121241899370-d79442f878599856692251154e0ddb36.png) note After performing the update action, always verify that the data has been correctly updated in your database. If your document is not streamed in real-time within your app, the updated data may not immediately appear. Check the data in FlutterFlow CMS or directly in Firebase to confirm the update. **Causes of Document Update Failures:** When the update action fails, the action flow stops, preventing any subsequent actions from executing. There are two common reasons why the update action may fail: * **Permission Issue in Firestore** The user may not have the necessary permission to write to the document. ![](/assets/images/20250430121242149430-fd479a11417ff877bd79c30bd2bb66d2.png) **Cause:**
The Firestore security rules may not allow the current user to write (edit) documents. **Solution:**
Review and configure your Firestore rules to grant write permission. For example, allowing write access to authenticated users is often sufficient if your app requires user authentication. * **Data Type Mismatch** The values you are attempting to write may not match the expected field types. For example, assigning a string value to a field that expects an integer will result in failure. ![](/assets/images/20250430121242530889-edd1b26adbf2601edf31c23cf3647e94.png) **Cause:**
Attempting to write a value of the wrong type, such as assigning text to a number field. **Solution:**
Verify that the values being written match the expected data types for each field. If the data comes from an API call or form input, consider using custom actions to convert the value to the appropriate type before performing the update. note If you want to save a text field value as a number, ensure that the text field input type is set to **Number**. Additional Troubleshooting You can check for error details in your browser's developer console (F12). For example, permission errors will typically appear in the console logs, as shown below: ![](/assets/images/20250430121242814005-79f185001a0291f523c966be512c7557.png) --- # Fix Cloud Functions Deployment Prerequisites * You must have a Firebase project connected to FlutterFlow. * Ensure your project is on the Blaze Plan. Cloud Functions allow you to execute backend code in response to events triggered by Firebase features or HTTPS requests. Various situations might cause Cloud Functions to malfunction, often stemming from setup problems or coding mistakes within the Cloud Function's script. This article guides you through common challenges with Cloud Functions in FlutterFlow and how to resolve them. **Errors Shown in FlutterFlow Builder** You may encounter the following errors in the FlutterFlow Builder: * `Out of Date (Error)` * `Not Deployed (Error)` These errors can arise from various situations. Below are screenshots of these errors: **Out of Date Error** ![](/assets/images/20250430121126719355-82d03fcbe771f63c6224d5397506917a.png) **Not Deployed Error** ![](/assets/images/20250430121126936614-d515543411b3756f0ff1a0e03156dfb0.png) ## Key Checks for Resolving Deployment Errors[​](/troubleshooting/cloud-functions/fix-cloud-functions-deployment.md#key-checks-for-resolving-deployment-errors "Direct link to Key Checks for Resolving Deployment Errors") 1. **Verify Has Necessary Permissions** To ensure FlutterFlow works smoothly with your project, ensure that `firebase@flutterflow.io` has the following permissions in your Firebase project: * Cloud Functions Admin * Editor * Service Account User Follow the steps below to add these permissions: * Go to the Firebase Console and log into your account. * Open your project and go to **Project Settings > Users and Permissions**. * Under **Advanced Settings Permissions**, locate `firebase@flutterflow.io`, click **Edit**, and add the required roles. ![](/assets/images/20250430121127218829-addc39229a055565157b00d29fa41251.png) ![](/assets/images/20250430121127501343-5f6dbb7491d1ddb5d008eb09a0d39245.png) 2. **Check for Function Name Mismatch** Ensure the function name in your code exactly matches the function name defined in FlutterFlow. For example, in this case, FlutterFlow expects `logoMaker`, but the code incorrectly uses `data`. ![](/assets/images/20250430121133833159-5303c0c6008b98cba4972c4d28150935.png) 3. **Validate Custom Code for Cloud Functions** Small mistakes in your custom Cloud Functions code can prevent deployment. * Double-check your code for errors. * Test locally using an IDE or Firebase CLI. ![](/assets/images/20250430121127844921-2a56c94139ae832cc5f01f809a6a424c.png) 4. **Verify Firebase Billing Plan (Blaze Plan Required):** * Ensure your Firebase project is on the **Blaze Plan**, not Spark Plan. * Check billing status on GCP. Even if Firebase shows Blaze, GCP billing issues may still block deployments. 5. **Check if Other Cloud Functions Are Deploying:** * If some Cloud Functions (like Push Notification or Stripe) are deploying successfully, it indicates your Firebase setup is mostly correct. * Focus on inspecting your specific function code and configuration. 6. **Ensure Region Selection Matches Firebase Project:** * The region set for your Cloud Function in FlutterFlow should match your Firebase project's region. * Do not leave the region as `[default]`. ![](/assets/images/20250430121128170242-7e143cda4b0438bc0763b049bb4e6ba1.png) ![](/assets/images/20250430121128453683-c8ce143b6022ca86729958388bf01ca9.png) tip If you previously deployed functions in the wrong region, delete them, set the correct region, and re-deploy. 7. **Protocol Conflicts: HTTP vs Callable Functions** If you initially deployed a function as HTTP and later try to redeploy it as Callable (or vice versa), you'll get this error: `[makeUserAdmin(us-central1)] Changing from an HTTPS function to a callable function is not allowed. Please delete your function and create a new one instead.` Follow the steps below to fix this error: * Delete the existing function in Firebase Console. * Modify the protocol type in FlutterFlow. * Redeploy the function. 8. **Verify `package.json` Integrity** * Use the generated `package.json` file as-is unless you need to add extra packages. * Ensure it’s not blank and doesn’t contain invalid characters. **Recommended structure:** ``` { "name": "functions", "description": "Firebase Custom Cloud Functions", "engines": { "node": "18" }, "main": "index.js", "dependencies": { "firebase-admin": "^11.8.0", "firebase-functions": "^4.3.1" }, "private": true } ``` 9. **Ensure Packages Are Included in `package.json`** If you are using third-party packages (e.g., `axios`), make sure they are properly added to the `dependencies` section in `package.json`: ![](/assets/images/20250430121128741407-9e4e60bdb55bc4f475e16ffc418576dd.png) 10. **Validate Third-Party Package Versions** The versions specified in your `package.json` should match available versions listed on **[npmjs.com](https://www.npmjs.com/package/axios?activeTab=versions)**. ![](/assets/images/20250430121129014430-550f4af2873fbfeab570f5289e3f4cc0.png) 11. **Check for Undeployed Firebase Rules and Indexes:** * Incomplete Firestore rules or indexes can block function deployment. * Make sure all rules and indexes have been deployed from FlutterFlow. **Additional Troubleshooting and Optimization:** * **Trigger Configuration Issues** If your Cloud Functions are not being triggered: **Review Event Triggers:** * For Firestore triggers: verify document paths and collection names. * For HTTP functions: ensure correct setup in FlutterFlow. **Check Permissions and Rules:** * Firebase security rules and project permissions must allow the Cloud Function operations. * **Execution Timeouts** * Cloud Functions may fail if execution time exceeds limits. * Set a custom timeout duration in FlutterFlow: ![](/assets/images/20250430121134186956-dd7a3ea01e0cbbd3b9e85a7913ce88f4.png) For longer processing tasks, increase the timeout duration in your Cloud Function configuration. Configuring Cloud Function regions in FlutterFlow can also optimize performance: ![](/assets/images/20250430121134509618-5cc01a26e7e36f1760c722f41c26a33a.png) note Longer timeouts may increase Firebase costs. * **Cold Start Delays** Cloud Functions may respond slower after periods of inactivity: * Use **Cloud Scheduler** to periodically invoke functions and keep them warm. * Minimize dependencies to reduce cold start delays. Following this comprehensive troubleshooting guide should help you resolve most issues encountered when working with Cloud Functions. --- # Custom Actions Errors Prerequisites * A basic understanding of how custom actions work. * A FlutterFlow project with a custom action already created. Custom actions are powerful, but troubleshooting them can be tricky. This guide will help you systematically resolve common issues. * **Read the Error Message** Always read the error message printed during test mode, compilation, or local build. The message often provides a clue about the potential issue. * **Common Troubleshooting Checklist** * **Action Name Mismatch** Ensure the name in the action matches the custom action in your code. ![](/assets/images/20250430121138021235-b2a84894e26e51c308d8165327e7429c.png) tip Use the `Add BoilerPlate Code` option to generate code with the correct action name. * **Imports and Arguments** * Check that all required imports are present. * Ensure arguments are defined in both the action settings and your code. ![](/assets/images/20250430121138830209-e031679e2bcca11bc6ec7ce09fe26dbe.png) Example: * Argument 1: Missing definition in settings panel * Argument 2: Correctly imported * Argument 3: Nullable selected, but not specified as nullable in code Follow the steps below to fix this issue: 1. Manually update arguments in both the settings panel and your code. 2. Use the `Add BoilerPlate Code` option (on web, copy only what you need; on desktop, it may replace all code). ![](/assets/images/20250430121139816551-758c54ef51a14b4f6fc2012ea6588d24.gif) * **Name Conflicts** * Avoid using the same name for an action and its argument. ![](/assets/images/20250430121142594662-94f9b7f5c350fec098cb25851c074efc.png) * **Reserved Keywords** * Do not use Dart/Flutter reserved keywords as argument names. **Examples:** `abstract`, `else`, `import`, `show`, `as`, `enum`, `in`, `static`, `this`. *FlutterFlow usually warns you, but double-check!* * **Return Type Mismatch** * Ensure the custom action returns the correct data type as defined in the settings. ![](/assets/images/20250430121143268592-1f07b2e96bfa688e76f7e800463421b9.png) *The function should return the type specified in the settings panel.* * **Internal Library Imports** * If importing internal libraries (example, `../../flutterflow`), set **Exclude from compilation** to `true` if needed. * **Pubspec Dependencies** * Ensure your dependencies are declared in your code and are compatible with FlutterFlow. ![](/assets/images/20250430121143614166-de2881be9794e6225e63e65781a10a65.png) Check for: * Version conflicts (check on **[pub.dev](https://pub.dev)**) * Multiple versions of the same dependency * Conflicts with FlutterFlow's auto-imported dependencies ![](/assets/images/20250430121143935249-28fbc1d922eb729fa65f328f42233253.png) ![](/assets/images/20250430121144228150-4b655f3fe4751bc7cf795ca8a02bd9e5.png) * **Code Errors:** * **Null values:** Handle null values safely. ``` int example = passingIntWhichMayBeNullable ?? 0; ``` * **Correct data types:** Convert data types explicitly. ``` String str = "5"; int result = int.parse(str); // ✅ ``` Use `.toString()`, `.toInt()`, `.toDouble()` as needed. * **Single elements** vs **arrays:** Ensure you are not passing a single element where a list is expected, or vice versa. * **Exclude from Compilation** If this option is enabled, the code won’t be checked during build but can still run during test.. ![](/assets/images/20250430121144509497-570186a33271937e74fe0b06fe081a55.png) * **Duplicate Data Types/Structs** Do not redefine data types or structs already defined in the data schema panel. ![](/assets/images/20250430121144853131-5e03acb547d4795fece1d7396bf9848c.png) * **Callback Data Types** Ensure callback actions return the correct data type. ![](/assets/images/20250430121145202849-bb345fa114bbdf9028e9ac06984923fc.png) Additional Resources * **Debugging with the Browser Console:** Use the browser debug console for logic errors. * **FlutterFlow University Video**: [Custom Actions Video](https://www.youtube.com/watch?v=rKaD9eKuZkY). * **Official Docs:** [Custom Actions | FlutterFlow Docs](/concepts/custom-code/custom-actions.md) tip When in doubt, regenerate the boilerplate and compare with your code. Consistency between settings and code is key! --- # Testing Custom Actions using Debug Console Sometimes, the compiler does not show any errors in the custom action, but the custom action still won't work as expected. This might be due to the code logic or the implementation. In order to test the implementation and the flow, you can use the debug console to test the custom action in different scenarios. Prerequisites * You have created a custom action in FlutterFlow. * You are familiar with using Run Mode and viewing the browser console. The core function that you can use to test the custom actions on the console is the `debugPrint` function in Flutter. To use that in the custom actions, follow the steps below: 1. **Add `debugPrint` Statements in the Code** Use `debugPrint` to print some error on the debug console in case of a specific result. You can use if-else statements or try-catch statements in order to test the success of the scenario. ![](/assets/images/20250430121216632942-63960ad02159d34e7a6e652d1cb4c7c3.png) Example: ``` try { final result = someFunction(); debugPrint('Function result: $result'); } catch (e) { debugPrint('Error occurred: $e'); } ``` 2. **Run the App and Open Console** After the correct implementation in the code, use the action inside the app. On the run mode, open the console. Now you should be able to see the errors in the console upon performing the action. ![](/assets/images/20250430121216962021-90ed68f4858e11cecae8b90992a7de16.png) Still having issues? If you continue to experience issues after testing your logic with debugPrint, please contact support at .​ --- # Codemagic Deployment Error Identification Follow the steps below to identify your codemagic error: * Press **Cmd/Ctrl + k**, type **"deployment"** and hit enter. It will take you to the deployment page.​ ![](/assets/images/20250430121346608131-6051c8ea2ed39c336fb416c77c21edd9.png) * Navigate to the Deployment section by clicking **Project Settings** > **Deployment** (under App Settings).​ ![](/assets/images/20250430121346890273-bd3b94558011c64dfb0021518ec0be4a.png) ​ * Click on the **Failed (VIEW LOGS)** text to see the logs. ​ ![](/assets/images/20250430121347217644-55dff2897e82db223506ec239460e025.png) In this step, you'll need to note the Failed Step that been displayed by CodeMagic error log. ​ ![](/assets/images/20250430121347593094-8827607888b9888bcf0478b4223d6c1e.png) ​ * Now, press **Cmd/Ctrl + F** to search for the term **"error"** in the logs to find the root cause of the issue. Keep pressing **"Enter"** till you find the error ( this is usually at the bottom of the logs ). If you search for "error" and still don't find an error message that makes sense to you then you can also try with the following keyword: "message". ![](/assets/images/20250430121347925706-7968ab925bd39390fc5f6701162d0f4e.png) * Now select and copy this error message and paste it in the Help Center search in the chat icon in the bottom-right corner to search the error. This will help you find the help article for this issue and then you can find the fix for it. ![](/assets/images/20250430121348293622-e59b4593b13524be85bdee95d0b752ca.gif) --- # CodeMagic Deployment Tips Here are some tips to avoid Deployment issues: tip * Make sure you've followed all the steps for **[setting up deployment](/deployment/deploy-for-environments.md#mobile-deployment)** in your project. * If you choose a deployment source from a GitHub Repository then please make sure that it's associated with FlutterFlow's GitHub integration. * If you are deploying to the Play Store from a GitHub repo, make sure to modify your build.gradle file to sign in release mode. * Setting a version number is optional but may be required for specific cases. If you are updating an existing app that has not been deployed using FlutterFlow yet, you will want to specify a version number. --- # Deployment Issues with Stripe Integration Integrating Stripe in your FlutterFlow project can help you accept payments efficiently. However, some common deployment issues may arise. This article outlines key steps and best practices to ensure a smooth Stripe integration and deployment experience. 1. **Firebase Connection** Stripe integration requires a connected Firebase project. Before running through this checklist, it's important to ensure your FlutterFlow project is linked to Firebase, a crucial step for successful payment processing. Detailed guidance can be found at **[FlutterFlow's Firebase Setup Guide](/integrations/firebase/connect-to-firebase.md#step-1-set-up-your-project)**. 2. **Upgrade to Firebase Blaze Plan** Stripe functionality requires a Firebase Blaze Plan for operational capabilities. To avoid disruptions, you will need to upgrade from the Firebase Spark plan to the Blaze plan. Learn more about **[Google's process for upgrading](https://firebase.google.com/docs/projects/billing/firebase-pricing-plans)**. 3. **Set the Google Cloud Platform (GCP) Location** A defined Google Cloud Platform (GCP) location for your Firebase project ensures the correct regional operation of services. The absence of a set location can hinder the deployment process.​ ![](/assets/images/20250430121121827511-04519b51a0219b97efed58e9cb8f6302.png) 4. **Firebase Project Permissions** Ensure you have the necessary permissions enabled for your Firebase project. Two critical permissions involve access management and service configuration. You can also reference the **[setup guide](/integrations/firebase/connect-to-firebase.md#step-1-set-up-your-project)** as well.​ ![](/assets/images/20250430121122068343-0d7529a28637317e6b080a6dfde3dce7.png) 5. **Correct Merchant Code** Use the correct 3-letter merchant country code (e.g., "GBR" for the United Kingdom vs. "UK"). Incorrect codes can lead to failed transactions. For accurate codes, refer to **[IBAN Country Codes](https://www.iban.com/country-codes)**.​ ![](/assets/images/20250430121122307123-ce254157c126b63ba4f6d28b18407876.png) ![](/assets/images/20250430121122597517-7bd69ca688add81b57c2786e5c82167a.png) 6. **Test and Live Keys** For deployment, both Test and Live Stripe keys must be configured in your project settings, regardless of the development stage. This ensures Stripe's API can properly interact with your application.​ ![](/assets/images/20250430121122925141-9d93238a034787161270b25ce87ec7b8.png) 7. **Consistent Region Settings** Align your Firebase project's region with that of your FlutterFlow settings to prevent deployment failures. Inconsistencies can cause function deployment issues.​ ![](/assets/images/20250430121123230941-b344665d460afd8118a7232a42fad81a.png) ![](/assets/images/20250430121123502329-e3650708a342858946ee640eb2349795.png) If you find that this article hasn't fully addressed your concerns or if you have more questions, please don't hesitate to reach out to us at ​ --- # Fixing Razorpay Deployment Razorpay is a major payment processor in India. Integrating **[Razorpay](https://razorpay.com/)** can allow users to make payments using their app. This article outlines some common scenarios and troubleshooting instructions for Razorpay deployment issues. 1. **Firebase Integration and Auth** FlutterFlow uses Firebase integration and cloud functions to facilitate Razorpay payments. Ensure you have Firebase configured in your FlutterFlow project and that Firebase Auth is enabled. ![](/assets/images/20250430121119193097-a438f40c7cdfcb6f5d1835d2ec6f5fc7.png) ![](/assets/images/20250430121119493481-f8b5d61a1b92ca72e37c18aab36a3c1c.png) 2. **Firebase Blaze Plan** Razorpay uses cloud functions behind the scenes to facilitate payments. Cloud functions are a part of Firebase's "Blaze" plan. You must upgrade from the Firebase Spark plan to the Blaze plan to avoid disruptions. Learn how to upgrade here. On the bottom left side of your Firebase console, you will see which plan you are on ![](/assets/images/20250430121119754142-f2fe2017ace7abe5aff8a39815eb0f66.png) 3. **Set Google Cloud Location** Ensuring your Firebase project is pinned to a specific Google Cloud Platform (GCP) location is key for optimal service functionality across regions. Skipping this step could result in errors.​ ![](/assets/images/20250430121120027064-04519b51a0219b97efed58e9cb8f6302.png) 4. **Firebase Project Permissions** Make sure your Firebase project has the required permissions activated. Access management and service configuration are two essential permissions to focus on. For guidance on setting these up, look at the instructions in the **[FlutterFlow Project Setup](/resources/projects/settings/project-setup.md)**. 5. **Razorpay Keys Check** Make sure to copy and paste the correct Key ID and Key Secret from Razorpay for testing and production, respectively. For testing, make sure "Is Production" is turned off. ![](/assets/images/20250430121120324713-954f9bacfc758924edb56feaf8d03874.png) ![](/assets/images/20250430121120614698-427f7e6d97ef154687e5c52d418bade7.png) ![](/assets/images/20250430121120833797-47659c8138d19168b2aa7de8c2f896c3.png) 6. **Razorpay Business Name** Finally, ensure you have entered the proper "Business Name" in the Razorpay additional settings in FlutterFlow. Make sure this business name matches your business name in Razorpay records. ![](/assets/images/20250430121121100378-562380f0010a8034013ffb62e8768126.png) Other Considerations Razorpay currently works only on mobile (Android and iOS). This is due to a limitation from Razorpay's Flutter Package. If you are planning to collect payments on a web app - consider using Stripe. ![](/assets/images/20250430121121294657-9281e98e690de9a3fbc9f3190be20cd5.png) If you are still facing issue with deploying Razorpay on Flutterflow, please feel free to reach out to --- # Fixing Stripe Deployment & Payment Errors Integrating Stripe for payment processing in FlutterFlow can significantly simplify monetization. However, developers may encounter issues during deployment or while managing transactions. This guide outlines common deployment and payment issues—and how to fix them—to help ensure a seamless Stripe integration experience in FlutterFlow apps. ## Deployment Checklist for Stripe Integration[​](/troubleshooting/deployment/fixing-stripe-deployment-and-payment-errors.md#deployment-checklist-for-stripe-integration "Direct link to Deployment Checklist for Stripe Integration") 1. **Firebase Connection** Stripe integration requires a connected Firebase project. Before running through this checklist, it's important to ensure your FlutterFlow project is linked to Firebase, a crucial step for successful payment processing. Detailed guidance can be found at **[FlutterFlow's Firebase Setup Guide](/integrations/firebase/connect-to-firebase.md)**. 2. **Upgrade to Firebase Blaze Plan** Stripe functionality requires a Firebase Blaze Plan for operational capabilities. To avoid disruptions, you will need to upgrade from the Firebase Spark plan to the Blaze plan. Learn more about **[Google's process for upgrading](https://firebase.google.com/docs/projects/billing/firebase-pricing-plans)**. 3. **Set the Google Cloud Platform (GCP) Location** A defined Google Cloud Platform (GCP) location for your Firebase project ensures the correct regional operation of services. The absence of a set location can hinder the deployment process.​ ![](/assets/images/20250430121145711998-04519b51a0219b97efed58e9cb8f6302.png) 4. **Firebase Project Permissions** Ensure you have the necessary permissions enabled for your Firebase project. Two critical permissions involve access management and service configuration. You can also reference the **[setup guide](/integrations/firebase/connect-to-firebase.md)**.​ ![](/assets/images/20250430121145949036-0d7529a28637317e6b080a6dfde3dce7.png) 5. **Correct Merchant Code** Use the correct 3-letter merchant country code (example., "GBR" for the United Kingdom vs. "UK"). Incorrect codes can lead to failed transactions. For accurate codes, refer to **[IBAN Country Codes](https://www.iban.com/country-codes)**.​ ![](/assets/images/20250430121146161973-ce254157c126b63ba4f6d28b18407876.png) ![](/assets/images/20250430121146400049-7bd69ca688add81b57c2786e5c82167a.png) 6. **Test and Live Keys** Both Test and Live Stripe keys must be configured in your project settings, regardless of the development stage. This ensures Stripe's API can properly interact with your application.​ ![](/assets/images/20250430121146604033-9d93238a034787161270b25ce87ec7b8.png) 7. **Consistent Region Settings** Align your Firebase project's region with that of your FlutterFlow settings to prevent deployment failures. Inconsistencies can cause function deployment issues.​ ![](/assets/images/20250430121146854018-b344665d460afd8118a7232a42fad81a.png) ![](/assets/images/20250430121147068781-e3650708a342858946ee640eb2349795.png) ## Addressing Payment Transaction Issues[​](/troubleshooting/deployment/fixing-stripe-deployment-and-payment-errors.md#addressing-payment-transaction-issues "Direct link to Addressing Payment Transaction Issues") 1. **Authentication Requirement** Stripe payments **require an authenticated user session**. Before initiating payment processes, ensure your application logic includes user login or account creation. 2. **Payment Modal Variations** It's important to note that web and mobile platforms present different payment modal presentations. These UI differences are out-of-the-box for Stripe and cannot currently be customized within FlutterFlow. 3. **Price Format** Prices should be submitted to Stripe in **cents**, not **dollars**. Utilize a custom function to convert dollar values to cents for accurate transaction processing.​To set a price in cents to Stripe, you can simply use a custom function that takes the price in dollars and returns it as cents.​ Here is a custom code you can use to make this calculation in a custom function: ``` int dollarToCent(double amount) { // Convert the amount to a string String st = amount.toString(); // Remove any dots or commas st = st.replaceAll('.', ''); st = st.replaceAll(',', ''); // Convert the cleaned string to an integer return int.parse(st); } ``` // Input: 14.99 // Output: 1499 cents 4. **CORS Error Resolution** A CORS error during payment initiation often indicates a permissions issue with your Firebase function. Verify and adjust the `allUsers` permission for your Stripe function in the Firebase console to resolve this error.​ ![](/assets/images/20250430121147385978-51634e1a435a358149efcdd9361de1bf.png) ![](/assets/images/20250430121147683388-db162fbb7e44ca3923b1521c56743149.png) 5. **Subscriptions** Currently, Apple and Google restrict Stripe subscriptions on mobile platforms. To expand your subscription capabilities, you can use alternative solutions like RevenueCat for mobile apps and direct API calls for web applications.​ **For further information and troubleshooting:** * [Stripe Documentation](https://stripe.com/docs) * [Stripe Payments](https://stripe.com/payments) * [FlutterFlow University](https://university.flutterflow.io/) * [Payments - Intro | FlutterFlow University](https://university.flutterflow.io/courses/flutterflow-payments) --- # Resolve Errors in Downloaded Code When you download your project from FlutterFlow and run it locally in your IDE, you may encounter errors due to Flutter version mismatches. This guide outlines how to resolve these issues by ensuring your local Flutter version matches the version supported by FlutterFlow. 1. **Check FlutterFlow’s supported Flutter version** To find the Flutter version currently supported by FlutterFlow: * Open the FlutterFlow dashboard. * Navigate to your project settings or export screen. * Locate the displayed Flutter version used for your project. ![](/assets/images/20250430121137152872-9f928bf5f226f815cf41220d9edc1795.png) 2. **Verify the Flutter version on your machine** To check the Flutter version installed locally, run the following command in your terminal: ``` flutter --version ``` Here's an example of how you can do that: ![](/assets/images/20250430121137421780-b885347a16240cdd13b780ae93f2b68a.png)​ 3. **Upgrading or Downgrading to the correct Flutter version** If the current version on your machine is different than what is currently supported by FlutterFlow, you can downgrade or upgrade to the supported version. You can learn more about [**upgrading Flutter**](/testing/local-run.md#4-running-app-on-device). ​By following these steps, you can fix the errors that you face after downloading the code and run locally. If you continue to experience issues, contact the FlutterFlow support team via live chat or email at . --- # Run Mode: Build Failure Encountering a "Run mode: Build failed" error can be frustrating when you're eager to see your app in action. This error typically signifies a project issue that prevents a successful build. Addressing these errors promptly ensures your app's functionality and performance. This guide provides a structured approach to troubleshooting and resolving "Run mode: Build failed" errors, ensuring a smooth development process for your projects. * **Recognizing the Error** Here's what the "Run mode: Build failed" error looks like inside of FlutterFlow: ![](/assets/images/20250430121148301014-4d413e5fef4fbe4880435e75c2edf4ee.png) * **Understanding Test Mode vs. Run Mode** Here's a little background on run mode vs. test mode in FlutterFlow. Test mode runs as a "test" to help you identify errors before deployment. These features include a debugger and display warnings. Alternatively, run mode attempts to run the app in **release mode** to better mimic what your users can expect in production. In release mode, **warnings are mostly suppressed**, meaning it's important to ensure you are acknowledging and addressing warnings in debug mode before you enter run mode. The "Run mode: Build failed" error can occur under various circumstances, during: * Run mode * APK download * Code download * GitHub push * And more ## Common Scenarios and Solutions[​](/troubleshooting/deployment/run-mode-build-failure.md#common-scenarios-and-solutions "Direct link to Common Scenarios and Solutions") * **Custom Code Failures** * **Issue**: Your project's custom code doesn't show errors within the editor, but errors appear when you try to run the app. * **Example**: A custom widget lacks web support. * **Solution**: Verify on pub.dev or equivalent platforms that the custom code supports the necessary platforms (example, web, iOS, Android). * **Best practice**: Consider running the code locally on a sample Flutter project before implementing the custom code inside FlutterFlow to identify possible errors logged. * **Widget Failures** * **Issue**: A widget within your app causes the build to fail due to errors. * **Example**: Actions assigned to a widget are incomplete or improperly configured. * **Solution**: Locate the error-causing widget (usually identified in the error message) To correct the issue: * Ensure the widget tree is correctly formatted * Verify that widgets are named clearly for easy identification * **Build Fails Without Error Messages** * **Issue**: The build process fails without displaying an error message, making it challenging to diagnose the problem. * **Solution**: Download and run the project code locally with a debugger to identify and resolve the issue. If downloading the code is problematic, check your browser's console for errors that might indicate the cause. ![](/assets/images/20250430121148811672-4c769af193fd8c00576f42eb585c2658.png) * **Grey Screen in Run Mode** * **Issue**: Encountering a grey screen in run mode usually indicates an error suppressed by the release mode. * **Solution**: Run the app in test mode to potentially reveal the error for troubleshooting. If test mode does not display errors, use the browser's developer console for clues. ## Checklist for Troubleshooting[​](/troubleshooting/deployment/run-mode-build-failure.md#checklist-for-troubleshooting "Direct link to Checklist for Troubleshooting") * **Identify when and where the error occurs**: Determine if the error is specific to run mode, test mode, or other instances like APK download or code download. * **Locate the source of the error**: The error message often provides clues about where the problem lies, whether in custom code, a specific widget, or elsewhere. * **Check for platform support**: For issues related to custom code, ensure compatibility with your target platforms. * **Examine widget configuration**: Verify that all actions and configurations associated with widgets are complete and correct. * **Utilize local debugging**: If the error is elusive, running the debugger locally on your downloaded code can help identify the issue. * **Leverage browser tools**: The browser's console and developer tools can offer insights, especially when dealing with errors that don't manifest in traditional debug outputs. Additional Resources [Basic Troubleshooting Guide – FlutterFlow Documentation](https://docs.flutterflow.io/troubleshooting/basic-troubleshooting-guide) --- # Enterprise ## Unable to access FlutterFlow[​](/troubleshooting/enterprise.md#unable-to-access-flutterflow "Direct link to Unable to access FlutterFlow") Few enterprise customers might have restrictions in accessing the internet. For example, allowing only safe URLs that are related to their work. If you have such restrictions, you might not be able to access FlutterFlow. To use FlutterFlow and get the best experience, you need to allow all the URLs FlutterFlow uses to operate. Allowlist of URLs: * [app.flutterflow.io](http://app.flutterflow.io/) * [flutterflow-io-6f20.firebaseapp.com](http://flutterflow-io-6f20.firebaseapp.com/) * [https://flutterflow-io-6f20.firebaseio.com](https://flutterflow-io-6f20.firebaseio.com/) * [flutterflow-io-6f20.appspot.com](http://flutterflow-io-6f20.appspot.com/) * [https://storage.googleapis.com](https://storage.googleapis.com/) * * * * * * * [https://maps.googleapis.com](https://maps.googleapis.com/) * * * --- # Client Access to Firestore Expired You may receive an email from Firebase with the subject: **"Client access to your Cloud Firestore database expired"** This message typically appears when your Firestore database is in **Test Mode** and the access duration has expired. You are seeing this error message because of the following: When setting up Firestore for the first time, Firebase offers two rule options: 1. **Test Mode** – Temporarily allows open access (expires after 30 days). 2. **Production Mode** – Starts off restricted and requires secure rules. ![](/assets/images/20250430121224235710-89e69fcdc91e04a8e895f061065d4b91.png) If you selected **Test Mode** during setup, Firestore access will automatically expire after the preset period. To continue using Firestore, you'll need to update the rules using one of the following options: * **Option 1: Manage Firestore Rules From FlutterFlow** You can **[manage and deploy Firestore rules](/integrations/database/cloud-firestore/firestore-rules.md)** directly from FlutterFlow. * **Option 2: Manually Update Firestore Rules in Firebase Console** Follow these steps to manually update the rules: 1. Go to the **[Firebase Console](https://console.firebase.google.com/)**. 2. Open your project and navigate to **Firestore Database**. 3. Select the **Rules** tab. From here, you have two options: * **Option A: Extend Test Mode** Update the expiration timestamp to a future date if you're still in development. ![](/assets/images/20250430121224547832-a2a051061854b7c100d8a54f3f166710.png) * **Option B: Secure Your Rules for Production** Update your rules to enforce proper authentication and access controls. ![](/assets/images/20250430121224874215-be277d57d8c10ead3f30dd39c3cd5b43.png) If the issue persists, contact us at for further assistance. --- # Configuring CORS for Firebase Storage When you deploy your web app to a custom domain, the domain and the Firebase Storage bucket are hosted on different servers. This means that the browser will block requests to the Firebase Storage bucket from your web app, because the origins (the domains and ports) of the two servers are different. **What is CORS?** CORS stands for **Cross-Origin Resource Sharing**. It allows you to specify which origins are allowed to access your resources. By configuring CORS, you can tell the browser that your web app is allowed to make requests to the Firebase Storage bucket, even though the two servers are hosted on different domains. Follow these steps to configure CORS for your Firebase Storage bucket: 1. Open **[Google Cloud Console](https://console.cloud.google.com)**. 2. **Launch the Cloud Shell**: Click the **Activate Cloud Shell** icon in the top-right corner. ![](/assets/images/20250430121203371000-ae959dd8cb3f0d459ec1a3c85478fac5.png) Wait for the terminal to load. ![](/assets/images/20250430121203911156-0d82267ed36a199657a864eeb231151d.png) ​ 3. **Run the following Command:** ``` gcloud config set project your-firebase-project-id; ``` 4. **Define and upload your cors.json file:** The `cors.json` file contains a list of origins that are allowed to access your resources. Each origin is a string that identifies a domain or port. For example, the following origin allows access from the domain `www.example.com`: ``` "origins": ["https://www.example.com"] ``` You can also specify a list of allowed headers. The following example allows access to the `Content-Type` and `Authorization` headers: ``` "origins": ["https://www.example.com"], "allowedHeaders": ["Content-Type", "Authorization"] ``` To allow any origin to access your resource, you can use `*`. The `cors.json` file below allows any origin to access, but not modify your resources. ``` [ { "origin": ["*"], "method": ["GET"], "maxAgeSeconds": 3600 } ] ``` Once you have defined your `cors.json` file, upload it to Google Cloud Console. ![](/assets/images/uploadToGCC-97604280ce723bb14f8c458427ed74c4.png) To confirm that you have uploaded it correctly, you can run `ls` in your console and you should see your `cors.json` file listed. 5. **Run the `cors` Command to Configure CORS:** ``` gcloud storage buckets update gs://your-google-storage-bucket-name --cors-file=cors.json ``` 6. **(Optional) Confirm success by viewing the CORS of your bucket** Run the following command to confirm that the rules from your `cors.json` file were applied. ``` gcloud storage buckets describe gs://your-google-storage-bucket-name --format="default(cors_config)" ``` You should see the same allowed origins and any other info defined in your `cors.json` file. For more information on configuring CORS in Firebase Storage, please see the **[official documentation](https://firebase.google.com/docs/storage/web/download-files#cors_configuration)**. --- # Content Manager Firestore Error You may see the following error message when accessing the **FlutterFlow Content Management System (CMS)**: ![](/assets/images/20250430121517855306-24e97004e33cce6167bdd47037e1263a.png) This error typically occurs when Firebase permissions or authentication settings are not properly configured. Follow the steps below to resolve it. 1. **Enable Email/Password Sign-In** 1. Open the **[Firebase Console](https://console.firebase.google.com/)**. 2. Select your project. 3. From the left-hand menu, click **Authentication**. 4. Click **Get started** (if not already started). 5. Go to the **Sign-in method** tab. 6. Ensure **Email/Password** is listed and marked as **Enabled** ✅. ![](/assets/images/20250430121518159572-c74b9cb5cb2f2eb2ebac07205fcbe1c4.png) note If Email/Password is not enabled, turn it on by clicking the pencil icon and toggling the setting. 2. **Add Required Firebase Project Permissions** FlutterFlow requires the following roles to be granted to `firebase@flutterflow.io` for proper functionality: * Editor * Cloud Functions Admin * Service Account Admin To add these permissions: 1. In the **[Firebase Console](https://console.firebase.google.com/)**, open your project. 2. Navigate to **Project Settings** > **Users & Permissions**. 3. Check if `firebase@flutterflow.io` has the roles listed above. ![](/assets/images/20250430121518370897-f0e035f033238446b162c7eacbb6af13.png) info If these roles are missing, the integration is incomplete. Make sure to add all three roles. 3. **Update Firestore Rules in FlutterFlow** 1. In your FlutterFlow project, go to **Firestore** > **Settings**. 2. Scroll down to the **Firestore Rules** section. 3. Click **Deploy/Redeploy** to apply your latest rules. ![](/assets/images/20250430121518594245-1fa5ba0cf70fb84c236da5b1a6e8d77d.png) 4. **Define Your Firebase Schema** Make sure your Firebase schema is fully defined. The Content Manager only displays fields that are already defined in your Firebase schema. 5. **Ensure You're Using the Latest FlutterFlow Version** Press `Ctrl + R` (on Windows) or `Cmd + R` (on macOS) to refresh and ensure you’re on the latest version of FlutterFlow. 6. **Clear Cache and Re-Login** After completing the above steps: * Clear your browser cache. * Log out and log back into FlutterFlow. Still not working? Try reconfiguring permissions from scratch. If none of the steps resolve the issue: 1. Remove existing Firebase permissions. 2. Re-add all necessary roles from scratch. 3. Follow the full setup instructions in the **[official FlutterFlow Firebase integration guide](/integrations/firebase/connect-to-firebase.md)**. By following the steps above, you should be able to resolve the error and continue using FlutterFlow CMS without interruptions. --- # Firebase Android Config File Missing You may see the following warning in FlutterFlow, as shown in the image below: ![](/assets/images/20250430121357585709-174b3b73ac2cca43e887898b6590d2d3.png) This typically means that the Firebase Android configuration file (`google-services.json`) has not been generated or uploaded to your FlutterFlow project. Follow the steps below to fix the issue: 1. **Verify your Firebase Setup** Make sure that Firebase has been fully configured for your project. Follow the **[Firebase setup guide](/integrations/firebase/connect-to-firebase.md)** to ensure all required steps have been completed. 2. **Open Project Settings in FlutterFlow** * Navigate to your FlutterFlow project. * From the left menu, select **Settings > Firebase**. ![](/assets/images/20250430121357870887-9a8bd8979530cdb39ac0650f847d866f.png) 3. **Regenerate your Firebase Configuration Files** * In the Firebase Settings screen, click **Regenerate Firebase Files** to create new configuration files and upload them automatically. 4. **Contact Support if Needed** If you continue to experience issues, reach out to [FlutterFlow Support](mailto:support@flutterflow.io) for further assistance. note The configuration file is required for successful builds and deployment on Android. Make sure it remains up-to-date if you make changes in your Firebase project. --- # Firebase Storage Limits in FlutterFlow Managing Firebase Storage properly is essential for controlling your app's file storage and associated costs in FlutterFlow. This article summarizes the current limits and best practices following Firebase’s September 2024 changes. ## Firebase Storage Plans and Limits[​](/troubleshooting/firebase/firebase-storage-limits-in-flutterflow.md#firebase-storage-plans-and-limits "Direct link to Firebase Storage Plans and Limits") * **Blaze Plan (Pay-as-you-go)** * Firebase Storage (Cloud Storage for Firebase) is only available on the Blaze plan for new Firebase projects. * Storage charges are based on usage volume. * The price per GB/TB decreases as your usage increases. * Refer to the **[Firebase Pricing page](https://firebase.google.com/pricing)** for current rates. * **Spark Plan (Free Tier)** * For projects created after September 2024, Cloud Storage for Firebase is **no longer available** on the Spark plan. * To use file storage (uploads, images, videos, etc.) with Firebase Storage, you must upgrade to the Blaze plan. info If your Firebase project was created before the September 2024 policy change, you may still have limited access to Firebase Storage under legacy conditions. However, new projects must follow the updated Blaze-only policy. ## Firebase Storage Operations Limits[​](/troubleshooting/firebase/firebase-storage-limits-in-flutterflow.md#firebase-storage-operations-limits "Direct link to Firebase Storage Operations Limits") * Firebase imposes limits on the number of operations (uploads, downloads, deletes) based on your plan. * With Blaze, these limits are generally higher but still subject to quotas depending on your usage volume. * Monitor your app’s usage patterns to avoid unexpected failures or costs. ## Best Practices for Managing Firebase Storage[​](/troubleshooting/firebase/firebase-storage-limits-in-flutterflow.md#best-practices-for-managing-firebase-storage "Direct link to Best Practices for Managing Firebase Storage") * Regularly delete unused or unnecessary files. * Compress large files (especially images and videos) before uploading. * Actively monitor storage usage in the Firebase Console. * Set up automated cleanup processes for apps with large or growing data volumes. tip Proactive storage management helps control costs and maintain app performance. Additional Resources * [Firebase Pricing](https://firebase.google.com/pricing) * [Firebase Storage FAQ (September 2024 Changes)](https://firebase.google.com/docs/storage/faqs-storage-changes-announced-sept-2024) * [Firebase Storage Documentation](https://firebase.google.com/docs/storage) * [FlutterFlow Docs: Storage](/integrations/firebase-storage/storage-rules.md) Always review your Firebase plan details to ensure you're aligned with the most current pricing model and storage policies. --- # Get the Sum of Firebase Document or API Values Sometimes you need to display a total, such as a subtotal or count based on data fetched from Firebase or an API. This guide walks you through the steps to calculate and display that sum in FlutterFlow. Prerequisites * A working Firebase collection or API that returns numeric values. * A FlutterFlow UI component (example, **Text**) where the sum will be displayed. **Steps to Calculate the Sum of Firebase Document or API Values** 1. **Identify where to Display the Total** Decide where in your app the total will appear. For example, insert a **Text** widget that will show the computed sum. ![](/assets/images/20250430121219360101-421db570635b0004a33c0e3c102580ba.png) 2. **Prepare your Data Type** Next, you need to specify what kind of data you're adding up. For example, if you're working with numbers with decimal points, you'll classify your data as double. Make sure to indicate that you're dealing with a list of these values. ![](/assets/images/20250430121219606895-00021a4fa8e3ae17e474ff9060a63370.png) 3. **Retrieve and Map your Data** When fetching data from Firebase or an API, extract the values you want to sum. Use the `map()` function to create a list of those values. ![](/assets/images/20250430121219871237-fcb0c28690750863a8af6ed74f10c3a4.png) 4. **Calculate the Sum** With your list of values ready, store them in a variable (let's call it `var1`). Then, decide on the format you want for your result. Use the `reduce` function to add up all the values in your list, `var1`, to get your total sum. ![](/assets/images/20250430121220084430-5a459b5c85db423fa188d82a944de37f.png) 5. **Checking Your Results** After completing these steps, you should have the total sum displayed where you need it. If it looks right, you've successfully calculated the sum! [](/assets/files/20250430121220338400-87ae823e6fb5f5d9c92b2651efbe48b6.png) Trobleshooting * Use `.isNotEmpty` to prevent errors when the list is empty. * Format the output using `.toStringAsFixed(2)` to show 2 decimal places if needed. * Optional: Store the sum in a global variable for use across multiple pages. --- # Missing Firebase Storage in FlutterFlow Settings When setting up Firebase Storage in your FlutterFlow project, you may notice that the **Firebase Storage** option is missing from the **Firebase Settings** tab. ![](/assets/images/20250430121309740417-bf1c5b27e75fe10c002115df6be9b0b0.png) This usually happens when Firebase Storage has not been enabled for your project in the Firebase Console. Until it’s enabled there, the option won’t appear in FlutterFlow. Follow these steps to enable Firebase Storage and make it available in your FlutterFlow settings: 1. In your FlutterFlow project, click **Firebase** from the left menu, then click **Open Firebase Console**. ![](/assets/images/20250430121310019673-e0ccc993e764abdf269d95415452c112.png) 2. In the Firebase Console, go to the **Build** menu and select **Storage**. ![](/assets/images/20250430121310317285-985cd6624de61ad14b28b6394eb4db6b.png) 3. Click **Get started** and complete the setup process. ![](/assets/images/20250430121310619096-a543a7ff241ac8ddb0501a78ef2ba3b3.png) 4. After successfully creating the storage bucket, return to FlutterFlow. You should now see the **Rules** option under **Firebase Settings**. ![](/assets/images/20250430121310959552-a17de5817984012a100fce3db8ec70bd.png) note After setting up Firebase Storage, it may take up to one hour for the changes to appear in FlutterFlow. --- # Resolving Firestore Index Deployment Issues If your Firestore indexes are not being deployed as expected, follow these troubleshooting steps to resolve the issue and ensure your app performs correctly. ![](/assets/images/20250430121118024255-a20f447be6780a9a23eba9e9c53d3240.png) 1. **Enable Email Sign-In** * Open your Firebase project. * Go to **Authentication** > **Sign-in method**. * Enable **Email/Password** sign-in. 2. **Grant Proper Permissions** * In your Firebase project, open **Project Settings** > **Users and permissions**. * Add as a member. * Assign the following roles: * **Editor** * **Cloud Functions Admin** * **Service Account User** ![](/assets/images/20250430121118320891-4875e5f70a7f07f81cffd302e3f013bb.png) 3. **Update Firestore Rules** * Update your Firestore rules in both Firebase Console and FlutterFlow. * Ensure they match your app’s data access requirements. * Follow the detailed steps in the **[Firestore Rules documentation](/integrations/database/cloud-firestore/firestore-rules.md)** to correctly configure your rules. ![](/assets/images/20250430121118592064-f6712af4376f88d2e1ef9f00fbd75b82.png) 4. **Verify Index Deployment** * In the Firebase Console, go to **Firestore Database** > **Indexes**. * Check that your indexes have been deployed. note Deployment may take a few minutes. Refresh the page if you don’t see updates immediately. Additional Tips * Make sure you completed all the steps above before retrying deployment. * For advanced troubleshooting, check Firebase logs and permissions in Google Cloud Console. Following these steps should help resolve Firestore index deployment issues in FlutterFlow. --- # Unable to Validate Firestore Schema When trying to validate your Firestore Schema, you may encounter the error as seen in the image below: ![](/assets/images/20250430121304770472-d33d51d68090bdbed7a8eb1502d7ef8d.png) **Troubleshooting Steps:** 1. **Verify that you have Created a Firestore database** Ensure that you have already created a Firestore database in your Firebase project. ![](/assets/images/20250430121305056379-894e3e17d43df54fe13ad23cf585188a.png) 2. **Check the Database Mode** A database in Test Mode may not work properly for FlutterFlow integration. note After creating the database in Test Mode, there is no direct visual option to switch to Production Mode. You need to update the Firebase security rules manually. However, if you deploy the rules from FlutterFlow, this step is handled automatically. **Steps to Update your Database Rules**: 1. Go to your Firebase project. 2. Select **Cloud Firestore**. 3. Navigate to **Rules**. You will see something like this: ![](/assets/images/20250430121305295728-e7dc52922d82931db1e441a76c95ebb7.png) Update the rules as needed. note Ensure that you specify the correct `rules_version` and verify your configuration. ![](/assets/images/20250430121305526883-dbdfc8d387727dfdb162fbcbae39ce53.png) 4. Click **Publish** to apply the changes. 3. Assign the necessary permissions to `firebase@flutterflow.io` You must grant the required cloud permissions to `firebase@flutterflow.io`: * **Editor** * **Cloud Functions Admin** * **Service Account** In the Firebase Console: 1. Open your project. 2. Go to **Project Settings** > **Users & Permissions**. 3. Confirm that the required roles are assigned to `firebase@flutterflow.io`. If you don't see these roles assigned, you need to complete this step: ![](/assets/images/20250430121305771267-b4c1e1d6d592a4e892881d4779c781fc.png) 4. Ensure you have at least one collection created in FlutterFlow In FlutterFlow, select the **Firestore** tab from the left menu. If no collections are listed, create at least one collection. ![](/assets/images/20250430121306066982-32b6ddf68c8679c186f91bb82de66a84.png) 5. **Confirm that your collections have documents** Use FlutterFlow's CMS to verify that your collections contain at least one document: * Select **Manage Content**. * Check each collection to confirm that data exists. If no documents exist, add at least one: ![](/assets/images/20250430121306294908-0e97d67ca6ccfbd57582ecd175bad8f7.png) ![](/assets/images/20250430121306553330-f80c6041d593bc2e43f17bbfd8783d9f.png) 6. **Deploy Firestore rules from FlutterFlow** In your FlutterFlow project: 1. Select **Firestore** > **Settings**. 2. Scroll down to **Firestore Rules**. 3. Select **Deploy** (or **Redeploy** if needed). ![](/assets/images/20250430121306835223-e3de6e45ed89ba05cbd17d52431f83cd.png) --- # Updating Firestore Security Rules Most backend issues are generated by the misconfiguration of the Firestore Security Rules. These backend issues may include Grey Screen errors, Infinite Loading screen, Firestore record creating error, Data mismatch errors, etc. To solve these issues, the Firestore rules have to be updated, for which you can follow the given series of steps: * **Update Your Firestore Rules** From within your FlutterFlow project, select **Firestore** > **Settings** > Scroll down to **Firestore Rules** > select **Deploy**/**Redploy**. ![](/assets/images/20250430121507937548-1fa5ba0cf70fb84c236da5b1a6e8d77d.png) * **Update Firestore Indexes** The next step is to see if the Firestore Rules and Indexes are **Out of Date** or **Not Deployed** (as shown in the image below). If yes, click on the blue **Deploy** button to push the latest rules. ![](/assets/images/20250430121508288240-4b57b60ef6edf955155ef962a6136c99.png) After clicking on the **Deploy** button, a confirmation dialog would be shown, highlighting the changes in the rules that are being made from the deployment. This compares the existing rules in Firestore and highlights what changes are being made in the Firestore rules. These changes are required when a new collection is created or is been edited or if the rules are Out of Date. ![](/assets/images/20250430121508604665-43762681cfc53b60ebe21a52469d8b49.png) You can review the changes, and then you can click on the **Deploy Now** button. An orange loading indicator would be shown, which means that the rules are getting deployed (This step usually finishes within less than a minute, and the loading indicator is replaced with a Green Checkbox button). * **Validate the Firestore Schema** After completing the steps above, validate the Firestore schema by clicking on the blue **Validate** button. This ensures that everything is configured correctly and the Firestore collection schema matches with the Collection schema configured in FlutterFlow. ![](/assets/images/20250430121508962664-81306c33ba320ea521aa4a3b4a8d6803.png) --- # Initialize GitHub Repository When pushing code to GitHub, the following error may occur: ``` Error pushing repository. Make sure your repository is initialized ``` This typically happens if the GitHub repository was not initialized correctly or if the project exceeds GitHub’s file size limits. Prerequisites * Access to your GitHub account. * A FlutterFlow project with GitHub integration enabled. Follow the steps below to initialize a GitHub repository: 1. **Create a New Repository** * Go to **[GitHub](https://github.com/)** and click **New** to create a repository. * Enable the option **Add a README file** during creation. 2. **Connect Repository to FlutterFlow** * Open your FlutterFlow project. * Navigate to **GitHub Integration** and follow the instructions to connect the new repository. ![](/assets/images/20250430121522561282-0a96b3cf65667cd8570589b9b0ca700a.gif) 3. **Download and Inspect Your Project** * Download the full source code from FlutterFlow. * Navigate to the `assets` folder. * Identify any files larger than **25MB**. Check Your Asset Size GitHub does not allow individual files larger than 25MB. Large image or video files may cause push failures. Tips to Reduce Project Size * Use **network assets** instead of uploading large media files directly to FlutterFlow. * Optimize images using tools like TinyPNG or ImageOptim before uploading. Additional Resources * **[Connect a GitHub Repo](/exporting/push-to-github.md#connect-a-github-repo)** * **[State Management](/concepts/state-management.md)** --- # Repository Head Deployment Failure This error may occur when deploying your FlutterFlow app to GitHub using Codemagic. The message `Failed to set the repository head` indicates a problem with repository access, configuration, or connectivity. Prerequisites * A connected GitHub repository with appropriate access permissions. * GitHub deployment enabled within FlutterFlow. **The Error Message** ``` Failed to set the repository head ``` This message typically appears in the build log during deployment. Below are the possible causes of this error: * The GitHub repository does not exist or was deleted. * The branch specified in build settings does not exist. * Insufficient permissions to push or write to the branch. * GitHub API or network connectivity issues. * Local build errors in the codebase. **Steps to Fix the Deployment Error:** 1. **Confirm the Repository Name** Ensure the repository name in your FlutterFlow deployment settings exactly matches the name in GitHub. 2. **Verify the Branch** Check that the branch exists in the repository and is correctly specified in your build settings. Avoid typos or casing mismatches. 3. **Review Repository Permissions** Confirm that your GitHub account or connected GitHub App has push/write access to the repository and branch. 4. **Check Network Access** Make sure your environment is not blocking GitHub via VPN, firewall, or DNS restrictions. 5. **Validate the Codebase Locally** Run the downloaded Flutter project locally to confirm that it builds without errors. Additional Resources * **[GitHub Deployment Overview](/deployment/deploy-from-github.md#steps-to-deploy)** * **[Codemagic Deployment Error Identification](/troubleshooting/deployment/codemagic-deployment-error-identification.md)** --- # AdMob Ads Not Displaying in Google Play Testing If your AdMob ads are not showing during **Open Testing** via the Google Play Store, the issue is often tied to AdMob configuration, app permissions, or settings in the Google Play Console. Follow the steps below to ensure ads display correctly. Prerequisites * An active **AdMob** account is set up. * Your FlutterFlow project is linked to **AdMob**. * The app is uploaded to **Google Play Console** under an Open Testing track. - **Use Test Ads During Development** Always use test ads during development to avoid policy violations or ad-serving issues: * Refer to the **[Google AdMob Test Ads](https://developers.google.com/admob/android/test-ads)** guide for appropriate test ad unit IDs. * Live ads should be used only after your app is published to production and approved. - **Verify AdMob Account Setup** 1. Go to the **AdMob Console**. 2. Confirm that your app is registered and linked to your Google Play listing. 3. Ensure the app’s release status in AdMob matches its status in the **Google Play Console**. note If your app is listed as `not released` in AdMob, live ads may not load during testing. - **Declare Use of Advertising ID** Apps targeting **Android 13 (API 33)** or above must declare use of the **Advertising ID**: 1. Open the **Google Play Console**. 2. Go to **Policy > App Content**. 3. Select **Advertising ID** and complete the required form. warning Failing to declare the Advertising ID may result in ads not showing during testing or after release. - **Confirm Ad Unit Configuration in FlutterFlow** 1. Open your project in **FlutterFlow**. 2. Navigate to **Settings > AdMob Integration**. 3. Confirm that the correct **Ad Unit IDs** are used. 4. Ensure Ad widgets are connected to the appropriate ad units. - **Test in the Correct Environment** * Use a physical device instead of an emulator when possible. * Ensure the device has a strong internet connection. * Avoid using VPNs or battery optimization tools that may interfere with ad delivery. - **Add app-ads.txt (Optional)** Setting up an `app-ads.txt` file is optional but recommended for better ad quality: * Follow the **[official guide](https://support.google.com/admob/answer/9363762?hl=en\&ref_topic=9675856\&sjid=8136071085841576181-EU)** to set it up. - **Wait for Ad Approval** Even after the app is released: * Live ads may take several days to appear due to the review process and inventory matching. * This delay is expected. If ads still aren’t appearing, contact FlutterFlow Support at --- # Declare Advertising ID for Android 13+ in Play Console If your app targets Android 13 (API 33) or higher, Google Play requires that you declare whether your app uses the **Advertising ID**. Failing to do so will result in an upload error when submitting artifacts to the Play Console. Prerequisites * Your app targets Android 13 (API 33) or above. * The app is being submitted via the **Google Play Console**. When uploading your app to Google Play, you may encounter this error: ``` { "error": { "code": 400, "message": "Your app targets Android 13 (API 33) or above. You must declare the use of advertising ID in Play Console.", "status": "INVALID_ARGUMENT" } } ``` This error occurs when the required declaration for the Advertising ID is missing, incomplete, or inconsistent with your app configuration. Google Play now requires developers targeting Android 13 (API 33) or above to explicitly declare if their app uses the **Advertising ID**. You may see this error if: * You didn't complete the advertising ID declaration in the Play Console. * Your app configuration suggests ad usage but you have not declared it. * Your declaration is incomplete or missing required details. Follow the steps below to fix this error: 1. **Open App Content Section in Play Console:** * Log into your **Google Play Console**. * Navigate to your app's **App Content** section. ![](/assets/images/20250430121230522324-4cc8e39aca512a60d499496ffd4f5c83.png) 2. **Declare Advertising ID Usage** * If your app **does not contain ads**, select **No** under the "Advertising ID" section. ![](/assets/images/20250430121230823138-2950ce06ec96a32cb831f17acdf336b1.png) * If your app **contains ads**, select **Yes** and provide the necessary details about how ads are used. This Declaration is important because Google Play uses this information to: * Inform users about your app’s data collection practices. * Ensure compliance with privacy policies. * Prevent build upload failures. If the issue persists after following these steps, please contact FlutterFlow Support via Chat or email at . --- # Error Running Pod Install This article addresses the common **Error Running Pod Install** issue, which typically occurs due to misconfiguration of Flutter or CocoaPods on macOS devices. Prerequisites * Flutter is installed on your development machine. * You are working on a macOS device. * Basic familiarity with terminal commands. ## Steps to Fix Error Running Pod Install:[​](/troubleshooting/google-play-store-deployment/error-running-pod-install.md#steps-to-fix-error-running-pod-install "Direct link to Steps to Fix Error Running Pod Install:") 1. Verify Flutter is set up correctly by following the official guide: **[Flutter - Get Started: Install on macOS](https://docs.flutter.dev/get-started/install/macos)**. 2. For troubleshooting specific to macOS, consult this guide: **[Troubleshooting Flutter on macOS](https://docs.flutter.dev/get-started/install/macos/mobile-ios#install-cocoapods)**. 3. Run `flutter doctor` in the terminal to check for missing dependencies or configuration issues. 4. Ensure CocoaPods is installed and up to date by running the following commands: ``` sudo gem install cocoapods pod repo update ``` 5. If the problem persists, try deleting the CocoaPods cache and reinstalling: ``` flutter clean ``` ``` flutter pub get ``` ``` cd ios ``` ``` pod install ``` Deleting the `ios/Pods` directory and `ios/Podfile.lock` file before running `pod install` can help resolve lingering CocoaPods issues. --- # Fix Flutter Launcher Icons Package Error This article describes how to resolve the **[flutter\_launcher\_icons package](https://pub.dev/packages/flutter_launcher_icons)** error that may occur during app build or deployment. Prerequisites * Access to your FlutterFlow project. * Ability to open and edit the `pubspec.yaml` file. * Familiarity with your build environment (FlutterFlow, GitHub, or IDE). **Understanding the Error:** During the build process, you might see the following error message: ``` Codemagic Deploy Output Failed Step: Generate Launch Icon Could not find package "flutter_launcher_icons". Did you forget to add a dependency? pub finished with exit code 65. Build failed: Step 5 script 'Generate Launch Icon' exited with status code 65. ``` This error indicates that the **flutter\_launcher\_icons** package is missing or not configured correctly. Follow the steps below to fix the error: 1. **Clear and Reset App Assets in FlutterFlow:** * Navigate to **Settings and Integrations** > **App Assets** inside FlutterFlow. * If the **Splash Screen** and **Launcher Icon** are set: * Clear both assets. * Re-upload the launcher icons. ![](/assets/images/20250430121327988277-fb0bf90a2c5c2fb0d4b1dddccbfe14ad.gif) 2. **`Add flutter_launcher_icons` Package in GitHub Deployment** If you are deploying via GitHub and encounter this error, add the package to your `pubspec.yaml` file: * Open your `pubspec.yaml` file. * Add the following under `dev_dependencies`: ``` dev_dependencies: flutter_launcher_icons: "^0.10.0" flutter_icons: android: true ios: true image_path_ios: "assets/images/launcher/ios.png" image_path_android: "assets/images/launcher/android.png" ``` * \**flutter\_launcher\_icons*: "^0.10.0" specifies the package version. * `image_path_ios` and `image_path_android` specify the paths to your launcher icon images. * Ensure the image files exist at the specified paths. 3. **Run the following commands in your terminal or IDE:** ``` flutter pub get ``` ``` flutter pub run flutter_launcher_icons:main ``` ``` flutter run ``` `flutter pub get` fetches packages. `flutter pub run flutter_launcher_icons:main` generates launcher icons. `flutter run` builds and runs the app. If the issue persists after following these steps, contact FlutterFlow Support at . --- # Google Play Draft Release Error When uploading an app to Google Play, you may encounter the following error: ``` { "error": { "code": 400, "message": "Only releases with status draft may be created on draft app.", "status": "INVALID_ARGUMENT" } } ``` This error occurs because Google Play only allows creating a Draft Release if your app is still marked as a draft in the Google Play Console. Typically, this means some required app information in the Play Console has not been completed, preventing full release submission. Prerequisites * Your app is registered in the Google Play Console. * Basic app details such as store listing and setup information are ready to be filled. This error indicates that Google Play only allows you to create a **Draft Release** when your app is still marked as a draft in your Google Play Console. You likely have missing or incomplete app information in Google Play preventing full release submission. Follow these steps to fix the issue: 1. Complete All Required Information in Google Play Console * Log in to your **Google Play Console**. * Complete all mandatory sections under: * **App Content** * **Store Listing** * **Pricing & Distribution** * **Target Audience & Content Rating** Google Play requires all required information to be filled out before allowing full production releases. 2. **Enable "Submit As Draft" in FlutterFlow** After completing your app information, proceed as follows: * **Open Settings and Integrations**: From your FlutterFlow project dashboard, navigate to **Settings > Integrations**. ![](/assets/images/20250430121320431269-11151722194c41cf6f4b090e04863662.png) * **Navigate to Mobile Deployment**: Select **Mobile Deployment**. ![](/assets/images/20250430121320759595-d43561d2552198d967e63415a52a152c.png) * **Enable Submit As Draft**: Under **Google Play Store Deployment**, toggle on **Submit as Draft**. ![](/assets/images/20250430121321051936-e566e7f0a931aff1babd68a214530c68.png) This allows you to submit your release as a draft until all Google Play requirements are fully satisfied. If you’ve followed all steps and still encounter the issue, contact **FlutterFlow Support** via Chat or email at for additional assistance. --- # Google Play Failed to Upload Artefacts Prerequisites * Ensure your app’s `Package Name` in FlutterFlow matches the package name in Google Play Console. * Firebase is configured in your project settings. * Your Google Play Console account is active and accessible. When uploading your app to Google Play, you may encounter the following error: ``` Google Play failed to upload artefacts. Package not found: com.flutterflow.appname.: { "error": { "code": 404, "message": "Package not found: com.flutterflow.appname.", "status": "NOT_FOUND" } } ``` This error usually occurs in two scenarios: * Deploying the app to Google Play for the first time. * Changing the app’s `Package Name` in FlutterFlow without regenerating the Firebase configuration files. **First Time Deployment to Google Play** Follow these steps to upload your app for the first time: 1. Generate your build in FlutterFlow and click the `AAB` button to download the build artifact. 2. Log in to your **[Google Play Console](https://play.google.com/console)**. 3. Navigate to your app project and upload the **AAB** file as a new release in the appropriate track (Internal, Closed, Open, or Production). 4. After this initial upload, future deployments should proceed without this error. ![](/assets/images/20250430121330484821-3b5795c533eecbd6fce52a72506ed56e.png) **Updating Package Name and Regenerating Config Files** If you have updated your app’s `Package Name` in FlutterFlow, follow these steps: 1. Open your project in FlutterFlow. 2. Navigate to **Settings** > **Firebase**. 3. Click **Regenerate Config Files**. ![](/assets/images/20250430121330727549-7e216628b1bef45cd6867c86b6fd659e.png) 4. Enter the new `Package Name` and click Generate File to download the updated configuration files. ![](/assets/images/20250430121331069027-992a83c99d1aaac98c6db7838fa1782e.png) 5. Rebuild and redeploy your app to confirm the error is resolved. If the error persists after completing these steps: * Verify the `Package Name` matches exactly between FlutterFlow and Google Play Console. * Confirm that Firebase configuration files have been updated correctly. * Contact FlutterFlow Support via Chat or email at . --- # Google Play Store Debug Signing Error When uploading your Android App Bundle (AAB) or APK to Google Play, you might encounter this error: ``` You uploaded an APK or Android App Bundle that was signed in debug mode. You need to sign your APK or Android App Bundle in release mode ``` This error indicates the app must be signed with a release key before uploading. Prerequisites * Access to the Android project files. * Familiarity with editing Gradle build files. **Steps to Fix Debug Signing Error:** 1. Open the `android/app/build.gradle` file in your project folder. 2. Locate the `buildTypes` section and find the configuration labeled `debug`. 3. Replace the `debug` keyword with `release` in the relevant signing configuration. ![](/assets/images/20250430121513060363-77131e391e5a3c171d3df0f670cec56f.png) 4. Save the file. ![](/assets/images/20250430121513225263-f3ae36bad62799f7c0ecbd08ee31e724.png) note Make sure that you fill out all the information in the play store including the store listing information and the setup information. ​ --- # Launcher Icon Missing After Upload Custom app launcher icons may fail to appear after being added in the project settings due to missing icon generation steps. Prerequisites * Flutter is installed on your development machine. * The project code has been downloaded or exported. * Basic familiarity with running terminal commands. **Steps to Resolve Missing Launcher Icon:** 1. Run the launcher icon generation command in the terminal at your project root: ``` flutter pub run flutter_launcher_icons:main ``` This generates the necessary launcher icon assets for your app. 2. Ensure your Flutter environment is properly set up. If needed, follow the official **[Flutter installation guide](https://docs.flutter.dev/get-started/install)**. * Verify your icon files are named correctly and placed in the appropriate directory. * Check that your `pubspec.yaml` includes the correct `flutter_launcher_icons` configuration. * Run `flutter clean` in your project directory before rerunning the icon generation command to clear caches. --- # Migrate to Play Integrity API From SafetyNet Attestation Google is deprecating the **SafetyNet Attestation API**, replacing it with the **Play Integrity API**. This article explains the migration steps needed to maintain app security and compliance with Google Play requirements. Prerequisites * The **SafetyNet Attestation API** is currently used in your Android app. * Preparation for app deployment or maintenance on Google Play is underway. **Migration Steps:** 1. **Begin the Migration Process**
Visit the official migration guide: **[SafetyNet Deprecation & Play Integrity Migration Guide](https://developer.android.com/google/play/integrity/migrate)** 2. **Update Your Backend Implementation** * Replace calls to the **SafetyNet Attestation API** with the **Play Integrity API** in your app code. * Modify your backend to validate responses from the Play Integrity API. 3. **Test Your Migration Thoroughly**
Verify that the Play Integrity API integration works correctly on multiple devices before publishing updates. tip Migrating is critical to: * Comply with the latest security standards. * Maintain access to Google's integrity services. * Benefit from improved error handling and security signals.
Failure to migrate may cause degraded app functionality and user experience. If issues arise during migration, contact FlutterFlow Support at . --- # Signed in Debug Mode Error Prerequisites * Generated an APK or Android App Bundle via **FlutterFlow → Build → Android**. * Access to the exported project folder. * Ability to edit the `android/app/build.gradle` file. When uploading an Android APK or App Bundle to the Play Store or a production environment, the following error may occur: ``` You uploaded an APK or Android App Bundle that was signed in debug mode. You need to sign your APK or Android App Bundle in release mode ``` This error indicates that the build was signed with a debug configuration, which is only for internal testing and not valid for production release. To fix this, update the `build.gradle` file to use the release signing configuration. **Steps to Update Build Configuration:** 1. Open the `android/app/build.gradle` file in your project folder. 2. Locate the `debug` keyword under `buildTypes`. 3. Replace the `debug` keyword with `release` and save the file. If the issue persists, contact FlutterFlow Support at . --- # Version Solving Failed Due to Incompatible Package A **version solving failed** error may occur when running `flutter pub get` if package versions in the project conflict with FlutterFlow's supported Flutter version. ``` Running "flutter pub get" in flutter_tools... 3.4s Resolving dependencies... Because every version of flutter_test from sdk depends on collection 1.15.0 and horse_care_new depends on collection 1.16.0, flutter_test from sdk is forbidden. So, because horse_care_new depends on flutter_test from sdk, version solving failed. pub finished with exit code 1 ``` Prerequisites * Custom actions or widgets are used in the project. * Access to the project's `pubspec.yaml` file. **Steps to Resolve the Error:** * Verify that all packages used in custom actions or widgets are compatible with FlutterFlow's Flutter version. * Before adding a new dependency in your custom widget or action, check if the package already exists in `pubspec.yaml`. If it does, only import the package in your code without adding it again as a dependency. * If no custom widgets or actions are used and the error persists, contact FlutterFlow Support at for assistance. --- # FCM Token Generation Troubleshooting When a user does not have an `fcm_token` sub-collection in their Firestore document, push notifications cannot be delivered to their device. This guide outlines the possible causes and solutions for resolving missing `fcm_token` sub-collections in FlutterFlow apps. **Understanding the Issue** Push notifications require a valid Firebase Cloud Messaging (FCM) token, which is generated when a user logs in or signs up on a physical device. This token is typically stored in the `fcm_token` sub-collection of the user document in Firestore. If this sub-collection is missing, the device cannot receive push notifications. Possible causes for missing tokens include: * Failures during FCM token generation. * Incomplete authentication flows. * Permission issues preventing token creation. * Invalid input data passed to Cloud Functions. Here are the steps to verify user eligibility for push notifications: 1. Check Firestore for `fcm_token` Sub-Collection 1. Open the **Firebase Console**. 2. Navigate to **Firestore Database**. 3. Locate the user document. 4. Verify that the `fcm_token` sub-collection exists. If present, the user is eligible to receive push notifications. ![](/assets/images/20250430121302960895-54aed4f3798bc79637d975fd9c18488a.png) ### Troubleshooting Missing FCM Token Generation[​](/troubleshooting/notifications/fcm-token-generation-troubleshooting.md#troubleshooting-missing-fcm-token-generation "Direct link to Troubleshooting Missing FCM Token Generation") 1. **Verify Cloud Function Execution** The `addFcmToken` Cloud Function is responsible for generating and storing FCM tokens. If token generation fails, review its logs: 1. Open the **Firebase Console**. 2. Navigate to **Functions**. 3. Locate the `addFcmToken` function. 4. Open its **Logs** to review errors or warnings. ![](/assets/images/20250430121303270464-01b82a1aec0dec61adb6565fa42ee0ee.png) 2. **Resolve Permission Errors** Proper permissions are required to allow the Cloud Function to write FCM tokens to Firestore. **Verify Firebase Security Rules** * Ensure your Firebase security rules permit writing to the `users` collection and its sub-collections. **Verify FlutterFlow Service Account Permissions** The `firebase@flutterflow.io` service account must have the following roles: * `Editor` * `Cloud Functions Admin` * `Service Account User` **How to Assign Roles**: 1. Open the **Firebase Console**. 2. Go to **Project Settings > Users & Permissions**. 3. Locate the `firebase@flutterflow.io` service account. 4. Assign any missing roles. Refer to **[this guide](/integrations/firebase/connect-to-firebase.md#connect-an-existing-firebase-project-manually)** for full instructions. 3. **Validate Input Data Passed to Cloud Function** If a Cloud Function fails with status code `400`, it may be receiving invalid input data. * Verify that your authentication flow correctly retrieves the user ID before calling the function. * Ensure the user ID is not `null`, empty, or malformed. * Implement conditional validation before invoking the function. * Add logging to your authentication code and Cloud Functions to trace failures. This is especially important if you are using custom authentication logic. If you are using FlutterFlow's built-in authentication, this issue is unlikely. 4. **Check for FCM Server Errors** Additional reasons FCM token generation may fail include: * FCM server downtime or temporary outages. * Incorrect or malformed requests sent from the Cloud Function to the FCM server. * Insufficient API access permissions. * Invalid or missing input data (e.g. device token). If server issues persist, consider contacting Firebase support for assistance. By following this complete troubleshooting process, you can ensure your users successfully receive push notifications. *** --- # Firebase Push Notification Troubleshooting Push notifications are essential for keeping users informed through timely alerts and updates. However, several common configuration issues can prevent push notifications from working as expected in FlutterFlow projects. This guide outlines potential causes and solutions. Prerequisites Before troubleshooting, ensure the following: * The FlutterFlow app is connected to Firebase. * The app is installed on a physical device (push notifications do not work on simulators). * The user is logged in to the app. * The app is not currently open when testing notifications. 1. **Verify Firebase Blaze Plan Subscription** * Navigate to **Firebase Console > Project Settings > Usage & Billing > Details & Settings**. * Confirm that the subscription is on the **Blaze Plan**. * If the current plan is **Spark**, upgrade by selecting **Modify Plan**. ![](/assets/images/20250430121514497717-35f747df9e6f0c6dc8a48f2d4df1db3e.png) 2. Verify Apple Push Notification (APN) Key Configuration * **Create an APN Key:** * Navigate to the Apple Developer Console. * Go to **Certificates, Identifiers & Profiles > Keys**. * Create a new key for push notifications if one does not exist. ![](/assets/images/20250430121514756330-9d82a66a1e7e3b46bdd41497816f5079.png) Instructions for **[adding a push notification key](https://developer.apple.com/account/resources/authkeys/list)** * **Upload the APN Key to Firebase** * Navigate to **Firebase Console > Project Settings > Cloud Messaging > iOS section**. * Upload the APNs Authentication Key. ![](/assets/images/20250430121515088626-084ba92102053aa15ae7a97621159519.png) Instructions for **[uploading APN key to Firebase](https://firebase.google.com/docs/cloud-messaging/ios/certs)**. 3. **Create Push Notification Identifier for Apple** * Go to the Apple Developer Console. * Navigate to **Certificates, Identifiers & Profiles > Identifiers**. * Create or verify an identifier for push notifications. ![](/assets/images/20250430121515418578-7f61320f7d4fe81294cf8228e2df56fd.png) Instructions for **[creating a push notification identifier](https://developer.apple.com/account/resources/identifiers/list)**. 4. **Verify Cloud Permissions for FlutterFlow Service Account** * Go to **Firebase Console > Project Settings > Users & Permissions**. * Locate the **** service account. * Ensure the following roles are assigned: * Editor * Cloud Functions Admin * Service Account User ![](/assets/images/20250430121515666267-f0e035f033238446b162c7eacbb6af13.png) Instructions to **[add required cloud permissions](/integrations/firebase/connect-to-firebase.md#allow-flutterflow-to-access-your-project)**. 5. **Confirm Cloud Function Region Consistency** * In **FlutterFlow > Settings > Firebase > Advanced Settings**, verify the Cloud Functions Region matches the region configured in **Firebase > Project Settings > Cloud Functions Location**. ![](/assets/images/20250430121515990341-cb8dfe481eb7782d65eedc674d617f20.png) ![](/assets/images/20250430121516228961-e32ba97b88a386f77aa588720976f146.png) 6. **Update FlutterFlow to Latest Version** **Refresh FlutterFlow:** * On Windows: Press `Ctrl + R`. * On Mac: Press `Cmd + R`. **Clear Browser Cache:** Clear the browser cache to ensure the latest version loads properly. 7. **Resolve FlutterFlow Insufficient Permissions Error** If an insufficient permissions error occurs: 1. Open **Firebase Console > Project Settings > Users & Permissions**. 2. Verify the **** account exists. 3. Assign the following permissions: * Editor * Cloud Functions Admin * Service Account User ![](/assets/images/20250430121516955662-6322dcfe8d6656e3d837fdc3e1bd3928.png) 4. Save changes and retry the operation in FlutterFlow. ![](/assets/images/20250430121517242675-be5188a887ac9adbfdd77e1f2148702a.png) --- # Firebase Push Notifications on Web FlutterFlow currently does not support sending Firebase push notifications on web apps natively. However, Firebase itself supports this capability. This guide outlines alternative approaches to enable Firebase push notifications on web projects built with FlutterFlow. ## Workarounds for Implementing Web Push Notifications[​](/troubleshooting/notifications/firebase-push-notifications-on-web.md#workarounds-for-implementing-web-push-notifications "Direct link to Workarounds for Implementing Web Push Notifications") There are two primary methods to implement Firebase web push notifications in FlutterFlow projects: * **Use Custom Actions:** * Create custom actions in FlutterFlow that utilize Firebase Cloud Messaging (FCM) to send push notifications. * This method requires writing custom code to handle notification logic and integrate it into FlutterFlow. * Custom actions offer flexibility for handling different types of notifications based on the app’s needs. * The Firebase Web SDK can be used alongside your FlutterFlow project to achieve this. Refer to official Firebase documentation for detailed steps on **[setting up web push notifications](https://firebase.google.com/docs/cloud-messaging/js/client)**. * **Use Back-End Functions:** * Implement server-side code using Firebase Functions or any other backend service. * Backend functions handle sending notifications independently of the FlutterFlow frontend. * This approach allows using the Firebase Admin SDK to programmatically send push notifications to targeted web clients. * Backend solutions also offer better scalability, error handling, and control over notification delivery. note * Web push notification support requires properly configured Firebase Cloud Messaging, service workers, and valid VAPID keys. * FlutterFlow may add native support for web push notifications in future updates as the platform evolves. --- # Fix Insufficient Permissions for Push Notifications If you encounter an **"Insufficient Permissions"** error when deploying push notifications from FlutterFlow to Firebase, it usually means the `firebase@flutterflow.io` service account does not have the necessary permissions in your Firebase project. This guide will walk you through how to resolve this issue. Prerequisites Before proceeding, ensure you have: * Connected your Firebase project to FlutterFlow. * Completed the steps in **[Connect to Firebase](/integrations/firebase/connect-to-firebase.md#allow-flutterflow-to-access-your-project)**. **Steps to Resolve the Insufficient Permissions Error:** 1. **Open Firebase Console** * Go to the **[Firebase Console](https://console.firebase.google.com/)**. * Click on your project tile to open your FlutterFlow project. 2. **Navigate to Users & Permissions:** * In the Firebase project dashboard, click on the gear icon (⚙️) to open **Project Settings**. * From the left sidebar, select **Users & Permissions**. ![](/assets/images/20250430121228826304-9171280a069d17c7274a0b43294ac183.png) 3. **Locate the `firebase@flutterflow.io` Account** * In the **Users** tab, search for `firebase@flutterflow.io`. * If this account is missing, click **Add User**, enter `firebase@flutterflow.io` as the email address, and continue. 4. **Assign the Required Permissions** * Click on `firebase@flutterflow.io` to open the user details. * Ensure the following roles are assigned: * **Editor** * **Cloud Functions Admin** * **Service Account User** ![](/assets/images/20250430121229163844-6322dcfe8d6656e3d837fdc3e1bd3928.png) * If any permissions are missing, click **Add Permissions** and select the missing roles. 5. **Save Changes:** * After assigning all necessary roles, click **Save** to apply changes. * Verify that all permissions have been successfully added and saved. 6. **Retry the Operation in FlutterFlow:** * Return to your FlutterFlow project. * Retry the action that previously failed due to insufficient permissions. The error should now be resolved. If you continue to experience issues, please contact the FlutterFlow Support team. note Granting the correct permissions to `firebase@flutterflow.io` is essential for FlutterFlow to deploy push notifications and access Firebase resources correctly. ![](/assets/images/20250430121229476348-be5188a887ac9adbfdd77e1f2148702a.png) Additional Resources * [Connect FlutterFlow to Firebase](/integrations/firebase/connect-to-firebase.md#allow-flutterflow-to-access-your-project) * [Firebase Roles and Permissions](https://firebase.google.com/docs/projects/iam/roles) --- # Fix Push Notifications Sent to Zero Devices Push notifications allow apps to send updates, alerts, and messages directly to users. In some cases, after triggering a push notification, FlutterFlow displays the following message: ``` Push Notification sent to 0 devices ``` This means that the notification was attempted, but no eligible devices received it. Here are the causes: * No registered devices have generated FCM tokens. * Target devices were offline at the time of sending. * Misconfiguration in Firebase or FlutterFlow settings. * Missing permissions or API configuration. * Recipient devices have blocked push notifications. The following steps below outline how to troubleshoot and resolve this issue: 1. **Verify Firebase Functions Are Enabled** * Ensure that Firebase Functions are enabled in the Firebase Console. * Confirm that your project is on the Blaze Plan. ![](/assets/images/20250430121213011292-1babf61949379581747118828e03c164.png) 2. **Delete and Redeploy Firebase Cloud Functions** * Manually delete the Cloud Functions related to push notifications from Firebase. ![](/assets/images/20250430121213284704-3afcc81ff3752e6b58e4d2e156ee73c5.png) * After deletion, redeploy Push Notifications from FlutterFlow: ![](/assets/images/20250430121213612267-3689521eba4ddac2ee7b5144c76dfcf6.png) 3. **Verify Server Region Configuration** * Ensure that the Firebase server region matches the configuration in FlutterFlow. * For example, if the server region is `us-central1`, it must match in both Firebase and FlutterFlow. In FlutterFlow: Navigate to **Settings > Firebase > Advanced Settings** and set the correct region. ![](/assets/images/20250430121214190877-0f8fa7167e3863fd0e98d1b7932ee2ac.png) In Firebase: Verify that Cloud Functions are deployed to the same region. ![](/assets/images/20250430121214486513-73b41f5de85f618a905f3d41ab672a67.png) 4. **Check FCM API Settings in Google Cloud Console** * Open the **[Google Cloud Console](https://console.cloud.google.com/)**. * Search for `FCM API` and ensure it is enabled. ![](/assets/images/20250430121214790195-9418483016c92cf5d1f7acfaa3b1b71c.png) * Make sure that a valid server key is available in Firebase Console. If missing, create one through Google Cloud Console. 5. **Verify Cloud Permissions for flutterflow\.io Service Account** To ensure proper communication between FlutterFlow and Firebase: * Step 1: Open Firebase Console * Go to [Firebase Console](https://console.firebase.google.com/). * Select your project. * Step 2: Navigate to Users & Permissions * Open **Project Settings** via the gear icon (⚙️). * Select **Users & Permissions**. ![](/assets/images/20250430121215127010-9171280a069d17c7274a0b43294ac183.png) * Step 3: Verify Existing Permissions * Locate the `firebase@flutterflow.io` service account. * Verify the following roles are assigned: * `Editor` * `Cloud Functions Admin` * `Service Account User` ![](/assets/images/20250430121215442199-6322dcfe8d6656e3d837fdc3e1bd3928.png) * Step 4: Add Missing Permissions * If any roles are missing: * Click **Add Member**. * Enter `firebase@flutterflow.io`. * Select missing roles from the dropdown: * `Editor` * `Cloud Functions Admin` * `Service Account User` ![](/assets/images/20250430121215729191-be5188a887ac9adbfdd77e1f2148702a.png) * Step 5: Verify All Permissions Are Applied * Confirm that all required roles now appear next to the service account. Following these steps should resolve most push notification delivery issues. --- # Black Screen During Preview If your app screen appears blank during Run Mode, follow these steps to resolve the issue: Prerequisites * You have already built and deployed at least one screen in your project. * You are running the app in **Run Mode** within the editor. 1. **Reload the Frame** Right-click on the preview screen and select **Reload Frame**. 2. **Change the Device** Use the device selector on the left panel to switch to a different preview device. 3. **Refresh the Page** Press `Ctrl + R` (Windows) or `Cmd + R` (Mac) to refresh the browser. 4. **Update FlutterFlow and Clear Cache** * Ensure you are using the latest version. * Clear your browser cache. * Log out and back in to your FlutterFlow account. 5. **Submit a Bug Report** If none of the steps work, submit a bug report using the **Send Feedback** button in FlutterFlow. ![](/assets/images/20250430121528287666-5ce70b2435c1ba565426093b5f908131.png) tip Blank screens are often temporary. Try switching devices or reloading before making major changes to your project. --- # Firestore Permission Error in Run Mode When previewing your app in Run Mode, you may encounter the following error message: **Firestore Security Rules: Missing or insufficient permissions** This occurs when your Firestore rules conflict with the permissions required for a query in your app. Prerequisites * You are using Firebase Firestore in your FlutterFlow project. * Your project has one or more Firestore queries configured. This error is typically triggered when: * Firestore rules prevent any user from reading the database. * A page attempts to run a query before a user is authenticated (e.g., querying user-specific data on the login page). Example: * If Firestore rules are configured as: ``` rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if false; } } } ``` Any Firestore query will fail because no read or write access is allowed. * If rules allow only authenticated access: ``` allow read, write: if request.auth != null; ``` And a query is placed on a page before the user signs in (e.g., on the login screen), it will trigger this error. Descriptive widget names can help you quickly identify which query or widget is triggering the permission issue. In the example above, the error message references a widget named Container. Renaming it to something like UserQueryContainer can make debugging easier. Take the steps below to fix this error: * **Review Firestore Rules** Go to Firestore → Settings → Rules and verify that your access rules align with how and when your app queries the database. * **Adjust Query Placement** Ensure that queries requiring authentication are not used on screens accessible to unauthenticated users. * **Use Conditional Visibility** If a query must exist on a pre-login screen, wrap it in conditional logic to only execute when the user is signed in. tip Test queries using the Run Mode Console and check the browser logs for more specific errors. Use Firestore Schema Validation in FlutterFlow to ensure your rules are properly deployed. --- # Gray Screen in Run Mode Seeing a gray screen in Run Mode usually points to a configuration issue in your Firebase or project settings. Follow these steps to diagnose and resolve the issue. Prerequisites * You have integrated Firebase with your FlutterFlow project. * You have access to your Firebase Console. 1. **Check Firebase Permissions** Ensure that has the following roles: * **Editor** * **Cloud Functions Admin** * **Service Account User** To verify: 1. Go to the **Firebase Console**. 2. Select your project → **Project Overview**. 3. Navigate to **Users and permissions** → **Advanced permissions**. 4. Locate and ensure it has the roles listed above. ![](/assets/images/20250430121529462395-fdde1719fe77b55aa50ec3df4e3744b0.png) If missing, click the pencil icon and assign the roles. 2. **Regenerate Firebase Configuration Files** 1. In FlutterFlow, go to **Settings & Integrations** → **Firebase**. 2. Click **Regenerate Config Files**. 3. In the popup, click **Generate Files**. ![](/assets/images/20250430121530070855-7e8912c83c1c2afb165d4762e7e4d84d.png) tip You must regenerate config files if you change your project name in FlutterFlow or Firebase. 3. **Update Firebase Rules** 1. In FlutterFlow, go to **Firestore** → **Settings**. 2. Scroll to **Firestore Rules** and click **Deploy**. 3. Confirm by selecting **Deploy Now** in the popup. ![](/assets/images/20250430121530401837-7055a97d7146894eafc1a13985ef7065.jpg) A green checkmark indicates success. 4. **Validate Firebase Schema** 1. In **Firestore** → **Settings**, scroll to **Firebase Schema Validation**. 2. Click **Validate**. ![](/assets/images/20250430121530999303-6bf58af56d0c6b82d655917f4b89ce88.jpg) If the schema is valid, you’ll see a success message. If not, review the identified issues. ![](/assets/images/20250430121531448037-98813d12cfb2a8261c32e01bb494151c.png) 5. **Ensure Collections Have Data** An empty Firestore collection can result in a gray screen. Visit the Firebase Console → **Firestore Database** to confirm your collections contain documents. ![](/assets/images/20250430121531723554-2d6543b11bba69cadfb4f34b0f265649.png) 6. **Verify Custom Widget Compatibility** If your app uses a custom widget, make sure its package supports web. On **[pub.dev](https://pub.dev)**, check that **WEB** is listed under platforms. ![](/assets/images/20250430121531973906-ddd21c7e53708e9079602d171afa3222.png) If not, choose an alternative package. 7. **Refresh FlutterFlow Environment** * Press Ctrl + R (Windows) or Cmd + R (Mac) to refresh FlutterFlow. * Clear your browser cache. * Log out and back in. tip Refreshing your session can fix slow or buggy behavior in the UI Builder. 8. **Retest the Project** After completing the above steps, create a new Run Mode session to test if the gray screen issue is resolved. 9. **Test Locally** If the issue persists, download your FlutterFlow code and run the project locally to diagnose further. Additional Resources * **[Run Flutter App Locally](/testing/local-run.md)** * **[FlutterFlow Firebase Integration Guide](/integrations/firebase/connect-to-firebase.md#step-1-set-up-your-project)** --- # Loading Spinner in Run Mode A persistent loading spinner in FlutterFlow's Run Mode usually indicates an issue with your Firestore rules configuration. Updating your rules can resolve this issue. Prerequisites * You have already connected your FlutterFlow project to Firebase. * You have access to your Firebase Console. Here are the steps to fix this error: 1. **Copy Firestore Rules from FlutterFlow** 1. Open your project. 2. Navigate to **Firestore** → **Settings**. 3. Click the **Copy** icon to copy the default Firestore rules. ![](/assets/images/20250430121355282620-97cec6fdabc1b155638a88186ec7cd62.gif) 2. **Paste the Rules in Firebase Console** 1. Open the **[Firebase Console](https://console.firebase.google.com/)**. 2. Select your project and go to **Firestore Database**. 3. Open the **Rules** tab. 4. Paste the copied rules into the editor and click **Publish**. ![](/assets/images/20250430121355575413-0179d33777cb89357eecedad825c3070.gif) 3. **Retest Your Project in FlutterFlow** Return to FlutterFlow and run your project again in **Run Mode**. The loading spinner should no longer appear if the Firestore rules were configured correctly. tip Always keep your Firestore rules up to date after making structural changes to your database in FlutterFlow. --- # Local Build ProviderInstaller Error This error commonly occurs when building Flutter apps on Android emulators. It is related to the `ProviderInstaller` service and can typically be resolved through basic cleanup and Flutter version upgrades. Prerequisites * You are testing or running your Flutter project on an Android emulator. * You have Flutter and Android Studio installed and configured. 1. **Uninstall the App from the Emulator** Before rebuilding your app, ensure the old installation is removed: 1. Open the Android Emulator. 2. Locate your app icon and uninstall it. 3. Alternatively, run the following command from your terminal: ``` adb uninstall com.yourcompany.yourapp ``` Replace com.yourcompany.yourapp with your actual app ID. 2. **Rebuild the App** After uninstalling: Run the following command in your project directory: ``` flutter clean ``` ``` flutter pub get ``` ``` flutter run ``` This will remove cached data and reinstall the app on the emulator. 3. **Upgrade Flutter (If Problem Persists)** If the issue continues, upgrading Flutter may help. Run the command below to upgrade: ``` flutter upgrade ``` Ensure your Flutter SDK is up to date. You can verify the version with: ``` flutter --version ``` note This error is often related to Google Play Services not being properly initialized on the emulator. If you're still encountering issues, consider creating a new emulator using a system image that includes the Play Store. Additional Resources * Read the official **[Flutter Build Documentation](https://docs.flutter.dev/testing/build-modes)**. * Check **[Android Emulator System Images](https://developer.android.com/studio/run/managing-avds#system-images)**. --- # Slow Loading in Test Mode If Test Mode takes several minutes to load or fails entirely, the issue may stem from your browser, network, or project configuration. This guide walks you through the most common causes and how to resolve them. Prerequisites * You are using FlutterFlow's Test Mode feature. * You have already deployed or previewed a version of your app. **Steps to Resolve Slow Loading:** * **Check Your Internet Connection** A weak or unstable connection may delay the loading of compiled apps. Make sure you have a stable network before launching Test Mode. * **Sync Your System Clock** Ensure your device’s time and date settings are accurate. An incorrect clock can cause authentication issues and impact performance. * **Clear Browser Cache** Browsers store temporary files that may interfere with page loading. Clearing your cache can resolve stale resource conflicts and improve speed. * **Try a Different Browser** Some browsers may conflict with specific web assets or settings. If one browser is slow, switch to another (e.g., from Chrome to Firefox). * **Disable Browser Extensions** Extensions like ad blockers or privacy tools can interfere with FlutterFlow’s platform. Temporarily disable them to check for improvement. Optimize Your Project Projects with many pages, assets, or custom code may take longer to compile. Follow these steps to optimize your project: * Remove unused images, fonts, or icons. * Consolidate or simplify custom code. * Limit the number of pages in a single testing session. Additional Resources If the issue persists after following the steps above, check the **[official support](https://intercom.help/flutterflow/en/articles/7052737-test-mode-is-not-loading-or-is-very-slow-it-takes-a-long-time-to-load-the-app)** article. Following these steps should resolve most Test Mode performance issues and reduce load times for future previews. --- # Test API Calls Verifying an API response before integrating it into your app helps prevent runtime issues and ensures your data is structured correctly. This guide walks you through testing an API directly within FlutterFlow. Prerequisites * A project is open in FlutterFlow. * An API key or endpoint is available if required by the API. **Steps to Test API Calls:** 1. **Open the `API Calls` Panel** From the left sidebar, go to the `API Calls` section. ![](/assets/images/20250430121444122926-bf78dbe544e70f93a7407dd066ab6d49.png) 2. **Select or Create an API Call** Choose an existing `API Call` or click `+ Add API Call` to create a new one. ![](/assets/images/20250430121444364083-3d7576af0612eb40005483e67956bfe9.png) 3. **Enter the API Endpoint** Add the endpoint and necessary parameters, headers, or authentication. ![](/assets/images/20250430121444571412-40417a9e3bacd13a6bdadb8d0ec44022.png) 4. **Click the `Response & Test` Tab** Navigate to the `Response & Test` tab to preview the response structure. ![](/assets/images/20250430121444783602-6c0e9c6851a4645a4cbfc0120c06e445.png) 5. **Run the API Test** Click the `Test API Call` button to trigger the request. If successful, the API response displays in JSON format. ![](/assets/images/20250430121445020637-d8c0b6a4a88e89945efcb6487f901642.png) A valid API response displays a structured output like the example below:: ![](/assets/images/20250430121445238952-ca370fce28cc37d1d114d4da9b593eb7.png) tip Use **[JSONPath](https://jsonpath.com/)** to validate and extract values from the returned JSON structure during testing. --- # Fix Google Translate Errors FlutterFlow integrates with Google Translate to help localize your app automatically. This guide outlines how to identify and resolve common issues with the translation integration. Prerequisites * Google Translate integration must be enabled for the project. * At least one supported language must be added in **App Settings > Localization**. * Review the [Google Translate Integration](/concepts/localization.md#add-multi-language-support) guide for setup instructions. ## Common Translation Issues and Fixes[​](/troubleshooting/translations/fix-google-translate-errors.md#common-translation-issues-and-fixes "Direct link to Common Translation Issues and Fixes") * **Long Text Forms**
**Problem:** Attempting to translate long blocks of text in forms or widgets can lead to API timeouts or failures.
**Solution:** Remove long text elements and translate them outside of FlutterFlow using external tools like Google Translate. Once translated, manually paste the content back into your project. Ensure the input field is empty before retrying automatic translation. * **Special Characters**
**Problem:** Some special characters—such as emojis, accented symbols, or non-Latin characters—may not be supported by the Google Translate API and can cause translation to fail.
**Solution:** Review the text and replace or remove any unsupported special characters. Then attempt the translation again. * **Exceeding Language Limit**
**Problem:** Adding more than 10 language options in your project may result in translation failure.
**Solution:** Limit your project to a maximum of 10 supported languages for translation to work reliably with Google Translate. ## Steps to Troubleshoot Translation Failures[​](/troubleshooting/translations/fix-google-translate-errors.md#steps-to-troubleshoot-translation-failures "Direct link to Steps to Troubleshoot Translation Failures") 1. **Locate the Problem Area**
Identify the specific widget, page, or field where translation fails. Focusing on the problematic component will make resolution faster. 2. **Use the Translate All Button**
In **App Settings > Localization**, click the **Translate All** button. The process will stop at the first failure, indicating the field or element causing the issue. 3. **Check Chrome Developer Console**
Open the Chrome DevTools console and monitor for any error logs related to translation requests. This can help identify issues such as invalid characters, request failures, or unsupported content. 4. **Remove and Isolate Problematic Text**
Temporarily delete the suspected text and retry the translation. If the translation proceeds successfully, that text is likely causing the failure. Manually translate and reinsert it. note Using shorter, plain-text strings without special characters improves success rates with the Google Translate API. Additional Help If the issue persists after troubleshooting, reach out to with the following: * Screenshot or screen recording of the failure * Console error logs (if available) * A description of where the failure occurs (page/widget/text field) This will help the support team resolve the issue faster. --- # Custom Widget Errors This article demonstrates common errors and issues that may occur when creating a `Custom Widget` in FlutterFlow, along with steps to resolve them. In this example, an `Animated Text Widget` is used. ![](/assets/images/20250430121322843622-c050cb0de4edcae7c8f0b355c0d1cbc0.gif) **Project URL:** [Animated Kit Widget Project](https://app.flutterflow.io/project/animated-kit-widget-fyqw6j) **Run Mode URL:** [Animated Kit Widget Run Mode](https://app.flutterflow.io/run/QP62FwanUTRs7O3HJzdo) Prerequisites * A custom widget has been added to your project. * Necessary packages have been added to **Custom Code > Packages**. Best Practices Before Creating a Custom Widget * Set a unique name for the custom widget in the left panel `Side Widget` field. * Start with the boilerplate code template provided by FlutterFlow. Copy it and modify your code from there. ![](/assets/images/20250430121323364253-2ec75fa5a0a999b940f42df1e62600fc.gif) **Common Errors and Solutions:** * **Widget Name Conflicts with Package Name** A common issue is using a widget name that conflicts with the name of an imported package. Avoid generic or conflicting names like `main` or `widget`. Use unique widget names that do not overlap with package names. ![](/assets/images/20250430121324152439-03c0a9f6e48a39760356762c6f92d182.png) ![](/assets/images/20250430121324382074-cce6c4b49b75d0c7cf167c99bb384323.png) Avoid using generic or conflicting names like `main` or `widget`. Always use unique widget names that do not overlap with any package names. * **Missing Package Imports in Code** After adding an external package as a dependency, you must import it at the top of your custom widget code. Failure to do so results in errors such as: ``` The method 'AnimatedText' isn't defined... ``` ![](/assets/images/20250430121324695186-b868bac41d84f6149620fb5cf28bc38c.png) Here is how to fix this issue: * Visit the package page on **[pub.dev](https://pub.dev/)** and locate the import line in the package details section. * Copy and paste the correct import statement into your custom widget code. ![](/assets/images/20250430121324981835-de0769611f3ddebf627bd5848965516b.png)
![](/assets/images/20250430121325311155-ffc69bca85d2e981b3bfe20e27edac57.png) * **Missing Indirect Dependencies** Some packages may rely on additional external packages. Ensure that all required dependencies are also imported in your code. ![](/assets/images/20250430121325659677-fd2659d65e96a310697c8f95f506ed47.png) In this example, the package depends on another package named `silver_tools`, which must also be imported. Always review the dependency chain for any external packages you add. ![](/assets/images/20250430121325972589-5ae05af75dca1a7c9f5a46e73b724a4b.png) * **Widget Name Mismatch Between UI and Code** A mismatch between the widget name in FlutterFlow and the class name in your code will cause compilation errors. Incorrect example: ![](/assets/images/20250430121326300880-a8bb8140772717c439f71c0c31bc0b9e.png) Corrected version with matching names: ![](/assets/images/20250430121326628836-42a2cc3c3d2e93715a5cbb6beabf95e5.png) Ensure that the widget name matches exactly in both places. By following these best practices and carefully reviewing package imports, dependencies, and widget names, most common issues with `Custom Widgets` in FlutterFlow can be avoided. --- # Emoji Size on iOS Devices On iOS devices, emojis can appear oversized when rendered inside text widgets, disrupting the intended design and layout. This guide explains how to maintain consistent emoji sizing across all devices using container constraints and auto-sizing configuration. Prerequisites * You are using a `Text` widget that includes emojis. * You are targeting iOS devices as part of your app deployment. ## Steps to Maintain Consistent Emoji Size[​](/troubleshooting/widget/emoji-size-on-ios-devices.md#steps-to-maintain-consistent-emoji-size "Direct link to Steps to Maintain Consistent Emoji Size") 1. **Wrap the Text Widget in a Container**
Create a `Container` with fixed width and height (example `32x32 pixels`) to restrict the emoji size. 2. **Place the Emoji Inside a Text Widget**
Add a `Text` widget containing the emoji and place it inside the container. 3. **Set a Font Size**
Apply a specific font size to the `Text` widget (example, `16`, `24`, etc.). 4. **Enable Auto-Size**
Turn on **Auto-Size** in the `Text` widget to allow responsive resizing within the fixed container. ![](/assets/images/20250430121253238523-5b0095421ea62c6cfa11ca4b39e2eb9d.png) This ensures that the emoji will resize according to the container's constraints and not exceed the intended bounds. tip Auto-Size works best when combined with fixed container dimensions. This approach prevents oversized emojis and supports responsive layouts. --- # Infinite Scroll Pagination in ListView If a `ListView` with **Infinite Scroll** enabled loads all items at once instead of paginating, the issue is typically related to layout configuration. This guide outlines how to correctly structure the widget for proper pagination behavior. Prerequisites * Infinite Scroll is enabled in the `ListView`. * The widget is placed inside a layout that allows height constraints to be respected. Follow the steps below to configure ListView for pagination: 1. **Ensure ListView Has a Defined Height**
A `ListView` must have a height constraint to determine the viewport size and paginate correctly. Without a defined height, it will attempt to load all items. 2. **Let ListView Handle Its Own Scrolling** * Disable scrolling in any parent `Column` or scrollable container. * Enable the **Primary** option, and wrap `ListView` in an `Expanded` widget. * This allows `ListView` to control scroll behavior and calculate items to load per page. ![](/assets/images/20250430121248035007-63bc015cf137d22fc50337da21f3a90e.png) 3. **Wrap ListView Inside a Fixed-Height Container (if nested)**
If `ListView` is inside a scrollable parent (like `Column` or `ListView`), wrap it in a `Container` with a defined height (e.g., `500px`). This ensures it doesn't expand indefinitely. ![](/assets/images/20250430121248379992-14e1ef9e72be0c45c03a56f55f8b68b1.png) 4. **Avoid Missing Height Constraints**
Without constraints, `ListView` will not know the visible size and will load all data at once, bypassing pagination. warning Placing `ListView` directly inside a scrollable parent without a defined height will break Infinite Scroll behavior. 5. **Use Layout Structure That Supports Scroll Isolation**
Allow `ListView` to scroll independently before the parent scroll takes over. Combine this with defined height and `Expanded` usage for best results. ![](/assets/images/20250430121249048672-6671c857c81494bec61769372aae4a77.gif) tip To optimize pagination, define consistent item heights and test using varying screen sizes. Additional Resources * **[ListView Scroll Example Project](https://app.flutterflow.io/project/list-view-scroll-example-wdv076)** – View a working configuration example. --- # Rive Animation Loading Errors Rive animations may fail to render when the source file is incorrectly linked. This guide outlines how to provide a valid `.riv` file URL for successful animation loading. Prerequisites * A valid Rive animation is hosted online with a `.riv` extension. * The animation is added to a FlutterFlow widget that supports Rive. ## Steps to Fix Rive Animation Not Loading[​](/troubleshooting/widget/rive-animation-loading-errors.md#steps-to-fix-rive-animation-not-loading "Direct link to Steps to Fix Rive Animation Not Loading") 1. **Verify the Rive File URL**
Ensure the file URL ends with `.riv` and points directly to a hosted Rive file. ``` https://public.rive.app/community/runtime-files/1199-2317-jack-olantern.riv ``` If the URL points to a webpage or lacks the `.riv` extension, the animation will not load in FlutterFlow. 2. **Copy the Correct Link from Rive Community:** * Go to the animation page on the **[Rive Community](https://rive.app/community/)**. * Right-click the **Download** button. * Select Copy Link Address. The copied link must end with `.riv`. Any URL that redirects to a webpage or file viewer will fail to render. --- # Scroll To Action on Page Load When a `Scroll To Action` fails to trigger during a page load, it is often because the scrollable widget has not fully rendered at the time the action executes. This guide outlines how to ensure the scroll action works reliably during page load. Prerequisites * The `Scroll To Action` is configured inside an `On Page Load` action flow. * The target widget is inside a scrollable view such as `ListView` or `Column`. ## Steps to Ensure Reliable Scroll Behavior:[​](/troubleshooting/widget/scroll-to-action-on-page-load.md#steps-to-ensure-reliable-scroll-behavior "Direct link to Steps to Ensure Reliable Scroll Behavior:") 1. **Add a Delay Before the Scroll Action**
Insert a `Delay Action` before the `Scroll To Action` to allow the widget tree to complete rendering. Recommended delay duration is 500 to 700 ms. ![](/assets/images/20250430121250453056-db9b60be4173ea5f88d908ab4673546a.png) 2. **Use Load Animations for Scrollable Widgets**
Applying an animation ensures the widget is fully visible before scrolling. * Add a load animation (e.g., `Fade`) to the scrollable widget. * Set the animation duration to approximately `1200 ms`. * Add a `Delay Action` before the scroll action (e.g., `700 ms`). ![](/assets/images/20250430121250214649-b4e5617b809194b82b868f3a40d38d17.png) tip Combining a delay with animation prevents the scroll action from executing before the widget appears, creating a smoother transition. --- # Store Custom Widget Output Using App State To use the output from a custom widget elsewhere in your project, you can store its value in an app state variable. FlutterFlow does not directly support retrieving data from custom widgets, so this method provides an effective workaround. Prerequisites * You have created a custom widget in your project. * You are familiar with the **[App State management](/resources/data-representation/app-state.md)** system in FlutterFlow. ## Steps to Store Output from a Custom Widget[​](/troubleshooting/widget/store-custom-widget-output-using-app-state.md#steps-to-store-output-from-a-custom-widget "Direct link to Steps to Store Output from a Custom Widget") 1. **Create an App State Variable**
Go to **App State**, then create a new app state variable that will hold the value returned by your custom widget. ![](/assets/images/20250430121220879251-67b746b90666bc17fb112c6558d78583.png) 2. **Update the App State Variable from the Custom Widget**
In your custom widget code, use `FFAppState()` to set the value of the app state variable. ![](/assets/images/20250430121221066642-eafa9c31015ec78d924fb47ad3774de8.png) ``` FFAppState().update(() { FFAppState().localvalue = 'setvalue'; }); ``` App state variables can be accessed anywhere in your FlutterFlow project, making them useful for sharing data between custom widgets and other parts of the app. ---