# How to Create Custom Code in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 20-30 min
- Compatibility: FlutterFlow Pro+ (code export recommended for testing)
- Last updated: March 2026

## TL;DR

FlutterFlow supports three types of custom Dart code: Custom Actions (async logic like API calls), Custom Functions (synchronous data transformation), and Custom Widgets (full Flutter UI components). Access them all from the left sidebar under Custom Code. Each type serves a different purpose — choosing the right one saves hours of debugging.

## Extending FlutterFlow with Dart Code

FlutterFlow's visual builder covers most app needs, but sometimes you need logic or UI that the drag-and-drop editor cannot express. Custom Code lets you drop into raw Dart/Flutter for exactly those cases. There are three distinct types, each with its own access pattern and constraints. Understanding which type to use — and why — is the key skill this tutorial teaches.

## Before you start

- A FlutterFlow project already created (Free plan works for writing; Pro for code export)
- Basic familiarity with FlutterFlow's widget canvas
- No prior Dart experience required, but helpful
- Understanding of what Actions and Functions mean in FlutterFlow's visual builder

## Step-by-step guide

### 1. Navigate to Custom Code and choose your code type

In the FlutterFlow editor, look at the far left sidebar. You will see icons for Pages, Components, and several others. Click the icon that looks like angle brackets (</>) — this opens the Custom Code panel. Inside, you will see three sections: Custom Actions, Custom Functions, and Custom Widgets. Each has a '+' button to create a new item. Take a moment to read the brief description FlutterFlow shows for each type before creating anything. This orientation step prevents the most common mistake: creating a Custom Action when you actually need a Custom Function.

**Expected result:** The Custom Code panel is open showing three sections: Custom Actions, Custom Functions, and Custom Widgets.

### 2. Create a Custom Action for Async Logic

Click '+' next to Custom Actions. Name it 'fetchWeatherData'. Custom Actions are async Dart functions — they can await HTTP calls, read device storage, call Firebase, or do anything that requires waiting. In the code editor that appears, you will see a pre-generated function signature. The function receives typed parameters you define in the Arguments panel on the right, and returns a value you declare in the Return Value section. Add a String parameter named 'city'. Set the return type to String. Write your async logic inside the function body. Custom Actions are invoked from Action Flows — drag them from the Action palette onto a button's On Tap event.

```
// Custom Action: fetchWeatherData
// Arguments: city (String)
// Return Value: String

Future<String> fetchWeatherData(String city) async {
  final response = await http.get(
    Uri.parse(
      'https://api.openweathermap.org/data/2.5/weather?q=$city&appid=YOUR_KEY',
    ),
  );

  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    final temp = data['main']['temp'];
    final description = data['weather'][0]['description'];
    return '$description, ${(temp - 273.15).toStringAsFixed(1)}°C';
  } else {
    return 'Could not fetch weather for $city';
  }
}
```

**Expected result:** A Custom Action named fetchWeatherData appears in the Custom Actions list with a green checkmark indicating no compile errors.

### 3. Create a Custom Function for Synchronous Transformations

Click '+' next to Custom Functions. Name it 'formatCurrency'. Custom Functions are SYNCHRONOUS pure Dart functions — they take inputs and immediately return an output with no waiting, no async, no await. They are ideal for formatting numbers, parsing strings, calculating values, and transforming data for display in widgets. In the editor, define your parameters (a double named 'amount' and a String named 'currencyCode') and a String return type. Write the pure transformation logic. Custom Functions appear in the Expression Editor throughout FlutterFlow — you can use them directly in widget Text bindings, Condition expressions, and anywhere you type a formula.

```
// Custom Function: formatCurrency
// Arguments: amount (double), currencyCode (String)
// Return Value: String
// IMPORTANT: No async/await — this is synchronous only

String formatCurrency(double amount, String currencyCode) {
  final symbols = {'USD': '\$', 'EUR': '€', 'GBP': '£', 'JPY': '¥'};
  final symbol = symbols[currencyCode] ?? currencyCode;

  if (amount >= 1000000) {
    return '$symbol${(amount / 1000000).toStringAsFixed(1)}M';
  } else if (amount >= 1000) {
    return '$symbol${(amount / 1000).toStringAsFixed(1)}K';
  }
  return '$symbol${amount.toStringAsFixed(2)}';
}
```

**Expected result:** The formatCurrency function appears under Custom Functions and is available in the Expression Editor when binding widget properties.

### 4. Create a Custom Widget for Flutter UI

Click '+' next to Custom Widgets. Name it 'GradientProgressBar'. Custom Widgets are full Flutter StatefulWidget or StatelessWidget classes. They appear as draggable components in the FlutterFlow widget palette, just like built-in widgets. Use them when you need UI that FlutterFlow's visual tools cannot produce — custom painters, complex animations, third-party package widgets, or any rendering logic requiring raw Flutter code. Define widget parameters in the Parameters panel on the right (e.g., a double named 'progress' and a Color named 'startColor'). These parameters become configurable Properties in the FlutterFlow UI when the widget is placed on a canvas.

```
// Custom Widget: GradientProgressBar
// Parameters: progress (double, 0.0-1.0), startColor (Color), endColor (Color)

class GradientProgressBar extends StatelessWidget {
  final double progress;
  final Color startColor;
  final Color endColor;
  final double height;

  const GradientProgressBar({
    Key? key,
    required this.progress,
    required this.startColor,
    required this.endColor,
    this.height = 12.0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(height / 2),
      child: SizedBox(
        height: height,
        child: Stack(
          children: [
            Container(color: Colors.grey.shade200),
            FractionallySizedBox(
              widthFactor: progress.clamp(0.0, 1.0),
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [startColor, endColor],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
```

**Expected result:** The GradientProgressBar widget appears in the widget palette under Custom Widgets and can be dragged onto any page canvas.

### 5. Wire a Custom Action to a Button

Navigate to a page in your app. Select a Button widget on the canvas. In the right panel, click the Actions tab, then click '+' to add an action. In the Action Flow Editor, click '+' on the flow and search for your Custom Action by name — type 'fetchWeather' and it will appear under the Custom Actions section. Select it. FlutterFlow will prompt you to bind the 'city' argument — tap the binding icon and choose a TextField's value, an App State variable, or type a literal string. Set the Action Output Name (e.g., 'weatherResult') so you can reference the return value in subsequent actions. Add a second action: Update App State → set a variable to 'weatherResult'. Now a Text widget bound to that variable will update after the button tap.

**Expected result:** Tapping the button in the FlutterFlow preview triggers the Custom Action, and the Text widget updates with the returned weather string.

### 6. Test and Debug Custom Code

FlutterFlow compiles your Dart code in real time — red underlines in the editor mean syntax errors, and the panel shows a compile error banner. Fix all errors before leaving the Custom Code panel. For runtime debugging, use the FlutterFlow Run Mode (the play button, top-right) to test in the browser. For deeper debugging — print statements, breakpoints — click the '</> View Code' button (top-right) to open the full generated Flutter project, then run it in your local VS Code or Android Studio with a connected device. Custom Functions can also be tested inline: the Expression Editor shows a live preview of the return value when you type test inputs into the argument fields.

**Expected result:** No compile errors in the Custom Code panel, and the feature works end-to-end in Run Mode.

## Complete code example

File: `custom_code_examples.dart`

```dart
// ============================================================
// FlutterFlow Custom Code — Three Types Demonstrated
// ============================================================

// --- 1. CUSTOM ACTION (async) ---
// File: custom_actions/fetch_weather_data.dart
// Use from: Action Flow Editor → Custom Actions

Future<String> fetchWeatherData(String city) async {
  final response = await http.get(
    Uri.parse(
      'https://api.openweathermap.org/data/2.5/weather?q=$city&appid=YOUR_KEY',
    ),
  );
  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    final temp = data['main']['temp'];
    final desc = data['weather'][0]['description'];
    return '$desc, ${(temp - 273.15).toStringAsFixed(1)}°C';
  }
  return 'Weather unavailable';
}

// --- 2. CUSTOM FUNCTION (synchronous only) ---
// File: custom_functions/format_currency.dart
// Use from: Expression Editor anywhere in FlutterFlow

String formatCurrency(double amount, String currencyCode) {
  final symbols = {'USD': '\$', 'EUR': '€', 'GBP': '£', 'JPY': '¥'};
  final symbol = symbols[currencyCode] ?? currencyCode;
  if (amount >= 1000000) {
    return '$symbol${(amount / 1000000).toStringAsFixed(1)}M';
  } else if (amount >= 1000) {
    return '$symbol${(amount / 1000).toStringAsFixed(1)}K';
  }
  return '$symbol${amount.toStringAsFixed(2)}';
}

// --- 3. CUSTOM WIDGET (Flutter UI component) ---
// File: custom_widgets/gradient_progress_bar.dart
// Drag from the widget palette onto any page

class GradientProgressBar extends StatelessWidget {
  final double progress;
  final Color startColor;
  final Color endColor;
  final double height;

  const GradientProgressBar({
    Key? key,
    required this.progress,
    required this.startColor,
    required this.endColor,
    this.height = 12.0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(height / 2),
      child: SizedBox(
        height: height,
        child: Stack(
          children: [
            Container(color: Colors.grey.shade200),
            FractionallySizedBox(
              widthFactor: progress.clamp(0.0, 1.0),
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [startColor, endColor],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
```

## Common mistakes

- **Writing async/await inside a Custom Function** — Custom Functions are strictly synchronous in FlutterFlow. The compiler will not allow async, await, or Future return types. FlutterFlow enforces this because Custom Functions are used inside the Expression Editor, which evaluates synchronously during widget builds. Fix: If your logic needs to fetch data, call an API, or wait for anything, create a Custom Action instead. Move the async logic there, and use the Action Output to pass the result back to the UI via App State or Page State.
- **Declaring a Custom Widget with no default parameter values for optional parameters** — FlutterFlow calls your Custom Widget constructor when placing it on the canvas before any properties have been configured. If required parameters have no defaults, the widget throws a null error and shows a red error box on the canvas. Fix: Add sensible default values for all parameters that are not strictly required, for example: 'this.height = 12.0' and 'this.startColor = Colors.blue'. Mark only truly required parameters with the 'required' keyword.
- **Adding package imports in Custom Code without adding the package to pubspec dependencies** — Writing 'import package:http/http.dart as http' in a Custom Action will fail to compile if 'http' is not listed in the project's pubspec.yaml. FlutterFlow manages dependencies separately from the code editor. Fix: Go to Settings (gear icon, top-right) → Pubspec Dependencies → search for the package name (e.g., 'http') → toggle it on. The dependency is then available in all Custom Code files.
- **Using a Custom Action where a Custom Function would work** — Custom Actions are invoked from Action Flows only — you cannot use their return value directly in a widget Text binding or condition expression. Wrapping simple transformations like string formatting in an Action adds unnecessary complexity and requires App State variables to pass the result. Fix: For pure data transformations (format a string, calculate a value, combine two strings), use a Custom Function. Its return value is available directly in the Expression Editor throughout the entire FlutterFlow UI.
- **Hardcoding API keys directly in Custom Action code** — Custom Action code is compiled into the app binary and can be extracted by anyone who decompiles the APK or IPA. Hardcoded secrets in client-side code are a critical security vulnerability. Fix: Store secrets in Firebase Remote Config or a backend environment variable. Call a Cloud Function that holds the key server-side, and have the Custom Action call that Cloud Function instead of the external API directly.

## Best practices

- Name Custom Actions with verb phrases (fetchUserProfile, sendPushNotification) and Custom Functions with noun phrases (formattedDate, userDisplayName) so their type is clear from usage.
- Keep Custom Functions under 20 lines. If the logic is complex, break it into multiple focused functions rather than one large one.
- Always handle null and error cases in Custom Actions — unhandled exceptions in async code crash the app silently in FlutterFlow Run Mode.
- Use the Arguments panel to type-check Custom Action inputs rather than accepting dynamic/Object and casting inside the function body.
- Test Custom Functions using the Expression Editor's inline preview before wiring them to a widget — type sample inputs to verify the output instantly.
- Store sensitive configuration (API base URLs, keys) in App State constants or Firebase Remote Config, not as hardcoded strings in Custom Code.
- Add a brief comment at the top of every Custom Code file explaining what it does and which widget or action flow uses it — FlutterFlow has no automatic documentation.
- For Custom Widgets that use third-party packages, pin the package version in pubspec.yaml to avoid breaking changes when FlutterFlow auto-updates dependencies.

## Frequently asked questions

### What is the difference between a Custom Action and a Custom Function in FlutterFlow?

Custom Actions are async Dart functions invoked from Action Flows (e.g., on button tap). They can await network calls, device APIs, and Firebase. Custom Functions are synchronous pure functions available in the Expression Editor for data transformation — they cannot use async/await. Use Actions for 'do something', Functions for 'transform something'.

### Do I need to know Dart to use Custom Code in FlutterFlow?

Basic Dart syntax helps but is not mandatory for simple cases. FlutterFlow generates boilerplate for you, and most Custom Functions only require a return statement with straightforward logic. For complex Custom Widgets involving animations or custom painters, intermediate Dart/Flutter knowledge is recommended.

### Can I use external Flutter packages in Custom Code?

Yes. Go to Settings → Pubspec Dependencies, search for the package (e.g., 'http', 'intl', 'fl_chart'), and enable it. The package is then importable in your Custom Code files. FlutterFlow Pro or higher is required to export and run the full project locally.

### Why does my Custom Widget show a red error box on the FlutterFlow canvas?

This usually means the widget threw an error during construction — most commonly because a required parameter has no default value and FlutterFlow passes null when the widget is first placed. Add default values to all optional parameters. You can also check the error details by hovering over the red box.

### Can Custom Functions access App State or Firestore?

No. Custom Functions are pure synchronous functions with no access to Flutter's context, FlutterFlow's app state, or any async data source. If you need to read App State or Firestore, use a Custom Action instead, which has access to the FFAppState singleton.

### How do I pass data from a Custom Action back to the UI?

In the Action Flow Editor, set an 'Action Output' name when you configure the Custom Action. This output is available as a local variable in subsequent actions in the same flow. Use an 'Update App State' or 'Update Page State' action immediately after to store the value, then bind a widget to that state variable.

### Can I use Custom Widgets on both mobile and web in FlutterFlow?

Yes, as long as the packages your Custom Widget uses support all platforms. Most Flutter UI packages are cross-platform. If you use a package that is mobile-only (e.g., camera or GPS), the widget will fail on web. Check pub.dev platform support badges before adding a package.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-custom-code-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-custom-code-in-flutterflow
