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
Design the Firestore schema for business rules
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.
1// Firestore document structure for business_rules collection2// Example: apply a 10% discount when order total > 1003{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.
Write the Cloud Function rule evaluation engine
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'.
1// functions/index.js2exports.evaluateRules = functions.https.onCall(async (data, context) => {3 const { inputData, ruleContext } = data;45 // Load active rules for this context6 const rulesSnap = await admin.firestore()7 .collection('business_rules')8 .where('active', '==', true)9 .where('context', '==', ruleContext)10 .orderBy('priority', 'asc')11 .get();1213 const firedRules = [];1415 rulesSnap.docs.forEach(doc => {16 const rule = doc.data();17 const conditions = rule.conditions || [];18 const combinator = rule.combinator || 'AND';1920 const results = conditions.map(cond => evaluateCondition(inputData, cond));2122 const passed = combinator === 'AND'23 ? results.every(Boolean)24 : results.some(Boolean);2526 if (passed) {27 firedRules.push({28 ruleId: doc.id,29 name: rule.name,30 action: rule.action,31 });32 }33 });3435 return { firedRules };36});3738function 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.
Call the rule engine from FlutterFlow
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.
1// Custom Action: callRuleEngine2import 'dart:convert';3import 'package:http/http.dart' as http;45Future<List<dynamic>> callRuleEngine(6 Map<String, dynamic> inputData,7 String ruleContext,8) async {9 // Using Firebase callable via HTTP10 // Replace with your Firebase project region and ID11 const String functionUrl =12 'https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/evaluateRules';1314 final idToken = await FirebaseAuth.instance.currentUser?.getIdToken();15 if (idToken == null) return [];1617 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 );3031 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.
Build the admin rules CRUD UI in FlutterFlow
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.
Add rule evaluation logging for debugging
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.
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
1// Firebase Cloud Function: Business Rule Engine2// Evaluates configurable rules stored in Firestore3// Deploy: firebase deploy --only functions:evaluateRules45const functions = require('firebase-functions');6const admin = require('firebase-admin');78admin.initializeApp();910/**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);1718 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}3435exports.evaluateRules = functions36 .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 }4243 const { inputData, ruleContext } = data;4445 if (!inputData || !ruleContext) {46 throw new functions.https.HttpsError(47 'invalid-argument',48 'inputData and ruleContext are required'49 );50 }5152 const rulesSnap = await admin.firestore()53 .collection('business_rules')54 .where('active', '==', true)55 .where('context', '==', ruleContext)56 .orderBy('priority', 'asc')57 .get();5859 const firedRules = [];6061 rulesSnap.docs.forEach(doc => {62 const rule = doc.data();63 const conditions = rule.conditions || [];64 const combinator = rule.combinator || 'AND';6566 const results = conditions.map(c => evaluateCondition(inputData, c));67 const passed = combinator === 'AND'68 ? results.every(Boolean)69 : results.some(Boolean);7071 if (passed) {72 firedRules.push({73 ruleId: doc.id,74 name: rule.name,75 action: rule.action,76 });77 }78 });7980 // Audit log81 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 });8889 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation