MScript Cookbook
1. Scope & Mental Model
MScript is how you “sneak logic into patterns” without breaking Mindbricks’ declarative nature. It’s used in:
-
Where clauses (filters, conditions)
-
Formulas for derived fields
-
Business API workflows (conditions, validations, actions)
-
Integration parameters, payload builders, templates
-
Many config fields across Authentication, AccessControl, StripeOrder, ShoppingCart, etc.
Most MScript is evaluated in the context of an API Manager class; you write expressions against the this context, plus the LIB global for service library functions.
1.1 Golden Rules (Quick Reminder)
-
Your script must be a valid JavaScript expression Pretend it’s on the right side of a
const value = .... -
No assignments (
=) inside MScript (to avoid side effects). -
If you’re unsure, you can wrap it in:
module.exports = (/* your expression here */) -
Remember double quoting in JSON:
-
MScript itself is a string in the design JSON.
-
String literals inside MScript must be wrapped in quotes inside that string.
-
Example:
{
"condition": "this.userType === 'premium'"
}
Behind the scenes, code becomes something like:
if (!(this.userType === 'premium')) {
// ...
}
2. The Context Cheat Sheet
When you write this, you are usually inside a BusinessApi context.
You can typically access:
-
Parameters –
this.productId,this.searchKeyword -
Session –
this.session.userId,this.session.roleId,this.session.tenantId -
Main object(s) –
this.order,this.customer,this.orders(for list) -
Action results –
this.userCompany,this.stats, etc. (whencontextPropertyNameis set on aBusinessApiAction) -
Library functions –
LIB.someModule.someFn(...)orLIB.common.md5(...)
3. Cookbook: Conditions & Access Checks
3.1 Ownership Check (Simple)
Use in ValidationAction.validationScript, ExtendedClause.doWhen, or BusinessApiAction.condition:
{
"validationScript": "this.customer.userId === this.session.userId"
}
Meaning:
3.2 Ownership OR Admin Role
{
"validationScript": "(this.customer.userId === this.session.userId) || (this.session.roleId === 'admin')"
}
If your project uses multi-role arrays:
{
"validationScript": "(this.customer.userId === this.session.userId) || (Array.isArray(this.session.roleId) && this.session.roleId.includes('admin'))"
}
3.3 Tenant-Scoped Access
Allow operation only when the object’s tenant matches the session:
{
"validationScript": "this.project.clientId === this.session.tenantId"
}
Or as an ExtendedClause.doWhen to conditionally add filters:
{
"doWhen": "!!this.session.tenantId",
"whereClause": "({ clientId: { "$eq": this.session.tenantId } })"
}
3.4 Ensuring Required Feature Flag
Assume you have a subscriptionLevel property in the tenant object and it’s loaded into context as this.tenant:
{
"validationScript": "['Pro', 'Enterprise'].includes(this.tenant.subscriptionLevel)"
}
Use in ValidationAction with checkType: "liveCheck" to hard-fail the API for non-eligible tenants.
4. Cookbook: MScript Queries (Where Clauses)
WhereClauseSettings.fullWhereClause, ExtendedClause.whereClause, ListJointFilter.whereClause, and ListSearchFilter all use MScript Query syntax.
4.1 Basic Equality Filter
List orders belonging to current user:
{
"fullWhereClause": "({ userId: { "$eq": this.session.userId } })"
}
Equivalent shorthand:
{
"fullWhereClause": "({ userId: this.session.userId })"
}
4.2 Filtering by Status & Tenant
{
"fullWhereClause": "({ "$and": [ { status: { "$eq": 'published' } }, { clientId: { "$eq": this.session.tenantId } } ] })"
}
4.3 Case-Insensitive Search (Name)
{
"fullWhereClause": "({ name: { "$ilike": `%${this.searchKeyword}%` } })"
}
4.4 Range Filter (Dates)
Orders in a given date range:
{
"fullWhereClause": "({ createdAt: { "$between": [this.startDate, this.endDate] } })"
}
4.5 Combining Multiple Conditions
Published & not soft-deleted:
{
"fullWhereClause": "({ "$and": [ { status: { "$eq": 'published' } }, { isActive: { "$eq": true } } ] })"
}
4.6 Using a Library Function to Build Query
First define the JS in functions:
// moduleName: buildProductSearchQuery
module.exports = function buildProductSearchQuery(searchKeyword, tenantId) {
return {
"$and": [
{
name: { "$ilike": `%${searchKeyword}%` }
},
{
clientId: { "$eq": tenantId }
}
]
};
};
Then in the BusinessApi:
{
"fullWhereClause": "LIB.buildProductSearchQuery(this.searchKeyword, this.session.tenantId)"
}
This keeps complex query logic in real JS, while the API pattern stays clean.
5. Cookbook: Formulas & Derived Fields
Pattern: PropertyFormulaSettings and PropertyFormulaSettingsConfig.formula
5.1 Simple Derived Field
lineTotal = unitPrice * quantity:
{
"formulaSettings": {
"isCalculated": true,
"configuration": {
"formula": "this.unitPrice * this.quantity"
}
}
}
5.2 Net Amount with Floor at Zero
{
"formulaSettings": {
"isCalculated": true,
"configuration": {
"formula": "(() => { const net = this.grossAmount - this.discountAmount; return Math.max(net, 0); })()"
}
}
}
This uses your inline arrow function trick for multi-step logic.
5.3 Tax Calculation with Shared Helper (Library)
Library function:
// moduleName: calcVat
module.exports = function calcVat(netAmount, vatRate) {
return Math.round(netAmount * vatRate * 100) / 100;
};
Property formula:
{
"formulaSettings": {
"isCalculated": true,
"configuration": {
"formula": "LIB.calcVat(this.netAmount, this.vatRate)"
}
}
}
5.4 One Formula for Create and Separate Update Formula
For totalPoints property:
{
"formulaSettings": {
"isCalculated": true,
"configuration": {
"formula": "this.purchasePoints + (this.bonusPoints || 0)",
"updateFormula": "this.purchasePoints + (this.bonusPoints || 0) + (this.extraPoints || 0)",
"calculateWhenInputHas": ["purchasePoints", "bonusPoints", "extraPoints"]
}
}
}
-
formulaused on create. -
updateFormulaused on update. -
calculateWhenInputHastells Mindbricks when to re-evaluate.
6. Cookbook: List & Data Transformations
6.1 Basic List Mapping (ListMapAction)
Pattern: ListMapAction (“ListMapActions” in action store)
Imagine you have this.orders and you want a lightweight DTO list:
{
"extendClassName": "ListMapAction",
"name": "mapOrdersToSummary",
"sourceList": "this.orders",
"itemName": "order",
"mapScript": "({ id: order.id, total: order.totalAmount, createdAt: order.createdAt })",
"contextPropertyName": "orderSummaries",
"writeToResponse": true
}
Now response.orderSummaries contains the simplified list.
6.2 Filter + Map in One Expression
You can filter inside mapScript using an IIFE:
{
"mapScript": "(() => { if (!order.isActive) return null; return { id: order.id, total: order.totalAmount }; })()"
}
Then follow up with a AddToContextAction or another ListMapAction to strip null items if needed.
6.3 Collating Lists (CollateListsAction)
Pattern: CollateListsAction
Example: attach addresses to users:
-
sourceList– list of addresses -
targetList– list of users -
key matching
user.id↔address.userId
{
"extendClassName": "CollateListsAction",
"name": "attachAddressesToUsers",
"sourceList": "this.addresses",
"targetList": "this.users",
"sourceKey": "userId",
"targetKey": "id",
"nameInTarget": "addresses",
"targetIsArray": true
}
After this:
// In later actions or MScript:
this.users[0].addresses // array of address objects
7. Cookbook: Using Action Results
Any BusinessApiAction with a contextPropertyName writes its result into this.[contextPropertyName].
7.1 Fetch and Use
FetchObjectAction named fetchCompany, contextPropertyName: "company":
{
"condition": "this.company && this.company.isActive"
}
7.2 Stats-Based Validation
FetchStatsAction with contextPropertyName: "orderStats" returns something like:
this.orderStats = {
count: 42,
sumOfAmount: 12345,
// etc…
};
Validation:
{
"validationScript": "this.orderStats.count < 5"
}
Use to prevent adding more than 5 orders per user per day, etc.
8. Cookbook: Integration Payloads & External APIs
Patterns: IntegrationAction, IntegrationParameter, ApiCallAction, HTTPRequest, HTTPRequestParameters
8.1 Building an Integration Payload
For a generic IntegrationAction:
{
"extendClassName": "IntegrationAction",
"name": "uploadFileToS3",
"provider": "amazonS3",
"action": "uploadFile",
"parameters": [
{
"parameterName": "bucket",
"parameterValue": "'my-bucket-name'"
},
{
"parameterName": "key",
"parameterValue": "`invoices/${this.order.id}.pdf`"
},
{
"parameterName": "body",
"parameterValue": "this.invoicePdf"
}
],
"contextPropertyName": "s3Result",
"writeToResponse": false
}
8.2 HTTP API Call with Dynamic Headers
Using ApiCallAction and HTTPRequest:
{
"extendClassName": "ApiCallAction",
"name": "callCrm",
"apiFetchProperty": null,
"contextPropertyName": "crmResponse",
"apiCallRequest": {
"httpRequestUrl": "https://crm.example.com/api/customers",
"httpRequestMethod": "POST",
"httpRequestParameters": {
"httpRequestHeaders": [
{
"name": "X-Tenant-Id",
"value": "this.session.tenantId"
},
{
"name": "Authorization",
"value": "`Bearer ${this.session.crmAccessToken}`"
}
],
"httpRequestBody": [
{
"name": "email",
"value": "this.customer.email"
},
{
"name": "name",
"value": "this.customer.fullname"
}
]
}
}
}
Later:
{
"validationScript": "this.crmResponse && this.crmResponse.status === 'OK'"
}
9. Cookbook: Verification & Flows
You often use MScript to drive verification flows, 2FA behavior, or conditional events.
9.1 Conditional Verification Start
In a custom Business API for “sensitive action”, you may want:
-
If user is premium and has 2FA → require 2FA
-
Otherwise, allow directly
{
"condition": "this.session.isPremium && this.session.sessionNeedsEmail2FA"
}
Use this condition in a RedirectAction or ValidationAction to decide whether to:
-
Return
EmailTwoFactorNeeded -
Or proceed with the action
9.2 Multi-Step Access: “Soft Enforcement”
You might want to allow an operation but log a warning when a user is missing a recommended verification:
{
"description": "Warn if user has not verified email.",
"shouldBeTrue": true,
"checkType": "storedCheck",
"validationScript": "this.session.emailVerified === true",
"errorMessage": "User email not verified.",
"errorStatus": "400"
}
Because checkType is storedCheck, the API doesn’t fail, but you can:
-
write result to context (via contextPropertyName),
-
or use it in later actions to log / alter behavior.
10. Cookbook: Date & Time Helpers
You might have a small date helper in functions:
// moduleName: dateUtils
module.exports = {
isToday(dateIso) {
const d = new Date(dateIso);
const today = new Date();
return d.toISOString().slice(0, 10) === today.toISOString().slice(0, 10);
},
daysBetween(aIso, bIso) {
const a = new Date(aIso);
const b = new Date(bIso);
return Math.round((b - a) / (1000 * 60 * 60 * 24));
}
};
Usage in MScript:
{
"validationScript": "LIB.dateUtils.daysBetween(this.order.createdAt, new Date().toISOString()) <= 30"
}
Used to:
-
Limit modifications to orders to within 30 days.
-
Decide whether a password reset token is “too old” (if you want an extra business rule beside
expireTimeWindow).
11. Final Pattern: How to Think in MScript
When writing MScript, try this mental pipeline:
-
What is my context?
-
Which object(s) do I have on
this? -
Which actions ran before me?
-
-
What is the minimal expression that describes the rule?
-
If it’s small → write directly in MScript.
-
If it becomes big or reused → move it into the service library.
-
-
Is this about selecting data?
-
Use MScript Query syntax (
$eq,$gte,$in,$ilike,$and,$or, etc.). -
Consider building the query via a library function.
-
-
Is this about shaping data?
-
Use
ListMapAction,CollateListsAction, formulas,CreateObjectAction, etc. -
Use MScript only where necessary.
-
-
Is this about deciding yes/no?
- Use
ValidationAction,PermissionCheckAction,MembershipCheckAction,ExtendedClause, or conditions on actions.
- Use
Once you get comfortable with these patterns, MScript stops feeling like “random snippets” and starts feeling like a small, expressive logic language embedded in your architecture.
Last updated today