This page suggests additional best practices for working with JavaScript in Make.com workflows.
Clear variable names are a first step to making the code self-documenting. This helps others (and your future self) understand the logic quickly without guessing.
Avoid | Use Descriptive Names | |
---|---|---|
Code | // Hard to understand
const e = input.email;
const t = input.items.reduce((s, i) => s + i.price, 0);
| // Clear and descriptive
const customerEmail = input.email;
const orderTotal = input.items.reduce((sum, item) => sum + item.price, 0);
|
Result | Short variable names make code cryptic. Other developers (or your future self) will struggle to understand the logic. | Descriptive names make the purpose of each variable obvious. Code is easier to read, maintain, and debug. |
Comments explain why the code exists, not just what it does.
Without Comments | With Comments | |
---|---|---|
Code | // No explanation of logic
const taxRate = input.country === 'US' ? 0.08 : 0.20;
const taxAmount = orderTotal * taxRate;
| // Calculate tax based on customer location
const taxRate = input.country === 'US' ? 0.08 : 0.20;
const taxAmount = orderTotal * taxRate;
|
Result | Code works, but the reasoning behind it is unclear. Anyone reading it must figure out the business logic from scratch. | Comments clarify *why* the logic exists. Makes onboarding, debugging, and collaboration much faster. |
Validating inputs prevents runtime errors and unexpected crashes.
Without Validation | With Validation | |
---|---|---|
Code | // Assumes all inputs are correct
const totalItems = input.items.length;
const user = input.userId;
| // Always validate critical inputs
if (!input.userId || typeof input.userId !== 'string') {
return { error: "Valid userId is required" };
}
|
Result | Risk of runtime errors (e.g., calling length on undefined).
Can break the entire workflow if inputs are invalid. | Invalid inputs are caught early. Workflow fails gracefully with clear error messages instead of crashing. |
External APIs can fail for reasons outside your control (timeouts, invalid payloads, network errors). Wrapping calls in try…catch ensures failures don’t crash the workflow but return structured errors you can handle.
Without Try-Catch | With Try-Catch | |
---|---|---|
Code | // No error handling
const response = await fetch(input.apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input.data)
});
const result = await response.json();
return { success: true, data: result };
| // With error handling
try {
const response = await fetch(input.apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input.data)
});
|
Result | Any API failure throws an exception. The workflow breaks with no useful output. | Failures are caught and returned as structured error objects. The workflow continues gracefully with clear diagnostics. |
Specific error messages guide the user toward fixing the problem. Generic ones leave them guessing and slow down troubleshooting.
Generic Error Message | Meaningful Error Message | |
---|---|---|
Code | // Generic: unclear message
if (!input.email.includes('@')) {
return { error: "Invalid input" };
}
| // Specific: tells user what’s wrong
if (!input.email.includes('@')) {
return { error: "Email must contain @ symbol" };
}
|
Result | User only knows something failed, but not why. Debugging requires trial and error. | User gets actionable feedback (missing “@”). Fixing the error is quick and straightforward. |
Array methods like .filter()
and .map()
are faster to write, easier to read, and less prone to errors than manual loops.
Manual Loop | Built-in Array Methods | |
---|---|---|
Code | // Verbose: manual loop
const activeUsers = [];
for (let i = 0; i < input.users.length; i++) {
if (input.users[i].status === 'active') {
activeUsers.push(input.users[i]);
}
}
| // Cleaner: use array methods
const activeUsers = input.users.filter(user => user.status === 'active');
const userEmails = activeUsers.map(user => user.email);
|
Result | Code is longer and harder to maintain. Easy to introduce off-by-one or push errors. | Code is concise and expressive. Improves readability and reduces chance of mistakes. |
Processing only the fields you actually need will reduce payload size and makes outputs easier to handle in downstream modules.
Returning All Data | Returning Only Essential Data | |
---|---|---|
Code | // Inefficient: includes unnecessary fields
return { user: input.user };
| // Efficient: only keep essential fields
const essentialData = {
id: input.user.id,
email: input.user.email,
name: input.user.name
};
|
Result | Payload may contain large or irrelevant data. Downstream modules do extra work filtering it out. | Output is lean and focused. Downstream modules process only what matters. |
Handling missing or empty values prevents runtime errors and ensures workflows return reliable results.
Without Edge Case Handling | With Edge Case Handling | |
---|---|---|
Code | // Risky: assumes input.items always exists
const total = input.items.reduce((sum, item) => sum + item.price, 0);
return { total };
| // Safe: handles missing/empty cases
const items = input.items || [];
const total = items.length > 0
? items.reduce((sum, item) => sum + (item.price || 0), 0)
: 0;
|
Result | Code throws an error if items is missing or empty.
Workflow fails unexpectedly. | Code works even if items is undefined or empty.
Workflow always produces a safe result. |
Before deploying your JavaScript in Make:
Test with sample data — Use the CustomJS platform to test your functions.
Verify return values — Ensure your return statement produces the expected output.
Check error scenarios — Test what happens with invalid or missing input.
Validate data types — Confirm your output matches expected data types.
Without await, you return a Promise instead of usable data. Make cannot work with unresolved Promises.
Without Await | With Await | |
---|---|---|
Code | // Wrong: returns a Promise, not data
const data = fetch(url).then(r => r.json());
return { data };
| // Correct: wait for the response
const response = await fetch(url);
const data = await response.json();
return { data };
|
Result | Make receives an unresolved Promise. The next module cannot use it. | Make receives the actual JSON data. The workflow continues smoothly. |
Inputs should be treated as immutable. Directly changing them can cause side effects and unexpected results. Creating a new object keeps data flow predictable.
Modifying Input | Creating New Object | |
---|---|---|
Code | // Avoid: modifies the input object
input.processed = true;
return input;
| // Better: return a new object
return {
...input,
processed: true,
processedAt: new Date().toISOString()
};
|
Result | Original input is mutated. Other modules may behave unexpectedly. | Original input stays intact. New object makes changes explicit and safe. |