Deluge is Zoho's proprietary scripting language embedded in their entire product ecosystem. Unlike traditional ERP scripting (Salesforce Apex, NetSuite SuiteScript), Deluge is shared across 40+ Zoho applications -- the same language works in CRM, Creator, Desk, Books, and every other Zoho product. This card covers the Deluge runtime as it applies to Zoho CRM and Zoho Creator (the most common contexts), but the core language and most governance limits are consistent across all Zoho products. Edition-specific limits (API credits, daily call quotas) vary by which Zoho product you are scripting in. [src6]
| Property | Value |
|---|---|
| Vendor | Zoho |
| System | Zoho Deluge Runtime (across Zoho One / CRM / Creator / 40+ apps) |
| API Surface | Deluge scripting language (integration tasks + invokeURL) |
| Current Version | Deluge 2.0 (current runtime) |
| Editions Covered | Standard, Professional, Enterprise, Ultimate, Zoho One |
| Deployment | Cloud |
| API Docs | Zoho Deluge Help |
| Status | GA |
Deluge provides three primary mechanisms for integrating with external systems and other Zoho services. [src4, src6]
| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| Integration Tasks | Zoho internal REST | Zoho-to-Zoho operations (CRM, Books, Desk, etc.) | Service-dependent | Counts as API credits | Yes | Limited |
| invokeURL | HTTPS (any method) | Third-party API calls, custom webhooks | 5 MB response (external), 15 MB (Zoho) | 2,000-5,000,000/day (edition) | Yes | No |
| Standalone Functions (REST API) | HTTPS POST | Exposing Deluge logic as API endpoints | 10 MB response, 95K chars body | Shares CRM API credit pool | Yes | No |
| Scheduled Functions | Internal trigger | Batch operations, periodic sync | N/A | 15-min execution window | No | Yes |
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max statements executed | 5,000 | All Deluge functions | Loops multiply: 3 statements x 100 iterations = 300 consumed [src1] |
| Max recursive function calls | 75 | All Deluge functions | Nested function calls within a function [src1] |
| Max response size | 10 MB | Standalone functions (REST API) | Return value size limit [src3] |
| Max lines of execution | 200,000 | Zoho CRM functions | Higher than statement limit; counts include framework overhead [src2] |
| Max POST body size | 95,000 chars | Standalone functions | URL arguments capped at 5,000 chars [src7] |
| Max CRM fields per data transfer | 10 | Workflow custom functions | Data pushed to third-party apps [src5] |
| Trigger Type | Timeout | Notes |
|---|---|---|
| Custom button click | 10 seconds | User-facing, must be fast |
| Validation rule | 10 seconds | Synchronous, blocks record save |
| Related list | 10 seconds | Synchronous rendering |
| REST API (standalone function) | 10 seconds | External API callers expect quick response |
| Workflow rule (automation) | 30 seconds | Triggered by record create/update/delete |
| Blueprint transition | 30 seconds | Triggered during process stage changes |
| Scheduled function | 15 minutes | Background execution, longest timeout |
[src3]
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| sendmail (email) | 500/day (Deluge general), 50,000/day (CRM) | Email sending | CRM has higher allocation |
| invokeURL / getUrl / postUrl | 2,000/day (Deluge general), 5,000,000/day (CRM) | External API calls | CRM Enterprise/Ultimate have much higher limits |
| Integration tasks | 2,000/day (Deluge general) | Zoho-to-Zoho API calls | Each execution = 1 API credit consumed |
| SMS | 1,000,000/day | CRM SMS functions | Enterprise and above |
| Edition | Base Credits | Per User License | Maximum | Notes |
|---|---|---|---|---|
| Standard | 5,000 | +200/license | 15,000 | Functions via Extensions only |
| Professional | 5,000 | +200/license | 20,000 | Functions via Extensions only |
| Enterprise / Zoho One | 20,000 | +500/license | 200,000 | Full function support |
| Ultimate / CRM Plus | 20,000 | +1,000/license | Unlimited | Scales with licenses |
Deluge handles authentication differently depending on the integration type. [src4, src7]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Built-in integration tasks | Zoho-to-Zoho operations | Managed by platform | Automatic | No explicit auth needed; uses logged-in user context or admin context |
| Connections (OAuth 2.0) | Third-party APIs via invokeURL | Managed by connection | Automatic refresh | Create in Settings > Connections; supports OAuth 2.0 for 100+ services |
| API Key | Exposing standalone functions as REST endpoints | Non-expiring (until regenerated) | N/A | Static key; admin-only regeneration |
| OAuth 2.0 (internal) | Sharing standalone functions within org | Standard OAuth token lifecycle | Yes | For internal organizational consumption |
connection:"my_slack_connection"). Typos in the connection name silently fail -- there is no compile-time validation. [src4]for each over 500 records with 10 statements inside = 5,000 statements exactly (the limit). [src1]START -- User needs to script in Zoho Deluge
|-- What kind of operation?
| |-- Zoho-to-Zoho (e.g., CRM to Books, CRM to Desk)
| | |-- Is there a built-in integration task?
| | | |-- YES --> Use integration task (zoho.crm.*, zoho.books.*, etc.)
| | | | Simplest approach, pre-built wrappers, 1 API credit each
| | | |-- NO --> Use invokeURL with Zoho API + connection
| |-- Zoho-to-third-party (e.g., CRM to Slack, Stripe, custom API)
| | |-- Set up OAuth connection in Settings first
| | |-- Use invokeURL with connection parameter
| | |-- Response > 5 MB? --> Cannot process in Deluge; use middleware
| |-- Internal logic only (calculations, validations, field updates)
| |-- No API calls needed
| |-- Watch statement limit (5,000) for complex logic
|-- How will it be triggered?
| |-- User action (button click, validation) --> 10s timeout
| |-- Automation (workflow, blueprint) --> 30s timeout
| |-- Scheduled (periodic batch) --> 15min timeout
| |-- External system (REST API) --> 10s timeout, expose as standalone
|-- Data volume?
| |-- < 500 records per execution --> OK for single function
| |-- 500-5,000 records --> Chunk into batches via scheduled functions
| |-- > 5,000 records --> Use Zoho CRM Bulk API (not Deluge)
|-- Error handling needed?
|-- YES --> Wrap in try-catch, log errors via info or integration tasks
|-- Critical failures --> sendmail alert + return error response
| Task | Deluge Syntax | Notes |
|---|---|---|
| Get CRM record | zoho.crm.getRecordById("Leads", recordId) | Returns MAP; 1 API credit |
| Search CRM records | zoho.crm.searchRecords("Contacts", "Email:equals:" + email) | Returns LIST of MAPs |
| Create CRM record | zoho.crm.createRecord("Deals", dataMap) | Returns MAP with id |
| Update CRM record | zoho.crm.updateRecord("Accounts", recordId, updateMap) | Returns MAP |
| Delete CRM record | zoho.crm.deleteRecord("Leads", recordId) | Returns MAP |
| HTTP GET (external) | invokeurl [url: apiUrl type: GET connection: "conn"] | 5 MB response limit |
| HTTP POST (external) | invokeurl [url: apiUrl type: POST body: jsonStr headers: headerMap connection: "conn"] | Set Content-Type in headers |
| Send email | sendmail [from: zoho.adminuserid to: email subject: subj message: body] | 15 MB attachment limit |
| Log debug output | info variableName | Visible in execution logs |
| Error handling | try { ... } catch(e) { info e.message; } | e.lineNo for line number |
| Return value | return responseMap | 10 MB max for standalone functions |
| Loop (list) | for each item in myList { ... } | Each iteration counts as statements |
Navigate to Setup > Developer Hub > Functions > + Create New Function. Name the function, add arguments, and write Deluge code in the editor. [src7]
// Function: updateAccountRevenue
// Arguments: accountId (bigint), newAmount (decimal)
accountMap = zoho.crm.getRecordById("Accounts", accountId);
currentRevenue = ifnull(accountMap.get("Annual_Revenue"), 0.0);
updatedRevenue = currentRevenue + newAmount;
updateMap = Map();
updateMap.put("Annual_Revenue", updatedRevenue);
response = zoho.crm.updateRecord("Accounts", accountId, updateMap);
info response;
return response;
Verify: Click "Save & Execute Script" with test argument values. Check the execution log for the info output.
Go to Setup > Automation > Workflow Rules > create a new rule. Set the trigger, add an instant action > Custom Function > select your function. Map arguments to CRM fields. [src5]
// Workflow-triggered function (30-second timeout)
// Argument mapping: accountId = ${Deals.Account_Name.id}
// newAmount = ${Deals.Amount}
accountMap = zoho.crm.getRecordById("Accounts", accountId);
currentRevenue = ifnull(accountMap.get("Annual_Revenue"), 0.0);
updatedRevenue = currentRevenue + newAmount;
updateMap = Map();
updateMap.put("Annual_Revenue", updatedRevenue);
zoho.crm.updateRecord("Accounts", accountId, updateMap);
Verify: Create or update a Deal matching the workflow criteria. Check the Account's Annual_Revenue field for the updated value.
Create a Connection first (Settings > Developer Space > Connections), then use invokeURL in your function. [src4]
// Call Slack webhook to notify on deal closure
slackUrl = "https://hooks.slack.com/services/T00/B00/xxx";
payload = Map();
payload.put("text", "Deal Won: " + dealName + " for $" + amount);
response = invokeurl
[
url: slackUrl
type: POST
parameters: payload.toString()
headers: {"Content-Type": "application/json"}
];
info response;
Verify: Check the Slack channel for the notification message. Check the execution log for the info output showing the API response.
Wrap external API calls and record operations in try-catch blocks to handle failures gracefully. [src8]
try
{
response = invokeurl
[
url: apiEndpoint
type: GET
connection: "my_api_connection"
detailed: true
];
statusCode = response.get("responseCode");
if (statusCode != 200)
{
info "API returned " + statusCode + ": " + response.get("responseText");
sendmail
[
from: zoho.adminuserid
to: "[email protected]"
subject: "Integration Error: API returned " + statusCode
message: "Response: " + response.get("responseText")
];
}
else
{
data = response.get("responseText").toJSONList();
for each record in data
{
recordMap = record.toMap();
// Process each record...
}
}
}
catch(e)
{
info "Error at line " + e.lineNo + ": " + e.message;
sendmail
[
from: zoho.adminuserid
to: "[email protected]"
subject: "Deluge Function Error"
message: "Line: " + e.lineNo + " Error: " + e.message
];
}
Verify: Deliberately provide an invalid URL to trigger the catch block. Check execution logs and email for error details.
# Input: Zoho CRM standalone function URL, API key or OAuth token
# Output: Function execution result
import requests
function_url = "https://www.zohoapis.com/crm/v2/functions/my_function/actions/execute"
params = {
"auth_type": "apikey",
"zapikey": "YOUR_API_KEY"
}
payload = {
"arguments": {
"accountId": "1234567890",
"newAmount": 5000.00
}
}
response = requests.post(
function_url,
params=params,
json=payload,
headers={"Content-Type": "application/json"}
)
if response.status_code == 200:
result = response.json()
print(f"Function result: {result}")
else:
print(f"Error {response.status_code}: {response.text}")
// Input: OAuth access token, function name, arguments
// Output: Function execution result
// npm install axios@1
const axios = require("axios");
const accessToken = process.env.ZOHO_ACCESS_TOKEN;
const functionName = "my_function";
async function callZohoFunction() {
const url = `https://www.zohoapis.com/crm/v2/functions/${functionName}/actions/execute`;
const response = await axios.post(url, {
arguments: {
accountId: "1234567890",
newAmount: 5000.00
}
}, {
params: { auth_type: "oauth" },
headers: {
Authorization: `Zoho-oauthtoken ${accessToken}`,
"Content-Type": "application/json"
}
});
console.log("Result:", response.data);
return response.data;
}
callZohoFunction().catch(console.error);
// Input: Module name, list of record IDs to process
// Output: Count of successfully processed records
// IMPORTANT: This pattern avoids the 5,000 statement limit
module = "Contacts";
processedCount = 0;
errorCount = 0;
// Fetch max ~400 records (10 statements per iteration = 4,000 max)
contacts = zoho.crm.getRecords(module, 1, 200);
for each contact in contacts
{
try
{
email = ifnull(contact.get("Email"), "");
if (email != "")
{
updateMap = Map();
updateMap.put("Email_Verified", true);
zoho.crm.updateRecord(module, contact.get("id"), updateMap);
processedCount = processedCount + 1;
}
}
catch(e)
{
errorCount = errorCount + 1;
info "Error processing " + contact.get("id") + ": " + e.message;
}
}
info "Processed: " + processedCount + ", Errors: " + errorCount;
return {"processed": processedCount, "errors": errorCount};
| Deluge Type | Description | Conversion Function | Gotcha |
|---|---|---|---|
| TEXT | String values | .toString() or toText() | Max length varies by context; no explicit limit documented |
| NUMBER | Integer/decimal | .toNumber() or toDecimal() | Division of integers returns integer (use toDecimal first) |
| BOOLEAN | true/false | .toBoolean() | Truthy/falsy coercion differs from JavaScript |
| DATE | Date without time | .toDate() | Format: yyyy-MM-dd; timezone depends on user settings |
| DATETIME | Date with time | .toDateTime() | Format: yyyy-MM-dd hh:mm; 24h format in API |
| LIST | Ordered collection | .toJSONList() | Zero-indexed; for-each creates new scope |
| MAP | Key-value pairs | .toMap() | Keys are case-sensitive; .get() returns null for missing keys |
| FILE | Binary file data | .toFile() | 50 MB max in Creator; fetched via invokeURL or getUrl |
ifnull(value, default) is the safest way to handle nulls. Directly accessing a null value in an operation will throw a runtime error. Use ifnull() or isBlank() checks before every operation on fetched data. [src5]yyyy-MM-dd strings, but Deluge date operations expect DATE type objects. Always convert with .toDate() before date arithmetic. [src5].toJSONList(), then each element to MAP with .toMap(). Skipping the second step gives you TEXT elements that look like MAPs but fail on .get(). [src4];. When writing, also use ; as delimiter. [src5]| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| EXECUTION_LIMIT_EXCEEDED | Statement limit (5,000) reached | Loop over large dataset | Reduce iteration count; chunk into scheduled batches |
| FUNCTION_CALL_LIMIT_EXCEEDED | 75 recursive calls exceeded | Deep recursion or circular function calls | Flatten recursive logic; use iteration instead |
| SOCKET_TIMEOUT | invokeURL timed out at 40s | External API too slow | Add timeout handling; use async webhook pattern instead |
| EXECUTION_TIME_EXCEEDED | Function exceeded trigger timeout | Complex logic in button/validation context | Move heavy logic to scheduled function or workflow |
| INVALID_DATA | Malformed data in API call | Wrong data type or missing required field | Validate all inputs with isBlank/ifnull before API calls |
| AUTHENTICATION_FAILURE | Connection OAuth token expired/revoked | Connection owner deactivated or token revoked | Re-authorize connection; use service account |
| DAILY_LIMIT_EXCEEDED | Daily API credit quota exhausted | Too many function executions in 24h window | Upgrade edition or optimize to reduce API calls per function |
| RESPONSE_SIZE_EXCEEDED | Response larger than 5 MB / 10 MB | Large API response or return value | Paginate API calls; reduce response payload |
Add a counter variable and check it periodically. Log completion status. Break large loops into smaller scheduled batches. [src1]Create connections using a shared service account that is never deactivated. Document which connections belong to which service account. [src4]Monitor credit usage via Zoho CRM admin dashboard. Set up alerts at 80% consumption. [src2]Use API pagination parameters (limit, offset) to keep responses under 5 MB. [src4]Always convert to a common timezone before comparison. Use zoho.currenttime for consistent operations. [src5]// BAD -- 1,000 records x 8 statements = 8,000 statements (exceeds 5,000 limit)
allContacts = zoho.crm.getRecords("Contacts", 1, 200);
for each contact in allContacts
{
email = contact.get("Email");
phone = contact.get("Phone");
company = contact.get("Company");
updateMap = Map();
updateMap.put("Processed", true);
updateMap.put("Last_Processed", zoho.currentdate);
zoho.crm.updateRecord("Contacts", contact.get("id"), updateMap);
info "Processed: " + contact.get("id");
// 8 statements per iteration -- exceeds limit quickly
}
// GOOD -- Process in batches of 100, stay within statement limits
batchSize = 100;
page = 1;
contacts = zoho.crm.getRecords("Contacts", page, batchSize);
for each contact in contacts
{
updateMap = Map();
updateMap.put("Processed", true);
zoho.crm.updateRecord("Contacts", contact.get("id"), updateMap);
// 3 statements per iteration = 300 total for 100 records
}
// For remaining records, trigger next batch via scheduled function
// BAD -- if API fails, function crashes with no logging
response = invokeurl
[
url: "https://api.example.com/data"
type: GET
connection: "example_api"
];
data = response.toJSONList();
for each item in data { /* process */ }
// GOOD -- capture status code, handle errors, log everything
try
{
response = invokeurl
[
url: "https://api.example.com/data"
type: GET
connection: "example_api"
detailed: true
];
if (response.get("responseCode") == 200)
{
data = response.get("responseText").toJSONList();
for each item in data
{
itemMap = item.toMap();
// Process safely
}
}
else
{
info "API Error: " + response.get("responseCode");
}
}
catch(e)
{
info "Exception: " + e.message + " at line " + e.lineNo;
}
// BAD -- credentials in script, breaks on environment change
apiKey = "sk_live_abc123def456";
response = invokeurl
[
url: "https://api.stripe.com/v1/charges"
type: POST
headers: {"Authorization": "Bearer sk_live_abc123def456"}
parameters: chargeMap
];
// GOOD -- OAuth managed by connection, no credentials in code
response = invokeurl
[
url: "https://api.stripe.com/v1/charges"
type: POST
parameters: chargeMap
connection: "stripe_production"
];
// Connection handles OAuth token refresh automatically
sendmail block counts as 1 statement. But a for each loop multiplies ALL inner statements by iteration count. Fix: Count statements by tracing execution flow, not by counting lines. Use info to log a counter variable to verify. [src1]Always test with production-scale data volumes. Calculate: (statements_per_iteration x max_expected_records) < 5,000. [src1]zoho.crm.getRecordById(), zoho.crm.searchRecords(), etc. consumes 1 API credit. A function with 5 integration tasks running 1,000 times/day = 5,000 credits. Fix: Audit credit consumption. Batch operations where possible. Use searchRecords instead of multiple getRecordById calls. [src2]Use ifnull(value, default) for every value fetched from a record or API. Use isBlank() for text fields. [src5]Always use invokeURL for new development. [src4]Consolidate related logic into a single function. Use a dispatcher function that calls sub-functions. [src5]// Check current API credit usage (Zoho CRM admin)
// Navigate to: Setup > Developer Hub > APIs > Dashboard
// Shows: credits consumed, remaining, per-function breakdown
// Debug a function: add info statements throughout
info "Step 1: Input received - accountId = " + accountId;
info "Step 2: Record fetched - " + recordMap;
info "Step 3: Before update - statement count approx: " + statementCounter;
// Test invokeURL with detailed:true to see full HTTP response
testResponse = invokeurl
[
url: "https://api.example.com/test"
type: GET
connection: "my_connection"
detailed: true
];
info "Status: " + testResponse.get("responseCode");
info "Headers: " + testResponse.get("responseHeader");
info "Body: " + testResponse.get("responseText");
// Check function execution logs
// Navigate to: Setup > Developer Hub > Functions > select function > Logs
// Shows: execution time, input arguments, output, errors
// Verify connection status
// Navigate to: Setup > Developer Space > Connections
// Shows: connection name, service, status (active/expired), last used
// Monitor scheduled function execution
// Navigate to: Setup > Automation > Schedules > History
// Shows: execution status, start/end time, errors
| Change | Date | Impact | Notes |
|---|---|---|---|
| Deluge 2.0 runtime (current) | 2020 | Platform upgrade | Improved performance, expanded built-in functions, wider product support |
| API credit system overhaul | 2024 | Credit calculation change | Tiered consumption for Java/Node.js functions; Deluge remains 1 credit/execution |
| invokeURL enhancements | 2024 | New capabilities | Added PATCH, OPTIONS methods; response-format and response-decoding parameters |
| Try-catch support expanded | 2023 | Error handling | Available across all Zoho products (previously limited to some) |
| 260+ integration tasks | 2025 | Expanded integrations | Coverage across 35 Zoho services |
| Scheduled functions 15-min timeout | 2023 | Increased limit | Enables longer-running batch operations |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Custom business logic inside any Zoho app (CRM, Creator, Desk, Books) | High-volume ETL processing (>5,000 records per batch) | Zoho CRM Bulk API or external ETL tool |
| Automating workflows between Zoho products (CRM to Books, Desk to CRM) | Compute-intensive operations (ML, image processing, heavy math) | External service via invokeURL + webhook callback |
| Quick integrations with third-party APIs (Slack, Stripe, custom endpoints) | Real-time processing requiring <1s latency on complex logic | External microservice with direct API integration |
| Scheduled data sync running under 15 minutes | Operations requiring more than 5,000 statement executions | Chunk across multiple scheduled functions or use external orchestration |
| Form validation and data enrichment on record save | Standard/Professional CRM editions without Extensions | Upgrade to Enterprise or use Zoho Flow for no-code automation |