1. Trigger design — be specific
The most common Flow Designer performance problem comes from overly broad triggers. A record-based trigger with no condition on the Incident table fires on every single incident field change across your entire instance — potentially thousands of executions per hour on a busy production instance.
// Bad trigger — fires on every incident update, every save
Table: incident
When: Updated
Condition: (none)
// Good trigger — fires only when the specific relevant change occurs
Table: incident
When: Updated
Condition: State changes to Resolved AND Resolved by is not empty
Trigger design guidelines:
- Always use a trigger condition on record-based triggers — no exceptions on high-volume tables
- Use "changes to" rather than "equals" — "State = Resolved" fires on every update of a resolved incident; "State changes to Resolved" fires only on the transition
- Use "For each unique change" run trigger for state transitions — prevents re-firing when a condition stays true across multiple saves
- Consider table volume — a trigger on cmdb_ci fires constantly during Discovery; scope it to specific CI classes
- Test trigger specificity in a non-prod environment — enable flow execution tracking and observe how often the trigger fires before promoting to production
For the complete trigger reference, see Flow Designer triggers.
2. Subflows for reusable logic
Any logic that appears in more than one flow should be extracted into a subflow. Any logic that is complex enough to benefit from independent testing should be in a subflow. Any logic that represents a business concept ("Send Manager Escalation", "Create Approval Task") should be a named subflow even if only one flow calls it.
The investment in a well-designed subflow library pays dividends quickly:
- Bug fixes in a subflow propagate to all flows that use it — you fix once, everywhere improves
- New flows are faster to build when common operations are pre-built
- Flows that call well-named subflows are readable without opening each step
- Subflows can be tested independently without triggering a specific record change
Good subflow candidates: sending notifications, creating tasks with standard logic, calling external APIs, looking up manager chains, validating and formatting data. For patterns and examples, see the complete subflows guide.
3. Error handling is not optional
A flow without error handling is a flow that will silently fail the moment something goes wrong. REST calls time out. Records are not found. Rate limits are hit. Services go down. In production, these failures go unnoticed until a user raises a ticket.
The minimum error handling standard for every production flow:
- Try/Catch around every external API call — never let a network failure crash a flow silently
- Meaningful log record in every Catch block — enough context to diagnose the failure without re-triggering it
- Status update on the triggering record — "Integration Failed" or "Pending Manual Review" so the user knows something went wrong
- Notification or work item for the operations team — create an incident or case, not just an email that gets missed
- Flow-level fault handler as a safety net — catches anything that escapes your Try/Catch blocks
For complete patterns with examples, see Flow Designer error handling.
4. Always type your inputs and outputs
Flow Designer allows untyped inputs and outputs, but you should always define explicit types. Typed inputs prevent runtime errors, make the flow readable to maintainers, and enable the condition builder to suggest appropriate operators and values.
// Bad — untyped, ambiguous
Input: record_id (String)
// Good — typed, clear
Input: incident_record (Reference: Incident)
// Or if you only have the sys_id:
Input: incident_sys_id (String) — Description: sys_id of the Incident to process
For subflow inputs and outputs especially, explicit types with descriptions make the subflow usable by other developers who did not build it.
5. One lookup per record, not one per step
Flow Designer makes it easy to add a Look Up Records step whenever you need data. But each lookup is a separate database query. If five consecutive steps each look up the same related record, you are making five identical database queries.
// Bad — four lookups on the same user record
Step 1: Look Up User → assigned_to
Step 2: Look Up User → assigned_to (for department)
Step 3: Look Up User → assigned_to (for manager)
Step 4: Look Up User → assigned_to (for location)
// Good — one lookup, dot-walk for related fields
Step 1: Look Up User → assigned_to
Then use: user_record > Department (dot-walked)
user_record > Manager (dot-walked)
user_record > Location (dot-walked)
If you need data from multiple related tables, consider combining the lookups into a Script step that queries all needed data in one block, or use a subflow that accepts a sys_id and returns a structured object with all needed values.
6. Use flow variables for data that multiple steps need
Data pills from a step are only accessible from steps that come after it in the flow. Flow variables are accessible from any step, in any branch, at any point in the flow. Use them to store computed values that multiple steps need:
// Define flow variables in Flow Properties:
// escalation_level (Integer) — tracks how many times this has been escalated
// assignment_group_name (String) — for use in multiple notification steps
// is_vip_customer (Boolean) — controls multiple branches
// Set in an early Script step:
outputs.escalation_level = calculateEscalationLevel(fd_data.incident_record);
// Read in any later step — no matter how many branches there are between
7. Name everything clearly
Flows, subflows, steps, and variables should have names that describe what they do — not what they are technically. "Run Script" tells you nothing; "Calculate Resolution SLA Status" tells you everything.
// Bad names
Flow: Incident BR Replacement
Step: Run Script
Subflow: SN Utils
Variable: x
// Good names
Flow: Incident State Change Automation
Step: Calculate Remaining SLA Time
Subflow: Send P1 Escalation Notification
Variable: remaining_sla_minutes
Name flows with a consistent pattern that makes them findable: "TableName - Trigger Condition - Purpose" works well at scale — "Incident - Resolved - Survey and Closure", "Change - Approved - Provisioning Start".
8. Never hardcode sys_ids
Sys_ids in flow steps will be different between development, test, and production instances. A flow that works perfectly in development fails immediately in production because the hardcoded sys_id does not exist.
// Bad — hardcoded sys_id breaks between instances
Step: Look Up Records where sys_id = "6816f79cc0a8016401c5a33be04be441"
// Good option 1 — look up by a stable identifier
Step: Look Up Records where Name = "Service Desk"
// Good option 2 — use a System Property
Step: Look Up Records where sys_id = gs.getProperty('my_app.service_desk_group_id')
// Good option 3 — pass as a flow input or subflow input
9. Version control — never edit active flows directly
Flow Designer supports versioning. Before making any change to a flow that is active in production:
- Create a new draft version of the flow
- Make your changes in the draft
- Test in a non-production environment
- Publish the new version, which replaces the active version
- The old version remains available for rollback if needed
Editing an active flow directly overwrites the production version with no rollback path if the change causes issues. Never do it.
10. Test error paths, not just the happy path
The happy path — everything works as expected — is easy to test manually. Error paths require deliberate effort:
- What happens when the REST API returns a 500? Test it by pointing at a bad endpoint temporarily.
- What happens when the lookup finds no record? Test it by passing a non-existent sys_id.
- What happens when the flow fires with missing optional inputs? Test it.
Build Automated Test Framework (ATF) tests for your flows, especially for critical business processes. ATF tests can trigger flows with specific input data and verify that outputs are correct — enabling automated regression testing when the flow is modified.
11. Monitor flow executions in production
Set up regular monitoring of Flow Error Records (Flow Designer > Flow Error Records). Consider building a separate monitoring flow that:
- Runs every hour
- Queries for new error records on critical flows
- Creates an incident for any unhandled failures
- Sends a summary digest to the automation team
Proactive monitoring catches silent failures before they become user-reported incidents.
Common anti-patterns to avoid
Anti-pattern 1 — Flow nesting deeper than two levels
Flow → Subflow → Subflow is the practical limit before execution becomes impossible to trace. Execution Details shows three levels clearly; deeper nesting becomes confusing. If you find yourself nesting further, refactor the design.
Anti-pattern 2 — Using flows for high-frequency operations
If a record updates hundreds of times per hour (CMDB CI updates during Discovery, high-volume audit records, real-time sensor data), a Flow Designer trigger creates hundreds of executions per hour. For high-frequency, low-complexity automation, a Business Rule or Scheduled Script Execution is more efficient.
Anti-pattern 3 — Skipping the Publish step
An unpublished flow does not fire. Ever. Developers who build and test a flow but forget to publish it discover this when the expected automation does not happen in production. Publish immediately after successful testing.
Anti-pattern 4 — Putting complex logic in Script steps that should be in Script Includes
Script steps in flows are not reusable across other flows. Complex logic in a Script step becomes a maintenance burden. If a Script step contains more than 20 lines of non-trivial code, extract it to a Script Include that the Script step calls — making it testable in Scripts - Background independently of the flow.
Anti-pattern 5 — Flow variables used as global mutable state
Flow variables should store computed values that multiple steps need to read. Using them as counters that many steps write to creates hard-to-trace state mutation. Keep flow variable writes to early, clearly-named steps.
Anti-pattern 6 — No description on the flow record
The flow description field is for future you and your teammates. Every production flow should have a description explaining: what triggers it, what it does, what record it operates on, and any important caveats. Six months later, this saves hours of archaeology.