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

How to Implement a Custom Rule Engine for Business Logic in FlutterFlow

A rule engine lets business users change pricing, validation, and notification logic from a Firestore admin UI without deploying new code. Store rules as JSON conditions in Firestore, evaluate them server-side in a Cloud Function, and call the evaluator from your FlutterFlow app. The critical point: never evaluate rules on the client — users can bypass them.

What you'll learn

  • Designing a Firestore schema for configurable business rules with JSON conditions
  • Writing a Cloud Function that loads and evaluates rules server-side
  • Calling the rule engine from FlutterFlow and acting on the result
  • Building an admin CRUD UI so non-technical users can manage rules
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read50-70 minFlutterFlow Pro+ (code export required for complex Custom Actions)March 2026RapidDev Engineering Team
TL;DR

A rule engine lets business users change pricing, validation, and notification logic from a Firestore admin UI without deploying new code. Store rules as JSON conditions in Firestore, evaluate them server-side in a Cloud Function, and call the evaluator from your FlutterFlow app. The critical point: never evaluate rules on the client — users can bypass them.

Business logic that changes without code deployments

Most business apps have rules that change frequently: discount conditions, approval thresholds, validation requirements, notification triggers. Hardcoding these in your FlutterFlow app means every change requires a code update and re-deploy. A rule engine externalizes these rules into a database. Each rule has a name, a set of conditions (field + operator + value), and an action (discount amount, message text, next step). A Cloud Function loads the relevant rules, evaluates the conditions against the input data, and returns which rules fired and what to do. The FlutterFlow app calls this function and reacts to the output — no app update needed when rules change.

Prerequisites

  • FlutterFlow project connected to Firebase with Firestore enabled
  • Firebase Cloud Functions set up and deployed at least once
  • Basic understanding of Firestore collections and Cloud Functions
  • An admin panel page in FlutterFlow (or willingness to create one)

Step-by-step guide

1

Design the Firestore schema for business rules

Create a 'business_rules' collection in Firestore. Each document represents one rule. The structure uses a conditions array (each item has a field, operator, and value) and an action object (type and parameters). Conditions can be combined with 'AND' or 'OR' logic using a combinator field. Add the rule documents manually in the Firebase console first — you'll build the admin UI later. Create 2-3 test rules to validate your engine logic before building the evaluation function. Rules are loaded by the Cloud Function, not by the client app, so they are never exposed to users.

firestore_rule_schema.json
1// Firestore document structure for business_rules collection
2// Example: apply a 10% discount when order total > 100
3{
4 "name": "Bulk Order Discount",
5 "description": "10% discount on orders over $100",
6 "active": true,
7 "priority": 1,
8 "combinator": "AND",
9 "conditions": [
10 { "field": "orderTotal", "operator": "gt", "value": 100 },
11 { "field": "customerType", "operator": "eq", "value": "standard" }
12 ],
13 "action": {
14 "type": "apply_discount",
15 "params": { "percentage": 10, "label": "Bulk discount" }
16 },
17 "created_at": "<timestamp>",
18 "updated_at": "<timestamp>"
19}

Expected result: A 'business_rules' collection in Firestore with 2-3 test rule documents visible in the Firebase console.

2

Write the Cloud Function rule evaluation engine

Create a Cloud Function named 'evaluateRules' that accepts an input data object and a context string (e.g., 'checkout', 'user_registration'). It loads all active rules for that context from Firestore, evaluates each rule's conditions against the input, and returns an array of fired rules with their action objects. The evaluation is pure server-side logic — the client never sees the raw rule definitions, only the results. Deploy with 'firebase deploy --only functions:evaluateRules'.

functions/index.js
1// functions/index.js
2exports.evaluateRules = functions.https.onCall(async (data, context) => {
3 const { inputData, ruleContext } = data;
4
5 // Load active rules for this context
6 const rulesSnap = await admin.firestore()
7 .collection('business_rules')
8 .where('active', '==', true)
9 .where('context', '==', ruleContext)
10 .orderBy('priority', 'asc')
11 .get();
12
13 const firedRules = [];
14
15 rulesSnap.docs.forEach(doc => {
16 const rule = doc.data();
17 const conditions = rule.conditions || [];
18 const combinator = rule.combinator || 'AND';
19
20 const results = conditions.map(cond => evaluateCondition(inputData, cond));
21
22 const passed = combinator === 'AND'
23 ? results.every(Boolean)
24 : results.some(Boolean);
25
26 if (passed) {
27 firedRules.push({
28 ruleId: doc.id,
29 name: rule.name,
30 action: rule.action,
31 });
32 }
33 });
34
35 return { firedRules };
36});
37
38function evaluateCondition(data, condition) {
39 const { field, operator, value } = condition;
40 const actual = field.split('.').reduce((o, k) => o?.[k], data);
41 switch (operator) {
42 case 'eq': return actual === value;
43 case 'neq': return actual !== value;
44 case 'gt': return actual > value;
45 case 'gte': return actual >= value;
46 case 'lt': return actual < value;
47 case 'lte': return actual <= value;
48 case 'contains': return String(actual).includes(String(value));
49 case 'in': return Array.isArray(value) && value.includes(actual);
50 default: return false;
51 }
52}

Expected result: Cloud Function deployed. Test it from the Firebase console with sample input data — it should return the correct firedRules array for conditions that match.

3

Call the rule engine from FlutterFlow

Create a Custom Action in FlutterFlow named 'callRuleEngine'. It accepts the inputData (as a JSON map) and the ruleContext string. It calls the Firebase evaluateRules Cloud Function via the cloud_firestore_platform_interface callable approach, or via an HTTP POST to the function URL. It returns the firedRules list. In your checkout flow Action Flow, add this Custom Action before completing the order — pass the order total, customer type, and other relevant fields as inputData. Then add Conditional actions that check the returned firedRules for specific action types (e.g., 'apply_discount') and update the UI accordingly.

call_rule_engine_action.dart
1// Custom Action: callRuleEngine
2import 'dart:convert';
3import 'package:http/http.dart' as http;
4
5Future<List<dynamic>> callRuleEngine(
6 Map<String, dynamic> inputData,
7 String ruleContext,
8) async {
9 // Using Firebase callable via HTTP
10 // Replace with your Firebase project region and ID
11 const String functionUrl =
12 'https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/evaluateRules';
13
14 final idToken = await FirebaseAuth.instance.currentUser?.getIdToken();
15 if (idToken == null) return [];
16
17 final response = await http.post(
18 Uri.parse(functionUrl),
19 headers: {
20 'Content-Type': 'application/json',
21 'Authorization': 'Bearer $idToken',
22 },
23 body: jsonEncode({
24 'data': {
25 'inputData': inputData,
26 'ruleContext': ruleContext,
27 },
28 }),
29 );
30
31 if (response.statusCode != 200) return [];
32 final result = jsonDecode(response.body);
33 return result['result']?['firedRules'] ?? [];
34}

Expected result: Custom Action callable from FlutterFlow Action Flows. Returns a list of fired rule objects with name and action fields.

4

Build the admin rules CRUD UI in FlutterFlow

Create a new page called 'Admin Rules'. Protect it with a role check — only users with admin: true in their Firestore user document should see this page. Add a Backend Query loading all documents from the business_rules collection. Display them in a ListView with each rule's name, active status toggle, context, and an Edit button. Add a FloatingActionButton to create new rules. The New Rule form has fields for name, context (dropdown), combinator (AND/OR dropdown), conditions (start with one condition row and an Add Condition button), and action type with its parameters. Use Firestore Create/Update Document actions to save changes.

Expected result: Admin page shows all rules in a list. Admins can toggle rules active/inactive, edit conditions and actions, and create new rules — all without touching code.

5

Add rule evaluation logging for debugging

Add Firestore logging to the Cloud Function so you can audit rule evaluations: which rules fired, on what input, at what time. This is invaluable for debugging why a discount didn't apply or why a validation failed. Create a 'rule_evaluations' collection and write a document on each call. Include the input data, fired rules, and timestamp. Add a read-only evaluation log page in the admin UI so non-technical admins can audit rule decisions without needing Firebase console access.

functions/index.js (logging addition)
1// Add to evaluateRules Cloud Function, after calculating firedRules:
2if (context.auth) {
3 await admin.firestore().collection('rule_evaluations').add({
4 userId: context.auth.uid,
5 ruleContext,
6 inputData,
7 firedRules: firedRules.map(r => ({ ruleId: r.ruleId, name: r.name })),
8 evaluatedAt: admin.firestore.FieldValue.serverTimestamp(),
9 });
10}

Expected result: Every rule evaluation creates an audit log document. The admin UI can display evaluation history for debugging and compliance.

Complete working example

evaluate_rules_function.js
1// Firebase Cloud Function: Business Rule Engine
2// Evaluates configurable rules stored in Firestore
3// Deploy: firebase deploy --only functions:evaluateRules
4
5const functions = require('firebase-functions');
6const admin = require('firebase-admin');
7
8admin.initializeApp();
9
10/**
11 * Evaluates a single condition against input data.
12 * Supports dot-notation field paths (e.g., 'user.tier').
13 */
14function evaluateCondition(data, condition) {
15 const { field, operator, value } = condition;
16 const actual = field.split('.').reduce((obj, key) => obj?.[key], data);
17
18 switch (operator) {
19 case 'eq': return actual === value;
20 case 'neq': return actual !== value;
21 case 'gt': return actual > value;
22 case 'gte': return actual >= value;
23 case 'lt': return actual < value;
24 case 'lte': return actual <= value;
25 case 'contains': return String(actual ?? '').includes(String(value));
26 case 'starts_with': return String(actual ?? '').startsWith(String(value));
27 case 'in': return Array.isArray(value) && value.includes(actual);
28 case 'not_in': return Array.isArray(value) && !value.includes(actual);
29 default:
30 console.warn(`Unknown operator: ${operator}`);
31 return false;
32 }
33}
34
35exports.evaluateRules = functions
36 .region('us-central1')
37 .runWith({ memory: '256MB', timeoutSeconds: 15 })
38 .https.onCall(async (data, context) => {
39 if (!context.auth) {
40 throw new functions.https.HttpsError('unauthenticated', 'Login required');
41 }
42
43 const { inputData, ruleContext } = data;
44
45 if (!inputData || !ruleContext) {
46 throw new functions.https.HttpsError(
47 'invalid-argument',
48 'inputData and ruleContext are required'
49 );
50 }
51
52 const rulesSnap = await admin.firestore()
53 .collection('business_rules')
54 .where('active', '==', true)
55 .where('context', '==', ruleContext)
56 .orderBy('priority', 'asc')
57 .get();
58
59 const firedRules = [];
60
61 rulesSnap.docs.forEach(doc => {
62 const rule = doc.data();
63 const conditions = rule.conditions || [];
64 const combinator = rule.combinator || 'AND';
65
66 const results = conditions.map(c => evaluateCondition(inputData, c));
67 const passed = combinator === 'AND'
68 ? results.every(Boolean)
69 : results.some(Boolean);
70
71 if (passed) {
72 firedRules.push({
73 ruleId: doc.id,
74 name: rule.name,
75 action: rule.action,
76 });
77 }
78 });
79
80 // Audit log
81 await admin.firestore().collection('rule_evaluations').add({
82 userId: context.auth.uid,
83 ruleContext,
84 firedRuleIds: firedRules.map(r => r.ruleId),
85 firedCount: firedRules.length,
86 evaluatedAt: admin.firestore.FieldValue.serverTimestamp(),
87 });
88
89 return { firedRules };
90 });

Common mistakes when implementing a Custom Rule Engine for Business Logic in FlutterFlow

Why it's a problem: Evaluating business rules on the client side in FlutterFlow Custom Functions

How to avoid: Always evaluate rules in a Cloud Function. The client only receives the evaluation result (e.g., 'discount applied: 10%'), never the raw rule conditions and thresholds.

Why it's a problem: Not adding a 'context' field to rules, causing all rules to evaluate on every call

How to avoid: Add a 'context' string field to each rule ('checkout', 'user_registration', 'order_approval') and filter by it in the Cloud Function query.

Why it's a problem: Storing rules in App State or hardcoding them as constants in Custom Actions

How to avoid: Store all rules exclusively in Firestore. The Cloud Function reads them at evaluation time so changes take effect immediately for all users without any app update.

Best practices

  • Always evaluate rules server-side — never expose raw rule conditions to the client
  • Add a priority field so conflicting rules resolve predictably
  • Log every rule evaluation with userId, context, and fired rule IDs for auditing
  • Set TTL policies on audit log collections to auto-delete old records and control costs
  • Cache frequently-used rule sets in Cloud Function memory (module-level variable) to reduce Firestore reads
  • Version your rules by adding a version field — this lets you roll back to a previous rule set if needed
  • Build the admin UI with role-based access so only authorized users can add or modify rules

Still stuck?

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

ChatGPT Prompt

I'm building a business rule engine in Firebase Cloud Functions. I want a function that loads rules from a Firestore 'business_rules' collection (filtered by an 'active' boolean and a 'context' string), evaluates each rule's conditions array against an input data object, and returns which rules fired with their action objects. Each condition has a field (dot-notation path), operator (eq/gt/lt/contains/in), and value. Support both AND and OR combinators. Include audit logging to a rule_evaluations collection.

FlutterFlow Prompt

In FlutterFlow, I have a checkout page with an order total and customer type. I want to call a Firebase Cloud Function 'evaluateRules' with these values and then check the returned firedRules array. If any rule has action.type == 'apply_discount', I want to subtract the discount percentage from the displayed order total. How do I build this Action Flow in FlutterFlow, including the Custom Action to call the function and the conditional logic to apply the discount?

Frequently asked questions

What types of business logic work well in a rule engine?

Pricing rules (discounts, surcharges), validation requirements (required fields change by user type), notification triggers (when to send emails or push notifications), approval workflows (auto-approve orders under $500, require review above), and feature flags (enable features for specific user segments or dates).

How complex can rule conditions get?

The example engine supports AND/OR combinators with basic comparison operators. For more complex needs, add nested condition groups (conditions that are themselves AND/OR groups) by making each condition optionally a nested array. For very complex logic, consider the json-rules-engine npm package, which handles nested conditions, facts with async functions, and priority chaining.

How do I handle rule evaluation errors gracefully in the app?

In your FlutterFlow Custom Action, wrap the Cloud Function call in a try-catch. If the function errors, default to a 'no rules fired' response — the app continues with default behavior. Never block a user transaction because rule evaluation failed. Log the error for debugging but don't surface it to the user.

Can non-technical admins manage rules without understanding JSON?

Yes, with the admin UI described in Step 4. Build form fields for each part of the rule — dropdown for operator, text field for value, dropdown for action type. The UI converts these to the JSON structure before saving to Firestore. The admin never sees raw JSON.

How many rules can the engine evaluate before it gets slow?

With the Firestore query filtering by context and active status, typical apps have 10-50 active rules per context. Evaluating 50 rules server-side in a Cloud Function takes under 10ms. The bottleneck is the Firestore read — use composite indexes on (active, context, priority) to keep that query fast.

Can I use Supabase instead of Firestore for storing rules?

Yes. Store rules in a Supabase table instead of Firestore collection. Use a Supabase Edge Function for evaluation instead of a Firebase Cloud Function. The rule schema and evaluation logic is identical — only the database client and function runtime differ.

RapidDev

Talk to an Expert

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

Book a free consultation

Need help with your project?

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

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.