A deep dive into synchronous vs asynchronous script execution in enterprise FileMaker solutions
The Challenge: One Button, Multiple Dependencies
Picture this: Your user clicks a single “Set Preferred Supplier” button. Behind the scenes, your FileMaker solution needs to:
- Toggle the supplier’s preferred status
- Cascade that change to all related supplier provisions
- Recalculate sort orders for both suppliers and provisions tables
- Refresh the user interface
Simple enough, right? But here’s the catch: order matters. Get the sequencing wrong, and you’ll have data inconsistency that’s nearly impossible to debug.

The Real-World Example
Let me show you a real script orchestration that handles this exact scenario. This isn’t theoretical—it’s production code managing thousands of supplier records in a manufacturing environment.
The Master Script: One Button Rules Them All
# setPreferedSupplier - The orchestrator script
# Step 1: Toggle the preference
If [ suppliers::_isPrefered = 0 or suppliers::_isPrefered = "" ]
Set Field [ suppliers::_isPrefered; 1 ]
Else
Set Field [ suppliers::_isPrefered; 0 ]
End If
# Step 2: CASCADE UPDATE (BLOCKING - Critical!)
Set Variable [ $JasonPayload; Value:JSONSetElement ( "{}" ;
[ "table" ; "supplierProvisions" ; JSONString ] ;
[ "targetField" ; "_isPrefered" ; JSONString ] ;
[ "foreignKey" ; "_fkSupplierID" ; JSONString ] ;
[ "targetValue" ; suppliers::_isPrefered ; JSONNumber ]
) ]
Perform Script on Server [ "refreshRelatedTableTrigger_Server"; Parameter: $JsonPayload ]
[ Wait for completion ] # ← THE KEY DECISION!
# Step 3: SORT OPERATIONS (NON-BLOCKING - Performance!)
Perform Script on Server with Callback [ "setSupplierCustomSortOrder_Server";
Callback script: "EmptyCallback"; State: "Continue" ]
Perform Script on Server with Callback [ "setSupplierProvisionsCustomSortOrder_Server";
Callback script: "EmptyCallback"; State: "Continue" ]
# Step 4: Refresh UI
Refresh Portal [ Object Name: "prtl1" ]
The Critical Decision: When to Block vs When to Flow
Notice the difference in execution patterns:
BLOCKING Execution (Wait for completion)
Perform Script on Server [ "refreshRelatedTableTrigger_Server" ]
[ Wait for completion ]
NON-BLOCKING Execution (Callback with Continue)
Perform Script on Server with Callback [ "setSupplierCustomSortOrder_Server";
Callback script: "EmptyCallback"; State: "Continue" ]
Why the difference? It all comes down to data dependencies.
Understanding Data Dependencies
The Dependency Chain
- suppliers._isPrefered changes from 0 → 1
- supplierProvisions._isPrefered must update to match (via foreign key relationship)
- Sort operations must use the UPDATED preference values
If step 2 doesn’t complete before step 3 begins, the sort operations will use stale data, creating an inconsistent user experience.
The Blocking Strategy
For the cascade update, we use blocking execution:
Perform Script on Server [ "refreshRelatedTableTrigger_Server" ]
[ Wait for completion ] # Don't continue until cascade finishes
This ensures that ALL related supplierProvisions
records have their _isPrefered
field updated before any subsequent operations begin.
The Non-Blocking Strategy
For the sort operations, we use non-blocking callbacks:
Perform Script on Server with Callback [ "setSupplierCustomSortOrder_Server";
Callback script: "EmptyCallback"; State: "Continue" ]
Since sorting suppliers and sorting provisions are independent operations (neither depends on the other), they can run in parallel for better performance.
The Universal Cascade Engine
The real magic happens in the universal cascade updater. Instead of writing specific scripts for every table relationship, we built a JSON-driven engine:
# refreshRelatedTableTrigger_Server
Set Variable [ $params; Value:SafeJSONParse(Get(ScriptParameter)) ]
Set Variable [ $table; Value:JSONGetElement($params; "table") ]
Set Variable [ $targetField; Value:JSONGetElement($params; "targetField") ]
Set Variable [ $foreignKey; Value:JSONGetElement($params; "foreignKey") ]
Set Variable [ $fieldValue; Value:JSONGetElement($params; "fieldValue") ]
Set Variable [ $targetValue; Value:JSONGetElement($params; "targetValue") ]
Go to Layout [ $table ]
Enter Find Mode [ ]
Set Field By Name [ $table & "::" & $foreignKey; $fieldValue ]
Perform Find [ ]
Loop [ Flush: Always ]
Set Field By Name [ $table & "::" & $targetField; $targetValue ]
Go to Record/Request/Page [ Next; Exit after last ]
End Loop
This one script handles ANY one-to-many relationship cascade. Just pass it the right JSON parameters:
{
"table": "supplierProvisions",
"targetField": "_isPrefered",
"foreignKey": "_fkSupplierID",
"targetValue": 1
}
Performance vs Consistency: The Balancing Act
When to Use Blocking (Wait for completion)
✅ Data dependencies exist
✅ Subsequent operations need the results
✅ Data consistency is critical
✅ User expects immediate feedback
When to Use Non-Blocking (Callbacks)
✅ Operations are independent
✅ Performance matters more than immediate completion
✅ User doesn’t need to wait for background processes
✅ System has multiple concurrent operations
The Architecture Principles
1. Separate Concerns
- Client scripts: UI orchestration only
- Server scripts: Data operations only
- Universal engines: Reusable business logic
2. JSON-Driven Configuration
Instead of hardcoding table/field relationships, use JSON parameters for maximum flexibility.
3. Transaction Safety
Always wrap related operations in transactions:
Open Transaction
# ... do work ...
Commit Transaction
4. Error Handling
Every server-side operation should include proper error trapping and logging.
Real-World Results
This orchestration pattern handles:
- 400+ suppliers with instant preference toggling
- 2,000+ supplier provisions with automatic cascade updates
- Sub-second response times even with complex data relationships
- Zero data inconsistency issues in production
The Takeaway
Modern FileMaker development isn’t just about writing scripts—it’s about orchestrating experiences. Understanding when to block vs when to flow is crucial for building systems that are both performant and reliable.
The next time you’re designing a complex user interaction, ask yourself:
- What are the data dependencies?
- Which operations must complete before others begin?
- Which operations can run in parallel?
- How can I make this reusable for future features?
Master these concepts, and you’ll build FileMaker solutions that feel magical to users while remaining maintainable for developers.
Want to dive deeper into FileMaker architecture patterns? The key is understanding that great user experiences often require sophisticated orchestration behind the scenes. One button click can trigger a symphony of coordinated operations—the art is in making it look effortless.
About the Author
This article was inspired by real-world enterprise FileMaker development challenges and the ongoing evolution of client-server architecture patterns in modern business applications.