assets/.gitkeep
# DOU Nodes — Destination Folder
Distilled Operational Understanding (DOU) nodes for the four Zoho CRM developer-tool primitives land here, one node per source page.
Produced by the [`distill-dou`](../../../distill-dou/SKILL.md) skill. Consumed by the parent [`zoho-crm`](../../SKILL.md) skill via its Reference Routing Table.
Naming convention: `<short-slug>.dou.md` (e.g. `queries-execute.dou.md`, `widgets-create.dou.md`, `clientscript-events.dou.md`, `functions-standalone.dou.md`).
assets/api/extract_schema.py
#!/usr/bin/env python3
"""Extract a single operation + its requestBody/2xx schemas from a Zoho CRM OAS YAML file."""
import argparse
import sys
try:
import yaml
except ImportError:
print("pyyaml not installed. Run: pip3 install pyyaml")
sys.exit(1)
HTTP_METHODS = {"get", "post", "put", "delete", "patch", "options", "head", "trace"}
def collect_refs(node, refs=None):
"""Collect $ref names pointing to #/components/schemas/ without recursing into found schemas."""
if refs is None:
refs = set()
if isinstance(node, dict):
for k, v in node.items():
if k == "$ref" and isinstance(v, str) and v.startswith("#/components/schemas/"):
refs.add(v.split("/")[-1])
else:
collect_refs(v, refs)
elif isinstance(node, list):
for item in node:
collect_refs(item, refs)
return refs
def extract(api_dir, resource, operation_id):
yaml_path = f"{api_dir}/{resource}/{resource}.yaml"
try:
with open(yaml_path) as f:
spec = yaml.safe_load(f)
except FileNotFoundError:
print(f"File not found: {yaml_path}")
print(f"Check --api-dir ({api_dir!r}) and --resource ({resource!r}).")
sys.exit(1)
# --- SERVERS ---
servers = spec.get("servers", [])
print("=== SERVERS ===")
if servers:
print(yaml.dump(servers[0], default_flow_style=False).rstrip())
print()
# --- OPERATION ---
paths = spec.get("paths", {})
found_path = found_method = found_op = None
for path, path_item in paths.items():
if not isinstance(path_item, dict):
continue
for method, op in path_item.items():
if method.lower() not in HTTP_METHODS:
continue
if isinstance(op, dict) and op.get("operationId") == operation_id:
found_path, found_method, found_op = path, method.lower(), op
break
if found_op:
break
if not found_op:
available = sorted(
op.get("operationId", "")
for path_item in paths.values()
if isinstance(path_item, dict)
for method, op in path_item.items()
if method.lower() in HTTP_METHODS and isinstance(op, dict) and "operationId" in op
)
print(f"operationId '{operation_id}' not found in {yaml_path}")
print(f"Available operationIds: {', '.join(available)}")
sys.exit(1)
print("=== OPERATION ===")
print(yaml.dump({found_path: {found_method: found_op}}, default_flow_style=False).rstrip())
print()
# --- SCHEMAS: requestBody + 2xx responses only; error schemas excluded ---
components = spec.get("components", {}).get("schemas", {})
scope_nodes = []
if "requestBody" in found_op:
scope_nodes.append(found_op["requestBody"])
for code, response in found_op.get("responses", {}).items():
if str(code).startswith("2"):
scope_nodes.append(response)
refs = set()
for node in scope_nodes:
collect_refs(node, refs)
print("=== SCHEMAS ===")
for name in sorted(refs):
if name in components:
print(yaml.dump({name: components[name]}, default_flow_style=False).rstrip())
print()
def main():
parser = argparse.ArgumentParser(
description="Extract an OAS operation and its requestBody/2xx schemas."
)
parser.add_argument("--api-dir", required=True, help="Path to .zoho-crm/references/api/")
parser.add_argument("--resource", required=True, help="Resource folder name (e.g. record)")
parser.add_argument("--operation", required=True, help="operationId to extract")
args = parser.parse_args()
extract(args.api_dir, args.resource, args.operation)
if __name__ == "__main__":
main()
assets/clientscript/client-apis/blueprinttransition.dou.md
FILE_PURPOSE: Read when reading or programmatically firing a Blueprint transition via the `BlueprintTransition` object returned by `ZDK.Page.getBlueprintTransitions()` / `getBlueprintTransitionByID(id)`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/BlueprintTransition
TRIGGER_KEYWORDS: BlueprintTransition, getId, getName, initiate, blueprint, transition, Detail
TECHNICAL_CONSTRAINTS:
- Obtained via `ZDK.Page.getBlueprintTransitions()` (returns an array) or `ZDK.Page.getBlueprintTransitionByID(id)` (returns one).
- **`initiate()` programmatically triggers the transition** — equivalent to clicking the transition button in the Blueprint UI. Source example: `transObj[0].initiate()`.
- **`getId()` returns String** (source output example: `'11021234562'`).
- **`getName()` returns String** — the transition's **API name** (e.g. `'Convert_Lead'`), not the display label.
- No `before` / `after` hook accessors on the transition object — listener hookup happens via Detail-Standard event-dictionary `beforeTransition` / `afterTransition` events.
REQUIRED_PARAMETERS:
- `getId() → String` — no args.
- `getName() → String` — no args.
- `initiate()` — no args. Fires the transition.
UI_ONLY_ACTIONS:
- Blueprint authoring (states, transitions, before/during/after sections): `Setup → Automation → Blueprint → +Create Blueprint`. Note: No API equivalent — UI-only.
- Bind a Client Script to a Blueprint transition lifecycle: `Setup → Developer Hub → Client Script → Page = Detail Page → Event = beforeTransition / afterTransition`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **`initiate()` fires the transition synchronously from the user's perspective** — any `beforeTransition` interceptor listener (from event-dictionary Detail surfaces) can still `return false` and cancel. (interceptor-stack)
- **`initiate()` does not prompt the user for any "during" form fields** — if the transition has required during-fields, the standard UI prompt is bypassed and the transition may fail server-side.
- **`getName()` returns the transition's API name** (e.g. `Convert_Lead`), not the display label — switch-case on the display label fails. (label-vs-api-name)
- **Transition handle goes stale after `$Client.refresh()` / blueprint state change** — refetch via `ZDK.Page.getBlueprintTransitions()` after state changes.
- **No `cancel()` / `revert()` on BlueprintTransition** — only `initiate`. Reverting state requires server-side action.
- **`getBlueprintTransitionByID` parameter ambiguity** — `Page` doc signature shows `()` but example passes a String ID.
- **Detail Canvas vs Standard event dictionaries differ on Blueprint hooks** — Detail Canvas exposes `beforeTransition`; legacy mobile matrix references `afterTransition` only on Standard. Cross-surface scripts diverge. (cross-surface-event-availability)
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getId` | Detail | Detail | — |
| `getName` | Detail | Detail | — |
| `initiate` | Detail | Detail | — |
assets/clientscript/client-apis/button.dou.md
FILE_PURPOSE: Read when scripting against a page Button object obtained via `ZDK.Page.getButton(api_name)` or `ZDK.Page.getButtons()` to read button metadata or programmatically click / enable / disable / relabel a button.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Button
TRIGGER_KEYWORDS: ZDK.Page.getButton, ZDK.Page.getButtons, Button.click, Button.disable, Button.enable, Button.setLabel, Button.getApiName, Button.getType, record_save
TECHNICAL_CONSTRAINTS:
- Button instances are obtained only via `ZDK.Page.getButton(api_name)` (single, by API name) or `ZDK.Page.getButtons()` (Array of all buttons on the current page) — Button is not directly constructible.
- `getType()` returns one of the literal strings `'system'` or `'custom'` (source `Output: system or custom`).
- `getApiName()` returns the button's API name (e.g. `record_save`); `getLabel()` returns the displayed label (e.g. `Save`). These are distinct values — never substitute one for the other when re-acquiring the button.
- `getId()` returns a numeric-string handle (source example output: `1101122131`). Treat as opaque.
- `click()`, `disable()`, `enable()`, `setLabel(label)` are mutator calls and return nothing in the source signature.
REQUIRED_PARAMETERS:
- `ZDK.Page.getButton(api_name)` — `api_name: String` — the button's API name (e.g. `'record_save'`). Source example uses the literal `'record_save'`.
- `ZDK.Page.getButtons()` — no params; returns `Array<Button>`.
- `getId() {String}` — no params; returns String id.
- `getType() {String}` — no params; returns `'system'` | `'custom'`.
- `getLabel() {String}` — no params; returns displayed label.
- `getApiName() {String}` — no params; returns API name.
- `click()` — no params; initiates the button's click event.
- `disable()` — no params; disables the button.
- `enable()` — no params; enables the button.
- `setLabel(label)` — `label: String` — new label text. Source example: `save_btn.setLabel('Approve & Save')`.
UI_ONLY_ACTIONS:
- Defining a Custom Button (its API name, position, profile visibility) is a Setup → Customization → Modules and Fields → <Module> → Links and Buttons action. The Client API consumes the API name but does not create the button. No API equivalent for Custom Button creation from inside Client Script.
CRITICAL_FAILURE_MODES:
- **`getButton(api_name)` requires the API name, not the label** — passing the display label (e.g. `'Save'` instead of `'record_save'`) returns nothing / fails to resolve the button. (label-vs-api-name)
- **`setLabel()` does not change the API name** — re-acquiring the button after `setLabel('Approve & Save')` still requires the original API name `'record_save'`. (label-vs-api-name)
- **`getButtons()` ordering is not documented** — code that indexes `buttonObj[0]` (source's own pattern) is order-dependent and silently breaks if Setup-side button order changes.
- **`click()` fires the full click pipeline** — any `onClick` event handlers, validation, or save flow attached to the button run; calling `click()` inside an `onClick` listener for the same button risks unbounded recursion. (recursion)
- **`disable()` / `enable()` operate on the live DOM button** — state is not persisted; a re-render of the page (navigation, refresh) reverts the button to its Setup-defined state.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getId` | Create/Edit/Clone, Detail, List | Detail, Create/Edit/Clone, List | Create/Edit |
| `getType` | Create/Edit/Clone, Detail, List | Detail, Create/Edit/Clone, List | Create/Edit |
| `getLabel` | Create/Edit/Clone, Detail, List | Detail, Create/Edit/Clone, List | Create/Edit |
| `getApiName` | Create/Edit/Clone, Detail, List | List, Detail, Create/Edit/Clone | Create/Edit |
| `click` | Create/Edit/Clone, Detail, List | List, Detail, Create/Edit/Clone | Create/Edit |
| `disable` | Create/Edit/Clone, Detail, List | List, Detail, Create/Edit/Clone | Create/Edit |
| `enable` | Create/Edit/Clone, Detail, List | List, Detail, Create/Edit/Clone | Create/Edit |
| `setLabel` | Create/Edit/Clone, Detail, List | List, Detail, Create/Edit/Clone | Create/Edit |
assets/clientscript/client-apis/client-util.dou.md
FILE_PURPOSE: Read when reading client-side context (userAgent, online status, platform, current URL, geo-location, selected text, root-context page) or driving client-side actions (soft refresh, open URL, copy to clipboard) via the `$Client` utility.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/$Client
TRIGGER_KEYWORDS: $Client.userAgent, $Client.isOnline, $Client.platform, $Client.url, $Client.geoLocation, $Client.selectedValue, $Client.refresh, $Client.openURL, $Client.copyToClipboard, $Client.rootContext
TECHNICAL_CONSTRAINTS:
- `$Client` is a global utility namespace; **most properties are accessed as bare property reads** (source examples: `$Client.userAgent;`, `$Client.isOnline;`, `$Client.platform;`, `$Client.url;`, `$Client.geoLocation;`, `$Client.selectedValue;`, `$Client.rootContext`), while `refresh`, `openURL`, `copyToClipboard` are called as **methods** with parens.
- `$Client.userAgent` — browser User-Agent String (source example: a Chrome 100 on macOS UA String).
- `$Client.isOnline` — `Boolean` indicating client online status.
- `$Client.platform` — String. Source example output: `'web'`.
- `$Client.url` — current page URL String (source example shows a `crm.zoho.com` Leads create URL with `org<id>/tab/Leads/create?layoutId=<id>`).
- `$Client.geoLocation` — Object: `{ latitude, longitude, altitude, accuracy, altitudeAccuracy, heading, speed }` (numeric or `null`). **Triggers a browser permission prompt** — source verbatim: "the user is asked for permission to report location information".
- `$Client.selectedValue` — Object: `{ value: String }` — text currently selected by the user on the page. Source documents the signature as `selectedValue()` (method form) but the example uses bare property access.
- `$Client.refresh(config)` — soft-refreshes the client. `config.triggerOnLoad: Boolean <optional>` — if `true`, re-fires the page's `onLoad` event. Examples: `$Client.refresh()` and `$Client.refresh({ triggerOnLoad: true })`.
- `$Client.openURL(url)` — opens `url` in a new browser tab/window. `url: String`.
- `$Client.copyToClipboard(text)` — copies `text` to the clipboard. `text: string`.
- `$Client.rootContext` — Object: stores the **original page context that launched a form**. Used in multi-context cases (Quick Create) to reach the parent record's fields. Source example: `$Client.rootContext.getField('Name').getValue()` — `rootContext` exposes the same `getField` / etc. shape as `ZDK.Page` (i.e. it is a Page-like object).
REQUIRED_PARAMETERS:
- `$Client.userAgent` — no invocation; returns String.
- `$Client.isOnline` — no invocation; returns Boolean.
- `$Client.platform` — no invocation; returns String.
- `$Client.url` — no invocation; returns String.
- `$Client.geoLocation` — no invocation; returns Object (triggers browser permission prompt).
- `$Client.selectedValue` — no invocation; returns `{ value: String }`.
- `$Client.refresh(config?)` — `config: Object`; `config.triggerOnLoad: Boolean <optional>`.
- `$Client.openURL(url)` — `url: String`.
- `$Client.copyToClipboard(text)` — `text: string`.
- `$Client.rootContext` — no invocation; returns a Page-like Object (use its `getField` etc. methods — see [page.dou.md](./page.dou.md)).
UI_ONLY_ACTIONS:
- Granting / revoking geo-location permission is a browser-level Setup → Site Settings action (e.g. Chrome Settings → Privacy → Site Settings). The script cannot request permission programmatically without invoking `$Client.geoLocation`.
- Clipboard permission may require a user-gesture context in some browsers; opening preferences to grant clipboard access is browser-side.
CRITICAL_FAILURE_MODES:
- **`$Client.geoLocation` triggers a permission prompt** — first-time access shows a browser dialog; the user may deny, in which case the returned object may be empty / `null` / throw (source does not document the rejection path). Wrap in try-catch and handle absent permission.
- **`$Client.copyToClipboard` requires a user-gesture context in modern browsers** — calling it from a non-user-initiated handler (e.g. on page load) silently fails in Chrome / Firefox / Safari. Trigger only from click / keypress handlers.
- **`$Client.openURL` opens a new tab/window** — popup blockers may suppress non-user-gesture openURL calls; same gesture-context requirement as clipboard.
- **`$Client.refresh({triggerOnLoad:true})` re-fires `onLoad`** — calling it from inside an `onLoad` handler creates an unbounded refresh loop. (recursion)
- **`$Client.rootContext` is only meaningful in multi-context launchers (Quick Create)** — in a normal Detail / Create flow `rootContext` may be undefined or equal to the current page; do not assume parent-record access elsewhere.
- **`$Client.url` is a String snapshot** — subsequent SPA navigation does not update the previously-read value; re-read on each use.
- **`$Client.platform` enum is not fully documented** — branching on `platform === 'web'` may exclude legitimate mobile / embedded contexts.
- **`$Client.selectedValue.value`** is `''` (empty) when nothing is selected (per common DOM selection semantics) — source does not document the empty-selection shape.
assets/clientscript/client-apis/client.dou.md
FILE_PURPOSE: Read when picking `ZDK.Client.*` UX primitives — toasts, alerts, confirmations, input pop-ups, mailer, navigation, popup / callout widgets, flyouts, and loaders.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Client
TRIGGER_KEYWORDS: ZDK.Client, showMessage, showAlert, showConfirmation, getInput, openMailer, navigateTo, openPopup, openCallout, showLoader
TECHNICAL_CONSTRAINTS:
- **Active-loader mutual exclusion**: *"The presence of an active loader restricts the availability of other pop-ups."* Applies to `showMessage`, `showAlert`, `showConfirmation`, `getInput`, `openMailer`, `openPopup`, `openCallout`, `showLoader`. Always pair `showLoader` with `hideLoader` before launching another pop-up.
- **`showMessage` Markdown is restricted**: source explicitly states *"Image Markdown not supported for showMessage"*. Supported tokens: Italics `_text_`, Bold `*text*`, Underline `__text__`, Strikeout `~text~`, Code `` `text` ``, Heading1 `# text`, Heading3 `### text`, Blockquote `!text`, Hyperlink `[label](url)`.
- **`showAlert` Markdown** supports the full set above **including** image (``) and `\n` line breaks. Demonstrated in source example.
- **`showConfirmation` returns Boolean** — `true` on accept, `false` on reject. Awaitable.
- **`getInput` per-type max-value limits**: `text` 120 chars · `number` 50 digits · `textarea` 2000 chars · `picklist options` 2000 options × 120 chars each · `multiselectpicklist options` 2000 options × 120 chars each. Max inputs per call: **7**.
- **`getInput` output shape**: array. Single-input call returns `['value']`; multi-input returns `['val1', 'val2', ...]`; multiselect returns `[[<selected_actual_values>]]` (nested array).
- **`openPopup` / `openCallout` require Widget SDK ≥ 1.2** — source explicit.
- **`navigateTo` page enum**: `tab` (Web Tab / default list view), `record_create`, `record_edit`, `record_detail`, `record_clone`, `record_list` (List view or Canvas List view).
- **`navigateTo` target enum**: `_self` (default) or `_blank` (new tab).
- **`showLoader` template enum**: `spinner`, `vertical-bar`, `standard` (default). Source note: *"spinner and vertical bar can be used when invoking time consuming APIs"*. `message` limit 240 characters.
- **`navigateTo` for canvas record_detail / record_list requires `view: { type:'canvas', id:<canvas_id> }`** — source comments `// id is mandatory for canvas`.
- **`navigateTo` `params.custom_data`** — source example passes `custom_data: [{key:'value'}]` inside `params` for record_list navigation, separate from the 3rd `data` arg.
- **`openPopup` / `openCallout` / `createFlyout` accept position-config keys** — source `openPopup` example uses `left: '10px'` alongside `height` / `width`; full position keyset (likely `top`, `right`, `bottom`, `left`) not enumerated in the captured headings.
- **`openPopup` / `openCallout` / `createFlyout` `animation_type` enum**: `1` top, `2` right, `3` left, `4` bottom, `5` fade, `6` zoom.
REQUIRED_PARAMETERS:
- `showMessage(message, options?)` — `message` String; `options.type` ∈ `'info' | 'error' | 'warning' | 'success'` (default `'info'`).
- `showAlert(message, heading?, accept_message?)` — `accept_message` default `'Okay'`.
- `showConfirmation(message, accept_message?, reject_message?) → Boolean` — defaults `'Yes, Proceed'` / `'Cancel'`.
- `getInput(options[], heading?, accept_message?, reject_message?) → Object` — `options[].type` ∈ `'text' | 'number' | 'textarea' | 'picklist' | 'multiselectpicklist'`; `options[].label` String; `options[].default_value` String or Array (multiselect); `options[].list_options` `{ actual_value, display_value }[]`. Defaults: `heading='Heading'`, `accept_message='Okay'`, `reject_message='Cancel'`.
- `openMailer(mail_config)` — `mail_config.from` String; `to` / `cc` arrays of `{email, label}`; `subject`, `body`, `templateId` Strings.
- `navigateTo(page, params, data?, target?)` — `params.module` (API name, String) required; `params.record_id`, `params.layout_id`, `params.custom_view_id` optional Strings. `data` is custom navigation data — readable on target page via `$Page.navigation_data` (per source).
- `openPopup(config, data?) → object|string|number|boolean` — `config.api_name` (widget API name, Custom Button type); `config.type='widget'`; `config.header` (default `${widget name}`; `undefined` hides header); `close_icon` (default `true`); `close_on_escape` Boolean; `animation_type` 1–6 (default 1); plus height / width / position config. Awaits widget's `$Client.close()` payload.
- `openCallout(config, target, data?) → object|string|number|boolean` — like `openPopup` but anchors to a target component. `target` example: `{ type:'field', api_name:'State' }`. Closes on out-of-focus by default. `close_on_body_click` default `true`.
- `createFlyout(name, config?) → Flyout` — `name` String; `config` mirrors popup config keys (header, close_icon, close_on_exit default `true`, animation_type 1–6, height, width).
- `getFlyout(name) → Flyout` — fetch existing flyout by name.
- `showLoader(config)` — `config.type='page'` (default `'page'`); `config.template` ∈ enum above (default `'standard'`); `config.message` String (≤ 240 chars).
- `hideLoader()` — no args.
UI_ONLY_ACTIONS:
- Widget binding for `openPopup` / `openCallout` — widget must be created as **Custom Button** type via the Widgets setup screen (referenced by source: "Custom Button type"). Note: No API equivalent — UI-only.
- Mail template ID lookup for `openMailer({templateId})` — created from `Setup → Customization → Templates → Email Templates`. Note: No API equivalent for creating the template — UI-only.
CRITICAL_FAILURE_MODES:
- **Active-loader blocks every other pop-up** — calling `showAlert` while a loader is active silently fails (or queues — source ambiguous). Always `hideLoader()` first. (loader-mutex)
- **`showMessage` Markdown subset is smaller than other pop-ups** — embedding `` in `showMessage` renders raw text, not an image. (showmessage-markdown-restricted)
- **`getInput` cap of 7 inputs per call** — passing > 7 input objects silently truncates or errors.
- **`getInput` multiselect returns nested array** — `output[0]` is the array of selected actual_values; treating as flat strings yields `undefined`. (multiselect-output-shape)
- **`navigateTo` canvas variant silently falls back to standard** if `view.id` omitted — comment `// id is mandatory for canvas` is enforced. (canvas-view-id-required)
- **`navigateTo('record_detail', {module, record_id})` requires `record_id`** — calling without it does not resolve to a record. (param-required)
- **`openPopup` / `openCallout` widget API name must match a **Custom Button type** widget** — other widget types silently fail to mount. (widget-type-required)
- **`createFlyout` returns a Flyout handle; subsequent `getFlyout(name)` fetches the same handle** — calling `createFlyout` twice with same name silently re-uses or replaces (source does not state).
- **`openMailer.templateId` is a record-style ID String** — passing a template name silently no-ops. (id-vs-name)
- **`navigateTo` `data` is opaque to CRM** — on `tab` page it surfaces as widget `PageLoad` data; on other pages it surfaces as `$Page.navigation_data`. Reading the wrong global on the wrong target gives `undefined`. (cross-surface-data-channel)
- **`showLoader` `message` > 240 chars** — silently truncated. (message-cap)
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `showMessage` | all page types | all page types | all page types |
| `showAlert` | all page types | all page types | all page types |
| `showConfirmation` | all page types | all page types | all page types |
| `getInput` | all page types | all page types | all page types |
| `openMailer` | Detail | Detail | — |
| `navigateTo` | all page types | all page types | all page types |
| `openPopup` | all page types | all page types | all page types |
| `openCallout` | all page types | all page types | all page types |
| `createFlyout` | all page types | all page types | all page types |
| `getFlyout` | all page types | all page types | all page types |
| `showLoader` | all page types | all page types | all page types |
| `hideLoader` | all page types | all page types | all page types |
assets/clientscript/client-apis/component.dou.md
FILE_PURPOSE: Read when manipulating a page-level Component (custom-view picker, wizard screen, picklist-style page component) obtained via `ZDK.Page.getComponent(api_name)` — read / set value, toggle visibility, read / write options, or transition wizard screens.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Component
TRIGGER_KEYWORDS: ZDK.Page.getComponent, Component.setValue, Component.getValue, Component.setVisibility, Component.getOptions, Component.setOptions, Component.transitionTo, list-custom-view, record-create-wizard
TECHNICAL_CONSTRAINTS:
- Component instances are obtained via `ZDK.Page.getComponent(api_name)`. Source examples use `"list-custom-view"` (Custom-View picker on a List page) and `"record-create-wizard"` (Wizard screen container on a Create page).
- `setValue(value)` accepts a String — source `list-custom-view` example passes the Custom View's id String (`"4312588000000087501"`).
- `getValue()` returns the component's current value. Source does not document the return type.
- `setVisibility(value)` — `value: boolean` (`true` | `false`).
- `getOptions()` returns the component's current option list. Source does not document the shape.
- `setOptions(options)` — `options: Array` of option Objects. Source example uses `[{ id: "111113000000042834" }, { id: "111113000000042894" }]` — option objects keyed by `id`.
- `transitionTo(api_name)` — applies to wizard-style components. Source example: `ZDK.Page.getComponent("record-create-wizard").transitionTo('Car_Loan')` — transitions to the wizard screen whose API name is `Car_Loan`.
REQUIRED_PARAMETERS:
- `ZDK.Page.getComponent(api_name)` — `api_name: String` — the component's API name (source examples: `"list-custom-view"`, `"record-create-wizard"`).
- `setValue(value)` — `value: String` — component value.
- `getValue()` — no params.
- `setVisibility(value)` — `value: boolean` — `true` shows, `false` hides.
- `getOptions()` — no params.
- `setOptions(options)` — `options: Array` of option Objects (`{ id: String }` per source).
- `transitionTo(api_name)` — `api_name: String` — API name of the destination screen (wizard component only).
UI_ONLY_ACTIONS:
- Defining a Wizard, its screens, and each screen's API name is a Setup → Customization → Wizards action. The Client API drives transitions between existing screens; it does not create them.
- Defining Custom Views (the list of options the `list-custom-view` component exposes) is a Setup → Customization → Modules and Fields → <Module> → Custom Views action.
CRITICAL_FAILURE_MODES:
- **`getComponent(api_name)` requires the component's API name verbatim** — passing the display label or a typo returns nothing. Source's literal `"list-custom-view"` and `"record-create-wizard"` are dash-separated lowercase tokens. (label-vs-api-name)
- **`transitionTo(api_name)` requires the screen's API name, not its display title** — Wizard screens have separate display title and API name in Setup; using the title silently no-ops. (label-vs-api-name)
- **`setValue` on `list-custom-view` requires the Custom View id (numeric String)**, not its name — source example passes `"4312588000000087501"`, not the view's title. (label-vs-api-name)
- **`setOptions` replaces the option list with the provided Array** — partial-option patching drops omitted options.
- **`setVisibility(false)` on a required wizard component does not bypass mandatory validation** — hiding a component that the layout / wizard step marks required leaves the form unsubmittable. (layout-binding)
- **`transitionTo` fires the wizard's transition pipeline** — any `onChange` / before-transition handlers on the target screen run; calling `transitionTo` inside such a handler for the same screen risks unbounded recursion. (recursion)
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `setValue` | List, Detail | Detail, List | — |
| `getValue` | List, Detail | Detail, List | — |
| `setVisibility` | List | List | — |
| `getOptions` | List | List | — |
| `setOptions` | List | List | — |
| `transitionTo` | — | — | Create/Edit |
assets/clientscript/client-apis/crm.dou.md
FILE_PURPOSE: Read when reading global CRM context inside a Client Script — current user (role / profile / type), environment (production / sandbox), deployment DC (US / EU / CN / JP / IN / AU), or org metadata — via the `$Crm` utility.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/$Crm
TRIGGER_KEYWORDS: $Crm.user, $Crm.environment, $Crm.deployment, $Crm.org, Regular User, Client Portal User, production sandbox, US EU CN JP IN AU
TECHNICAL_CONSTRAINTS:
- `$Crm` is a global utility namespace; properties are accessed as `$Crm.<prop>` (source examples use property-access syntax, not method-call syntax — e.g. `$Crm.user`, `$Crm.environment`). The source signatures `(){Object}` / `(){string}` suggest method form, but every example accesses as a property.
- `$Crm.user` returns an Object describing the presently logged-in user. **Shape differs by user type:**
- **Regular User** keys: `id`, `zuid`, `full_name`, `first_name`, `email`, `last_name`, `type` (`"Regular User"`), `date_format`, `role: {id, name}`, `profile: {id, name}`.
- **Client Portal User** keys: `id`, `zuid`, `full_name`, `first_name`, `email`, `last_name`, `date_format`, `type` (`"Client Portal User"`), `role: {id, name}` (e.g. `{name:"Portal User"}`), `profile: {id, name}` (e.g. `{name:"ClientPortal"}`). **Portal User output in source does not include `date_format` in the same position but does include it — verify ordering not significant.**
- `$Crm.environment` returns one of the literal strings `'production'` | `'sandbox'`.
- `$Crm.deployment` returns one of the literal DC tokens `'US'` | `'EU'` | `'CN'` | `'JP'` | `'IN'` | `'AU'`.
- `$Crm.org` returns an Object: `{ type: String, domain_name: String, name: String, id: String }`. Source example: `{ type: "production", domain_name: "org77591XXXX", name: "Zoho", id: "77591XXXX" }`.
REQUIRED_PARAMETERS:
- `$Crm.user` — no invocation; property-access returns Object.
- `$Crm.environment` — no invocation; property-access returns string.
- `$Crm.deployment` — no invocation; property-access returns string.
- `$Crm.org` — no invocation; property-access returns Object.
UI_ONLY_ACTIONS:
- Sandbox / production environment switching is a Setup → Developer Space → Sandbox action. The script reads the current value via `$Crm.environment` but cannot switch environments.
- DC residency / data-centre assignment is set at org-provisioning time. `$Crm.deployment` reads it; there is no API equivalent to change it.
CRITICAL_FAILURE_MODES:
- **`$Crm.deployment` token gates base-URL construction** — code that hard-codes `https://www.zohoapis.com` (US) breaks for EU / IN / AU / etc. users. Build base URLs from `$Crm.deployment` (e.g. `zohoapis.eu`, `zohoapis.in`). (DC-mismatch)
- **`$Crm.environment === 'sandbox'` flag must gate destructive operations** — production / sandbox share the same script body; a script that always hits prod webhooks misroutes from sandbox.
- **User-type branching: `$Crm.user.type`** — `'Regular User'` vs `'Client Portal User'` exposes different navigation, profiles, and module visibility. A script that assumes `'Regular User'` shape silently fails for portal users (some keys may be absent / different).
- **`$Crm.user.profile.name`** is a display label, not an API-stable token — string-compare against `"Administrator"` etc. breaks under profile rename. Compare against `profile.id` for stability. (label-vs-api-name)
- **`$Crm.user.role.name`** is also a display label — same caveat as profile. (label-vs-api-name)
- **`$Crm.org.domain_name`** is the org subdomain (e.g. `org77591XXXX`), not a full URL — concatenation with hard-coded prefix is required.
- **Portal-User `$Crm.user`** output in source omits some keys present for Regular User (no `date_format` shown in the Portal block snippet — though it may be present at runtime). Defensive `??` / `hasOwnProperty` checks recommended.
- **Property-vs-method ambiguity** — if `$Crm.user` is actually a getter that fires on every access, repeated reads in a tight loop may incur cost; cache the value.
assets/clientscript/client-apis/field.dou.md
FILE_PURPOSE: Read when reading / writing a record's top-level Field via the `Field` object returned by `ZDK.Page.getField(api_name)` — covers value I/O, validation, styling, and lookup/picklist criteria.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Field
TRIGGER_KEYWORDS: Field, getValue, setValue, setCriteria, getPicklistOptions, mask, showError, setMandatory, setReadOnly, addStyle
TECHNICAL_CONSTRAINTS:
- Obtained via `ZDK.Page.getField(api_name)`. The arg is the field API name (not display label).
- **Detail-page write restrictions**: `setValue` works on Detail (Canvas/Standard) **only inside Mandatory Form**. Direct field writes elsewhere on Detail are silently dropped.
- **`showError` on Canvas Detail** is supported **only in Ajax edit and Mandatory form** (source explicit).
- **`setCriteria` on Detail page** supports **only lookup fields** (source explicit) — picklist / date-range criteria are Create / Edit / Clone-page features.
- **Lookup `setCriteria` operators**: `starts_with`, `equals`. The `id` field also supports `in`. Max 5 criteria per setCriteria call.
- **Picklist `setCriteria` operators**: `starts_with`, `equals`, `ends_with`, `contains`, `not_equals`, `in`. Max 5 criteria per call.
- **Date `setCriteria`**: only the `between` operator, single criterion: `(value:between:YYYY-MM-DD,YYYY-MM-DD)`.
- **`mask({character, length, reverse})`** — `character` is the mask char (e.g. `'*'`, `'#'`), `length` is mask-prefix count, `reverse=true` masks suffix instead of prefix.
- **`getValue` / `setValue` shape varies by field type**:
· Scalar fields → String / Number / Boolean.
· Checkbox → `true | false`.
· Lookup → `{ id, name }`.
· Subform → array of objects, each `{ <subform_field_api_name>: <value>, ... }`.
- **`addStyle` config**: `backgroundColor`, `foregroundColor`, `borderColor` accept hex strings (`'#FF0000'`); `fontWeight` accepts `'bold'`, numeric strings (`'100'`), or `'inherit'`.
REQUIRED_PARAMETERS:
- `getApiName() → String` — no args; returns the field API name.
- `mask({character, length, reverse})` — example: `mask({character:'*', length:4, reverse:false})` → `****rt@zohomail.com`.
- `showError(message)` — `message` String; renders inline error on the field input.
- `setValue(value)` — `value` ∈ String | Number | Boolean | Object (Object for Lookup `{id, name}` or Subform array).
- `getValue() → String | Number | Boolean | Object` — returns current value in field-type-appropriate shape.
- `setMandatory(value)` — `value` Boolean.
- `setMaxLength(value)` — `value` Number.
- `setReadOnly(value)` — `value` Boolean.
- `setVisibility(value)` — `value` Boolean. `true` shows, `false` hides.
- `addStyle(style_config)` — see config shape above.
- `addInfo(message)` — `message` String; adds an info icon with message.
- `addTooltip(message)` — `message` String; adds a tooltip.
- `setCriteria(criteria, config?)` — `criteria` is the criteria string in `((Field:operator:Value))` syntax; `config.filterOnSearch` Boolean (per source example).
- `getPicklistOptions() → array` — returns `[{ display_value, actual_value, id?, filtered? }, ...]`. Output includes a `-None-` entry first; subsequent entries carry `id` and `filtered` booleans.
UI_ONLY_ACTIONS:
- Field API name lookup: `Setup → Customization → Modules and Fields → <Module> → <Field> → Edit → API Name`. Note: No API-only equivalent for renaming — UI-only.
CRITICAL_FAILURE_MODES:
- **`setValue` on Detail surfaces is silent no-op outside Mandatory Form** — Canvas / Standard Detail field writes drop unless in Mandatory Form. Use Mandatory Form fields, or Web API Update Record + `$Client.refresh()`. (detail-setvalue-restricted)
- **`showError` on Canvas Detail is restricted to Ajax-edit + Mandatory-form contexts** — calling outside these contexts silently no-ops. (canvas-detail-showerror-restricted)
- **`setCriteria` on Detail supports only lookup fields** — calling on picklist / date on Detail silently no-ops. (detail-setcriteria-lookup-only)
- **Lookup `setCriteria` operator set is tiny** (`starts_with`, `equals`; `in` only on `id`) — using `contains` / `ends_with` against a lookup field silently rejects.
- **Picklist `setCriteria` syntax** uses `actual_value` as the key (or `id` for in-operator); using `display_value` silently fails to match.
- **`setValue` before `showError` on the same field** — `setValue` triggers a field re-render that wipes any error state applied by `showError`. Always call `setValue` first (to set or clear the value), then call `showError` on the already-rendered field. Reversing the order causes `showError` to render and then be immediately wiped. (setvalue-showerror-order)
- **`setValue` for Lookup requires `{id, name}` shape** — passing just the name string silently fails to bind the relation. (lookup-shape)
- **`setValue` for Subform replaces all rows** — pass the full row array; partial updates require `getValue() → mutate → setValue`.
- **`api_name` is the field API name, not display label** — `getField('Last_Name')` works, `'Last Name'` does not. (label-vs-api-name)
- **`mask` is display-only** — does not change `getValue()` output; do not use as a security control.
- **`getPicklistOptions` returns the `-None-` placeholder** as the first entry — iteration code must skip it. (none-placeholder)
- **`addStyle` colour values must be hex strings** — named colours / rgb() syntax not documented.
- **`setMaxLength` does not validate existing value** — if current value > new max, no auto-truncation documented.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getApiName` | all page types | all page types | all page types |
| `mask` | Detail | Detail | — |
| `showError` | Create/Edit/Clone | Detail, Create/Edit/Clone | Create/Edit |
| `setValue` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `getValue` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setMandatory` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `setMaxLength` | Create/Edit/Clone, Detail | Detail, Create/Edit/Clone | Create/Edit |
| `setReadOnly` | Create/Edit/Clone, Detail | Detail, Create/Edit/Clone | Create/Edit |
| `setVisibility` | Detail, Create/Edit/Clone | Create/Edit/Clone, Detail | Create/Edit |
| `addStyle` | Detail | Detail | — |
| `addInfo` | Detail, Create/Edit/Clone | Create/Edit/Clone, Detail | Create/Edit |
| `addTooltip` | Detail, Create/Edit/Clone | Create/Edit/Clone, Detail | Create/Edit |
| `setCriteria` | Detail, Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `getPicklistOptions` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
assets/clientscript/client-apis/flyout.dou.md
FILE_PURPOSE: Read when driving a Flyout (created via `ZDK.Client.createFlyout`) — open a Widget inside it with PageLoad data, notify the Widget (with optional await), or close the Flyout.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Flyout
TRIGGER_KEYWORDS: ZDK.Client.createFlyout, ZDK.Client.getFlyout, Flyout.open, Flyout.notify, Flyout.close, widget PageLoad, Notify, NotifyAndWait, widget sdk 1.2
TECHNICAL_CONSTRAINTS:
- Flyout instances are obtained via `ZDK.Client.getFlyout(name)` after creation via `ZDK.Client.createFlyout(name, config)` (see [client.dou.md](./client.dou.md)).
- **Active-loader mutex** — source verbatim: "The presence of an active loader restricts the availability of other pop-ups." A loader started via `ZDK.Client.showLoader` will block `Flyout.open`. (listener-after-init)
- `open(config, data)` opens a Widget inside the flyout. `config.type` must be the literal String `'widget'`. `config.api_name` is the Widget's API name (registered as a Custom Button-type Widget). `data` is forwarded to the Widget's `'PageLoad'` event (Widget SDK ≥ 1.2).
- `notify(data, options)` — pushes `data` to the Widget. If `options.wait` is `true`, the Widget receives the `'NotifyAndWait'` event and the call awaits the Widget's response (return type `{Any}`); otherwise the Widget receives `'Notify'` (fire-and-forget). Source verbatim: "The 'Notify' is not supported for components."
- `close()` — closes the flyout.
- Source-stated Widget SDK floor: **>= 1.2**. Widgets built against SDK < 1.2 do not receive `'PageLoad'` data forwarded via `open`'s second argument.
REQUIRED_PARAMETERS:
- `open(config, data)`:
- `config: Object` — flyout type config.
- `config.type: String` — must be `'widget'` (source enumerates only `widget`).
- `config.api_name: String` — API name of the Widget (Custom Button type).
- `data: Any <optional>` — forwarded to the Widget's `'PageLoad'` event.
- `notify(data, options)`:
- `data: Any` — payload forwarded to Widget's `'Notify'` / `'NotifyAndWait'` event.
- `options: Object <optional>`.
- `options.wait: String <optional>` — `'true' | 'false'` (source types `wait` as `String`, not `Boolean`; example passes the boolean literal `true`).
- `close()` — no params.
UI_ONLY_ACTIONS:
- Registering / hosting the Widget that the Flyout loads is a Setup → Developer Space → Widgets action (see [../../widgets/widgets-usage.dou.md](../../widgets/widgets-usage.dou.md)). The Flyout API consumes the Widget API name but does not host the Widget.
CRITICAL_FAILURE_MODES:
- **Active loader blocks `Flyout.open`** — calling `open` while a loader from `ZDK.Client.showLoader` is active silently fails to surface the flyout. Hide the loader first. (listener-after-init)
- **`config.type` must be the literal String `'widget'`** — source enumerates no other value; an unknown type silently fails to open. (label-vs-api-name)
- **`config.api_name` must be the Widget's API name, not its display name** — using the display name fails to resolve the Widget. (label-vs-api-name)
- **Widget must be SDK ≥ 1.2 to receive `data` via `'PageLoad'`** — older Widgets open but their `PageLoad` handler does not receive the forwarded `data`. (listener-after-init)
- **`notify` is not supported for components** — source verbatim: "The 'Notify' is not supported for components." Only Widgets opened via `Flyout.open` receive `Notify` / `NotifyAndWait`. (listener-after-init)
- **`options.wait: true` blocks until the Widget responds** — a Widget that fails to call its `NotifyAndWait` responder leaves the script awaiting indefinitely. Consider a Widget-side timeout.
- **`close()` is silent** — no documented error if the flyout was never opened or was already closed.
- **Widget opened via `Flyout.open` must be registered as Custom Button type** — using any other Widget type (Dashboard, Web Tab, Custom Related List, etc.) silently fails to mount the Widget inside the flyout. When delivering a Flyout solution, always state the Custom Button type requirement in deployment instructions *before* the user registers the Widget in CRM Setup. (widget-type-required)
SUPPORTED_PAGES:
- `open` — no page restriction; available on all page types.
- `notify` — no page restriction; available on all page types.
- `close` — no page restriction; available on all page types.
assets/clientscript/client-apis/form.dou.md
FILE_PURPOSE: Read when bulk-reading or bulk-writing all fields (including subforms and lookups) on a Create / Edit / Quick-Create / Clone form via the Form object from `ZDK.Page.getForm()`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Form
TRIGGER_KEYWORDS: ZDK.Page.getForm, Form.setValues, Form.getValues, lookup id name, Subform_1, Email_Opt_Out, bulk field write
TECHNICAL_CONSTRAINTS:
- The Form object is obtained via `ZDK.Page.getForm()`. Source does not document constructor / multi-form accessors.
- `setValues(values)` accepts an Object keyed by **field API name** (verbatim: `'Custom_Field1'`, `'Account_Name'`, `'Email_Opt_Out'`, `'Subform_1'` in source example). Display labels (`"Last Name"` with a space) are not accepted. (label-vs-api-name)
- **Lookup fields take a `{ id, name }` Object** — source example: `'Account_Name': { id: "5164254000000366162", name: "John Doe" }`. Plain strings will not resolve the lookup.
- **Boolean fields take a literal `true` / `false`** — source example: `'Email_Opt_Out': true`.
- **Subform fields take an Array of row Objects keyed by subform-field API name** — source example: `'Subform_1': [{ 'Email_1': 'henry@zylker.com', 'Website1': 'www.henryzyker.com' }]`.
- `getValues() {Object}` returns the entire form payload as an Object keyed by field API name, with subform rows nested as Arrays of Objects. Source example output mixes top-level subform-row keys (`'Subform_1': [{...}]`) **and** flattened sibling keys (`"Email_1": "henry@zylker.com"`) at the top level.
REQUIRED_PARAMETERS:
- `ZDK.Page.getForm()` — no params; returns the current page's Form object.
- `setValues(values)` — `values: Object` keyed by field API name. Required structure per field type:
- Text / Number / Boolean → primitive.
- Lookup → `{ id: String, name: String }`.
- Subform → `Array<Object>` (each row keyed by subform-field API name).
- `getValues() {Object}` — no params; returns the form's current values Object.
UI_ONLY_ACTIONS:
- Defining which fields render on a form (layout, section order, mandatory rules, validation rules) is a Setup → Customization → Modules and Fields → <Module> → Layouts / Validation Rules action. `setValues` writes only to fields that the active layout exposes. (layout-binding)
CRITICAL_FAILURE_MODES:
- **`setValues` keys must be field API names, not display labels** — `setValues({ "Last Name": "..." })` silently no-ops; correct form is `setValues({ Last_Name: "..." })`. (label-vs-api-name)
- **Lookup writes require `{ id, name }`** — `setValues({ Account_Name: "John Doe" })` does not resolve the account; required shape is `{ id: "<record_id>", name: "John Doe" }`. (label-vs-api-name)
- **Subform writes replace the visible row set with the provided Array** — partial-row patching (without including unchanged rows) drops the omitted rows.
- **Fields hidden by the active layout / profile silently no-op** — writing to a field that the current layout does not expose returns no error but does not persist on save. (layout-binding)
- **`getValues()` reflects only fields rendered on the form** — fields suppressed by the active layout are not in the output, so a downstream comparison against the full module schema falsely reports missing values. (layout-binding)
- **The output shape ambiguity (nested-vs-flat subform keys) above must be resolved before consumers iterate** — naive `Object.keys(form.getValues())` will see both `Subform_1` and `Email_1` and may double-process the subform-row data.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `setValues` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `getValues` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
assets/clientscript/client-apis/http.dou.md
FILE_PURPOSE: Read when calling a third-party HTTP endpoint from Client Script via `ZDK.HTTP.request` — covers method enum, request shape, response handle, and the per-page request cap.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/HTTP
TRIGGER_KEYWORDS: ZDK.HTTP, request, AJAX, headers, parameters, content, Response, third-party
TECHNICAL_CONSTRAINTS:
- **Per-page request cap**: source explicit — *"Supports a maximum of 15 requests per page."* The cap is per page load, not per script.
- Returns a `Response` object — see [response.dou.md] (separate node, not yet distilled).
- `method` enum: `'POST' | 'GET' | 'PUT' | 'DELETE' | 'PATCH'`. Default `'GET'`.
- `request_object.content` is the request body (raw string). For JSON payloads, set `headers['Content-Type'] = 'application/json'`.
- `request_object.parameters` is the query-string map; merged into the URL.
- `request_object.headers` is a free-form object of request headers.
- `ZDK.HTTP.request` is the only documented method on `ZDK.HTTP` (no `get`, `post`, `put` shortcuts).
- This API targets **external** endpoints. For Zoho CRM record operations, use Web API (`ZDK.Apps.CRM.*`) — those calls do not consume the 15-request cap on this page object.
REQUIRED_PARAMETERS:
- `request(request_object) → Response`
· `request_object.url` (String, required) — request URL.
· `request_object.method` (enum, optional, default `'GET'`).
· `request_object.content` (String, optional) — request body.
· `request_object.headers` (Object, optional).
· `request_object.parameters` (Object, optional) — query parameters.
UI_ONLY_ACTIONS:
- *(none in source)* — `ZDK.HTTP.request` does not require any console-side binding; URL allow-listing (if any) is implicit.
CRITICAL_FAILURE_MODES:
- **15-requests-per-page hard cap** — request #16 silently errors (or rejects). Hoist repeated calls into a single batched endpoint, or cache in `getForm().getValues()` / `$Page.navigation_data`. (page-request-cap)
- **`content` is a String, not auto-serialised** — passing a JS object to `content` without `JSON.stringify` sends `[object Object]`. (body-serialisation)
- **`Content-Type` header is not auto-set** — JSON bodies need `headers: { 'Content-Type': 'application/json' }` explicitly.
- **`parameters` keys are URL-encoded once** — pre-encoding values double-encodes.
- **CORS / CSP / domain-allowlist behaviour unspecified in source** — third-party endpoints that block browser-origin calls fail with no documented error code in this page.
- **No documented `timeout` parameter** — long-running requests block per-page request budget and may contend with the 10 s mobile timeout (per `../clientscript-mobile.dou.md`).
- **No documented retry / circuit-breaker** — caller must implement retries manually; counts against the 15-call cap.
- **Synchronous vs async semantics not stated in source** — example does not show `await` / `.then`.
- **No Zoho CRM auth automatically attached** — `ZDK.HTTP.request` is for third-party calls; do not use it to hit `*.zohoapis.com/crm/...` (use Web API instead). (wrong-tool-for-crm)
SUPPORTED_PAGES:
- `request` — no page restriction; available on all page types.
assets/clientscript/client-apis/link.dou.md
FILE_PURPOSE: Read when scripting against a page Link object obtained via `ZDK.Page.getLinks()` to read a Custom Link's id / name or programmatically click / enable / disable it.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Link
TRIGGER_KEYWORDS: ZDK.Page.getLinks, Link.getId, Link.getName, Link.click, Link.disable, Link.enable, custom link
TECHNICAL_CONSTRAINTS:
- Link instances are obtained via `ZDK.Page.getLinks()` (Array of all links on the current page). Source does not document a single-link accessor by name.
- `getId()` returns a numeric-string handle (source example output: `11021234562`). Treat as opaque.
- `getName()` returns the link's name token (source example output: `my_link`).
- `click()`, `disable()`, `enable()` are mutator calls and return nothing in the source signature.
REQUIRED_PARAMETERS:
- `ZDK.Page.getLinks()` — no params; returns `Array<Link>`.
- `getId() {String}` — no params; returns String id.
- `getName() {String}` — no params; returns the link's name token.
- `click()` — no params; initiates the link's click event.
- `disable()` — no params; disables the link.
- `enable()` — no params; enables the link.
UI_ONLY_ACTIONS:
- Creating / configuring a Custom Link (its name, URL pattern, profile visibility, merge-fields) is a Setup → Customization → Modules and Fields → <Module> → Links and Buttons action. The Client API consumes the link object but does not create it. No API equivalent for Custom Link creation from inside Client Script.
CRITICAL_FAILURE_MODES:
- **`getLinks()` ordering is not documented** — code that indexes `linkObj[0]` (source's own pattern) is order-dependent and silently breaks if Setup-side link order changes.
- **`click()` fires the full link-click pipeline** — for a custom link that navigates / opens a URL, the navigation happens. Calling `click()` inside an `onClick` handler for the same link risks unbounded recursion. (recursion)
- **`disable()` / `enable()` operate on the live DOM link** — state is not persisted; a re-render of the page reverts the link to its Setup-defined state.
- **No per-link `getApiName()` documented** — if `getName()` returns the display label (not the API name), there is no way to re-acquire the same link by stable identifier across renders.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getId` | Detail | Detail, List | — |
| `getName` | Detail | Detail, List | — |
| `click` | Detail | Detail, List | — |
| `disable` | Detail | Detail, List | — |
| `enable` | Detail | Detail, List | — |
assets/clientscript/client-apis/list.dou.md
FILE_PURPOSE: Read when scripting the List View — freeze columns, mask a field, sort by field, read / select / clear selected records, or style records / fields conditionally via the List object from `ZDK.Page.getList()`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/List
TRIGGER_KEYWORDS: ZDK.Page.getList, List.freezeColumns, List.maskField, List.sortByField, List.getRecords, List.selectRecords, List.selectRecordsByID, List.clearSelection, List.style, list view
TECHNICAL_CONSTRAINTS:
- The List object is obtained via `ZDK.Page.getList()`. Source does not document multi-list accessors.
- `freezeColumns(option)` — `option: Boolean` — `true` freezes the view and disables column modification; `false` unfreezes.
- `maskField(field_name, config)` — masks a field's rendered value across all rows. Source config keys: `length: Number` (chars to mask), `character: Char` (mask character), `reverse: Boolean` (mask from the right). Source example: `maskField("Email", { length: 8, character: '#', reverse: true })`.
- `sortByField(field_name, option)` — `option: String` — one of the literal tokens `'asc' | 'desc' | 'unsort'`.
- `getRecords()` — returns the list's records. Each record carries a `$selected` property indicating whether the row is currently selected (source: "$selected property denoted if respective records is selected or not").
- `selectRecords(criteria)` — `criteria: String <optional>` — COQL-style criteria expression. Source example: `"((Last_Name:equals:Burns)and(First_Name:starts_with:M))"`. **Only fields rendered in the list are available for criteria** (source verbatim: "Fields that are in the list are only available for criteria").
- `selectRecordsByID(ids)` — `ids: Array<String>` — array of record id strings. Source example: `["111111000000000812", "111111000000000834", "111111000000000845"]`.
- `clearSelection()` — clears the current selection set.
- `style(style_config, criteria)` — applies conditional styling. `style_config.record` Object supports key `backgroundColor`. `style_config.field` Object keyed by field API name supports `backgroundColor`, `fontWeight`, `foregroundColor`. `criteria: String` — same COQL-style expression as `selectRecords`. To remove styling, pass empty Objects: `style({ record: {}, field: {} }, "<criteria>")`.
REQUIRED_PARAMETERS:
- `ZDK.Page.getList()` — no params.
- `freezeColumns(option)` — `option: Boolean`.
- `maskField(field_name, config)` — `field_name: String` (field API name); `config: Object` with `{ length: Number, character: Char, reverse: Boolean }`.
- `sortByField(field_name, option)` — `field_name: String` (field API name); `option: String` in `{'asc','desc','unsort'}`.
- `getRecords()` — no params.
- `selectRecords(criteria?)` — `criteria: String` (optional).
- `selectRecordsByID(ids)` — `ids: Array<String>`.
- `clearSelection()` — no params.
- `style(style_config, criteria)` — `style_config: Object` (with optional `.record` and `.field` sub-Objects); `criteria: String`.
UI_ONLY_ACTIONS:
- Defining which fields appear in the List View (column set) is a Setup → Customization → Modules and Fields → <Module> → Custom Views action. `selectRecords` / `style` criteria can only reference columns that the active Custom View includes (list-bound). (layout-binding)
- Defining Custom Views themselves (the saved filter that determines which records load) is also Setup-only.
CRITICAL_FAILURE_MODES:
- **`maskField` / `sortByField` / `selectRecords` field names must be API names** — passing display labels (`"Email Address"` instead of `"Email"`) silently no-ops. (label-vs-api-name)
- **`selectRecords` / `style` criteria can only reference fields rendered in the current Custom View** — referencing a field absent from the column set returns no records / styles nothing, with no error. (layout-binding)
- **`sortByField(field_name, 'asc'|'desc'|'unsort')` — any other token silently no-ops** — there is no documented error path for an unsupported sort option.
- **`style` with empty Objects (`{record:{}, field:{}}`) is the documented removal idiom** — omitting the criteria removes styling globally; including a stale criteria removes it only from matching rows.
- **`maskField` is a render-only mask** — the underlying record value remains unmasked; downstream `getRecords()` / `getValue()` calls return the real value. Do not rely on `maskField` for security / HIPAA redaction. (HIPAA-redaction)
- **`getRecords()` `$selected` is a snapshot** — subsequent UI clicks change selection but the previously returned array is not updated; re-call `getRecords()` after user interaction.
- **`selectRecordsByID(ids)` silently ignores ids not present in the current page of records** — Custom-View pagination / row-virtualization may make some ids unreachable.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `freezeColumns` | List | — | — |
| `maskField` | List | List | — |
| `sortByField` | List | List | — |
| `getRecords` | List | List | — |
| `selectRecords` | List | List | — |
| `selectRecordsByID` | List | List | — |
| `clearSelection` | List | List | — |
| `style` | List | List | — |
assets/clientscript/client-apis/page-util.dou.md
FILE_PURPOSE: Read when reading the current page's CRM context inside a Client Script — current record, record id, layout id, module, parent-record id (clone), custom navigation data, or page name — via the `$Page` utility.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/$Page
TRIGGER_KEYWORDS: $Page.record, $Page.record_id, $Page.layout_id, $Page.module, $Page.parent_record_id, $Page.navigation_data, $Page.name, record_detail, record_create, record_clone, record_edit, record_list
TECHNICAL_CONSTRAINTS:
- `$Page` is a global utility namespace; **every example accesses properties bare** (no parens) — e.g. `$Page.record;`, `$Page.record_id;`, `$Page.module;`. Source signatures notate them as `(){Object}` / `(){String}` (method form).
- `$Page.record` returns the **current record as stored on the server** (source verbatim: "get current record information stored in the server"). Source example output: `{ First_Name: "John", Full_Name: "John zylker", Last_Name: "zylker", ... }`. Object keys are field API names.
- `$Page.record_id` returns the current record id as String (source example: `527627200000037XXXX`). Treat as opaque.
- `$Page.layout_id` returns the current layout id as String (source example: `527627200000009XXXX`). Treat as opaque.
- `$Page.module` returns the current module's API name as String (source example: `Leads`).
- `$Page.parent_record_id` returns the parent record id **while cloning** (source verbatim: "Get the parent record ID while cloning"). Source example: `1776121000000694009`.
- `$Page.navigation_data` returns the custom `params.custom_data` payload forwarded by `ZDK.Client.navigateTo` (see [client.dou.md](./client.dou.md)). Source example: caller passes `custom_data: [{ key: "value" }]` and receiver reads `$Page.navigation_data` → `{ key: "value", ... }`. **Source returns the payload as an Object** (`{ key: "value", ... }`) even though the caller passed an Array (`[{ key: "value" }]`).
- `$Page.name` returns the **current page name** as one of the literal tokens: `'record_detail'` | `'record_create'` | `'record_clone'` | `'record_edit'` | `'record_list'` | `undefined`. Source declares the signature as `name()` with no return-type annotation.
REQUIRED_PARAMETERS:
- `$Page.record` — no invocation; returns Object keyed by field API names.
- `$Page.record_id` — no invocation; returns String.
- `$Page.layout_id` — no invocation; returns String.
- `$Page.module` — no invocation; returns String (module API name).
- `$Page.parent_record_id` — no invocation; returns String (clone scenario).
- `$Page.navigation_data` — no invocation; returns Object (mirrors `custom_data` payload from `ZDK.Client.navigateTo`).
- `$Page.name` — no invocation; returns String in `{'record_detail','record_create','record_clone','record_edit','record_list'}` or `undefined`.
UI_ONLY_ACTIONS:
- Defining the layout that `$Page.layout_id` resolves to is a Setup → Customization → Modules and Fields → <Module> → Layouts action. `$Page.layout_id` reads the active layout's id but does not switch it.
CRITICAL_FAILURE_MODES:
- **`$Page.record` is the server-side snapshot, not the live form values** — on an Edit page, user edits in the form are not reflected in `$Page.record` until save; use `ZDK.Page.getForm().getValues()` for in-flight values. (layout-binding)
- **`$Page.record_id` is empty / undefined on Create pages** — guarding on truthiness before using it as a key avoids passing `undefined` to downstream API calls.
- **`$Page.layout_id` reflects the active layout** — a script that compares `layout_id` against a hard-coded value silently misroutes when an admin clones / renames layouts; pin against the documented id and confirm in Setup.
- **`$Page.module` returns the module API name, not the display label** — string-compare against `"Leads"` works only because `Leads` happens to be both API name and label; for renamed / custom modules use the API name. (label-vs-api-name)
- **`$Page.parent_record_id` is meaningful only during Clone** — reading it on a non-clone page returns an undocumented value (likely `undefined` / empty). Always branch on `$Page.name === 'record_clone'` first.
- **`$Page.navigation_data` is `undefined` when the page was not reached via `ZDK.Client.navigateTo`** — direct navigation (URL paste, bookmark, in-app link) leaves it empty. Branch defensively.
- **`$Page.name === undefined`** is a documented value (source enumerates `undefined` in the page-name set) — code that switches on the 5 known names without an `undefined` / default branch silently no-ops on unsupported page types (Wizard, Canvas list, etc.).
- **Property-vs-method ambiguity** — if `$Page.record` is a getter that re-fetches from the server on every read, repeated access in a loop incurs cost; cache the value.
assets/clientscript/client-apis/page.dou.md
FILE_PURPOSE: Read when picking `ZDK.Page.*` accessors to reach Fields, Subforms, Components, Form, List, Buttons, Links, Blueprint Transitions, or Tags on the current Client-Script page.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Page
TRIGGER_KEYWORDS: ZDK.Page, getField, getSubform, getComponent, getForm, getList, getButton, getBlueprintTransitions, addTag, removeTag
TECHNICAL_CONSTRAINTS:
- `ZDK.Page` is the page-scoped root accessor on Client Script — every method returns either a single domain object or an array of them; no method on `ZDK.Page` itself takes a write payload (writes happen on the returned objects).
- Component `name` is an enum (not a free string). Source-enumerated names: `list-custom-view` (custom view drop down on list view), `list-view-type` (view-select drop down on list view), `canvas-list-view` (canvas-list-view drop down on canvas listview page), `record-create-wizard` (wizard component on create / edit / draft page), `notes-editor` (notes component on detail / canvas-detail / list page).
- `addTag` / `removeTag` accept `<repeatable>` String varargs — single call: `addTag('tagname')`; multi-tag: `addTag('tagname1', 'tagname2', 'tagname3')`.
- `getTags()` returns array of objects shaped `{ name: <tag> }` — not a flat string array.
- **`getButtonByID` is the only method without a "Supported Pages" subsection in source** — implies universal availability (button IDs resolve on any page that hosts the button).
REQUIRED_PARAMETERS:
- `getField(api_name) → Field` — `api_name` is the field API name (not display label). Example: `ZDK.Page.getField('Last_Name')`.
- `getFields() → Field[]` — no args; returns all fields on the page.
- `getSubform(api_name) → Subform` — `api_name` is the subform API name. Example: `ZDK.Page.getSubform('CFSubform')`.
- `getComponent(name) → Component` — `name` is one of the 5 source-enumerated component names.
- `getComponents() → array` — no args; returns all components.
- `getForm() → Form` — no args; returns the page Form object.
- `getList() → List` — no args; returns the List-View object (list-page surfaces).
- `getButtonByID(id) → Button` — `id` is a String button ID. Example: `getButtonByID('118881188181')`.
- `getButton(api_name) → Button` — `api_name` is the button API name. Example: `getButton('record_save')` → `{ getId, getApiName, getType }`.
- `getButtons() → Button[]` — no args; returns all page buttons.
- `getLinkByID(id) → Link` — `id` is a String link ID.
- `getLinks() → Link[]` — no args.
- `getBlueprintTransitionByID(id) → BlueprintTransition` — fetch shows `id` arg in example (`'104567100090'`) though signature line displays `()`.
- `getBlueprintTransitions() → BlueprintTransition[]` — no args.
- `addTag(...tag_names)` — variadic String tag names. No return value documented.
- `removeTag(...tag_names)` — variadic String tag names. No return value documented.
- `getTags() → Array.object` — returns `[{ name }, ...]`.
UI_ONLY_ACTIONS:
- *(none in source)* — `ZDK.Page` is a script-side reader, not a UI-binding surface; binding is configured via Client Script setup (see `references/dou/clientscript/event-dictionary/` nodes).
CRITICAL_FAILURE_MODES:
- **`api_name` is the API name, not the display label** — `getField('Last_Name')` works; `getField('Last Name')` does not. (label-vs-api-name)
- **`getComponent(name)` outside the 5 source-enumerated names returns nothing useful** — silent miss on typos like `'noteseditor'` or `'wizard'`. (component-name-enum)
- **`getList()` only resolves on list-page surfaces** — calling on a Detail / Create / Edit / Quick-Create page returns nothing operational.
- **`getBlueprintTransitions()` only resolves on Detail-surface pages where Blueprint is bound** — Create / List / Quick-Create surfaces have no transitions.
- **`addTag` / `removeTag` operate on the current page record** — on Create / Edit surfaces before save, tag persistence depends on save; on Detail surfaces tags fire immediately.
- **`getButtonByID` vs `getButton(api_name)` are distinct accessors** — passing an API name to `getButtonByID` (or vice versa) silently returns nothing. (id-vs-api-name)
- **No `setTag` / `replaceTags`** — only add / remove; full-replace requires `getTags()` + diff + per-name remove + per-name add.
- **`Component` name strings are surface-coupled** — e.g. `record-create-wizard` only resolves on Create/Edit/Draft, `notes-editor` only on Detail / Canvas Detail / List. Cross-surface scripts silently miss. (component-surface-coupling)
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getField` | Detail, Create/Edit/Clone | Detail, Create/Edit/Clone | Create/Edit |
| `getFields` | Detail, Create/Edit/Clone | Detail, Create/Edit/Clone | Create/Edit |
| `getSubform` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `getComponent` | List, Detail | Detail, List | Wizard |
| `getComponents` | List, Detail | Detail, List | Wizard |
| `getForm` | Detail, Create/Edit/Clone | Detail, Create/Edit/Clone | Create/Edit |
| `getList` | List | List | — |
| `getButtonByID` | all page types | all page types | all page types |
| `getButton` | all page types | all page types | all page types |
| `getButtons` | Create/Edit/Clone, Detail, List | Detail, Create/Edit/Clone, List | Wizard |
| `getLinkByID` | Detail | Detail, List | — |
| `getLinks` | Detail | Detail, List | — |
| `getBlueprintTransitionByID` | Detail | Detail | — |
| `getBlueprintTransitions` | Detail | Detail | — |
| `addTag` | Detail | Detail | — |
| `removeTag` | Detail | Detail | — |
| `getTags` | Detail | Detail | — |
assets/clientscript/client-apis/response.dou.md
FILE_PURPOSE: Read when inspecting the return value of `ZDK.HTTP.request(...)` — extract the response body via `getResponse()`, headers via `getHeaders()`, or HTTP status code via `getStatusCode()`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Response
TRIGGER_KEYWORDS: ZDK.HTTP.request, Response.getResponse, Response.getHeaders, Response.getStatusCode, http response, status code
TECHNICAL_CONSTRAINTS:
- Response instances are returned synchronously by `ZDK.HTTP.request(config)` (see [http.dou.md](./http.dou.md)). Response is not directly constructible.
- `getResponse() {Object}` — returns the parsed response body as an Object. Source does not document raw-string vs auto-parsed-JSON semantics.
- `getHeaders() {Object}` — returns the response headers as an Object (header-name keyed). Source does not document case-sensitivity or multi-value-header handling.
- `getStatusCode() {Number}` — returns the HTTP status code as a Number. Source example output: `201`.
- Source does not enumerate any error / failure shape. For 4xx / 5xx responses, the assumption is that `getStatusCode()` returns the non-2xx code and `getResponse()` / `getHeaders()` still resolve, but source is silent.
REQUIRED_PARAMETERS:
- `getResponse() {Object}` — no params.
- `getHeaders() {Object}` — no params.
- `getStatusCode() {Number}` — no params.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- **Response is the return of a synchronous `ZDK.HTTP.request` call** — the `request` call counts against the documented per-page cap (15 — see [http.dou.md](./http.dou.md)). Holding a Response and re-issuing the request does not bypass the cap.
- **`getResponse()` shape is not contractually documented** — code that assumes `JSON.parse`-style Object access will throw on a non-JSON response if the call returns String. Type-guard before property access.
- **`getHeaders()` key case is not documented** — `headers['Content-Type']` vs `headers['content-type']` may not be interchangeable.
- **No error-shape documented** — non-2xx responses, network failures, and timeouts are not described. A caller that branches only on `getStatusCode() >= 400` may miss network errors that throw before Response is constructed.
- **Holding a Response across an `await` / `Page` transition** — source does not document Response lifetime; treat as ephemeral and re-fetch rather than caching across page navigation.
SUPPORTED_PAGES:
- `getResponse` — no page restriction; available on all page types.
- `getHeaders` — no page restriction; available on all page types.
- `getStatusCode` — no page restriction; available on all page types.
assets/clientscript/client-apis/subform.dou.md
FILE_PURPOSE: Read when manipulating a Subform as a whole via the `Subform` object returned by `ZDK.Page.getSubform(api_name)` — covers row-level bulk I/O, row insert / delete / clear, and subform-level mandatory / info.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/Subform
TRIGGER_KEYWORDS: Subform, setValues, getValues, insertRows, deleteRow, clear, getRow, getField, setMandatory
TECHNICAL_CONSTRAINTS:
- Obtained via `ZDK.Page.getSubform(api_name)`. Returns a `Subform` handle on the named subform.
- **`setValues(values)` replaces the entire subform contents** with the provided array of row objects. Each row is a `{ <field_api_name>: <value>, ... }` map.
- **`insertRows(values, index?)`** — `values` is an array of row objects. If `index` provided, rows are inserted at that index; if omitted, rows append.
- **`deleteRow(index)`** removes a single row at the given 0-based index.
- **`clear()`** removes all rows.
- **`getRow(index) → SubformRow`** — 0-based index; see [subformrow.dou.md](./subformrow.dou.md).
- **`getField(api_name) → SubformField`** — returns the SubformField definition (not a per-row cell); see [subformfield.dou.md](./subformfield.dou.md) for the field-level operations.
- **Row indexing is 0-based** (source examples show `getRow(0)` / `getRow(1)` for first / second).
REQUIRED_PARAMETERS:
- `getApiName() → String` — no args; returns subform API name.
- `getField(api_name) → SubformField` — `api_name` String.
- `setValues(values)` — `values` Array of objects. Each object's keys are field API names, values are the field values.
- `getValues() → Array` — returns `[{ <api_name>: <value>, ... }, ...]`.
- `getRow(index) → SubformRow` — `index` Number, 0-based.
- `insertRows(values, index?)` — `values` Array of row objects; `index` Number (optional, append if omitted).
- `deleteRow(index)` — `index` Number.
- `clear()` — no args.
- `setMandatory(value)` — `value` Boolean.
- `addInfo(message)` — `message` String.
UI_ONLY_ACTIONS:
- Subform layout & field definitions configured via `Setup → Customization → Modules and Fields → <Module> → <Subform> → Edit`. Note: No API equivalent for subform layout authoring — UI-only.
CRITICAL_FAILURE_MODES:
- **`setValues` is a full replace, not a merge** — to append, use `insertRows` (or `getValues` + concat + `setValues`). (subform-replace-semantics)
- **Row keys are field API names**, not display labels — `'Name_1'` works, `'Name'` (display) does not. (label-vs-api-name)
- **`getRow(index)` out-of-bounds returns silently** (no error documented) — guard with `getValues().length`.
- **`insertRows` with no `index` appends** — passing `null` or `undefined` for `index` may behave differently from omitting.
- **`deleteRow(index)` shifts subsequent indices** — iterating + deleting requires reverse iteration or index-recalculation.
- **Subform `setValues` cannot exceed the subform-row hard cap** — CRM enforces a per-subform row cap separately.
- **No bulk `updateRow(index, partial)`** — partial-row updates require `getRow(index).setValues({...})` (SubformRow API) or `getRow(index).getCell(api).setValue(val)` (SubformCell API).
- **No `getRows() → SubformRow[]`** documented at the Subform level — use `getValues()` for bulk read or loop `getRow(i)` for handles.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getApiName` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `getField` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setValues` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `getValues` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `getRow` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `insertRows` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `deleteRow` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `clear` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `setMandatory` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `addInfo` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
assets/clientscript/client-apis/subformcell.dou.md
FILE_PURPOSE: Read when reading or writing a single cell inside a Subform row via the `SubformCell` object returned by `SubformRow.getCell(field)`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/SubformCell
TRIGGER_KEYWORDS: SubformCell, getValue, setValue, setReadOnly, showError, cell, value
TECHNICAL_CONSTRAINTS:
- Obtained via `ZDK.Page.getSubform(api_name).getRow(index).getCell(field)`.
- **`setValue(value)`** accepts `String | Number | Boolean | Object`. The Object shape mirrors Field.setValue semantics (e.g. Lookup → `{id, name}`).
- **`showError(message)`** renders inline error on the cell input (e.g. "List Price cannot be zero").
- **`setReadOnly(value)`** sets read-only at the cell level — finer-grained than `SubformRow.setReadOnly`.
REQUIRED_PARAMETERS:
- `getValue() → String | Object` — source-listed return types; shape depends on cell's field type.
- `setValue(value)` — `value` ∈ String | Number | Boolean | Object.
- `setReadOnly(value)` — `value` Boolean.
- `showError(message)` — `message` String.
UI_ONLY_ACTIONS:
- *(none in source)* — `SubformCell` is a leaf data handle, not a UI configuration surface.
CRITICAL_FAILURE_MODES:
- **`setValue` shape must match cell's field type** — passing a String to a Lookup cell silently fails; pass `{id, name}`. (cell-value-shape)
- **`setValue` on read-only cell silently no-ops** — call `setReadOnly(false)` first if mutation needed. (readonly-mutation)
- **`showError` may share Field's Detail-surface restrictions** (Ajax-edit / Mandatory-form only) — source does not state the restriction explicitly for SubformCell.
- **No `getApiName()` on SubformCell** — cell handles do not expose which field they're on (caller must remember). To inspect, fetch the SubformField via `Subform.getField(api_name)`. (no-introspection)
- **Cell handle goes stale after row delete / subform clear / setValues replace** — refetch via `getRow(i).getCell(api)` after parent mutations.
- **No `mask` / `addStyle` / `addTooltip` on SubformCell** — those decorations are Field-level only; SubformCell only exposes `getValue` / `setValue` / `setReadOnly` / `showError`. (decoration-gap)
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getValue` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setValue` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setReadOnly` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `showError` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
assets/clientscript/client-apis/subformfield.dou.md
FILE_PURPOSE: Read when configuring a Subform field definition (cross-row attributes) via the `SubformField` object returned by `Subform.getField(api_name)` — covers criteria, mandatory, read-only, info, visibility.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/SubformField
TRIGGER_KEYWORDS: SubformField, setCriteria, setReadOnly, setMandatory, addInfo, setVisibility, lookup, picklist, dynamic
TECHNICAL_CONSTRAINTS:
- Obtained via `ZDK.Page.getSubform(api_name).getField(api_name)`. The handle represents the **field definition** across all subform rows — not a single cell.
- **Detail / Canvas-detail surfaces support `setCriteria` only on lookup fields** (source explicit).
- **Lookup `setCriteria` operators**: `starts_with`, `equals`. `id` field also supports `in`. Max 5 criteria per call.
- **Picklist `setCriteria` operators**: `starts_with`, `equals`, `ends_with`, `contains`, `not_equals`, `in`. Max 5 criteria per call.
- **Date `setCriteria`**: only `between`, single criterion: `(value:between:YYYY-MM-DD,YYYY-MM-DD)`.
- **Dynamic criteria binding**: `setCriteria` accepts handlebars-style template tokens `{{<Subform>.<Field>}}` to reference other subform-cell values. Example: `setCriteria("((Last_Name:equals:{{CFSubform.Single_Line_1}}))", { filterOnSearch:true })`.
- **`config.filterOnSearch`** Boolean — per source example on lookup criteria.
- **No `setValue` / `getValue` / `mask` / `addStyle` / `addTooltip` on SubformField** — those are cell-level (`SubformCell`) or row-level (`SubformRow`) operations.
REQUIRED_PARAMETERS:
- `getApiName() → String` — no args; returns the subform-field API name.
- `setCriteria(criteria, config?)` — `criteria` String in `((Field:operator:Value))` syntax; `config` Object (e.g. `{ filterOnSearch: true }`).
- `setReadOnly(value)` — `value` Boolean. Applies cross-row to all cells of this field.
- `setMandatory(value)` — `value` Boolean.
- `addInfo(message)` — `message` String; renders info icon on field header.
- `setVisibility(value)` — `value` Boolean. `false` hides the column.
UI_ONLY_ACTIONS:
- Subform field API name lookup: `Setup → Customization → Modules and Fields → <Module> → <Subform> → <Field> → Edit → API Name`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **`setCriteria` on Detail / Canvas-detail supports only lookup** — calling on picklist / date silently no-ops on Detail surfaces. (detail-setcriteria-lookup-only)
- **Dynamic-criteria token `{{<Subform>.<Field>}}` is evaluated per-row at filter time** — the `<Subform>` segment must match the subform's API name and `<Field>` must be a sibling cell API name. Typos silently produce empty filter results. (dynamic-criteria-binding)
- **Lookup operator set is small** (`starts_with`, `equals`, `in` for id) — `contains` / `ends_with` silently rejected on lookup.
- **`setReadOnly` / `setMandatory` / `setVisibility` apply cross-row** — to toggle per-row, use `SubformRow.setReadOnly` (row scope) or `SubformCell.setReadOnly` (single cell). (granularity)
- **`api_name` is field API name**, not display label. (label-vs-api-name)
- **No `setValue` on SubformField** — bulk row writes go through `Subform.setValues` / `insertRows`; per-cell writes via `SubformCell.setValue`. (no-bulk-field-write)
- **No `addStyle` / `mask` / `addTooltip`** on SubformField (contrast with Field) — styling at the subform-field level is not exposed. (decoration-gap)
- **`addInfo` icon placement is on the field header, not per cell** — per-cell info messages require `SubformCell.showError` or external Widget rendering. (info-placement)
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getApiName` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setCriteria` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setReadOnly` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setMandatory` | Create/Edit/Clone | Create/Edit/Clone | Create/Edit |
| `addInfo` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setVisibility` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
assets/clientscript/client-apis/subformrow.dou.md
FILE_PURPOSE: Read when reading or updating a single Subform row in place via the `SubformRow` object returned by `Subform.getRow(index)`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/SubformRow
TRIGGER_KEYWORDS: SubformRow, getValues, setValues, getCell, setReadOnly, row, index
TECHNICAL_CONSTRAINTS:
- Obtained via `ZDK.Page.getSubform(api_name).getRow(index)` where `index` is 0-based.
- **`setValues(values)` updates the single row** — `values` is an Object (not Array — contrast with Subform-level `setValues` which is Array).
- **`getValues()` returns a single Object** shaped `{ <field_api_name>: <value>, ... }` (source example shows the same shape that `Subform.getRow(1).getValues()` returns).
- **`getCell(value)` returns a `SubformCell`** — the example passes a String field API name (`'Name'`), though the source signature parameter is labelled `value`.
- **`setReadOnly(value)`** sets read-only on the entire row.
REQUIRED_PARAMETERS:
- `getValues() → Array` — source signature says `{Array}` but example output `{'Name': 'Zylker', 'Age': '20', ...}` is an Object.
- `setValues(values)` — `values` Object; keys are field API names.
- `getCell(value) → SubformCell` — see signature-vs-example flag above.
- `setReadOnly(value)` — `value` Boolean.
UI_ONLY_ACTIONS:
- *(none in source)* — `SubformRow` is a read/write handle, not a UI configuration surface.
CRITICAL_FAILURE_MODES:
- **`setValues` shape differs from Subform-level `setValues`** — `SubformRow.setValues` takes an **Object**; `Subform.setValues` takes an **Array of objects**. Passing an array to row-level silently fails. (shape-shift-vs-parent)
- **`getCell` signature ambiguity** — see flag above; passing the wrong shape silently returns no cell. (signature-ambiguity)
- **`getValues` return-type ambiguity** — signature vs example mismatch; iteration code that assumes `Array` may silently fail. (return-type-ambiguity)
- **Row handle becomes stale after `Subform.deleteRow` / `Subform.clear`** — subsequent `getValues()` on a stale row may return undefined or wrong data.
- **`setReadOnly(true)` on a row affects all cells in the row** — per-cell override requires `SubformCell.setReadOnly`. (granularity)
- **No `delete()` on SubformRow** — row deletion goes through parent `Subform.deleteRow(index)`. (granularity)
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `getValues` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setValues` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `getCell` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
| `setReadOnly` | Create/Edit/Clone, Detail | Create/Edit/Clone, Detail | Create/Edit |
assets/clientscript/client-apis/ui.dou.md
FILE_PURPOSE: Read when using `ZDK.UI.*` to freeze the page, scroll to a Canvas element, highlight an element, or fetch a UIElement handle on Canvas surfaces.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/UI
TRIGGER_KEYWORDS: ZDK.UI, freeze, scrollTo, highlight, getElementByID, UIElement, Canvas, element_id
TECHNICAL_CONSTRAINTS:
- `ZDK.UI` operates on **Canvas-page element IDs** — `scrollTo`, `highlight`, `getElementByID` all take an `element_id` String identifying a Canvas page element (the ID assigned to the element in Canvas Builder).
- `freeze(value)` is the only non-Canvas method — it freezes the entire page UI regardless of layout type.
- `highlight(config)` signature in source: `highlight(config)` but the example call is `ZDK.UI.highlight('div1', { time: 1000 })` — passing an element ID String first and a config object second.
- `getElementByID` returns a `UIElement` object (see separate UIElement node — not in this batch).
REQUIRED_PARAMETERS:
- `freeze(value)` — `value` Boolean. `true` = freeze UI; `false` = unfreeze. No return.
- `scrollTo(element_id)` — `element_id` String (ID of a Canvas-page element). No return.
- `highlight(config)` — source signature shows single `config` Object arg; example call passes element_id String + config Object. `config.time` = duration in ms (per example, `1000`).
- `getElementByID(element_id) → UIElement` — `element_id` String.
UI_ONLY_ACTIONS:
- Element-ID assignment for `scrollTo` / `highlight` / `getElementByID` happens in `Setup → Customization → Canvas → <canvas> → element → properties (ID field)`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **`element_id` must match an ID set in Canvas Builder** — calling `getElementByID('section1')` against a Standard layout (no Canvas) silently returns nothing. (canvas-only)
- **`freeze(true)` without a paired `freeze(false)` leaves the page locked** — always pair, even in error paths. (freeze-leak)
- **`scrollTo` / `highlight` on a non-Canvas page no-op silently** — there is no error event. (canvas-only-noop)
- **`highlight` signature ambiguity** — see flag above; calling with the wrong arg shape may silently no-op.
- **No `unhighlight` documented** — highlight clears after `config.time` ms; programmatic cancellation not exposed.
- **No keyboard / focus / accessibility hooks** documented on `ZDK.UI` — focus management requires Web-platform APIs and is not surfaced by Client Script.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `freeze` | all page types | all page types | all page types |
| `scrollTo` | — | Detail | — |
| `highlight` | — | Detail | — |
| `getElementByID` | — | Detail | Create/Edit |
assets/clientscript/client-apis/uielement.dou.md
FILE_PURPOSE: Read when manipulating an individual Canvas UI element (text / image / tab / related-list / generic) obtained via `ZDK.UI.getElementByID(id)` — toggle visibility, add / remove tooltip, apply CSS, freeze, set read-only, set text content, set image, or activate a tab.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/clientapi/UIElement
TRIGGER_KEYWORDS: ZDK.UI.getElementByID, UIElement.setVisibility, UIElement.addToolTip, UIElement.removeToolTip, UIElement.addStyle, UIElement.freeze, UIElement.setReadOnly, UIElement.setContent, UIElement.setImage, UIElement.setActive
TECHNICAL_CONSTRAINTS:
- UIElement instances are obtained via `ZDK.UI.getElementByID(id)` (see [ui.dou.md](./ui.dou.md)) where `id` is the Canvas-element id assigned in Canvas Builder.
- `setVisibility()` — source signature shows no documented param, but contextually (sibling pattern `setVisibility(true|false)`) takes a Boolean. Source example passes `true`.
- `addToolTip(config)` — `config: Object`. Source example key: `text: String` (tooltip text). Source does not enumerate other config keys.
- `removeToolTip()` — no params; removes the tooltip.
- `addStyle(config)` — `config: Object` keyed by CSS property name (kebab-case **and** camelCase mixed in source example: `'background-color': 'red'`, `color: 'white'`, `'border-radius': '40px'`). Values are CSS-property strings.
- `freeze(value)` — `value: Boolean` (`true` | `false`) — freezes the element.
- `setReadOnly(value)` — `value: Boolean` — "Removes write actions from the given related list" (source verbatim — read-only applies to related-list elements).
- `setContent(value)` — `value: string` — sets text content. **Text elements only** (source: "Set text content for the text element").
- `setImage(value)` — `value: object` — sets the image. Source documents two object shapes: `{ type: "url", url: "https://..." }` and `{ type: "userId", userId: "111222333444555" }`. **Image elements only** (source: "Set image for the image element").
- `setActive()` — no params — sets the active tab in a container. **Tab elements only** (source: "Set active tab in a container").
REQUIRED_PARAMETERS:
- `setVisibility(value)` — `value: Boolean` (inferred from example).
- `addToolTip(config)` — `config: { text: String }`.
- `removeToolTip()` — no params.
- `addStyle(config)` — `config: Object` of CSS-property → CSS-value pairs.
- `freeze(value)` — `value: Boolean`.
- `setReadOnly(value)` — `value: Boolean` — related-list elements only.
- `setContent(value)` — `value: string` — text elements only.
- `setImage(value)` — `value: { type: "url", url: String } | { type: "userId", userId: String }` — image elements only.
- `setActive()` — no params — tab elements only.
UI_ONLY_ACTIONS:
- Defining the Canvas element itself (its element id, type, default styling, position) is a Setup → Customization → Canvas → <View> action. The UIElement API mutates the rendered element by id but does not create / register it.
CRITICAL_FAILURE_MODES:
- **`getElementByID(id)` requires the Canvas-element id verbatim** — Canvas Builder ids are user-assigned strings (source examples: `'myElem'`, `'txt1'`, `'tab1'`); a typo returns nothing. (label-vs-api-name)
- **`setContent` on a non-text element silently no-ops** — source restricts to text elements; passing the id of an image / tab / generic element returns no error but does not update content.
- **`setImage` on a non-image element silently no-ops** — same pattern.
- **`setActive()` on a non-tab element silently no-ops** — same pattern.
- **`setReadOnly` is documented only for related-list elements** — calling it on a non-related-list UIElement has undocumented behaviour.
- **`setImage({type:"userId", userId:"..."})` requires a CRM user id (zuid-style)** — passing a record id fails to resolve the user avatar. (label-vs-api-name)
- **`addStyle` config keys mix kebab-case and camelCase in source** — `'background-color'` and `'border-radius'` are quoted (kebab-case) while `color` is unquoted (single token). Stick to the CSS-property kebab-case form for multi-word properties.
- **`freeze(true)` does not remove the element from the tab order** — kept frozen but still focusable; combine with `setVisibility(false)` for full removal from interaction.
- **Element state is not persisted** — `addStyle`, `freeze`, `setVisibility`, `setContent` etc. apply to the live DOM only; a re-render reverts to the Canvas-defined state.
SUPPORTED_PAGES:
| Method | Standard | Canvas | Wizard |
|---|---|---|---|
| `setVisibility` | — | Detail | Create/Edit |
| `addToolTip` | — | Detail | — |
| `removeToolTip` | — | Detail | — |
| `addStyle` | — | Detail | — |
| `freeze` | — | Detail | — |
| `setReadOnly` | — | Detail | — |
| `setContent` | — | Detail | Create/Edit |
| `setImage` | — | Detail | — |
| `setActive` | — | Detail | — |
assets/clientscript/clientscript-best-practices.dou.md
FILE_PURPOSE: Read when authoring or refactoring a Client Script — applying the ten documented best practices for static-resource reuse, event-arg usage, layout coverage, Trusted-Domains whitelist, and 10 s-timeout loader pattern.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/client-script-best-practices.html
TRIGGER_KEYWORDS: ZDK.Page.getField, ZDK.Client.showLoader, ZDK.Client.hideLoader, ZDK.Apps.CRM.Functions.execute, Trusted Domains, Reorder, log statement, Static Resources, 30 scripts per page
TECHNICAL_CONSTRAINTS:
- Client Script execution is bounded by a 10-second timeout; long-running ZDK Web (CRM) API or third-party calls must be wrapped in a loader to pause the user-perceived flow.
- Hard cap — up to 30 Client Scripts per page.
- Hard cap — up to 5 Static Resources per page (echoes [clientscript-static-resources.dou.md](./clientscript-static-resources.dou.md)).
- Per-event argument injection — each event handler receives a documented argument variable; the handler reads the injected variable directly instead of re-fetching via ZDK.
- Reorder of script execution order is scoped to a single Page + Event combo within a module — cross-event / cross-page ordering is not configurable.
- Third-party API calls from Client Script require the target domain to be whitelisted in `Trusted Domains` before the request will succeed.
REQUIRED_PARAMETERS:
- Per-event injected arguments (from source's event-argument table):
· Create / Clone / Edit Page — Page `onChange` → `field_name`; Page `onLoad` → none; Page `onSave` → none; Field `onChange` → `value`.
· Detail Page (Canvas) — Page `onLoad` → none; Field `onBeforeUpdate` → `value`; Mandatory Fields Form `onBeforeMandatoryFormSave` → `value`, `onMandatoryFormLoad` → `field`; Blueprint `beforeTransition` → `transition`; Tag `onTagChanged` → `tag, added and removed`; Canvas / Icon / Text `onClick` → none.
· Detail Page (Standard) — same shape as Detail (Canvas).
· List Page (Standard) — Page `onCustomViewLoad` → `custom_view`; `onBeforeCustomViewChange` → `custom_view`.
· Create / Edit Page (Wizard) — Wizard `onLoad` → none; `onChange` → `field_name`; `onTransition` → `screen`; `onBeforeTransition` → `source_screen, target_screen`; `onBeforeSave` → none; Field `onChange` → `value`.
- Loader pattern (10 s timeout workaround):
```javascript
if (value == 'Delivered') {
ZDK.Client.showLoader({ type: 'page', template: 'spinner', message: 'Sending Quotation...' });
ZDK.Apps.CRM.Functions.execute("Send_Quote");
ZDK.Client.hideLoader();
ZDK.Client.showMessage('Quotation sent successfully', { type: 'success' });
}
```
- Static-resource reuse — extract repeated logic (e.g. SSN regex `is_socialSecurity_Number`) into a JS file uploaded as a Static Resource, then call by bare function name from the script.
- Multi-field handling on the same page — prefer one Page `onChange` script with `switch(field_name)` over multiple Field `onChange` scripts.
- Debug logging — `log(...)` statements surface in the IDE Run → Messages panel; do not rely on browser `console.log` (see [clientscript-ide-components.dou.md](./clientscript-ide-components.dou.md)).
- Ad-hoc ZDK invocation — use the Run → Terminal panel (e.g. `ZDK.Client.showMessage('...', { type: 'info' })`) to test ZDK calls without saving a script.
- Event-type → use-case mapping (Best Practice #9, source-verbatim):
· Pre-save validation across the whole record — Page `onSave`.
· Validation as soon as data is entered on a single field — Field `onChange`.
· Validation as soon as data is entered across multiple fields — Page `onChange` (with `switch(field_name)`).
· Custom message / information box on page open — Page `onLoad`.
UI_ONLY_ACTIONS:
- Reorder scripts within a Page + Event combo: `Setup → Developer Hub → Client Script → Reorder` (scoped to one Page + Event per module). Note: No API equivalent — UI-only.
- Whitelist a third-party domain: add the domain under `Setup → Developer Space → Trusted Domains` before the script's `fetch` will succeed. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- ZDK Web (CRM) API call inside an `onType` handler exhausts API credits; prefer `onChange` or page-level events for API-bearing logic. (rate-limit)
- Skipping the loader on a > 10 s ZDK / third-party call: execution halts at 10 s; user-perceived save / transition appears frozen. (timeout)
- Third-party `fetch(...)` to a non-whitelisted domain returns a network error; add the domain to Trusted Domains before retry. (trusted-domain)
- Multiple Field `onChange` scripts on the same page create ordering ambiguity even with Reorder applied; consolidate into one Page `onChange` with a `switch(field_name)`.
- Layout binding — Client Scripts execute only for the configured layout; multi-layout coverage requires one script per layout (see [clientscript-overview.dou.md](./clientscript-overview.dou.md)). (layout-binding)
- The injected `value` argument is scoped to the current event family — `value` in Field `onChange` is the new field value; in Mandatory-Form events it is the form-level map. Reusing the variable name across event types misreads the payload.
- 30-script cap per page — adding a 31st script is rejected; consolidate using `switch` blocks.
- `field_name` argument is provided only on Page `onChange` and Wizard `onChange` per the source table — relying on it inside Page `onLoad` / `onSave` returns `undefined`.
assets/clientscript/clientscript-commands.dou.md
FILE_PURPOSE: Read when authoring a command-triggered Client Script — confirming Commands category, the 30-command cap, command-palette and per-user keyboard-shortcut trigger paths.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/commands.html
TRIGGER_KEYWORDS: Client Script Commands, Command Palette, C C shortcut, Manage Extensibility Permission, Category Commands, Keyboard Shortcuts
TECHNICAL_CONSTRAINTS:
- Commands are a Client Script category that runs anywhere in CRM, decoupled from a Module + Page binding.
- Hard cap — up to 30 Client Script Commands per CRM org.
- Default keyboard shortcut to open the Command Palette is `C C`.
- Per-user keyboard shortcuts are disabled by default; each user must enable Keyboard Shortcuts under Personal Settings before binding shortcuts to Commands.
- The "Manage" option in the per-user Commands shortcut UI is visible only to users with the `Manage Extensibility Permission` profile permission.
- Disabling or deleting a Command also deletes every keyboard-shortcut binding for that Command across all users in the org.
- Commands run "anywhere in CRM" — no page-type or module-event binding is required at creation time.
REQUIRED_PARAMETERS:
- Creation contract — when creating the Client Script, set Category to `Commands` instead of a Module; the rest of the IDE flow matches event-based scripts.
- Trigger surfaces:
· Command Palette icon in the CRM footer (or open the palette via the `C C` shortcut), then select the Command name.
· Per-user keyboard shortcut bound under `Setup → General → Personal Settings → Keyboard Shortcuts → Commands → Choose Command → enter Shortcut → Add shortcut`.
UI_ONLY_ACTIONS:
- Create a Command-category Client Script: `Setup → Developer Hub → Client Script → +New Script → Category = Commands → Name + Description → Next → IDE → Save and Close → toggle Enable`. Note: No API equivalent — UI-only.
- Per-user keyboard-shortcut binding: `Setup → General → Personal Settings → Keyboard Shortcuts → enable Keyboard Shortcuts → Commands section → Choose Command → enter Shortcut → Add shortcut`. Note: Bindings are per-user; no admin API.
- Admin shortcut to the Commands tab: `Personal Settings → Keyboard Shortcuts → Commands → Manage` (visible only with `Manage Extensibility Permission`). Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- Attempting to create more than 30 Commands is rejected at the cap of 30; delete an unused Command first.
- Keyboard Shortcuts disabled on a user's account → bound Command shortcut keys produce no action; verify the per-user Keyboard Shortcuts master toggle before triage.
- Disabling or deleting a Command deletes every user-bound shortcut for that Command — re-enabling the Command does not restore the bindings; users must re-bind.
- "Manage" link absent from the Commands shortcut UI → user is missing `Manage Extensibility Permission`; grant via profile before expecting admin-level Command control.
- Category mismatch — choosing Category = Module when Commands was intended routes the script under the module-event binding flow and the script becomes invisible to the Command Palette.
assets/clientscript/clientscript-creation.dou.md
FILE_PURPOSE: Read when creating a Client Script via Setup or the in-record sidebar — confirming required pop-up fields, Event Details split, IDE handoff, and post-save activation toggle.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/creation.html
TRIGGER_KEYWORDS: New Script, Developer Hub, Client Script setup page, Category Module, Category Commands, onLoad, onChange, onSave, ZDK Library
TECHNICAL_CONSTRAINTS:
- Two authoring entry points — Setup page (`Setup → Developer Hub → Client Script → +New Script`) and the right-most Client Script panel on any create / clone / edit record page.
- Module list in the Create Script pop-up: Leads, Contacts, Accounts, Deals, Products, Quotes, Sales Orders, Purchase Orders, Invoices, Campaigns, Vendors, Price Books, Cases, Solutions, Custom Module.
- Layout selector — Standard or any custom layout; one Client Script binds to one selected layout.
- Page selector for in-Setup creation supports: Create Page, Clone Page, Edit Page.
- Event Details "Type" dropdown — available options depend on the selected Page; see [clientscript-event-type-matrix.dou.md](./clientscript-event-type-matrix.dou.md) for the full per-page Type/Event/selector matrix (this replaces the outdated "page | field only" claim previously here). Type does **not** apply to Category = Commands scripts at all — no Page/Type/Event fields exist for Commands (see [clientscript-commands.dou.md](./clientscript-commands.dou.md)). Wizard Create/Edit pages require choosing which Wizard in the Page Details section (`Page = Create/Edit Page (Wizard) → Wizard = <name>`) — this happens *before* Type is even shown, analogous to Layout, not a Type-dependent field. Subform Event has no creation-time subform picker — the target subform is chosen in code via `ZDK.Page.getSubform(<api_name>)`.
- IDE handoff — clicking Next opens the Client Script IDE; the script is not persisted until Save and Close.
- Post-save state — Client Script setup page lists Name, size, last-modified user, and status (enabled / disabled); enable via the toggle.
- ZDK Library — dot-notation SDK exposed in the IDE Information panel; click the help icon in Library to open ZDK docs.
REQUIRED_PARAMETERS:
- Create Script pop-up fields (Setup flow): Name, Description, Category (Module | Commands), Page, Module, Layout, (Wizard — only when Page = Create/Edit Page (Wizard), chosen here in Page Details before Type is shown), then Event Details → Type (see [clientscript-event-type-matrix.dou.md](./clientscript-event-type-matrix.dou.md) for the complete per-Page list of values) → Event, plus Field (only when Type = Field Event — the sole genuine post-Type Event-Details selector, confirmed via this source doc's literal section-ordering).
- Create Script pop-up fields (in-record flow): page details (Page, Module, Layout) pre-populated by the system; caller supplies Name, Description, Event Details, then Next → IDE → Save and Close.
- Activation — toggle the per-script status from the Client Script setup list to enable the script after Save and Close.
UI_ONLY_ACTIONS:
- Create via Setup: `Setup → Developer Hub → Client Script → +New Script → Name + Description → Category + Page + Module + Layout (+ Wizard, if Page = Wizard) → Event Details: Type + Event (+ Field, if Type = Field Event) → Next → IDE → Save and Close → toggle Enable`. Note: No API equivalent — UI-only.
- Create via record page: open the target Create / Clone / Edit record page → right-most `Client Script` panel → `Add Script` → fill Name / Description / Event Details → Next → IDE → Save and Close. Note: No API equivalent — UI-only.
- Edit or delete a script: `Setup → Developer Hub → Client Script → {script} → Edit | Delete`. Note: No API equivalent — UI-only.
- Open ZDK docs: in the IDE Information panel, click the help icon inside the Library section. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- Layout selected at creation time fixes the script binding — selecting the wrong layout silently no-fires on records using a different layout. (layout-binding) See [clientscript-overview.dou.md](./clientscript-overview.dou.md) for the per-layout-script rule.
- Save and Close persists the script in the setup list with an enable/disable status toggle; use the toggle to enable the script before it will execute (source: "You can enable a script by sliding the toggle button.").
- Category mismatch — choosing Module for a command-style script (or Commands for a module-bound script) routes the script away from its intended trigger surface. (category-binding)
- In-record creation pre-populates Page / Module / Layout from the current record context; editing those fields requires returning to the Setup-page edit flow.
- Field Event only supports `onChange` (per the pop-up); attempting `onLoad` / `onSave` as a field event is not offered — model the script as a Page Event instead.
assets/clientscript/clientscript-event-type-matrix.dou.md
FILE_PURPOSE: Read first when determining which "Type" dropdown value and Event to select for a given Page — single-table fast lookup, cross-checked against the official event catalog and every per-page event-dictionary file.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/client-script-events.html, https://www.zoho.com/crm/developer/docs/client-script/creation.html
TRIGGER_KEYWORDS: Type dropdown, Event Details, Page Event, Field Event, Blueprint Event, Button Event, Subform Event, Mandatory Fields Form Event, Tag Event, Notes Related List, Canvas Button Event, Icon Event, Text Event, Wizard Event
TECHNICAL_CONSTRAINTS:
- This file is the authoritative fast-scan lookup for "which Type value + which Events" per Page. For full argument signatures, interceptor semantics, and failure modes, defer to the matching `eventdict-*.dou.md` (derivable 1:1 from the Page name, e.g. Detail (Standard) → `eventdict-detail-standard.dou.md`).
- Type values here are exactly what appears in the CRM Create Script "Type" dropdown (Event Details section). Type options are gated by the Page selected first (Page Details section).
- **Type does not apply to Category = Commands** — Commands scripts have no Page/Type/Event fields at all; they trigger via Command Palette or a per-user keyboard shortcut (see [clientscript-commands.dou.md](./clientscript-commands.dou.md)).
- **Wizard Create/Edit pages** additionally require choosing which Wizard in the **Page Details** section (`Page = Create/Edit Page (Wizard) → Wizard = <name>`), before Type/Event are even shown — analogous to Layout, this applies uniformly regardless of which Type is later chosen on that page. It is **not** an Event-Details/after-Type selector.
- `Field` is the only genuine **post-Type, Event-Details** selector (confirmed via `creation.html`'s literal "In the Event Details section, select the Type... For a field event, select the Field..." ordering).
- `Subform Event` has **no creation-time subform picker** — the target subform is chosen in code via `ZDK.Page.getSubform(<subform_api_name>)`, not via a UI field.
- Canvas Button Event, Icon Event, and Text Event are **3 distinct Types** (each with its own `onClick`), not one combined "Canvas Button/Icon/Text" type.
- List-page custom-view events (`onCustomViewLoad` / `onBeforeCustomViewChange`) are still **Page Event** — not a separate "Custom View Event" type.
REQUIRED_PARAMETERS:
- Full Page × Type × Events matrix (44 rows, 8 Page surfaces):
| Page | Type | Events | Configured via | Extra selector after Type |
|---|---|---|---|---|
| Create/Clone/Edit (Standard) | Page Event | onLoad, onChange, onSave | Type dropdown | None |
| Create/Clone/Edit (Standard) | Field Event | onChange, onType | Type dropdown | Field |
| Create/Clone/Edit (Standard) | Subform Event | onRowAdd, onCellChange, onRowDelete, beforeRowDelete | Type dropdown | None (targeted via `getSubform()` in code) |
| Create/Clone/Edit (Standard) | Button Event | onClick | Buttons page | None |
| Detail (Standard) | Page Event | onLoad | Type dropdown | None |
| Detail (Standard) | Field Event | onBeforeUpdate | Type dropdown | Field |
| Detail (Standard) | Blueprint Event | beforeTransition | Type dropdown | None |
| Detail (Standard) | Mandatory Fields Form Event | onBeforeMandatoryFormSave, onMandatoryFormLoad | Type dropdown | None |
| Detail (Standard) | Tag Event | onTagChanged | Type dropdown | None |
| Detail (Standard) | Subform Event | onRowAdd, onCellChange, onRowDelete, beforeRowDelete, beforeRowUpdate | Type dropdown | None (targeted via `getSubform()` in code) |
| Detail (Standard) | Notes Related List | onBeforeAdd, onBeforeUpdate | Type dropdown | None |
| Detail (Standard) | Button Event | onClick | Buttons page | None |
| Detail (Canvas) | Page Event | onLoad | Type dropdown | None |
| Detail (Canvas) | Field Event | onBeforeUpdate | Type dropdown | Field |
| Detail (Canvas) | Mandatory Fields Form Event | onBeforeMandatoryFormSave, onMandatoryFormLoad | Type dropdown | None |
| Detail (Canvas) | Blueprint Event | beforeTransition | Type dropdown | None |
| Detail (Canvas) | Tag Event | onTagChanged | Type dropdown | None |
| Detail (Canvas) | Canvas Button Event | onClick | Canvas element designer | None |
| Detail (Canvas) | Icon Event | onClick | Canvas element designer | None |
| Detail (Canvas) | Text Event | onClick | Canvas element designer | None |
| Detail (Canvas) | Subform Event | onRowAdd, onCellChange, onRowDelete, beforeRowDelete, beforeRowUpdate | Type dropdown | None (targeted via `getSubform()` in code) |
| Detail (Canvas) | Notes Related List | onBeforeAdd, onBeforeUpdate | Type dropdown | None |
| Detail (Canvas) | Button Event | onClick | Buttons page | None |
| Create/Clone/Edit (Canvas) | Page Event | onLoad, onChange, onSave | Type dropdown | None |
| Create/Clone/Edit (Canvas) | Field Event | onChange, onType | Type dropdown | Field |
| Create/Clone/Edit (Canvas) | Mandatory Fields Form Event | onMandatoryFormLoad | Type dropdown | None |
| Create/Clone/Edit (Canvas) | Canvas Button Event | onClick | Canvas element designer | None |
| Create/Clone/Edit (Canvas) | Icon Event | onClick | Canvas element designer | None |
| Create/Clone/Edit (Canvas) | Text Event | onClick | Canvas element designer | None |
| Create/Clone/Edit (Canvas) | Subform Event | onRowAdd, onCellChange, onRowDelete, beforeRowDelete | Type dropdown | None (targeted via `getSubform()` in code) |
| Create/Clone/Edit (Canvas) | Button Event | onClick | Buttons page | None |
| Wizard Create/Edit | Wizard Event | onLoad, onChange, onBeforeSave, onTransition, onBeforeTransition | Type dropdown | None (Wizard itself is chosen in Page Details, before Type) |
| Wizard Create/Edit | Field Event | onChange, onType | Type dropdown | Field |
| Wizard Create/Edit | Subform Event | onRowAdd, onCellChange, onRowDelete, beforeRowDelete | Type dropdown | None (targeted via `getSubform()` in code) |
| Wizard Create/Edit | Button Event | onClick | Buttons page | None |
| List (Standard) | Page Event | onCustomViewLoad, onBeforeCustomViewChange | Type dropdown | None |
| List (Standard) | Button Event | onClick | Buttons page | None |
| List (Canvas) | Page Event | onLoad, onBeforeCustomViewChange | Type dropdown | None |
| List (Canvas) | Canvas Button Event | onClick | Canvas element designer | None |
| List (Canvas) | Icon Event | onClick | Canvas element designer | None |
| List (Canvas) | Text Event | onClick | Canvas element designer | None |
| List (Canvas) | Button Event | onClick | Buttons page | None |
| Quick Create popup | Page Event | onLoad, onChange, onSave | Type dropdown | None |
| Quick Create popup | Field Event | onChange, onType | Type dropdown | Field |
- `Configured via` distinguishes how the row's Type is actually reached: `Type dropdown` (standard `Setup → Developer Hub → Client Script → +New Script` dialog), `Buttons page` (custom Button Event scripts can only be created from the module's Buttons page), or `Canvas element designer` (Canvas Button/Icon/Text Event scripts can only be created from `Setup → Customization → Canvas → <element> → Actions → Client Script`).
- `Extra selector after Type` names the one further field shown after choosing Type, inside Event Details — only `Field` qualifies; everything else is `None` (Subform is a code-level target, and Wizard is chosen earlier, in Page Details).
UI_ONLY_ACTIONS:
- All Type-dropdown-configured rows: `Setup → Developer Hub → Client Script → +New Script → Category = Module → Page → Module → Layout (+ Wizard, if Page = Wizard) → Event Details: Type → Event (+ Field, if Type = Field Event) → Next → IDE → Save and Close`. Note: No API equivalent — UI-only.
- Button Event rows: create only from `Setup → Customization → Modules and Fields → <Module> → Links and Buttons → Buttons → Create/select button → action = Client Script`. Note: No API equivalent — UI-only.
- Canvas Button/Icon/Text Event rows: create only from `Setup → Customization → Canvas → <canvas> → select element (Button/Icon/Text) → Actions → Client Script`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- Canvas Button Event, Icon Event, and Text Event are 3 distinct Types (not 1 combined "Canvas Button/Icon/Text" type) — treating them as one risks binding the script to the wrong canvas element category.
- List-page custom-view events are still Type = **Page Event** — describing them as a separate "Custom View Event" type will send a user looking for a Type value that doesn't exist in the dropdown.
- Button Event and Canvas Button/Icon/Text Event are **not reachable from the standard Type dropdown at all** — a response that says "Type: Button Event" without naming the Buttons-page (or Canvas-designer) entry point recreates the exact confusion this file exists to prevent.
- Category = Commands has no Type/Event concept — do not state a Type for Commands-category scripts.
- SOURCE_CONFLICT — `clientscript-faqs.dou.md` FAQ #10/#31's supported-pages list omits **List Page (Canvas)** entirely. This matrix treats List Page (Canvas) as valid, per `clientscript-events.dou.md` section 8 and `eventdict-list-canvas.dou.md`. Do not edit the FAQ file to "fix" this — it's flagged here only.
- `List (Canvas) | Button Event` is confirmed only via `eventdict-list-canvas.dou.md` (the official events doc's page for this section does not explicitly enumerate it) — event-dictionary files are authoritative per this skill's convention.
- Wizard `Field Event` and Create/Clone/Edit (Canvas) `Field Event` exclude `onBeforeUpdate` (multi-select lookup) — that hook appears only in the legacy source prose, not in the corresponding event-dictionary file; treat the event-dictionary as authoritative (SOURCE_CONFLICT, see those files' own CRITICAL_FAILURE_MODES).
assets/clientscript/clientscript-events.dou.md
FILE_PURPOSE: Read when picking the Page Type + Event Family + Event Name for a Client Script — confirming per-page event lists, supported/unsupported field types, and `return false` cancel semantics.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/client-script-events.html
TRIGGER_KEYWORDS: onLoad, onChange, onSave, onBeforeUpdate, beforeTransition, onBeforeMandatoryFormSave, BeforeRowDelete, onCustomViewLoad, onType, onTagChanged
TECHNICAL_CONSTRAINTS:
- For a fast Page→Type→Events lookup table (including the exact "Type" dropdown value and where each Type is configured), see [clientscript-event-type-matrix.dou.md](./clientscript-event-type-matrix.dou.md).
- Event-configurable pages — Create page, Clone page, Edit page, List Page (Standard), Detail Page (Standard), Detail Page (Canvas), Create Page (Wizard), Edit Page (Wizard), Create / Clone / Edit Page (Canvas), List Page (Canvas), Quick Create popup.
- Field `onChange` fires when focus leaves the field; `onType` fires as the user keys characters.
- Layout binding — picking a layout during script creation restricts execution to that layout; multi-layout coverage requires one script per layout.
- Custom-button-bound Client Scripts are created from the Buttons page only; they appear under the Button Event slot on every supported page.
- Mandatory Fields Form is a CRM-generated pop-up triggered by Layout Rules during inline edit on Detail (Canvas) / Detail (Standard); its events are exposed only on those page types.
- Multi-select field timing nuances (source-verbatim):
· Create/Clone/Edit Page (Canvas) Page `onChange` on a Multi-select Picklist fires immediately after the user adds or modifies the selection.
· Wizard `onChange` on a Multi-select Picklist fires immediately after add/modify; on a Multi-select Lookup it fires after the field's value is updated.
· Field `onBeforeUpdate` (Create/Clone/Edit Canvas, Wizard) fires for Multi-select Lookup as soon as the "confirm" button is clicked and before the value is committed.
REQUIRED_PARAMETERS:
- Create / Clone / Edit Page events:
· Page Event: `onLoad`, `onChange`, `onSave`.
· Field Event: `onChange`, `onType`.
· Subform Event: `onRowAdd`, `onCellChange`, `onRowDelete`, `BeforeRowDelete`.
· Button Event (from Buttons page).
- Create / Clone / Edit Page Field Event — supported field types: Single Line, Multi-Line, Email, Phone, Pick List, Date, Number, Currency, Decimal, Percent, Long Integer, Checkbox, URL, Lookup, Owner Lookup, Multi-select Picklist.
- Create / Clone / Edit Page Field Event — unsupported fields: Salutation, Adjustment, Discount.
- Detail Page (Canvas) events:
· Page Event: `onLoad`.
· Field Event: `onBeforeUpdate`.
· Mandatory Fields Form Event: `onBeforeMandatoryFormSave`, `onMandatoryFormLoad`.
· Blueprint Event: `beforeTransition`.
· Tag Event: `onTagChanged`.
· Canvas Button Event: `onClick`.
· Icon Event: `onClick`.
· Text Event: `onClick`.
· Subform Event: `onRowAdd`, `onCellChange`, `onRowDelete`, `BeforeRowDelete`, `BeforeRowUpdate`.
· Notes Related List: `onBeforeAdd`, `onBeforeUpdate`.
· Button Event.
- Detail Page (Canvas) Field Event — unsupported fields: Reporting To, Currency, Probability, Pricing Model, Wizard, Territory Updated Time.
- Detail Page (Standard) events — same event families as Detail Page (Canvas) (Page `onLoad`, Field `onBeforeUpdate`, Mandatory Form, Blueprint `beforeTransition`, Tag `onTagChanged`, Subform inc. `BeforeRowUpdate`, Notes Related List, Button Event); supported / unsupported field-type lists identical to Detail Page (Canvas).
- Create / Clone / Edit Page (Canvas) events:
· Page Event: `onLoad`, `onChange`, `onSave`.
· Field Event: `onChange`, `onType`, `onBeforeUpdate` (multi-select lookup).
· Mandatory Fields Form Event: `onMandatoryFormLoad`.
· Canvas Button Event: `onClick`.
· Icon Event: `onClick`.
· Text Event: `onClick`.
· Subform Event: `onRowAdd`, `onCellChange`, `RowDelete`, `BeforeRowDelete`.
· Button Event.
- Create / Edit Page (Wizard) events:
· Wizard Event: `onLoad`, `onChange`, `onBeforeSave`, `onTransition`, `onBeforeTransition`.
· Field Event: `onChange`, `onType`, `onBeforeUpdate` (multi-select lookup).
· Subform Event: `onRowAdd`, `onCellChange`, `onRowDelete`, `BeforeRowDelete`.
· Button Event.
- List Page (Standard) events: Page Event `onCustomViewLoad`, `onBeforeCustomViewChange`; Button Event.
- List Page (Canvas) events: Page Event `onLoad`, `onBeforeCustomViewChange`; Canvas Button Event `onClick`; Icon Event `onClick`; Text Event `onClick`; Button Event (per `eventdict-list-canvas.dou.md`, no `context` arg on this surface).
- Quick Create popup events: Page Event `onLoad`, `onChange`, `onSave`; Field Event `onChange`, `onType`.
- `return false` cancel semantics — append `return false` at the end of these handlers to cancel the corresponding native action: `onSave` (block record save), `BeforeRowDelete` (block subform row delete), `BeforeRowUpdate` (block subform row update), `beforeTransition` (block Blueprint transition), `onBeforeSave` (block Wizard save), `onBeforeTransition` (block Wizard transition), `onBeforeUpdate` (block Detail-page inline field save), `onBeforeMandatoryFormSave` (block Mandatory Fields Form save).
- A single Client Script with Page `onChange` can replace many Field `onChange` scripts on the same page when multiple fields need handling.
UI_ONLY_ACTIONS:
- Pick the Page Type during creation: `Setup → Developer Hub → Client Script → +New Script → Page` (no API to bulk-bind events). Note: No API equivalent — UI-only.
- Bind a Button Event handler: create the Client Script from the module's Buttons page (the standalone Client Script setup page rejects button-event creation). Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- `return false` omitted from an `onSave` / `onBeforeSave` / `BeforeRowDelete` / `BeforeRowUpdate` / `beforeTransition` / `onBeforeTransition` / `onBeforeUpdate` / `onBeforeMandatoryFormSave` handler → native action proceeds even after validation logic reports an error. (cancel-semantics)
- Field Event configured on an unsupported field type for the page (e.g. Salutation / Adjustment / Discount on Create / Clone / Edit; Reporting To / Currency / Probability / Pricing Model / Wizard / Territory Updated Time on Detail pages) → handler never fires; no error surfaced.
- `onType` triggers on every keystroke — wiring ZDK Web (CRM) API calls into `onType` exhausts API credits.
- `onChange` vs `onType` confusion — `onChange` waits for focus loss; using `onChange` when keystroke-level reaction is required produces "delayed" UX that looks like no-fire.
- Blueprint `beforeTransition` and Wizard `onBeforeTransition` are the only documented hooks to block transitions; relying on `onSave` to gate transitions silently no-ops.
- Layout binding silent no-fire — a record on a non-bound layout will never trigger the script; verify Layouts on the script definition. (layout-binding)
- Tag Event `onTagChanged` fires on Add-Tags save only.
- Custom-button-bound Client Script attempted from the standalone Client Script setup page is rejected — create only from the Buttons page.
assets/clientscript/clientscript-faqs.dou.md
FILE_PURPOSE: Read when answering operational Client Script questions — supported JS, scopes, layouts, debugging, unsupported APIs, setCriteria operators, Detail-Page setValue restriction, JS-method blocklist.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/faqs.html, https://www.zohocrm.dev/explore/client-script/faqs
TRIGGER_KEYWORDS: setCriteria, setValue, getInput, getForm, setValues, log, JSON.stringify, param_type, addTooltip, console.log
TECHNICAL_CONSTRAINTS:
- Language: JavaScript only — no other languages supported (FAQ #5). ES7 features supported including `let`, `const`, async functions (FAQ #19, #21).
- One Client Script binds to exactly one module-layout pair; multi-layout coverage requires one script per layout (FAQ #6).
- Execution timeout — 10 s; use a loader when exceeding (FAQ #43).
- Connection invocation `param_type` map (FAQ #23): `param_type = 1` → request parameter; `param_type = 2` → request body / payload.
- ZDK Web API calls from a Client Script consume the org's daily Zoho CRM API limit — every ZDK Web API execution counts (FAQ #2).
- `setCriteria(...)` operators supported (FAQ #27): only `starts_with` and `equals`. Other operators silently rejected.
- Contextual lookup filter (FAQ #32) — supported on single-lookup field only; Multi-select lookup contextual filter not yet supported.
- Static Resources scope (FAQ #28) — per-page; any script on the same page can use them.
- Custom libraries — upload as Static Resource and consume (FAQ #33).
- 3rd-party calls only to URLs in Trusted Domains (FAQ #34).
- Run-mode does not require Save before testing (FAQ #29); Run-mode CRM operations DO persist to the live account (per FAQ #4 note).
- Browser support — matches Zoho CRM's supported-browsers list (FAQ #12).
- Mobile coverage — see separate mobile DOU node; FAQ does not enumerate mobile events.
- Hover support — `addTooltip()` shows a hover tooltip; no `onHover` event exists (FAQ #38).
- Custom event creation not supported (FAQ #22) — only the documented event catalog fires.
- Alert line breaks — `\n` inside `ZDK.Client.showAlert(...)` renders multi-line (FAQ #46).
- **onChange Field vs onChange Page** (new-mirror FAQ) — bind Field `onChange` when the script must run only for one specific field's update; bind Page `onChange` when any field-update on the page must trigger the same script (use `if`/`switch..case` on the changed field inside the handler). Field-level fires for the bound field only; Page-level fires for every field on the page.
- **Display 3rd-party content (PDF, HTML, web page) inside CRM** (new-mirror FAQ) — there is no direct Client-Script render API for external HTML/PDF; the supported pattern is to render a Widget and open it via `ZDK.Client.openPopup` / Callout / Flyout. Client Script invokes the Widget; the Widget loads the external content.
REQUIRED_PARAMETERS:
- Lookup `setValue` requires both id and name (FAQ #3): `ZDK.Page.getField('Category').setValue({ "id": "<record_id>", "name": "<display_name>" })`.
- Subform get/set values — `ZDK.Page.getForm().getValues()` and `getForm().setValues(...)` (FAQ #14, #35); `getForm()` returns all fields on the current page (Create/Clone/Edit).
- Debug log — `log(<value>)` writes to the IDE Messages panel; `console.log(<value>)` writes to the browser console (FAQ #4, #20).
- Stringified ZDK response — `log("response stringified:", JSON.stringify(ZDK.Apps.CRM.Leads.fetch()))` (FAQ #45).
- Cancel save on validation failure — `onSave` event + `field.showError(<msg>)` (or `ZDK.Client.showMessage` / `showAlert`) + `return false` (FAQ #9).
- Detail Page (Canvas/Standard) field updates — `setValue` on direct fields is **not** supported; use the Update Record Web API + `$Client.refresh()` to re-render the page (FAQ #25, #42). Detail-Page Mandatory-Form fields **do** support `setValue`.
- Lookup fetch — `ZDK.Page.getField('Lookup').getValue()` returns `{ id, name }` (FAQ #26).
- Detail-Page Canvas linked-field cascade — must call `$Client.refresh()` after Web-API write to surface the change (FAQ #42).
- Disabling buttons — both custom and system buttons can be enabled/disabled via Client Script (FAQ #30, #37). Pre-delete validation is **not** supported (FAQ #37).
- Pseudo fields — `getInput()` creates a pop-up with pseudo fields scoped to the current execution (FAQ #40).
- **Dynamic forms from Dynamic-field values** (new-mirror FAQ) — three supported paths: (a) `getInput()` to build a pop-up form from pseudo fields driven by Dynamic-field values; (b) render a Widget that builds the form, then pass values back to the Page via Client Script; (c) `setVisibility()` to hide/show existing Page fields based on Dynamic values.
- **Dynamically populate one field from another field's input** (new-mirror FAQ) — supported via `onChange-field` (or Page `onChange`) handler reading source field and writing target field; see the `populate-age-based-on-date-of-birth-entered` code sample on `zohocrm.dev/developerspace/client-script/codesamples`.
UI_ONLY_ACTIONS:
- `localStorage` / `window.localStorage` — **not permitted** (FAQ #18). No alternate browser storage equivalent.
- Pages where Client Script runs (FAQ #10, #31): Create Page, Edit Page, Clone Page, Detail Page (Canvas), Detail Page (Standard), List Page (Standard), Create Page (Wizard), Edit Page (Wizard). Note: Quick Create is **excluded** per FAQ #11 — see SOURCE_CONFLICT below.
- ZDK reference inside the IDE — `Setup → Developer Hub → Client Script IDE → Library → Information → Help icon` (FAQ #1). Note: No API equivalent — UI-only.
- **Terminal section of the Client Script IDE** (new-mirror FAQ) — executes ZDK API statements instantly without binding to an event. Typing e.g. `ZDK.Client.showMessage('Welcome to Client Script IDE', { type: 'info' });` and pressing Enter renders the message immediately. Use for ad-hoc ZDK probing; no script-save required. UI-only surface, no programmatic equivalent.
CRITICAL_FAILURE_MODES:
- **JS method blocklist** (FAQ #44) — calling any of the following silently fails or breaks the script: `postMessage`, `importScripts`, `close`, `addEventListener`, `removeEventListener`, `getEventListeners`, `WebSocket`, `setInterval`, `setTimeout`, `clearTimeout`, `clearInterval`, `Worker`, `MessagePort`, `MessageEvent`, `MessageChannel`, `onmessage`, `onmessageerror`, `window`, `document`. (no-timer / no-dom)
- **Detail Page field-write silent no-op**: calling `field.setValue(...)` on a Detail Page (Canvas or Standard) direct field does nothing (FAQ #25). Must route through Web-API Update Record + `$Client.refresh()`. (detail-page-setvalue)
- **Linked-field cascade requires reload**: in Detail Page (Canvas), a linked field reads stale until `$Client.refresh()` is called; Detail Page (Standard) has no comparable refresh API yet (FAQ #42). (cascade-stale)
- **setCriteria operator silent-reject**: any operator other than `starts_with` / `equals` is ignored, returning the unfiltered lookup list (FAQ #27). (filter-operator-binding)
- **Multi-select lookup contextual filter unsupported**: `setCriteria` on a multi-select lookup does not narrow choices (FAQ #32). (multi-select-filter)
- **Lookup setValue without id**: setting only `{name: "..."}` on a lookup field silently saves nothing; both id and name required (FAQ #3). (lookup-id-missing)
- **Custom event creation impossible**: there is no API to register new event triggers — only the documented event catalog fires (FAQ #22). (custom-events-absent)
- **Pre-delete validation impossible**: `beforeDelete`-style hook does not exist for records; only `beforeRowDelete` for subform rows (FAQ #37). (no-pre-delete)
- **Per-layout binding**: a script tagged to one layout does not fire on other layouts of the same module; missing layout coverage looks like a silent listener miss (FAQ #6). (layout-binding)
- **Run-mode side-effects persist**: Run-mode CRM writes hit the live tenant (FAQ #4 note) — destructive samples can mutate production data.
- **SOURCE_CONFLICT — Quick Create**: FAQ #11 says "currently Client Script in Zoho CRM cannot be executed in Quick Create Page", but the dedicated event-dictionary page `module_quick_create` enumerates `onLoad/onChange/onSave/onChange-field/onType-field` for Quick Create Popup. Treat the event-dictionary as authoritative; FAQ entry is stale.
- **Portals — unsupported ZDK calls**: the following silently fail / are blocked when the script runs under a Portal User context: `ZDK.Apps.CRM.Functions.execute()`, `ZDK.Apps.CRM.Connections.invoke()`, `ZDK.Page.addTag()`, `ZDK.Page.removeTag()`, `ZDK.Page.getTags()`, `ZDK.Page.getList().freezeColumns`, `ZDK.Page.getList().selectRecords()`, `ZDK.Page.getList().clearSelection()`, `ZDK.Client.openMailer()`. (portal-unsupported)
- **Canvas copy-icon button — not yet shipped**: adding a copy-to-clipboard custom-icon action inside a Canvas view via Client Script is "under development" — no working API today. (canvas-copy-unavailable)
- **Flyout formatted text without Widget — not yet supported**: rendering rich/formatted text inside a Flyout currently requires rendering a Widget; a text-only formatted Flyout is on the feasibility-check list, not shipped. (flyout-richtext-absent)
assets/clientscript/clientscript-healthcare.dou.md
FILE_PURPOSE: Read when modelling a 5-pattern Healthcare Client Script bundle on Orders / Deals — dynamic lookup filter, widget Flyout, widget pop-up, List Page customization, and getInput pseudo-field pop-up.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/client-script-health-care.html
TRIGGER_KEYWORDS: setCriteria, createFlyout, openPopup, freezeColumns, maskField, getInput, getList, getSubform, $Crm.user
TECHNICAL_CONSTRAINTS:
- Sample modules — Orders (primary), Deals (Sample E), Instruments (lookup target).
- Sample fields (API names): `Type`, `Instruments_List` (subform), `Instrument_Name`, `Instrument_Category`, `Warehouse`, `Shipment_Charge`, `Shipping_Speed`, `Phone`, `Status`, `Description`.
- Dynamic lookup filter is set via `setCriteria(criteria_string, { filterOnSearch: true })` on the Subform's lookup field handle.
- Flyout rendering requires a Widget; `ZDK.Client.createFlyout(<name>, options)` declares the surface and `getFlyout(<name>).open({ api_name, type: 'widget' })` mounts the Widget by Widget API Name.
- Bi-directional Widget messaging: `getFlyout(<name>).notify({ data: ... }, { wait: true })` blocks until the Widget responds; the response is returned synchronously to the Client Script.
- Pop-up widget rendering — `ZDK.Client.openPopup({ id: <widget_id>, type: 'widget', ... }, { data: ZDK.Page.getForm().getValues() })` accepts the widget record ID and a payload.
- List Page customization uses `ZDK.Page.getList()` handle for `freezeColumns`, `style({record, field}, criteria_string)`, `maskField`, `getRecords()`.
- `getList().style(...)` accepts a criteria string in `((Field:operator:Value))` syntax for conditional formatting per row.
- `ZDK.Client.showConfirmation(<message>, <ok_label>, <cancel_label>)` renders inline Markdown inside `<message>` — Sample D embeds `[count + " orders](http://ordersite.com)` link, `_not approved yet_` italics, and `\n\n` paragraph breaks in the prompt text.
- `ZDK.Client.getInput([{type, label}], header, ok_text, cancel_text)` creates a pseudo-field pop-up; pseudo fields do not count against the module's field limit.
REQUIRED_PARAMETERS:
- Sample A (Dynamic lookup filter, Page `onChange` on Orders Create):
· `ZDK.Page.getField('Type').getValue()` → category.
· `ZDK.Page.getSubform("Instruments_List").getField("Instrument_Name")` → subform lookup handle.
· `Product.setCriteria("(Instrument_Category:equals:"+Category+")", { filterOnSearch: true })`.
- Sample B (Flyout):
· `ZDK.Client.createFlyout('myFlyout', { header, animation_type: 2, height: '400px', width: '420px', top: 'center', right: '0' })`.
· `getFlyout('myFlyout').open({ api_name: 'flyout_for_warehouse', type: 'widget' })`.
· `var response = getFlyout('myFlyout').notify({ data: value }, { wait: true })` (synchronous).
· `getFlyout('myFlyout').close()`; then `Shipment_Charge` / `Shipping_Speed` / `Warehouse` field `setValue(response.<key>)`.
- Sample C (Pop-up Widget):
· `ZDK.Client.openPopup({ id: '<widget_record_id>', type: 'widget', header, animation_type: 1, close_icon: false, height, width, left: 'center' }, { data: ZDK.Page.getForm().getValues() })` returns the response.
- Sample D (List View Customization, List Page Standard `onCustomViewLoad` on Orders):
· `ZDK.Page.getList().freezeColumns(true)`.
· `style({record:{backgroundColor}, field:{Status:{backgroundColor, foregroundColor, fontWeight}}}, "((Status:equals:Delivered))")` — repeat per Status value (Cancelled red, Approved blue).
· `maskField('Phone', { length: 10, character: '*', reverse: false })`.
· `getList().getRecords()` iteration to count `Status == "Created"` and trigger `ZDK.Client.showConfirmation("Hi " + $Crm.user.full_name + ", ...", 'Yes. Approve!', "No, Not now")` when count > 0.
- Sample E (Pseudo-field pop-up, Field `onChange` on Deals `Stage`):
· `ZDK.Client.getInput([{ type: 'text', label: 'Comments' }], 'Deal', 'OK', 'Cancel')` returns the user input.
· Append to `Description`: `notes_field.setValue(notes_field.getValue() + " " + value + "- " + notes_pop_up + ",")`.
UI_ONLY_ACTIONS:
- Create each sample script: `Setup → Developer Hub → Client Script → +New Script → Module = Orders | Deals → Page → Layout → Event → Next → IDE → Save and Close → Enable`. Note: No API equivalent — UI-only.
- Build the Flyout / Pop-up Widget: create the Widget under `Setup → Developer Hub → Widgets` and copy its API Name (Flyout) or record ID (Pop-up) into the Client Script. Note: Widget creation is documented under the Widgets routing rows.
CRITICAL_FAILURE_MODES:
- `setCriteria` operators are restricted — per FAQ #27, only `starts_with` and `equals` are supported (Sample A uses `equals`). Other operators silently fail. (filter-operator-binding)
- Quoted field API names in `getField` / `getSubform` (`Type`, `Instruments_List`, `Instrument_Name`, `Instrument_Category`, `Warehouse`, `Shipment_Charge`, `Shipping_Speed`, `Status`, `Phone`, `Description`) must match the target module exactly — using display labels returns no field. (label-vs-api-name)
- `Status` literal values in `style(...)` criteria strings (`Delivered` / `Cancelled` / `Approved` / `Created`) must match the Status pick-list API values exactly; pick-list label drift silently no-styles the rows.
- Flyout `notify({ wait: true })` blocks the Client Script — combined with the 10 s timeout, the Widget must respond within budget or the script halts. (timeout)
- Sample C's Pop-up Widget record ID `1982484000000482011` is per-org and per-widget; copying the literal into another tenant silently opens nothing.
- Pseudo fields from `getInput` exist only during script execution; calling `setValue` on a pseudo field outside the same script execution does nothing.
- Sample D source contains a stray closing paren in `if (x.Status == "Created"))` — copying the snippet verbatim breaks parse; remove the extra `)`.
- Sample D `getList().getRecords()` only returns records on the loaded view page; off-page records are not iterated, so the "overdue Created" count under-counts on large lists.
- `$Crm.user.full_name` is Portal-aware (returns the Portal user's name) — assume Portal context if Portal is enabled.
assets/clientscript/clientscript-ide-components.dou.md
FILE_PURPOSE: Read when working inside the Client Script IDE — Code Editor / Review / Revisions / Run / Settings panels, API-name autocomplete, log + Terminal usage, and the 30-revision history cap.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/ide-components.html
TRIGGER_KEYWORDS: Client Script IDE, ZDK.client.showMessage, Revisions, Terminal, Messages panel, Run, Commit Message, Hard Reload
TECHNICAL_CONSTRAINTS:
- IDE provides syntax highlighting, smart code completions, JavaScript + ZDK definitions, and auto-suggestion of module / field API names whenever a single or double quote is entered.
- Single-line comment syntax `//`; block comment syntax `/* ... */`.
- On Save, if the editor finds an error, an error panel listing the errors pops up at the bottom; the error icon on the status bar (bottom-right) reopens the panel.
- Information section (right pane) shows page + event details, a Commit Message field, and a Library help icon that opens ZDK documentation.
- Static Resources section (right pane, below Information) lists the org's uploaded static resources for one-click Add into the current script; see [clientscript-static-resources.dou.md](./clientscript-static-resources.dou.md).
- Run mode executes the script live — CRM operations performed in Run mode persist in the live CRM account (no sandbox isolation inside Run).
- Run panel exposes Reload and Hard Reload of the underlying CRM tab for retesting.
- Messages panel under Run logs execution metadata: event + script details, execution status, logs, exception messages, and execution time.
- Terminal panel under Run accepts ad-hoc ZDK statements (e.g. `ZDK.client.showMessage('Welcome to Client Script IDE', { type: 'info' })`) and executes them on Enter.
- Revisions panel keeps up to 30 saved versions of a script with commit messages; the "Use this revision" action on a revision rolls the script back to that version.
- Review (Save) flow opens a diff editor; on save, if the server has a newer version of the script, the latest source and the local source are presented for conflict-resolution review.
- Status bar shows the last-modifying user and provides a Full-screen toggle and a Feedback icon for submitting event / ZDK API feedback to Zoho.
- Settings panel customizes Theme (Dark / Light), Cursor, Format (auto-format on paste / on type), Font, Minimap, Word Spacing, Selection (drag-to-move), Find, Suggestion (Insert vs Replace), Brackets (default = highlight all matching).
REQUIRED_PARAMETERS:
- IDE entry — opens automatically on `Next` from the Create Script pop-up; subsequently via `Setup → Developer Hub → Client Script → {script} → Edit`.
- Per-save inputs — script body (Code Editor) + optional Commit Message (Information section).
- Terminal contract — any valid ZDK expression evaluated on Enter; observed result printed inline.
UI_ONLY_ACTIONS:
- Open the IDE for an existing script: `Setup → Developer Hub → Client Script → {script} → Edit`. Note: No API equivalent — UI-only.
- View or restore a prior revision: IDE → Revisions panel → select revision → `Use this revision`. Note: No API equivalent — UI-only.
- Submit feedback on events / ZDK APIs: IDE status bar → Feedback icon. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- Run-mode CRM operations are not sandboxed — running a script that writes to records mutates live CRM data; guard with explicit test records.
- Revision history caps at 30 — older saves drop off; tag important versions in the Commit Message so the right revision can be located before drop-off.
- Save-time conflict — when another author updated the same script concurrently, the diff editor opens; choosing the wrong side overwrites the other author's edit. (conflict-management)
- Editor error panel pops up only when Save finds errors — runtime errors surface only in the Run → Messages panel; relying on the Save-time error panel for runtime issues misses them.
- Settings (Theme / Font / Minimap / etc.) are per-user IDE preferences; they do not change script behavior, so do not rely on Settings parity across authors during debugging.
assets/clientscript/clientscript-insurance.dou.md
FILE_PURPOSE: Read when modelling a field-validation / auto-population Client Script using onChange events — Date_of_Birth → Age → Category cascade, uppercase enforcement, and the page-event-vs-field-event consolidation pattern.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/client-script-insurance.html
TRIGGER_KEYWORDS: ZDK.Page.getField, age_field.showError, setValue, Date_of_Birth, Policyholder, onChange field, onChange page, field_name
TECHNICAL_CONSTRAINTS:
- Sample module — `Policyholder` (custom module); custom fields used in samples: `Date_of_Birth`, `Age`, `Category`, `Name` (first name), `Last_Name`.
- All three sample scripts attach to the Create page of the `Policyholder` module via field events on `Date_of_Birth`, `Name`, and `Last_Name`.
- The consolidated single-script version uses Page `onChange` with `switch (field_name)` to handle all three field updates inside one script.
- Page `onChange` runs on every field change on the page; Field `onChange` runs only when the specific field is updated.
REQUIRED_PARAMETERS:
- Per-script binding: Module = `Policyholder`, Page = Create page, Event Family = Field `onChange` (Date_of_Birth | Name | Last_Name) OR Page `onChange` (consolidated).
- ZDK call surface used in samples:
· `ZDK.Page.getField("<API_Name>")` — handle for `Age`, `Category`, `Name`, `Last_Name`, `Date_of_Birth`.
· `<field>.getValue()` — read current field value.
· `<field>.setValue(<v>)` — write field value (used for `Age`, `Category`, uppercased `Name` / `Last_Name`).
· `<field>.showError("<message>")` — surface a validation error inline against the field.
- Injected `value` argument carries the new field value inside Field `onChange`; the consolidated Page `onChange` reads `field_name` to branch.
- Sample logic — compute age from `value` (DOB string) with month / day adjustment, then `age > 80` ⇒ `showError`; `60 < age ≤ 80` ⇒ Category `Senior Citizen`; otherwise Category `Citizen`.
- Uppercasing — `first_name.setValue(value.toUpperCase())` inside Field `onChange` on `Name`; same pattern on `Last_Name`.
UI_ONLY_ACTIONS:
- Create the three scripts: `Setup → Developer Hub → Client Script → +New Script → Module = Policyholder → Page = Create Page → Layout → Event Type = Field → Field = Date_of_Birth | Name | Last_Name → Field Event = onChange → Next → IDE → Save and Close → Enable`. Note: No API equivalent — UI-only.
- Alternative consolidated flow: configure one script with Event Type = Page, Page Event = onChange; branch on `field_name` inside the IDE. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- Quoting a display label in `ZDK.Page.getField("…")` (e.g. `"Date of Birth"` instead of `Date_of_Birth`) silently returns no field; always use API names. (label-vs-api-name)
- The sample uses `Name` as the first-name API name — module-specific renames must update the script accordingly; copy-pasting the sample to a module whose first-name field is `First_Name` silently no-ops.
- Page `onChange` re-runs on every field change — placing heavy logic (age recomputation) inside an unconditional branch wastes execution time; gate inside a `field_name === '<API>'` check.
- `value` argument inside Field `onChange` is the raw new value — for `Date_of_Birth` it is a date string, not a Date object; constructing `new Date(value)` is required before arithmetic.
- `showError` raises an inline error but does NOT block the user from clicking Save — to block save, add an `onSave` Page Event with `return false` (see [clientscript-events.dou.md](./clientscript-events.dou.md)).
- Layout binding — these scripts run only for the layout selected at creation; multi-layout `Policyholder` deployments need one script copy per layout. (layout-binding)
assets/clientscript/clientscript-manufacturing.dou.md
FILE_PURPOSE: Read when modelling a multi-pattern Client Script bundle — third-party fetch with Trusted-Domains, read-only auto-populate, confirmation dialog, static-resource shared validator, and lookup-field auto-population.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/client-script-manufacturing.html
TRIGGER_KEYWORDS: ZDK.Page.getField, ZDK.Page.getForm, ZDK.Client.showConfirmation, setReadOnly, Trusted Domains, fetch, Request module, Quantity_of_Products
TECHNICAL_CONSTRAINTS:
- Sample modules — `Accounts` (sellers + manufacturing units), `Products`, `Category` (custom module), `Request` (custom module).
- Sample fields used in scripts (all API names): `Seller_Location`, `Manufacturer_Location`, `Distance`, `Request_Name`, `Quantity_of_Products`, `Product`, `Category`.
- `Product` and `Category` are Lookup fields in the `Request` module.
- Third-party `fetch(...)` from a Client Script requires the target domain to be whitelisted in Trusted Domains.
- The "Quantity ≥ 100" validation is enforced in two scripts — Field `onChange` on `Quantity_of_Products` AND Page `onSave` — both call the shared `quantityCheck(...)` static resource.
- Lookup-field auto-population accepts a `{ "id": "<record_id>", "name": "<display>" }` object via `setValue`.
- Page `onChange` (or Field `onChange`) is interchangeable for many of these patterns — Field `onChange` runs only for the specific field; Page `onChange` runs for every field change on the page.
REQUIRED_PARAMETERS:
- Sample A — Distance auto-population via third-party API:
· Event: Page `onChange` on Create Page of `Request`; gated by `field_name === "Seller_Location" || field_name === "Manufacturer_Location"`.
· Calls: `ZDK.Page.getField("Distance").setReadOnly(true)`; `ZDK.Page.getForm().getValues()`; `fetch("https://api.***/maps/api/dist**/json?origins=…&destinations=…&key=…")`; on resolution, `distance.setValue(data.rows[0].elements[0].distance.text)`.
- Sample B — `Request_Name` read-only auto-populate:
· Event: Page `onLoad` on Create Page of `Request`.
· Calls: `reqName_field.setValue("REQ-" + dd + "-" + mm + "-" + yyyy)`; `reqName_field.setReadOnly(true)`.
- Sample C — Quantity validator (Static Resource `quantityCheck(quantity)` shared across two scripts):
· JS file body: `if (quantity < 100) { quantity_field.showError("Minimum Quantity is 100"); }`.
· Script 1 — Field `onChange` on `Quantity_of_Products`: `quantityCheck(value);`.
· Script 2 — Page `onSave`: read via `ZDK.Page.getField("Quantity_of_Products")` then `quantityCheck(qty_field);`.
- Sample D — Confirmation on save:
· Event: Page `onSave` on Create Page of `Request`.
· Single-arg form: `ZDK.Client.showConfirmation('Your Request is submitted and will be shipped within 4 days.');`.
· Two-button form: `var isProceed = ZDK.Client.showConfirmation('…', 'Proceed', 'Cancel'); if (isProceed) { return true; } else { return false; }` — `return false` cancels the save.
- Sample E — Auto-populate `Category` lookup from `Product` lookup:
· Event: Field `onChange` on `Product` (or Page `onChange` gated on `field_name === 'Product'`).
· Logic: branch on `value.name` ∈ {Airbag sensor, Light sensor, Fuel level sensor} ⇒ Category = Sensors id; ∈ {Ignition Switch, Thermostat, Battery} ⇒ Category = Electrical Switches id.
· Call: `ZDK.Page.getField('Category').setValue({ "id": "<lookup_id>", "name": "<display>" });` — lookup IDs are org-specific.
UI_ONLY_ACTIONS:
- Whitelist the third-party API host: `Setup → Developer Space → Trusted Domains → add domain` before running Sample A. Note: No API equivalent — UI-only.
- Create each of the five sample scripts via `Setup → Developer Hub → Client Script → +New Script → Module = Request → Page = Create Page → Layout → Event Type / Field → Next → IDE → Save and Close → Enable`. Note: No API equivalent — UI-only.
- Upload the `quantityCheck` static resource: `Setup → Developer Hub → Client Script → Static Resources tab → New Resource → choose .js file → Save`, then add to both Quantity scripts via the right-pane Static Resources panel. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- `fetch(...)` to a domain not present in Trusted Domains is blocked at request time; the promise rejects and `distance.setValue` never runs. (trusted-domain)
- Lookup `setValue({"id": "…", "name": "…"})` IDs are per-org — copying the sample IDs `4967860000001049208` / `4967860000001049217` into another org sets an invalid lookup.
- Sample A's `fetch` call returns immediately; the `.then(...).then(...)` chain runs after the Client Script handler returns — long-latency APIs risk exceeding the 10 s execution budget (see [clientscript-best-practices.dou.md](./clientscript-best-practices.dou.md) for the loader pattern).
- `Product` and `Category` are Lookup fields — `value` inside `onChange` is a `{ id, name }` object, not a string; using `value === 'Airbag sensor'` silently fails.
- The `quantityCheck` static resource references `quantity_field` without defining it — the calling script must define `quantity_field = ZDK.Page.getField("Quantity_of_Products")` in the enclosing scope, else `showError` throws.
- Two scripts (Field `onChange` and Page `onSave`) sharing the same validator double-fire on Save — by design, but be aware of order via Reorder (see [clientscript-best-practices.dou.md](./clientscript-best-practices.dou.md)).
- `setReadOnly(true)` enforced in the script does not survive a layout change — opening the record under a non-bound layout leaves Distance / Request_Name editable. (layout-binding)
- Field API names quoted in samples must exist verbatim in the target `Request` module before activation; renames silently no-op `getField`. (label-vs-api-name)
assets/clientscript/clientscript-mobile.dou.md
FILE_PURPOSE: Read when authoring a Client Script that must also run in the Zoho CRM iOS / Android mobile apps — per-page-type mobile event matrix.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/mobile.html
TRIGGER_KEYWORDS: mobile, iOS, Android, onBeforeMandatoryFormSave, beforeTransition, afterTransition, onRowAdd, onClick
TECHNICAL_CONSTRAINTS:
- Same Client Script script runs in CRM web AND CRM mobile (iOS + Android) — no separate authoring; behaviour is event-supported-or-silent on mobile.
- Mobile-supported page types: Create Page (Standard), Edit Page (Standard), Clone Page (Standard), Create Page (Wizard), Edit Page (Wizard), Detail Page (Standard), Detail Page (Canvas).
- Page types **absent** from the mobile-supported matrix: Create / Clone / Edit Page (Canvas), List Page (Standard), List Page (Canvas), Quick Create Popup. Scripts bound to those surfaces silently no-op on mobile.
- Detail Page (Standard) Blueprint events on mobile — `beforeTransition`, `afterTransition`. Note: `afterTransition` appears on mobile-Detail-Standard here but is not in the web Detail-Standard event-dictionary (eventdict-detail-standard).
- Detail Page (Canvas) on mobile is reduced — only Page `onLoad`, Mandatory Form `onBeforeMandatoryFormSave` / `onMandatoryFormLoad`, Blueprint `beforeTransition`, and Canvas Button / Icon / Text `onClick`. Subform, Notes, Tag, Field, and standard custom Button events are **not** listed for mobile Detail Canvas.
REQUIRED_PARAMETERS:
- Mobile event matrix (verbatim from source, with Type labels added for cross-reference with [clientscript-event-type-matrix.dou.md](./clientscript-event-type-matrix.dou.md)):
· **Create / Edit / Clone Page (Standard)** — Page (Type: Page Event): `onLoad`, `onChange`, `onSave`; Field (Type: Field Event): `onChange`, `onType`; Subform (Type: Subform Event): `onCellChange`, `onRowAdd`, `beforeRowDelete`, `onRowDelete`; Custom Button (Type: Button Event): `onClick`.
· **Create / Edit Page (Wizard)** — Wizard (Type: Wizard Event): `onLoad`, `onChange`, `onTransition`, `onBeforeTransition`, `onBeforeSave`; Field (Type: Field Event): `onChange`, `onType`; Subform (Type: Subform Event): `onCellChange`, `onRowAdd`, `beforeRowDelete`, `onRowDelete`; Custom Button (Type: Button Event): `onClick`.
· **Detail Page (Standard)** — Page (Type: Page Event): `onLoad`; Mandatory Form (Type: Mandatory Fields Form Event): `onBeforeMandatoryFormSave`, `onMandatoryFormLoad`; Blueprint (Type: Blueprint Event): `beforeTransition`, `afterTransition`; Subform (Type: Subform Event): `onCellChange`, `onRowAdd`, `beforeRowDelete`, `beforeRowUpdate`, `onRowDelete`; Custom Button (Type: Button Event): `onClick`.
· **Detail Page (Canvas)** — Page (Type: Page Event): `onLoad`; Mandatory Form (Type: Mandatory Fields Form Event): `onBeforeMandatoryFormSave`, `onMandatoryFormLoad`; Blueprint (Type: Blueprint Event): `beforeTransition`; Canvas Button (Type: Canvas Button Event): `onClick`; Icon (Type: Icon Event): `onClick`; Text (Type: Text Event): `onClick`.
- Sample scenario in source: Leads-record duplicate-check on save — compare other records' Email / Phone and `return false` to block save with an error message.
UI_ONLY_ACTIONS:
- *(none in source — script authoring is via Setup → Developer Hub → Client Script regardless of execution surface)*
CRITICAL_FAILURE_MODES:
- **List Page (Standard + Canvas) silent absence on mobile** — `onCustomViewLoad` / `onBeforeCustomViewChange` / `getList()` operations and per-row canvas-element clicks do not fire on the mobile List view; List-Page scripts behave as no-ops on mobile. (mobile-listpage-absent)
- **Create / Clone / Edit Page (Canvas) silent absence on mobile** — only Standard Create / Clone / Edit surfaces fire on mobile; Canvas Create / Clone / Edit scripts (including canvas Button / Icon / Text `onClick`) do not execute on mobile.
- **Quick Create absent on mobile** — Quick Create Popup events do not fire on mobile.
- **Mobile Detail-Canvas Field / Subform / Notes / Tag events silently absent** — a Detail-Canvas script with `onBeforeUpdate` (field), `onRowAdd` (subform), `onTagChanged`, or `onBeforeAdd` (notes) does not execute on mobile despite executing on web. (mobile-detail-canvas-gaps)
- **`afterTransition` (Blueprint, Detail Standard) is mobile-only per this source** — web event-dictionary does not list it; cross-surface scripts will see different Blueprint hook firing.
- **Custom Button on Detail Page Canvas absent on mobile** — only Canvas Button / Icon / Text `onClick` listed for mobile Detail-Canvas; the standard custom Button (top-bar) `onClick` is omitted.
- **Web-only ZDK UI surfaces** — Flyout / Popup / Widget-render APIs are not documented as mobile-supported in this page; cross-surface Widget invocations need explicit mobile validation.
- **Sample-scenario duplicate-check on save**: requires Web API list-records call inside `onSave`; the 10 s timeout applies on mobile networks and may abort the check.
assets/clientscript/clientscript-overview.dou.md
FILE_PURPOSE: Read when scoping a ClientScript build — confirming edition / profile prerequisites, supported pages, event-vs-command triggers, 10 s timeout, and ZDK / ZRC surface area before authoring.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/overview.html
TRIGGER_KEYWORDS: ZDK.Client.showAlert, ZDK, ZRC, Developer Permissions, $Crm.user, onLoad, onChange, onSave
TECHNICAL_CONSTRAINTS:
- Execution context — JavaScript runs on the user's web browser instead of the server.
- Edition gating — Client Script feature is available in Enterprise, Ultimate and Professional editions of Zoho CRM.
- Browser gating — only the CRM-supported browser versions render Client Script (per the linked browser-versions page).
- Profile gating — Developer Permissions must be enabled on the caller's profile before they can access the Client Script feature.
- Execution timeout — every Client Script execution is bounded to 10 seconds.
- Supported pages: Create page, Edit page, Clone page, List Page (Standard), Detail Page (Standard), Detail Page (Canvas), Create Page (Wizard), Edit Page (Wizard).
- Per-layout binding — author one Client Script per layout of a module; one definition does not span layouts.
- Custom Button binding — a Client Script for a custom button can be created only from the Buttons page (edit / update later from the Client Script setup page).
- Portal scope — Client Scripts automatically run inside Zoho CRM Portals; scope to portal-wide, specific user types, or individual users; newly created user types have Client Script enabled by default.
- ZDK Web (CRM) API calls invoke Zoho CRM APIs internally and consume an API credit per call; ZDK Client API calls do not.
- ZRC (Zoho Request Client) is a built-in SDK with one consistent syntax for CRM APIs, Connection requests, and public APIs; **org-level feature enablement required when used from ClientScript; available by default in Widgets without any org enablement**.
REQUIRED_PARAMETERS:
- Trigger category — choose Module (for event-based) or Commands (for command palette / keyboard shortcut) when creating a script (see [clientscript-creation.dou.md](./clientscript-creation.dou.md)).
- Event family per supported page (page-event vs field-event vs subform-event vs button-event vs blueprint-event vs tag-event vs mandatory-form-event vs canvas-button/icon/text vs notes-related-list) — full event list in [clientscript-events.dou.md](./clientscript-events.dou.md).
- ZDK Client API surface (in-page UI + record field manipulation) — entry points include `ZDK.Page.getField`, `ZDK.Client.showAlert`; full surface in the Client APIs nodes.
- ZDK Web API surface (CRM data reads / writes via the SDK) — entry point `ZDK.Apps.CRM.*` family; full surface in the Web APIs nodes.
- Portal user context — read user-specific details from `$Crm.user` when running inside a Portal.
- ZRC invocation pattern — covered in the ZRC nodes.
UI_ONLY_ACTIONS:
- Enable per-profile Client Script access: `Setup → Users and Control → Security Control → {Profile} → enable Developer Permissions → Save`. Note: No API equivalent — UI-only.
- Confirm tenant edition: `Setup → General → Company Details`. Note: No API equivalent — UI-only.
- Create a Client Script for a Custom Button: open the Buttons setup page for the target module and configure the Client Script from there (the standalone Client Script setup page cannot create button-bound scripts). Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- Edition below Professional / Enterprise / Ultimate: Client Script node hidden in Setup; no API workaround. (edition-gating)
- Caller's profile missing Developer Permissions: Client Script setup is inaccessible for that profile; admin sees it work, end-users get silent no-fire. (profile-binding)
- Layout-binding mismatch — script attached to one layout will not fire on records using a different layout of the same module; no error, no log. (layout-binding)
- Browser off the supported-versions list: Client Script does not run on that browser; verify browser before triage.
- Execution exceeds 10 s: the script is halted; long-running ZDK Web API or third-party calls require a loader pattern (see [clientscript-best-practices.dou.md](./clientscript-best-practices.dou.md)).
- ZDK Web (CRM) API calls account for an API credit per invocation — high-fan-out scripts drain the daily credit pool.
- ZRC org feature not enabled: `zrc` is unavailable in the ClientScript runtime — fails silently or throws on first call; confirm org enablement before using ZRC from ClientScript. In Widgets, ZRC is available by default.
- ZRC errors not caught in `try/catch`: silently absorbed by the ClientScript environment — no console error, no log (see [clientscript-zrc.dou.md](./zrc/clientscript-zrc.dou.md)).
- Custom Button Client Script attempted from the standalone Client Script page instead of the Buttons page: creation rejected; use the Buttons page.
- Portal Client Script auto-enables for newly created user types — assume opt-in unless explicitly disabled per user type.
assets/clientscript/clientscript-static-resources.dou.md
FILE_PURPOSE: Read when adding reusable JS modules to a Client Script via Static Resources — confirming upload limits, the 5-per-page import cap, and the no-DOM / no-window restriction.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/static-resources.html
TRIGGER_KEYWORDS: Static Resources, ZDK.Page.getField, ZDK.Client.showConfirmation, ZDK.Client.showMessage, .js upload, 5 MB resource, 200 files
TECHNICAL_CONSTRAINTS:
- Edition gating — Static Resources feature is available in Enterprise, Ultimate and Professional editions of Zoho CRM.
- File type — JS files only.
- Hard caps — each file ≤ 5 MB; up to 200 files per org; up to 5 static resources imported per Client Script page; all imported resources are available to every script on that page.
- `document` and `window` references are not available during Client Script execution; DOM- or window-dependent libraries must not be uploaded as static resources.
- Resource upload is served from a static server; reference the resource by Name from the IDE Static Resources panel.
- Functions defined in a static resource are callable directly from any Client Script that imports the resource (e.g. `is_socialSecurity_Number(ssn)` inside a script after `ssnCheck.js` is added).
- Minified JS distribution is recommended in source to reduce network latency.
REQUIRED_PARAMETERS:
- Upload payload — Name, Description, File (a .js file ≤ 5 MB).
- Per-script import — open the IDE Static Resources panel (right pane, below Information) and click `Add` next to the resource Name; Remove via hover → Remove on the Added row.
- Invocation contract — call the exported function by its bare name (no namespace) from inside the Client Script body; combine with `ZDK.Page.getField("API_Name")` to read field values.
UI_ONLY_ACTIONS:
- Upload a Static Resource: `Setup → Developer Hub → Client Script → Static Resources tab → New Resource → Name + Description + Choose File → Save`. Note: No API equivalent — UI-only.
- Add resource to a script: open the script in the IDE → right-pane Static Resources icon → `Add` next to the resource Name. Note: No API equivalent — UI-only.
- Remove resource from a script: in the right-pane Static Resources panel, hover the Added row and click `Remove`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- Uploading a non-JS file: rejected; only `.js` is accepted.
- File > 5 MB: rejected at upload time; minify or split before re-uploading.
- 200-file cap hit: new uploads rejected; delete unused resources to free slots.
- Importing a 6th resource on the same script page: rejected at the cap of 5 per page; consolidate logic into fewer files.
- Static resource containing `document.*` / `window.*` / DOM access executes inside an environment where those globals are absent → the call throws at runtime and the dependent script halts. (sandbox-binding)
- Field API names quoted in the static resource (e.g. `ZDK.Page.getField("SSN")`) must match the module's field API name exactly; quoting a display label silently returns no field. (label-vs-api-name)
assets/clientscript/clientscript-troubleshooting.dou.md
FILE_PURPOSE: Read when debugging a Client Script — using the in-IDE `log()` Messages panel and the in-browser `debugger;` pause / inspect workflow with Chrome DevTools source maps.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/client-script/troubleshooting.html
TRIGGER_KEYWORDS: log statement, debugger, ZDK.Page.getField, JavaScript Source Maps, DevTools, F12, Cmd+Option+I, Ctrl+Shift+I
TECHNICAL_CONSTRAINTS:
- `log(...)` writes to the IDE Run → Messages panel; it surfaces only when the script is executed via the IDE Run option.
- `debugger;` is the standard JavaScript debugger statement — when encountered with the browser DevTools open, the browser pauses execution at that line; with DevTools closed, the statement is a no-op.
- DevTools shortcut keys (source-verbatim): Windows / Linux — `Ctrl + Shift + I` or `F12`; Mac — `Cmd + Option + I`.
- Source-map mapping back to the original Client Script source requires enabling `JavaScript Source Maps` in Chrome DevTools settings.
- Only two debugging primitives are documented on this page: `log` and `debugger`.
REQUIRED_PARAMETERS:
- `log(<expression>)` — call to emit a Messages-panel entry; runs only under IDE Run.
- `debugger;` — bare statement; place at any line where execution should pause once DevTools is open.
- DevTools enablement — open DevTools before the user action that triggers the script; pre-trigger is required because the statement is no-op when DevTools is closed.
UI_ONLY_ACTIONS:
- Open the Messages panel for log inspection: open the script in the IDE → click Run → execute → Messages panel at the bottom. Note: No API equivalent — UI-only.
- Enable JavaScript Source Maps: Chrome DevTools → Settings → check `JavaScript Source Maps`. Note: Browser-side toggle; no CRM API equivalent.
CRITICAL_FAILURE_MODES:
- `debugger;` left in a saved script + a user runs the script with DevTools open → unrelated end-users hit the pause line and their CRM page freezes; strip `debugger;` before activating.
- `debugger;` with DevTools closed = no-op — relying on it in a production debug pass without instructing the operator to open DevTools yields zero visibility.
- `log(...)` calls do not surface anywhere outside the IDE Run → Messages panel — they are invisible to end-users and provide no production telemetry.
- Source-map view diverges from the saved Client Script body when Source Maps is disabled in DevTools — debugger pauses on the transformed source and variable names do not align.
- The page documents only `log` and `debugger`; profile / layout / Developer-Permissions silent no-fire modes are not covered here (see [clientscript-overview.dou.md](./clientscript-overview.dou.md)).
assets/clientscript/event-dictionary/.gitkeep
assets/clientscript/event-dictionary/eventdict-create-edit-clone-canvas.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on Create / Clone / Edit Page (Canvas) — canonical wire signature including canvas-button / icon / text hooks.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_create_edit_clone_canvas
TRIGGER_KEYWORDS: onLoad, onChange, onSave, onType, onMandatoryFormLoad, Canvas Button, Icon, Text, beforeRowDelete
TECHNICAL_CONSTRAINTS:
- Page-type scope — Canvas Create Page, Edit Page, Clone Page (one event-dictionary covers all three).
- Interceptor events: Page `onSave`, Subform `beforeRowDelete`.
- Canvas-only event families: Canvas Button `onClick()`, Icon `onClick()`, Text `onClick()` — these fire on click of canvas-design elements and take no args.
- Mandatory Form has only the load hook here (`onMandatoryFormLoad`); there is **no** `onBeforeMandatoryFormSave` on Canvas Create/Edit/Clone.
- Custom Button binding configured only from the Buttons page.
REQUIRED_PARAMETERS:
- **Page events** (Type: Page Event):
· `onLoad()` — fires when the canvas create/clone/edit page loads. No args.
· `onChange(field_name)` — `field_name` = API name of the modified field. Supported field types: Single Line, Multi-Line, Email, Phone, Pick List, Date, Number, Currency, Decimal, Percent, Long Integer, Checkbox, URL, Lookup, Owner Lookup, Multi-select Picklist.
· `onSave()` — **Interceptor**. Fires after Save / Save and New click, before persistence. `return false` cancels save.
- **Field events** (Type: Field Event):
· `onChange(value)` — `value` = current value. Fires on blur. Supported field types: same as Page-onChange list above (incl. Multi-select Picklist).
· `onType(value)` — `value` = current value. Fires per keystroke.
- **Mandatory Form event** (Type: Mandatory Fields Form Event): `onMandatoryFormLoad(field)` — `field` = `{ base field API name, value }`.
- **Canvas Button event** (Type: Canvas Button Event): `Canvas Button onClick()` — no args, fires on click of a canvas button.
- **Icon event** (Type: Icon Event): `Icon onClick()` — no args, fires on click of a canvas icon.
- **Text event** (Type: Text Event): `Text onClick()` — no args, fires on click of canvas text.
- **Subform events** (Type: Subform Event):
· `onRowAdd(row_data, index)`, `onCellChange(field_name, value, index)`, `onRowDelete(row_data, index)` — non-interceptor.
· `beforeRowDelete(row_data, index)` — **Interceptor**. `return false` cancels deletion.
- **Button event** (Type: Button Event): `onClick(context)` — `context` returns `{ Module API Name, ID }`.
UI_ONLY_ACTIONS:
- Canvas-element script binding — `Setup → Customization → Canvas → <canvas> → select element (Button / Icon / Text) → Actions → Client Script`. Note: No API equivalent — UI-only.
- Custom Button (top-bar) binding — `Setup → Customization → Modules and Fields → <Module> → Links and Buttons → Buttons → Placement`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **SOURCE_CONFLICT — Field `onBeforeUpdate`**: legacy `client-script-events.html` documents `onBeforeUpdate` (Field) for Multi-select Lookup on Canvas Create/Edit/Clone, but this event-dictionary lists **only** `onChange` and `onType` under Field events. Treat event-dictionary as authoritative for new scripts; legacy `onBeforeUpdate` may not fire here. (event-absent-in-wire-signature)
- **No `onBeforeMandatoryFormSave` on Canvas Create/Edit/Clone** — only `onMandatoryFormLoad`; mandatory-form-save interception is not possible on this surface. (intercept-gap)
- **No Subform `beforeRowUpdate`** — only `beforeRowDelete`; subform-row in-place edits cannot be intercepted.
- **No Blueprint / Tag / Notes events** — those fire only on Detail surfaces; cross-surface scripts hit silent no-listener.
- **Canvas-element `onClick` events take no args** — there is no `context` for Canvas Button / Icon / Text; deriving record-id requires reading the form state via `getForm().getValues()`. (no-canvas-context)
- **`onChange` does not fire on field types outside the source-enumerated supported list above** — silent listener miss. (unsupported-field-type)
- **`field_name` arg is API name, not display label** — `case "Last_Name"` works, `case "Last Name"` does not. (label-vs-api-name)
- **Per-layout binding** — script bound to one Canvas layout does not fire on other layouts. (layout-binding)
assets/clientscript/event-dictionary/eventdict-create-edit-clone-standard.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on Create / Edit / Clone Page of a Standard layout — canonical wire signature.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_create_edit_clone_standard
TRIGGER_KEYWORDS: onLoad, onChange, onSave, onType, onRowAdd, onCellChange, beforeRowDelete, onClick, Interceptor
TECHNICAL_CONSTRAINTS:
- Page-type scope — Standard Create Page, Edit Page, Clone Page (one event-dictionary covers all three).
- Interceptor events accept `return false` at end-of-script to cancel the gesture; non-interceptor events cannot block.
- Field events fire per-field; Page-level `onChange` fires for any field change on the page and dispatches the field's API name.
- Subform events are scoped to a specific subform; the script binds to a subform handle via `getSubform(<subform_api_name>)`.
- Custom Button `onClick` Client Scripts are configured **only** from the Buttons page (not from the Client Script setup list directly); existing scripts then appear on the Client Script setup page.
REQUIRED_PARAMETERS:
- **Page events** (Type: Page Event):
· `onLoad()` — fires when the create/clone/edit page loads. No args.
· `onChange(field_name)` — `field_name` = API name of the modified field. Supported field types: Single Line, Multi-Line, Email, Phone, Pick List, Date, Number, Currency, Decimal, Percent, Long Integer, Checkbox, URL, Lookup, Owner Lookup, Multi-select Picklist.
· `onSave()` — **Interceptor**. Fires after Save / Save and New click, before persistence. `return false` cancels save.
- **Field events** (Type: Field Event):
· `onChange(value)` — `value` = current value in the field. Fires on blur / focus-out after edit. Supported field types: identical to Page-onChange list above (incl. Multi-select Picklist).
· `onType(value)` — `value` = current value. Fires per keystroke / per type event.
- **Subform events** (Type: Subform Event; bound to a subform):
· `onRowAdd(row_data, index)` — `row_data` = object details of newly added row; `index` = its row index.
· `onCellChange(field_name, value, index)` — `field_name` = edited cell's field API name; `value` = new value; `index` = row index.
· `onRowDelete(row_data, index)` — fires after row deletion. `row_data` / `index` as above.
· `beforeRowDelete(row_data, index)` — **Interceptor**. Fires after delete-icon click, before row removal. `return false` cancels deletion.
- **Button event** (Type: Button Event):
· `onClick(context)` — `context` returns `{ Module API Name, ID }`.
UI_ONLY_ACTIONS:
- Custom Button Client Script binding — `Setup → Customization → Modules and Fields → <Module> → Links and Buttons → Buttons → Create / select button → action = Client Script`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- `onChange` (Page or Field) does not fire on field types outside the source-enumerated supported list above — silent listener miss. (unsupported-field-type)
- `field_name` in Page `onChange` is the field **API name**, not the display label — `switch(field_name) { case "Last_Name": ... }` works, `case "Last Name"` does not. (label-vs-api-name)
- `onSave` `return false` cancels save **only when returned synchronously**; an async `return false` after a `Promise` resolves leaves the save uncancelled.
- `beforeRowDelete` is **camelCase** in this event-dictionary; the legacy `client-script-events.html` doc spells it `BeforeRowDelete` (PascalCase). Treat the event-dictionary spelling as authoritative for new scripts. (case-mismatch)
- **No `onBeforeUpdate` field event on Create/Edit/Clone Standard** — only Detail Page exposes field `onBeforeUpdate`. Attempting that hook on the create surface registers no listener.
- **No Mandatory-Form events on Create/Edit/Clone Standard** — Mandatory Form fires only on Detail-Page surfaces.
- **No Subform `beforeRowUpdate`** — only Detail-Page subforms expose `beforeRowUpdate`; subform-row edits on Create/Edit cannot be intercepted.
- Custom Button `context` does **not** include selected related-list IDs on this surface — that variant only exists on Detail Page button context. (button-context-shape)
- Per-layout binding — script bound to one layout does not fire on other layouts of the same module. (layout-binding)
assets/clientscript/event-dictionary/eventdict-create-edit-wizard.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on Wizard Create / Edit Page — canonical wire signature for transition-based multi-screen flows.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_create_edit_wizard
TRIGGER_KEYWORDS: onLoad, onChange, onTransition, onBeforeTransition, onBeforeSave, onType, beforeRowDelete, source_screen, target_screen
TECHNICAL_CONSTRAINTS:
- Page-type scope — Wizard Create Page and Edit Page (one event-dictionary covers both). The hook family is "Wizard Event", not "Page Event".
- Interceptor events: Wizard `onBeforeTransition`, `onBeforeSave`, Subform `beforeRowDelete`.
- Wizard transitions move the user between screens within the configured wizard; transition events do not fire for in-screen field edits.
- Custom Button binding configured only from the Buttons page.
REQUIRED_PARAMETERS:
- **Wizard events** (Type: Wizard Event):
· `onLoad()` — fires when the Client-Script-configured wizard loads. No args.
· `onChange(field_name)` — `field_name` = API name of the modified field. Supported field types: Single Line, Multi-Line, Email, Phone, Pick List, Date, Number, Currency, Decimal, Percent, Long Integer, Checkbox, URL, Lookup, Owner Lookup, Multi-select Picklist.
· `onTransition(screen)` — fires after a screen transition completes. `screen` = `{ Name, API Name }` of the target screen.
· `onBeforeTransition(source_screen, target_screen)` — **Interceptor**. Fires before a transition. Args: `source_screen` = `{ Name, API }`; `target_screen` = `{ Name, API }`. `return false` cancels.
· `onBeforeSave()` — **Interceptor**. Fires on Save click. `return false` cancels save.
- **Field events** (Type: Field Event):
· `onChange(value)` — `value` = current value. Fires on blur. Supported field types: same as Wizard-onChange list above (incl. Multi-select Picklist).
· `onType(value)` — `value` = current value. Fires per keystroke.
- **Subform events** (Type: Subform Event):
· `onRowAdd(row_data, index)`, `onCellChange(field_name, value, index)`, `onRowDelete(row_data, index)` — non-interceptor.
· `beforeRowDelete(row_data, index)` — fires after delete-icon click, before row removal; add `return false` to cancel. **Note**: source description matches Interceptor semantics but does NOT carry the explicit `Interceptor` label here (legacy doc behaviour preserved).
- **Button event** (Type: Button Event): `onClick(context)` — `context` returns `{ Module API Name, ID }`.
UI_ONLY_ACTIONS:
- Wizard authoring (screens, transitions, layouts per screen): `Setup → Customization → Wizards → +New Wizard → screens → transitions → assign profiles`. Note: No API equivalent — UI-only.
- Bind a Client Script to a specific wizard: `Setup → Developer Hub → Client Script → +New → Page = Create Page (Wizard) | Edit Page (Wizard) → Wizard = <wizard_name>`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **SOURCE_CONFLICT — Field `onBeforeUpdate`**: legacy `client-script-events.html` documents `onBeforeUpdate` (Field) for Wizard, but this event-dictionary lists only `onChange` and `onType` under Field. Treat event-dictionary as authoritative; the legacy `onBeforeUpdate` hook may not fire on Wizard. (event-absent-in-wire-signature)
- **No Subform `beforeRowUpdate` on Wizard** — only `beforeRowDelete`; row-edit interception unavailable.
- **No Mandatory Form / Blueprint / Tag / Notes events on Wizard** — those surfaces fire only on Detail pages.
- **No Canvas-element events** — Wizard uses standard layouts, not Canvas; Canvas Button / Icon / Text events do not apply.
- **`onBeforeTransition` and `onBeforeSave` cancel only synchronously** — async `return false` after a Promise does not halt the gesture. (async-cancel)
- **`source_screen` / `target_screen` are screen objects, not field maps** — they identify which screens, not which field values changed. Use `getForm().getValues()` separately to read field state.
- **`onTransition` fires after the move** — to block, hook `onBeforeTransition`; trying to revert state from `onTransition` produces user-visible flicker. (listener-order)
- **Per-layout / per-wizard binding** — script bound to one wizard does not fire on other wizards of the same module. (layout-binding)
- **`field_name` arg is API name, not display label** — `case "Last_Name"` works, `case "Last Name"` does not. (label-vs-api-name)
- **Wizard is supported on mobile** (`../clientscript-mobile.dou.md`) — same event coverage modulo Field types and 10 s timeout.
assets/clientscript/event-dictionary/eventdict-detail-canvas.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on Detail Page (Canvas) — canonical wire signature including canvas-element, Blueprint, Mandatory Form, Tag, Notes hooks.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_view_canvas
TRIGGER_KEYWORDS: onLoad, onBeforeUpdate, beforeTransition, onBeforeMandatoryFormSave, onTagChanged, beforeRowUpdate, Canvas Button, onBeforeAdd
TECHNICAL_CONSTRAINTS:
- Page-type scope — Detail Page (Canvas layout). For Detail Page (Standard), see eventdict-detail-standard.
- Interceptor events: Field `onBeforeUpdate`, Blueprint `beforeTransition`, Mandatory Form `onBeforeMandatoryFormSave`, Subform `beforeRowDelete` / `beforeRowUpdate`, Notes `onBeforeAdd` / `onBeforeUpdate`.
- Canvas-only event families: Canvas Button `onClick()`, Icon `onClick()`, Text `onClick()` — no args.
- Detail Page has no direct field write-through; field edits go through Mandatory Form or Web-API Update Record + `$Client.refresh()`.
REQUIRED_PARAMETERS:
- **Page event** (Type: Page Event): `onLoad()` — fires when the canvas page loads. No args.
- **Field event** (Type: Field Event): `onBeforeUpdate(value)` — **Interceptor**. Fires on tick-icon click after inline field edit. `value` = current value. `return false` cancels. Supported field types: Single Line, Multi-Line, Email, Phone, Pick List, Date, Number, Currency, Decimal, Percent, Long Integer, Checkbox, URL, Lookup, Owner Lookup. **Multi-select Picklist is NOT in this list** (contrast with standard Create/Edit `onChange` list).
- **Mandatory Fields Form events** (Type: Mandatory Fields Form Event):
· `onBeforeMandatoryFormSave(value)` — **Interceptor**. `value` = values of the fields in Mandatory form + base-field current value. `return false` cancels. **Note: arg name is `value`, not `form`** (Detail-Standard uses `form`).
· `onMandatoryFormLoad(field)` — `field` = `{ base field API name, value }`.
- **Blueprint event** (Type: Blueprint Event): `beforeTransition(transition)` — **Interceptor**. `transition` = `{ ID, name }`. `return false` cancels.
- **Tag event** (Type: Tag Event): `onTagChanged(tags_added, tags_removed)` — **two** args: `tags_added` = list of tags added; `tags_removed` = list of tags removed. (No `tag` arg here, contrast with Detail-Standard which has 3-arg `(tag, added, removed)`.)
- **Canvas Button event** (Type: Canvas Button Event): `Canvas Button onClick()` — no args.
- **Icon event** (Type: Icon Event): `Icon onClick()` — no args.
- **Text event** (Type: Text Event): `Text onClick()` — no args.
- **Subform events** (Type: Subform Event):
· `onRowAdd(row_data, index)`, `onCellChange(field_name, value, index)`, `onRowDelete(row_data, index)` — non-interceptor.
· `beforeRowDelete(row_data, index)` — **Interceptor**. `return false` cancels deletion.
· `beforeRowUpdate(row_data, index)` — **Interceptor**. `return false` cancels update.
- **Button event** (Type: Button Event): `onClick(context)` — **shape-variant**:
· In Record → `{ Module API Name, ID }`.
· Related List → `{ Module API Name of the related list, ID, selected record ID }`.
- **Notes events** (Type: Notes Related List — no "Event" suffix):
· `onBeforeAdd(notes)` — **Interceptor**. `notes` = `{ title, content }`. `return false` blocks add.
· `onBeforeUpdate(notes)` — **Interceptor**. `notes` = `{ title, content }`. `return false` blocks update.
UI_ONLY_ACTIONS:
- Canvas-element binding — `Setup → Customization → Canvas → <canvas> → element → Actions → Client Script`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **Tag event arg shape differs across Detail surfaces** — Canvas = `(tags_added, tags_removed)` 2-arg; Standard = `(tag, added, removed)` 3-arg. Cross-surface scripts that read `tag` on Canvas get `undefined`. (cross-surface-arg-shape)
- **Mandatory-Form `onBeforeMandatoryFormSave` arg differs across Detail surfaces** — Canvas = `value`; Standard = `form`. Cross-surface scripts read the wrong shape. (cross-surface-arg-shape)
- **Field `onBeforeUpdate` does NOT include Multi-select Picklist** on this Canvas surface — clicking save on an MSP field bypasses the interceptor. (msp-intercept-absent)
- **Field `setValue` silently no-op on Detail Page** — direct field writes do nothing; use Mandatory-Form fields or Web-API Update Record + `$Client.refresh()`. (detail-page-setvalue)
- **Linked-field cascade requires `$Client.refresh()`** — Detail Canvas does not re-render automatically after a Web-API write. (cascade-stale)
- **Canvas-element `onClick` events take no args** — derive record-id from `getForm().getValues()` or `$Page.record.id`. (no-canvas-context)
- **Button `context` shape varies by placement** — In Record vs Related List shapes differ. (button-context-placement)
- **`beforeTransition` blocks transition only, not parallel listener side-effects** — listener-order matters. (listener-order)
- **Blueprint `afterTransition` not in this surface** — mobile Detail-Standard exposes it; cross-surface scripts diverge.
assets/clientscript/event-dictionary/eventdict-detail-standard.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on Detail Page (Standard) — canonical wire signature including Blueprint, Mandatory Form, Tag, Notes hooks.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_detail
TRIGGER_KEYWORDS: onLoad, onBeforeUpdate, beforeTransition, onBeforeMandatoryFormSave, onMandatoryFormLoad, onTagChanged, beforeRowUpdate, onBeforeAdd
TECHNICAL_CONSTRAINTS:
- Page-type scope — Detail Page (Standard layout). For Detail Page (Canvas), see eventdict-detail-canvas.
- Detail Page has **no** field write-through (`setValue`) — field edits go through Mandatory Form pop-up or Web-API Update Record. See `../clientscript-faqs.dou.md` for the workaround.
- Interceptor events: `onBeforeUpdate` (field), `beforeTransition` (blueprint), `onBeforeMandatoryFormSave`, `beforeRowDelete` / `beforeRowUpdate` (subform), `onBeforeAdd` / `onBeforeUpdate` (notes).
- Mandatory Form events fire when a field edit triggers the system's "fill required fields" pop-up.
- Notes events scope to the Notes Related List only.
REQUIRED_PARAMETERS:
- **Page event** (Type: Page Event): `onLoad()` — fires on detail-page load. No args.
- **Field event** (Type: Field Event): `onBeforeUpdate(value)` — **Interceptor**. Fires on tick-icon click after inline field edit. `value` = current value. `return false` cancels the save. (Source does not enumerate supported field types here.)
- **Blueprint event** (Type: Blueprint Event): `beforeTransition(transition)` — **Interceptor**. `transition` = `{ ID, name }` of the current transition. `return false` cancels the blueprint transition.
- **Mandatory Fields Form events** (Type: Mandatory Fields Form Event):
· `onBeforeMandatoryFormSave(form)` — **Interceptor**. `form` = values of all fields in the mandatory form + current value of the base field. `return false` cancels.
· `onMandatoryFormLoad(form)` — fires when the mandatory-form pop-up opens. `form` = same shape.
- **Tag event** (Type: Tag Event): `onTagChanged(tag, added, removed)` — **three** args: `tag` = list of tags; `added` = list of tags added; `removed` = list of tags removed. Fires on Save of the Add-Tags dialog.
- **Subform events** (Type: Subform Event):
· `onRowAdd(row_data, index)`, `onCellChange(field_name, value, index)`, `onRowDelete(row_data, index)` — non-interceptor; signatures identical to create/edit/clone.
· `beforeRowDelete(row_data, index)` — **Interceptor**. `return false` cancels deletion.
· `beforeRowUpdate(row_data, index)` — **Interceptor**. Fires after tick-icon click during inline row update. `return false` cancels.
- **Button event** (Type: Button Event): `onClick(context)` — `context` is **shape-variant**:
· In Record placement → `{ Module API Name, ID }`.
· Related List placement → `{ Module API Name of the related list, ID, selected record ID }`.
- **Notes events** (Type: Notes Related List — no "Event" suffix; Related List Notes only):
· `onBeforeAdd(notes)` — **Interceptor**. `notes` = `{ title, content }` of the note about to be added. `return false` blocks the add.
· `onBeforeUpdate(notes)` — **Interceptor**. `notes` = `{ title, content }` of the note about to be updated. `return false` blocks the update.
UI_ONLY_ACTIONS:
- Custom Button placement (Record top-bar vs. Related List) is chosen at button creation: `Setup → Customization → Modules and Fields → <Module> → Links and Buttons → Buttons → Placement`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **Field `setValue` silently no-op on Detail Page** — direct field writes do nothing; only Mandatory-Form fields support `setValue`. Use Web-API Update Record + `$Client.refresh()`. (detail-page-setvalue)
- **Tag event arg shape differs across surfaces** — Detail Standard = `(tag, added, removed)` 3-arg; Detail Canvas = `(tags_added, tags_removed)` 2-arg. Scripts ported across surfaces hit `undefined` args. (cross-surface-arg-shape)
- **Mandatory-Form `onBeforeMandatoryFormSave` arg shape differs across surfaces** — Detail Standard = `form` (full form values + base); Detail Canvas = `value`. (cross-surface-arg-shape)
- **Button `context` shape varies by placement** — assuming the In-Record shape on a Related-List button silently reads `undefined` for the selected record ID. (button-context-placement)
- **`beforeRowUpdate` exists only on Detail-Page subforms** — not on Create/Edit/Clone subforms; cross-surface scripts hit silent no-listener.
- `beforeRowDelete` / `beforeRowUpdate` are **camelCase**; legacy `client-script-events.html` uses PascalCase (`BeforeRowDelete` / `BeforeRowUpdate`). Treat event-dictionary as authoritative.
- `beforeTransition` only blocks the transition — it does **not** revert state changes already made by other listeners; chain ordering matters. (listener-order)
- Blueprint `afterTransition` is **absent** here but present in the mobile Detail-Standard matrix; cross-surface scripts that key on `afterTransition` fire on mobile only. (cross-surface-blueprint)
- `onTagChanged` fires only on Save of the Add-Tags dialog, not on row-level inline tag edits.
assets/clientscript/event-dictionary/eventdict-list-canvas.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on List Page (Canvas) — canonical wire signature including canvas-element per-record click hooks.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_list_page_canvas
TRIGGER_KEYWORDS: onLoad, onBeforeCustomViewChange, onClick, Canvas Button, Icon, Text, custom_view, context
TECHNICAL_CONSTRAINTS:
- Page-type scope — List Page (Canvas layout). For List Page (Standard), see eventdict-list-standard.
- Interceptor event — Page `onBeforeCustomViewChange`. `return false` blocks the view-change.
- Canvas-element events here **do take a `context` arg** (unlike Canvas Create / Detail surfaces where canvas-element `onClick` takes no args) — each click returns the record ID of the canvas row clicked.
- Custom Button binding configured only from the Buttons page.
REQUIRED_PARAMETERS:
- **Page events** (Type: Page Event):
· `onLoad()` — fires when the List Page (Canvas) loads. No args.
· `onBeforeCustomViewChange(custom_view)` — **Interceptor**. Fires before custom-view change. `custom_view` = `{ ID, name }`. `return false` cancels.
- **Canvas Button event** (Type: Canvas Button Event): `Canvas Button onClick(context)` — `context` = `{ Record ID, Module API name, ID }`.
- **Icon event** (Type: Icon Event): `Icon onClick(context)` — `context` = `{ Record ID, Module API name, ID }`.
- **Text event** (Type: Text Event): `Text onClick(context)` — `context` = `{ Record ID, Module API name, ID }`.
- **Button event** (Type: Button Event): `onClick()` — fires on Client-Script-configured custom button click. **No `context` arg listed on this surface** (contrast with List-Standard which exposes 3 placement-variant contexts).
UI_ONLY_ACTIONS:
- Canvas list-row design (which buttons / icons / text appear per row): `Setup → Customization → Canvas → <list canvas> → Row Design`. Note: No API equivalent — UI-only.
- Custom Button (top-bar) placement on Canvas list: `Setup → Customization → Modules and Fields → <Module> → Links and Buttons → Buttons`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **Canvas-element `onClick` has `context` HERE but no `context` on Detail / Create canvas surfaces** — cross-surface scripts that read `context.record_id` work on List Canvas but read `undefined` on Detail / Create Canvas. (cross-surface-arg-shape)
- **Button `onClick` takes no `context` on List Canvas** per this source (contrast with List Standard's 3 placement-variants: Util Menu, Each Record, Mass Action Menu). Scripts that need selected-record IDs from a Mass Action button on List Canvas have no documented event signature.
- **No Field / Subform / Mandatory Form / Blueprint / Tag / Notes events** — List Canvas is read-only at the field level; record-level edits must navigate to Detail / Edit.
- **No record-selection event** — there is no `onRecordSelect` or `onSort`. Per-row interactions must hook the per-row canvas-element clicks.
- **`onBeforeCustomViewChange` blocks only the view-switch** — does not block URL navigation away from the page.
- **List Canvas is not in the mobile-supported page matrix** (`../clientscript-mobile.dou.md` lists no List Canvas row) — scripts silently no-op on mobile. (mobile-listpage-absent)
- **No Page `onChange` / `onSave` events** — only `onLoad` and the view-change interceptor.
assets/clientscript/event-dictionary/eventdict-list-standard.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on List Page (Standard) — canonical wire signature for custom-view and list-button hooks.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_list
TRIGGER_KEYWORDS: onCustomViewLoad, onBeforeCustomViewChange, onClick, custom_view, Util Menu, Mass Action
TECHNICAL_CONSTRAINTS:
- Page-type scope — List Page (Standard layout). For List Page (Canvas), see eventdict-list-canvas.
- Only two Page events exist — view-load and view-change. There is **no** Page `onLoad`, `onChange`, `onSave`, or `onType` on List Page (Standard).
- Interceptor event — `onBeforeCustomViewChange`. `return false` blocks the view-change.
- Custom Button binding — configured only from the Buttons page; existing scripts appear on the Client Script setup list after creation.
REQUIRED_PARAMETERS:
- **Page events** (Type: Page Event):
· `onCustomViewLoad(custom_view)` — fires when a custom view loads. `custom_view` = `{ ID, name }` of the current custom view.
· `onBeforeCustomViewChange(custom_view)` — **Interceptor**. Fires before the user changes the current custom view. `custom_view` = `{ ID, name }`. `return false` cancels the change.
- **Button event** (Type: Button Event): `onClick(context)` — `context` is **shape-variant by placement**:
· Util Menu placement → `{ Module API Name, ID }`.
· Each Record placement → `{ Module API Name, ID, record ID }`.
· Mass Action Menu placement → `{ Module API Name, ID, selected record IDs (array) }`.
UI_ONLY_ACTIONS:
- Custom Button placement (Util Menu vs. Each Record vs. Mass Action Menu): chosen at button creation under `Setup → Customization → Modules and Fields → <Module> → Links and Buttons → Buttons → Placement`. Note: No API equivalent — UI-only.
- Custom-view creation (the target of `onCustomViewLoad`): `Setup → ... → <Module> → List → Create View`. Note: No API equivalent for the script-binding side — UI-only.
CRITICAL_FAILURE_MODES:
- **Button `context` shape varies by placement** (3 variants) — code that assumes `context.record_id` (Each Record shape) on a Util Menu button hits `undefined`; code that assumes a single `record_id` on a Mass Action button misses the array. (button-context-placement)
- **No record-level events on List Page (Standard)** — there is no `onRowClick`, `onRecordSelect`, or `onSort` event. Such logic must live in a custom button or in row-level styling via `ZDK.Page.getList().style(...)` invoked in `onCustomViewLoad`.
- **No `onLoad` Page event** — initial-page-load logic must hook `onCustomViewLoad`, which fires per custom-view switch (including initial default-view load).
- **`onBeforeCustomViewChange` blocks only the view-switch** — it does not block other navigation away from the list page; URL navigation away triggers no event.
- **Custom-view delete / rename is not hooked** — `onBeforeCustomViewChange` does not fire on view metadata changes, only on which view is currently active.
- **List-Page scripts are silently absent on mobile** — see `../clientscript-mobile.dou.md`. (mobile-listpage-absent)
- **Mass-Action selected-IDs array can be large** — combined with the 10 s execution cap, iterating many selected records via Web API risks timeout. (timeout)
assets/clientscript/event-dictionary/eventdict-quick-create.dou.md
FILE_PURPOSE: Read when picking event names and argument signatures for a Client Script on Quick Create Popup — canonical wire signature for the lean inline-create surface.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/event-dictionary/module_quick_create
TRIGGER_KEYWORDS: onLoad, onChange, onSave, onType, quick_create, popup, Interceptor
TECHNICAL_CONSTRAINTS:
- Page-type scope — Quick Create Popup (the inline create dialog launched from list pages, related lists, lookup fields, etc.).
- Only Page and Field event families exist — **no** Subform, Button, Blueprint, Mandatory Form, Tag, or Notes events on Quick Create.
- Interceptor event — Page `onSave`. `return false` blocks the record-save.
REQUIRED_PARAMETERS:
- **Page events** (Type: Page Event):
· `onLoad()` — fires when the Quick Create popup loads. No args.
· `onChange(field_name)` — `field_name` = API name of the field that was changed.
· `onSave()` — **Interceptor**. Fires after Save / Save and New click, before persistence. `return false` cancels save.
- **Field events** (Type: Field Event):
· `onChange(value)` — `value` = current value in the field. Fires on blur / focus-out after edit.
· `onType(value)` — `value` = current value. Fires per keystroke.
- Source does **not** enumerate Quick-Create-specific supported field types — assume the parent module's field set unless verified otherwise.
UI_ONLY_ACTIONS:
- Configure Quick Create layout (which fields appear in the popup): `Setup → Customization → Modules and Fields → <Module> → Layouts → <layout> → Quick Create View`. Note: No API equivalent — UI-only.
CRITICAL_FAILURE_MODES:
- **SOURCE_CONFLICT — FAQ #11 says Quick Create is unsupported** (`../clientscript-faqs.dou.md`), but this event-dictionary page exists and enumerates 5 events. Treat the event-dictionary as authoritative; FAQ entry is stale.
- **No Subform events** — Quick Create cannot intercept row add / delete / cell change even when the underlying layout includes subforms. (subform-event-absent)
- **No Custom Button events** — Quick Create does not surface custom buttons; `onClick` listeners do not fire.
- **No Blueprint / Mandatory Form events** — Quick Create saves bypass blueprint-state hooks and mandatory-form pop-ups that fire on full Detail / Create pages. (cross-surface-gap)
- **`onSave` `return false` cancels save only synchronously** — async return values do not halt the save.
- **Field-type coverage not stated in source** — Multi-select Picklist / Lookup behaviour on Quick Create not verified; test before relying.
- **Per-layout binding** — Quick Create script bound to one layout does not fire on Quick Create of other layouts. (layout-binding)
- **Mobile silent absence** — Quick Create is not in the mobile-supported page list (`../clientscript-mobile.dou.md`); Quick Create scripts no-op on mobile.
assets/clientscript/web-apis/.gitkeep
assets/clientscript/web-apis/clientscript-webapi-connections.dou.md
FILE_PURPOSE: Read when egressing to an external HTTP service (or a Zoho CRM REST endpoint) from a ClientScript via a pre-configured named Connection using `ZDK.Apps.CRM.Connections.invoke`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/webapi_v2/Connections
TRIGGER_KEYWORDS: ZDK.Apps.CRM.Connections.invoke, connection_name, param_type, request parameter, request body, GET, POST, DELETE, CRMResponses
TECHNICAL_CONSTRAINTS:
- Surface is `ZDK.Apps.CRM.Connections` — single method `invoke` that routes an HTTP request through a named Connection (auth + endpoint allow-list owned by the Connection).
- The Connection (`connection_name`) must be pre-created under Setup → Developer Hub → Connections and must list the target endpoint's host in its allowed-domains / scope set; ClientScript cannot create Connections.
- `param_type` is numeric: `1` → send `parameters` as URL query parameters; `2` → send `parameters` as request body / payload. The two are mutually exclusive; pass empty `{}` for the unused slot rather than omitting the arg.
- Source samples demonstrate three HTTP methods (`GET`, `POST`, `DELETE`); the `method` arg is a free string forwarded to the underlying HTTP layer.
- Returns `CRMResponses` synchronously — response body shape depends on the upstream endpoint, not the helper.
- Source samples target both Zoho CRM endpoints (`https://www.zohoapis.com/crm/v2/Leads`) and external services (`https://mailchimp.api/sample_api`); choice is encoded in the Connection.
- DC base host varies per data centre (`zohoapis.com` / `.eu` / `.in` / `.com.au` / `.jp` / `.com.cn`) — ensure the `url` host matches the Connection's data-centre binding.
- Source page lists exactly one method on this surface (`invoke`); no helpers for listing Connections or rotating tokens are exposed here.
- Sandbox Connections and production Connections have distinct names — republish per environment, or fetch the active Connection name at runtime.
- The helper does not surface a separate timeout knob — timeout behaviour is inherited from the upstream endpoint and the Connection layer.
REQUIRED_PARAMETERS:
- Source samples (verbatim) live at SOURCE_DOC — three samples cover `GET` with `param_type=1`, `POST` with `param_type=2`, `DELETE` with `param_type=2`.
- `invoke(connection_name: String, url: String, method: String, param_type: number, parameters: object, headers: object)` — six positional args.
- `connection_name` — Connection API name (e.g. `"cscript"`, `"mailchimp4"`); required.
- `url` — full request URL (scheme + host + path).
- `url` host must match a domain authorized by the Connection.
- `method` — HTTP method string (`"GET"`, `"POST"`, `"DELETE"` shown in source; other methods follow the same forwarding contract).
- `param_type` — numeric mode selector.
- `param_type = 1` → `parameters` serialized as URL query parameters.
- `param_type = 2` → `parameters` serialized as request body / payload.
- `parameters` — object; for `GET` sample: `parameters.ids = "347706269003,34770129002"`.
- `parameters` — for `POST` sample: `{ "data": [{ "Last_Name": "CScript", "Company": "DOTSDK" }] }`.
- Pass `parameters` as `{}` when none are needed (source instructs to pass empty json rather than omit).
- `headers` — object of HTTP request headers.
- Pass `headers` as `{}` when none are needed.
- Auth headers are owned by the Connection — do not pass `Authorization` in `headers`.
- Returns `CRMResponses` — inspect `.status` / `.code` / `.message` for failure detection.
UI_ONLY_ACTIONS:
- Setup → Developer Hub → Connections → +Create your First Connection → Pick Service or Custom Service → scopes + Client ID/Secret → Create and Connect → authorize popup → copy Connection Name. Note: Connection Name is the exact string passed as `connection_name` here; there is no API to create Connections.
CRITICAL_FAILURE_MODES:
- `connection_name` mistyped or referring to a Connection scoped to a different sandbox / production environment → invocation fails with connection-not-found.
- Sandbox-vs-production drift is a recurring failure source.
- `param_type=1` against an endpoint that requires a body → endpoint returns 4xx with field-not-found / bad-request; helper does not validate against the endpoint.
- `param_type=2` against an endpoint that requires query params → endpoint returns 4xx with missing-param error.
- `param_type=2` (body) used with `method="GET"` → some upstreams reject GET-with-body silently and return an empty / 200 with no data; treat as protocol error.
- Calling `invoke` against a URL whose host is **not** in the Connection's allowed-domains list → Connection layer rejects the call with permission / scope error before egress; no upstream call is fired.
- Passing `Authorization` (or any auth header) inside `headers` → helper either overrides with the Connection's auth or rejects; redundant and may leak intent. Let the Connection own auth.
- Connection's OAuth scope set narrower than the upstream call needs → upstream returns `OAUTH_SCOPE_MISMATCH`; re-authorize Connection with the broader scope.
- Connection's stored refresh token revoked at the upstream provider → `invoke` returns auth-failed; re-authorize via the Setup UI flow.
- Source does not document a per-call rate limit, payload-size cap, or per-Connection daily quota for the helper.
- Helper limits are inherited from the upstream endpoint and the org's Connection quota.
- Source does not enumerate error codes for the wrapper.
- Surface failures by reading `CRMResponses.status` / `.code` / `.message` and any upstream-specific error body inside the response.
assets/clientscript/web-apis/clientscript-webapi-functions.dou.md
FILE_PURPOSE: Read when invoking a server-side Deluge Custom Function by name from a ClientScript via `ZDK.Apps.CRM.Functions.execute` with optional URL parameters and optional body map.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/webapi_v2/Functions
TRIGGER_KEYWORDS: ZDK.Apps.CRM.Functions.execute, function_name, parameters, functions_body, Custom Functions, CRMResponses
TECHNICAL_CONSTRAINTS:
- Surface is `ZDK.Apps.CRM.Functions` — single method `execute` for invoking a saved Custom Function by name.
- Returns `CRMResponses` synchronously from the script's perspective.
- Source signature does not wrap the return in a Promise.
- The target Function must exist server-side under `Setup → Developer Hub → Functions`.
- `function_name` is the Function's API name (e.g. `"dealtoquote2"`), not its display label.
- Source does not state whether the target Function requires its REST API toggle to be on for ZDK invocation.
- Source does not enumerate OAuth scope, edition gating, or rate-limit caps for this helper.
- Source does not state execution-timeout for the helper; bound to the underlying Function's own runtime limits.
- Both `parameters` and `functions_body` are JSON objects, both optional.
- Sample 1 omits both optional args; Sample 2 supplies only `parameters`; Sample 3 supplies both.
- The two optional args are positional — to pass only `functions_body`, pass an explicit `{}` in the `parameters` slot.
- Source page lists exactly one method on this surface (`execute`); no helpers for listing Functions or fetching Function metadata are exposed here.
- Companion ZRC `ZDK.Client.invoke` / `zoho.crm.invokeConnector` / `ZOHO.CRM.FUNCTIONS.execute` (Widget) cover the same primitive on other surfaces — do not mix surfaces in one DOU.
- The Function runs as the user under whose ClientScript session this `execute` call originated — record visibility, territory filtering, and HIPAA redaction follow that user's profile.
- The helper does not surface a separate timeout knob; the script-side call blocks until the underlying Function returns or times out server-side.
- The helper does not surface a `trigger` map — any write performed inside the Function fires the full automation stack unless the Deluge body suppresses it via `optionalMap.trigger` on its own writes.
REQUIRED_PARAMETERS:
- Source samples (verbatim) live at SOURCE_DOC — three samples cover (1) no-arg, (2) `parameters` only, (3) `parameters` + `functions_body`.
- `execute(function_name: String, parameters?: object, functions_body?: object)` — three-arg signature.
- `function_name` — required; API name of the target Custom Function (e.g. `"dealtoquote2"`).
- `parameters` — optional; JSON map of URL / query parameters surfaced to the Function as named args.
- `parameters` sample shape: `{ "page": 1, "per_page": 10 }`.
- `functions_body` — optional; JSON map sent as the request body.
- `functions_body` sample shape: `{ "amount": 100, "name": "test" }`.
- Returns `CRMResponses`.
- Source does not document the `CRMResponses` schema for this method in detail; inspect `.status` / `.code` / `.message`.
UI_ONLY_ACTIONS:
- Setup → Developer Hub → Functions → New Function → author Deluge → assign API name (the `function_name` token passed here) → Save → Active. Note: the Function authoring lifecycle itself is UI-only and out of scope of this page; this DOU only covers the ClientScript-side invocation contract.
CRITICAL_FAILURE_MODES:
- `function_name` passed as the display label (`"Deal To Quote"`) instead of the API name (`"dealtoquote2"`) → server returns function-not-found — `label-vs-api-name`.
- Target Function does not exist (mistyped name) → invocation fails server-side; surfaced in `CRMResponses`.
- Target Function not yet activated → invocation fails server-side.
- Target Function scoped to a different sandbox / production environment → invocation fails; sandbox-vs-production naming drift is a recurrent failure source.
- `parameters` keys not matching the target Function's declared argument names → Function receives those parameters as null / undefined.
- `parameters` key mismatch → silent over-broad behaviour rather than hard error, mirroring the Native Query `:placeholder` mismatch class.
- Function recursion: a Function invoked here that updates the record currently displayed in the ClientScript host page can re-trigger workflows that loop until the daily-credit cap — `automation-interaction`.
- Function exceeds its server-side execution timeout → returns `EXECUTION_TIMEOUT` from the underlying runtime, surfaced into `CRMResponses`.
- Source does not state execution-timeout on this page — consult the Functions runtime doc for the per-edition limit.
- Daily Function-credit exhaustion → 429 returned silently into the ClientScript handler with no UI alert.
- Source does not document error codes for the ZDK wrapper specifically.
- Failures bubble from the underlying Function runtime; treat any non-success `status` in `CRMResponses` as failure and branch on `.code` / `.message`.
- Source page does not document how the wrapper serializes `parameters` when both `parameters` and `functions_body` carry overlapping keys — keep key sets disjoint to avoid ambiguity.
assets/clientscript/web-apis/clientscript-webapi-modules.dou.md
FILE_PURPOSE: Read when calling `ZDK.Apps.CRM.<Module>.*` from a ClientScript to fetch, create, update, upsert, delete, search, convert, tag, mail, or approve module records.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/webapi_v2/Modules
TRIGGER_KEYWORDS: ZDK.Apps.CRM.Leads.fetch, ZDK.Apps.CRM.<Module>.create, fetchById, upsert, deleteByIds, searchByCriteria, fetchRelatedRecords, convert, approveRecord, sendMail, addTagsToRecords
TECHNICAL_CONSTRAINTS:
- Surface is the per-module facade exposed at `ZDK.Apps.CRM.<Module_API_Name>` inside a ClientScript runtime; module token must be the module API name (`Leads`, `Contacts`, `Invoices`, `Price_Books`, `Events`, `Notes`, custom-module API name) — never the display label.
- Each method wraps a documented Zoho CRM v2 REST endpoint — `Reference API` links from source point to `zoho.com/crm/developer/docs/api/v2/{get-records,insert-records,update-records,upsert-records,delete-records,get-deleted-records,convert-lead,search-records,get-related-records,delink,update-related-records,blueprint-details,add-tags,remove-tags,tag-record-count}.html` and `v2.1/send-mail.html`.
- Calls are synchronous from script perspective — return value is the response array / object directly (e.g. `var leads = ZDK.Apps.CRM.Leads.fetch();`), not a Promise.
- Record creation requires `new ZDK.Apps.CRM.<Module>.Models.<Module>()`; picklist fields must be assigned from `ZDK.Apps.CRM.<Module>.Models.<Field>Picklist.<MEMBER>` (e.g. `Lead_StatusPicklist.CONTACTED`, `StagePicklist.CLOSED_WON`, `Pricing_ModelPicklist.FLAT`).
- Inventory modules require nested `Line_Items` built from `ZDK.Apps.CRM.<Module>.Models.Line_Items`; `Price_Books` requires nested `Pricing_Details`; `Events` requires nested `Participants`; `Notes` requires `Parent_Id_Lookup_Id` + `$se_module`.
- `getFeatureInfo` hard caps documented in source: max 100 record ids per call, max 5 feature names per call.
- Source does not enumerate OAuth scopes, edition gating, rate limits, or HTTP error codes for these helpers — bound to the underlying v2 REST endpoint's scope and quota; consult the linked `Reference API` page per method.
REQUIRED_PARAMETERS:
- `fetch(page?, per_page?, sort_by?, sort_order?)` — all optional; `sort_by` is field API name; `sort_order` ∈ `"asc" | "desc"`. Returns array.
- `fetchById(id: String)` — returns single record object of module type.
- `create(arrayOfObjects | singleObject)` — payload built via `Models.<Module>` constructor; returns array.
- `update(arrayOfObjects | singleObject)` — each object must carry `id`; built by mutating a `fetchById` result.
- `updateById(arrayOfObjects | singleObject, record_id: String)` — explicit id form; returns `CRMResponses`.
- `upsert(arrayOfObjects | singleObject)` — duplicate match driven by module's configured duplicate-check fields.
- `deleteByIds(ids: Array<String>)` — bulk delete; returns array.
- `fetchDeletedRecords()` — returns deleted-records array.
- `convert(convertLeads: ConvertLeads, id: String)` — Leads only; `ConvertLeads` model carries optional `Accounts` (id string) + nested `Deals` object built from `ZDK.Apps.CRM.Deals.Models.Deals`.
- `searchByCriteria(criteria: String)` — criteria expression per CRM search-records grammar, URL-percent-encoded (commas as `%5C%2C`).
- `searchByEmail(email: String)` · `searchByPhone(phone: String)` · `searchByWord(word: String)` — single-string queries.
- `fetchRelatedRecords(id: String, related_list_api_name: String)` — `related_list_api_name` is the related-list API name (e.g. `"Notes"`).
- `delinkRelatedRecords(id, related_list_api_name, array_of_related_record_ids: Array<String>)` and singular `delinkRelatedRecord(id, related_list_api_name, related_record_id: String)`.
- `updateRelatedRecords(related_record_object: Related_Records, record_id, related_list_api_name, related_record_id)` — mutate a `fetchRelatedRecords` element then pass it back.
- `fetchBlueprint(record_id: String)` — returns `Blueprint` object.
- `addTagsToRecord(record_id, array_of_tag_names: Array<String>)` and bulk `addTagsToRecords(array_of_tag_names, record_ids: Array<String>)`; mirror methods `removeTagsToRecord` / `removeTagsToRecords`.
- `sendMail(mail_object: Mail, record_id: Array<String>)` — `mail_object` built from `new ZDK.Apps.CRM.__Actions.Models.Mail()`; required fields `from {user_name,email}`, `to [{user_name,email}]`, `subject`, `content`, `mail_format` (e.g. `"text"`).
- `approveRecord(record_id: String, action: String, comment: String)` — `action` ∈ `"approve" | "delegate" | "reject"`; response carries `.status`.
- `getFeatureInfo(ids: Array<String>, features: Array<String>)` — `features` from `{blocked_emails, consecutive_negative_emails}`; response `[0]._features_info` is the per-record feature array.
- Source-sample payloads (verbatim from source) for the non-trivial shapes:
Invoices `create` (Line_Items via `product()` + `quantity`):
```javascript
var invoice = new ZDK.Apps.CRM.Invoices.Models.Invoices();
invoice.Subject = "testing2";
invoice.Account_Name_Lookup_Id = "2000000136022";
var line_item = new ZDK.Apps.CRM.Invoices.Models.Line_Items();
var product = ZDK.Apps.CRM.Products.fetchById("2000000136027");
line_item.product(product);
line_item.quantity = 3.0;
invoice.Product_Details = [line_item];
var response = ZDK.Apps.CRM.Invoices.create(invoice);
```
Price_Books `create` (`Pricing_Details` rows: `from_range` / `to_range` / `discount`):
```javascript
var book = new ZDK.Apps.CRM.Price_Books.Models.Price_Books();
book.Price_Book_Name = "test";
book.Pricing_Model = ZDK.Apps.CRM.Price_Books.Models.Pricing_ModelPicklist.FLAT;
var pricing_detail = new ZDK.Apps.CRM.Price_Books.Models.Pricing_Details();
pricing_detail.from_range = 10;
pricing_detail.to_range = 100;
pricing_detail.discount = 5;
book.Pricing_Details = [pricing_detail];
var response = ZDK.Apps.CRM.Price_Books.create(book);
```
Events `create` (`Event_Title` / `Start_DateTime` / `End_DateTime` / `Participants[].type`):
```javascript
var my_event = new ZDK.Apps.CRM.Events.Models.Events();
my_event.Event_Title = "testing";
my_event.Start_DateTime = "2020-10-22T21:00:00+05:45";
my_event.End_DateTime = "2020-10-24T21:00:00+06:45";
my_event.Currency = "USD";
var participant = new ZDK.Apps.CRM.Events.Models.Participants();
participant.participant = "423383000004997180";
participant.type = "lead";
my_event.Participants = [participant];
var response = ZDK.Apps.CRM.Events.create(my_event);
```
Notes `create` (`Note_Content` + `Parent_Id_Lookup_Id` + `$se_module`):
```javascript
var note = new ZDK.Apps.CRM.Notes.Models.Notes();
note.Note_Content = "create test";
note.Parent_Id_Lookup_Id = "2000000069181";
note.$se_module = "Leads";
var response = ZDK.Apps.CRM.Notes.create(note);
```
Leads `convert` (`ConvertLeads.Accounts` + nested `Deals.{Deal_Name, Closing_Date, Stage, Amount}`):
```javascript
var convertLead = new ZDK.Apps.CRM.Leads.Models.ConvertLeads();
convertLead.Accounts = "4312588000000296007";
var deal = new ZDK.Apps.CRM.Deals.Models.Deals();
deal.Deal_Name = "Convert";
deal.Closing_Date = "2020-11-20";
deal.Stage = ZDK.Apps.CRM.Deals.Models.StagePicklist.CLOSED_WON;
deal.Amount = 56.6;
convertLead.Deals = deal;
var response = ZDK.Apps.CRM.Leads.convert(convertLead, "4312588000000846005");
```
`sendMail` (`Mail` model shape):
```javascript
var mail = new ZDK.Apps.CRM.__Actions.Models.Mail();
mail.from = { "user_name": "james", "email": "james.cooper@zoho.com" };
mail.to = [{ "user_name": "john", "email": "john.carlton@zoho.com" }];
mail.subject = "Mail subject";
mail.content = "Content of the mail";
mail.mail_format = "text";
var response = ZDK.Apps.CRM.Leads.sendMail(mail, "423383000000015966");
```
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- Module token passed as display label (`"Lead"`, `"Price Books"`) instead of API name (`Leads`, `Price_Books`) → `ZDK.Apps.CRM.<label>` is undefined; `TypeError` at script load — `label-vs-api-name`.
- Field assignment using display label key (`record["Last Name"]`) instead of API-name property (`record.Last_Name`) → field silently unset on `create` / `update`; record persists with field empty — `label-vs-api-name`.
- Picklist field assigned a raw display string (`lead.Lead_Status = "Contacted"`) instead of the model enum (`Models.Lead_StatusPicklist.CONTACTED`) → CRM rejects with picklist-value-not-allowed at server side.
- `update(obj)` payload missing `id` (object not seeded from `fetchById`) → API rejects update; the helpers do not auto-upsert on missing id.
- `upsert` invoked against a module with no duplicate-check fields configured → every call inserts a new record instead of matching.
- `searchByCriteria` query containing unescaped `,`, `(`, `)`, `:` inside a value → criteria parser returns empty array silently; escape per CRM search grammar (commas as `%5C%2C`).
- `searchByEmail` / `searchByPhone` / `searchByWord` on records where the searched field is null → records skipped silently; no warning.
- `fetch()` consumer not iterating `page` until response is empty → silently truncated at default page (1, server-side `per_page` cap). Source does not state the cap.
- `convert(convertLead, id)` invoked against a Lead already converted → API returns conversion-not-allowed error; pre-check `Converted` state.
- `delinkRelatedRecords` / `updateRelatedRecords` with `related_list_api_name` mismatch (`"Deals"` vs module-specific `"Potentials"`) → silently no-ops; resolve the related-list API name via [clientscript-webapi-settings.dou.md](./clientscript-webapi-settings.dou.md) `fetchRelatedLists`.
- `sendMail` without `mail_format` → API rejects; without `from`/`to` entries → rejects with malformed-mail payload.
- `approveRecord` called on a record not in an approval queue for the script's invoker user → returns approval-not-pending error; pre-check via [clientscript-webapi-settings.dou.md](./clientscript-webapi-settings.dou.md) `fetchApprovalProcess`.
- `getFeatureInfo` invoked with > 100 record ids or > 5 features → source declares hard cap; exceeding it returns request-too-large error.
- Source does not surface a `trigger` map / automation-suppression knob — every `create` / `update` / `upsert` / `delete` / `convert` here fires the full workflow / blueprint / approval / scoring / orchestration stack; this surface cannot suppress automation — `automation-interaction`.
- Source does not enumerate error codes; surface failures by inspecting the returned `CRMResponses` `status` / `code` / `message` and the linked `Reference API` doc for each method.
assets/clientscript/web-apis/clientscript-webapi-org.dou.md
FILE_PURPOSE: Read when fetching org metadata, enabling multi-currency, or managing currency catalogue (home + active currencies) from a ClientScript via `ZDK.Apps.CRM.Org`.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/webapi_v2/Org
TRIGGER_KEYWORDS: ZDK.Apps.CRM.Org.fetch, enableMultiCurrency, updateHomeCurrency, fetchCurrencies, fetchCurrencyById, createCurrencies, updateCurrencies, updateCurrencyById, Models.Currencies
TECHNICAL_CONSTRAINTS:
- Surface is `ZDK.Apps.CRM.Org` — single facade for org-level reads and the currency catalogue.
- `fetch` wraps `zoho.com/crm/developer/docs/api/v2/get-org-data.html`.
- `enableMultiCurrency` wraps `enable-multi-currency.html`.
- `updateHomeCurrency` wraps `update-home-currency.html`.
- `fetchCurrencies` / `fetchCurrencyById` wrap `get-currencies-data.html`.
- `createCurrencies` wraps `add-currencies.html`.
- `updateCurrencies` / `updateCurrencyById` wrap `update-currencies.html`.
- Calls are synchronous from script perspective — return value is the `Org` / `Currencies` object or array directly.
- `Currencies` model built via `new ZDK.Apps.CRM.Org.Models.Currencies()`.
- `Currencies` scalar fields: `prefix_symbol` (boolean), `name`, `iso_code`, `symbol`, `exchange_rate` (string).
- `Currencies.format` is a map: `decimal_separator`, `thousand_separator`, `decimal_places` (stringified number per sample).
- `enableMultiCurrency` is a one-time-per-org operation server-side.
- Once multi-currency is enabled, the home-currency mutation surface is `updateHomeCurrency`.
- Additional active currencies after enable are added via `createCurrencies`.
- Source does not enumerate OAuth scope, admin-privilege gating, edition gating, or rate-limit caps for these helpers.
REQUIRED_PARAMETERS:
- Source samples (verbatim) live at SOURCE_DOC under each method heading; re-fetch when authoring a `Currencies` payload or the `format` map.
- `fetch()` — no args; returns single `Org` object.
- `enableMultiCurrency(Currency: Currencies)` — returns `CRMResponses`.
- `enableMultiCurrency` required `Currencies` fields per sample: `prefix_symbol` (boolean), `name`, `iso_code`, `symbol`, `exchange_rate`, `format` map.
- `updateHomeCurrency(Currency: Currencies)` — returns `CRMResponses`.
- `updateHomeCurrency` payload must carry `id` of the existing home currency (seed from `fetchCurrencyById`).
- `fetchCurrencies()` — returns array of `Currencies`.
- `fetchCurrencyById(id: String)` — returns single `Currencies` instance.
- `createCurrencies(arrayOfObjects: Array<Currencies>)` — array form required; returns array.
- `createCurrencies` element fields per sample: `name`, `iso_code`, `symbol`, `exchange_rate`, `format`, `prefix_symbol`.
- `updateCurrencies()` — source signature has no formal arg list; sample passes `[currency]` array of mutated `fetchCurrencyById` results. Returns array.
- `updateCurrencies` elements must carry `id`.
- `updateCurrencyById(Currency: Currencies, currency_id: String)` — explicit id form; returns `CRMResponses`.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- `enableMultiCurrency` invoked on an org that already has multi-currency enabled → server-side rejects (already-enabled).
- Guard `enableMultiCurrency` with a `fetch()` check on the org's `multi_currency` flag before calling.
- `updateHomeCurrency` payload lacking `id` of the existing home currency → API returns invalid-id; do not pass a fresh `Models.Currencies()`.
- If the helper validates field names strictly, a typoed prefix-symbol key is silently dropped and the CRM-side prefix toggle does not change.
- `createCurrencies` called with a single object (non-array) → signature expects `Array<Currencies>`; helper may reject or silently coerce. Wrap in `[…]`.
- `format.decimal_places` provided as integer instead of stringified number → CRM rejects with type-mismatch on some clusters; pass `"2"` per source sample.
- ClientScript invoker without org-admin privileges → org / currency mutation calls return permission-denied server-side even though the surface is exposed.
- Source does not document a `trigger` knob — org / currency mutations fire any configured downstream automation (e.g. currency-change workflows on Deals) without suppression — `automation-interaction`.
- Source does not enumerate error codes.
- Inspect returned response object's `.status` / `.code` / `.message` and consult the linked `Reference API` endpoint per method.
assets/clientscript/web-apis/clientscript-webapi-records.dou.md
FILE_PURPOSE: Read when mutating a single record instance via its `__`-prefixed instance methods (`record.__update`, `__remove`, `__addTags`, `__sendMail`, …) instead of the module-level facade.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/webapi_v2/Records
TRIGGER_KEYWORDS: __update, __create, __remove, __convert, __addTags, __removeTags, __fetchRelatedRecords, __delinkRelatedRecords, __updateRelatedRecords, __fetchBlueprint, __approveRecord, __sendMail, __getFeatureSpecificInfo, Owner
TECHNICAL_CONSTRAINTS:
- Instance-method surface lives on every record object returned by `ZDK.Apps.CRM.<Module>.fetchById(...)` / `.fetch(...)`.
- Also on every newly-constructed `new ZDK.Apps.CRM.<Module>.Models.<Module>()` instance.
- Methods are double-underscore-prefixed (`__create`, `__update`, `__remove`, etc.) to namespace them off the field accessors.
- Method semantics mirror the module-facade method one-for-one, but operate on the instance's own `id`.
- No separate `record_id` parameter required for `__update`, `__remove`, `__fetchRelatedRecords`, `__fetchBlueprint`, `__sendMail`.
- Lookup fields expose two access modes — scalar `<Lookup>_Lookup_Id` and method `<Lookup>(userObject?)`.
- Calling the lookup method with no arg fires an API call to hydrate the lookup object.
- Calling the lookup method with a model object reassigns the lookup in memory; persist with `__update`.
- Each method wraps the same v2 REST endpoint as its module-facade counterpart (`insert-records`, `update-records`, `delete-records`, `convert-lead`, `add-tags`, `remove-tags`, `get-related-records`, `delink`, `update-related-records`, `blueprint-details`, `v2.1/send-mail`).
- Returns are sync arrays / `CRMResponses` objects — not Promises.
- Source does not enumerate OAuth scope, edition gating, or rate-limit caps for the instance methods — inherited from the underlying v2 REST endpoint.
REQUIRED_PARAMETERS:
- Source samples (verbatim) live at SOURCE_DOC under each method heading; re-fetch when authoring a non-trivial payload (Mail / ConvertLeads / Related_Records) not fully described by the bullets below.
- `Owner(userObject?: Users)` — no-arg form returns the hydrated owner record; arg form assigns a new owner (then `__update` to persist). Scalar shortcut `record.Owner_Lookup_Id` reads/writes only the id.
- `__create()` — call on an instance built from `new ZDK.Apps.CRM.<Module>.Models.<Module>()` with fields assigned; returns array.
- `__update()` — call on an instance whose `id` is set (typically from `fetchById`); returns `CRMResponses`.
- `__remove()` — delete the record by its own `id`; returns `CRMResponses`.
- `__convert(object: ConvertLeads)` — Leads only; `object` built from `new ZDK.Apps.CRM.Leads.Models.ConvertLeads()` with optional `Accounts` id and nested `Deals` object.
- `__addTags(arrayOfTagNames: Array<String>)` · `__removeTags(arrayOfTagNames: Array<String>)`.
- `__fetchRelatedRecords(related_list_api_name: String)` — returns related-records array.
- `__delinkRelatedRecords(related_list_api_name: String, array_of_related_record_ids: Array<String>)`.
- `__updateRelatedRecords(record: Related_Records, related_list_api_name: String, id: String)` — mutate an element from `__fetchRelatedRecords`, then pass it back with its related-record id.
- `__fetchBlueprint()` — returns `Blueprint` object for this record.
- `__approveRecord(action: String, comment: String)` — `action` ∈ `"approve" | "delegate" | "reject"`; instance must have `id` set (can be a fresh model with `lead.id = "..."`); response carries `.status`.
- `__sendMail(mailObject: Mail)` — `mailObject` built from `new ZDK.Apps.CRM.__Actions.Models.Mail()` with `from {user_name,email}`, `to [{user_name,email}]`, `subject`, `content`, `mail_format`.
- `__getFeatureSpecificInfo(features: Array<String>)` — `features` from `{blocked_emails, consecutive_negative_emails, unsubscribed_emails}`; source caps at 5 features per call; response `[0]._features_info` carries the per-feature array.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- Calling `__update` / `__remove` / `__fetchRelatedRecords` / `__fetchBlueprint` / `__approveRecord` / `__sendMail` on an instance whose `id` is unset → API returns id-required / not-found error.
- Building an instance via `new …Models.<Module>()` without assigning `id` then calling `__update` → same id-missing failure (mirrors the module-facade `update` path).
- Reassigning a lookup via `record.Owner(newUser)` and forgetting the trailing `__update()` → in-memory change only; not persisted — silent no-op against CRM.
- Mixing scalar `Owner_Lookup_Id` write with a subsequent `Owner(otherUser)` method call before `__update` → the method-form wins and overwrites the scalar; ordering ambiguity.
- Calling `Owner()` no-arg in a loop → fires one extra API call per iteration.
- Prefer batch hydration via [clientscript-webapi-settings.dou.md](./clientscript-webapi-settings.dou.md) or module facade `fetchById` when resolving many owners.
- `__convert` invoked on an already-converted Lead → API returns conversion-not-allowed; pre-check `Converted` state.
- `__updateRelatedRecords` with the wrong `related_list_api_name` for the module (e.g. `"Deals"` on a module that exposes `"Potentials"`) → silently no-ops.
- Resolve `related_list_api_name` via [clientscript-webapi-settings.dou.md](./clientscript-webapi-settings.dou.md) `fetchRelatedLists`.
- `__sendMail` payload missing `mail_format` → API rejects.
- `__sendMail` payload missing `from` or `to` entries → API rejects malformed-mail.
- `__getFeatureSpecificInfo` invoked with > 5 features (source-stated cap) → request-too-large error.
- Instance-method writes have no `trigger` knob in source — every `__create` / `__update` / `__remove` / `__convert` fires the full workflow / blueprint / approval / scoring / orchestration stack — `automation-interaction`.
- Field assignment via display-label key on the instance (`lead["Last Name"]`) instead of API name (`lead.Last_Name`) before `__update` → field silently unset on persist — `label-vs-api-name`.
- Source does not enumerate error codes.
- Inspect returned `CRMResponses` `.status` / `.code` / `.message` and the underlying v2 REST endpoint's docs for each method.
assets/clientscript/web-apis/clientscript-webapi-settings.dou.md
FILE_PURPOSE: Read when fetching tags / roles / profiles / territories / module metadata / fields / layouts / related-lists / custom-views / org variables / variable-groups / assignment-rules / approval-process from a ClientScript via `ZDK.Apps.CRM.Settings`, or mutating tags or org variables.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/webapi_v2/Settings
TRIGGER_KEYWORDS: ZDK.Apps.CRM.Settings.fetchTags, createTags, mergeTags, fetchRoles, fetchProfiles, fetchTerritories, fetchAllModules, fetchFields, fetchLayouts, fetchRelatedLists, fetchCustomViews, fetchVariables, fetchVariableGroups, fetchAssignmentRules, fetchApprovalProcess, Models.Tags, Models.Variables
TECHNICAL_CONSTRAINTS:
- Surface is `ZDK.Apps.CRM.Settings` — a single facade aggregating metadata reads for tags, roles, profiles, territories, modules, fields, layouts, related-lists, custom-views, org variables, variable-groups, assignment-rules, and approval-process; plus mutating helpers for tags and org variables.
- Each method wraps a Zoho CRM v2 REST endpoint per source: `get-tag-list.html`, `create-tag.html`, `update-tags.html`, `delete-tag.html`, `tag-record-count.html`, `merge-tags.html`, `get-roles.html`, `get-profiles.html`, `territories.html`, `module-meta.html`, `field-meta.html`, `layouts-meta.html`, `related-list-meta.html`, `custom-view-meta.html`, `get-variables.html`, `delete-variable.html`, `create-variables.html`, `update-variables.html`, `get-variable-group.html`. `fetchAssignmentRules*` and `fetchApprovalProcess*` are documented without `Reference API` links on this page.
- Calls are synchronous from script perspective — return value is the object / array directly.
- Module-scoped tag operations require both the `Tags` payload and the module's API name as a second arg (e.g. `"Leads"`); never the display label.
- Layout / related-list / custom-view / field / assignment-rule reads are scoped per module — pass the module API name.
- `Variables` model built via `new ZDK.Apps.CRM.Settings.Models.Variables()`; required fields per sample: `api_name`, `name`, `value`, `type` (e.g. `"text"`), `variable_group { name }`.
- Source does not enumerate OAuth scope, edition gating, or rate-limit caps for these helpers.
REQUIRED_PARAMETERS:
- Source samples (verbatim) live at SOURCE_DOC under each method heading — re-fetch when authoring a `Tags` / `Variables` payload or when the response shape (e.g. `roles[i].name`, `modules[i].api_name`, `fields[i].api_name`) matters.
- Tags:
- `fetchTags(api_name: String)` — module API name; returns array of `Tags`.
- `createTags(arrayOfObjects: Array<Tags> | singleObject, api_name: String)` — `Tags` payload built from `new …Models.Tags()` with `name`.
- `updateTags(arrayOfObjects, api_name)` and explicit-id form `updateTagsById(Object: Tags, tag_id: String, api_name: String)`.
- `deleteTagsById(tag_id: String, api_name: String)`.
- `fetchTagsCount(tag_id: String, api_name: String)` — returns numeric total records tagged.
- `mergeTags(conflicting_tag_id: String, tag_id: String)` — merges the conflicting tag into the target tag.
- Roles / Profiles / Territories:
- `fetchRoles()` · `fetchRolesById(id: String)`.
- `fetchProfiles()` · `fetchProfilesById(id: String)`.
- `fetchTerritories()` — array; no by-id variant on this page.
- Module metadata:
- `fetchAllModules()` — array of modules; each carries `api_name`.
- `fetchFields(module_name: String)` — array of fields with `api_name`.
- `fetchLayouts(module_name: String)` · `fetchLayoutsById(id: String, module_name: String)`.
- `fetchRelatedLists(module_name: String)`.
- `fetchCustomViews(module_name: String)` · `fetchCustomViewsById(id: String, module_name: String)`.
- Org variables:
- `fetchVariables()` · `fetchVariablesById(id: String)`.
- `createVariables(array_of_variable_objects: Array<Variables>)` — required fields `api_name`, `name`, `value`, `type`, `variable_group {name}`.
- `updateVariables(array_of_variable_objects)` and explicit-id form `updateVariablesById(object: Variables, id: String)`.
- `deleteVariablesByIds(ids: Array<String>)` and singular `deleteVariablesById(id: String)`.
- `fetchVariableGroups(group_name_or_id: String)` — accepts either the group's name or id.
- Assignment rules / approval process:
- `fetchAssignmentRules()` · `fetchAssignmentRulesByModule(module: String)`.
- `fetchApprovalProcess()` · `fetchApprovalProcessById(id: String)`.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- `api_name` / `module_name` / `module` arg passed as display label (`"Lead"`, `"Price Books"`) instead of module API name (`"Leads"`, `"Price_Books"`) → metadata helpers return empty array / not-found; tag mutations are silently scoped to nothing — `label-vs-api-name`.
- `Variables` `api_name` collision with an existing org variable → `createVariables` returns duplicate-api-name; pre-check via `fetchVariables`.
- `Variables.variable_group` reference to a non-existent group name → CRM returns invalid-group; create the group first via UI.
- `mergeTags(conflicting_tag_id, tag_id)` argument ordering reversed → conflicting tag becomes the target; records re-tag under the wrong tag and the originally-intended target is deleted.
- `deleteTagsById` invoked on a tag still in use → CRM may either reject or cascade-untag depending on org policy; verify with `fetchTagsCount` first.
- `fetchLayoutsById` / `fetchCustomViewsById` arg order — `(id, module_name)` not `(module_name, id)` — reversing them silently returns not-found.
- `updateTags` / `updateVariables` payload not seeded from a `fetch*` result → missing `id` → API rejects update.
- `fetchAssignmentRules*` and `fetchApprovalProcess*` — source does not link a `Reference API` doc; response shape is undocumented on this page.
- ClientScript invoker without admin / setup-modify privileges → mutating helpers (`createTags`, `updateTags`, `deleteTagsById`, `mergeTags`, `createVariables`, `updateVariables`, `deleteVariables*`) return permission-denied server-side.
- Source does not document a `trigger` knob — tag / variable mutations fire any configured downstream automation without suppression — `automation-interaction`.
- Source does not enumerate error codes; inspect returned `CRMResponses` `.status` / `.code` / `.message` and the linked `Reference API` endpoint per method.
assets/clientscript/web-apis/clientscript-webapi-users.dou.md
FILE_PURPOSE: Read when fetching, creating, updating, or deleting CRM org users from a ClientScript via the `ZDK.Apps.CRM.Users` Web API v2 facade.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/webapi_v2/Users
TRIGGER_KEYWORDS: ZDK.Apps.CRM.Users.fetch, ZDK.Apps.CRM.Users.fetchById, ZDK.Apps.CRM.Users.create, ZDK.Apps.CRM.Users.update, ZDK.Apps.CRM.Users.deleteById, Models.Users, profile, role
TECHNICAL_CONSTRAINTS:
- Surface is `ZDK.Apps.CRM.Users` — single facade.
- No per-user instance method namespace documented beyond field reads on the returned `Users` object.
- `fetch` wraps `zoho.com/crm/developer/docs/api/v2/get-users.html`.
- `create` wraps `add-user.html`.
- `update` wraps `update-user.html`.
- `deleteById` wraps `delete-user.html`.
- Calls are synchronous from script perspective.
- Return value is the user object / array directly — not a Promise.
- Source documents only five methods (`fetch`, `fetchById`, `create`, `update`, `deleteById`); admin-side bulk operations and reactivation are not in scope of this page.
- New `Users` instance constructed via `new ZDK.Apps.CRM.Users.Models.Users()`.
- User fields exposed as snake_case scalars: `first_name`, `last_name`, `email`, `profile`, `role`.
- `profile` and `role` are id strings, not names.
- Source does not enumerate OAuth scope, admin-privilege gating, edition gating, or rate-limit caps for these helpers.
- Source page lists exactly five methods on this surface; reactivation / activation toggles and `type=AllUsers|ActiveUsers|...` filter are not documented here.
- `fetch` return order is not documented — do not rely on the array being sorted by `id`, `email`, or `last_name`.
- The `Models.Users` constructor exposes only the five fields used in the source sample; additional user attributes documented in `add-user.html` may or may not be settable through this constructor.
- Source page does not document a paginated `fetch(page?, per_page?)` overload — the array returned by `fetch()` may already be truncated by the underlying endpoint's default page size.
REQUIRED_PARAMETERS:
- Source samples (verbatim) live at SOURCE_DOC under each method heading; re-fetch when authoring a `Users` model payload with fields beyond the five named below.
- `fetch()` — no args; returns array of `Users`.
- `fetch()` — source does not document paging or a `type` filter on this facade.
- `fetchById(id: String)` — returns single `Users` instance.
- `create(arrayOfObjects: Array<Users> | singleObject)` — payload built from `new ZDK.Apps.CRM.Users.Models.Users()`; returns array.
- `create` required fields per sample: `first_name`, `last_name`, `email`, `profile` (profile id string), `role` (role id string).
- `update(arrayOfObjects: Array<Users> | singleObject)` — payload seeded from `fetchById` then mutated.
- `update` requires `id` on each payload object (set by the prior `fetchById`).
- `deleteById(id: String)` — returns array.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- `profile` supplied as display name (`"Administrator"`) instead of profile id string → CRM rejects with invalid-id / not-found — `label-vs-api-name`.
- `role` supplied as display name (`"CEO"`) instead of role id string → CRM rejects with invalid-id / not-found — `label-vs-api-name`.
- Resolve `profile` ids via [clientscript-webapi-settings.dou.md](./clientscript-webapi-settings.dou.md) `fetchProfiles`.
- Resolve `role` ids via [clientscript-webapi-settings.dou.md](./clientscript-webapi-settings.dou.md) `fetchRolesById`.
- `create` payload missing any of `first_name` / `last_name` / `email` / `profile` / `role` (per source sample) → API returns mandatory-field error.
- `create` helpers do not default missing required fields.
- `create` with `email` already assigned to another org user → API returns duplicate-user error.
- `update` payload built from `new …Models.Users()` instead of a `fetchById` result → missing `id` → API rejects.
- `deleteById` against the last admin user / current-session user → CRM blocks deletion server-side; surface as failure response.
- ClientScript invoker without admin-level CRM privileges → user-mutation calls return permission-denied even though `ZDK.Apps.CRM.Users.*` is in scope.
- Source does not document a `trigger` knob or workflow suppression — user create / update / delete fires whatever user-event automation the org has configured — `automation-interaction`.
- Source does not enumerate error codes; inspect returned response object's `.status` / `.code` / `.message`.
- Consult the linked `Reference API` endpoint per method for the wire-level error catalogue.
assets/clientscript/zrc/.gitkeep
assets/clientscript/zrc/clientscript-zrc.dou.md
FILE_PURPOSE: Read when calling Zoho CRM REST APIs, Zoho Connections, or external HTTP services from a ClientScript via the built-in `zrc` HTTP client.
SOURCE_DOC: https://www.zohocrm.dev/explore/client-script/zrc/overview, https://www.zohocrm.dev/explore/client-script/zrc/zrc_methods, https://www.zohocrm.dev/explore/client-script/zrc/request_configuration_and_error_types, https://www.zohocrm.dev/explore/client-script/zrc/limitations_and_considerations, https://www.zohocrm.dev/explore/client-script/zrc/code_samples
TRIGGER_KEYWORDS: zrc.get, zrc.post, zrc.request, zrc.createInstance, connection, baseUrl, responseType, ApiError, ConnectionError, ZrcValidationError
TECHNICAL_CONSTRAINTS:
- Surface is `zrc` — a built-in REST client exposed in the ClientScript runtime. The source documentation is hosted on `zohocrm.dev`, which carries an Early Access banner for the developer portal itself — this does NOT indicate that `zrc` as a feature is in Early Access.
- Three request modes documented: (1) CRM API (relative path `/crm/v8/...`), (2) Connection-based (absolute URL + `connection` link name in `requestConfig`), (3) External API (absolute URL, no auth, "syntactic sugar over the standard fetch API").
- All three modes share identical request-config semantics — source asserts: "All other request configuration like request body, params, headers behave the same way for both Zoho CRM APIs and Connection-based requests" and "Consistent syntax for making API calls — Whether it's CRM, Connection, or External API calls, ZRC uses the same syntax."
- CRM-mode: API domain is auto-resolved from the logged-in user's data centre; calls to the same-org CRM are auto-authenticated (no token / credential handling required by the script).
- CRM-mode: can target any publicly available CRM API version (`/crm/v7/...`, `/crm/v8/...`, etc.); unlike the `clientscript-webapi-*` helpers which wrap v2 endpoints only.
- Connection-mode: the Connection (`connection` link name) must be pre-created out-of-band; ZRC does not expose a Connection-creation API (sibling node: [clientscript-webapi-connections.dou.md](../web-apis/clientscript-webapi-connections.dou.md)).
- Connection-mode supports file upload / download via `FormData` payloads and `responseType: 'blob'` (sample-confirmed: WorkDrive upload + Desk download in `code_samples`); CRM-mode also supports file upload via `FormData` (sample-confirmed: `/crm/v8/Leads/.../Attachments`). Source's "Why use ZRC" header reads "File uploads via Connection or External API" but no External-mode upload sample is provided on any of the 5 pages.
- All `zrc.*` methods return `Promise`s; `async`/`await` and `.then(...).catch(...)` are both supported per the Code Samples page.
- Request body is auto-serialized for three payload shapes: JSON, `FormData`, `URLSearchParams`; source instructs callers **not** to set `Content-Type` manually for these three shapes — the wrapper handles it.
- Query parameters supplied via `requestConfig.params` are auto-URL-encoded; JSON / array values are auto-serialized.
- Response is auto-parsed per `requestConfig.responseType` (default `'json'`); when `responseType` is omitted, parsing falls back to the response's `content-type` header.
- Response shape for body-returning methods: `{ status: number, data: any, headers: object }`. `HEAD` and `OPTIONS` responses omit `data` per source samples (`HEAD` → `{ status, headers }`; `OPTIONS` → `{ status, headers }` with `Allow` and `Content-Length` populated).
- External-mode is documented with "no request limits" (browser-direct, fetch-sugar); CRM-mode has "no enforced ZRC-layer limits" and inherits browser timeout; Connection-mode is the only mode with explicit ZRC-enforced caps (see CRITICAL_FAILURE_MODES).
- Source does not enumerate OAuth scope, edition, profile, or rate-limit caps for the `zrc` helper itself — auth derives from the ClientScript-session user (CRM mode) or the named Connection (Connection mode).
REQUIRED_PARAMETERS:
- Source samples (verbatim) live across the five SOURCE_DOC pages — the `code_samples` page covers ~16 end-to-end examples (CRM CRUD, COQL, Function `execute`, attachment upload / download, Desk / WorkDrive Connection, external POST); re-fetch when authoring a non-trivial payload, Connection call, or Function invocation.
- Quick API methods (one method per HTTP verb):
- `get(path: String, requestConfig?: object) → Promise<Response>`.
- `delete(path: String, requestConfig?: object) → Promise<Response>`.
- `head(path: String, requestConfig?: object) → Promise<Response>`.
- `options(path, requestConfig?: object) → Promise<Response>`.
- `post(path: String, body: any, requestConfig?: object) → Promise<Response>`.
- `put(path: String, body: any, requestConfig?: object) → Promise<Response>`.
- `patch(path: String, body: any, requestConfig?: object) → Promise<Response>`.
- Custom request:
- `request(requestConfig: object) → Promise<Response>` — `path` is the only required key in `requestConfig`. Source prose declares "Must include method, path, etc." but the property table lists `method` default as `'GET'` and the first `zrc.request` sample on the same page omits `method` and returns `status: 200`; treat `method` as optional (defaults to `'GET'`).
- Reusable instance:
- `createInstance(requestConfig: object) → ZRC Instance` — returns an object exposing the same Quick / Custom methods seeded with the base config; per-request overrides allowed without affecting the base.
- `createInstance` ignores `body` in the base config (source Note: "body is not accepted in requestConfig for createInstance and will be ignored if provided").
- Source-recommended pattern for Connection calls: pair `connection` + `baseUrl` inside `createInstance` to avoid repeating the connection link name and domain on every request (source Note under "Connection-based Requests").
- `requestConfig` properties (verbatim from source's property table):
- `headers: Object` — applies to all methods; e.g. `{ 'content-type': 'application/json' }`.
- `connection: String` — Connection link name; applies to Connection-based requests only.
- `baseUrl: String` — API domain (e.g. `https://api.example.com`); applies to Connection-based requests and `createInstance()`; **mandatory when `path` is a relative URL**.
- `params: Object` — query parameters as key-value pairs; applies to all methods; auto-encoded; JSON / arrays auto-serialized. Source escape-hatch: "if you need more customization, please add queryparams directly in the path variable, e.g. `/path/to/myservice?name=brad&city=jaipur`".
- `responseType: 'json' | 'text' | 'blob' | 'stream' | 'arraybuffer'` — default `'json'`; applies to all methods.
- `body: any` — applies to `zrc.request()` only (the Quick methods take `body` as a positional arg, not via config).
- `method: String` — default `'GET'`; applies to `zrc.request()` and `createInstance()` only.
- `path: String` — applies to `zrc.request()` only.
- Response shape (consistent across all methods and modes): `{ status, data, headers }` — `data` is the parsed body, `headers` is the response-headers object.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- **ZRC errors not wrapped in `try/catch` are silently absorbed by the ClientScript execution environment — no console error, no visible exception, no log entry.** This is intentional ClientScript behavior: unhandled Promise rejections from `zrc` do not propagate as visible runtime errors. Always wrap every `zrc` call in `try { ... } catch (error) { ... }` and branch on `error.name` to surface failures.
- Error type catalogue (source-stated, verbatim from the Error Types table):
- `ZrcError` — error occurred while setting up the request (pre-flight); inspect `error.message`.
- `ZrcValidationError` — validation error due to incorrect arguments passed to ZRC methods; inspect `error.message`.
- `ApiError` — server-side issue (`status >= 300`, server unreachable, or no response received); carries `error.status` and `error.details` (sample shows `{ "name": "ApiError", "message": "Request failed with status code 404", "status": 404, "details": { "error": "User not found" } }`).
- `ConnectionError` — issues specific to connection-based API calls.
- Connection-mode hard caps (source-stated, Limitations & Considerations page):
- Response payload cap **100 MB** (source: "Accepts responses up to 100 MB").
- Read timeout fixed at **40 seconds** against the upstream Connection target (source: "Uses a 40-second read timeout from the target service").
- CRM-mode caps (source-stated, Limitations & Considerations page):
- No enforced ZRC-layer request / response limits (sent directly from the browser).
- Request timeout follows the browser's default timeout behaviour — not pinned by ZRC.
- All CRM-mode calls execute under the logged-in user's authorization (source: "All Zoho CRM APIs are executed under the logged-in user's authorization").
- Subject to the standard Zoho CRM API limits (`zoho.com/crm/developer/docs/api/v8/api-limits.html`).
- `zrc.request(...)` config missing `path` → `ZrcValidationError`; `method` is optional and defaults to `'GET'` per the property table (see REQUIRED_PARAMETERS note on the source prose / property-table conflict).
- `createInstance({..., body: ...})` → `body` is silently dropped at instance construction (source-stated Note); pass `body` per-call on the returned instance instead.
- Connection-mode call without `connection` in the config (or with a mistyped / unconfigured link name) → `ConnectionError`; the Connection must be pre-created out-of-band before any `zrc` call against it succeeds.
- Connection-mode or `createInstance` with a relative `path` and no `baseUrl` → request setup fails; source states `baseUrl` is **mandatory if `path` is relative**.
- Function execution via `/crm/v7/functions/<api_name>/actions/execute?auth_type=oauth` requires the target Function to have its OAuth2 toggle enabled in the CRM Functions setup page (source-stated in the sample comment: "oauth2 should be enabled for 'my_function' in Zoho CRM functions setup page"); without it the call fails server-side.
- Function execution return value: source-stated in the sample comment — "Function output will be in `details.output` key" (read via `response.data.details.output`).
- Function execution `function_name` passed as display label instead of the Function's API name → server returns function-not-found — same class as [clientscript-webapi-functions.dou.md](../web-apis/clientscript-webapi-functions.dou.md).
- Body auto-serialization vs explicit `Content-Type` collision — if the caller sets `headers['content-type']` manually for a JSON / `FormData` / `URLSearchParams` body, the auto-serializer's content-type and the manual header may conflict.
- `responseType: 'json'` against an upstream that returns a non-JSON body → parsing fails.
- The `zohocrm.dev` documentation portal carries an Early Access banner — this refers to the portal itself, not to `zrc` as a feature. Do not surface Early Access caveats about `zrc` to users based on this banner.
- Source does not enumerate per-call error codes for the `ApiError` / `ConnectionError` cases beyond the catalogue above; surface failures by reading `error.name`, `error.status`, `error.message`, and `error.details` and the upstream endpoint's documented error catalogue.
assets/connections/connections-setup.dou.md
FILE_PURPOSE: Read when authoring a CRM Connection (Setup → Developer Hub → Connections) for invoking a third-party OAuth 2.0 API from Deluge Functions, ClientScript, or Widget egress surfaces.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/connectors/set-up.html
TRIGGER_KEYWORDS: Connections, Developer Hub, Custom Service, OAuth 2.0, Client ID, Authorize URL, Access Token URL, Refresh Token URL, link name, invokeurl
TECHNICAL_CONSTRAINTS:
- Connections live at `Setup → Developer Hub → Connections` and split across two tabs — `Pick Your Service` (pre-built integrations published by Zoho) and `Custom Service` (BYO third-party OAuth 2.0 endpoint).
- `Pick Your Service` entries ship with their OAuth client (`Client ID` / `Client Secret`) pre-registered by Zoho — the author supplies only a `Connection name` to instantiate one. `Custom Service` is the only path where the author must register an OAuth client at the third party and paste its `Client ID` / `Client Secret` into CRM.
- A Connection abstracts the OAuth 2.0 authorization-code flow plus refresh-token rotation — callers reference the Connection by its **link name** at runtime (`connection:"<link_name>"` clause on `invokeurl` from a Function, `ZDK.Apps.CRM.Connection.invoke("<link_name>", …)` from a ClientScript, `CONNECTION.invoke("<link_name>", …)` from a Widget (legacy JS SDK), or `zrc.*` with `{ connection: "<link_name>" }` from a ClientScript (org-level enablement required) or Widget (ZRC — preferred)) and never handle tokens directly.
- Custom Service `Authentication type` field accepts the protocol family supported by the third-party; OAuth 2.0 is the documented path. The auth-code grant is the only flow exposed by the help page.
- Custom Service requires nine inputs in this order: `Service name`, `Authentication type`, `Client ID`, `Client Secret`, `Request Token URL` (a.k.a. `Authorize URL`), `Access Token URL`, `Refresh Token URL`, `Connection name`, `Scopes`.
- Three OAuth parameters are injected by CRM and must NOT be appended to the Authorize / Access / Refresh URLs by hand: `Redirect URL`, `Client ID`, `Response type`. Every other mandatory third-party parameter (`scope`, `state`, `access_type`, custom keys) is the developer's responsibility, encoded into the Authorize URL per the third-party's spec.
- For Zoho-to-Zoho Custom Services, the Zoho-side OAuth client is registered at `https://accounts.zoho.com/developerconsole` with Redirect URI `https://deluge.zoho.com/delugeauth/callback` and Client Type `Web based`; non-Zoho services register at the third-party's own developer console with their own redirect URI list.
- `Scopes` value is verbatim from the third-party documentation — CRM does not validate the scope string until the first authorization round-trip against the resource server.
- The Connection's link name is generated from the user-supplied `Connection name` at the moment of `Create and Connect`; the link name is the only identifier accepted at runtime — the display name is not.
- Connection HTTP egress executes server-side inside the CRM Function / ClientScript / Widget egress runtime — stored tokens never reach the browser.
- After `Create and Connect`, the Connection surfaces under `Pick Your Service` for re-use by other authors in the same org / DC.
- Runtime invocation route owns its own keys: Deluge `invokeurl` uses `parameters` (Deluge syntax); the ClientScript and Widget bridge each use `param` (singular) on `req_data` — see [widgets-jssdk-egress.dou.md](../widgets/jssdk/widgets-jssdk-egress.dou.md) for the Widget-side contract.
REQUIRED_PARAMETERS:
- Custom Service lifecycle is **two-stage**:
- Stage 1 — Define the service shell: `Setup → Developer Hub → Connections → +Create your First Connection → Custom Service` → fill `Service name`, `Authentication type`, `Client ID`, `Client Secret`, `Request Token URL`, `Access Token URL`, `Refresh Token URL`, `Scopes` → save. The new service definition now appears under `Pick Your Service`.
- Stage 2 — Instantiate a runtime Connection: `Setup → Developer Hub → Connections → Pick Your Service → {your custom service} → Connection name → Create and Connect` (browser tab redirects through the third-party consent screen and lands on the CRM callback).
- Pre-built-service flow (single stage — Zoho-published services already have their OAuth client pre-registered): `Setup → Developer Hub → Connections → Pick Your Service → {service} → Connection name → Create and Connect`.
- Post-`Create and Connect` envelope (read off the success page): `Link name`, `Service name`, `Status`, plus an auto-generated `invokeurl` snippet pre-filled with the `connection:"<link_name>"` clause — copy the link name from here, not from the display title.
- Runtime invocation from a Function: `invokeurl [ url :"<absolute_url>" type :GET|POST|PUT|DELETE parameters :<map> headers :<map> connection :"<link_name>" ]`. The `parameters` map is the templating slot for substituting per-record / per-call dynamic values into the third-party URL or body.
- Runtime invocation from a ClientScript (standard SDK): `ZDK.Apps.CRM.Connection.invoke("<link_name>", { url, method, param?, headers?, body? })` — same `req_data` shape as Widget `CONNECTION.invoke`; see [clientscript.md](../../references/clientscript.md) for the ZRC enablement note.
- Runtime invocation from a ClientScript (ZRC — org-level enablement required): `zrc.get|post|...(path, body?, { connection: '<link_name>' })` or `zrc.createInstance({ baseUrl, connection: '<link_name>' })` — see [clientscript.md](../../references/clientscript.md).
- Runtime invocation from a Widget (legacy JS SDK): `ZOHO.CRM.CONNECTION.invoke("<link_name>", { url, method, param?, headers?, body? })` — see [widgets-jssdk-egress.dou.md](../widgets/jssdk/widgets-jssdk-egress.dou.md).
- Runtime invocation from a Widget (ZRC — preferred): `zrc.get|post|...(path, body?, { connection: '<link_name>' })` or `zrc.createInstance({ baseUrl, connection: '<link_name>' })` — see [widgets-zrc-reference.dou.md](../widgets/widgets-zrc-reference.dou.md).
- Third-party-side prerequisite (Custom Service only): an OAuth 2.0 client registered at the resource server whose redirect URI list includes the CRM-side callback that the Authorize URL flow lands on (varies per service — read the third-party's OAuth setup doc before authoring).
UI_ONLY_ACTIONS:
- Create / list / edit / delete a Connection: `Setup → Developer Hub → Connections`. Note: No API equivalent — Connection definitions are admin-only Setup objects.
- Re-authorize an expired refresh token: `Setup → Developer Hub → Connections → {connection} → Authorize`. Note: No API equivalent — must be re-triggered by a human admin from the Setup UI.
- Register a Zoho-side OAuth client (Zoho-to-Zoho integrations only): visit `https://accounts.zoho.com/developerconsole`, then `Add Client ID` → `Client Type: Web based` → `Redirect URI: https://deluge.zoho.com/delugeauth/callback` → `Create`. Note: No API equivalent — Zoho-Accounts developer console is UI-only.
- Read the third-party's API documentation for its exact OAuth Authorize / Access / Refresh endpoints and scope-string syntax before opening the Custom Service form. Note: No API equivalent — third-party-side discovery is doc-driven.
CRITICAL_FAILURE_MODES:
- Connection referenced from `invokeurl` / `CONNECTION.invoke` by its display name instead of the generated link name: runtime returns connection-not-found, no callable surface — `label-vs-api-name`.
- `Redirect URL`, `Client ID`, or `Response type` appended manually to the Authorize URL: auto-injected duplicates collide with the third-party's parser and the consent screen rejects the request — leave the three Zoho-managed parameters out of the configured URLs.
- Custom Service Authorize URL configured without the third-party's mandatory `scope` / `state` / `access_type`: the OAuth handshake completes against CRM but the access token is missing the required claim, and the resource server returns 403 only on the first runtime API call.
- Zoho-side OAuth client registered with a Redirect URI other than `https://deluge.zoho.com/delugeauth/callback`: the auth-code callback never reaches Deluge and the Connection stays in the `Not Connected` state with no actionable error.
- `Scopes` field configured with a superset of what the third-party's client app was registered for: the third-party silently downgrades the granted scope on the access token — verify the effective scope after first authorization, not at config time.
- Connection's stored refresh token expired per third-party policy: every subsequent runtime call returns auth-error; admin re-`Authorize` from `Setup → Developer Hub → Connections` is the only recovery — there is no programmatic refresh endpoint.
- Two authors create separate Custom Service Connections against the same third-party with diverging scope sets: runtime behaviour differs by link name, hard to debug — consolidate to one Connection per service per org.
- Connection created in sandbox but referenced from a production Function: link names are not auto-promoted across environments — re-create in production before promoting the Function.
- `Client Secret` rotated at the third-party without re-saving in CRM: token-refresh fails silently the next time the access token expires — update `Client Secret` in the Connection immediately on third-party rotation.
- Connection's `Authorize URL` set to an OAuth1 endpoint for a service that also exposes OAuth2 (legacy Twitter pattern): consent screen errors and CRM does not surface the protocol mismatch — confirm OAuth2 endpoints in the third-party's current API reference.
- Pre-built service deleted and re-created with the same `Connection name`: the link name is regenerated per creation event, so existing `invokeurl` references break — keep the original link name or refactor every caller.
- `Service name` / `Connection name` containing a forward slash, colon, or `?`: generated link name truncates and downstream calls miss the suffix — use alphanumeric + underscore identifiers.
- Connection used inside a recursive Function call graph: each invocation consumes the org's daily Function-execution credits and the third-party's rate limit simultaneously — handle the third-party's 429 with explicit backoff.
- `Scopes` field left empty on a service that requires a non-empty scope claim: authorization succeeds against the consent screen but the token grants zero permissions and every API call returns 403.
- Connection authored against a sandbox third-party endpoint then promoted to production without swapping URLs: production calls hit the sandbox host and return test data — partition Connections by environment in the `Connection name`.
- Authorize URL containing `state` hardcoded to a static value instead of a per-flow nonce: third-parties enforcing CSRF protection reject the consent callback — supply a per-invocation nonce per the third-party's spec.
- Token endpoint behind a third-party that issues opaque error bodies on refresh-token revocation: the SDK surface returns a generic auth-error without the revocation detail — capture the raw response in the caller's log before retrying.
- Custom Service whose third-party rotates the Access-Token URL after a major API version bump: existing Connections continue to point at the old host and fail silently — audit all Connections after every third-party API-version migration.
- Connection `Client Secret` pasted with trailing whitespace from the third-party developer console: token exchange returns `invalid_client` with no inline hint — trim before saving.
- Connection authored by an admin who later loses CRM admin rights: Connection still resolves at runtime under the org's stored OAuth, but no other admin can `Authorize` again because edit-permission is profile-gated — re-assign Connection authorship before deactivating the original admin.
assets/coql/coql-execute.dou.md
## FILE_PURPOSE
Read when invoking the `POST /coql` endpoint — scopes, headers, per-field-type comparator matrix, aggregate / alias / CVID / external-ID / metadata samples, and the full COQL error code list.
## SOURCE_DOC
https://www.zoho.com/crm/developer/docs/api/v8/Get-Records-through-COQL-Query.html
## TRIGGER_KEYWORDS
POST /coql, ZohoCRM.coql.READ, include_meta, like wildcard, CVID, what_id, External_Id, consent_lookup, INVALID_QUERY, LIMIT_EXCEEDED, What_Id->
## TECHNICAL_CONSTRAINTS
- Endpoint — `POST {api-domain}/crm/{version}/coql`. HTTP method is **POST** (the verb refers to "posting" the query body, not writing records). Body shape: `{ "select_query": "<query>" }`.
- Authorization header — `Authorization: Zoho-oauthtoken <token>`.
- Mandatory scope — `ZohoCRM.coql.READ` **AND** `ZohoCRM.modules.{operation_type}` OR `ZohoCRM.modules.{module_name}.{operation_type}`. `operation_type` ∈ `{ALL, READ}` (ALL = full data access, READ = get module data).
- Metadata retrieval (`include_meta` key in the body) additionally requires `ZohoCRM.settings.fields.READ` — calls without it raise `OAUTH_SCOPE_MISMATCH` 401.
- Supported base modules: `leads`, `accounts`, `contacts`, `users`, `deals`, `campaigns`, `tasks`, `cases`, `events`, `calls`, `solutions`, `products`, `vendors`, `pricebooks`, `quotes`, `salesorders`, `purchaseorders`, `invoices`, and `custom` (custom modules).
- Per-field-type comparator matrix (mandatory — use only these per field type):
- Text: `=`, `!=`, `like`, `not like`, `in`, `not in`, `is null`, `is not null`.
- Picklist: `=`, `!=` only — `in`, `not in`, `like`, `not like` appear in the source comparator table but are rejected by the COQL engine at runtime. Apply exclusion logic client-side.
- Lookup, Multi-module lookup / `what_id`, Roles & Profiles, Alias: `=`, `!=`, `in`, `not in`, `is null`, `is not null`.
- Date, DateTime, Number: `=`, `!=`, `>=`, `>`, `<=`, `<`, `between`, `not between`, `in`, `not in`, `is null`, `is not null`.
- Boolean (raw field): `=` (source explicitly lists only `=`).
- Rollup Summary: `=`, `!=`, `>=`, `>`, `<=`, `<`, `between`, `not between`, `in`, `not in`, `is null`, `is not null`. From V7, Date / DateTime return types return values in their native formats.
- **Formula field** comparators vary by return type (separate table on source):
- Formula returning Decimal / Date / DateTime / Number: `=`, `!=`, `>=`, `>`, `<=`, `<`, `between`, `not between`, `in`, `not in`, `is null`, `is not null`.
- Formula returning String (text): `=`, `!=`, `like`, `not like`, `in`, `not in`, `is null`, `is not null`.
- Formula returning Boolean: `is null`, `is not null`, `equals`.
- Encrypted-field rules — numeric encrypted fields support only `=`, `!=`, `is null`, `is not null`; non-numeric encrypted fields support only `is null` and `is not null`. Encrypted fields are valid in both SELECT and WHERE.
- `like` wildcard grammar — `%tech` = ends-with, `tech%` = starts-with, `%tech%` = contains. `not like` only behaves correctly when `%` is at BOTH ends (`not like "%tech%"` = does-not-contain); single-sided `%` with `not like` does NOT work.
- `in` / `not in` accept up to 100 values per comparator in the SELECT-column criterion.
- Aggregate functions (`SUM`, `MAX`, `MIN`, `AVG`, `COUNT`) — case-sensitive; only valid on numeric data types (`integer`, `decimal`, `currency`, `bigint`, formula fields returning those). `COUNT` additionally supports `number`, `lookup`, `picklist`. `GROUP BY` is mandatory when aggregate and non-aggregate fields appear together in SELECT.
- `Full_Name` is a read-only concatenation of `First_Name` + `Last_Name`, available only in Leads and Contacts.
- Module API name disambiguation — when a module API name collides with a field API name in a path, prefix the module reference with `!` (e.g. `!Leads.Owner.role.id`).
- Multi-module lookup (`what_id`) — supported only for `Tasks`, `Calls`, `Events`; query the associated record via `What_Id->{Module}.{field}` path syntax.
- Multi-Module Lookup module name query (separate from `What_Id` traversal) — to discover the module each record is associated with for an MML field (e.g. `Appointment_For` on the Appointments module), query the field directly in SELECT.
- Lookup field WHERE-filter compares against the target record's `id` (not its display name): `where Owner = '554023000000235011'` — pre-fetch the target record id before composing the filter.
- Record Image / Profile Image — the `Record_Image` field IS queryable in COQL (returns the image ID, which feeds the GET Files API); this is an exception to the Limitations page's blanket "Image upload field is not supported."
- Subform data — query via the subform's own module API name (subforms are independent modules).
- Multi-select lookup fields (MxN) — query via the auto-generated **linking module**, not the parent module's field.
- Custom View querying — `FROM {module_API_name}#{CVID}`. Unsupported CV types: `Shared By Me`, `Co-owner`, `Shared To Me`.
- Territory fields — supported in SELECT only; field API name is `Territories` for Accounts / Contacts / Leads and `Territory` for Deals.
- V7+ lookup display names — available for Lookup and User Lookup fields in SELECT; **not** available for Consent Lookup or Multi-Module Lookup (MML). Users-module display field is `last_name`.
- Consent Lookup requires GDPR enabled on the module; the auto-created field has data type `consent_lookup` and is discovered via Field Metadata API.
- Roles & Profiles fields available from the owner lookup — role: `id`, `name`, `reporting_to`, `share_data_with_peers`, `description`. Profile: `id`, `name`, `created_by`, `modified_by`, `description`, `created_time`, `modified_time`.
- `include_meta` request key — value type is `JSON array`; supported element value is `fields` (string). Returns field metadata for SELECT-column fields.
- Tuple is not supported in COQL.
### Deluge Usage
Calling COQL from a Deluge Function via `invokeurl`:
1. **Workbench WHERE-clause runtime requirement** — The Queries Workbench COQL source type requires a WHERE clause at runtime even though the COQL grammar marks WHERE as optional. If no real filter is needed, use `is not null` on a key field: `WHERE (id is not null)`. The standalone `POST /crm/v8/coql` API does not enforce this restriction.
2. **`is not null` vs `= null`** — Use `is null` / `is not null` for null checks; `= null` or `!= null` raises SYNTAX_ERROR 400.
3. **Body map construction** — Build the request body as a Deluge Map variable first; inline map literals in `parameters` cause Improper Statement Error:
```deluge
bodyMap = Map();
bodyMap.put("select_query", "SELECT id, Last_Name FROM Leads WHERE Lead_Status = 'New' LIMIT 0, 200");
response = invokeurl
[
url: "https://www.zohoapis.com/crm/v8/coql"
type: POST
parameters: bodyMap.toString()
connection: "crm_connection"
];
records = response.get("data");
```
4. **Pagination from Deluge** — Capture `response.get("info").get("more_records")` (boolean); increment the LIMIT offset by `per_page` per iteration and `break` when `more_records` is false or the data list is empty.
## REQUIRED_PARAMETERS
- Request body — `{ "select_query": "<COQL SELECT statement>" }`. The `select_query` key is mandatory and the only top-level key.
- Optional metadata key — `"include_meta": ["fields"]` (requires `ZohoCRM.settings.fields.READ`).
- Header — `Authorization: Zoho-oauthtoken <token>`.
- Pagination cursor — when paging beyond a single response, capture `info.more_records` and bump `LIMIT <offset>, <limit>` accordingly.
## UI_ONLY_ACTIONS
*(none in source)*
## CRITICAL_FAILURE_MODES
- `SYNTAX_ERROR` 400 — query missing proper criteria, base table, or WHERE clause. Resolution: form a query with all three.
- `SYNTAX_ERROR` 400 — query missing `FROM`. Resolution: supply a `FROM <module>` clause.
- `SYNTAX_ERROR` 400 — non-SELECT statement (`update Leads set ...`). Resolution: COQL is read-only; use the v8 Records API for writes.
- `SYNTAX_ERROR` 400 — `Parsing is happening for so long for the given query` (unbalanced parens / over-complex). Resolution: re-bracket the WHERE; simplify.
- `LIMIT_EXCEEDED` 400 — LIMIT or SELECT column count breached. Canonical caps (per Overview + Limitations pages, which agree): max 2000 records per call, max 500 SELECT fields. The error message text on this page reads `maximum limit of 200 and 500` / `fetch only a maximum of 200 records and 50 fields`;
- `LIMIT_EXCEEDED` 400 — more than two joins in a single SELECT chain depth (`A.B.C`). Resolution: cap relation chain at depth 2; for more, switch to the Bulk Read API or split queries.
- `LIMIT_EXCEEDED` 400 — `Max records limit exceeded` past 1,00,000 records per criteria. Resolution: paginate with id-continuation criteria or use Bulk Read API.
- `LIMIT_EXCEEDED` 400 — more than 100 values in an `in` / `not in` comparator. Resolution: chunk the value set across multiple queries.
- `LIMIT_EXCEEDED` 400 — ORDER BY fields > 10; GROUP BY fields > 4; aggregate fields > 5. Resolution: cut to the cap.
- `INVALID_QUERY` 400 — invalid column name (typo / display-label use). Resolution: use API name. (`label-vs-api-name`)
- Picklist field filtered with `!=`, `NOT IN`, `IN`, `LIKE`, or `NOT LIKE` in WHERE (e.g. `Stage NOT IN ('Closed Won','Closed Lost')`): rejected at runtime — Picklist only supports `=`, `!=` in WHERE despite the comparator matrix listing broader operators. Apply exclusion logic client-side after fetching with `=`, `!=` or without a stage filter.
- `INVALID_QUERY` 400 — unsupported data type in SELECT (e.g. multi-select lookup used directly instead of via linking module). Resolution: switch to the linking module per the MxN rule.
- `INVALID_QUERY` 400 — value type mismatch (`No_of_Employees = 'adfkahfd'`). Resolution: pass values of the correct type for each field.
- `INVALID_QUERY` 400 — invalid operator for field type. Resolution: consult the per-type comparator matrix above.
- `INVALID_QUERY` 400 — `include_meta` value or datatype invalid. Resolution: value must be `fields`; datatype must be JSON array of text.
- `INVALID_QUERY` 400 — Territory fields used outside SELECT. Resolution: restrict territory fields to SELECT.
- `INVALID_QUERY` 400 — invalid CVID. Resolution: fetch a valid CVID via the Custom Views Metadata API.
- `INVALID_ALIAS` 400 — empty-string alias. Resolution: supply a non-empty alias.
- `OAUTH_SCOPE_MISMATCH` 401 — user lacks the required module scope or `ZohoCRM.settings.fields.READ` when `include_meta` is set. Resolution: have admin grant the scope.
- `DUPLICATE_DATA` 400/401 — duplicate SELECT columns, duplicate aggregate fields, or duplicate fields in ORDER BY / GROUP BY. Resolution: deduplicate.
- `DUPLICATE_ALIAS` 400 — same alias bound to multiple SELECT columns. Resolution: alias each column uniquely.
- `INTERNAL_ERROR` 500 — server-side exception. Resolution: retry; escalate to support if persistent.
- `INVALID_REQUEST_METHOD` 400 — non-POST verb on `/coql`. Resolution: POST only.
- `not like` used with `%` on only one side (`not like "%tech"`): silently does not filter — wrap with `%` on both ends for not-contains, or pivot to `=` / `!=` for exact match.
- Notes-module fetch by non-admin user: source states "Only admin users can fetch the records from the Notes module" — failure mode (error code vs empty response) is not specified. `OAUTH_SCOPE_MISMATCH` / `INVALID_QUERY` / empty `data[]`. Gate Notes queries behind an admin permission check at author-time regardless.
- Querying a lookup field without including the linked module's display field: response carries only the `id` — to fetch the display value, also include `<lookup>.<display_field>` in SELECT.
- Consent Lookup queried in an org where GDPR is not enabled: `data_type: consent_lookup` field does not exist on the module — verify GDPR setup before query author-time.
- `What_Id` used on a module outside `{Tasks, Calls, Events}`: feature does not apply elsewhere — fall back to direct lookup traversal.
- Multi-module lookup display name read from the response: source documents display-name unavailability for MML and Consent Lookup; only `id` is returned — separate metadata fetch required.
assets/coql/coql-joins.dou.md
FILE_PURPOSE: Read when traversing related modules in a COQL `select_query` via the `.` lookup operator — Base JOIN vs SELECT-column JOIN classification and nested-JOIN behaviour.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/api/v8/COQL-Joins.html
TRIGGER_KEYWORDS: COQL JOIN, Base JOIN, SELECT column JOIN, lookup field, dot operator, nested join, Account_Name.Parent_Account, -> operator, What_Id->
TECHNICAL_CONSTRAINTS:
- COQL has no `JOIN` keyword. Joins are expressed by traversing a lookup field via the `.` (dot) operator (e.g. `Account_Name.Account_Name` traverses the `Account_Name` lookup on Contacts into the Accounts module).
- A lookup field traversal is classified into one of two JOIN kinds based on **where** it appears in the query:
- **Base JOIN** — lookup traversal appears in `WHERE`, `ORDER BY`, `GROUP BY`, or inside an aggregate column. Example: `select id from Contacts where Owner.role is not null` (`Owner.role` is a Base JOIN).
- **SELECT column JOIN** — lookup traversal appears **only** in the SELECT column list and nowhere else. Example: `select Owner.profile, Created_By.role from Contacts where id is not null`.
- Two distinct lookup fields pointing to the **same** target module are counted as **two separate JOINs** in Base JOIN classification (e.g. `Owner.role` + `Created_By.profile` both target Users → two Base JOINs).
- The same disjoint-lookups rule does **not** apply to SELECT column JOINs — COQL optimises and collapses multiple SELECT-only traversals into the same target module into a single JOIN. Example: `Owner.profile` + `Created_By.role` both targeting Users in SELECT only = one SELECT column JOIN.
- Nested JOINs (`lookup_field_1.lookup_field_2.field`) decompose as follows:
- The first hop (`lookup_field_1.lookup_field_2`) is always a Base JOIN.
- The second hop (`lookup_field_2.field`) is a Base JOIN if used in WHERE / ORDER BY / GROUP BY / aggregate, or a SELECT column JOIN if used only in the SELECT column.
- JOIN counts are capped by the Limitations page (see [coql-limitations.dou.md](./coql-limitations.dou.md)): max 5 Base JOINs per query, max 15 SELECT column JOINs per query.
- Relation chain *depth* is capped separately at 2 (`A.B.C` has chain depth 2) — see [coql-execute.dou.md](./coql-execute.dou.md) `LIMIT_EXCEEDED` failure mode ("more than two joins") and [coql-limitations.dou.md](./coql-limitations.dou.md) for the explicit cap.
- Response payload for a lookup traversal — bare `Account_Name` returns `{id, name}` of the linked record; `Account_Name.Account_Name` returns the linked record's `Account_Name` field value (string).
- Lookup traversal in SELECT requires the linked field's API name; without it, only the linked record's `id` returns (see [coql-execute.dou.md](./coql-execute.dou.md)).
- The User lookup target's display field is `last_name` (not `name`) — `Owner.last_name` returns the owner's last name string.
- Roles & Profiles sub-traversal: `Owner.role.id` / `Owner.role.name` / `Owner.profile.id` / `Owner.profile.name` are the documented sub-fields off a user lookup's role / profile.
- Multi-Module Lookup (`What_Id`) traversal uses the `->` operator to disambiguate the target module: `What_Id->Accounts.Account_Name` (Tasks / Calls / Events only).
- Self-referential lookups are supported and traversable — e.g. `Parent_Account` on Accounts points to Accounts; `Account_Name.Parent_Account.Account_Name` (Contacts → Accounts → Accounts) is a valid two-relation chain.
- Source-documented silent behaviour for chains > 2 relations: per the Limitations page, "If you use more than two relations in a select query, system validates only the last two relations" — i.e. the parser does NOT error on depth-3+ chains; it silently drops validation of all but the last two. This contradicts the Get-Records error catalog's `LIMIT_EXCEEDED` for "more than two joins."
REQUIRED_PARAMETERS:
- Lookup-traversal syntax in SELECT: `<lookup_field_api_name>.<target_field_api_name>` (e.g. `Account_Name.Account_Name`).
- Nested traversal syntax: `<lookup_1>.<lookup_2>.<field>` (max chain depth 2 — i.e. at most three dot-segments including the base field).
- Base-JOIN-in-WHERE example: `select id from Contacts where Owner.role is not null`.
- SELECT-column-JOIN example: `select Owner.profile, Created_By.role from Contacts where id is not null`.
- Two-hop projection example: `select Last_Name, Account_Name.Parent_Account, Account_Name.Parent_Account.Account_Name from Contacts where Last_Name is not null and Account_Name.Parent_Account.Account_Name is not null`.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- Chain depth > 2 in a single traversal (e.g. `A.B.C.D`): `LIMIT_EXCEEDED` 400 (`The query has more than two joins`) — collapse via intermediate query or subquery (see [coql-subquery.dou.md](./coql-subquery.dou.md)).
- Total Base JOIN count exceeds 5 per query (multiple lookup fields used in WHERE / ORDER BY / GROUP BY / aggregate, including same-target same-field duplicates which count separately): `LIMIT_EXCEEDED` 400 — consolidate or split the query.
- Total SELECT column JOIN count exceeds 15 per query: `LIMIT_EXCEEDED` 400 — partition the projection across multiple queries.
- Two lookup fields pointing to the same target module in WHERE (e.g. `Owner.role` + `Created_By.profile`) misjudged as one JOIN: each counts toward the Base JOIN cap of 5 — audit the per-clause join count, not the per-module count.
- Lookup field returned in SELECT without a `.` traversal: response carries only `{id, name}` of the linked record — caller wanting other linked fields must restate the lookup with the desired path.
- Author confuses Base JOIN vs SELECT column JOIN classification when a lookup appears in **both** SELECT and WHERE: the WHERE usage promotes the field to a Base JOIN, consuming the Base JOIN budget — count joins per (lookup-field, target-module) pair, scoped to the most-restrictive clause it appears in.
- Mixed-module same-target lookups optimised in SELECT but not in WHERE: SELECT shows one logical JOIN, WHERE shows two — caller's mental model breaks when the same query body shifts a lookup between SELECT and WHERE without re-counting.
- Nested traversal where the second hop's target is itself a lookup that the caller assumes is auto-resolved to its display name: only Lookup / User Lookup return display names (per [coql-execute.dou.md](./coql-execute.dou.md)); Consent Lookup and MML return only `id`.
- Query author relies on the same `.`-path appearing twice in SELECT to deduplicate as one JOIN: SELECT-column-JOIN collapsing only applies when both traversals target the **same module** — different-target same-syntax does not collapse.
- Owner / Created_By / Modified_By join cap exhaustion when querying many user-side attributes: each distinct `<user-lookup>.<field>` in WHERE is its own Base JOIN — pre-fetch user metadata via Users module and avoid join inflation.
- WHERE-only Base JOIN to a module the user lacks read access to: source Limitations only states scope is required for the **base** module; behaviour when only a JOINed-to module's scope is missing is undocumented.
- Author writes `<lookup_field>` alone in SELECT expecting all linked fields to inline: only `{id, name}` returns — every linked field needed in the response requires its own `<lookup>.<field>` path entry.
- Dotted traversal across an inactive / archived lookup target module: COQL returns nulls for the inner field path with no error — verify both modules are active before composing the join.
- Same lookup field traversed in both SELECT and ORDER BY of the same query: counts as one Base JOIN (ORDER BY usage promotes it from SELECT-column to Base) — budget against the Base cap, not the SELECT-column cap.
- Lookup field traversal where the lookup is itself stored as `null` on a parent row: the traversed path returns `null` rather than excluding the row (no implicit inner-join semantics) — filter `<lookup> is not null` in WHERE to drop null-parent rows.
- Author conflates module API name with field API name in a dotted path (`Leads.Owner.role.id` where `Leads` collides with a field): query rejected — prefix the module reference with `!` (`!Leads.Owner.role.id`).
- Aggregate function over a dotted-traversal field (`SUM(Account_Name.Annual_Revenue)`): the lookup becomes a Base JOIN and the aggregate target must be a numeric type per [coql-execute.dou.md](./coql-execute.dou.md) — both constraints apply simultaneously.
- Single-quoted dotted traversal path in SELECT without an alias (`SELECT 'Account_Name.Owner' FROM Contacts`): COQL interprets the quoted value as a string literal rather than a field path — the response returns the literal string `"Account_Name.Owner"` for every row, or the request fails with a gateway-level 502. Dotted traversal paths in SELECT must be **unquoted** unless they form part of an aliased projection (`'Account_Name.Owner' AS 'owner'`). Always follow the unquoted form shown in the SELECT-column-JOIN examples above.
- `Who_Id` (the Name / Contact polymorphic field on Tasks, Calls, and Events) is not filterable in COQL WHERE under any form — `->` traversal raises `SYNTAX_ERROR`, and `IN (subquery)` raises `INVALID_QUERY: subquery return more than one values which is incompatible for the operator`. The `->` operator is documented only for `What_Id`; do not assume symmetric support for `Who_Id`. Use `zoho.crm.getRelatedRecords` per parent record as the alternative when Activities must be filtered by a linked Contact's attributes.
assets/coql/coql-limitations.dou.md
FILE_PURPOSE: Read when sizing or validating a COQL query against per-call hard caps — record / clause / JOIN / subquery / aggregate counts, unsupported field types in WHERE, and module-level access restrictions.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/api/v8/COQL-Limitations.html
TRIGGER_KEYWORDS: COQL limits, max records, max joins, max subqueries, unsupported field, Territory in WHERE, Notes admin, multi-select lookup criteria, subform
TECHNICAL_CONSTRAINTS:
- **Record pagination ceiling** — at most 1,00,000 records can be returned across paginated calls per unique criteria. For larger sets, switch to the Bulk Read API.
- **Per-call LIMIT cap** — max 2000 records per single API call; default 200 (see [coql-overview.dou.md](./coql-overview.dou.md)).
- **SELECT column cap** — max 500 fields in the SELECT clause per query.
- **WHERE criteria cap** — max 25 criteria in WHERE per query.
- **ORDER BY cap** — max 10 fields.
- **GROUP BY cap** — max 4 fields.
- **Aggregate function cap** — max 5 aggregate columns per query.
- **Dynamic formula field cap** — max 5 dynamic formula fields in SELECT per query.
- **`IN` / `NOT IN` value cap** — max 100 values per comparator.
- **Subquery caps** — max 5 subqueries per parent query; max 100 records per subquery; subqueries valid only in WHERE; single SELECT column inside the subquery (see [coql-subquery.dou.md](./coql-subquery.dou.md)).
- **JOIN caps** — max 5 Base JOINs per query; max 15 SELECT-column JOINs per query (see [coql-joins.dou.md](./coql-joins.dou.md)).
- **Relation-chain depth cap** — max 2 relations per single dotted traversal (`A.B.C` is depth 2; `A.B.C.D` exceeds the cap).
- **Silent partial-validation for > 2 relations** — source literally states: "If you use more than two relations in a select query, system validates only the last two relations." This contradicts the Get-Records error catalog which raises `LIMIT_EXCEEDED` for "more than two joins."
- **Notes module** — only admin users can query the Notes module via COQL. Non-admin requests return empty / forbidden.
- **Unsupported field types in WHERE / criteria** — Territory fields (Accounts/Contacts/Leads `Territories`, Deals `Territory`), Multi-Select Lookup, Multi-line text fields (`Description`, `Terms and Conditions`, `Comments`), Line Items, Participants, File Upload, Attachments, Image Upload, Tuple.
- **Territory fields** — usable only in SELECT, never in WHERE / ORDER BY / GROUP BY.
- **Multi-line text fields** in `{Description, Terms and Conditions, Comments}` — cannot be used in criteria; safe in SELECT only.
- **Multi-Select Lookup (MxN)** — direct field reference in WHERE is unsupported; query via the linking module instead (see [coql-execute.dou.md](./coql-execute.dou.md)).
- **Subform** — query via the subform module's own API name (subforms are independent modules); cannot be traversed via `.` from the parent module.
- **`Tuple` data type** — unsupported across the COQL surface.
- **Image Upload vs Record Image** — generic Image Upload fields are unsupported; the special `Record_Image` (profile image) field IS queryable in COQL and returns the image ID (see [coql-execute.dou.md](./coql-execute.dou.md)).
- **External ID lookup** — supported (per Get-Records source); query via `External_Id_Field_API_Name` directly.
- Picklist WHERE comparator runtime cap: `=`, `!=` are the only operator reliably accepted by the COQL engine for Picklist fields. The Limitations page (listing only `=`) matches production behavior; the Get-Records comparator matrix listing `in`, `not in`, `like`, `not like` for Picklist is stale. Use only `=` for Picklist WHERE filters; handle exclusion logic client-side.
- **Encrypted fields** — restricted comparator set (see [coql-execute.dou.md](./coql-execute.dou.md)).
- **API credit consumption per call** — `LIMIT 1`–`200` = 1 credit, `LIMIT 201`–`1000` = 2 credits, `LIMIT 1001`–`2000` = 3 credits.
REQUIRED_PARAMETERS:
- Pagination beyond 1,00,000 — paginate within the cap, then issue a new query with continuation criteria `(<prior criteria>) and id > <last_id_returned>`; otherwise switch to Bulk Read API.
- Per-clause budget audit — sum (SELECT fields ≤ 500), (WHERE criteria ≤ 25), (ORDER BY ≤ 10), (GROUP BY ≤ 4), (aggregates ≤ 5), (dynamic formula ≤ 5), (`IN` values ≤ 100), (subqueries ≤ 5, each ≤ 100 rows / 1 SELECT col), (Base JOINs ≤ 5), (SELECT-col JOINs ≤ 15), (relation chain depth ≤ 2) before submitting.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- Query pulls > 1,00,000 records under one criteria via repeated paging: `LIMIT_EXCEEDED` 400 (`Max records limit exceeded`) — switch to Bulk Read API or rotate criteria.
- LIMIT > 2000 in a single call: `LIMIT_EXCEEDED` 400 — cap at 2000 per Overview + Limitations (agreed). The Get-Records error string reads `200` / `50 fields`; treat that error text as stale, not as a separate cap
- WHERE includes Territory field as a filter: query returns `INVALID_QUERY` 400 — restrict territory fields to SELECT only.
- WHERE includes Multi-Select Lookup field directly: `INVALID_QUERY` 400 — query via the linking module.
- WHERE includes `Description` / `Terms and Conditions` / `Comments` (multi-line text): `INVALID_QUERY` 400 — restrict to SELECT only.
- WHERE includes a line item, Participant, File Upload, Attachment, or Image Upload field: `INVALID_QUERY` 400 — these data types are not queryable in COQL criteria.
- Non-admin user issues a query against the Notes module: silently returns empty / unauthorised — gate Notes queries behind an admin permission check before composing.
- 6th subquery added to a parent query: `LIMIT_EXCEEDED` 400 — split across multiple API calls.
- 16th SELECT-column JOIN or 6th Base JOIN added to a query: `LIMIT_EXCEEDED` 400 — partition the projection / filter.
- `IN`-list of 101 values in a single WHERE comparator: `LIMIT_EXCEEDED` 400 — chunk values into batches of ≤ 100.
- 11th ORDER BY field / 5th GROUP BY field / 6th aggregate column / 6th dynamic formula in SELECT: `LIMIT_EXCEEDED` 400 — trim to the per-clause budget.
- 26th WHERE criterion or 501st SELECT field: `LIMIT_EXCEEDED` 400 — split across multiple queries.
- Single dotted traversal of depth 3 (`A.B.C.D`): `LIMIT_EXCEEDED` 400 (`more than two joins`) — flatten via intermediate query or subquery.
- Subform queried via parent-module dot traversal (`Parent_Module.Subform_Field.Inner_Field`): unsupported — query the subform's own module API name directly.
- Author treats Tuple field as queryable: feature unavailable — Tuple is not supported in any COQL clause.
- Pre-V7 client assumes Lookup display names auto-return: only V7+ surfaces display names for Lookup / User Lookup; older API versions return only `{id, name}` (see [coql-execute.dou.md](./coql-execute.dou.md)).
- Author depends on Consent Lookup display name returning: never surfaced (only `id`) — separate Field Metadata fetch required.
- Credit-budget exhaustion under sustained `LIMIT 2000` paging: each call costs 3 credits — model 3 × page_count against the org's per-day credit budget.
- Caller relies on `more_records: false` to declare completion at exactly 1,00,000 records: per source, the cap is per unique criteria — `more_records` returns `false` both when the dataset ends and when the cap is hit. Distinguish dataset end vs cap hit via cumulative row count check against 1,00,000.
assets/coql/coql-overview.dou.md
FILE_PURPOSE: Read when composing a COQL `select_query` body — SELECT / FROM / WHERE / GROUP BY / ORDER BY / LIMIT grammar, alias rules, pagination beyond 100K, and API-credit cost.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/api/v8/COQL-Overview.html
TRIGGER_KEYWORDS: COQL, select_query, SELECT, WHERE, ORDER BY, LIMIT OFFSET, GROUP BY, alias AS, pagination, API credits
TECHNICAL_CONSTRAINTS:
- COQL is the SQL-like read-only query language for CRM modules and fields; it uses module + field **API names** verbatim as table / column names (never display labels).
- Only `SELECT` statements are accepted — `UPDATE` / `INSERT` / `DELETE` issued through the COQL endpoint return `SYNTAX_ERROR` HTTP 400.
- Supported clause set: `SELECT`, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY`, `LIMIT offset, limit`. The two LIMIT operands are written `LIMIT <offset>, <limit>` — offset first, limit second.
- `LIMIT` default value is 200; max value is 2000 per single API call.
- Default sort is ascending by record `id` when `ORDER BY` is omitted.
- COQL keywords are case-insensitive (`SELECT` ≡ `select`) **except** aggregate function names (`SUM`, `MAX`, `MIN`, `AVG`, `COUNT`) which are case-sensitive.
- SQL reserved words and field API names containing special characters must be enclosed in single quotes inside the query string (e.g. `select Last_Name, 'Dynamic' from Contacts ...`).
- `SELECT` accepts up to 500 field API names per query.
- `WHERE` accepts up to 25 criteria; for three criteria A,B,C the source documents four valid bracketing forms: `(A and (B and C))`, `((A and B) and C)`, `A and (B and C)`, `(A and B) and C`. Fully-unbracketed `A and B and C` is rejected as ambiguous.
- Alias support uses `AS` (case-insensitive) and is valid **only** in the SELECT column list and the `order_by` clause — alias values appear as the corresponding response keys.
- `GROUP BY` and aggregate functions: `SUM`, `MAX`, `MIN`, `AVG`, `COUNT`. `GROUP BY` is mandatory whenever aggregate and non-aggregate fields appear together in `SELECT`.
- Pagination beyond the 1,00,000-record per-unique-criteria ceiling requires chaining queries: capture the last record's `Created_Time` and `id` from the previous page, then add `Created_Time > '<last>' and id > <last_id>` to the next query's WHERE.
- Pagination per unique criteria caps at 1,00,000 records across multiple API calls — for larger sets, switch to the Bulk Read API (not the COQL endpoint).
- API credit cost per call scales with `LIMIT`: `1`–`200` = 1 credit; `201`–`1000` = 2 credits; `1001`–`2000` = 3 credits.
- Request body shape is exactly `{ "select_query": "<query string>" }` — single JSON key, mandatory, value is the COQL SELECT statement as one string.
- Response envelope is `{ "data": [...], "info": { "count": <n>, "more_records": <bool> } }`; `more_records: true` signals further pages exist under the same criteria.
- Aliases are echoed back as the corresponding JSON keys in the `data[]` response objects; without an alias, the key is the field API name.
- `null` literals are supplied via `is null` / `is not null` only — `= null` is invalid syntax (source documents `is null` / `is not null` as the only null comparators).
REQUIRED_PARAMETERS:
- Query string skeleton: `SELECT <field_api_name>[, ...] FROM <module_api_name> [WHERE <criteria>] [GROUP BY <fields>] [ORDER BY <fields> ASC|DESC] [LIMIT <offset>, <limit>]`.
- Field API names: discover via the Field Metadata API or `Setup → Developer Hub → APIs and SDKs → API Names → {module}`.
- Aliased projection example: `select 'Account_Name' AS 'Account id', 'Account_Name.Account_Name' as 'Account Name' from Contacts where (Account_Name.Parent_Account.Account_Name = 'King') limit 1`.
- Pagination-beyond-100K continuation criteria: `((<other criteria>) and id > <last_record_id>)` (adjust against the actual ORDER BY axis if custom-sorted).
- Cross-module field traversal uses the `.` operator on lookup fields (e.g. `Account_Name.Account_Name`) — see the JOINs DOU node for join-count caps.
UI_ONLY_ACTIONS:
- Look up field API names by module: `Setup → Developer Hub → APIs and SDKs → API Names → {module}`. Note: equivalent to the Field Metadata REST API.
CRITICAL_FAILURE_MODES:
- **Queries Workbench COQL source enforces a WHERE clause at runtime even though the grammar documents it as optional** — a query with no WHERE is rejected. Always include at least one `is not null` criterion when there is no real filter condition (e.g. `WHERE (Account_Name is not null)`). The standalone `POST /crm/v8/coql` endpoint does not enforce this; the restriction is Workbench-specific.
- `= null` or `!= null` written instead of `is null` / `is not null`: `SYNTAX_ERROR` 400 — the only valid null-comparison forms are `is null` and `is not null`.
- Query references a field by display label (`"Last Name"`) instead of API name (`Last_Name`): returns `INVALID_QUERY` 400 with `invalid column name`. (`label-vs-api-name`)
- `LIMIT 0, 2500` or any `limit` value > 2000: `LIMIT_EXCEEDED` 400 — split across pages of ≤ 2000.
- Aggregate function in lowercase (`sum`, `count`): query parses but does not execute as aggregate — aggregate function names are case-sensitive per source `Notes`.
- `WHERE` with three or more criteria written without surrounding brackets (`A and B and C`): parser ambiguity → `SYNTAX_ERROR` 400 (`Parsing is happening for so long for the given query`) — always nest as `(A and (B and C))` or `((A and B) and C)`.
- `SELECT` column count exceeds 500: `LIMIT_EXCEEDED` 400 (`maximum limit of 200 and 500`) — partition into multiple queries.
- Issuing `UPDATE` / `INSERT` / `DELETE` through the `/coql` endpoint: `SYNTAX_ERROR` 400 (`The request contains a query other than select`). COQL is read-only; writes require the v8 Records API (see parent skill Top-5 gotcha on `trigger`-map suppression for the write path).
- Pagination past 1,00,000 records under the same WHERE without adding the `id > <last_id>` continuation criteria: `LIMIT_EXCEEDED` 400 (`Max records limit exceeded`) — either add continuation criteria or move to Bulk Read API.
- Alias supplied as an empty string (`'Account_Name' AS ""`): `INVALID_ALIAS` 400 — alias must be a non-empty string per source.
- Alias used outside SELECT / `order_by` (e.g. in WHERE): query parser rejects — alias scope is restricted to SELECT and ORDER BY per source.
- Custom-view-based query targeting `Shared By Me` / `Co-owner` / `Shared To Me`: those CVs are unsupported in COQL (see Get-Records DOU) — pick a supported view ID.
- Aggregate function duplicated in SELECT (`COUNT(De1), COUNT(De1)`): `DUPLICATE_DATA` 401 — deduplicate aggregate columns.
- COQL caller assumes default ORDER BY is by `Created_Time`: source documents default as ascending by `id` only — supply explicit `ORDER BY` when chronological order is required.
- `select_query` JSON body missing the `select_query` key (any other key name): the request fails JSON-schema validation — the only accepted top-level key is `select_query`.
- API-credit consumption planning that assumes 1 credit per call regardless of LIMIT: a single `LIMIT 0, 2000` call costs 3 credits — model credit cost against the LIMIT brackets above.
- SQL reserved word used unquoted in SELECT (e.g. `select Dynamic from Contacts`): parser rejects — wrap in single quotes (`'Dynamic'`).
- `= null` written instead of `is null`: `SYNTAX_ERROR` 400 — the only null-comparison form is `is null` / `is not null`.
- LIMIT operands transposed (caller writes `LIMIT <count>, <offset>` per MySQL convention instead of COQL's `LIMIT <offset>, <count>`): row set is empty or skips wrong window with no error — COQL parses the first integer as offset, second as count.
assets/coql/coql-subquery.dou.md
FILE_PURPOSE: Read when nesting `SELECT` statements inside a COQL WHERE clause as an alternative to JOINs — subquery operators, execution sequence, the 100-row-per-subquery cap, and the 5-subqueries-per-parent cap.
SOURCE_DOC: https://www.zoho.com/crm/developer/docs/api/v8/COQL-subquery.html
TRIGGER_KEYWORDS: COQL subquery, nested SELECT, IN subquery, BETWEEN subquery, subquery sequence, parent query, outer query
TECHNICAL_CONSTRAINTS:
- A subquery is a nested SELECT inside a parent ("outer") query's WHERE clause; result rows feed the outer query at evaluation time.
- Subqueries are valid **only** inside the WHERE clause — never in SELECT, FROM, GROUP BY, ORDER BY, or aggregate columns.
- A single parent query accepts **at most 5 subqueries** (including nested-inside-subquery uses).
- A subquery returns **at most 100 rows**, with two distinct overflow behaviours by composition:
- Subquery **without** an explicit LIMIT and underlying selection > 100 rows: silently truncated to the first 100 (per source's Subquery-vs-JOIN scenario where 400 matching accounts → only the first 100 are used → only 350 contact rows returned). No error raised.
- Subquery **with** an explicit LIMIT > 100: `LIMIT_EXCEEDED` 400 ("The subquery retrieves more records than the allowed limit") per source error catalog.
- For > 100 matching rows in either case, switch to a JOIN (see [coql-joins.dou.md](./coql-joins.dou.md)).
- Each subquery's SELECT column count is exactly 1 — multi-column subqueries are rejected with `INVALID_QUERY` 400.
- Supported subquery operators split into two groups:
- **Multi-value operators**: `IN`, `NOT IN` — accept the full subquery result set as operands.
- **Single-value operators**: `=`, `!=`, `<`, `>`, `<=`, `>=`, `BETWEEN`, `NOT BETWEEN` — require each operand subquery to return exactly one row.
- `BETWEEN` / `NOT BETWEEN` with subquery operands: each side (lower bound + upper bound) is evaluated independently; either side that returns more than one row fails the query.
- BETWEEN operands accept mixed subquery + literal pairs: e.g. `between (select Created_Time from Leads where id is not null Limit 1) and '2024-09-25T23:59:59+05:30'`.
- **Subquery sequence** — every subquery is assigned a zero-based execution sequence: independent subqueries execute first in declaration order; dependent subqueries wait for their dependency's sequence to complete and execute after.
- A subquery itself accepts a nested subquery; the inner subquery executes first and is assigned an earlier sequence number than the outer subquery that depends on it.
- Subqueries are JOIN-substitutes only when the lookup-inner-field appears **only in WHERE**. If the inner field is also needed in SELECT / ORDER BY / GROUP BY, fall back to a JOIN.
- Choice between subquery and JOIN for matching-row counts > 100: source explicitly recommends JOIN over subquery (subquery hits the 100-row cap and silently drops the remainder).
- A subquery references a lookup target module **once per WHERE clause** (e.g. `Account_Name in (select id from Accounts where ...)`) — replacing repeated dotted-traversals in the WHERE.
- A subquery's `LIMIT` clause is optional; when omitted, the 100-row cap still applies (the cap is implicit, not driven by LIMIT).
- Subquery WHERE clauses follow the same per-field-type comparator matrix as the outer query (see [coql-execute.dou.md](./coql-execute.dou.md)).
- A subquery's `SELECT` field must be a real field on the inner module — alias names from the outer query are out of scope inside the subquery.
REQUIRED_PARAMETERS:
- Subquery syntax skeleton: `<outer_field> <operator> (select <single_column> from <module> where <criteria> [LIMIT <offset>, <limit>])`.
- IN-subquery example: `select Lead_Source, Email from Leads where Contacts in (select id from Contacts where Email='patricia@mail.com')`.
- BETWEEN with two subquery operands: `select Last_Name from Leads where Created_Time between (select Created_Time from Leads where id is not null Limit 1) and (select Created_Time from Leads where id is not null limit 200,1)`.
- Nested-subquery example (parent → outer subquery → inner subquery): `select id from Vendors where id in (select id from Vendors where Vendor_Name like 'V%')` embedded inside `Vendor_Name in (...)` on the parent.
- Single-column constraint — every subquery's SELECT must contain exactly one field; never multi-select inside a subquery.
UI_ONLY_ACTIONS:
*(none in source)*
CRITICAL_FAILURE_MODES:
- `INVALID_QUERY` 400 — subquery placed in an unsupported clause (SELECT / FROM / GROUP BY / ORDER BY / aggregate). Resolution: move the subquery into WHERE.
- `INVALID_QUERY` 400 — subquery's SELECT contains multiple columns. Resolution: split into separate subqueries, each with one SELECT column.
- `INVALID_QUERY` 400 — single-value operator (`=`, `!=`, `<`, `>`, `<=`, `>=`, `BETWEEN`, `NOT BETWEEN`) paired with a subquery returning more than one row. Resolution: add `LIMIT 1` to the subquery or switch to `IN` / `NOT IN`.
- `LIMIT_EXCEEDED` 400 — more than 5 subqueries in a single parent query (counting nested subqueries). Resolution: consolidate or split across multiple API calls.
- `LIMIT_EXCEEDED` 400 — single subquery's LIMIT exceeds 100. Resolution: cap each subquery at 100; if more rows are required, switch to a JOIN.
- Subquery silently truncates at row 100 when the underlying selection has more matching rows AND no explicit LIMIT is set: outer query's row set is incomplete with no warning — pre-check the inner SELECT's `count` first, or switch to JOIN per the source's "use JOIN over subquery for > 100 records" guidance. Concrete source scenario: 400 accounts matching `Account_Name like 'A%'` with 1600 linked contacts → subquery returns 350 (first-100-accounts subset); JOIN equivalent returns all 1600.
- Author uses a subquery to filter on a lookup-inner-field that is also required in the SELECT projection: subquery semantics only filter — to project the inner field, supplement with the dotted traversal `<lookup>.<inner>` in SELECT and let the WHERE keep the subquery.
- Subquery sequence misunderstood — assuming declaration order is execution order when one subquery depends on another: independent subqueries fire first (in declaration order), then dependents — debug by mapping every `(<inner-subquery>)` reference to its enclosing subquery before predicting sequence.
- BETWEEN with one literal + one subquery operand whose subquery returns multiple rows: query rejected — restrict the subquery to exactly one row with `LIMIT 1`.
- Subquery used as the only filter on a high-cardinality lookup (`Account_Name in (select id from Accounts where Account_Name like 'A%')` against 400+ matching accounts): outer query returns only the rows linked to the first 100 accounts, silently dropping the rest — switch to JOIN (`Account_Name.Account_Name like 'A%'`) for full coverage.
- Same lookup field referenced both via dotted traversal in WHERE and via a subquery against the same target module: produces redundant work and inflates the JOIN count toward the 5-Base-JOIN cap — pick one expression form per (lookup, target) pair.
- Subquery's `from` module differs from the lookup field's target module: outer `IN` returns no rows (silent) — confirm the inner module matches the lookup target.
- Subquery returns zero rows under single-value `=` / `!=`: outer comparison resolves to false for every row → empty result set — branch on the inner subquery's expected cardinality before composing.
- Author uses BETWEEN's two-subquery form to express "between the first and last record" without sort-stable ORDER BY in the inner subqueries: row order is by `id` ASC default — supply explicit `ORDER BY` inside each operand subquery when the bounds depend on a custom axis.
- Nested-subquery dependency chain longer than two levels: source documents two-level dependency examples — deeper chains' execution order is undefined per source silence.
- `IN` subquery operand list exceeds 100 distinct values after expansion: hits the `in` / `not in` 100-value cap (see [coql-execute.dou.md](./coql-execute.dou.md)) — chunk values across multiple queries.
assets/functions/deluge/built-in-functions/conversion/toDate.dou.md
FILE_PURPOSE: Deluge `toDate()` — parse TEXT, NUMBER, or DATE-TIME expression into a DATE-TIME value.
MENTAL_MODEL: `expr.toDate(mapping)` = DATE-TIME; `mapping` tells the parser which text segment corresponds to year/month/day.
WHEN_TO_USE:
- Parse a custom-formatted date string (e.g., `"01,11,2019"`) into a DATE-TIME using a `dateTimeMapping`.
- Convert epoch milliseconds (NUMBER) to a DATE-TIME value.
- Normalise a date-time expression to the application's configured date format.
WHEN_NOT_TO_USE:
- When the mapping must include time components (hours, minutes, seconds, am/pm) — use `toTime()` which supports `a`, `H`, `h`, `m`, `s` literals.
- When the expression is already a DATE-TIME: additional parameters are ignored and the value is returned as-is.
TRIGGER_KEYWORDS:
toDate, dateTimeMapping toDate, y M d literals, epoch-milliseconds toDate, text-to-datetime parse, app-settings date format
RULES:
- Return type: DATE-TIME; output format follows application settings.
- Input types: TEXT, NUMBER, DATE-TIME.
- NUMBER input: value `1` = `31-Dec-1969`; each increment of `1000` adds 1 second; `dateTimeMapping` is ignored.
- DATE-TIME input: additional parameters are ignored; throws a runtime error if month value exceeds months in a year or day value exceeds days in a month.
- TEXT input: `dateTimeMapping` specifies component mapping; applicable literals: `y` (year), `M` (month), `d` (day).
- `dateTimeMapping` separator must exactly match the separator used in the expression string.
- Numeric month: use `M` or `MM`; text month (e.g., "Jan"): use `MMM` or more `M`s.
CANONICAL_EXAMPLES:
```deluge
dateTimeString = "01,11,2019 1:30:00 pm";
info toDate(dateTimeString, "MM,d,yyyy");
// MM=01 (January), d=11, yyyy=2019
// Returns 11-Jan-2019 (assuming app settings format is dd-MMM-yyyy)
```
FAILURE_MODES:
- DATE-TIME input with a month value exceeding the number of months in a year throws a runtime error.
- DATE-TIME input with a day value exceeding the number of days in a month throws a runtime error.
- `dateTimeMapping` separator that does not exactly match the expression separator causes incorrect or silent mis-parsing.
RELATED_DOCS:
- toTime — toTime.dou.md
- Type conversion functions — https://www.zoho.com/deluge/help/functions/conversion.html
SOURCE:
https://www.zoho.com/deluge/help/functions/common/todate.html
assets/functions/deluge/built-in-functions/conversion/toDateTimeString.dou.md
FILE_PURPOSE: Deluge `toDateTimeString()` — convert Unix epoch milliseconds to formatted TEXT with timezone.
MENTAL_MODEL: `toDateTimeString(epochMs, fmt, tz)` = TEXT; input is NUMBER (ms); inverse of unixEpoch().
WHEN_TO_USE:
- Converting a Unix timestamp (milliseconds) to a human-readable or service-compatible date string
- Formatting an epoch value in a specific timezone or date-time pattern
WHEN_NOT_TO_USE:
- Input is a DATE-TIME or TEXT date (not epoch ms) → use toString() / toText() instead
- Need the reverse (date text → epoch ms) → use unixEpoch()
TRIGGER_KEYWORDS: toDateTimeString(), unix-epoch-to-text, epoch-milliseconds-input, dateTimeFormat-literal, TZ-database-conversion
RULES:
1. Return type: TEXT.
2. Input: NUMBER (epochMs — Unix timestamp in milliseconds ONLY).
3. Epoch 0 = 01-Jan-1970 00:00:00 UTC. Each +1000 adds one second; each -1000 subtracts one second.
4. dateTimeFormat param: datetime pattern literals — y (year), M (month), d (day), H (hour 0-23), h (hour 1-12), m (minute), s (second), z (timezone), E (day name), a (am/pm). Optional; defaults to application settings if omitted.
5. timeZone param: TZ database name (e.g. "IST", "UTC+8", "America/New_York", "GMT+5:30").
6. DST is applied automatically wherever applicable.
7. Output (month names, timezone labels, day names) is always in English regardless of user locale.
8. Applicable to all Zoho services.
CANONICAL_EXAMPLES:
```deluge
ctime = 1693905560373; // Unix ms timestamp
result = toDateTimeString(ctime, "MMM dd, yy 'at' hh:mm:ss z, E", "IST");
// → "Sep 05, 23 at 02:49:20 IST, Tue"
// Dot notation
result = ctime.toDateTimeString("dd-MMM-yyyy HH:mm:ss", "UTC+8");
// Epoch 0 = Unix origin
result = toDateTimeString(0, "dd-MMM-yyyy HH:mm:ss", "GMT"); // → "01-Jan-1970 00:00:00"
```
FAILURE_MODES:
- Passing epoch in seconds (not milliseconds) → result off by factor of 1000
- Passing TEXT or DATE-TIME as input instead of NUMBER → not the intended use; behavior not documented
- Invalid TZ database name → behavior not documented in source; validate TZ string before use
- Unsupported format literal in dateTimeFormat → output unpredictable
RELATED_DOCS:
- unixEpoch() (inverse: date text → epoch ms) — ../number/../number/unixEpoch.dou.md
- toString() — toString-toText.dou.md
- toDateTime() — https://www.zoho.com/deluge/help/functions/common/to-date-time.html
- Date-time data type — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/common/todatetimestring.html
assets/functions/deluge/built-in-functions/conversion/toDecimal.dou.md
FILE_PURPOSE: Deluge `toDecimal()` — convert a NUMBER, DECIMAL, or numeric TEXT to a DECIMAL value.
MENTAL_MODEL: `expr.toDecimal()` = DECIMAL; TEXT must be purely numeric; preserves fractional precision unlike `toNumber()`.
WHEN_TO_USE:
- Convert a NUMBER to DECIMAL to retain fractional precision in calculations.
- Convert a numeric text string (e.g., `"99.9"`) to DECIMAL for arithmetic.
WHEN_NOT_TO_USE:
- When the TEXT contains non-numeric characters — throws a runtime error.
- When the TEXT is empty — throws a runtime error.
- When fraction truncation is desired — use `toNumber()` instead.
TRIGGER_KEYWORDS:
toDecimal, numeric-text toDecimal, decimal conversion, empty-text runtime, number-to-decimal
RULES:
- Return type: DECIMAL.
- Input types: DECIMAL, NUMBER, TEXT.
- TEXT input: must contain only numerical values; any non-numeric character throws a runtime error.
- Empty TEXT throws a runtime error.
CANONICAL_EXAMPLES:
```deluge
marks = 99.9;
marks_text = "100";
info toDecimal(marks); // Returns 99.9
info toDecimal(marks_text); // Returns 100
```
FAILURE_MODES:
- TEXT containing non-numeric characters throws a runtime error.
- Empty TEXT throws a runtime error.
RELATED_DOCS:
- toNumber / toLong — toNumber-toLong.dou.md
- Type conversion functions — https://www.zoho.com/deluge/help/functions/conversion.html
SOURCE:
https://www.zoho.com/deluge/help/functions/common/todecimal.html
assets/functions/deluge/built-in-functions/conversion/toHex.dou.md
FILE_PURPOSE: Deluge `toHex()` — convert a NUMBER to its hexadecimal string representation.
MENTAL_MODEL: `num.toHex()` = TEXT containing the hex representation of the integer `num`.
WHEN_TO_USE:
- Convert an integer NUMBER to its hexadecimal representation for hex-based encoding or display.
WHEN_NOT_TO_USE:
- When encoding a TEXT string to hex — use `textToHex()` instead.
- When the input is a DECIMAL — input type is NUMBER only.
TRIGGER_KEYWORDS:
toHex(), number to hex, integer hex conversion, hex-string output, toHex number
RULES:
- Return type: TEXT.
- Input type: NUMBER.
- Both `<number>.toHex()` and `toHex(<number>)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
number = 100;
hexValue = number.toHex(); // returns "64"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- textToHex — ../text/../text/textToHex.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/tohex.html
assets/functions/deluge/built-in-functions/conversion/toJSONList.dou.md
FILE_PURPOSE: Deluge `toJSONList()` — convert a JSON-formatted TEXT array into a Deluge LIST.
MENTAL_MODEL: `jsonText.toJSONList()` = LIST; input must be a valid JSON array string; each element is accessible via `getJSON()`.
WHEN_TO_USE:
- Parse a JSON array response (e.g., from `getJSON()` on an API result) into a Deluge LIST for iteration.
- Extract nested objects from a JSON response array, then iterate with `for each` to access fields.
WHEN_NOT_TO_USE:
- When the TEXT is not in valid JSON array format — throws a runtime error.
- When the text is a comma-separated non-JSON string — use `toListString()` instead.
TRIGGER_KEYWORDS:
toJSONList, getJSON toJSONList, JSON-array parse, LIST from JSON, non-JSON runtime
RULES:
- Return type: LIST.
- Input type: TEXT (must be a valid JSON array string).
- Non-JSON text throws a runtime error.
- After conversion, call `getJSON("<key>")` on each list element to extract nested key-value pairs.
CANONICAL_EXAMPLES:
```deluge
// Extract a simple value array from a JSON string
json_text = "{Name:'Lia Shelton', OrderNo:[343,443,577]}";
orderNo = json_text.getJSON("OrderNo");
info orderNo.toJSONList(); // Returns 343,443,577
// Iterate a JSON array of objects to collect IDs
response = "{\"data\" : [{\"id\":1, \"firstname\":\"John\"}, {\"id\":2, \"firstname\":\"Patricia\"}]}";
data = response.getJSON("data");
json_list = data.toJSONList();
id_list = List();
for each item in json_list {
id_list.add(item.getJSON("id"));
}
info id_list; // Returns 1,2
```
FAILURE_MODES:
- Input TEXT not in valid JSON format throws a runtime error.
RELATED_DOCS:
- getJSON — ../../deluge-tasks/xml-manipulation/../../deluge-tasks/xml-manipulation/executeXpath.dou.md
- List functions — ../../datatypes/list.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/common/tojsonlist.html
assets/functions/deluge/built-in-functions/conversion/toList.dou.md
FILE_PURPOSE: Deluge `toList()` — split a TEXT string by a separator into a LIST.
MENTAL_MODEL: `text.toList(sep)` = LIST; tokenises text by separator; no-separator case returns single-element list.
WHEN_TO_USE:
- Split a delimited text string (e.g., slash-separated, comma-separated) into a LIST for iteration or indexing.
WHEN_NOT_TO_USE:
- When the input is a JSON array — use `toJSONList()` instead.
- When list elements must be TEXT type explicitly — use `toListString()` for comma-separated input.
TRIGGER_KEYWORDS:
toList, separator tokenise, toList separator, single-element list, consecutive-separator empty-element
RULES:
- Return type: LIST.
- Input types: TEXT (source_text), TEXT (separator).
- Syntax: `<source_text>.toList(<separator>)`.
- Text values punctuated by the separator become the list's elements.
- If separator is not provided or not found in source_text → entire source_text returned as a single-element list.
- If separators appear consecutively in source_text → empty string elements are created in the list at each consecutive position.
CANONICAL_EXAMPLES:
```deluge
products = "ZohoCRM/ZohoProjects";
productList = products.toList("/");
info productList; // Returns ZohoCRM,ZohoProjects
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- toListString — toListString.dou.md
- toJSONList — toJSONList.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/tolist.html
assets/functions/deluge/built-in-functions/conversion/toListString.dou.md
FILE_PURPOSE: Deluge `toListString()` — split a comma-separated TEXT or LIST into a LIST of TEXT elements.
MENTAL_MODEL: `"a,b,c".toListString()` = LIST of TEXT; all elements are TEXT regardless of original content type.
WHEN_TO_USE:
- Split a comma-delimited string (e.g., `"1,2,3,4,5"`) into a Deluge LIST where each element is TEXT.
- Convert a LIST into a LIST of TEXT elements for downstream string operations.
WHEN_NOT_TO_USE:
- When element type preservation is required (e.g., numeric elements should remain NUMBER) — use `toList()` instead.
- When the input is a JSON array string — use `toJSONList()` instead.
TRIGGER_KEYWORDS:
toListString, comma-separated to LIST, TEXT-element list, toList vs toListString, LIST-STRING split
RULES:
- Return type: LIST; all elements are of type TEXT.
- Input types: LIST, TEXT.
- All elements of the output list are TEXT regardless of the original input content.
CANONICAL_EXAMPLES:
```deluge
number_text = "1,2,3,4,5";
items = number_text.toListString();
// items is a LIST with TEXT elements: "1","2","3","4","5"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- toList — https://www.zoho.com/deluge/help/functions/list/to-list-string.html
SOURCE:
https://www.zoho.com/deluge/help/functions/list/to-list-string.html
assets/functions/deluge/built-in-functions/conversion/toLowerCase-toUpperCase.dou.md
FILE_PURPOSE: Deluge `toLowerCase()` / `toUpperCase()` — convert all string letters to lower or upper case.
MENTAL_MODEL: `str.toLowerCase()` = all-lowercase TEXT; `str.toUpperCase()` = all-uppercase TEXT; `lower()`/`upper()` are aliases.
WHEN_TO_USE:
- Normalise string case before storage or display formatting.
- Perform case-insensitive comparison by converting both strings to the same case first.
WHEN_NOT_TO_USE:
- When comparing strings case-insensitively — prefer `equalsIgnoreCase()` or `containsIgnoreCase()` directly to avoid creating an intermediate string.
TRIGGER_KEYWORDS:
toLowerCase(), toUpperCase(), lower(), upper(), lowercase conversion, uppercase conversion, case-normalise
RULES:
- Return type: TEXT.
- Input type: TEXT.
- `toLowerCase()` and `lower()` are aliases; all four syntaxes are equivalent.
- `toUpperCase()` and `upper()` are aliases; all four syntaxes are equivalent.
- Only letter characters are converted; digits, spaces, and special characters are unchanged.
CANONICAL_EXAMPLES:
```deluge
mainString = "Create online database applications";
newText = lower(mainString);
// returns "create online database applications"
newText = upper(mainString);
// returns "CREATE ONLINE DATABASE APPLICATIONS"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- equalsIgnoreCase — ../text/../text/equalsIgnoreCase.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/tolowerCase.html, https://www.zoho.com/deluge/help/functions/string/touppercase.html
assets/functions/deluge/built-in-functions/conversion/toMap.dou.md
FILE_PURPOSE: Deluge `toMap()` — convert a JSON-formatted TEXT string into a KEY-VALUE map.
MENTAL_MODEL: `jsonText.toMap()` = KEY-VALUE; input must be valid JSON; max 5 MB; non-JSON → runtime error.
WHEN_TO_USE:
- Parse a JSON object TEXT string (e.g., from an API response payload) into a Deluge KEY-VALUE map for `get()` / `put()` operations.
WHEN_NOT_TO_USE:
- When the TEXT is a JSON array — use `toJSONList()` instead.
TRIGGER_KEYWORDS:
toMap(), JSON-text parse, toMap 5MB limit, non-JSON runtime, JSON-to-map conversion
RULES:
- Return type: KEY-VALUE.
- Input type: TEXT (must be in JSON format).
- Syntax: `<json_text>.toMap()` (only one syntax form documented in source).
- Input TEXT must be valid JSON format; non-JSON throws a runtime error.
- Input TEXT size must be less than 5 MB.
CANONICAL_EXAMPLES:
```deluge
products = "{company:Zoho, product:CRM}";
info products.toMap();
// Returns {"product":"CRM","company":"Zoho"}
```
FAILURE_MODES:
- Non-JSON formatted TEXT input throws a runtime error.
- Input TEXT size ≥ 5 MB throws a runtime error.
RELATED_DOCS:
- toJSONList — toJSONList.dou.md
- Map functions — https://www.zoho.com/deluge/help/functions/key-value.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/tomap.html
assets/functions/deluge/built-in-functions/conversion/toNumber-toLong.dou.md
FILE_PURPOSE: Deluge `toNumber()` / `toLong()` — convert any expression to an integer NUMBER, truncating decimals.
MENTAL_MODEL: `expr.toNumber()` = cast to integer; decimal fraction is truncated; DATE-TIME → milliseconds since epoch.
WHEN_TO_USE:
- Convert a numeric text string (e.g., `"1234"`) to a NUMBER for arithmetic operations.
- Strip the fractional part of a DECIMAL to get an integer.
- Convert a DATE-TIME value to its millisecond representation (epoch offset from `'31-Dec-1969 16:00:00'`).
WHEN_NOT_TO_USE:
- When the TEXT contains non-numeric characters (e.g., `"1000abc"`) — throws a runtime error.
- When fraction preservation is required — use `toDecimal()` instead.
- When the TEXT is empty — throws a runtime error.
TRIGGER_KEYWORDS:
toNumber, toLong, decimal truncation, datetime milliseconds, numeric-text cast, alphanumeric cast fails
RULES:
- Return type: NUMBER.
- Input types accepted: NUMBER, DECIMAL, DATE-TIME, TEXT.
- `toNumber()` and `toLong()` are aliases; all four syntaxes are equivalent:
`expr.toNumber()`, `toNumber(expr)`, `expr.toLong()`, `toLong(expr)`.
- DECIMAL input: fraction part is silently truncated (e.g., `123.25` → `123`; `99.9` → `99`).
- DATE-TIME input: converted to milliseconds offset from `'31-Dec-1969 16:00:00'` (= 0);
`'31-Dec-1969 16:00:01'` → `1000`; `'31-Dec-1969 15:59:59'` → `-1000`.
- TEXT input: must contain only numeric characters; alphanumeric text throws a runtime error.
- Empty TEXT input throws a runtime error.
CANONICAL_EXAMPLES:
```deluge
decimal = 123.25;
info decimal.toNumber(); // Returns 123 (fraction truncated, not rounded)
num_text = "1234";
info num_text.toNumber(); // Returns 1234
alpha_text = "134A123B";
info alpha_text.toNumber(); // Throws runtime error — alphanumeric not allowed
marks = 99.9;
info toLong(marks); // Returns 99 (toLong is alias of toNumber)
```
FAILURE_MODES:
- TEXT containing alphanumeric characters (e.g., `"134A123B"`) throws a runtime error.
- Empty TEXT input throws a runtime error.
- DECIMAL fraction is silently truncated without rounding: `99.9` → `99`, not `100`.
RELATED_DOCS:
- toDecimal — toDecimal.dou.md
- Type conversion functions — https://www.zoho.com/deluge/help/functions/conversion.html
SOURCE:
https://www.zoho.com/deluge/help/functions/common/to-number.html, https://www.zoho.com/deluge/help/functions/common/tolong.html
assets/functions/deluge/built-in-functions/conversion/toString-toText.dou.md
FILE_PURPOSE: Deluge `toString()` / `toText()` — convert any expression to TEXT with optional datetime formatting.
MENTAL_MODEL: `expr.toString()` = TEXT representation; DATE-TIME input accepts optional format pattern and timezone for output control.
WHEN_TO_USE:
- Convert any Deluge value (number, date-time, boolean, etc.) to its text representation.
- Format a DATE-TIME value with a custom pattern (e.g., `"MMM dd, yy"`) or specific timezone.
- Convert epoch milliseconds to a human-readable datetime string.
TRIGGER_KEYWORDS:
toString, toText, dateTimeFormat pattern, TZ-database timezone, epoch-milliseconds format, datetime-literal null
RULES:
- Return type: TEXT.
- Input types: ALL DATA TYPES.
- `toString()` and `toText()` are aliases; all four syntaxes are equivalent.
- `dateTimeFormat` (optional): applies when input is DATE-TIME or NUMBER (milliseconds); uses date-time pattern letters (y, M, d, H, h, m, s, etc.); text within single quotes is treated as literal; non-literal characters outside single quotes cause `null` to be returned for those characters.
- `timeZone` (optional): must be an exact TZ database name (e.g., `"Europe/Moscow"`); applies when input is DATE-TIME; defaults to application settings timezone if omitted.
- NUMBER (milliseconds) with `dateTimeFormat`/`timeZone`: supported.
- TEXT in date-time format as input: `dateTimeFormat`/`timeZone` params take effect.
- Unrecognized date-time value → error.
CANONICAL_EXAMPLES:
```deluge
marks = 100;
info toString(marks); // Returns "100"
dateValue = '01-Jan-2019 10:15:30';
info toString(dateValue, "MMM dd, yy 'at' hh:mm:ss, E", "Europe/Moscow");
// Returns "Jan 01, 19 at 07:45:30, Fri"
milliValue = 1596719471334;
info toString(milliValue, "MMM dd, yy 'at' hh:mm:ss, E", "Europe/Moscow");
// Returns "Aug 06, 20 at 03:41:11, Thu"
```
FAILURE_MODES:
- Non-literal characters in `dateTimeFormat` outside single quotes return `null` for those characters in the output.
- Unrecognized date-time value throws an error.
RELATED_DOCS:
- Type conversion functions — https://www.zoho.com/deluge/help/functions/conversion.html
- toDate — toDate.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/common/tostring.html, https://www.zoho.com/deluge/help/functions/common/to-text.html
assets/functions/deluge/built-in-functions/conversion/toTime.dou.md
FILE_PURPOSE: Deluge `toTime()` — parse a datetime expression into DATE-TIME with explicit timezone conversion.
MENTAL_MODEL: `expr.toTime(mapping, tz)` = DATE-TIME; `mapping` describes the input structure; `tz` names the input timezone.
WHEN_TO_USE:
- Parse a TEXT datetime string with a custom pattern AND an explicit source timezone into DATE-TIME.
- Convert epoch milliseconds (NUMBER) to a DATE-TIME value.
- Convert a datetime TEXT from a known timezone to the application's default timezone.
WHEN_NOT_TO_USE:
- When no timezone conversion is needed and only date components (y, M, d) matter — use `toDate()`.
- When the expression is already a DATE-TIME: additional parameters are ignored; value is returned as-is.
TRIGGER_KEYWORDS:
toTime, dateTimeMapping toTime, timeZone TZ-database, GMT fallback, epoch-milliseconds toTime, app-settings timezone
RULES:
- Return type: DATE-TIME; formatted and timezone-converted per application settings.
- Input types: TEXT, NUMBER, DATE-TIME.
- NUMBER input: value `1` = `31-Dec-1969`; each increment of `1000` adds 1 second; `dateTimeMapping` is ignored.
- DATE-TIME input: additional parameters are ignored; throws a runtime error if month or day value exceeds valid range.
- `dateTimeMapping` (optional): applies to TEXT input only; applicable literals: `y`, `M`, `d`, `a`, `H`, `h`, `m`, `s`; separator must exactly match the expression separator.
- `timeZone` (optional): applies to TEXT input only; must be an exact TZ database name; if unrecognised, returns time converted to GMT (no error raised).
- Default timezone: application settings timezone.
CANONICAL_EXAMPLES:
```deluge
// App settings: dd-MMM-yyyy, Indian Standard Time (IST = Moscow + 2h 30m)
dateTimeString = "01,11,19 1:30:00 pm";
info toTime(dateTimeString, "MM,d,yyyy hh:mm:ss a", "Europe/Moscow");
// Returns 11-Jan-0019 16:00:00 (1:30 pm Moscow → 16:00 IST)
```
FAILURE_MODES:
- DATE-TIME input with month or day value exceeding valid range throws a runtime error.
- Unrecognised `timeZone` value: silently falls back to GMT; no error is raised.
- `dateTimeMapping` separator that does not exactly match the expression separator causes incorrect or silent mis-parsing.
RELATED_DOCS:
- toDate — toDate.dou.md
- Type conversion functions — https://www.zoho.com/deluge/help/functions/conversion.html
SOURCE:
https://www.zoho.com/deluge/help/functions/common/totime.html
assets/functions/deluge/built-in-functions/conversion/toWords.dou.md
FILE_PURPOSE: Deluge `toWords()` — convert NUMBER or DECIMAL to words in a target language.
MENTAL_MODEL: `num.toWords(langCode)` = TEXT; converts numeric value to human-readable words; supports 11 languages.
WHEN_TO_USE:
- Render a NUMBER or DECIMAL as words for display in documents, invoices, or reports.
- Generate locale-specific word representations (e.g., Indian numbering with `en-in`).
WHEN_NOT_TO_USE:
- When the NUMBER exceeds 18 integer digits — not supported.
- When the DECIMAL input's whole and decimal parts combined exceed 15 digits — not supported.
TRIGGER_KEYWORDS:
toWords(), number to words, toWords locale-tag, en-us toWords, en-in crore format, decimal to words
RULES:
- Return type: TEXT.
- Input types: NUMBER or DECIMAL (number), NUMBER or TEXT (language code).
- Both `<number>.toWords(<language>)` and `toWords(<number>, <language>)` syntaxes are valid.
- NUMBER input: value can contain up to 18 digits.
- DECIMAL input: whole and decimal parts collectively can be up to 15 digits.
- Supported language codes: `en-gb`, `en-us`, `fr`, `de`, `it`, `ja`, `ko`, `zh`, `en-in`, `es`, `pt`.
- Indian English (`en-in`) uses the Indian numbering system (e.g., 10,000,000 → "one crore").
- Negative numbers are supported (e.g., -12 → "minus twelve" in en-gb).
- DECIMAL values include "point" separator in English output (e.g., 1989.13 → "...point one three").
CANONICAL_EXAMPLES:
```deluge
whole_number = 1989;
info whole_number.toWords("en-us");
// returns "one thousand nine hundred eighty-nine"
decimal_number = 1989.13;
info decimal_number.toWords("en-us");
// returns "one thousand nine hundred eighty-nine point one three"
negative_number = -12;
info negative_number.toWords("en-gb");
// returns "minus twelve"
info (24).toWords("ko");
// returns "이십사"
info (10000000).toWords("en-gb");
// returns "ten million"
info (10000000).toWords("en-in");
// returns "one crore"
```
FAILURE_MODES:
- NUMBER input exceeding 18 digits is not supported.
- DECIMAL input where whole and decimal parts combined exceed 15 digits is not supported.
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/towords.html
assets/functions/deluge/built-in-functions/date-time/add-seconds.dou.md
## FILE_PURPOSE
Deluge addSeconds() — add (or subtract) seconds from a DATE-TIME value.
## MENTAL_MODEL
addSeconds(dateTime, n) = DATE-TIME shifted by n seconds; negative n subtracts.
## WHEN_TO_USE
- Add a number of seconds to a DATE-TIME value.
- Subtract seconds by passing a negative number.
## WHEN_NOT_TO_USE
- Adding/subtracting minutes — use addMinutes()/subMinutes() instead.
- Adding/subtracting hours — use addHour()/subHour() instead.
## TRIGGER_KEYWORDS
addSeconds, numberOfSecs, datetime seconds shift, negative seconds subtract
## RULES
- Syntax: `<var> = <dateTimeValue>.addSeconds(<numberOfSecs>);` or `addSeconds(<dateTimeValue>, <numberOfSecs>)`.
- Return type: DATE-TIME.
- Negative `numberOfSecs` subtracts that many seconds.
- Date-time without a time component defaults to 00:00:00.
- hour ≥ 24 → auto-reset to 00:00:00; minutes ≥ 60 → mm:ss reset to 00:00; seconds ≥ 60 → ss reset to 00.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
// DATE-TIME examples
currentDate = '01-Jan-2019';
info currentDate.addSeconds(70); // returns '01-Jan-2019 00:01:10'
currentDate = '01-Jan-2019 23:15:10';
info currentDate.addSeconds(2); // returns '01-Jan-2019 23:15:12'
```
## FAILURE_MODES
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
## RELATED_DOCS
- subSeconds — ../number/../number/subSeconds.dou.md
- addMinutes — add-sub-minutes.dou.md
- addHour — add-sub-hour.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/addseconds.html
assets/functions/deluge/built-in-functions/date-time/add-sub-business-day.dou.md
## FILE_PURPOSE
Deluge addBusinessDay() and subBusinessDay() — advance or retreat a date by business days.
## MENTAL_MODEL
addBusinessDay/subBusinessDay(date, n, weekends, holidays) = DATE-TIME skipping non-business days.
## WHEN_TO_USE
- Advance a date-time by a number of business days, skipping weekends and optional holidays.
- Retreat a date-time by a number of business days, skipping weekends and optional holidays.
## WHEN_NOT_TO_USE
- Simple calendar day arithmetic that should not skip weekends — use addDay()/subDay() instead.
- Hourly business-time calculations — use addBusinessHour() instead.
## TRIGGER_KEYWORDS
addBusinessDay, subBusinessDay, numberOfBusinessDays, weekends collection, holidays collection, business day skip
## RULES
- addBusinessDay() syntax: `<var> = <dateTimeValue>.addBusinessDay(<n>[, <weekends>[, <holidays>]]);`
or `addBusinessDay(<dateTimeValue>, <n>[, <weekends>[, <holidays>]])`.
- subBusinessDay() syntax: `<var> = <dateTimeValue>.subBusinessDay(<n>[, <weekends>[, <holidays>]]);`
or `subBusinessDay(<dateTimeValue>, <n>[, <weekends>[, <holidays>]])`.
- Both functions return DATE-TIME.
- Default business days: Monday–Friday (weekends = Saturday and Sunday).
- `weekends` (optional) — COLLECTION OF TEXT; defaults to `{"Saturday", "Sunday"}`.
- `holidays` (optional) — COLLECTION OF DATES; if omitted, all non-weekend days are working days.
- Negative `numberOfBusinessDays` in addBusinessDay() → returns given date-time unchanged.
- Negative `numberOfBusinessDays` in subBusinessDay() → returns given date-time unchanged.
- subBusinessDay() only: `numberOfBusinessDays` cannot exceed 365.
- Date-time without a time component defaults to time 00:00:00.
- hour ≥ 24 → auto-reset to 00:00:00; minutes ≥ 60 → mm:ss reset to 00:00; seconds ≥ 60 → ss reset to 00.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
// addBusinessDay: Friday + 1 business day = next Monday
currentDate = '15-Mar-2019';
newDate = currentDate.addBusinessDay(1); // returns '18-Mar-2019 00:00:00'
// addBusinessDay with custom weekends and a holiday
newDate = zoho.currentdate.addBusinessDay(5, {"Sunday"}, {'25-May-2020'});
// if starting from 23-May-2020, returns '30-May-2020 00:00:00'
// subBusinessDay: default weekends
info '21-Jul-2021'.subBusinessDay(5); // returns '14-Jul-2021 00:00:00'
// subBusinessDay with custom weekends and holidays
info '21-Jul-2021'.subBusinessDay(5, {"Sunday"}, {'20-Jul-2021', '19-Jul-2021'});
// returns '13-Jul-2021 00:00:00'
// Negative n in addBusinessDay → returns date-time unchanged
info '15-Mar-2019'.addBusinessDay(-3); // returns '15-Mar-2019 00:00:00'
```
## FAILURE_MODES
- Negative `numberOfBusinessDays` → returns the original date-time unchanged (no error thrown).
- subBusinessDay(): `numberOfBusinessDays` > 365 → runtime error.
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
## RELATED_DOCS
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
- addDay — add-sub-day.dou.md
- addBusinessHour — https://www.zoho.com/deluge/help/functions/datetime/addbusinesshour.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/addbusinessday.html, https://www.zoho.com/deluge/help/functions/datetime/subBusinessDay.html
assets/functions/deluge/built-in-functions/date-time/add-sub-day.dou.md
## FILE_PURPOSE
Deluge addDay() and subDay() — add or subtract calendar days from a date-time value.
## MENTAL_MODEL
addDay/subDay(date, n) = DATE-TIME shifted by n calendar days; negative n inverts the direction.
## WHEN_TO_USE
- Add a fixed number of calendar days to a DATE-TIME value.
- Subtract a fixed number of calendar days from a DATE-TIME value.
## WHEN_NOT_TO_USE
- Skipping weekends or holidays — use addBusinessDay()/subBusinessDay() instead.
- Month-level offsets — use addMonth()/subMonth() instead.
## TRIGGER_KEYWORDS
addDay, subDay, numberOfDays, calendar day shift, datetime addDay subDay
## RULES
- addDay() syntax: `<var> = <dateTimeValue>.addDay(<numberOfDays>);`
or `addDay(<dateTimeValue>, <numberOfDays>)`.
- subDay() syntax: `<var> = <dateTimeValue>.subDay(<numberOfDays>);`
or `subDay(<dateTimeValue>, <numberOfDays>)`.
- Both functions return DATE-TIME.
- Negative `numberOfDays` in addDay() subtracts that many days.
- Negative `numberOfDays` in subDay() adds that many days.
- Date-time without a time component defaults to time 00:00:00.
- hour ≥ 24 → auto-reset to 00:00:00; minutes ≥ 60 → mm:ss reset to 00:00; seconds ≥ 60 → ss reset to 00.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
currentDate = '01-Jan-2019';
currentDateTime = '01-Jan-2019 15:00:00';
newDate = currentDate.addDay(11); // returns '12-Jan-2019 00:00:00'
newDateTime = currentDateTime.addDay(40); // returns '10-Feb-2019 15:00:00'
newDate = currentDate.subDay(1); // returns '31-Dec-2018 00:00:00'
// Negative n in addDay → subtracts
info '15-Mar-2019'.addDay(-3); // returns '12-Mar-2019 00:00:00'
```
## FAILURE_MODES
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
## RELATED_DOCS
- addBusinessDay — add-sub-business-day.dou.md
- addMonth — add-sub-month.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/addday.html, https://www.zoho.com/deluge/help/functions/datetime/subday.html
assets/functions/deluge/built-in-functions/date-time/add-sub-hour.dou.md
## FILE_PURPOSE
Deluge addHour() and subHour() — add or subtract hours from a DATE-TIME value.
## MENTAL_MODEL
addHour/subHour(dateTime, n) = DATE-TIME shifted by n hours; negative n inverts direction.
## WHEN_TO_USE
- Add or subtract a number of hours from a DATE-TIME value.
## WHEN_NOT_TO_USE
- Adding/subtracting minutes — use addMinutes()/subMinutes() instead.
## TRIGGER_KEYWORDS
addHour, subHour, numberOfHours, datetime hour shift
## RULES
- addHour() syntax: `<var> = <dateTimeValue>.addHour(<n>);` or `addHour(<dateTimeValue>, <n>)`.
- subHour() syntax: `<var> = <dateTimeValue>.subHour(<n>);` or `subHour(<dateTimeValue>, <n>)`.
- Return type: DATE-TIME.
- Negative `numberOfHours` in addHour() subtracts; negative in subHour() adds.
- Date-time without a time component defaults to 00:00:00.
- hour ≥ 24 → auto-reset to 00:00:00; minutes ≥ 60 → mm:ss reset to 00:00; seconds ≥ 60 → ss reset to 00.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
// DATE-TIME examples
currentDate = '01-Jan-2019';
info currentDate.addHour(1); // returns '01-Jan-2019 01:00:00'
currentDate = '01-Jan-2019 23:15:10';
info currentDate.addHour(2); // returns '02-Jan-2019 01:15:10'
currentDate = '02-Jan-2019';
info currentDate.subHour(1); // returns '01-Jan-2019 23:00:00'
```
## FAILURE_MODES
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
## RELATED_DOCS
- addMinutes — add-sub-minutes.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/addhour.html, https://www.zoho.com/deluge/help/functions/datetime/subhour.html
assets/functions/deluge/built-in-functions/date-time/add-sub-minutes.dou.md
## FILE_PURPOSE
Deluge addMinutes() and subMinutes() — add or subtract minutes from a DATE-TIME value.
## MENTAL_MODEL
addMinutes/subMinutes(dateTime, n) = DATE-TIME shifted by n minutes; negative n inverts direction.
## WHEN_TO_USE
- Add or subtract a number of minutes from a DATE-TIME value.
## WHEN_NOT_TO_USE
- Adding/subtracting hours — use addHour()/subHour() instead.
- Adding/subtracting seconds — use addSeconds() instead.
## TRIGGER_KEYWORDS
addMinutes, subMinutes, numberOfMins, datetime minutes shift
## RULES
- addMinutes() syntax: `<var> = <dateTimeValue>.addMinutes(<n>);` or `addMinutes(<dateTimeValue>, <n>)`.
- subMinutes() syntax: `<var> = <dateTimeValue>.subMinutes(<n>);` or `subMinutes(<dateTimeValue>, <n>)`.
- Return type: DATE-TIME.
- Negative `numberOfMins` in addMinutes() subtracts; negative in subMinutes() adds.
- Date-time without a time component defaults to 00:00:00.
- hour ≥ 24 → auto-reset to 00:00:00; minutes ≥ 60 → mm:ss reset to 00:00; seconds ≥ 60 → ss reset to 00.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
// DATE-TIME examples
currentDate = '01-Jan-2019';
info currentDate.addMinutes(1); // returns '01-Jan-2019 00:01:00'
currentDate = '01-Jan-2019 23:59:10';
info currentDate.addMinutes(2); // returns '02-Jan-2019 00:01:10'
currentDate = '02-Jan-2019';
info currentDate.subMinutes(1); // returns '01-Jan-2019 23:59:00'
currentDate = '01-Jan-2019 23:59:10';
info currentDate.subMinutes(2); // returns '01-Jan-2019 23:57:10'
```
## FAILURE_MODES
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
## RELATED_DOCS
- addHour — add-sub-hour.dou.md
- addSeconds — add-seconds.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/addminutes.html, https://www.zoho.com/deluge/help/functions/datetime/subminutes.html
assets/functions/deluge/built-in-functions/date-time/add-sub-month.dou.md
## FILE_PURPOSE
Deluge addMonth() and subMonth() — add or subtract calendar months from a date-time value.
## MENTAL_MODEL
addMonth/subMonth(date, n) = DATE-TIME shifted by n months counted at month boundaries, not day count.
## WHEN_TO_USE
- Add a number of calendar months to a DATE-TIME value.
- Subtract a number of calendar months from a DATE-TIME value.
## WHEN_NOT_TO_USE
- Day-based arithmetic — use addDay()/subDay() instead.
- Year-level offsets — use addYear()/subYear() instead.
## TRIGGER_KEYWORDS
addMonth, subMonth, numberOfMonths, calendar month boundary, datetime month shift
## RULES
- addMonth() syntax: `<var> = <dateTimeValue>.addMonth(<numberOfMonths>);`
or `addMonth(<dateTimeValue>, <numberOfMonths>)`.
- subMonth() syntax: `<var> = <dateTimeValue>.subMonth(<numberOfMonths>);`
or `subMonth(<dateTimeValue>, <numberOfMonths>)`.
- Both functions return DATE-TIME.
- Negative `numberOfMonths` in addMonth() subtracts; negative in subMonth() adds.
- Month arithmetic counts calendar month boundaries, not the number of days in each month.
(01-Jan→01-Feb = 1 month; 01-Feb→01-Mar = 1 month, even though day counts differ.)
- Date-time without a time component defaults to 00:00:00.
- hour ≥ 24 → auto-reset to 00:00:00; minutes ≥ 60 → mm:ss reset to 00:00; seconds ≥ 60 → ss reset to 00.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
currentDate = '01-Jan-2019';
currentDateTime = '01-Jan-2019 15:00:00';
newDate = currentDate.addMonth(11); // returns '01-Dec-2019 00:00:00'
newDateTime = currentDateTime.addMonth(13); // returns '01-Feb-2020 15:00:00'
newDate = currentDate.subMonth(1); // returns '01-Dec-2018 00:00:00'
// Negative n in addMonth → subtracts
info '01-Mar-2019'.addMonth(-2); // returns '01-Jan-2019 00:00:00'
```
## FAILURE_MODES
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
## RELATED_DOCS
- addDay — add-sub-day.dou.md
- addYear — ../number/../number/addYear-subYear.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/addmonth.html, https://www.zoho.com/deluge/help/functions/datetime/submonth.html
assets/functions/deluge/built-in-functions/date-time/days-hours-between.dou.md
## FILE_PURPOSE
Deluge daysBetween() and hoursBetween() — count calendar days or whole hours between two date-times.
## MENTAL_MODEL
daysBetween/hoursBetween(start, end) = NUMBER; hoursBetween truncates minutes and seconds.
## WHEN_TO_USE
- Count the number of calendar days between two DATE-TIME values.
- Count the number of whole hours between two DATE-TIME values (ignoring partial minutes/seconds).
## WHEN_NOT_TO_USE
- Month-level or year-level span counting — use monthsBetween()/totalMonths() instead.
- Exact sub-hour time difference — hoursBetween() truncates minutes and seconds.
## TRIGGER_KEYWORDS
daysBetween, hoursBetween, date difference days, hours between dates, start end datetime count
## RULES
- daysBetween() syntax: `<var> = <startDateValue>.daysBetween(<endDateValue>);`
or `daysBetween(<startDateValue>, <endDateValue>)`.
- hoursBetween() syntax: `<var> = <startDateTimeValue>.hoursBetween(<endDateTimeValue>);`
or `hoursBetween(<startDateTimeValue>, <endDateTimeValue>)`.
- Both return NUMBER; both parameters are DATE-TIME.
- days360() is an alternative to daysBetween() and behaves the same way.
- hoursBetween() does NOT calculate the minutes and seconds difference; result is truncated to whole hours.
- hoursBetween(): if startDateTimeValue > endDateTimeValue → returns a negative number.
## CANONICAL_EXAMPLES
```deluge
// daysBetween
startDate = '1-Jan-2019';
endDate = '31-Dec-2019';
numberOfDays = daysBetween(startDate, endDate); // returns 364
newStartDate = '1-Jan-2020';
newEndDate = '31-Dec-2020';
numOfDays = daysBetween(newStartDate, newEndDate); // returns 365 (leap year)
// hoursBetween — truncates minutes/seconds
start_time = '18-Dec-2019 07:00:00';
end_time = '18-Dec-2019 08:59:59';
total_hours = hoursBetween(start_time, end_time); // returns 1 (59:59 of partial hour discarded)
start_time = '18-Dec-2019 09:00:00';
end_time = '19-Dec-2019 10:00:00';
total_hours = hoursBetween(start_time, end_time); // returns 25
```
## FAILURE_MODES
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error (daysBetween) / incorrect year error (hoursBetween).
## RELATED_DOCS
- days360 — ../number/../number/days360.dou.md
- monthsBetween — months-years-between.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/daysbetween.html, https://www.zoho.com/deluge/help/functions/datetime/hoursbetween.html
assets/functions/deluge/built-in-functions/date-time/eomonth.dou.md
## FILE_PURPOSE
Deluge eomonth() — shift a date by N months and return that month's last day.
## MENTAL_MODEL
eomonth(date, n) = DATE-TIME of last day in month (date + n months); n negative subtracts.
## WHEN_TO_USE
- Find the last day of the month that is N months ahead of a given date.
- Find the last day of the current month using offset 0: `eomonth(today, 0)`.
- Find the last day of a prior month using a negative offset.
## WHEN_NOT_TO_USE
- Getting start of month — use toStartOfMonth() instead.
- Adding months without needing the last-day snap — use addMonth() instead.
## TRIGGER_KEYWORDS
eomonth, end of month, last day of month, month offset last day, numberOfMonths eomonth
## RULES
- Syntax: `<var> = <dateTimeValue>.eomonth(<numberOfMonths>);`
or `eomonth(<dateTimeValue>, <numberOfMonths>)`.
- Returns DATE-TIME (date value only; time component is not included in the output).
- Time value of the input is not taken into account.
- Negative `numberOfMonths` subtracts months from dateTimeValue before returning the last day.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
currentDate = '01-Jan-2019';
newDate = currentDate.eomonth(1); // adds 1 month → Feb 2019; returns '28-Feb-2019'
```
## FAILURE_MODES
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- hour/min/sec value specified in more than 2 digits → runtime error.
- Year value has more than 4 digits → runtime error.
## RELATED_DOCS
- toStartOfMonth — to-start-of-month-week.dou.md
- addMonth — add-sub-month.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/eomonth.html
assets/functions/deluge/built-in-functions/date-time/months-years-between.dou.md
## FILE_PURPOSE
Deluge monthsBetween() and yearsBetween() — count months (30-day) or years (365-day) between dates.
## MENTAL_MODEL
monthsBetween/yearsBetween(start, end) = NUMBER using fixed unit; 1 month = 30 days, 1 year = 365 days.
## WHEN_TO_USE
- Count complete 30-day periods between two dates (monthsBetween()).
- Count complete 365-day periods between two dates (yearsBetween()).
## WHEN_NOT_TO_USE
- Calendar-month or calendar-year counting that respects actual month/year lengths — use totalMonths()/totalYears() instead.
- Time component is significant — both functions ignore the time value.
## TRIGGER_KEYWORDS
monthsBetween, yearsBetween, months between dates, years between dates, fixed 30 day month, 365 day year
## RULES
- monthsBetween() syntax: `<var> = <startDateTimeValue>.monthsBetween(<endDateTimeValue>);`
or `monthsBetween(<startDateTimeValue>, <endDateTimeValue>)`.
- yearsBetween() syntax: `<var> = <startDateTimeValue>.yearsBetween(<endDateTimeValue>);`
or `yearsBetween(<startDateTimeValue>, <endDateTimeValue>)`.
- Both return NUMBER; both parameters are DATE-TIME.
- monthsBetween() always treats 1 month as 30 days regardless of actual month length.
- 01-Feb-2020 to 01-Mar-2020 = 28 days → returns 0 (28 < 30 day threshold).
- yearsBetween() always treats 1 year as 365 days regardless of leap year.
- 02-Jan-2020 to 01-Jan-2021 = 365 days → returns 1 (fixed 365, ignores leap year).
- monthsBetween(): time value is not taken into account.
- Use monthsDiff() instead when month boundary semantics (not day count) are needed.
## CANONICAL_EXAMPLES
```deluge
// monthsBetween — fixed 30-day month
startDate = '31-Dec-1989';
endDate = '01-Jan-1990';
numberOfMonths = monthsBetween(startDate, endDate); // returns 0 (1 day < 30)
newEndDate = '01-Feb-1990';
numOfMonths = monthsBetween(startDate, newEndDate); // returns 1 (32 days >= 30)
startDate = '01-Feb-2020';
endDate = '01-Mar-2020';
info monthsBetween(startDate, endDate); // returns 0 (Feb 2020 = 28 days < 30)
// yearsBetween — fixed 365-day year
startDate = '1-Jan-2019';
endDate = '31-Dec-2019';
numberOfYears = yearsBetween(startDate, endDate); // returns 0 (364 days < 365)
startDate = '1-Jan-2020';
endDate = '31-Dec-2020';
numOfYears = yearsBetween(startDate, endDate); // returns 1 (365 days = 365)
```
## FAILURE_MODES
- hour/minute/second value specified in more than 2 digits → runtime error.
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
- monthsBetween(): span < 30 days → returns 0 even if it crosses a calendar month boundary.
- yearsBetween(): 365 days across a leap year counts as 1 year (fixed unit ignores extra day).
## RELATED_DOCS
- monthsDiff — https://www.zoho.com/deluge/help/functions/datetime/monthsdiff.html
- totalMonths — total-months-years.dou.md
- totalYears — total-months-years.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/monthsbetween.html, https://www.zoho.com/deluge/help/functions/datetime/yearsbetween.html
assets/functions/deluge/built-in-functions/date-time/now-today.dou.md
## FILE_PURPOSE
Deluge now and today — return the current date-time or current date-only value.
## MENTAL_MODEL
now = current DATE-TIME with time; today = current date with time forced to 00:00:00.
## WHEN_TO_USE
- Get the current date and time — use `now`.
- Get the current date with time component set to midnight — use `today`.
## WHEN_NOT_TO_USE
- Computing differences between dates — use daysBetween(), hoursBetween() etc. instead.
## TRIGGER_KEYWORDS
now, today, current datetime, current date no time, zoho.currentdate
## RULES
- `now` syntax: `<variable> = now;` — no parentheses, no arguments.
- `today` syntax: `<variable> = today;` — no parentheses, no arguments.
- Both return DATE-TIME.
- `now` returns the current date and time value.
- `today` returns the current date value with time component set to 00:00:00.
- Both return values in the format specified in application / service settings.
- Return format can be converted to a specific format and time zone using toString().
## CANONICAL_EXAMPLES
```deluge
currentDateTime = now; // e.g. returns '23-Oct-2023 14:35:22' (current date-time)
currentDate = today; // e.g. returns '23-Oct-2023 00:00:00' (current date, time = 00:00:00)
```
## FAILURE_MODES
- `today()` called with parentheses → runtime error `Not able to find 'today' function`; `today` is a keyword (no parentheses), not a function call. Valid forms: `today` (returns current date with time 00:00:00) or `zoho.currentdate` (returns current date-only). Choose based on whether the time component is needed.
## RELATED_DOCS
- toString — ../conversion/../conversion/toString-toText.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/now.html, https://www.zoho.com/deluge/help/functions/datetime/today.html
assets/functions/deluge/built-in-functions/date-time/to-start-of-month-week.dou.md
## FILE_PURPOSE
Deluge toStartOfMonth() and toStartOfWeek() — get first day of a date's month or week.
## MENTAL_MODEL
toStartOfMonth/toStartOfWeek(date) = DATE-TIME at midnight on period start; week starts Sunday.
## WHEN_TO_USE
- Get the first day of the month a given date falls in.
- Get the first day of the week a given date falls in (week starts on Sunday).
## WHEN_NOT_TO_USE
- Getting end of month — use eomonth() instead.
- Getting current period start — combine with now or today: `now.toStartOfMonth()`.
## TRIGGER_KEYWORDS
toStartOfMonth, toStartOfWeek, first day of month, first day of week, Sunday week start
## RULES
- toStartOfMonth() syntax: `<var> = <dateTimeValue>.toStartOfMonth();`
or `toStartOfMonth(<dateTimeValue>)`.
- toStartOfWeek() syntax: `<var> = <dateTimeValue>.toStartOfWeek();`
or `toStartOfWeek(<dateTimeValue>)`.
- Both return DATE-TIME; time component of the result is always 00:00:00.
- Time value of the input is not taken into account.
- toStartOfWeek(): Sunday is the first day of the week.
- Runtime error: 2-digit date value exceeds number of days in that month → runtime error.
- Runtime error: month value specified in 3 or more characters that is incorrect → runtime error.
- Returns null (NOT a runtime error) if: month value < 3 characters; hour/min/sec > 2 digits;
date value > 2 digits; year value > 4 digits.
- Return value format follows application settings.
## CANONICAL_EXAMPLES
```deluge
// toStartOfMonth
currentDate = '15-Mar-2019 15:00:00';
startDate = currentDate.toStartOfMonth(); // returns '01-Mar-2019 00:00:00'
// toStartOfWeek (15-Mar-2019 is Friday; week start = Sunday 10-Mar-2019)
startDate = currentDate.toStartOfWeek(); // returns '10-Mar-2019 00:00:00'
```
## FAILURE_MODES
- 2-digit date value exceeds days in that month → runtime error.
- Month value in 3+ characters that is incorrect → runtime error.
- Month value specified in fewer than 3 characters → returns null (no error thrown).
- hour/min/sec value > 2 digits → returns null (no error thrown).
- Date value specified in more than 2 digits → returns null (no error thrown).
- Year value > 4 digits → returns null (no error thrown).
## RELATED_DOCS
- eomonth — eomonth.dou.md
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/tostartofmonth.html, https://www.zoho.com/deluge/help/functions/datetime/tostartofweek.html
assets/functions/deluge/built-in-functions/date-time/total-months-years.dou.md
## FILE_PURPOSE
Deluge totalMonths() and totalYears() — count calendar months or years respecting actual day counts.
## MENTAL_MODEL
totalMonths/totalYears(start, end) = NUMBER using actual calendar units; leap years and month lengths respected.
## WHEN_TO_USE
- Count exact complete calendar months between two dates, where each month's length is its actual day count.
- Count exact complete calendar years between two dates, accounting for leap years.
## WHEN_NOT_TO_USE
- Fixed-unit counting where 1 month = 30 days or 1 year = 365 days — use monthsBetween()/yearsBetween() instead.
- Time component is significant — both functions ignore the time value.
## TRIGGER_KEYWORDS
totalMonths, totalYears, exact calendar months, leap year aware years, calendar month count
## RULES
- totalMonths() syntax: `<var> = <startDateTimeValue>.totalMonths(<endDateTimeValue>);`
or `totalMonths(<startDateTimeValue>, <endDateTimeValue>)`.
- totalYears() syntax: `<var> = <startDateTimeValue>.totalYears(<endDateTimeValue>);`
or `totalYears(<startDateTimeValue>, <endDateTimeValue>)`.
- Both return NUMBER; both parameters are DATE-TIME.
- totalMonths() considers actual days in each month (not a fixed 30).
- 01-Feb-2020 to 01-Mar-2020: Feb 2020 has 28 days; 28 = 1 full Feb → returns 1.
- Compare: monthsBetween() returns 0 for this case (28 < 30).
- totalYears() considers actual year length including leap years.
- 02-Jan-2020 to 01-Jan-2021: leap year 2020 has 366 days; 365 < 366 → returns 0.
- Compare: yearsBetween() returns 1 for this case (365 = fixed 365).
- Time value is not taken into account for either function.
## CANONICAL_EXAMPLES
```deluge
// totalMonths
startDate = '31-Dec-1989';
endDate = '01-Jan-1990';
numMonths = totalMonths(startDate, endDate); // returns 0 (1 day < full month)
newEndDate = '01-Feb-1990';
numMonths2 = totalMonths(startDate, newEndDate); // returns 1
startDate = '01-Feb-2020';
endDate = '01-Mar-2020';
info totalMonths(startDate, endDate); // returns 1 (Feb 2020 = 28 days, full month)
// totalYears
startDate = '1-Jan-2019';
endDate = '31-Dec-2019';
numYears = totalYears(startDate, endDate); // returns 0 (364 days)
newStartDate = '1-Jan-2019';
newEndDate = '1-Jan-2020';
numYears2 = totalYears(newStartDate, newEndDate); // returns 1
```
## FAILURE_MODES
- Date value exceeds the number of days in that month → runtime error.
- Incorrect month value → runtime error.
- Year value has more than 4 digits → runtime error.
- totalYears(): 365 days spanning a leap year is not a full year → returns 0, not 1.
## RELATED_DOCS
- monthsBetween — months-years-between.dou.md
- yearsBetween — months-years-between.dou.md
- monthsDiff — https://www.zoho.com/deluge/help/functions/datetime/monthsdiff.html
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
## SOURCE
https://www.zoho.com/deluge/help/functions/datetime/totalmonth.html, https://www.zoho.com/deluge/help/functions/datetime/totalyear.html
assets/functions/deluge/built-in-functions/list/list-add-addall.dou.md
## FILE_PURPOSE
Deluge list add() and addAll() — append element(s) to a list in place.
## MENTAL_MODEL
add(el) appends one element (list arg = nested); addAll(list2) spreads list2 into list1.
## WHEN_TO_USE
- Append a single element (including another list as a nested element) to an existing list.
- Append all elements from a second list individually into a first list (flat merge).
## WHEN_NOT_TO_USE
- Getting elements that appear in both lists — use intersect() instead.
- Extracting a slice of a list — use subList() instead.
## TRIGGER_KEYWORDS
list.add, list.addAll, append element list, merge lists, nested list element
## RULES
- add() syntax: `<listVariable>.add(<addElement>);` — no return value; mutates listVariable in place.
- addAll() syntax: `<listVariableOne>.addAll(<listVariableTwo>);` — no return value; mutates listVariableOne in place.
- add(): if addElement is a list, it is added as a single nested element (not spread).
- `list.add({"CRM", "Projects"})` → `{"Leads", {"Contacts", "Deals"}}`, not `{"Leads", "Contacts", "Deals"}`.
- addAll(): all elements of listVariableTwo are individually appended to listVariableOne.
- Mixed data types are allowed if the list is not restricted to a specific data type.
- Neither add() nor addAll() has a return value.
## CANONICAL_EXAMPLES
```deluge
// add() — list arg becomes a single nested element
Products = List();
Products.add("Leads");
Products.add({"CRM", "Projects"});
// Products is now {"Leads", {"Contacts", "Deals"}}
// addAll() — spreads all elements from the second list
listOne = {"Leads"};
listTwo = {"CRM", "Projects"};
listOne.addAll(listTwo);
// listOne is now {"Leads", "Contacts", "Deals"}
```
## FAILURE_MODES
*(none in source)*
## RELATED_DOCS
- addAll — https://www.zoho.com/deluge/help/functions/list/add.html
- intersect — list-intersect-sublist.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
## SOURCE
https://www.zoho.com/deluge/help/functions/list/add.html, https://www.zoho.com/deluge/help/functions/list/addall.html
assets/functions/deluge/built-in-functions/list/list-average-median.dou.md
FILE_PURPOSE: Deluge `average()` / `median()` — compute mean and median of a NUMBER or DECIMAL list.
MENTAL_MODEL: `list.average()` = DECIMAL mean; `list.median()` = middle value; even count → mean of two middle values.
WHEN_TO_USE:
- Computing the arithmetic mean of a numeric list → use average()
- Finding the statistical median of a numeric list → use median()
WHEN_NOT_TO_USE:
- Need the maximum value → use largest()
- Need the minimum value → use smallest()
- Need the Nth ranked value → use nthLargest() / nthSmallest()
TRIGGER_KEYWORDS: average(), median(), number-list-aggregate, even-count-median-average, decimal-list-stats
RULES:
1. Return type: DECIMAL for both average() and median().
2. Input: NUMBER LIST or DECIMAL LIST. Numerical values can be in double quotes.
3. median(): sorts list in ascending order internally, then returns the middle element.
4. median() with even number of elements: returns the average of the two middle elements.
CANONICAL_EXAMPLES:
```deluge
numberList = {1, 5, 2, 3};
averageValue = numberList.average(); // → 2.75 (mean: (1+5+2+3)/4)
medianValue = numberList.median(); // → 2.5 (sorted: 1,2,3,5 → avg of 2 and 3)
// Functional forms
avg = average({1, 5, 2, 3}); // → 2.75
med = median({1, 5, 2, 3}); // → 2.5
```
FAILURE_MODES:
- median() on even-sized list returns average of two middle values, not one of them
- Passing non-numeric elements → behavior not documented; use NUMBER LIST or DECIMAL LIST only
RELATED_DOCS:
- largest() / smallest() — list-largest-smallest.dou.md
- nthLargest() / nthSmallest() — list-nthLargest-nthSmallest.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/average.html, https://www.zoho.com/deluge/help/functions/number/median.html
assets/functions/deluge/built-in-functions/list/list-contains.dou.md
## FILE_PURPOSE
Deluge list contains() — exact membership test for a list element.
## MENTAL_MODEL
list.contains(el) = BOOLEAN true only on exact case-sensitive type-sensitive whole-element match.
## WHEN_TO_USE
- Check whether a specific value exists as an element in a list (exact match, case-sensitive).
## WHEN_NOT_TO_USE
- Case-insensitive membership check — contains() is strictly case-sensitive.
- Searching for a map key within a nested map element — contains() matches the entire element, not a key inside it.
- Partial list match — contains() does not match a subset of a nested list element.
## TRIGGER_KEYWORDS
list.contains, list membership check, element in list boolean, exact match list, case sensitive contains
## RULES
- Syntax: `<variable> = <listVariable>.contains(<searchElement>);`
- Returns BOOLEAN: true if searchElement is found as an element, false otherwise.
- searchElement accepts ALL DATA TYPES.
- Match is exact: case-sensitive AND type-sensitive.
- String `"1"` does NOT match number `1` in the list.
- String `"zoho"` does NOT match `"Zoho"` (case mismatch).
- Map key is NOT the same as the whole map element.
- `"version"` does NOT match element `{"version":5}`.
- `{"Version":5}` does NOT match `{"version":5}` (case mismatch on key).
- Partial list is NOT matched against a full nested list element.
- `{"India"}` does NOT match element `{"India", "USA", "Australia"}`.
## CANONICAL_EXAMPLES
```deluge
listVar = {1, "2", "Zoho", "CRM", {"version":8}, {"India", "USA", "Australia"}};
a = listVar.contains("1"); // false — type mismatch (string vs number 1)
b = listVar.contains(2); // false — type mismatch (number vs string "2")
c = listVar.contains("zoho"); // false — case mismatch ("zoho" vs "Zoho")
d = listVar.contains("CRM"); // true — exact match
e = listVar.contains("version"); // false — map key ≠ whole element
f = listVar.contains({"Version": 5}); // false — case mismatch on map key
g = listVar.contains({"India"}); // false — partial list ≠ full list element
```
## FAILURE_MODES
- Type mismatch (string vs number for same value) → returns false.
- Case mismatch on string values or map keys → returns false.
- Searching for a map key instead of the full map element → returns false.
- Searching for a partial list instead of the full nested list element → returns false.
## RELATED_DOCS
- indexOf — list-indexOf-lastIndexOf.dou.md
- notContains (text) — list-notContains-isEmpty.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
## SOURCE
https://www.zoho.com/deluge/help/functions/list/contains.html
assets/functions/deluge/built-in-functions/list/list-get-size-clear.dou.md
## FILE_PURPOSE
Deluge list get() size() clear() — index access, element count, and list emptying.
## MENTAL_MODEL
get(i) = element at index i (0-based); size() = element count; clear() empties the list.
## WHEN_TO_USE
- Retrieve a specific element from a list by its zero-based index position.
- Get the total number of elements in a list (including nested lists as single elements).
- Remove all elements from a list.
## WHEN_NOT_TO_USE
- Searching by value rather than position — use contains() or indexOf() instead.
- Extracting a range of elements — use subList() instead.
## TRIGGER_KEYWORDS
list.get, list.size, list.clear, list index access, count list elements, empty list
## RULES
- get() syntax: `<variable> = <listVariable>.get(<indexValue>);` — return type matches element's data type.
- size() syntax: `<variable> = <listVariable>.size();` — returns NUMBER.
- clear() syntax: `<listVariable>.clear();` — no return value; empties the list in place.
- Index starts from 0; valid range for get(): 0 to (size() - 1).
- get(): indexValue must not be negative → runtime error.
- get(): indexValue must not exceed list size → runtime error if it does.
- size(): a nested list counts as one element regardless of how many elements it contains.
- clear(): removes all elements; list variable remains but is empty.
## CANONICAL_EXAMPLES
```deluge
// get() — zero-based index; nested list at index 2 is one element
listVar = {"Leads", "Contacts", {"Deals", "Tasks"}};
elementValue = listVar.get(2); // returns {"Projects", "Reports"}
// size() — nested list {"Zoho Cliq", "Zoho CRM"} counts as 1
listVar = {"Projects", "Mail", {"Zoho Cliq", "Zoho CRM"}};
countElements = listVar.size(); // returns 3
// clear()
listVar.clear(); // listVar is now {}
```
## FAILURE_MODES
- get(): negative indexValue → runtime error.
- get(): indexValue ≥ list size → runtime error.
## RELATED_DOCS
- subList — list-intersect-sublist.dou.md
- contains — list-contains.dou.md
- indexOf — list-indexOf-lastIndexOf.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
## SOURCE
https://www.zoho.com/deluge/help/functions/list/get.html, https://www.zoho.com/deluge/help/functions/list/size.html, https://www.zoho.com/deluge/help/functions/list/clear.html
assets/functions/deluge/built-in-functions/list/list-indexOf-lastIndexOf.dou.md
FILE_PURPOSE: Deluge `indexOf()` / `lastIndexOf()` — find first or last index of element in a LIST.
MENTAL_MODEL: `list.indexOf(el)` = first 0-based index of el; -1 if absent. `lastIndexOf` = last occurrence.
WHEN_TO_USE:
- Finding the position of an element within a LIST
- Checking existence by testing for index != -1
WHEN_NOT_TO_USE:
- Need boolean membership test → use contains() / notContains()
- Need to retrieve element by position → use get()
TRIGGER_KEYWORDS: indexOf(), lastIndexOf(), 0-based-list-index, not-found-returns-minus-1, first-vs-last-occurrence
RULES:
1. Return type: NUMBER. Index is 0-based (first element = 0).
2. Returns -1 if searchElement is not found.
3. indexOf() returns the FIRST occurrence index; lastIndexOf() returns the LAST occurrence index.
4. Input: LIST (listVariable), any data type (searchElement).
CANONICAL_EXAMPLES:
```deluge
listVar = {"Leads", "Contacts", "Deals"};
firstIndex = listVar.indexOf("Leads"); // → 0
lastIndex = listVar.lastIndexOf("Leads"); // → 0 (appears only once)
missing = listVar.indexOf("Books"); // → -1 (not found)
```
FAILURE_MODES:
- searchElement not found → returns -1 (no error raised)
- Existence guard: test for != -1, NOT for > 0 — index 0 is a valid position
RELATED_DOCS:
- contains() — list-contains.dou.md
- get() — list-get-size-clear.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
SOURCE:
https://www.zoho.com/deluge/help/functions/list/indexof.html, https://www.zoho.com/deluge/help/functions/list/lastindexof.html
assets/functions/deluge/built-in-functions/list/list-intersect-sublist.dou.md
## FILE_PURPOSE
Deluge list intersect() and subList() — get common elements or a slice of a list.
## MENTAL_MODEL
intersect(list2) = LIST of shared elements; subList(start, end) = LIST slice [start, end).
## WHEN_TO_USE
- Get the elements that appear in both of two lists (intersect()).
- Extract a contiguous range of elements from a list by index positions (subList()).
## WHEN_NOT_TO_USE
- Combining all elements from two lists — use addAll() instead.
- Retrieving a single element by index — use get() instead.
## TRIGGER_KEYWORDS
list.intersect, list.subList, common elements two lists, list slice, index range list
## RULES
- intersect() syntax: `<variable> = <listVariableOne>.intersect(<listVariableTwo>);`
- subList() syntax: `<variable> = <listVariable>.subList(<start_index>, <end_index>);`
- Both return LIST.
- subList(): index starts from 0.
- subList(): range is inclusive of start_index and exclusive of end_index — [start_index, end_index).
- subList(): if end_index is omitted → returns all elements from start_index to end of list.
- subList(): negative start_index → runtime error.
- subList(): end_index exceeds list size → runtime error.
## CANONICAL_EXAMPLES
```deluge
// intersect() — elements present in both lists
listOne = {"Zoho Cliq", "Zoho CRM", "Zoho Projects"};
listTwo = {"Zoho Mail", "Zoho Analytics", "Zoho Cliq"};
commonList = listOne.intersect(listTwo); // returns {"Zoho Cliq"}
// subList() — [start_index inclusive, end_index exclusive)
listVar = {1, 2, 3, 4};
newListVar = listVar.subList(0, 3); // returns {1, 2, 3} (index 0,1,2; index 3 excluded)
```
## FAILURE_MODES
- subList(): negative start_index → runtime error.
- subList(): end_index > list size → runtime error.
## RELATED_DOCS
- addAll — list-add-addall.dou.md
- get — list-get-size-clear.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
## SOURCE
https://www.zoho.com/deluge/help/functions/list/intersect.html, https://www.zoho.com/deluge/help/functions/list/sublist.html
assets/functions/deluge/built-in-functions/list/list-largest-smallest.dou.md
FILE_PURPOSE: Deluge `largest()` / `smallest()` — max and min DECIMAL from a numeric list.
MENTAL_MODEL: `list.largest()` = max DECIMAL; `list.smallest()` = min DECIMAL; input: NUMBER LIST or DECIMAL LIST.
WHEN_TO_USE:
- Finding the maximum value in a numeric list → use largest()
- Finding the minimum value in a numeric list → use smallest()
WHEN_NOT_TO_USE:
- Need the Nth largest or Nth smallest → use nthLargest() / nthSmallest()
- Need mean or median → use average() / median()
TRIGGER_KEYWORDS: largest(), smallest(), list-max-min, decimal-return-number-list, number-list-extremes
RULES:
1. Return type: DECIMAL for both largest() and smallest().
2. Input: NUMBER LIST or DECIMAL LIST. Numerical values can be in double quotes.
CANONICAL_EXAMPLES:
```deluge
numberList = {1, 5, 2, 3};
largestValue = numberList.largest(); // → 5
smallestValue = numberList.smallest(); // → 1
// Functional forms
l = largest({1, 5, 2, 3}); // → 5
s = smallest({1, 5, 2, 3}); // → 1
```
FAILURE_MODES:
- Passing non-numeric elements → behavior not documented; use NUMBER LIST or DECIMAL LIST only
RELATED_DOCS:
- nthLargest() / nthSmallest() — list-nthLargest-nthSmallest.dou.md
- average() / median() — list-average-median.dou.md
- max() (two-value comparison) — ../number/../number/stat-max-min.dou.md
- min() (two-value comparison) — ../number/../number/stat-max-min.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/largest.html, https://www.zoho.com/deluge/help/functions/number/smallest.html
assets/functions/deluge/built-in-functions/list/list-notContains-isEmpty.dou.md
FILE_PURPOSE: Deluge `notContains()` / `isEmpty()` — boolean membership absence and empty checks on TEXT/LIST/MAP.
MENTAL_MODEL: `notContains(val)` = !contains(); case-sensitive. `isEmpty()` = true if zero-length; whitespace TEXT is NOT empty.
WHEN_TO_USE:
- Verifying a value is absent from a TEXT, LIST, MAP, or COLLECTION → use notContains()
- Checking whether a list, collection, map, or text has zero elements/length → use isEmpty()
WHEN_NOT_TO_USE:
- Need to check presence → use contains()
- Need to guard for whitespace-only strings → use isBlank() (isEmpty returns false for " ")
- Need to guard for null → use isNull()
TRIGGER_KEYWORDS: notContains(), isEmpty(), case-sensitive-membership, boolean-collection-check, whitespace-not-empty
RULES:
1. notContains() return type: BOOLEAN. Input types: TEXT, LIST, MAP, or COLLECTION. searchValue: any data type.
2. notContains() is case-sensitive — "crm" does not match "CRM".
3. notContains() on MAP: checks KEY existence only — does NOT search values.
4. notContains() on key-value COLLECTION: checks KEY existence only.
5. notContains() on index-value COLLECTION and LIST: checks element values.
6. isEmpty() return type: BOOLEAN. Input: TEXT, LIST, MAP, or COLLECTION.
7. isEmpty() on TEXT: a whitespace-only string (e.g. " ") → false (whitespace is NOT empty).
8. isEmpty() on empty Collection() or empty map {} → true.
CANONICAL_EXAMPLES:
```deluge
// notContains on TEXT (substring search, case-sensitive)
"Zoho CRM".notContains("CRM"); // → false ("CRM" is a substring)
"Zoho CRM".notContains("crm"); // → true (case-sensitive mismatch)
// notContains on LIST (element membership)
products = {"Leads", "Contacts", "Deals"};
products.notContains("Books"); // → true
// notContains on MAP (checks KEYS, not values)
m = Map(); m.put("product", "CRM"); m.put("subscription", "yearly");
m.notContains("price"); // → true (key "price" is absent)
// isEmpty
{"Projects", "Mail"}.isEmpty(); // → false
Collection().isEmpty(); // → true (empty collection)
" ".isEmpty(); // → false (whitespace is NOT empty)
```
FAILURE_MODES:
- notContains() on MAP or key-value COLLECTION checks keys only — does NOT scan values
- isEmpty(" ") → false; use isBlank() to detect whitespace-only strings
- Confusing isEmpty with isNull or isBlank — three distinct functions with different semantics
RELATED_DOCS:
- contains() — list-contains.dou.md
- isBlank() / isNull() / isEmpty() differences — https://www.zoho.com/deluge/help/functions/common/isnull-isblank-isempty-difference.html
- Functions returning boolean — https://www.zoho.com/deluge/help/functions-returning-boolean.html
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
- List functions — https://www.zoho.com/deluge/help/functions/list.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/notcontains.html, https://www.zoho.com/deluge/help/functions/text/is-empty.html
assets/functions/deluge/built-in-functions/list/list-nthLargest-nthSmallest.dou.md
FILE_PURPOSE: Deluge `nthLargest()` / `nthSmallest()` — retrieve the Nth ranked value from a numeric COLLECTION.
MENTAL_MODEL: `col.nthLargest(n)` = nth from top (1-based); `col.nthSmallest(n)` = nth from bottom (1-based); index > size → runtime.
WHEN_TO_USE:
- Need the 2nd largest, 3rd smallest, or any ranked position in a numeric collection
- Ranked value retrieval beyond the absolute max/min
WHEN_NOT_TO_USE:
- Need absolute max → use largest()
- Need absolute min → use smallest()
- Rank 1 from a LIST input — largest() / smallest() accept LIST; nthLargest() / nthSmallest() require COLLECTION
TRIGGER_KEYWORDS: nthLargest(), nthSmallest(), 1-based-rank-index, collection-numeric-rank, rank-exceeds-size-runtime
RULES:
1. Return type: NUMBER / DECIMAL.
2. Input: COLLECTION of numerical values (NUMBER and DECIMAL). Index: NUMBER (1-based).
3. nthLargest(n): sorts values descending, returns element at rank n.
4. nthSmallest(n): sorts values ascending, returns element at rank n.
5. Index starts from 1 (NOT 0).
6. Index cannot exceed the size of the collection → runtime error if exceeded.
CANONICAL_EXAMPLES:
```deluge
// nthLargest — COLLECTION input, 1-based index
marks = Collection(300, 455, 124, 926, 780);
result = marks.nthlargest(5); // → 124 (5th largest of 5 = the minimum)
// nthSmallest — example in source uses list {} notation
marks = {300, 455, 124, 926, 780};
result = marks.nthSmallest(2); // → 300 (2nd smallest after 124)
```
FAILURE_MODES:
- Index starts from 1 — nthLargest(0) or nthSmallest(0) exceeds range → runtime error
- Index > collection size → runtime error
- nthLargest source uses Collection(); nthSmallest source example uses list {} — source inconsistency; Collection is the documented input type
RELATED_DOCS:
- largest() / smallest() — list-largest-smallest.dou.md
- average() / median() — list-average-median.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/nth-largest.html, https://www.zoho.com/deluge/help/functions/number/nth-smallest.html
assets/functions/deluge/built-in-functions/list/list-remove-removeAll-removeElement.dou.md
FILE_PURPOSE: Deluge `remove()` / `removeAll()` / `removeElement()` — three ways to remove from a LIST.
MENTAL_MODEL: remove(i) removes+returns by index; removeAll(list) and removeElement(val) mutate list in-place.
WHEN_TO_USE:
- Remove element by index and capture it → use remove()
- Bulk-remove all elements present in another list → use removeAll()
- Remove a specific element by value → use removeElement()
WHEN_NOT_TO_USE:
- Need to empty the entire list → use clear()
- Need to remove by index without capturing the element → remove() still works but discards the return value
TRIGGER_KEYWORDS: remove(), removeAll(), removeElement(), list-mutate-removal, index-vs-element-based-removal
RULES:
1. remove(indexValue): removes AND returns the element at the 0-based indexValue. Return type: TEXT. Mutates list.
2. remove(): negative indexValue → runtime error. indexValue >= list size → runtime error.
3. removeAll(removeListVariable): removes all elements in removeListVariable from listVariable. If an element is not found, listVariable is unchanged. No return value (void; mutates in-place).
4. removeElement(searchElement): removes first occurrence of searchElement by value. If not found, list unchanged. No return value (void; mutates in-place).
5. removeElement(): if the list is a typed list and searchElement data type mismatches the list's type → runtime error.
CANONICAL_EXAMPLES:
```deluge
// remove() — by index, returns removed element
Product = {"Leads", "Contacts", "Deals"};
element = Product.remove(1); // element = "Contacts"; Product = {"Leads", "Deals"}
// removeAll() — removes all elements present in the second list
Products = {"Leads", "Contacts", "Deals"};
removeProducts = {"CRM", "Projects"};
Products.removeAll(removeProducts); // Products = {"Leads"}
// removeElement() — by value, first occurrence
Product = {"Leads", "Contacts", "Deals"};
Product.removeElement("Contacts"); // Product = {"Leads", "Deals"}
```
FAILURE_MODES:
- remove() with negative index → runtime error
- remove() with index >= list size → runtime error
- removeElement() with type-mismatched value on a typed list → runtime error
- removeAll() / removeElement() return no value — do NOT assign to a variable expecting a result
RELATED_DOCS:
- clear() — list-get-size-clear.dou.md
- indexOf() — list-indexOf-lastIndexOf.dou.md
- List functions — https://www.zoho.com/deluge/help/functions/list.html
SOURCE:
https://www.zoho.com/deluge/help/functions/list/remove.html, https://www.zoho.com/deluge/help/functions/list/removeall.html, https://www.zoho.com/deluge/help/functions/list/removeelement.html
assets/functions/deluge/built-in-functions/list/list-sort-distinct.dou.md
## FILE_PURPOSE
Deluge list sort() and distinct() — sort elements or return unique values from a list.
## MENTAL_MODEL
sort(bool) = LIST ordered asc/desc; distinct() = LIST with duplicates removed; cross-type dedup applies.
## WHEN_TO_USE
- Sort a list in ascending or descending order (sort()).
- Remove duplicate values from a list, retaining one of each unique value (distinct()).
## WHEN_NOT_TO_USE
- Sorting a list with mixed data types — sort() returns null for such lists.
## TRIGGER_KEYWORDS
list.sort, list.distinct, sort ascending descending, remove duplicates list, list unique values
## RULES
- sort() syntax: `<variable> = <listVariable>.sort(<booleanValue>);`
— both `<variable>` and `<booleanValue>` are optional.
- distinct() syntax: `<variable> = <listVariable>.distinct();`
- Both return LIST.
- sort(): `true` → ascending; `false` → descending; omitted → ascending.
- sort(): for text lists, letter case is checked before the actual letter (case-aware ordering).
Sort order: `! " # $ % & ' ( ) * + , - . / : ; < = > ? @ <capitals> [ ] ^ _ ` <lowercase> { | } ~`
- sort(): if list contains elements of different data types → returns null.
- distinct(): treats STRING and NUMBER representations of the same value as duplicates.
- Input `{"1", 1, 1, 2, 2, 3}` → output `{1, 1, 2, 3}` (text "1" treated as duplicate of number 1).
## CANONICAL_EXAMPLES
```deluge
// sort() — uppercase letters sort before lowercase
listVar = {"Leads", "crm", "Zoho Leads"};
sortOrder = listVar.sort(true);
// returns {"Leads", "Zoho Leads", "crm"}
// Explanation: "Leads" and "Zoho Leads" (capital L/Z) sort before "crm" (lowercase c)
// distinct() — cross-type: text "1" and number 1 treated as duplicates
listVar = {"1", 1, 1, 2, 2, 3};
uniqueList = listVar.distinct();
// returns {1, 1, 2, 3}
```
## FAILURE_MODES
- sort(): list contains mixed data types → returns null.
- distinct(): string and number representations of the same value are treated as duplicates;
one instance is removed, which produces counter-intuitive results for mixed-type lists.
## RELATED_DOCS
- List functions — https://www.zoho.com/deluge/help/functions/list.html
## SOURCE
https://www.zoho.com/deluge/help/functions/list/sort.html, https://www.zoho.com/deluge/help/functions/list/distinct.html
assets/functions/deluge/built-in-functions/map/map-containkey-containvalue-notcontains.dou.md
## FILE_PURPOSE
Deluge map containKey() containValue() notContains() — membership test for keys and values.
## MENTAL_MODEL
containKey/containValue = BOOLEAN key/value exists (case-sensitive); notContains = inverse membership.
## WHEN_TO_USE
- Check if a specific key exists in a map (containKey()).
- Check if a specific value exists in a map (containValue()).
- Check if a key (or substring/element) is ABSENT from a map, list, text, or collection (notContains()).
## WHEN_NOT_TO_USE
- Retrieving the value for a key — use get() instead.
- Case-insensitive membership check — all three functions are strictly case-sensitive.
## TRIGGER_KEYWORDS
map.containKey, map.containValue, map.notContains, key exists in map, value exists in map, case sensitive key check
## RULES
- containKey() syntax: `<variable> = <mapVariable>.containKey(<searchkey>);` — returns BOOLEAN.
- searchkey type: TEXT.
- containValue() syntax: `<variable> = <mapVariable>.containValue(<searchValue>);` — returns BOOLEAN.
- searchValue type: TEXT.
- notContains() syntax: `<variable> = <inputValue>.notContains(<searchValue>);` — returns BOOLEAN.
- inputValue type: TEXT/LIST/MAP/COLLECTION; searchValue: any datatype.
- All three functions perform case-sensitive searches.
- notContains() returns true if searchValue is NOT present, false if it is.
- notContains() on MAP: checks for KEY presence (not value) — behaves as inverse of containKey().
- notContains() on TEXT: checks for substring absence.
- notContains() on LIST: checks for element absence.
## CANONICAL_EXAMPLES
```deluge
// containKey() — case-sensitive key check
mapVar = {"Product": "CRM", "Company": "Zoho"};
boolVal = mapVar.containKey("product"); // false — lowercase mismatch
newBoolVal = mapVar.containKey("Product"); // true
// containValue() — case-sensitive value check
boolVal = mapVar.containValue("creator"); // false — lowercase mismatch
newBoolVal = mapVar.containValue("CRM"); // true
// notContains() on MAP — checks KEY absence
product_details = Map();
product_details.put("product", "CRM");
product_details.put("subscription", "yearly");
boolVal = product_details.notContains("price"); // true — key absent
boolVal = product_details.notContains("subscription"); // false — key exists
// notContains() on TEXT — substring absence
products = "Zoho CRM";
boolVal = products.notContains("CRM"); // false — substring found
boolVal = products.notContains("CREATOR"); // true — case mismatch
```
## FAILURE_MODES
- containKey()/containValue(): case mismatch → returns false (no error).
- notContains() on MAP: checks KEYS only — cannot detect value absence.
## RELATED_DOCS
- get — ../../datatypes/key-value.dou.md
- Map functions — https://www.zoho.com/deluge/help/functions/key-value.html
## SOURCE
https://www.zoho.com/deluge/help/functions/map/containkey.html, https://www.zoho.com/deluge/help/functions/map/containvalue.html, https://www.zoho.com/deluge/help/functions/string/notcontains.html
assets/functions/deluge/built-in-functions/map/map-get-keys-size-isempty.dou.md
## FILE_PURPOSE
Deluge map get() keys() size() isEmpty() — read access and state inspection.
## MENTAL_MODEL
get(key)=value|null; keys()=LIST of all keys; size()=pair count; isEmpty()=BOOLEAN empty check.
## WHEN_TO_USE
- Retrieve the value mapped to a known key (get()).
- Get all keys from a map as a list for iteration (keys()).
- Count the number of key-value pairs in a map (size()).
- Check whether a map, list, text, or collection has no elements (isEmpty()).
## WHEN_NOT_TO_USE
- Checking whether a specific key exists — use containKey() instead.
- Mutating the map — use put(), remove(), or clear() instead.
## TRIGGER_KEYWORDS
map.get, map.keys, map.size, map.isEmpty, retrieve map value, list map keys, null if key absent
## RULES
- get() syntax: `<variable> = <mapVariable>.get(<searchkey>);` — return type matches value's data type.
- keys() syntax: `<variable> = <mapVariable>.keys();` — returns LIST.
- size() syntax: `<variable> = <mapVariable>.size();` — returns NUMBER (count of key-value pairs).
- isEmpty() syntax: `<variable> = <expression>.isEmpty();` — returns BOOLEAN; accepts TEXT/LIST/MAP/COLLECTION.
- get(): if the key is not found → returns null (no error).
- keys(): map keys can be TEXT, NUMBER, or LIST; all are returned as elements in the resulting list.
- isEmpty(): a text value containing only spaces (e.g. `" "`) → returns false (spaces ≠ empty).
- isEmpty() is distinct from isBlank() and isNull() — refer to differences doc for each behavior.
## CANONICAL_EXAMPLES
```deluge
// get() — key not found returns null
mapVar = {"Product": "CRM", "Company": "Zoho"};
boolVal = mapVar.get("Module"); // returns null (not a key)
newBoolVal = mapVar.get("Product"); // returns "CRM"
// keys() — all keys returned as a list
mapVar = {"Product": "Zoho CRM", "Version": 8};
listVar = mapVar.keys(); // returns {Product, Version}
// size()
size = mapVar.size(); // returns 2
// isEmpty() — empty map, empty collection, but NOT space-string
mapVar = {};
info mapVar.isEmpty(); // returns true
textVar = " ";
info textVar.isEmpty(); // returns false (space is not empty)
```
## FAILURE_MODES
- get(): key not found → returns null; using null in arithmetic or string operations causes downstream errors.
- isEmpty(): whitespace-only string → returns false (use isBlank() to catch whitespace-only strings).
## RELATED_DOCS
- containKey — map-containkey-containvalue-notcontains.dou.md
- isBlank/isNull/isEmpty differences — https://www.zoho.com/deluge/help/functions/common/isnull-isblank-isempty-difference.html
- Map functions — https://www.zoho.com/deluge/help/functions/key-value.html
## SOURCE
https://www.zoho.com/deluge/help/functions/map/get.html, https://www.zoho.com/deluge/help/functions/map/keys.html, https://www.zoho.com/deluge/help/functions/map/size.html, https://www.zoho.com/deluge/help/functions/text/is-empty.html
assets/functions/deluge/built-in-functions/map/map-put-putall.dou.md
## FILE_PURPOSE
Deluge map put() and putAll() — insert or merge key-value pairs in place.
## MENTAL_MODEL
put(k,v) inserts/overwrites one pair; putAll(map2) merges all pairs; duplicate keys overwrite value.
## WHEN_TO_USE
- Add a single key-value pair to an existing map.
- Merge all key-value pairs from a second map into a first map.
## WHEN_NOT_TO_USE
- Removing key-value pairs — use remove() or clear() instead.
## TRIGGER_KEYWORDS
map.put, map.putAll, insert key value pair, merge maps, duplicate key overwrite
## RULES
- put() syntax: `<mapVariable>.put(<key>, <value>);` — no return value; mutates mapVariable in place.
- putAll() syntax: `<mapVariableOne>.putAll(<mapVariableTwo>);` — no return value; mutates mapVariableOne in place.
- put(): duplicate key → new value OVERWRITES the existing value; no second entry is created.
- putAll(): duplicate key in mapVariableTwo → new value from mapVariableTwo OVERWRITES existing value in mapVariableOne.
## CANONICAL_EXAMPLES
```deluge
// put() — duplicate key "CRM" overwrites value (4 → 5)
Products = map();
Products.put("CRM", 7);
Products.put("CRM", 3);
Products.put("CRM", 8); // map is now {"CRM": 8, "Deals": 3}
// putAll() — merges all pairs from mapTwo into mapOne
mapOne = {"Product": "CRM", "Version": 8};
mapTwo = {"Company": "Zoho", "Country": "India"};
mapOne.putAll(mapTwo);
// mapOne is now {"Product": "CRM", "Version": 8, "Company": "Zoho", "Country": "India"}
```
## FAILURE_MODES
- put(): duplicate key does not create a second entry — value is silently overwritten.
- putAll(): duplicate key from mapVariableTwo silently overwrites the existing value in mapVariableOne.
## RELATED_DOCS
- remove — map-remove-clear.dou.md
- Map functions — https://www.zoho.com/deluge/help/functions/key-value.html
## SOURCE
https://www.zoho.com/deluge/help/functions/map/put.html, https://www.zoho.com/deluge/help/functions/map/putAll.html
assets/functions/deluge/built-in-functions/map/map-remove-clear.dou.md
## FILE_PURPOSE
Deluge map remove() and clear() — delete a key-value pair or empty the entire map.
## MENTAL_MODEL
remove(key) deletes one pair silently if key absent; clear() removes all pairs in place.
## WHEN_TO_USE
- Remove a specific key-value pair from a map by its key.
- Remove all key-value pairs from a map at once.
## WHEN_NOT_TO_USE
- Adding or updating pairs — use put() or putAll() instead.
- Checking key existence before removal — use containKey() first if presence confirmation is needed.
## TRIGGER_KEYWORDS
map.remove, map.clear, delete key-value pair, empty map, remove map entry
## RULES
- remove() syntax: `<mapVariable>.remove(<key>);` — no return value; mutates mapVariable in place.
- clear() syntax: `<mapVariable>.clear();` — no return value; empties the map in place.
- remove(): if the specified key is not found, mapVariable is returned as-is — no error thrown.
- clear(): removes all key-value pairs; the map variable remains but is empty.
## CANONICAL_EXAMPLES
```deluge
// remove() — removes the specified key-value pair
details = {"Company": "Zoho", "Product": "CRM", "Version": 8};
details.remove("Version");
// details is now {"Company": "Zoho", "Product": "CRM"}
// remove() — key not found → map unchanged, no error
details.remove("NonExistentKey");
// details unchanged
// clear() — empties the entire map
mapVar = {"Product": "CRM", "Version": 8};
mapVar.clear();
// mapVar is now {}
```
## FAILURE_MODES
- remove(): key not found → map returned unchanged, no error thrown (silent no-op).
## RELATED_DOCS
- put — map-put-putall.dou.md
- containKey — map-containkey-containvalue-notcontains.dou.md
- Map functions — https://www.zoho.com/deluge/help/functions/key-value.html
## SOURCE
https://www.zoho.com/deluge/help/functions/map/remove.html, https://www.zoho.com/deluge/help/functions/map/clear.html
assets/functions/deluge/built-in-functions/number/abs-frac.dou.md
FILE_PURPOSE: Deluge `abs()` / `frac()` — extract the magnitude or fractional part of a number.
MENTAL_MODEL: `num.abs()` = DECIMAL without sign; `num.frac()` = NUMBER fractional part only; sign preserved in frac.
WHEN_TO_USE:
- Compute the unsigned magnitude of a positive or negative number (abs).
- Isolate the decimal portion of a number for separate processing (frac).
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
abs(), frac(), abs-magnitude, fractional-part, unsigned-result, frac-sign-preserved
RULES:
- `abs()` return type: DECIMAL; input: NUMBER or DECIMAL.
- `frac()` return type: NUMBER; input: NUMBER.
- `abs()` removes the sign; both positive and negative inputs return the positive magnitude.
- `frac()` preserves the sign of the fractional part: `frac(-25.1)` → `-0.1`.
- Both accept two syntaxes: `<num>.abs()` / `abs(<num>)` and `<num>.frac()` / `frac(<num>)`.
CANONICAL_EXAMPLES:
```deluge
number = -15.50;
absValue = number.abs(); // returns 15.50
inputNumber = 24.6;
info inputNumber.frac(); // returns 0.6
inputNumber = -25.1;
info frac(inputNumber); // returns -0.1
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/abs.html, http://www.zoho.com/deluge/help/functions/number/frac.html
assets/functions/deluge/built-in-functions/number/addWeek-subWeek.dou.md
FILE_PURPOSE: Deluge `addWeek()` / `subWeek()` — shift a DATE-TIME forward or backward by weeks.
MENTAL_MODEL: `dt.addWeek(n)` = dt + n×7 days; `dt.subWeek(n)` = dt − n×7 days; negative n reverses direction.
WHEN_TO_USE:
- Advance a DATE-TIME by a number of whole weeks.
- Move a DATE-TIME backwards by a number of whole weeks.
WHEN_NOT_TO_USE:
- On TIME values — both functions accept DATE-TIME only.
TRIGGER_KEYWORDS:
addWeek(), subWeek(), week-arithmetic, datetime-app-settings, datetime-00-default, negative-reverses-direction
RULES:
- Return type: DATE-TIME; input: DATE-TIME (dateTimeValue), NUMBER (numberOfWeeks).
Note: addWeek() Return Type section states "Number" — documentation error; examples output DATE-TIME (e.g., '12-Mar-2019 00:00:00'), subWeek() correctly states "Date-Time", and variable description says "returned date-time value". DATE-TIME confirmed authoritative.
- `numberOfWeeks` is the number of weeks (not minutes); subWeek() param description stating "minutes" is a source copy-paste error. Function name, overview, and addWeek() confirm weeks.
- Output formatted per Application Settings.
- DATE-TIME input without time component defaults to 00:00:00.
- Negative `numberOfWeeks` reverses direction: `addWeek(-n)` = `subWeek(n)` and vice versa.
- Auto-correction (no error raised): hour ≥ 24 → resets to 00:00:00; minutes ≥ 60 → resets mm:ss to 00:00; seconds ≥ 60 → resets ss to 00.
- Runtime errors: hour/minute/second specified in more than 2 digits; date exceeds days in month; year has more than 4 digits.
CANONICAL_EXAMPLES:
```deluge
currentDate = '01-Jan-2019';
currentDateTime = '01-Jan-2019 15:00:00';
newDate = currentDate.addWeek(10); // returns '12-Mar-2019 00:00:00'
newDateTime = currentDateTime.addWeek(55); // returns '21-Jan-2020 15:00:00'
currentDate2 = '01-May-2019';
newDate2 = currentDate2.subWeek(4); // returns '03-Apr-2019 00:00:00'
```
FAILURE_MODES:
- Runtime error when: hour/minute/second specified in more than 2 digits; date exceeds days in month; year has more than 4 digits.
RELATED_DOCS:
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/addweek.html, https://www.zoho.com/deluge/help/functions/datetime/subweek.html
assets/functions/deluge/built-in-functions/number/addYear-subYear.dou.md
FILE_PURPOSE: Deluge `addYear()` / `subYear()` — shift a DATE-TIME forward or backward by years.
MENTAL_MODEL: `dt.addYear(n)` = dt + n years; `dt.subYear(n)` = dt − n years; negative n reverses direction.
WHEN_TO_USE:
- Advance a DATE-TIME by a number of years.
- Move a DATE-TIME backwards by a number of years.
WHEN_NOT_TO_USE:
- On TIME values — both functions accept DATE-TIME only.
TRIGGER_KEYWORDS:
addYear(), subYear(), year-arithmetic, datetime-app-settings, datetime-00-default, negative-reverses-direction
RULES:
- Return type: DATE-TIME; input: DATE-TIME (dateTimeValue), NUMBER (numberOfYears).
Note: addYear and subYear source parameter tables list `<variable>` type as "NUMBER" but the Return Type heading states "Date-Time" and the variable description says "returned date-time value"; examples confirm DATE-TIME output.
- Output formatted per Application Settings.
- DATE-TIME input without time component defaults to 00:00:00.
- Negative `numberOfYears` reverses direction: `addYear(-n)` = `subYear(n)` and vice versa.
- Auto-correction (no error raised): hour ≥ 24 → resets to 00:00:00; minutes ≥ 60 → resets mm:ss to 00:00; seconds ≥ 60 → resets ss to 00.
- Runtime errors: hour/minute/second specified in more than 2 digits; date exceeds days in month; year has more than 4 digits.
CANONICAL_EXAMPLES:
```deluge
currentDate = '01-Jan-2019';
currentDateTime = '01-Jan-2019 15:00:00';
newDate = currentDate.addYear(100); // returns '01-Jan-2119 00:00:00'
newDateTime = currentDateTime.addYear(10); // returns '01-Jan-2029 15:00:00'
currentDate2 = '01-Jan-2019';
newDate2 = currentDate2.subYear(1); // returns '01-Jan-2018 00:00:00'
```
FAILURE_MODES:
- Runtime error when: hour/minute/second specified in more than 2 digits; date exceeds days in month; year has more than 4 digits.
RELATED_DOCS:
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/addyear.html, https://www.zoho.com/deluge/help/functions/datetime/subyear.html
assets/functions/deluge/built-in-functions/number/atan2.dou.md
FILE_PURPOSE: Deluge atan2() — angle from positive x-axis to point (x,y) in radians
MENTAL_MODEL: atan2(y, x) = NUMBER angle in radians from x-axis to ray through point; y-axis argument is first
WHEN_TO_USE:
- Compute the angle (in radians) between the positive x-axis and the ray from the origin to a specified point (x, y).
- Use when both x and y coordinates are available and quadrant-aware angle is needed.
WHEN_NOT_TO_USE:
- When only one coordinate is available — use atan() instead.
TRIGGER_KEYWORDS:
atan2, number.atan2, atan2 y-axis x-axis, two-argument arctangent, origin-to-point angle
RULES:
- Syntax: `<variable> = atan2(<y-axis>, <x-axis>);` or `<variable> = <y-axis>.atan2(<x-axis>);`.
- First argument is the y-axis value; second argument is the x-axis value.
- Both arguments accept NUMBER.
- Returns NUMBER (angle in radians).
- Result represents the angle between the positive x-axis and the ray from (0,0) to the point (x, y).
CANONICAL_EXAMPLES:
```deluge
number = 1;
atan2Value = number.atan2(2); // returns 0.4636476090008061
// equivalent: atan2(1, 2) — y=1, x=2
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- atan — trig-tan-atan.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/atan2.html
assets/functions/deluge/built-in-functions/number/ceil-floor.dou.md
FILE_PURPOSE: Deluge `ceil()` / `floor()` — round a DECIMAL to the nearest integer above or below.
MENTAL_MODEL: `d.ceil()` = nearest-larger INTEGER; `d.floor()` = nearest-smaller INTEGER; both return NUMBER.
WHEN_TO_USE:
- Round a decimal up to the next whole number (ceil).
- Round a decimal down to the previous whole number (floor).
WHEN_NOT_TO_USE:
- When rounding to a specific number of decimal places — use `round()` instead.
TRIGGER_KEYWORDS:
ceil(), floor(), ceiling-integer, floor-integer, nearest-above, nearest-below
RULES:
- Both functions input type: DECIMAL.
- `ceil()` return type: NUMBER — always rounds toward positive infinity (away from zero for positives).
- `floor()` return type: NUMBER — always rounds toward negative infinity (toward zero for positives).
- Both accept two syntaxes: `<decimalValue>.ceil()` / `ceil(<decimalValue>)` and `<decimalValue>.floor()` / `floor(<decimalValue>)`.
CANONICAL_EXAMPLES:
```deluge
number = 10.1;
ceilValue = number.ceil(); // returns 11
floorValue = number.floor(); // returns 10
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- round — round.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/ceil.html, https://www.zoho.com/deluge/help/functions/number/floor.html
assets/functions/deluge/built-in-functions/number/day-month.dou.md
FILE_PURPOSE: Deluge `day()` / `month()` — extract day-of-month or month NUMBER from DATE-TIME.
MENTAL_MODEL: `dt.day()` = day-of-month NUMBER; `dt.month()` = month NUMBER (1–12); both have getDay/getMonth aliases.
WHEN_TO_USE:
- Extract the day-of-month integer from a DATE-TIME value.
- Extract the month number (1–12) from a DATE-TIME value.
WHEN_NOT_TO_USE:
- When null from malformed input is not guarded — certain invalid formats return null silently rather than raising errors.
TRIGGER_KEYWORDS:
day(), month(), getDay(), getMonth(), day-of-month, month-number, null-invalid-format
RULES:
- Both return type: NUMBER; input: DATE-TIME.
- `day()` and `getDay()` are aliases; `month()` and `getMonth()` are aliases.
- Runtime errors (NOT null): 2-digit date exceeds days in that month; incorrect month value specified in 3 or more characters.
- Returns null (no runtime error) when: month value specified in fewer than 3 characters; hour/minute/second in more than 2 digits; date in more than 2 digits; year has more than 4 digits.
CANONICAL_EXAMPLES:
```deluge
currentDate = '01-Jan-2019';
date = currentDate.day(); // returns 1
currentDate2 = '14-Dec-2023';
monthNumber = currentDate2.month();
info monthNumber; // returns 12
```
FAILURE_MODES:
- Returns null (no error) when: month specified in fewer than 3 characters; time components in more than 2 digits; date in more than 2 digits; year in more than 4 digits.
- Runtime error (not null) when: date value exceeds days in month; incorrect month value given in 3+ characters.
RELATED_DOCS:
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/day.html, https://www.zoho.com/deluge/help/functions/datetime/month.html, https://www.zoho.com/deluge/help/functions/datetime/getday.html, https://www.zoho.com/deluge/help/functions/datetime/getmonth.html
assets/functions/deluge/built-in-functions/number/days360.dou.md
FILE_PURPOSE: Deluge `days360()` — count days between two DATE-TIME values; equivalent to `daysBetween()`.
MENTAL_MODEL: `start.days360(end)` = NUMBER of calendar days between dates; identical to `daysBetween()`.
WHEN_TO_USE:
- Need the number of calendar days between two dates
- Computing date intervals or duration in days
WHEN_NOT_TO_USE:
- Need business days only → use workDaysBetween()
- Need hours between → use hoursBetween()
- Need months between → use monthsBetween()
TRIGGER_KEYWORDS: days360(), daysBetween-equivalent, date-interval-days, two-datetime-params, days360-runtime-validation
RULES:
1. Return type: NUMBER (days between startDateTimeValue and endDateTimeValue).
2. Params: DATE-TIME (start), DATE-TIME (end). Both required; no optional params.
3. Exactly equivalent to daysBetween() — both functions behave identically.
4. Despite the name, days360() is NOT a 360-day financial year calculator — it counts actual calendar days.
5. Runtime error when: hour/min/sec in > 2 digits; date exceeds days in the month; incorrect month; year > 4 digits.
CANONICAL_EXAMPLES:
// Dot notation
startDate = '31-Dec-1989';
endDate = '01-Jan-1990';
n = startDate.days360(endDate); // → 1
// Functional form
n = days360('31-Dec-1989', '01-Jan-1990'); // → 1
// Equivalent daysBetween call — same result
n = daysBetween('31-Dec-1989', '01-Jan-1990'); // → 1
FAILURE_MODES:
- Expecting Excel-style 360-day year behavior — Deluge days360() counts actual calendar days, not a 360-day year
- Hour/min/sec in > 2 digits → runtime error
- Date exceeds days in that month → runtime error
- Incorrect month → runtime error
- Year in > 4 digits → runtime error
RELATED_DOCS:
- daysBetween() (identical behavior) — ../date-time/../date-time/days-hours-between.dou.md
- workDaysBetween() — https://www.zoho.com/deluge/help/functions/datetime/workdaysbetween.html
- hoursBetween() — ../date-time/../date-time/days-hours-between.dou.md
- monthsBetween() — ../date-time/../date-time/months-years-between.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/days360.html
assets/functions/deluge/built-in-functions/number/edate.dou.md
FILE_PURPOSE: Deluge `edate()` — add N months to a DATE-TIME; negative N subtracts months.
MENTAL_MODEL: `dt.edate(n)` = DATE-TIME n months ahead; n < 0 subtracts; time component stripped from output.
WHEN_TO_USE:
- Advancing or retreating a date by whole calendar months
- Month-based deadline or period calculations
WHEN_NOT_TO_USE:
- Need to add calendar days → use addDay()
- Need to add business days → use workday() / addBusinessDay()
- Need to add years → use addYear()
TRIGGER_KEYWORDS: edate(), add-months-datetime, negative-month-subtraction, edate-time-stripped, month-offset-DATE-TIME
RULES:
1. Return type: DATE-TIME (date only — time component is NOT retained in the output).
2. Input: DATE-TIME (base), NUMBER (numberOfMonths). No optional params.
3. Negative numberOfMonths subtracts that number of months from the base date.
4. Time value in the input is NOT taken into account — output is always a date without time.
5. Output format follows application settings.
6. Runtime error when: date exceeds days in that month; incorrect month; h/m/s in > 2 digits; year > 4 digits.
CANONICAL_EXAMPLES:
```deluge
currentDate = '01-Jan-2019';
newDate = currentDate.edate(11); // → '01-Dec-2019'
currentDateTime = '01-Jan-2019 15:00:00';
newDateTime = currentDateTime.edate(13); // → '01-Feb-2020' (time stripped from output)
// Functional form
newDate = edate('01-Jan-2019', 11); // → '01-Dec-2019'
// Negative N subtracts months
prev = '01-Mar-2019'.edate(-2); // → '01-Jan-2019'
```
FAILURE_MODES:
- Time component in input is silently dropped from output (not preserved)
- Date exceeds days in that month → runtime error
- Incorrect month string → runtime error
- Hour/min/sec in > 2 digits → runtime error
- Year in > 4 digits → runtime error
RELATED_DOCS:
- addMonth() — ../date-time/../date-time/add-sub-month.dou.md
- subMonth() — ../date-time/../date-time/add-sub-month.dou.md
- Date-time data type — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/edate.html
assets/functions/deluge/built-in-functions/number/exp.dou.md
FILE_PURPOSE: Deluge `exp()` — compute e raised to the power of a given number.
MENTAL_MODEL: `num.exp()` = e^num; e ≈ 2.71828182845904; input NUMBER or DECIMAL, return NUMBER.
WHEN_TO_USE:
- Compute the natural exponential function e^x for scientific or statistical calculations.
- Use as the inverse of the natural logarithm log().
WHEN_NOT_TO_USE:
- When raising an arbitrary base to a power — use power() instead.
TRIGGER_KEYWORDS:
exp(), natural-exponential, e-raised-power, euler-number, exp-inverse-log, e-power-formula
RULES:
- Input types: NUMBER or DECIMAL.
- Return type: NUMBER (per parameter table; the Return Type section states "Decimal" — source inconsistency, parameter table treated as authoritative per user resolution).
- e constant = 2.71828182845904 (as documented in source).
- Both `<num>.exp()` and `exp(<num>)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
number = 1;
expValue = number.exp(); // returns 2.718281828459045
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- log — log-log10.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/exp.html
assets/functions/deluge/built-in-functions/number/getDayOfYear.dou.md
FILE_PURPOSE: Deluge `getDayOfYear()` — get day-of-year NUMBER (1–366) from a DATE-TIME value.
MENTAL_MODEL: `dt.getDayOfYear()` = 1–365 (normal year) or 1–366 (leap year); no alias; time defaults 00:00:00.
WHEN_TO_USE:
- Need the ordinal day of year (1-based) from a DATE-TIME value
- Calculating whether a year is a leap year (getDayOfYear('31-Dec-YYYY') == 366)
- Comparing Julian day positions across dates within a year
WHEN_NOT_TO_USE:
- Need day-of-month → use getDay() / day()
- Need day-of-week → use weekday() / getDayOfWeek()
- Need week-of-year → use getWeekOfYear()
TRIGGER_KEYWORDS: getDayOfYear(), day-of-year, leap-year-366, datetime-ordinal, null-invalid-datetime-format
RULES:
1. Return type: NUMBER (1–365 for non-leap years; 1–366 for leap years).
2. Input: DATE-TIME only — no TIME variant exists.
3. If no time component is provided, defaults to 00:00:00.
4. Returns NULL (not error) when: month is < 3 chars; hour/min/sec is > 2 digits; date is > 2 digits; year is > 4 digits.
5. Runtime error when: 2-digit date exceeds days in the month; month specified in 3+ chars but incorrect.
6. No alias — only getDayOfYear() syntax (no shorthand like dayOfYear()).
CANONICAL_EXAMPLES:
// Day 366 of a leap year (2020)
result = '31-Dec-2020'.getDayOfYear(); // → 366
// Functional form
result = getDayOfYear('31-Dec-2020'); // → 366
// Jan 1 always returns 1
result = '01-Jan-2021'.getDayOfYear(); // → 1
// No time provided — defaults to 00:00:00
result = '15-Jun-2023'.getDayOfYear(); // → 166
FAILURE_MODES:
- Month in < 3 chars (e.g., '01-Ja-2019') → null, not an error
- Hour/min/sec in > 2 digits → null
- Date in > 2 digits → null
- Year in > 4 digits → null
- 2-digit date exceeds days in that month → runtime error
- Incorrect month string (3+ chars) → runtime error
- Calling on a TIME value → not supported (no TIME variant)
RELATED_DOCS:
- getDay() / day() — day-month.dou.md
- getWeekOfYear() — getWeekOfYear.dou.md
- weekday() / getDayOfWeek() — weekday.dou.md
- Date-time data type — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/getdayofyear.html
assets/functions/deluge/built-in-functions/number/getWeekOfYear.dou.md
FILE_PURPOSE: Deluge `getWeekOfYear()` — get week-of-year NUMBER (1–52) with optional start-day override.
MENTAL_MODEL: `dt.getWeekOfYear([start_day])` = 1–52; default start Sunday.
WHEN_TO_USE:
- Need the week number for a date within the year
- Grouping or comparing dates by week number
- Custom fiscal-week calculations where week start differs from Sunday
WHEN_NOT_TO_USE:
- Need day-of-week → use weekday() / getDayOfWeek()
- Need day-of-year → use getDayOfYear()
TRIGGER_KEYWORDS: getWeekOfYear(), week-of-year, start_day-optional, Sunday-default-start
RULES:
1. Return type: NUMBER (1–52 representing the week number within the year).
2. Input: DATE-TIME. Optional second param [start_day] TEXT (default: "Sunday").
3. Weeks start from Sunday by default; dates from the previous year that fall in the first week of the new year return 1.
4. Returns NULL (not error) when: month is < 3 chars; hour/min/sec is > 2 digits; date is > 2 digits; year is > 4 digits.
5. Runtime error when: 2-digit date exceeds days in the month; month specified in 3+ chars but incorrect.
CANONICAL_EXAMPLES:
// Week 7 of 2021 (default Sunday start)
weekNum = '7-Feb-2021'.getWeekOfYear(); // → 7
// Functional form with Monday start
weekNum = getWeekOfYear('7-Feb-2021', "Monday"); // → 6
// Dot notation with Monday start
weekNum = '7-Feb-2021'.getWeekOfYear("Monday"); // → 6
// Previous year's date in first week of new year returns 1
weekNum = '01-Jan-2023'.getWeekOfYear(); // → 1
FAILURE_MODES:
- Month in < 3 chars → null
- Hour/min/sec in > 2 digits → null
- Date in > 2 digits → null
- Year in > 4 digits → null
- 2-digit date exceeds days in month → runtime error
- Incorrect month string (3+ chars) → runtime error
RELATED_DOCS:
- getDayOfYear() — getDayOfYear.dou.md
- weekday() / getDayOfWeek() — weekday.dou.md
- Date-time data type — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/getweekofyear.html
assets/functions/deluge/built-in-functions/number/getYear.dou.md
FILE_PURPOSE: Deluge `getYear()` — extract year as NUMBER from a DATE-TIME value; alias `year()`.
MENTAL_MODEL: `dt.getYear()` = 4-digit year NUMBER; time component ignored; `year()` is an alias.
WHEN_TO_USE:
- Need the year component from a DATE-TIME value
- Year-based comparisons, grouping, or arithmetic
WHEN_NOT_TO_USE:
- Need month → use getMonth() / month()
- Need day-of-month → use getDay() / day()
- Need day-of-year → use getDayOfYear()
TRIGGER_KEYWORDS: getYear(), year(), datetime-year-extract, getYear-alias, null-invalid-datetime-format
RULES:
1. Return type: NUMBER (the 4-digit year, e.g., 2019).
2. Input: DATE-TIME only — no TIME variant. Time component is NOT taken into account.
3. Aliases: getYear() and year() are equivalent; both forms supported.
4. Returns NULL (not error) when: month is < 3 chars; hour/min/sec is > 2 digits; date is > 2 digits; year is > 4 digits.
5. Runtime error when: 2-digit date exceeds days in the month; month specified in 3+ chars but incorrect.
CANONICAL_EXAMPLES:
// Dot notation
currentDate = '01-Jan-2019';
yearVal = currentDate.getYear(); // → 2019
// Alias form
yearVal = '01-Jan-2019'.year(); // → 2019
// Functional form
yearVal = getYear('01-Jan-2019'); // → 2019
// Time component is ignored
yearVal = '01-Jan-2019 23:59:59'.getYear(); // → 2019
FAILURE_MODES:
- Month in < 3 chars (e.g., '01-Ja-2019') → null, not error
- Hour/min/sec in > 2 digits → null
- Date in > 2 digits → null
- Year in > 4 digits → null
- 2-digit date exceeds days in month → runtime error
- Incorrect month string (3+ chars) → runtime error
- Calling on a TIME value → not supported (no TIME variant)
RELATED_DOCS:
- getDay() / day() — day-month.dou.md
- getMonth() / month() — day-month.dou.md
- getDayOfYear() — getDayOfYear.dou.md
- Date-time data type — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/getYear.html
assets/functions/deluge/built-in-functions/number/hour-minute-second.dou.md
FILE_PURPOSE: Deluge `hour()` / `minute()` / `second()` — get hour, minute, second from a DATE-TIME value.
MENTAL_MODEL: `dt.hour()` = 0–23; `dt.minute()` = 0–59; `dt.second()` = 0–59.
WHEN_TO_USE:
- Extract the hour, minute, or second component as a NUMBER from a DATE-TIME value.
WHEN_NOT_TO_USE:
- Need week or day number — use getWeekOfYear() or day() instead.
TRIGGER_KEYWORDS:
hour(), minute(), second(), getHour(), getMinutes(), getSeconds()
RULES:
- All three return type: NUMBER; input: DATE-TIME.
- Aliases: `hour()` = `getHour()`; `minute()` = `getMinutes()`; `second()` = `getSeconds()`.
- DATE-TIME without time component defaults to 00:00:00 (returns 0 for all three).
- DATE-TIME runtime errors: hour/minute/second in more than 2 digits; date exceeds days in month; year more than 4 digits.
CANONICAL_EXAMPLES:
```deluge
// DATE-TIME examples
currentDate = '01-Jan-2019';
info currentDate.getHour(); // Returns 00
info currentDate.getMinutes(); // Returns 00
info currentDate.getSeconds(); // Returns 00
currentDate = '01-Jan-2019 23:15:10';
info currentDate.hour(); // Returns 23
info currentDate.minute(); // Returns 15
info currentDate.second(); // Returns 10
```
FAILURE_MODES:
- DATE-TIME with hour/minute/second in more than 2 digits throws runtime error.
RELATED_DOCS:
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/hour.html, https://www.zoho.com/deluge/help/functions/datetime/minute.html, https://www.zoho.com/deluge/help/functions/datetime/second.html, https://www.zoho.com/deluge/help/functions/datetime/gethour.html, https://www.zoho.com/deluge/help/functions/datetime/getminutes.html, https://www.zoho.com/deluge/help/functions/datetime/getseconds.html
assets/functions/deluge/built-in-functions/number/hyperbolic-cosh-acosh.dou.md
FILE_PURPOSE: Deluge cosh() and acosh() — hyperbolic cosine and inverse hyperbolic cosine
MENTAL_MODEL: cosh(x) = NUMBER hyperbolic cosine of radian angle; acosh(x) = NUMBER inverse hyperbolic cosine in radians
WHEN_TO_USE:
- Compute the hyperbolic cosine of a radian-valued angle with cosh().
- Compute the inverse hyperbolic cosine (arcosh) of a number with acosh().
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
cosh, acosh, number.cosh, number.acosh, hyperbolic cosine, inverse hyperbolic cosine radians
RULES:
- cosh() syntax: `<variable> = <number>.cosh();` or `cosh(<number>)`.
- acosh() syntax: `<variable> = <number>.acosh();` or `acosh(<number>)`.
- Both accept NUMBER as input.
- Both return NUMBER.
- Angle is measured in radians.
CANONICAL_EXAMPLES:
```deluge
number = 0;
coshValue = number.cosh(); // returns 1.0
number = 3;
acoshValue = number.acosh(); // returns 1.762747174039086
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/cosh.html, https://www.zoho.com/deluge/help/functions/number/acosh.html
assets/functions/deluge/built-in-functions/number/hyperbolic-sinh-asinh.dou.md
FILE_PURPOSE: Deluge sinh() and asinh() — hyperbolic sine and inverse hyperbolic sine
MENTAL_MODEL: sinh(x) = NUMBER hyperbolic sine of radian angle; asinh(x) = NUMBER inverse hyperbolic sine in radians
WHEN_TO_USE:
- Compute the hyperbolic sine of a radian-valued angle with sinh().
- Compute the inverse hyperbolic sine (arsinh) of a number with asinh().
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
sinh, asinh, number.sinh, number.asinh, hyperbolic sine, inverse hyperbolic sine radians
RULES:
- sinh() syntax: `<variable> = <number>.sinh();` or `sinh(<number>)`.
- asinh() syntax: `<variable> = <number>.asinh();` or `asinh(<number>)`.
- Both accept NUMBER as input.
- Both return NUMBER.
- Angle is measured in radians.
CANONICAL_EXAMPLES:
```deluge
number = 1;
sinhValue = number.sinh(); // returns 1.1752011936438014
number = 1;
asinhValue = number.asinh(); // returns 0.8813735870195429
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/sinh.html, https://www.zoho.com/deluge/help/functions/number/asinh.html
assets/functions/deluge/built-in-functions/number/hyperbolic-tanh-atanh.dou.md
FILE_PURPOSE: Deluge tanh() and atanh() — hyperbolic tangent and inverse hyperbolic tangent
MENTAL_MODEL: tanh(x) = NUMBER hyperbolic tangent of radian angle; atanh(x) = NUMBER inverse hyperbolic tangent in radians
WHEN_TO_USE:
- Compute the hyperbolic tangent of a radian-valued angle with tanh().
- Compute the inverse hyperbolic tangent (artanh) of a number with atanh().
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
tanh, atanh, number.tanh, number.atanh, hyperbolic tangent, inverse hyperbolic tangent radians
RULES:
- tanh() syntax: `<variable> = <number>.tanh();` or `tanh(<number>)`.
- atanh() syntax: `<variable> = <number>.atanh();` or `atanh(<number>)`.
- Both accept NUMBER as input.
- Both return NUMBER.
- Angle is measured in radians.
CANONICAL_EXAMPLES:
```deluge
number = 0.785398;
tanhValue = number.tanh(); // returns 0.6557941095069071
number = 0.7;
atanhValue = number.atanh(); // returns 0.8673005276940532
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/tanh.html, https://www.zoho.com/deluge/help/functions/number/atanh.html
assets/functions/deluge/built-in-functions/number/isDate.dou.md
FILE_PURPOSE: Deluge `isDate()` — return BOOLEAN true when expression is a valid DATE-TIME value.
MENTAL_MODEL: `isDate(expr)` = true (DATE-TIME), false (NUMBER), null (LIST/MAP), error (invalid-text).
WHEN_TO_USE:
- Guard-checking whether a variable holds a valid date-time before performing date operations
- Differentiating DATE-TIME values from numbers in mixed-type scenarios
WHEN_NOT_TO_USE:
- Validating arbitrary text strings for date format — TEXT with an invalid datetime throws an error, not false
- Expecting a universal safe boolean for all non-date inputs — behavior differs by input type
TRIGGER_KEYWORDS: isDate(), boolean-date-check, invalid-text-throws-runtime, null-for-list-map, isDate-boolean-guard
RULES:
1. Return type: BOOLEAN (true/false); or null for LIST/MAP inputs; or runtime error for invalid TEXT.
2. DATE-TIME typed value → true.
3. TEXT containing a valid date-time value → true.
4. TEXT NOT containing a valid date-time value → THROWS ERROR (does not return false).
5. NUMBER → false (no error thrown).
6. LIST or MAP (key-value) → null (not true or false).
CANONICAL_EXAMPLES:
```deluge
dateString = "01-Jan-2019"; // TEXT with valid date
dateValue = '01-Jun-2020'; // DATE-TIME typed value
numValue = 100;
boolValOne = isDate(dateString); // → true
boolValTwo = isDate(numValue); // → false
boolValThree = dateValue.isDate(); // → true
// TRAP: TEXT with invalid date → runtime error, NOT false
// isDate("not-a-date") → ERROR
```
FAILURE_MODES:
- Passing TEXT with an invalid date-time string → runtime error (does NOT return false)
- LIST or MAP input → null (not a boolean; cannot be used in boolean guard without null check)
- Assuming isDate() is a safe universal boolean for all inputs — it is not for invalid TEXT
RELATED_DOCS:
- isNumber() — https://www.zoho.com/deluge/help/functions/type-check/isnumber.html
- isText() — https://www.zoho.com/deluge/help/functions/type-check/istext.html
- Type check functions — https://www.zoho.com/deluge/help/functions/type-check.html
SOURCE:
https://www.zoho.com/deluge/help/functions/common/isdate.html
assets/functions/deluge/built-in-functions/number/isnumber.dou.md
FILE_PURPOSE: Deluge isNumber() — check whether an expression evaluates to a valid numeric value
MENTAL_MODEL: isNumber(x) = Boolean; true if x is a valid number literal, variable, or arithmetic result; null for List/Map
WHEN_TO_USE:
- Validate that an expression or variable holds a numeric value before performing arithmetic.
- Check if a string in quotes contains a valid numeric literal (e.g. "12.3" returns true).
WHEN_NOT_TO_USE:
- Checking a List or Key-value — isNumber() returns null (not false) for those input types.
TRIGGER_KEYWORDS:
isNumber, expression.isNumber, boolean type-check numeric, quoted number isNumber, null for List Map
RULES:
- Two syntaxes: `isNumber(<expression>)` or `<expression>.isNumber()`.
- Returns BOOLEAN (true or false).
- Valid numerical values in single or double quotes return true.
- String expressions containing arithmetic operators (e.g. `"24/2*10"`) return false.
- List and Key-value data types always return null (not true or false).
CANONICAL_EXAMPLES:
```deluge
numValue = isNumber("12.3"); // returns true
numValue = "1".isNumber(); // returns true
numValue = 24.isNumber(); // returns true
numValue = (24/2*10).isNumber(); // returns true
numValue = "24/2*10".isNumber(); // returns false
```
FAILURE_MODES:
- List or Key-value as input → returns null, not false; null-check required to avoid silent logic errors.
RELATED_DOCS:
- Type check functions — https://www.zoho.com/deluge/help/functions/type-check.html
- Functions returning boolean — https://www.zoho.com/deluge/help/functions-returning-boolean.html
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/common/isnumber.html
assets/functions/deluge/built-in-functions/number/log-log10.dou.md
FILE_PURPOSE: Deluge `log()` / `log10()` — compute the natural or base-10 logarithm of a number.
MENTAL_MODEL: `n.log()` = ln(n) as DECIMAL; `n.log10()` = log₁₀(n) as DECIMAL; both return null for n ≤ 0.
WHEN_TO_USE:
- Compute the natural logarithm (ln) for mathematical or statistical operations (log).
- Compute the base-10 logarithm for scale or magnitude calculations (log10).
WHEN_NOT_TO_USE:
- When input is ≤ 0 — both functions return null silently instead of throwing an error.
TRIGGER_KEYWORDS:
log(), log10(), natural-logarithm, base-10-log, null-nonpositive, ln-inverse-exp
RULES:
- `log()` return type: DECIMAL; input: NUMBER or DECIMAL; computes natural logarithm (ln, base e).
- `log10()` return type: DECIMAL; input: NUMBER or DECIMAL; computes base-10 logarithm.
- Both functions return null when input ≤ 0 (no runtime error).
- `log()` accepts two syntaxes: `<num>.log()` / `log(<num>)`.
- `log10()` accepts two syntaxes: `<num>.log10()` / `log10(<num>)`.
CANONICAL_EXAMPLES:
```deluge
number = 90;
logValue = number.log(); // returns 4.499809670330265 (natural log)
number = 81;
info number.log10(); // returns 1.9084850188786497 (base-10 log)
info (0).log(); // returns null (input ≤ 0)
info (-5).log10(); // returns null (input ≤ 0)
```
FAILURE_MODES:
- Input ≤ 0 returns null silently for both log() and log10() — no runtime error is raised.
RELATED_DOCS:
- exp — exp.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/log.html, https://www.zoho.com/deluge/help/functions/number/log10.html
assets/functions/deluge/built-in-functions/number/nextWeekDay-previousWeekDay.dou.md
FILE_PURPOSE: Deluge `nextWeekDay()` / `previousWeekDay()` — seek next or previous date by weekday name.
MENTAL_MODEL: `dt.nextWeekDay(day)` = next date on that weekday; `.previousWeekDay(day)` = previous date on that weekday.
WHEN_TO_USE:
- Finding the next or previous occurrence of a specific named weekday from a date
- Scheduling or calendar calculations targeting a named day (including weekends)
WHEN_NOT_TO_USE:
- Need to add N business days → use workday() / addBusinessDay()
- Need day-of-week number for a date → use weekday() / getDayOfWeek()
TRIGGER_KEYWORDS: nextWeekDay(), previousWeekDay(), inputDay-weekday-label, non-business-day, day-of-week-seek
RULES:
1. FLAG: return type contradiction in source — Return Type section states TEXT; parameter table states DATE-TIME.
Candidates:
a) Return type is TEXT (Return Type header is authoritative).
b) Return type is DATE-TIME (parameter table entry is authoritative).
2. Input: DATE-TIME (inputDate), TEXT (inputDay — one of: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday).
3. NOT related to business days — inputDay can be any weekday including Saturday and Sunday.
4. nextWeekDay(): if inputDate already falls on inputDay, returns the NEXT occurrence (inputDate itself is NOT returned).
5. previousWeekDay(): if inputDate already falls on inputDay, returns the PREVIOUS occurrence (inputDate itself is NOT returned).
CANONICAL_EXAMPLES:
```deluge
// nextWeekDay: current date 20-Jul-2021 (Tuesday) → next Monday
info zoho.currentdate.nextWeekDay("Monday"); // → 26-Jul-2021
// Same-day: 20-Jul-2021 (Tuesday).nextWeekDay("Tuesday") → next Tuesday, not same day
inputDate = '20-Jul-2021';
info inputDate.nextWeekDay("Tuesday"); // → 27-Jul-2021
// previousWeekDay: current date 21-Jul-2021 (Wednesday) → previous Monday
info zoho.currentdate.previousWeekDay("Monday"); // → 19-Jul-2021
// Same-day: 21-Jul-2021 (Wednesday).previousWeekDay("Wednesday") → previous Wednesday
inputDate = '21-Jul-2021';
info inputDate.previousWeekDay("Wednesday"); // → 14-Jul-2021
```
FAILURE_MODES:
- Return type ambiguity — source documents both TEXT and DATE-TIME; resolve FLAG before chaining operations
- Calling nextWeekDay("Monday") when inputDate is already Monday → returns NEXT Monday, not inputDate
- Calling previousWeekDay("Monday") when inputDate is already Monday → returns PREVIOUS Monday, not inputDate
- inputDay value outside allowed list (Sunday–Saturday) → behavior not documented in source
RELATED_DOCS:
- weekday() / getDayOfWeek() — weekday.dou.md
- workday() — workday.dou.md
- Date-time built-in functions — https://www.zoho.com/deluge/help/functions/date-time.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/nextWeekDay.html, https://www.zoho.com/deluge/help/functions/number/previousWeekDay.html
assets/functions/deluge/built-in-functions/number/power-sqrt.dou.md
FILE_PURPOSE: Deluge `power()` / `sqrt()` — exponentiate a number or compute its square root.
MENTAL_MODEL: `b.power(p)` = b^p as DECIMAL; `n.sqrt()` = √n as DECIMAL; √ of negative → null.
WHEN_TO_USE:
- Compute base raised to an arbitrary exponent using power().
- Compute the positive square root of a non-negative number using sqrt().
WHEN_NOT_TO_USE:
- When computing e^x — use exp() instead.
- When the sqrt() input is negative — returns null, not an error; guard before use.
TRIGGER_KEYWORDS:
power(), sqrt(), exponentiation, square-root, negative-sqrt-null, power-base-exponent
RULES:
- `power()` return type: DECIMAL; inputs: NUMBER or DECIMAL (baseNumber), NUMBER or DECIMAL (powerNumber).
- `sqrt()` return type: DECIMAL; input: NUMBER or DECIMAL.
- `sqrt()` on values below 0 returns null (not a runtime error).
- `power()` accepts two syntaxes: `<base>.power(<exp>)` / `power(<base>, <exp>)`.
- `sqrt()` accepts two syntaxes: `<num>.sqrt()` / `sqrt(<num>)`.
CANONICAL_EXAMPLES:
```deluge
numOne = 3;
numTwo = 2;
newValue = numOne.power(numTwo); // returns 9 (3^2)
number = 9;
sqrtValue = number.sqrt(); // returns 3.0
negative = -4;
info negative.sqrt(); // returns null (no error)
```
FAILURE_MODES:
- `sqrt()` on a negative number returns null silently — no runtime error is raised.
RELATED_DOCS:
- exp — exp.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/power.html, https://www.zoho.com/deluge/help/functions/number/sqrt.html
assets/functions/deluge/built-in-functions/number/randomnumber.dou.md
FILE_PURPOSE: Deluge randomNumber() — generate a random number within an inclusive-start exclusive-end range
MENTAL_MODEL: randomNumber(start, end) = random in [start, end); NUMBER if both integers, DECIMAL if either is decimal
WHEN_TO_USE:
- Generate a random integer within a whole-number range.
- Generate a random decimal within a fractional range.
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
randomNumber, randomnumber, start_limit end_limit, exclusive end_limit, random decimal range
RULES:
- Syntax: `<variable> = randomNumber(<start_limit>, <end_limit>);`
- Both `<start_limit>` and `<end_limit>` accept NUMBER or DECIMAL.
- Returns NUMBER if both arguments are whole numbers.
- Returns DECIMAL if either argument is a decimal value.
- The generated value includes start_limit and any values between, but is NEVER equal to end_limit (end is exclusive).
CANONICAL_EXAMPLES:
```deluge
response1 = randomnumber(1, 3); // returns 1 or 2 (never 3)
response2 = randomnumber(1.789, 34.54); // random decimal in [1.789, 34.54)
response3 = randomnumber(5, 123.4); // random decimal in [5, 123.4)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/randomnumber.html
assets/functions/deluge/built-in-functions/number/round.dou.md
FILE_PURPOSE: Deluge `round()` — round a number to a specified decimal or integer precision.
MENTAL_MODEL: `num.round(p)` = DECIMAL; positive `p` = decimal places; negative `p` = places left of decimal.
WHEN_TO_USE:
- Round a numeric value to a fixed number of decimal places for display or calculation.
- Round large integers to the nearest 10, 100, or 1000 using negative precision.
WHEN_NOT_TO_USE:
- When truncating decimals to integers without rounding — use `floor()` or `ceil()` instead.
TRIGGER_KEYWORDS:
round(), roundingPrecision, negative-precision rounding, round-to-left, half-up rounding, round-precedence
RULES:
- Return type: DECIMAL; input: DECIMAL/NUMBER (numericalExpression), NUMBER (roundingPrecision).
- Positive `roundingPrecision`: rounds to that many decimal places.
- Digit to the right of the rounding position ≥ 5 → round up (add 1 to rounding digit, drop remaining).
- Digit to the right of the rounding position ≤ 4 → truncate (drop without carry).
- Negative `roundingPrecision`: rounds to left of decimal point.
- `round(-1)` → nearest 10; `round(-2)` → nearest 100.
- Negative precision ≥ total digits before decimal → returns 0.
- Operator precedence: `.round()` binds to its immediately preceding number literal, not the whole expression.
- `1.4+3.6.round(0)` evaluates as `1.4 + round(3.6, 0)` = `1.4 + 4` = `5.4`.
- Use parentheses for whole-expression rounding: `(1.4+3.6).round(0)` = `5`.
CANONICAL_EXAMPLES:
```deluge
218.5.round(0); // returns 219 (≥5 → round up)
218.4.round(0); // returns 218 (≤4 → truncate)
218.49.round(1); // returns 218.5
214.2.round(-1); // returns 210 (nearest 10)
218.2.round(-2); // returns 200 (nearest 100)
218.222.round(-3); // returns 0 (precision ≥ digit count)
1.4 + 3.6.round(0); // returns 5.4 (rounds 3.6 only — precedence trap)
(1.4 + 3.6).round(0); // returns 5 (rounds the sum)
```
FAILURE_MODES:
- `round(-p)` where `p` ≥ the number of digits before the decimal returns `0` silently — not an error.
- `1.4+3.6.round(0)` ≠ `(1.4+3.6).round(0)`; `.round()` applies to its directly preceding operand without explicit parentheses.
RELATED_DOCS:
- ceil — ceil-floor.dou.md
- floor — ceil-floor.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/round.html
assets/functions/deluge/built-in-functions/number/stat-max-min.dou.md
FILE_PURPOSE: Deluge max() and min() — compare two numbers and return the larger or smaller
MENTAL_MODEL: max(a, b) = DECIMAL larger of two; min(a, b) = DECIMAL smaller of two
WHEN_TO_USE:
- Return the larger of exactly two numbers with max().
- Return the smaller of exactly two numbers with min().
WHEN_NOT_TO_USE:
- Finding the extreme value across a list — use largest() or smallest() instead.
TRIGGER_KEYWORDS:
max, min, numberOne.max, numberOne.min, two-number comparison, max-min scalar pair
RULES:
- max() syntax: `<variable> = <numberOne>.max(<numberTwo>);` or `max(<numberOne>, <numberTwo>)`.
- min() syntax: `<variable> = <numberOne>.min(<numberTwo>);` or `min(<numberOne>, <numberTwo>)`.
- Both arguments accept NUMBER or DECIMAL.
- Both return DECIMAL.
CANONICAL_EXAMPLES:
```deluge
numOne = 100;
numTwo = 200;
maxValue = numOne.max(numTwo); // returns 200
minValue = numOne.min(numTwo); // returns 100
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- largest — ../list/../list/list-largest-smallest.dou.md
- smallest — ../list/../list/list-largest-smallest.dou.md
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/max.html, https://www.zoho.com/deluge/help/functions/number/min.html
assets/functions/deluge/built-in-functions/number/subSeconds.dou.md
FILE_PURPOSE: Deluge `subSeconds()` — subtract seconds from a DATE-TIME value; negative value adds seconds.
MENTAL_MODEL: `dt.subSeconds(n)` = dt minus n seconds; negative n adds.
WHEN_TO_USE:
- Subtract a number of seconds from a DATE-TIME value.
WHEN_NOT_TO_USE:
- Subtracting minutes — use subMinutes() instead.
- Subtracting hours — use subHour() instead.
TRIGGER_KEYWORDS:
subSeconds(), negative-adds-seconds, datetime-subtraction, datetime-00-default
RULES:
- Return type: DATE-TIME.
- Input types: DATE-TIME (dateTimeValue), NUMBER (numberOfSecs).
- DATE-TIME input without time component defaults to 00:00:00.
- Negative `numberOfSecs` adds that many seconds instead of subtracting.
- DATE-TIME auto-correction (no error): hour ≥ 24 → resets to 00:00:00; minutes ≥ 60 → resets mm:ss to 00:00; seconds ≥ 60 → resets ss to 00.
- DATE-TIME runtime errors: hour/minute/second in more than 2 digits; date exceeds days in month; year more than 4 digits.
CANONICAL_EXAMPLES:
```deluge
// DATE-TIME examples
currentDate = '02-Jan-2019';
info currentDate.subSeconds(70); // Returns '01-Jan-2019 23:58:50'
currentDate = '01-Jan-2019 23:15:10';
info currentDate.subSeconds(2); // Returns '01-Jan-2019 23:15:08'
```
FAILURE_MODES:
- DATE-TIME with hour/minute/second in more than 2 digits throws a runtime error.
- DATE-TIME date value exceeding days in that month throws a runtime error.
RELATED_DOCS:
- Date-time functions — https://www.zoho.com/deluge/help/functions/date-time.html
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/subseconds.html
assets/functions/deluge/built-in-functions/number/trig-cos-acos.dou.md
FILE_PURPOSE: Deluge cos() and acos() — trigonometric cosine and arccosine in radians
MENTAL_MODEL: cos(x) = DECIMAL trig cosine of radian angle; acos(x) = DECIMAL inverse cosine returning radian angle
WHEN_TO_USE:
- Compute the trigonometric cosine of a radian-valued angle with cos().
- Compute the angle in radians whose cosine equals a given value with acos().
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
cos, acos, number.cos, number.acos, trigonometric cosine, arccosine radians
RULES:
- cos() syntax: `<variable> = <number>.cos();` or `cos(<number>)`.
- acos() syntax: `<variable> = <number>.acos();` or `acos(<number>)`.
- Both accept NUMBER or DECIMAL as input.
- Both return DECIMAL.
- Angle is measured in radians.
CANONICAL_EXAMPLES:
```deluge
number = 90;
cosValue = number.cos(); // returns -0.4480736161291702
number = 0.5;
acosValue = number.acos(); // returns 1.0471975511965979
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
- Degrees — https://www.zoho.com/deluge/help/functions/number/degrees.html
- Radians — https://www.zoho.com/deluge/help/functions/number/radians.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/cos.html, https://www.zoho.com/deluge/help/functions/number/acos.html
assets/functions/deluge/built-in-functions/number/trig-sin-asin.dou.md
FILE_PURPOSE: Deluge sin() and asin() — trigonometric sine and arcsine in radians
MENTAL_MODEL: sin(x) = DECIMAL trig sine of radian angle; asin(x) = DECIMAL inverse sine returning radian angle
WHEN_TO_USE:
- Compute the trigonometric sine of a radian-valued angle with sin().
- Compute the angle in radians whose sine equals a given value with asin().
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
sin, asin, number.sin, number.asin, trigonometric sine, arcsine radians
RULES:
- sin() syntax: `<variable> = <number>.sin();` or `sin(<number>)`.
- asin() syntax: `<variable> = <number>.asin();` or `asin(<number>)`.
- Both accept NUMBER or DECIMAL as input.
- Both return DECIMAL.
- Angle is measured in radians.
CANONICAL_EXAMPLES:
```deluge
number = 90;
sinValue = number.sin(); // returns 0.8939966636005579
number = 1;
asinValue = number.asin(); // returns 1.5707963267948966
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
- Degrees — https://www.zoho.com/deluge/help/functions/number/degrees.html
- Radians — https://www.zoho.com/deluge/help/functions/number/radians.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/sin.html, https://www.zoho.com/deluge/help/functions/number/asin.html
assets/functions/deluge/built-in-functions/number/trig-tan-atan.dou.md
FILE_PURPOSE: Deluge tan() and atan() — trigonometric tangent and arctangent in radians
MENTAL_MODEL: tan(x) = DECIMAL trig tangent of radian angle; atan(x) = DECIMAL inverse tangent returning radian angle
WHEN_TO_USE:
- Compute the trigonometric tangent of a radian-valued angle with tan().
- Compute the angle in radians whose tangent equals a given value with atan().
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
tan, atan, number.tan, number.atan, trigonometric tangent, arctangent radians
RULES:
- tan() syntax: `<variable> = <number>.tan();` or `tan(<number>)`.
- atan() syntax: `<variable> = <number>.atan();` or `atan(<number>)`.
- Both accept NUMBER or DECIMAL as input.
- tan() returns DECIMAL.
- atan() return type is listed as Decimal in the Return Type section of the source.
- Angle is measured in radians.
CANONICAL_EXAMPLES:
```deluge
number = 90;
tanValue = number.tan(); // returns -1.995200412208242
number = 1;
atanValue = number.atan(); // returns 0.7853981633974483
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Number functions — https://www.zoho.com/deluge/help/functions/number.html
- atan2 — atan2.dou.md
- Degrees — https://www.zoho.com/deluge/help/functions/number/degrees.html
SOURCE:
https://www.zoho.com/deluge/help/functions/number/tan.html, https://www.zoho.com/deluge/help/functions/number/atan.html
assets/functions/deluge/built-in-functions/number/unixEpoch.dou.md
FILE_PURPOSE: Deluge `unixEpoch()` — convert a TEXT date to Unix milliseconds since 1970-01-01 UTC.
MENTAL_MODEL: `dateText.unixEpoch([tz])` = milliseconds since epoch; input must be TEXT; invalid TZ defaults GMT.
WHEN_TO_USE:
- Need a Unix timestamp (milliseconds) from a date string for external system interop
- Timezone-aware epoch conversion from a known date-time text
WHEN_NOT_TO_USE:
- Input is a DATE-TIME typed variable → must convert to TEXT first (via toString())
- Need to reverse the operation (epoch → formatted date string) → use toDateTimeString()
TRIGGER_KEYWORDS: unixEpoch(), unix-milliseconds, text-only-input, TZ-database-key, invalid-TZ-defaults-GMT
RULES:
1. Return type: NUMBER (milliseconds elapsed since 1970-01-01 00:00:00 UTC).
2. Input: TEXT (dateText), optional TEXT (timeZone — TZ database name e.g. "Asia/Calcutta", "GMT+5:30", "EST").
3. CRITICAL: Input MUST be TEXT format. Passing a DATE-TIME typed value (single-quoted literal or DATE-TIME variable) → runtime error.
4. No timezone or invalid timezone → silently defaults to GMT (no error thrown).
5. If the function cannot recognize the datetime string → returns the input value as text (no error, no ms returned).
6. Leap seconds are NOT included in elapsed time.
CANONICAL_EXAMPLES:
```deluge
currentDate = "03-Feb-2020 03:48:12"; // TEXT (double-quoted)
indiaTime = currentDate.unixEpoch("Asia/Calcutta"); // → 1580681892000
gmtMinus8 = currentDate.unixEpoch("GMT-8"); // → 1580730492000
defaultGMT = currentDate.unixEpoch(); // → 1580730492000 (defaults to GMT)
utcPlus8 = currentDate.unixEpoch("UTC+8"); // → 1580701692000
indiaGMT = currentDate.unixEpoch("GMT+5:30"); // → 1580681892000
// Invalid timezone → silently defaults to GMT
invalidTZ = currentDate.unixEpoch("Hello"); // → 1580730492000
// DATE-TIME typed literal (single-quoted) → runtime error
// '03-Feb-2020 03:48:12'.unixEpoch("GMT") → runtime error
```
FAILURE_MODES:
- Single-quoted date literal or DATE-TIME variable as input → runtime error (TEXT required)
- Invalid timezone string → silently defaults to GMT; no error raised
- Unrecognized datetime format in TEXT → returns original text, not milliseconds
- Leap seconds not counted; results near leap-second boundaries differ from strict UTC
RELATED_DOCS:
- toDateTimeString() (inverse: epoch ms → text) — https://www.zoho.com/deluge/help/functions/datetime/todatetimestring.html
- Date-time data type — ../../datatypes/date-time.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/unix-epoch.html
assets/functions/deluge/built-in-functions/number/weekday.dou.md
FILE_PURPOSE: Deluge `weekday()` — get day-of-week NUMBER (1=Sunday, 7=Saturday) from DATE-TIME.
MENTAL_MODEL: `dt.weekday()` = 1 (Sun) through 7 (Sat); time ignored; alias: `getDayOfWeek()`.
WHEN_TO_USE:
- Need a numeric day-of-week from a DATE-TIME (1-based, Sunday=1)
- Conditional logic based on whether a date is a weekday or weekend
- Scheduling checks for specific weekdays
WHEN_NOT_TO_USE:
- Need week-of-year number → use getWeekOfYear()
- Need day-of-month → use getDay() / day()
- Need next/previous business day → use nextWeekDay() / previousWeekDay()
TRIGGER_KEYWORDS: weekday(), getDayOfWeek(), day-of-week-number, Sunday-equals-1, Saturday-equals-7
RULES:
1. Return type: NUMBER — 1=Sunday, 2=Monday, 3=Tuesday, 4=Wednesday, 5=Thursday, 6=Friday, 7=Saturday.
2. Input: DATE-TIME only. Time component is NOT taken into account.
3. Aliases: weekday() and getDayOfWeek() are equivalent.
4. Returns NULL (not error) when: month is < 3 chars; hour/min/sec is > 2 digits; date is > 2 digits; year is > 4 digits.
5. Runtime error when: 2-digit date exceeds days in the month; month specified in 3+ chars but incorrect.
CANONICAL_EXAMPLES:
// 15-Mar-2019 is Friday → returns 6
dayNum = '15-Mar-2019'.weekday(); // → 6
// Alias getDayOfWeek()
dayNum = '15-Mar-2019'.getDayOfWeek(); // → 6
// Functional form
dayNum = weekday('15-Mar-2019'); // → 6
// Weekend check: 1=Sunday, 7=Saturday
dayNum = today.weekday();
if (dayNum == 1 || dayNum == 7) { /* weekend */ }
FAILURE_MODES:
- Expecting day name strings — function returns NUMBER 1–7, not "Monday" etc.
- Off-by-one: Deluge convention is 1=Sunday, NOT 0=Sunday or 1=Monday
- Month in < 3 chars → null
- Hour/min/sec in > 2 digits → null
- Date in > 2 digits → null
- Year in > 4 digits → null
- 2-digit date exceeds days in month → runtime error
- Incorrect month string (3+ chars) → runtime error
RELATED_DOCS:
- getWeekOfYear() — getWeekOfYear.dou.md
- getDayOfYear() — getDayOfYear.dou.md
- nextWeekDay() — https://www.zoho.com/deluge/help/functions/datetime/nextweekday.html
- Date-time data type — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/weekday.html
assets/functions/deluge/built-in-functions/number/workday.dou.md
FILE_PURPOSE: Deluge `workday()` — add business days to DATE-TIME with optional weekend and holiday lists.
MENTAL_MODEL: `dt.workday(n, [weekends], [holidays])` = DATE-TIME n business days ahead; n < 0 returns dt unchanged.
WHEN_TO_USE:
- Calculating a deadline N business days from a given date
- Custom weekend or holiday configurations needed for business-day arithmetic
WHEN_NOT_TO_USE:
- Need to add calendar days (not business) → use addDay()
- Need to subtract business days → workday() does not go backward (use subBusinessDay())
- Need count of business days between two dates → use workDaysBetween()
TRIGGER_KEYWORDS: workday(), add-business-days, workday-weekends-param, workday-holidays-param, negative-numberOfBusinessDays-no-op
RULES:
1. Return type: DATE-TIME (date only — time component not taken into account).
2. Params: DATE-TIME (start), NUMBER (numberOfBusinessDays), optional COLLECTION<TEXT> (weekends), optional COLLECTION<DATE> (holidays).
3. Default weekends: {"Saturday", "Sunday"}. Override by passing a custom weekends COLLECTION.
4. Default holidays: empty — any non-weekend day is a working day if no holidays provided.
5. CRITICAL: A negative numberOfBusinessDays returns the input date unchanged (does NOT subtract business days).
6. Runtime error when: h/m/s in > 2 digits; date exceeds days in month; incorrect month; year > 4 digits.
CANONICAL_EXAMPLES:
```deluge
// 15-Mar-2019 is Friday; +1 business day → Monday 18-Mar-2019
newDate = '15-Mar-2019'.workday(1); // → '18-Mar-2019'
// Custom weekend (Sunday only) with holiday list
// Assuming current date is 23-May-2020
newDate = zoho.currentdate.workday(5, {"Sunday"}, {'25-May-2020'}); // → '30-May-2020'
// Functional form
newDate = workday('15-Mar-2019', 1); // → '18-Mar-2019'
// Negative N returns input unchanged — does NOT go backward
same = '15-Mar-2019'.workday(-3); // → '15-Mar-2019' (not 12-Mar)
```
FAILURE_MODES:
- Negative numberOfBusinessDays does NOT subtract business days — returns input date unchanged
- Date exceeds days in that month → runtime error
- Hour/min/sec in > 2 digits → runtime error
- Incorrect month → runtime error
- Year in > 4 digits → runtime error
RELATED_DOCS:
- addBusinessDay() — ../date-time/../date-time/add-sub-business-day.dou.md
- subBusinessDay() — https://www.zoho.com/deluge/help/functions/datetime/subbusinessday.html
- workDaysBetween() — https://www.zoho.com/deluge/help/functions/datetime/workdaysbetween.html
- Date-time data type — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/datetime/workday.html
assets/functions/deluge/built-in-functions/text/concat.dou.md
FILE_PURPOSE: Deluge `concat()` — join two TEXT strings into a single concatenated string.
MENTAL_MODEL: `text1.concat(text2)` = TEXT; equivalent to `text1 + text2`; both arguments must be TEXT.
WHEN_TO_USE:
- Append one text string to another to form a combined string.
- Chain multiple `concat()` calls to join more than two strings.
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
concat, text1.concat(text2), chained concat, string join, text concatenation
RULES:
- Return type: TEXT.
- Input types: TEXT, TEXT.
- Both `.concat(text2)` and `concat(text1, text2)` syntaxes are valid.
- `concat()` calls can be chained: `text1.concat(" ").concat(text2)`.
CANONICAL_EXAMPLES:
```deluge
text1 = "Zoho";
text2 = "Deluge";
result = text1.concat(text2);
info concat(text1, text2); // yields "ZohoDeluge"
info result; // yields "ZohoDeluge"
info text1.concat(" ").concat(text2); // yields "Zoho Deluge"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Text functions — text.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/concat.html
assets/functions/deluge/built-in-functions/text/contains-containsIgnoreCase.dou.md
FILE_PURPOSE: Deluge `contains()` / `containsIgnoreCase()` — check if a TEXT string contains a substring.
MENTAL_MODEL: `str.contains(sub)` = BOOLEAN; `contains()` is case-sensitive; `containsIgnoreCase()` ignores case.
WHEN_TO_USE:
- Check if a substring exists anywhere within a text string (case-sensitive).
- Check if a substring exists regardless of letter case using `containsIgnoreCase()`.
WHEN_NOT_TO_USE:
- When checking only at the start or end of a string — use `startsWith()` or `endsWith()` instead.
- When the position (index) of the substring is needed — use `find()` / `indexOf()` instead.
TRIGGER_KEYWORDS:
contains(), containsIgnoreCase(), substring existence check, case-sensitive contains, case-insensitive contains
RULES:
- Return type: BOOLEAN.
- Input types: TEXT (string), TEXT (searchString).
- `contains()`: performs a case-sensitive search.
- `containsIgnoreCase()`: performs a case-insensitive search.
- Returns `false` (not an error) when the `searchString` is not found.
- Space character is a valid `searchString` and is treated like any other character.
CANONICAL_EXAMPLES:
```deluge
Product = "Zoho CRM";
check = Product.contains("CRM"); // true (exact case match)
check = Product.contains("creator "); // false (case mismatch and trailing space)
check = Product.contains(" "); // true (space is a valid search string)
boolVal = Product.containsIgnoreCase("creator"); // true (case-insensitive)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- notContains — ../../built-in-functions/list/../list/list-notContains-isEmpty.dou.md
- Text functions — text.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/contains.html, https://www.zoho.com/deluge/help/functions/string/containsignorecase.html
assets/functions/deluge/built-in-functions/text/endsWith-endsWithIgnoreCase.dou.md
FILE_PURPOSE: Deluge `endsWith()` / `endsWithIgnoreCase()` — check if a TEXT string ends with a specific suffix.
MENTAL_MODEL: `str.endsWith(suffix)` = BOOLEAN; `endsWith()` is case-sensitive; `endsWithIgnoreCase()` ignores case.
WHEN_TO_USE:
- Validate that a string ends with a specific suffix (e.g., file extension, domain ending).
- Check suffix match without caring about letter case using `endsWithIgnoreCase()`.
WHEN_NOT_TO_USE:
- When checking the start of a string — use `startsWith()` instead.
- When checking anywhere in the string (not just the end) — use `contains()` instead.
TRIGGER_KEYWORDS:
endsWith(), endsWithIgnoreCase(), suffix check, case-sensitive endsWith, case-insensitive endsWith
RULES:
- Return type: BOOLEAN.
- Input types: TEXT (string), TEXT (searchString).
- `endsWith()`: performs a case-sensitive search.
- `endsWithIgnoreCase()`: performs a case-insensitive search.
- Returns `false` (not an error) when `searchString` is not found at the end.
CANONICAL_EXAMPLES:
```deluge
Product = "Zoho CRM";
var = endsWith(Product, "CRM"); // true (exact case match)
var = endsWith(Product, "creator"); // false (case mismatch)
boolVal = endsWithIgnoreCase(Product, "creator"); // true (case-insensitive)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- startsWith — https://www.zoho.com/deluge/help/functions/string/endswith.html
- Text functions — text.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/endswith.html, https://www.zoho.com/deluge/help/functions/string/endswithignorecase.html
assets/functions/deluge/built-in-functions/text/equalsIgnoreCase.dou.md
FILE_PURPOSE: Deluge `equalsIgnoreCase()` — compare two TEXT strings for equality, ignoring case differences.
MENTAL_MODEL: `str1.equalsIgnoreCase(str2)` = BOOLEAN; full-string equality ignoring case; spaces and all other chars remain significant.
WHEN_TO_USE:
- Compare two text strings for equality while treating uppercase and lowercase as equivalent.
- Validate user input against a known string without requiring exact case match.
WHEN_NOT_TO_USE:
- When exact case-sensitive equality is required — use the `==` operator instead.
- When checking for substring presence (not full-string equality) — use `contains()` instead.
TRIGGER_KEYWORDS:
equalsIgnoreCase(), case-insensitive equality, full-string equality check, spaces-significant, ignore-case comparison
RULES:
- Return type: BOOLEAN.
- Input types: TEXT (string1), TEXT (string2).
- Performs a case-insensitive comparison of the full strings.
- Spaces and all non-letter characters are significant: `"ZohoCRM"` vs `"Zoho CRM"` (space differs) → false.
- Both `.equalsIgnoreCase(str2)` and `equalsIgnoreCase(str1, str2)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
Product = "ZohoCRM";
var = equalsIgnoreCase(Product, "ZohoCRM"); // true (same string, any casing)
var = equalsIgnoreCase(Product, "ZOHOCREATOR"); // true (case-insensitive)
var = equalsIgnoreCase(Product, "Zoho CRM"); // false (space differs)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Text functions — text.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/equalsIgnoreCase.html
assets/functions/deluge/built-in-functions/text/find-indexOf.dou.md
FILE_PURPOSE: Deluge `find()` / `indexOf()` — return the 0-based index of a searchString's first occurrence.
MENTAL_MODEL: `str.find(sub)` = NUMBER; 0-based index of `sub`'s first character in `str`; -1 if not found; case-sensitive.
WHEN_TO_USE:
- Locate the position of a character or substring within a text string.
- Determine whether a substring exists by checking if the returned index is ≥ 0 (found) or -1 (absent).
WHEN_NOT_TO_USE:
- When only boolean presence (not position) is needed — use `contains()` instead.
- When the last occurrence index is needed — use `lastIndexOf()` instead.
TRIGGER_KEYWORDS:
find(), indexOf(), first-occurrence index, zero-based index, -1 not found, case-sensitive find
RULES:
- Return type: NUMBER.
- Input types: TEXT (string), TEXT (searchString).
- `find()` and `indexOf()` are aliases; all four syntaxes are equivalent:
`str.find(sub)`, `find(str, sub)`, `str.indexOf(sub)`, `indexOf(str, sub)`.
- Index is 0-based (first character of string = index 0).
- Returns the index of the FIRST character of `searchString`'s first occurrence in the string.
- Returns `-1` if `searchString` is not found (no error is raised).
- Search is case-sensitive.
CANONICAL_EXAMPLES:
```deluge
text = "Hello world, welcome to the universe";
firstIndex = text.find("e"); // returns 1 (first 'e' at index 1 in "Hello")
firstIndex = text.indexOf("e"); // returns 1 — indexOf is an alias of find
notFound = text.find("z"); // returns -1 (not present)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- lastIndexOf — lastIndexOf.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/find.html, https://www.zoho.com/deluge/help/functions/string/indexof.html
assets/functions/deluge/built-in-functions/text/getAlpha-getAlphaNumeric.dou.md
FILE_PURPOSE: Deluge `getAlpha()` / `getAlphaNumeric()` — extract letters or alphanumeric characters from a string.
MENTAL_MODEL: `str.getAlpha()` = letters only; `str.getAlphaNumeric()` = letters + digits; all other chars stripped and compacted.
WHEN_TO_USE:
- Strip all non-letter characters (digits, spaces, punctuation, special chars) from a string using `getAlpha()`.
- Strip all non-alphanumeric characters (spaces, punctuation, special chars) using `getAlphaNumeric()`.
WHEN_NOT_TO_USE:
- When specific characters (not all non-alpha) need removal — use `remove()` or `replaceAll()` instead.
- When the original character positions or spacing must be preserved — these functions compact the result with no separators.
TRIGGER_KEYWORDS:
getAlpha(), getAlphaNumeric(), extract letters only, extract alphanumeric, strip special chars, spaces stripped
RULES:
- Return type: TEXT.
- Input type: TEXT.
- `getAlpha()`: returns only letter characters (A-Z, a-z); digits, spaces, and all other characters are removed.
- `getAlphaNumeric()`: returns only letters and digits (A-Z, a-z, 0-9); spaces, hyphens, and special characters are removed.
- Characters are returned in their original order; the result is a compacted string with no separators.
- Both `.getAlpha()` and `getAlpha(str)` syntaxes are valid; same for `getAlphaNumeric()`.
CANONICAL_EXAMPLES:
```deluge
Subject = "Your joining date is January 25th, 2019";
letters = getAlpha(Subject);
// returns "YourjoiningdateisJanuaryth" (digits, comma, space stripped)
Subject = "Your joining date is 25-01-2019";
chars = getAlphaNumeric(Subject);
// returns "Yourjoiningdateis25012019" (hyphens, spaces stripped)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- removeAllAlpha — https://www.zoho.com/deluge/help/functions/string/getAlpha.html
- Text functions — text.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/getAlpha.html, https://www.zoho.com/deluge/help/functions/string/getAlphaNumeric.html
assets/functions/deluge/built-in-functions/text/getOccurenceCount.dou.md
FILE_PURPOSE: Deluge `getOccurenceCount()` — count occurrences of a substring within a text string.
MENTAL_MODEL: `str.getOccurenceCount(sub)` = NUMBER; counts all occurrences of `sub` in `str`; case-sensitive.
WHEN_TO_USE:
- Count how many times a specific substring appears in a text string.
WHEN_NOT_TO_USE:
- When only checking presence (not count) — use `contains()` instead.
TRIGGER_KEYWORDS:
getOccurenceCount(), occurrence count, case-sensitive count, searchString frequency, substring tally
RULES:
- Return type: NUMBER.
- Input types: TEXT (string), TEXT (searchString).
- Performs a case-sensitive search.
- Both `.getOccurenceCount(searchString)` and `getOccurenceCount(string, searchString)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
text = "Zoho CRM, a customer relationship platform, helps you manage customer data effectively";
count = text.getOccurenceCount("database"); // returns 2
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/getOccurenceCount.html
assets/functions/deluge/built-in-functions/text/getPrefix-getSuffix.dou.md
FILE_PURPOSE: Deluge `getPrefix()` / `getSuffix()` — split a string on the first delimiter occurrence.
MENTAL_MODEL: `str.getPrefix(delim)` = TEXT before first `delim`; `str.getSuffix(delim)` = TEXT after first `delim`; null if not found.
WHEN_TO_USE:
- Extract the portion of a string before a known delimiter (e.g., comma, keyword, separator).
- Extract the portion of a string after a known delimiter.
WHEN_NOT_TO_USE:
- When splitting on more than the first occurrence — both functions use only the FIRST occurrence as the split point.
- When the delimiter is not found — both return `null`, not an empty string; guard before using the result.
TRIGGER_KEYWORDS:
getPrefix(), getSuffix(), before-delimiter extraction, after-delimiter extraction, null-not-found, case-sensitive split
RULES:
- Return type: TEXT.
- Input types: TEXT (string), TEXT (searchString).
- Both functions perform a case-sensitive search.
- `getPrefix()`: returns all characters BEFORE the first occurrence of searchString.
- `getSuffix()`: returns all characters AFTER the first occurrence of searchString.
- If searchString is not found, both functions return `null` (not an empty string and not an error).
- Only the FIRST occurrence determines the split point; all content after it is included in getSuffix.
CANONICAL_EXAMPLES:
```deluge
text = "Zoho CRM is a customer relationship platform, which helps you manage sales pipelines";
newText = text.getPrefix(","); // returns "Zoho CRM is a customer relationship platform"
text2 = "Manage customer data with Zoho CRM";
newText2 = text2.getSuffix("with"); // returns " Zoho CRM" (includes trailing space)
```
FAILURE_MODES:
- When searchString is not found, both functions return `null` — check for null before further string operations on the result.
- Only the FIRST occurrence is used as split point; content between multiple occurrences of the delimiter is included in the result.
RELATED_DOCS:
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/getPrefix.html, https://www.zoho.com/deluge/help/functions/string/getSuffix.html
assets/functions/deluge/built-in-functions/text/hexToText.dou.md
FILE_PURPOSE: Deluge `hexToText()` — decode a hexadecimal-encoded string to its text equivalent.
MENTAL_MODEL: `hexStr.hexToText()` = TEXT; decodes hex-encoded bytes to their ASCII/text representation.
WHEN_TO_USE:
- Decode a hexadecimal-encoded string back to its human-readable text form.
WHEN_NOT_TO_USE:
- When converting text TO hex — use `textToHex()` instead.
TRIGGER_KEYWORDS:
hexToText(), hex decode, hexadecimal to text, hex literal decode, hex-string conversion, textToHex inverse
RULES:
- Return type: TEXT.
- Input type: TEXT (hexadecimal string).
- Both `<text>.hexToText()` and `hexToText(<text>)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
response = hexToText("5a6f686f2043524d"); // Returns "Zoho CRM"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- textToHex — textToHex.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/hextotext.html
assets/functions/deluge/built-in-functions/text/lastIndexOf.dou.md
FILE_PURPOSE: Deluge `lastIndexOf()` — return the 0-based index of the LAST occurrence of a searchString.
MENTAL_MODEL: `str.lastIndexOf(sub)` = NUMBER; 0-based index of last occurrence of `sub`'s first char; -1 if not found; case-sensitive.
WHEN_TO_USE:
- Locate the position of the LAST occurrence of a character or substring within a text string.
- Complement `indexOf()` when the rearmost match is needed instead of the first.
WHEN_NOT_TO_USE:
- When the FIRST occurrence index is needed — use `indexOf()` / `find()` instead.
- When only boolean presence is needed — use `contains()` instead.
TRIGGER_KEYWORDS:
lastIndexOf(), last-occurrence index, zero-based lastIndexOf, -1 not found, case-sensitive lastindex
RULES:
- Return type: NUMBER.
- Input types: TEXT (string), TEXT (searchString).
- Index is 0-based (first character of string = index 0).
- Returns the index of the first character of searchString's LAST occurrence in the string.
- Returns `-1` if searchString is not found (no error is raised).
- Performs a case-sensitive search.
- Both `.lastIndexOf(searchString)` and `lastIndexOf(string, searchString)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
text = "Hello world, welcome to the universe";
lastIndex = text.lastIndexOf("e"); // returns 35
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- indexOf / find — find-indexOf.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/lastindexof.html
assets/functions/deluge/built-in-functions/text/left.dou.md
FILE_PURPOSE: Deluge `left()` — return a specified number of characters from a string's left side.
MENTAL_MODEL: `str.left(n)` = TEXT; first `n` chars of `str`; negative `n` returns full string; exceeding length throws.
WHEN_TO_USE:
- Extract a known-length prefix from a text string.
WHEN_NOT_TO_USE:
- When characters from the right side are needed — use `right()` instead.
- When a substring at an arbitrary position is needed — use `mid()` or `substring()` instead.
TRIGGER_KEYWORDS:
left(), substring from-left, left-side characters, negative-n full-string, left-overflow runtime
RULES:
- Return type: TEXT.
- Input types: TEXT (string), NUMBER (number of characters to return).
- Syntax: `<string>.left(<number>)` (only one syntax form documented in source).
- If `number` exceeds the character count of the string → runtime error.
- If `number` is negative → returns the full original string unchanged.
CANONICAL_EXAMPLES:
```deluge
product_name = "Zoho CRM";
newText = product_name.left(4); // returns "Zoho"
```
FAILURE_MODES:
- `left(n)` where `n` exceeds the string's character count throws a runtime error.
RELATED_DOCS:
- right — right.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/left.html
assets/functions/deluge/built-in-functions/text/leftpad.dou.md
FILE_PURPOSE: Deluge `leftpad()` — add leading whitespace to a text string for alignment.
MENTAL_MODEL: `str.leftpad(n)` = TEXT with `n` leading spaces prepended; max `n` is 100; negative `n` returns original string.
WHEN_TO_USE:
- Add leading whitespace to a text string for fixed-width formatting or column alignment.
WHEN_NOT_TO_USE:
- When right-side padding is needed — use `rightpad()` instead.
- When non-space padding characters are required — this function pads with whitespace only.
TRIGGER_KEYWORDS:
leftpad(), left-pad whitespace, leading-space prepend, negative-pad no-change, leftpad max-limit
RULES:
- Return type: TEXT.
- Input types: TEXT (text), NUMBER (whitespaces).
- Both `.leftpad(<whitespaces>)` and `leftpad(<text>, <whitespaces>)` syntaxes are valid.
- Pads with whitespace characters only (padding character is not configurable).
- Negative `whitespaces` value returns the original string unchanged.
- Maximum `whitespaces` value: 100 (source notation "100,00" is a typo; authoritative value is 100).
- `whitespaces` is the number of spaces to prepend (per source description); source example output appears inconsistent with this but the description is treated as authoritative.
CANONICAL_EXAMPLES:
```deluge
text = "Left";
newText = text.leftpad(6); // returns " Left" (per source example)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- rightpad — rightpad.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/leftpad.html
assets/functions/deluge/built-in-functions/text/len-length.dou.md
FILE_PURPOSE: Deluge `len()` / `length()` — return the character count (including spaces) of a text string.
MENTAL_MODEL: `str.len()` = NUMBER of chars including spaces; `str.length()` is a direct alias.
WHEN_TO_USE:
- Determine the total number of characters in a text string (spaces count).
- Validate string length before passing to length-limited functions.
WHEN_NOT_TO_USE:
- When checking if a string is empty — use `isEmpty()` instead.
TRIGGER_KEYWORDS:
len(), length(), character count, spaces-included count, len-length aliases, string length check
RULES:
- Return type: NUMBER.
- Input type: TEXT.
- `len()` and `length()` are aliases; all four syntaxes are equivalent:
`.len()`, `len(str)`, `.length()`, `length(str)`.
- Spaces are included in the returned character count.
CANONICAL_EXAMPLES:
```deluge
text = "Manage customer relationships with Zoho CRM";
count = text.len(); // returns 46
textLength = text.length(); // returns 46 — length() is alias of len()
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- ltrim — ltrim.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/len.html, https://www.zoho.com/deluge/help/functions/string/length.html
assets/functions/deluge/built-in-functions/text/ltrim.dou.md
FILE_PURPOSE: Deluge `ltrim` — remove all leading whitespace characters from the left side of a string.
MENTAL_MODEL: `str.ltrim()` = TEXT with all leading spaces stripped; trailing spaces and internal spaces are unaffected.
WHEN_TO_USE:
- Remove leading (left-side) whitespace from a text string before processing or comparison.
WHEN_NOT_TO_USE:
- When trailing (right-side) whitespace needs removal — use `rtrim()` instead.
- When ALL leading and trailing whitespace should be removed — use `trim()` instead.
TRIGGER_KEYWORDS:
ltrim(), leading-whitespace removal, left-trim, ltrim vs trim, ltrim vs rtrim
RULES:
- Return type: TEXT.
- Input type: TEXT.
- Removes ALL whitespace inserted BEFORE the text (all leading spaces).
- Trailing and internal spaces are not affected.
- Both `.ltrim()` and `ltrim(<input_text>)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
text = " Zoho";
result = text.ltrim(); // returns "Zoho"
text_size = text.length(); // returns 7 (3 leading spaces + 4 chars)
result_size = result.length(); // returns 4
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- rtrim — rtrim.dou.md
- trim — trim.dou.md
- length — len-length.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/ltrim.html
assets/functions/deluge/built-in-functions/text/matches.dou.md
FILE_PURPOSE: Deluge matches() — test if a string matches a regex pattern exactly
MENTAL_MODEL: matches(string, regex) = Boolean; true only when string matches entire regex pattern; case-sensitive
WHEN_TO_USE:
- Validate that a string fully conforms to a regex pattern (e.g. ID format, email format, currency).
- Use case-sensitive full-string comparison against a regular expression.
WHEN_NOT_TO_USE:
- Partial pattern matching (substring search) — use `contains()` instead.
- Case-insensitive matching — `matches()` is always case-sensitive.
TRIGGER_KEYWORDS:
matches, string.matches, regex comparison, boolean return matches, regexString, case-sensitive matches
RULES:
- Two syntaxes: `<variable> = <string>.matches(<regexString>);` and `matches(<string>, <regexString>)`.
- Return type: BOOLEAN.
- Returns `true` if `<string>` matches the entire `<regexString>` regex; `false` otherwise.
- Comparison is case-sensitive.
- `<string>` and `<regexString>` are both TEXT.
CANONICAL_EXAMPLES:
```deluge
IDValue = "ID004500F";
retValue = matches(IDValue, "[A-Z]{2}[0-9]{6}[A-Z]"); // returns true
num = "$345.78";
ret = matches(num, "\\$[0-9]+\\.[0-9]{2}"); // returns true
useremail = "jane2023@zylker.com";
format_check = matches(useremail, "[a-z 0-9]+\\@[a-z]+\\.[a-z]+"); // returns true
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
- Functions returning boolean — https://www.zoho.com/deluge/help/functions-returning-boolean.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/matches.html
assets/functions/deluge/built-in-functions/text/mid.dou.md
FILE_PURPOSE: Deluge mid() — extract a substring between two 0-based character indices
MENTAL_MODEL: mid(text, start, end) = TEXT slice [start, end); 0-based; end optional; start must not exceed length
WHEN_TO_USE:
- Extract a substring from a known start index to a known end index.
- Extract from a start index to the end of the string (omit end_index).
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
mid, string.mid, start_index, end_index optional, 0-based index mid, substring mid
RULES:
- Two syntaxes: `<variable> = <source_text>.mid(<start_index>, [<end_index>]);` and `mid(<source_text>, <start_index>, [<end_index>])`.
- Return type: TEXT.
- Index is 0-based.
- `<end_index>` is optional; if omitted, rest of text from `<start_index>` is returned.
- Negative `<end_index>` is treated as omitted; rest of text from `<start_index>` is returned.
- If `<start_index>` equals `<end_index>`, returns empty string.
- Returned text includes `<start_index>` character and excludes `<end_index>` character.
- `<start_index>` and `<end_index>` must not exceed the length of `<source_text>`.
- `<end_index>` must be greater than or equal to `<start_index>`.
CANONICAL_EXAMPLES:
```deluge
sourceText = "Welcome to Zoho Deluge";
newText = sourceText.mid(11, 22);
info newText; // returns "Zoho Deluge"
```
FAILURE_MODES:
- `<start_index>` or `<end_index>` exceeds length of `<source_text>` → runtime error.
- `<end_index>` is less than `<start_index>` → runtime error.
RELATED_DOCS:
- subString — subString-subText.dou.md
- subText — subString-subText.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/mid.html
assets/functions/deluge/built-in-functions/text/proper.dou.md
FILE_PURPOSE: Deluge proper() — convert a string to title/start case
MENTAL_MODEL: proper(string) = TEXT; first char of each word uppercase, all remaining lowercase
WHEN_TO_USE:
- Normalize user-entered names or titles to consistent start case.
- Convert an all-lowercase or all-uppercase string to title case.
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
proper, string.proper, title case, start case, first char uppercase proper
RULES:
- Syntax: `<variable> = <string>.proper();`
- Return type: TEXT.
- First character of each word is returned in upper case.
- All other characters in each word are returned in lower case.
- `<string>` must be TEXT.
CANONICAL_EXAMPLES:
```deluge
product_name = "zoho creator";
newText = product_name.proper(); // returns "Zoho Crm"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/proper.html
assets/functions/deluge/built-in-functions/text/remove-all-alpha.dou.md
FILE_PURPOSE: Deluge removeAllAlpha() — strip all alphabetic letters from a string
MENTAL_MODEL: removeAllAlpha(string) = TEXT; removes all [a-zA-Z] letters; spaces and non-letter chars retained
WHEN_TO_USE:
- Extract only digits, spaces, and special characters from a mixed string.
- Strip letter characters to isolate numeric or symbolic content.
WHEN_NOT_TO_USE:
- Removing digits as well — use `removeAllAlphaNumeric()` instead.
TRIGGER_KEYWORDS:
removeAllAlpha, string.removeAllAlpha, strip letters, alpha removal, empty on all-alpha
RULES:
- Two syntaxes: `<variable> = <string>.removeAllAlpha();` and `removeAllAlpha(<string>)`.
- Return type: TEXT.
- Removes all alphabetic letters (a–z, A–Z) from the string.
- Returns all other characters, including spaces and digits.
- If the string contains only letters, returns an empty string.
- `<string>` must be TEXT.
CANONICAL_EXAMPLES:
```deluge
Product = "Zoho CRM 8";
newText = Product.removeAllAlpha(); // returns " 5"
```
FAILURE_MODES:
- String contains only letters → returns empty string (not null; no error).
RELATED_DOCS:
- removeAllAlphaNumeric — remove-all-alphanumeric.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/removeAllAlpha.html
assets/functions/deluge/built-in-functions/text/remove-all-alphanumeric.dou.md
FILE_PURPOSE: Deluge removeAllAlphaNumeric() — strip all letters and digits from a string
MENTAL_MODEL: removeAllAlphaNumeric(string) = TEXT; removes [a-zA-Z0-9]; spaces and special chars retained
WHEN_TO_USE:
- Extract only spaces and special characters from a mixed string.
- Isolate punctuation or symbols from a string containing letters and digits.
WHEN_NOT_TO_USE:
- Removing only letters (keeping digits) — use `removeAllAlpha()` instead.
TRIGGER_KEYWORDS:
removeAllAlphaNumeric, string.removeAllAlphaNumeric, strip alphanumeric, special chars only, empty on all-alphanumeric
RULES:
- Two syntaxes: `<variable> = <string>.removeAllAlphaNumeric();` and `removeAllAlphaNumeric(<string>)`.
- Return type: TEXT.
- Removes all alphanumeric characters (letters a–z, A–Z and digits 0–9) from the string.
- Returns all other characters, including spaces.
- If the string contains only alphanumeric characters, returns an empty string.
- `<string>` must be TEXT.
CANONICAL_EXAMPLES:
```deluge
Product = "Zoho CRM v8 is live!";
newText = Product.removeAllAlphaNumeric(); // returns " !"
```
FAILURE_MODES:
- String contains only alphanumeric characters → returns empty string (not null; no error).
RELATED_DOCS:
- removeAllAlpha — remove-all-alpha.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/removeallalphanumeric.html
assets/functions/deluge/built-in-functions/text/remove-first-occurence.dou.md
FILE_PURPOSE: Deluge removeFirstOccurence() — remove only the first matching substring occurrence
MENTAL_MODEL: removeFirstOccurence(string, search) = TEXT; removes first match only; unchanged if absent; case-sensitive
WHEN_TO_USE:
- Remove only the first occurrence of a substring when duplicates should be preserved.
WHEN_NOT_TO_USE:
- Removing all occurrences — use `remove()` instead.
- Removing only the last occurrence — use `removeLastOccurence()` instead.
TRIGGER_KEYWORDS:
removeFirstOccurence, string.removeFirstOccurence, first occurrence removal, case-sensitive first, remove first match
RULES:
- Two syntaxes: `<variable> = <string>.removeFirstOccurence(<searchString>);` and `removeFirstOccurence(<string>, <searchString>)`.
- Return type: TEXT.
- Removes only the FIRST occurrence of `<searchString>` from `<string>`.
- If `<searchString>` is not found, returns the original string unchanged.
- Performs a case-sensitive search.
- Both `<string>` and `<searchString>` must be TEXT.
CANONICAL_EXAMPLES:
```deluge
Product = "Zoho CRM v8";
newText = Product.removeFirstOccurence("v8"); // returns "Zoho CRM "
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- removeLastOccurence — remove-last-occurence.dou.md
- remove — remove.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/removefirstoccurence.html
assets/functions/deluge/built-in-functions/text/remove-last-occurence.dou.md
FILE_PURPOSE: Deluge removeLastOccurence() — remove only the last matching substring occurrence
MENTAL_MODEL: removeLastOccurence(string, search) = TEXT; removes last match only; unchanged if absent; case-sensitive
WHEN_TO_USE:
- Remove only the last occurrence of a substring when earlier occurrences should be preserved.
WHEN_NOT_TO_USE:
- Removing all occurrences — use `remove()` instead.
- Removing only the first occurrence — use `removeFirstOccurence()` instead.
TRIGGER_KEYWORDS:
removeLastOccurence, string.removeLastOccurence, last occurrence removal, case-sensitive last, remove last match
RULES:
- Two syntaxes: `<variable> = <string>.removeLastOccurence(<searchString>);` and `removeLastOccurence(<string>, <searchString>)`.
- Return type: TEXT.
- Removes only the LAST occurrence of `<searchString>` from `<string>`.
- If `<searchString>` is not found, returns the original string unchanged.
- Performs a case-sensitive search.
- Both `<string>` and `<searchString>` must be TEXT.
CANONICAL_EXAMPLES:
```deluge
Product = "Manage leads using Zoho CRM lead module";
newText = Product.removeLastOccurence("app");
// returns "Manage leads using Zoho CRM module"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- removeFirstOccurence — remove-first-occurence.dou.md
- remove — remove.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/removelastoccurence.html
assets/functions/deluge/built-in-functions/text/remove.dou.md
FILE_PURPOSE: Deluge remove() — remove all occurrences of a substring from a string
MENTAL_MODEL: remove(string, search) = TEXT; removes ALL occurrences of search; unchanged if absent; case-sensitive
WHEN_TO_USE:
- Remove every occurrence of a known substring from a text value.
- Sanitize strings by stripping unwanted tokens.
WHEN_NOT_TO_USE:
- Removing only the first occurrence — use `removeFirstOccurence()` instead.
- Removing only the last occurrence — use `removeLastOccurence()` instead.
TRIGGER_KEYWORDS:
remove, string.remove, remove all occurrences, case-sensitive remove, searchString remove
RULES:
- Two syntaxes: `<variable> = <string>.remove(<searchString>);` and `remove(<string>, <searchString>)`.
- Return type: TEXT.
- Removes ALL occurrences of `<searchString>` from `<string>`.
- If `<searchString>` is not found, returns the original string unchanged.
- Performs a case-sensitive search.
- Both `<string>` and `<searchString>` must be TEXT.
CANONICAL_EXAMPLES:
```deluge
Product = "Zoho CRM";
newText = Product.remove("CRM"); // returns "Zoho "
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- removeFirstOccurence — remove-first-occurence.dou.md
- removeLastOccurence — remove-last-occurence.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/remove.html
assets/functions/deluge/built-in-functions/text/replace-all.dou.md
FILE_PURPOSE: Deluge replaceAll() — replace all occurrences of a substring in a string
MENTAL_MODEL: replaceAll(string, search, replacement, regexEscape) = TEXT; replaces all matches; supports regex by default
WHEN_TO_USE:
- Replace every occurrence of a substring or regex pattern in a string.
- Use with `boolean=true` when the search string contains regex special characters that should be treated as literals.
WHEN_NOT_TO_USE:
- Replacing only the first occurrence — use `replaceFirst()` instead.
TRIGGER_KEYWORDS:
replaceAll, string.replaceAll, regex escape boolean, replaceAll case-sensitive, all occurrences replace, replacementString
RULES:
- Two syntaxes: `<variable> = <string>.replaceAll(<searchString>, <replacementString>, <boolean>);` and `replaceAll(<string>, <searchString>, <replacementString>, <boolean>)`.
- Return type: TEXT.
- Replaces ALL occurrences of `<searchString>` with `<replacementString>`.
- If `<searchString>` is not found, returns the original string unchanged.
- Performs a case-sensitive search.
- `<boolean>` parameter (optional):
- `false` (default): regex is supported — special regex chars like `$` are treated as metacharacters.
- `true`: regex is escaped — all characters in `<searchString>` treated as literals.
- `<searchString>` accepts TEXT; accepts regex patterns when `<boolean>` is `false`.
CANONICAL_EXAMPLES:
```deluge
mainString = "Create online database applications";
newText = replaceAll(mainString, "online", "custom");
// returns "Create custom database applications"
// Literal replacement of regex-special char ($): pass true to escape
price = "$100 and $200";
newText = price.replaceAll("$100", "USD100", true); // treats "$" as literal
```
FAILURE_MODES:
- `<boolean>` is `false` (default) and `<searchString>` contains unescaped regex special chars (e.g. `$`) → special chars act as regex metacharacters, not literals; use `\\$` or pass `true` for literal match.
RELATED_DOCS:
- replaceFirst — replace-first.dou.md
- replaceAllIgnoreCase — https://www.zoho.com/deluge/help/functions/string/replaceallignorecase.html
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/replaceall.html
assets/functions/deluge/built-in-functions/text/replace-first.dou.md
FILE_PURPOSE: Deluge replaceFirst() — replace only the first occurrence of a substring
MENTAL_MODEL: replaceFirst(string, search, replacement, regexEscape) = TEXT; replaces first match only; supports regex
WHEN_TO_USE:
- Replace only the first occurrence of a substring or regex pattern while leaving later occurrences intact.
- Use with `boolean=true` when the search string contains regex special characters that should be treated as literals.
WHEN_NOT_TO_USE:
- Replacing all occurrences — use `replaceAll()` instead.
TRIGGER_KEYWORDS:
replaceFirst, string.replaceFirst, regex escape boolean, first occurrence replace, replaceFirst case-sensitive, replacementString
RULES:
- Two syntaxes: `<variable> = <string>.replaceFirst(<searchString>, <replacementString>, <boolean>);` and `replaceFirst(<string>, <searchString>, <replacementString>, <boolean>)`.
- Return type: TEXT.
- Replaces only the FIRST occurrence of `<searchString>` with `<replacementString>`.
- If `<searchString>` is not found, returns the original string unchanged.
- Performs a case-sensitive search.
- `<searchString>` accepts TEXT and regex patterns.
- `<boolean>` parameter:
- `false` (default): regex is supported — special regex chars are treated as metacharacters.
- `true`: regex is escaped — all characters in `<searchString>` treated as literals.
CANONICAL_EXAMPLES:
```deluge
mainString = "Create online database applications";
newText = replaceFirst(mainString, "online", "custom");
// returns "Create custom database applications"
// Regex pattern replacement
useremail = "nancy@zylker.com";
newdomain_email = replaceFirst(useremail, "\\@[a-z]{6}\\.[a-z]{3}", "@zylkertech.com");
// returns "nancy@zylkertech.com"
```
FAILURE_MODES:
- `<boolean>` is `false` (default) and `<searchString>` contains unescaped regex special chars → special chars act as metacharacters; use `\\` escape or pass `true` for literal match.
RELATED_DOCS:
- replaceAll — replace-all.dou.md
- replaceFirstIgnoreCase — https://www.zoho.com/deluge/help/functions/string/replacefirstignorecase.html
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/replacefirst.html
assets/functions/deluge/built-in-functions/text/right.dou.md
FILE_PURPOSE: Deluge `right()` — return a specified number of characters from a string's right side.
MENTAL_MODEL: `str.right(n)` = TEXT; last `n` chars; negative or exceeding-length `n` throws a runtime error.
WHEN_TO_USE:
- Extract a known-length suffix from a text string.
WHEN_NOT_TO_USE:
- When characters from the left side are needed — use `left()` instead.
- When a substring at an arbitrary position is needed — use `subString()` or `subText()` instead.
TRIGGER_KEYWORDS:
right(), right-side characters, right-suffix extraction, right-overflow runtime, right negative-n
RULES:
- Return type: TEXT.
- Input types: TEXT (string), NUMBER (number of characters to return).
- Syntax: `<string>.right(<number>)` (only one syntax form documented in source).
- If `number` exceeds the character count of the string → runtime error.
- If `number` is negative → runtime error.
CANONICAL_EXAMPLES:
```deluge
product_name = "Zoho CRM";
text = product_name.right(3); // returns "CRM"
```
FAILURE_MODES:
- `right(n)` where `n` exceeds the string's character count throws a runtime error.
- `right(n)` where `n` is negative throws a runtime error.
RELATED_DOCS:
- left — left.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/right.html
assets/functions/deluge/built-in-functions/text/rightpad.dou.md
FILE_PURPOSE: Deluge `rightpad()` — add trailing whitespace to a text string for alignment.
MENTAL_MODEL: `str.rightpad(n)` = TEXT with `n` trailing spaces appended; max 100; negative `n` returns original.
WHEN_TO_USE:
- Add trailing whitespace to a text string for fixed-width formatting or column alignment.
WHEN_NOT_TO_USE:
- When left-side padding is needed — use `leftpad()` instead.
- When non-space padding characters are required — this function pads with whitespace only.
TRIGGER_KEYWORDS:
rightpad(), right-pad whitespace, trailing-space append, negative-pad no-change, rightpad max-100
RULES:
- Return type: TEXT.
- Input types: TEXT (text), NUMBER (whitespaces).
- Both `.rightpad(<whitespaces>)` and `rightpad(<text>, <whitespaces>)` syntaxes are valid.
- Pads with whitespace characters only (padding character is not configurable).
- Negative `whitespaces` value returns the original string unchanged.
- Maximum `whitespaces` value: 100 (source notation "100,00" is a typo; same resolution as leftpad).
- `whitespaces` is the number of spaces to append (per source description; same pattern as leftpad — description treated as authoritative despite example inconsistency).
CANONICAL_EXAMPLES:
```deluge
text = "Right";
newText = text.rightpad(6); // returns "Right " (per source example)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- leftpad — leftpad.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/rightpad.html
assets/functions/deluge/built-in-functions/text/rtrim.dou.md
FILE_PURPOSE: Deluge `rtrim` — remove all trailing whitespace characters from the right side of a string.
MENTAL_MODEL: `str.rtrim()` = TEXT with all trailing spaces stripped; leading and internal spaces are unaffected.
WHEN_TO_USE:
- Remove trailing (right-side) whitespace from a text string before processing.
WHEN_NOT_TO_USE:
- When leading (left-side) whitespace needs removal — use `ltrim()` instead.
- When ALL leading and trailing whitespace should be removed — use `trim()` instead.
TRIGGER_KEYWORDS:
rtrim(), trailing-whitespace removal, right-trim, rtrim vs trim, rtrim vs ltrim
RULES:
- Return type: TEXT.
- Input type: TEXT.
- Removes ALL whitespace inserted AFTER the text (trailing spaces).
- Leading and internal spaces are not affected.
- Both `.rtrim()` and `rtrim(<input_text>)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
text = " Zoho Deluge ";
result = text.rtrim(); // returns " Zoho Deluge"
text_size = text.length(); // returns 17
result_size = result.length(); // returns 14
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- ltrim — ltrim.dou.md
- trim — trim.dou.md
- length — len-length.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/rtrim.html
assets/functions/deluge/built-in-functions/text/startsWith-startsWithIgnoreCase.dou.md
FILE_PURPOSE: Deluge `startsWith()` / `startsWithIgnoreCase()` — check if a TEXT string starts with a prefix.
MENTAL_MODEL: `str.startsWith(prefix)` = BOOLEAN; `startsWith()` is case-sensitive; `startsWithIgnoreCase()` ignores case.
WHEN_TO_USE:
- Validate that a string begins with a specific prefix (e.g., protocol, keyword, category).
- Check prefix match without caring about letter case using `startsWithIgnoreCase()`.
WHEN_NOT_TO_USE:
- When checking the end of a string — use `endsWith()` instead.
- When checking anywhere in the string (not just the start) — use `contains()` instead.
TRIGGER_KEYWORDS:
startsWith(), startsWithIgnoreCase(), prefix check, case-sensitive startsWith, case-insensitive startsWith
RULES:
- Return type: BOOLEAN.
- Input types: TEXT (string), TEXT (searchString).
- `startsWith()`: performs a case-sensitive search.
- `startsWithIgnoreCase()`: performs a case-insensitive search.
- Returns `false` (not an error) when `searchString` is not found at the start.
CANONICAL_EXAMPLES:
```deluge
Product = "Zoho CRM";
var = startsWith(Product, "Zoho"); // true (exact case match)
var = startsWith(Product, "zoho"); // false (case mismatch)
var = startsWithIgnoreCase(Product, "zoho"); // true (case-insensitive)
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- endsWith — endsWith-endsWithIgnoreCase.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/startswith.html, https://www.zoho.com/deluge/help/functions/string/startswithignorecase.html
assets/functions/deluge/built-in-functions/text/subString-subText.dou.md
FILE_PURPOSE: Deluge `subString()` / `subText()` — extract a substring between two 0-based character indices.
MENTAL_MODEL: `str.subString(start, end)` = TEXT[start..end); 0-based; end is exclusive; `subText()` is a direct alias.
WHEN_TO_USE:
- Extract a substring at an arbitrary position within a text string using start and end indices.
- Extract from a start index to the end of the string by omitting end_index.
WHEN_NOT_TO_USE:
- When extracting from the left or right by character count — use `left()` or `right()` instead.
- When extracting based on a delimiter — use `getPrefix()` or `getSuffix()` instead.
TRIGGER_KEYWORDS:
subString(), subText(), start-index substring, end-index exclusive, zero-based substring, negative-end-index-ignored
RULES:
- Return type: TEXT.
- Input types: TEXT (source_text), NUMBER (start_index), NUMBER (end_index, optional).
- `subString()` and `subText()` are aliases; all four syntaxes are equivalent.
- Index is 0-based (first character = index 0).
- `end_index` is EXCLUSIVE — the character at end_index is NOT included in the result.
- If `end_index` is omitted or negative → returns from start_index to end of string.
- If `start_index` equals `end_index` → returns empty text (no error).
- If `start_index` or `end_index` exceeds the string length → runtime error.
- If `end_index` < `start_index` → runtime error.
CANONICAL_EXAMPLES:
```deluge
sourceText = "Welcome to Zoho Deluge";
newText = sourceText.subString(11, 22); // returns "Zoho Deluge" (chars 11–21)
Email_Data = "Email: jack@zylker.com";
Email = Email_Data.subString(7); // returns "jack@zylker.com" (index 7 to end)
product_name = "Zoho Deluge";
result = product_name.subText(0, 4); // returns "Zoho" (end_index 4 excluded)
result = product_name.subText(5, -12); // returns "Deluge" (negative end ignored)
result = product_name.subText(6, 4); // throws error (end < start)
```
FAILURE_MODES:
- `start_index` or `end_index` exceeding the string length throws a runtime error.
- `end_index` < `start_index` throws a runtime error.
- `start_index` == `end_index` returns empty text (not an error).
RELATED_DOCS:
- mid — mid.dou.md
- left — left.dou.md
- right — right.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/string/substring.html, https://www.zoho.com/deluge/help/functions/string/subtext.html
assets/functions/deluge/built-in-functions/text/text.dou.md
FILE_PURPOSE: Deluge `text()` — format NUMBER, DECIMAL, or DATE-TIME using a custom format pattern string.
MENTAL_MODEL: `expr.text(formatPattern)` = TEXT; format pattern uses `#`/`0` for numbers or datetime literals for dates; mandatory.
WHEN_TO_USE:
- Apply custom numeric formatting (leading zeros, thousands separators, currency symbols) to NUMBER or DECIMAL.
- Format a DATE-TIME value using pattern literals (y, M, d, H, h, E, D, etc.).
WHEN_NOT_TO_USE:
- When `formatText` param is omitted — throws a runtime error.
- On TEXT input — accepted input types are DATE-TIME, DECIMAL, and NUMBER only.
TRIGGER_KEYWORDS:
text(), .text() format, ##,###, $#,##0.00, DDD day-of-year, datetime literal format
RULES:
- Return type: TEXT.
- Input types: DATE-TIME, DECIMAL, NUMBER.
- Syntax: `<expr>.text(<formatText>)`.
Note: source syntax section shows `<expr>.toText(<formatText>)` but all source examples use `.text()`; `.text()` is treated as the authoritative form.
- `formatText` is mandatory; calling `.text()` with no arguments throws a runtime error.
- Numeric format characters: `#` = digit or nothing; `0` = digit including leading/trailing zeros; `,` = grouping separator; `.` = decimal separator.
- Datetime format letters: `y`/`Y` (year), `M` (month), `d` (day of month), `D` (day of year), `E` (day name), `H`/`h` (hour), `m` (minute), `s` (second), `S` (millisecond), `a` (am/pm), `z`/`Z`/`X` (timezone).
- Text within single quotes in `formatText` is treated as a literal string (e.g., `'at'` outputs "at").
CANONICAL_EXAMPLES:
```deluge
leadingZero = 2434.text("000000000"); // "000002434"
separateValue = "123123456".text("##,###"); // "123,123,456"
dayOfYear = "02/04/2020".text("DDD"); // "035"
dayName = "02/04/2020".text("EEEE"); // "Tuesday"
currencyFormat = "12345654.1234".text("$#,##0.00"); // "$12,345,654.12"
dateFormat = zoho.currenttime.text("MM/dd/YY"); // e.g. "02/04/20"
noParam = zoho.currenttime.text(); // Runtime error: argument count mismatch
```
FAILURE_MODES:
- Calling `.text()` with no arguments throws: "No. of arguments mismatches for the function text".
RELATED_DOCS:
- toText — ../conversion/../conversion/toString-toText.dou.md
- toString — ../conversion/../conversion/toString-toText.dou.md
- Number functions — ../../datatypes/number-decimal.dou.md
- Date-time functions — ../../datatypes/date-time.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/number/text.html
assets/functions/deluge/built-in-functions/text/textToHex.dou.md
FILE_PURPOSE: Deluge `textToHex()` — encode a text string to its hexadecimal representation.
MENTAL_MODEL: `str.textToHex()` = hex-encoded TEXT; inverse of `hexToText()`.
WHEN_TO_USE:
- Encode a text string to its hexadecimal representation for transmission or storage.
WHEN_NOT_TO_USE:
- When decoding hex back to text — use `hexToText()` instead.
TRIGGER_KEYWORDS:
textToHex(), text-to-hex encoding, hexToText inverse, hex-string encode, hex-literal output
RULES:
- Return type: TEXT (hexadecimal string).
- Input type: TEXT.
- Both `<text>.textToHex()` and `textToHex(<text>)` syntaxes are valid.
CANONICAL_EXAMPLES:
```deluge
response = textToHex("Zoho CRM"); // Returns "5a6f686f2043524d"
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- hexToText — hexToText.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
SOURCE:
https://www.zoho.com/deluge/help/functions/string/texttohex.html
assets/functions/deluge/built-in-functions/text/trim.dou.md
## FILE_PURPOSE
Deluge `trim()` — remove all leading and trailing whitespace from a text string.
## MENTAL_MODEL
`str.trim()` = TEXT with all leading and trailing spaces removed; internal spaces are unaffected.
## WHEN_TO_USE
- Remove both leading and trailing whitespace from a text string in a single operation.
## WHEN_NOT_TO_USE
- When only leading whitespace needs removal — use `ltrim()` instead.
- When only trailing whitespace needs removal — use `rtrim()` instead.
## TRIGGER_KEYWORDS
trim(), leading-trailing whitespace removal, full-trim, trim vs ltrim, trim vs rtrim
## RULES
- Return type: TEXT.
- Input type: TEXT.
- Removes ALL leading AND trailing spaces.
- Internal (mid-string) spaces are not affected.
- Both `.trim()` and `trim(<string>)` syntaxes are valid.
## CANONICAL_EXAMPLES
```deluge
mainString = " Welcome to Zoho CRM ";
newText = trim(mainString); // returns "Welcome to Zoho CRM"
```
## FAILURE_MODES
- CRM merge variables (e.g. `${Accounts.Id}`) can deliver whitespace-only strings; `length() == 0` or `isEmpty()` checks pass these through as non-empty. Always `trim()` merge variable inputs before an empty check: `if (value == null || value.trim().length() == 0)`.
## RELATED_DOCS
- ltrim — ltrim.dou.md
- rtrim — rtrim.dou.md
- Text functions — https://www.zoho.com/deluge/help/functions/text.html
## SOURCE
https://www.zoho.com/deluge/help/functions/string/trim.html
assets/functions/deluge/datatypes/collection.dou.md
FILE_PURPOSE: Deluge Collection datatype — ordered index-value or unordered key-value storage
MENTAL_MODEL: Collection = ordered list OR named map; two mutually exclusive modes addressed by integer index or string key
WHEN_TO_USE:
- Store a sequence addressable by position (index-value mode).
- Store named pairs addressable by unique key (key-value mode).
- Nest Collections inside Collections for hierarchical data structures.
WHEN_NOT_TO_USE:
- Only ordered integer-indexed access needed — use List (broader built-in method support).
- Only named-pair access with map-specific methods — use Key-Value.
TRIGGER_KEYWORDS:
Collection(), collection.insert(), collection.get(), index-value collection, key-value collection, for each, 50000 elements
RULES:
- Two mutually exclusive modes: index-value (ordered, 0-based integer index) OR key-value (unordered, unique string key).
- A single Collection variable cannot mix modes.
- Collection size limit: 50,000 elements.
- Keys in key-value mode must be unique; duplicate key silently overwrites existing value.
- First element of index-value Collection has index 0; last is n-1.
- `for each` iterates every element; no documented break-out mechanism in source.
CANONICAL_EXAMPLES:
```deluge
// Index-value Collection
gadgets = Collection("Mobile phone", "Laptop");
value = gadgets.get(0); // "Mobile phone"
gadgets.insert("Camera"); // appends
// Key-value Collection
details = Collection("Name":"Shawn", "Gender":"Male");
details.insert("Company":"Zoho");
name = details.get("Name"); // "Shawn"
// Iterate
sum = 0;
nums = Collection(1, 2, 3);
for each v in nums { sum = sum + v; }
info sum; // 6
```
FAILURE_MODES:
- Accessing index >= Collection size or < 0 → error; validate bounds before indexed access.
- `insert()` is Collection-specific; using `put()` (Key-Value) or `add()` (List) on a Collection → type error.
- Duplicate key in key-value Collection silently overwrites; no error raised.
- Exceeding 50,000 elements → error.
- Mixing index-value and key-value `insert()` syntax on the same Collection → undefined behavior.
RELATED_DOCS:
- Collection built-in functions — https://www.zoho.com/deluge/help/datatypes/collection.html
SOURCE:
https://www.zoho.com/deluge/help/datatypes/collection.html
assets/functions/deluge/datatypes/date-time.dou.md
FILE_PURPOSE: Deluge Date-Time datatype — single-quoted date and time values with 40+ format support
MENTAL_MODEL: Date-Time = single-quoted temporal literal; missing time defaults to 00:00:00; 40+ formats supported
WHEN_TO_USE:
- Store, compare, or perform arithmetic on date/time values in Deluge Functions.
- Read or write CRM date/date-time fields via API responses.
WHEN_NOT_TO_USE:
- String representation of a date for display only — use Text datatype.
TRIGGER_KEYWORDS:
date-time datatype, single-quote date, dd-MMM-yyyy, yyyy-MM-dd, MM-dd-yyyy, date default 00:00:00, date format mismatch
RULES:
- Date-time literals must be enclosed in single quotes (not double quotes).
- A date without a time component defaults to `00:00:00` time.
- 40+ date formats are supported; if specified format cannot be auto-converted, an error is thrown.
- Key supported formats: `dd-MMM-yyyy`, `yyyy-MM-dd`, `MM-dd-yyyy`, `dd/MM/yyyy`, `MM/dd/yyyy`, `dd MMM yyyy`.
- Format mismatch: Deluge attempts to convert to the closest supported format; if conversion fails, error is thrown.
CANONICAL_EXAMPLES:
```deluge
date1 = '15-Aug-1947'; // time defaults to 00:00:00
appt = '15-Aug-1947 19:00:00'; // explicit time
iso = '1947-08-15'; // yyyy-MM-dd format
```
FAILURE_MODES:
- Date-time literal in double quotes (`"15-Aug-1947"`) → interpreted as Text; date built-in functions fail.
- Format not matching any supported format and not auto-convertible → error.
- Date without time silently defaults to `00:00:00`; time-aware comparisons on date-only values produce unexpected results.
RELATED_DOCS:
- Date-Time built-in functions — https://www.zoho.com/deluge/help/datatypes/date-time.html
SOURCE:
https://www.zoho.com/deluge/help/datatypes/date-time.html
assets/functions/deluge/datatypes/file.dou.md
FILE_PURPOSE: Deluge FILE datatype — files fetched from web or cloud services
MENTAL_MODEL: FILE = remote-fetched file object; uniform across MIME types; produced by invokeUrl/getUrl/integration tasks
WHEN_TO_USE:
- Pass a downloaded file to CRM `attachFile`, FTP/SFTP tasks, or `send mail` attachments.
- Apply file built-in methods to a downloaded file.
WHEN_NOT_TO_USE:
- Local (offline) filesystem files — FILE datatype only represents remotely-fetched files.
TRIGGER_KEYWORDS:
FILE datatype, invokeUrl, getUrl, file-methods, zoho.sign.downloadDocument, zoho.writer.mergeAndDownload
RULES:
- FILE datatype is produced by: `invokeUrl` task, `getUrl` task, or integration tasks returning a file (e.g. `zoho.writer.mergeAndDownload`, `zoho.sign.downloadDocument`).
- FILE type is uniform regardless of MIME type (.pdf, .jpeg, .mp4, etc.); datatype behavior is identical for all formats.
CANONICAL_EXAMPLES:
```deluge
file_variable = invokeUrl[
url: "https://sample-videos.com/img/Sample-png-image-100kb.png"
type: GET
];
file_variable = zoho.sign.downloadDocument(2131000000002069);
```
FAILURE_MODES:
- `invokeUrl` against a local path fails; source must be a reachable URL.
- Non-FILE references have no applicable file methods; pass them only to integration tasks or `send mail`.
RELATED_DOCS:
- File built-in functions — https://www.zoho.com/deluge/help/datatypes/file-data-type.html
- invokeUrl task — ../integrations/../integrations/invokeurl-task.dou.md
- getUrl task — ../integrations/../integrations/invokeurl-task.dou.md
- attach file CRM task — ../integrations/zoho-crm/attach-file.dou.md
SOURCE:
https://www.zoho.com/deluge/help/datatypes/file-data-type.html
assets/functions/deluge/datatypes/key-value.dou.md
FILE_PURPOSE: Deluge Key-Value datatype — dictionary map of unique-keyed values
MENTAL_MODEL: Key-Value = typed dictionary; keyed by any Deluge type; duplicate key silently overwrites existing value
WHEN_TO_USE:
- Store and retrieve values by unique named key.
- Merge two maps with putAll.
- Check key/value existence before conditional operations.
WHEN_NOT_TO_USE:
- Ordered integer-indexed access — use List.
- Ordered iteration with positional access — use Collection (index-value mode).
TRIGGER_KEYWORDS:
map.put, map.get, map.putAll, containKey, containValue, map.keys, map.size, key-value map
RULES:
- Keys must be unique; inserting a duplicate key silently overwrites the existing value with no error.
- Both keys and values can be of any Deluge data type.
- `get()` returns null when the key does not exist; use `containKey()` before relying on `get()`.
- `keys()` returns a List of all keys; `size()` returns count of key-value pairs.
CANONICAL_EXAMPLES:
```deluge
ZohoProduct = {"Product":"CRM", "Version":8};
ZohoProduct.put("Status", "Active"); // insert/overwrite
ver = ZohoProduct.get("Version"); // 5
ZohoProduct.putAll(other_map); // merge all pairs
has_key = ZohoProduct.containKey("Product"); // true
has_active = ZohoProduct.containValue("Active"); // true
all_keys = ZohoProduct.keys(); // List of keys
size = ZohoProduct.size(); // count of pairs
```
FAILURE_MODES:
- Duplicate key insert silently overwrites; validate with `containKey()` before conditional puts.
- `put()` (Key-Value) and `add()` (List) are not interchangeable; type error when misapplied.
- `put()` and Collection `insert()` are not interchangeable.
- `putAll()` silently overwrites existing keys on collision — no error.
- `get()` returns null for missing key — no error; always check `containKey()` first.
RELATED_DOCS:
- Key-Value built-in functions — https://www.zoho.com/deluge/help/datatypes/key-value.html
- get() — https://www.zoho.com/deluge/help/datatypes/key-value.html
SOURCE:
https://www.zoho.com/deluge/help/datatypes/key-value.html, https://www.zoho.com/deluge/help/functions/map/get.html
assets/functions/deluge/datatypes/list.dou.md
FILE_PURPOSE: Deluge List datatype — ordered zero-indexed heterogeneous element sequence
MENTAL_MODEL: List = zero-indexed ordered array; mixed types allowed; grows via add/addAll; addAll capped at 25,000
WHEN_TO_USE:
- Store ordered sequences addressable by integer index.
- Perform set operations: intersect two lists, sort, remove by index.
- Iterate ordered results from CRM API responses.
WHEN_NOT_TO_USE:
- Named (keyed) access — use Key-Value or Collection (key-value mode).
- Batches > 25,000 elements added in one addAll call — split into multiple adds.
TRIGGER_KEYWORDS:
list.add, list.addAll, list.contains, list.indexOf, list.sublist, list.intersect, list.sort, addAll 25000
RULES:
- First element index is 0; last is n-1.
- Elements of different Deluge data types are permitted in the same List.
- `addAll` hard cap: 25,000 elements per call.
- `add` built-in: no per-call limit (adds one element at a time).
- Direct declaration (curly-brace literal): no element limit.
CANONICAL_EXAMPLES:
```deluge
ZohoProducts = {"Leads", "Contacts", "Deals"};
found = ZohoProducts.contains("Contacts"); // true
element = ZohoProducts.get(0); // "Leads"
pos = ZohoProducts.indexOf("Deals"); // 2
sub = ZohoProducts.sublist(1, 3); // ["Contacts","Deals"]
common = listA.intersect(listB); // shared elements
sorted = ZohoProducts.sort();
ZohoProducts.remove(1); // removes at index 1
```
FAILURE_MODES:
- `addAll` exceeds 25,000 elements per call — error; batch large datasets.
- Accessing index outside bounds (negative or >= List size) throws an error.
- Applying Key-Value `put()` or Collection `insert()` on a List variable — type error.
RELATED_DOCS:
- List built-in functions — https://www.zoho.com/deluge/help/datatypes/list.html
- add / addAll — ../built-in-functions/list/list-add-addall.dou.md
SOURCE:
https://www.zoho.com/deluge/help/datatypes/list.html
assets/functions/deluge/datatypes/number-decimal.dou.md
FILE_PURPOSE: Deluge Number and Decimal datatypes — integers and floating-point values
MENTAL_MODEL: Number = integer only; Decimal = fractional; mixed Number+Decimal arithmetic always yields Decimal
WHEN_TO_USE:
- Number: whole-number counters, IDs, index arithmetic, integer comparisons.
- Decimal: currency, percentages, ratio calculations requiring fractional precision.
WHEN_NOT_TO_USE:
- Number for fractional values — assign decimals to Decimal variables.
- Decimal where integer-only type consistency is required downstream.
TRIGGER_KEYWORDS:
Number datatype, Decimal datatype, planPrice, 9223372036854775808, number-to-decimal promotion, mixed arithmetic result, number-decimal shared functions
RULES:
- Number range: −9,223,372,036,854,775,808 to 9,223,372,036,854,775,808; integers only.
- Mixed arithmetic (Number + Decimal) always yields Decimal result.
- Integer division producing a fractional result yields Decimal datatype.
- Number and Decimal share the same built-in functions reference page.
CANONICAL_EXAMPLES:
```deluge
Version = 5; // Number
planPrice = 0.99; // Decimal
discount = 0.15;
count = 5; rate = 1.25;
total = count * rate; // Decimal (6.25)
d = 10 / 3; // Decimal (3.333...)
price = 49.99;
final_price = price - (price * discount); // Decimal
```
FAILURE_MODES:
- Value outside Number range — overflow error.
- Assigning a decimal literal to a Number variable silently promotes it to Decimal; downstream Number checks fail.
- Mixed Number + Decimal arithmetic always returns Decimal; type checks expecting Number after mixed arithmetic fail.
- Decimal precision limits not documented; behavior for very large fractional values is undefined per source.
RELATED_DOCS:
- Number and Decimal built-in functions — https://www.zoho.com/deluge/help/datatypes/number.html
SOURCE:
https://www.zoho.com/deluge/help/datatypes/number.html, https://www.zoho.com/deluge/help/datatypes/decimal.html
assets/functions/deluge/datatypes/text-boolean.dou.md
FILE_PURPOSE: Deluge Text and Boolean datatypes — string literals and true/false state
MENTAL_MODEL: Text = double-quoted character sequence; Boolean = unquoted true/false; no implicit coercion between them
WHEN_TO_USE:
- Text: store any character sequence — words, sentences, numeric-looking strings, special characters.
- Boolean: represent binary state in conditionals; returned by relational and contains-type functions.
WHEN_NOT_TO_USE:
- Text for arithmetic — store numeric values as Number or Decimal.
- Text string "true"/"false" as substitute for Boolean true/false.
TRIGGER_KEYWORDS:
text datatype, boolean true, boolean false, double-quote delimiter, backslash escape, case-insensitive boolean, no-quote boolean
RULES:
- Text values must be enclosed in double quotes (`"value"`); single quotes produce Date-Time, not Text.
- Backslash `\` is the escape character; use `\"` for a literal double quote inside Text.
- Numeric characters stored in Text are Text datatype — no implicit coercion to Number.
- Boolean values must NOT be enclosed in quotes (single or double).
- Boolean is case-insensitive: `true`, `TRUE`, `True`, `tRuE` are all equivalent.
CANONICAL_EXAMPLES:
```deluge
Name = "John";
Address = "Zoho Corp, Pleasanton, CA";
escaped = "He said \"hello\"";
Num = "1234567890"; // Text, NOT Number
job_experience = false;
Salary_negotiable = true;
flag = TRUE; // same as true
wrong = "true"; // Text "true", NOT Boolean true
```
FAILURE_MODES:
- Text not in double quotes — interpreted as variable name or identifier, not string literal.
- Text in single quotes (e.g. `'John'`) — interpreted as Date-Time; applying Text built-ins fails.
- Unescaped `"` inside a Text literal — parse error; use `\"`.
- Numeric-looking Text not auto-coerced to Number; explicit conversion needed for arithmetic.
- `"true"` (Text) does not equal Boolean `true`; `if (flag == true)` where `flag = "true"` evaluates false.
- `'true'` (single-quoted) — Text or Date-Time, never Boolean.
RELATED_DOCS:
- Text built-in functions — ../built-in-functions/text/text.dou.md
- Functions returning Boolean — https://www.zoho.com/deluge/help/functions-returning-boolean.html
SOURCE:
https://www.zoho.com/deluge/help/datatypes/text.html, https://www.zoho.com/deluge/help/datatypes/boolean.html
assets/functions/deluge/deluge-index.md
# Deluge DOU Node Index
Complete DOU node index for Deluge — all built-in functions, data types, operators, variables, tasks, file functions, and integrations.
Maintenance: add every new DOU node here first; also add to [functions.md#dou-reference-nodes](../../../references/functions.md#dou-reference-nodes) only if it belongs to a high-frequency section (categories, patterns, integrations, File Functions, built-in Conversion/Date-Time/List/Map/Number(utility)/Text).
### Function Category Templates
| Task | Read This File |
|---|---|
| All 7 category signatures + return types | [#canonical-examples](../functions-categories.dou.md#canonical-examples) |
| Button | [#button](../functions-categories.dou.md#button) |
| Automation | [#automation](../functions-categories.dou.md#automation) |
| Schedule | [#schedule](../functions-categories.dou.md#schedule) |
| Related List | [#related-list](../functions-categories.dou.md#related-list) |
| Standalone (toString, invokeFunction) | [#standalone](../functions-categories.dou.md#standalone) |
| Sales Signals | [#sales-signals](../functions-categories.dou.md#sales-signals) |
| Validation Rule (crmAPIRequest + Map return) | [#validation-rule](../functions-categories.dou.md#validation-rule) |
### Common Patterns
| Task | Read This File |
|---|---|
| Read-then-write | [#pattern-1-read-then-write](../functions-patterns.dou.md#pattern-1-read-then-write) |
| Call external API and store result | [#pattern-2-external-api-and-store-result](../functions-patterns.dou.md#pattern-2-external-api-and-store-result) |
| Guard clause / early exit | [#pattern-3-conditional-guard-clause](../functions-patterns.dou.md#pattern-3-conditional-guard-clause) |
| Loop over search results and update | [#pattern-4-loop-over-search-results-and-update](../functions-patterns.dou.md#pattern-4-loop-over-search-results-and-update) |
| Bulk create with explicit trigger | [#pattern-5-bulk-create-with-trigger-automation](../functions-patterns.dou.md#pattern-5-bulk-create-with-trigger-automation) |
| Two-step cross-module retrieval (Who_Id) | [#pattern-6-two-step-cross-module-retrieval](../functions-patterns.dou.md#pattern-6-two-step-cross-module-retrieval) |
Distilled Operational Understanding nodes for the Deluge scripting language, grouped by category.
### Data Types
| Task | Read This File |
|---|---|
| Work with Collection data type | [collection.dou.md](./datatypes/collection.dou.md) |
| Work with Date-Time data type | [date-time.dou.md](./datatypes/date-time.dou.md) |
| Work with File data type | [file.dou.md](./datatypes/file.dou.md) |
| Work with Key-Value (Map) data type | [key-value.dou.md](./datatypes/key-value.dou.md) |
| Work with List data type | [list.dou.md](./datatypes/list.dou.md) |
| Work with Number / Decimal data types | [number-decimal.dou.md](./datatypes/number-decimal.dou.md) |
| Work with Text / Boolean data types | [text-boolean.dou.md](./datatypes/text-boolean.dou.md) |
### Operators
| Task | Read This File |
|---|---|
| Use arithmetic and assignment operators | [arithmetic-assignment-operators.dou.md](./operators/arithmetic-assignment-operators.dou.md) |
| Use logical and relational operators | [logical-relational-operators.dou.md](./operators/logical-relational-operators.dou.md) |
### Variables
| Task | Read This File |
|---|---|
| Declare and use Deluge variables | [deluge-variables.dou.md](./variables/deluge-variables.dou.md) |
### Deluge Tasks — Conditions
| Task | Read This File |
|---|---|
| Write conditional statements (if / else if / else) | [conditional-statements.dou.md](./deluge-tasks/conditions/conditional-statements.dou.md) |
| Write criteria-based conditional statements | [criteria-conditional-statements.dou.md](./deluge-tasks/conditions/criteria-conditional-statements.dou.md) |
### Deluge Tasks — Misc
| Task | Read This File |
|---|---|
| Break out of a loop | [break.dou.md](./deluge-tasks/misc/break.dou.md) |
| Call a function from within Deluge | [call-function.dou.md](./deluge-tasks/misc/call-function.dou.md) |
| Continue to the next loop iteration | [continue.dou.md](./deluge-tasks/misc/continue.dou.md) |
| Return a value from a function | [return.dou.md](./deluge-tasks/misc/return.dou.md) |
| Send an email from Deluge | [send-mail.dou.md](./deluge-tasks/misc/send-mail.dou.md) |
| Set a variable | [set-variable.dou.md](./deluge-tasks/misc/set-variable.dou.md) |
| Handle exceptions with try-catch | [try-catch.dou.md](./deluge-tasks/misc/try-catch.dou.md) |
### File Functions
| Task | Read This File |
|---|---|
| Compress files | [compress.dou.md](./file-functions/compress.dou.md) |
| Convert a file to PDF | [convert-to-pdf.dou.md](./file-functions/convert-to-pdf.dou.md) |
| Extract a compressed file | [extract.dou.md](./file-functions/extract.dou.md) |
| Get file content | [get-file-content.dou.md](./file-functions/get-file-content.dou.md) |
| Get file name | [get-file-name.dou.md](./file-functions/get-file-name.dou.md) |
| Get file size | [get-file-size.dou.md](./file-functions/get-file-size.dou.md) |
| Get file type | [get-file-type.dou.md](./file-functions/get-file-type.dou.md) |
| Check if a value is a file | [is-file.dou.md](./file-functions/is-file.dou.md) |
| Set charset on a file | [set-charset.dou.md](./file-functions/set-charset.dou.md) |
| Set file name | [set-file-name.dou.md](./file-functions/set-file-name.dou.md) |
| Set file password | [set-file-password.dou.md](./file-functions/set-file-password.dou.md) |
| Set file type | [set-file-type.dou.md](./file-functions/set-file-type.dou.md) |
| Convert a value to a file | [to-file.dou.md](./file-functions/to-file.dou.md) |
### Integrations
| Task | Read This File |
|---|---|
| Use a named Connection in invokeUrl | [connections.dou.md](./integrations/connections.dou.md) |
| Transfer files via FTP / SFTP | [ftp-sftp-tasks.dou.md](./integrations/ftp-sftp-tasks.dou.md) |
| Call an API using the invokeApi task | [invokeapi-task.dou.md](./integrations/invokeapi-task.dou.md) |
| Call an external URL using the invokeUrl task | [invokeurl-task.dou.md](./integrations/invokeurl-task.dou.md) |
### Integrations — Zoho CRM Tasks
| Task | Read This File |
|---|---|
| Attach a file to a CRM record | [attach-file.dou.md](./integrations/zoho-crm/attach-file.dou.md) |
| Bulk create CRM records | [bulk-create-records.dou.md](./integrations/zoho-crm/bulk-create-records.dou.md) |
| Bulk update CRM records | [bulk-update-records.dou.md](./integrations/zoho-crm/bulk-update-records.dou.md) |
| Convert a Lead to a Contact | [convert-lead.dou.md](./integrations/zoho-crm/convert-lead.dou.md) |
| Create a CRM record | [create-record.dou.md](./integrations/zoho-crm/create-record.dou.md) |
| Get CRM field metadata | [get-fields.dou.md](./integrations/zoho-crm/get-fields.dou.md) |
| Get a CRM record by ID | [get-record-by-id.dou.md](./integrations/zoho-crm/get-record-by-id.dou.md) |
| Get CRM records (list) | [get-records.dou.md](./integrations/zoho-crm/get-records.dou.md) |
| Get related records from a CRM module | [get-related-records.dou.md](./integrations/zoho-crm/get-related-records.dou.md) |
| Search CRM records | [search-records.dou.md](./integrations/zoho-crm/search-records.dou.md) |
| Update a CRM record | [update-record.dou.md](./integrations/zoho-crm/update-record.dou.md) |
| Update a related record | [update-related-record.dou.md](./integrations/zoho-crm/update-related-record.dou.md) |
| Upsert a CRM record | [upsert-record.dou.md](./integrations/zoho-crm/upsert-record.dou.md) |
### Function Lifecycle and Best Practices
| Task | Read This File |
|---|---|
| Understanding CRM Functions architecture — categories, trigger types, language choices, execution lifecycle, and return type semantics per category | [functions-overview.dou.md](../functions-overview.dou.md) |
| Designing production-quality CRM Functions — idempotency, credential hygiene, performance, logging discipline, and naming conventions | [functions-best-practices.dou.md](../functions-best-practices.dou.md) |
| Building multi-function pipelines, migrating Workflow Rules to Functions, or implementing bulk-record pagination and chunking | [functions-strategies.dou.md](../functions-strategies.dou.md) |
### Deluge Errors
| Task | Read This File |
|---|---|
| Deluge error message reference — save errors, runtime errors, and built-in function type errors | [errors.dou.md](./errors.dou.md) |
### Built-in Functions — Conversion
| Task | Read This File |
|---|---|
| Parse TEXT, NUMBER, or DATE-TIME expression into a DATE-TIME value | [toDate.dou.md](./built-in-functions/conversion/toDate.dou.md) |
| Convert Unix epoch milliseconds to formatted TEXT with timezone | [toDateTimeString.dou.md](./built-in-functions/conversion/toDateTimeString.dou.md) |
| Convert a NUMBER, DECIMAL, or numeric TEXT to a DECIMAL value | [toDecimal.dou.md](./built-in-functions/conversion/toDecimal.dou.md) |
| Convert a NUMBER to its hexadecimal string representation | [toHex.dou.md](./built-in-functions/conversion/toHex.dou.md) |
| Convert a JSON-formatted TEXT array into a Deluge LIST | [toJSONList.dou.md](./built-in-functions/conversion/toJSONList.dou.md) |
| Split a TEXT string by a separator into a LIST | [toList.dou.md](./built-in-functions/conversion/toList.dou.md) |
| Split a comma-separated TEXT or LIST into a LIST of TEXT elements | [toListString.dou.md](./built-in-functions/conversion/toListString.dou.md) |
| Deluge `toLowerCase()` / `toUpperCase()` — convert all string letters to lower or upper case | [toLowerCase-toUpperCase.dou.md](./built-in-functions/conversion/toLowerCase-toUpperCase.dou.md) |
| Convert a JSON-formatted TEXT string into a KEY-VALUE map | [toMap.dou.md](./built-in-functions/conversion/toMap.dou.md) |
| Deluge `toNumber()` / `toLong()` — convert any expression to an integer NUMBER, truncating decimals | [toNumber-toLong.dou.md](./built-in-functions/conversion/toNumber-toLong.dou.md) |
| Deluge `toString()` / `toText()` — convert any expression to TEXT with optional datetime formatting | [toString-toText.dou.md](./built-in-functions/conversion/toString-toText.dou.md) |
| Parse a datetime expression into DATE-TIME with explicit timezone conversion | [toTime.dou.md](./built-in-functions/conversion/toTime.dou.md) |
| Convert NUMBER or DECIMAL to words in a target language | [toWords.dou.md](./built-in-functions/conversion/toWords.dou.md) |
### Built-in Functions — Date-Time
| Task | Read This File |
|---|---|
| Add (or subtract) seconds from a DATE-TIME value | [add-seconds.dou.md](./built-in-functions/date-time/add-seconds.dou.md) |
| Deluge addBusinessDay() and subBusinessDay() — advance or retreat a date by business days | [add-sub-business-day.dou.md](./built-in-functions/date-time/add-sub-business-day.dou.md) |
| Deluge addDay() and subDay() — add or subtract calendar days from a date-time value | [add-sub-day.dou.md](./built-in-functions/date-time/add-sub-day.dou.md) |
| Deluge addHour() and subHour() — add or subtract hours from a DATE-TIME value | [add-sub-hour.dou.md](./built-in-functions/date-time/add-sub-hour.dou.md) |
| Deluge addMinutes() and subMinutes() — add or subtract minutes from a DATE-TIME value | [add-sub-minutes.dou.md](./built-in-functions/date-time/add-sub-minutes.dou.md) |
| Deluge addMonth() and subMonth() — add or subtract calendar months from a date-time value | [add-sub-month.dou.md](./built-in-functions/date-time/add-sub-month.dou.md) |
| Deluge daysBetween() and hoursBetween() — count calendar days or whole hours between two date-times | [days-hours-between.dou.md](./built-in-functions/date-time/days-hours-between.dou.md) |
| Shift a date by N months and return that month's last day | [eomonth.dou.md](./built-in-functions/date-time/eomonth.dou.md) |
| Deluge monthsBetween() and yearsBetween() — count months (30-day) or years (365-day) between dates | [months-years-between.dou.md](./built-in-functions/date-time/months-years-between.dou.md) |
| Deluge now and today — return the current date-time or current date-only value | [now-today.dou.md](./built-in-functions/date-time/now-today.dou.md) |
| Deluge toStartOfMonth() and toStartOfWeek() — get first day of a date's month or week | [to-start-of-month-week.dou.md](./built-in-functions/date-time/to-start-of-month-week.dou.md) |
| Deluge totalMonths() and totalYears() — count calendar months or years respecting actual day counts | [total-months-years.dou.md](./built-in-functions/date-time/total-months-years.dou.md) |
### Built-in Functions — List
| Task | Read This File |
|---|---|
| Deluge list add() and addAll() — append element(s) to a list in place | [list-add-addall.dou.md](./built-in-functions/list/list-add-addall.dou.md) |
| Deluge `average()` / `median()` — compute mean and median of a NUMBER or DECIMAL list | [list-average-median.dou.md](./built-in-functions/list/list-average-median.dou.md) |
| Deluge list contains() — exact membership test for a list element | [list-contains.dou.md](./built-in-functions/list/list-contains.dou.md) |
| Deluge list get() size() clear() — index access, element count, and list emptying | [list-get-size-clear.dou.md](./built-in-functions/list/list-get-size-clear.dou.md) |
| Deluge `indexOf()` / `lastIndexOf()` — find first or last index of element in a LIST | [list-indexOf-lastIndexOf.dou.md](./built-in-functions/list/list-indexOf-lastIndexOf.dou.md) |
| Deluge list intersect() and subList() — get common elements or a slice of a list | [list-intersect-sublist.dou.md](./built-in-functions/list/list-intersect-sublist.dou.md) |
| Deluge `largest()` / `smallest()` — max and min DECIMAL from a numeric list | [list-largest-smallest.dou.md](./built-in-functions/list/list-largest-smallest.dou.md) |
| Deluge `notContains()` / `isEmpty()` — boolean membership absence and empty checks on TEXT/LIST/MAP | [list-notContains-isEmpty.dou.md](./built-in-functions/list/list-notContains-isEmpty.dou.md) |
| Deluge `nthLargest()` / `nthSmallest()` — retrieve the Nth ranked value from a numeric COLLECTION | [list-nthLargest-nthSmallest.dou.md](./built-in-functions/list/list-nthLargest-nthSmallest.dou.md) |
| Deluge `remove()` / `removeAll()` / `removeElement()` — three ways to remove from a LIST | [list-remove-removeAll-removeElement.dou.md](./built-in-functions/list/list-remove-removeAll-removeElement.dou.md) |
| Deluge list sort() and distinct() — sort elements or return unique values from a list | [list-sort-distinct.dou.md](./built-in-functions/list/list-sort-distinct.dou.md) |
### Built-in Functions — Map
| Task | Read This File |
|---|---|
| Deluge map containKey() containValue() notContains() — membership test for keys and values | [map-containkey-containvalue-notcontains.dou.md](./built-in-functions/map/map-containkey-containvalue-notcontains.dou.md) |
| Deluge map get() keys() size() isEmpty() — read access and state inspection | [map-get-keys-size-isempty.dou.md](./built-in-functions/map/map-get-keys-size-isempty.dou.md) |
| Deluge map put() and putAll() — insert or merge key-value pairs in place | [map-put-putall.dou.md](./built-in-functions/map/map-put-putall.dou.md) |
| Deluge map remove() and clear() — delete a key-value pair or empty the entire map | [map-remove-clear.dou.md](./built-in-functions/map/map-remove-clear.dou.md) |
### Built-in Functions — Number
| Task | Read This File |
|---|---|
| Deluge `abs()` / `frac()` — extract the magnitude or fractional part of a number | [abs-frac.dou.md](./built-in-functions/number/abs-frac.dou.md) |
| Deluge `addWeek()` / `subWeek()` — shift a DATE-TIME forward or backward by weeks | [addWeek-subWeek.dou.md](./built-in-functions/number/addWeek-subWeek.dou.md) |
| Deluge `addYear()` / `subYear()` — shift a DATE-TIME forward or backward by years | [addYear-subYear.dou.md](./built-in-functions/number/addYear-subYear.dou.md) |
| Angle from positive x-axis to point (x,y) in radians | [atan2.dou.md](./built-in-functions/number/atan2.dou.md) |
| Deluge `ceil()` / `floor()` — round a DECIMAL to the nearest integer above or below | [ceil-floor.dou.md](./built-in-functions/number/ceil-floor.dou.md) |
| Deluge `day()` / `month()` — extract day-of-month or month NUMBER from DATE-TIME | [day-month.dou.md](./built-in-functions/number/day-month.dou.md) |
| Count days between two DATE-TIME values; equivalent to `daysBetween()` | [days360.dou.md](./built-in-functions/number/days360.dou.md) |
| Add N months to a DATE-TIME; negative N subtracts months | [edate.dou.md](./built-in-functions/number/edate.dou.md) |
| Compute e raised to the power of a given number | [exp.dou.md](./built-in-functions/number/exp.dou.md) |
| Get day-of-year NUMBER (1–366) from a DATE-TIME value | [getDayOfYear.dou.md](./built-in-functions/number/getDayOfYear.dou.md) |
| Get week-of-year NUMBER (1–52) with optional start-day override | [getWeekOfYear.dou.md](./built-in-functions/number/getWeekOfYear.dou.md) |
| Extract year as NUMBER from a DATE-TIME value; alias `year()` | [getYear.dou.md](./built-in-functions/number/getYear.dou.md) |
| Deluge `hour()` / `minute()` / `second()` — get hour, minute, second from a DATE-TIME value | [hour-minute-second.dou.md](./built-in-functions/number/hour-minute-second.dou.md) |
| Deluge cosh() and acosh() — hyperbolic cosine and inverse hyperbolic cosine | [hyperbolic-cosh-acosh.dou.md](./built-in-functions/number/hyperbolic-cosh-acosh.dou.md) |
| Deluge sinh() and asinh() — hyperbolic sine and inverse hyperbolic sine | [hyperbolic-sinh-asinh.dou.md](./built-in-functions/number/hyperbolic-sinh-asinh.dou.md) |
| Deluge tanh() and atanh() — hyperbolic tangent and inverse hyperbolic tangent | [hyperbolic-tanh-atanh.dou.md](./built-in-functions/number/hyperbolic-tanh-atanh.dou.md) |
| Return BOOLEAN true when expression is a valid DATE-TIME value | [isDate.dou.md](./built-in-functions/number/isDate.dou.md) |
| Check whether an expression evaluates to a valid numeric value | [isnumber.dou.md](./built-in-functions/number/isnumber.dou.md) |
| Deluge `log()` / `log10()` — compute the natural or base-10 logarithm of a number | [log-log10.dou.md](./built-in-functions/number/log-log10.dou.md) |
| Deluge `nextWeekDay()` / `previousWeekDay()` — seek next or previous date by weekday name | [nextWeekDay-previousWeekDay.dou.md](./built-in-functions/number/nextWeekDay-previousWeekDay.dou.md) |
| Deluge `power()` / `sqrt()` — exponentiate a number or compute its square root | [power-sqrt.dou.md](./built-in-functions/number/power-sqrt.dou.md) |
| Generate a random number within an inclusive-start exclusive-end range | [randomnumber.dou.md](./built-in-functions/number/randomnumber.dou.md) |
| Round a number to a specified decimal or integer precision | [round.dou.md](./built-in-functions/number/round.dou.md) |
| Deluge max() and min() — compare two numbers and return the larger or smaller | [stat-max-min.dou.md](./built-in-functions/number/stat-max-min.dou.md) |
| Subtract seconds from a DATE-TIME value; negative value adds seconds | [subSeconds.dou.md](./built-in-functions/number/subSeconds.dou.md) |
| Deluge cos() and acos() — trigonometric cosine and arccosine in radians | [trig-cos-acos.dou.md](./built-in-functions/number/trig-cos-acos.dou.md) |
| Deluge sin() and asin() — trigonometric sine and arcsine in radians | [trig-sin-asin.dou.md](./built-in-functions/number/trig-sin-asin.dou.md) |
| Deluge tan() and atan() — trigonometric tangent and arctangent in radians | [trig-tan-atan.dou.md](./built-in-functions/number/trig-tan-atan.dou.md) |
| Convert a TEXT date to Unix milliseconds since 1970-01-01 UTC | [unixEpoch.dou.md](./built-in-functions/number/unixEpoch.dou.md) |
| Get day-of-week NUMBER (1=Sunday, 7=Saturday) from DATE-TIME | [weekday.dou.md](./built-in-functions/number/weekday.dou.md) |
| Add business days to DATE-TIME with optional weekend and holiday lists | [workday.dou.md](./built-in-functions/number/workday.dou.md) |
### Built-in Functions — Text
| Task | Read This File |
|---|---|
| Join two TEXT strings into a single concatenated string | [concat.dou.md](./built-in-functions/text/concat.dou.md) |
| Deluge `contains()` / `containsIgnoreCase()` — check if a TEXT string contains a substring | [contains-containsIgnoreCase.dou.md](./built-in-functions/text/contains-containsIgnoreCase.dou.md) |
| Deluge `endsWith()` / `endsWithIgnoreCase()` — check if a TEXT string ends with a specific suffix | [endsWith-endsWithIgnoreCase.dou.md](./built-in-functions/text/endsWith-endsWithIgnoreCase.dou.md) |
| Compare two TEXT strings for equality, ignoring case differences | [equalsIgnoreCase.dou.md](./built-in-functions/text/equalsIgnoreCase.dou.md) |
| Deluge `find()` / `indexOf()` — return the 0-based index of a searchString's first occurrence | [find-indexOf.dou.md](./built-in-functions/text/find-indexOf.dou.md) |
| Deluge `getAlpha()` / `getAlphaNumeric()` — extract letters or alphanumeric characters from a string | [getAlpha-getAlphaNumeric.dou.md](./built-in-functions/text/getAlpha-getAlphaNumeric.dou.md) |
| Count occurrences of a substring within a text string | [getOccurenceCount.dou.md](./built-in-functions/text/getOccurenceCount.dou.md) |
| Deluge `getPrefix()` / `getSuffix()` — split a string on the first delimiter occurrence | [getPrefix-getSuffix.dou.md](./built-in-functions/text/getPrefix-getSuffix.dou.md) |
| Decode a hexadecimal-encoded string to its text equivalent | [hexToText.dou.md](./built-in-functions/text/hexToText.dou.md) |
| Return the 0-based index of the LAST occurrence of a searchString | [lastIndexOf.dou.md](./built-in-functions/text/lastIndexOf.dou.md) |
| Return a specified number of characters from a string's left side | [left.dou.md](./built-in-functions/text/left.dou.md) |
| Add leading whitespace to a text string for alignment | [leftpad.dou.md](./built-in-functions/text/leftpad.dou.md) |
| Deluge `len()` / `length()` — return the character count (including spaces) of a text string | [len-length.dou.md](./built-in-functions/text/len-length.dou.md) |
| Remove all leading whitespace characters from the left side of a string | [ltrim.dou.md](./built-in-functions/text/ltrim.dou.md) |
| Test if a string matches a regex pattern exactly | [matches.dou.md](./built-in-functions/text/matches.dou.md) |
| Extract a substring between two 0-based character indices | [mid.dou.md](./built-in-functions/text/mid.dou.md) |
| Convert a string to title/start case | [proper.dou.md](./built-in-functions/text/proper.dou.md) |
| Strip all alphabetic letters from a string | [remove-all-alpha.dou.md](./built-in-functions/text/remove-all-alpha.dou.md) |
| Strip all letters and digits from a string | [remove-all-alphanumeric.dou.md](./built-in-functions/text/remove-all-alphanumeric.dou.md) |
| Remove only the first matching substring occurrence | [remove-first-occurence.dou.md](./built-in-functions/text/remove-first-occurence.dou.md) |
| Remove only the last matching substring occurrence | [remove-last-occurence.dou.md](./built-in-functions/text/remove-last-occurence.dou.md) |
| Remove all occurrences of a substring from a string | [remove.dou.md](./built-in-functions/text/remove.dou.md) |
| Replace all occurrences of a substring in a string | [replace-all.dou.md](./built-in-functions/text/replace-all.dou.md) |
| Replace only the first occurrence of a substring | [replace-first.dou.md](./built-in-functions/text/replace-first.dou.md) |
| Return a specified number of characters from a string's right side | [right.dou.md](./built-in-functions/text/right.dou.md) |
| Add trailing whitespace to a text string for alignment | [rightpad.dou.md](./built-in-functions/text/rightpad.dou.md) |
| Remove all trailing whitespace characters from the right side of a string | [rtrim.dou.md](./built-in-functions/text/rtrim.dou.md) |
| Deluge `startsWith()` / `startsWithIgnoreCase()` — check if a TEXT string starts with a prefix | [startsWith-startsWithIgnoreCase.dou.md](./built-in-functions/text/startsWith-startsWithIgnoreCase.dou.md) |
| Deluge `subString()` / `subText()` — extract a substring between two 0-based character indices | [subString-subText.dou.md](./built-in-functions/text/subString-subText.dou.md) |
| Format NUMBER, DECIMAL, or DATE-TIME using a custom format pattern string | [text.dou.md](./built-in-functions/text/text.dou.md) |
| Encode a text string to its hexadecimal representation | [textToHex.dou.md](./built-in-functions/text/textToHex.dou.md) |
| Remove all leading and trailing whitespace from a text string | [trim.dou.md](./built-in-functions/text/trim.dou.md) |
### Deluge Tasks — Debug
| Task | Read This File |
|---|---|
| Deluge info statement — debug task to print expression values during script execution | [info.dou.md](./deluge-tasks/debug/info.dou.md) |
### Deluge Tasks — List Manipulations
| Task | Read This File |
|---|---|
| Deluge typed list declarations — List:String, List:Int, List:Float, List:date, List:Bool | [create-list-typed.dou.md](./deluge-tasks/list-manipulations/create-list-typed.dou.md) |
| Declare an empty or pre-populated heterogeneous list variable | [create-list.dou.md](./deluge-tasks/list-manipulations/create-list.dou.md) |
| Each-element loop — iterate over list elements or CSV file rows | [for-each-element.dou.md](./deluge-tasks/list-manipulations/for-each-element.dou.md) |
| Each-index loop — iterate over list element indices (0-based positions) | [for-each-index.dou.md](./deluge-tasks/list-manipulations/for-each-index.dou.md) |
| Permanently remove all elements from a list | [list-clear.dou.md](./deluge-tasks/list-manipulations/list-clear.dou.md) |
| Remove the first matching element by value | [list-remove-element.dou.md](./deluge-tasks/list-manipulations/list-remove-element.dou.md) |
| Remove and return the element at a specified index | [list-remove-index.dou.md](./deluge-tasks/list-manipulations/list-remove-index.dou.md) |
### Deluge Tasks — Map Manipulations
| Task | Read This File |
|---|---|
| Manipulation tasks — create, insert, merge, delete, and clear map entries | [map-manipulations.dou.md](./deluge-tasks/map-manipulations/map-manipulations.dou.md) |
### Deluge Tasks — Open URL
| Task | Read This File |
|---|---|
| Browser-navigate to a URL in a target window | [openUrl.dou.md](./deluge-tasks/open-url/openUrl.dou.md) |
### Deluge Tasks — XML Manipulation
| Task | Read This File |
|---|---|
| Query XML or JSON text using XPath expressions to extract node values | [executeXpath.dou.md](./deluge-tasks/xml-manipulation/executeXpath.dou.md) |
assets/functions/deluge/deluge-tasks/conditions/conditional-statements.dou.md
FILE_PURPOSE: Deluge conditional statements — if, else if, else, conditional if, and ifNull branching
MENTAL_MODEL: Five branching forms; if/else-if/else execute actions; conditional-if and ifNull return values
WHEN_TO_USE:
- Branch Deluge execution based on boolean criteria using if, else if, else.
- Return one of two values based on a criterion using conditional if.
- Return a fallback value when an expression evaluates to null using ifNull.
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
if, else if, else, conditional if, ifNull, ifNull expression, criteria-based branch
RULES:
- Criteria is mandatory for `if`, `else if`, and `conditional if`; `else` does NOT accept criteria.
- `else if` must be preceded by an `if` statement.
- `else` must be preceded by `if` or `else if`; must be the last statement in its set; cannot be followed by `if` or `else if` in the same set.
- Conditional if syntax: `<variable> = if(<criteria>, <success_value>, <failure_value>);` — returns `<success_value>` if criterion met, `<failure_value>` if not.
- `ifNull` syntax: `<variable> = ifNull(<expression1>, <expression2>);` — returns `expression1` if not null; returns `expression2` if `expression1` is null.
- `<success_value>` and `<failure_value>` accept direct values, expressions, or nested conditional statements.
CANONICAL_EXAMPLES:
```deluge
// if / else if / else
if(zoho.currentdate.weekday() == 7) {
info "Today is a Holiday";
} else if(zoho.currentDate.weekday() == 6) {
info "Today is a half working day";
} else {
info "Today is a working day";
}
// ifNull — returns "Not found" if nameList.get(10) is null
name = ifNull(nameList.get(10), "Not found");
// Equivalent to:
// if(nameList.get(10) == null) { name = "Not Found"; }
// else { name = nameList.get(10); }
```
FAILURE_MODES:
- `else` given criteria → syntax error; `else` accepts no criteria.
- `else if` or `else` without a preceding `if` → syntax error.
- `else` followed by `if` or `else if` in the same set → syntax error; `else` must be the last statement.
RELATED_DOCS:
- Criteria in conditional statements — criteria-conditional-statements.dou.md
SOURCE:
https://www.zoho.com/deluge/help/conditional-statements/condition.html
assets/functions/deluge/deluge-tasks/conditions/criteria-conditional-statements.dou.md
FILE_PURPOSE: Deluge criteria syntax — boolean conditions chained by logical operators for branching
MENTAL_MODEL: Criteria = one or more boolean conditions joined by logical operators; overall evaluates true/false
WHEN_TO_USE:
- Specify boolean conditions inside `if`, `else if`, or `conditional if` statements.
- Chain multiple conditions with `&&` (AND) and `||` (OR) logical operators.
WHEN_NOT_TO_USE:
- Inside an `else` statement — `else` does not accept criteria.
TRIGGER_KEYWORDS:
criteria, condition BOOLEAN, isNull, in not in, logical-operator chain, expression1 operator expression2
RULES:
- Criteria syntax: `(<condition_1> <logical_operator> <condition_2> ... <condition_n>)`
- Each condition is either `(<expression1> <operator> <expression2>)` or `(<function_returning_boolean>)`.
- Conditions evaluate to BOOLEAN; overall criteria evaluates to BOOLEAN.
- TEXT operators: `==`, `!=`, `is`, `is not`, `in`, `not in`; `in`/`not in` accept TEXT list.
- NUMBER/Decimal operators: `==`, `!=`, `is`, `is not`, `>`, `<`, `>=`, `<=`, `in`, `not in`.
- DATE-TIME operators: `==`, `!=`, `is`, `is not`, `in`, `not in`; accepts system variables `tomorrow`, `yesterday`, `now`, `today`.
- LIST operators: `==`, `in`, `!=`, `not in`; `in` matches if variable contains any specified list values even when additional elements are present.
- Null checks via `is not`: source advises using `isNull` function instead for null-checking.
- Field link names NOT supported in Custom Functions or Custom Schedule Workflows.
CANONICAL_EXAMPLES:
```deluge
// Multi-condition criteria with && and ||
if ((a > 5) && (b > 10) || (c > 20)) {
// actions_to_be_executed
}
```
FAILURE_MODES:
- Passing criteria to `else` → syntax error; `else` accepts no criteria.
- Using field link name syntax in Custom Functions or Custom Schedule Workflows → unsupported.
RELATED_DOCS:
- Conditional statements — conditional-statements.dou.md
- Criteria while fetching records — https://www.zoho.com/deluge/help/criteria-conditional-statements.html
- Logical operators — ../../operators/logical-relational-operators.dou.md
SOURCE:
https://www.zoho.com/deluge/help/criteria-conditional-statements.html
assets/functions/deluge/deluge-tasks/debug/info.dou.md
FILE_PURPOSE: Deluge info statement — debug task to print expression values during script execution
MENTAL_MODEL: info <expression>; = print any value to debug output visible in the Functions execution log
WHEN_TO_USE:
- Print variable values, intermediate results, or debug messages during script development.
- Inspect list, map, or collection contents at any point in a workflow.
WHEN_NOT_TO_USE:
- Providing user-facing messages — use `alert` for notifications visible to non-admin users.
TRIGGER_KEYWORDS:
info, info statement, debug output, 500KB info limit, info truncation, admin-visible output
RULES:
- Syntax: `info <expression>;`
- `<expression>` accepts any value, variable, or expression that evaluates to a value.
- Combined response of `info` and `alert` statements in a single function is capped at 500 KB; when exceeded, output is truncated with the message: "The info message has been truncated because its size exceeded 500 KB."
- In Schedules and Approvals events, the info task is listed under Miscellaneous (not Debug).
- File Upload field values display in the format `<file-id>_<file-name>.<file-type>`.
CANONICAL_EXAMPLES:
```deluge
info "Hello World"; // Returns Hello World
info 1; // Returns 1
```
FAILURE_MODES:
- Combined info + alert output exceeds 500 KB → output truncated, note appended.
- Used in Schedules or Approvals events → listed under Miscellaneous, not Debug.
RELATED_DOCS:
*(none)*
SOURCE:
https://www.zoho.com/deluge/help/debug/info.html
assets/functions/deluge/deluge-tasks/list-manipulations/create-list-typed.dou.md
FILE_PURPOSE: Deluge typed list declarations — List:String, List:Int, List:Float, List:date, List:Bool
MENTAL_MODEL: List:Type() = list constrained to one data type; auto-converts some types; rejects others at runtime
WHEN_TO_USE:
- Enforce a single data type for all list elements to prevent type-mismatch errors downstream.
WHEN_NOT_TO_USE:
- When list elements have mixed data types — use generic `List()` instead.
TRIGGER_KEYWORDS:
List:String(), List:Int(), List:Float(), List:date(), List:Bool(), typed list, type-constrained list
RULES:
- **Text list (`List:String()`)**: Text in quotes accepted. Numbers/Decimals without quotes auto-converted to Text. Date-time in single quotes auto-converted to Text. Booleans auto-converted to Text.
- **Number list (`List:Int()`)**: Only whole numbers accepted; any other data type causes a runtime error.
- **Decimal list (`List:Float()`)**: Numeric values accepted with or without quotes. Date-time in single quotes auto-converted to equivalent numeric milliseconds value. Other data types cause a runtime error.
- **Date-time list (`List:date()`)**: Date-time values in single or double quotes accepted. Numbers allowed: epoch offset where 1000 = +1 second from 31-Dec-1969 16:00:00. Number or Text in single/double quotes causes a runtime error. Missing time component defaults to 00:00:00.
- **Boolean list (`List:Bool()`)**: Only `'True'` or `'False'` values accepted (with or without quotes). Any other value in quotes causes a runtime error.
CANONICAL_EXAMPLES:
```deluge
var = List:Float();
var.add('1-Jan-2019'); // adds 1546329600000 (epoch ms equivalent)
listVar = List:String();
listVar.add(1); // adds "1" (number auto-converted to string)
```
FAILURE_MODES:
- Adding an incompatible data type to a typed list → runtime error.
- Number or Text in single/double quotes in `List:date()` → runtime error.
- Non-boolean value in `List:Bool()` → runtime error.
RELATED_DOCS:
- Create List — create-list.dou.md
SOURCE:
https://www.zoho.com/deluge/help/create-list-datatype.html
assets/functions/deluge/deluge-tasks/list-manipulations/create-list.dou.md
FILE_PURPOSE: Deluge List() — declare an empty or pre-populated heterogeneous list variable
MENTAL_MODEL: List() = ordered, zero-indexed, mutable collection; mixed data types allowed unless type-restricted
WHEN_TO_USE:
- Create a list to hold multiple values of potentially different data types.
- Initialize a list variable before adding elements with `.add()`.
WHEN_NOT_TO_USE:
- When elements must be constrained to a single data type — use `List:String()`, `List:Int()`, etc.
TRIGGER_KEYWORDS:
List(), list literal, list curly brackets, list initialization, mixed-type list
RULES:
- Three declaration forms:
- `<variable> = List();` — empty list.
- `<variable> = List(<value>);` — accepts only Text or List as `<value>`.
- `<variable> = {<e1>, <e2>, ...};` — literal form; brackets `{}` or `[]` both accepted.
- Text value with commas in `List(<value>)`: words on either side of each comma become individual list elements.
- Mixed data types coexist in the same list unless it is a typed list.
CANONICAL_EXAMPLES:
```deluge
colors = {"red", "blue", "green"}; // literal list
listVar = List();
listVar.add(1);
listVar.add("Harry"); // mixed types: integer + string
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- List datatype — ../../datatypes/list.dou.md
- Create list of specific data type — create-list-typed.dou.md
SOURCE:
https://www.zoho.com/deluge/help/list-manipulations/create-list.html
assets/functions/deluge/deluge-tasks/list-manipulations/for-each-element.dou.md
FILE_PURPOSE: Deluge for-each-element loop — iterate over list elements or CSV file rows
MENTAL_MODEL: for each <var> in <list|csv> {} = loop body executes once per element; var holds current element
WHEN_TO_USE:
- Iterate over all elements in a list.
- Process each row in a CSV file (up to 2 MB).
WHEN_NOT_TO_USE:
- Iterating by index position rather than element value — use `for each index` instead.
TRIGGER_KEYWORDS:
for each element, for each in list, CSV iteration, 2MB CSV limit, elementVariable last value
RULES:
- Syntax: `for each <elementVariable> in <variable> { }`
- `<variable>` supports List and CSV file types.
- CSV file size limit: 2 MB maximum.
- `<elementVariable>` needs no prior declaration.
- After iteration completes, `<elementVariable>` holds the last element value.
CANONICAL_EXAMPLES:
```deluge
numbers = {"One", "Two", "Three"};
for each item in numbers {
info item;
}
// Output: One Two Three
// CSV iteration
csv_file_zip = invokeurl[url:"https://example.com/file.zip" type:GET];
csv_file = csv_file_zip.extract().get("file.csv");
for each val in csv_file {
info val;
}
```
FAILURE_MODES:
- CSV file exceeds 2 MB → error.
RELATED_DOCS:
- For each index — for-each-index.dou.md
- Extract — ../../file-functions/extract.dou.md
- Send Mail — ../misc/../misc/send-mail.dou.md
SOURCE:
https://www.zoho.com/deluge/help/list-manipulations/for-each-element.html
assets/functions/deluge/deluge-tasks/list-manipulations/for-each-index.dou.md
FILE_PURPOSE: Deluge for-each-index loop — iterate over list element indices (0-based positions)
MENTAL_MODEL: for each index <var> in <list> = loop body executes once per index; var holds current 0-based index
WHEN_TO_USE:
- Iterate over list elements by their integer index positions.
- When both the index position and the element value are needed within the loop body.
WHEN_NOT_TO_USE:
- Iterating by element value without needing the index — use `for each element` instead.
- Iterating CSV files — only `for each element` supports CSV; `for each index` is List-only.
TRIGGER_KEYWORDS:
for each index, for each index in list, index-based iteration, loop variable index, list.get by index
RULES:
- Syntax: `for each index <loop_variable> in <list_variable>;`
- `<loop_variable>` holds the current 0-based integer index on each iteration.
- Only List data type is supported (not CSV file).
- `<loop_variable>` needs no prior declaration.
- After iteration completes, `<loop_variable>` holds the index of the last element.
- Retrieve element at each index using `<list_variable>.get(<loop_variable>)`.
CANONICAL_EXAMPLES:
```deluge
colorList = {"red", "green", "blue"};
for each index colorIndex in colorList {
info "(" + colorIndex + ")" + colorList.get(colorIndex);
}
// Output: (0)red (1)green (2)blue
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- For each element — for-each-element.dou.md
SOURCE:
https://www.zoho.com/deluge/help/list-manipulations/for-each-index.html
assets/functions/deluge/deluge-tasks/list-manipulations/list-clear.dou.md
FILE_PURPOSE: Deluge list.clear() — permanently remove all elements from a list
MENTAL_MODEL: list.clear() = empties the list in place; all elements permanently removed; no return value
WHEN_TO_USE:
- Reset a list to empty within a script without creating a new list variable.
- Remove stale data from a list before repopulating it in the same script execution.
WHEN_NOT_TO_USE:
- When individual element removal is needed — use `remove()` or `removeElement()`.
TRIGGER_KEYWORDS:
list.clear, clear list, empty list, list reset, clear all elements
RULES:
- Syntax: `<list_variable>.clear();`
- Permanently removes all elements from the specified list.
CANONICAL_EXAMPLES:
```deluge
alldays = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
alldays.clear();
// alldays is now an empty list []
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
*(none in source)*
SOURCE:
https://www.zoho.com/deluge/help/list-manipulations/clear.html
assets/functions/deluge/deluge-tasks/list-manipulations/list-remove-element.dou.md
FILE_PURPOSE: Deluge list.removeElement() — remove the first matching element by value
MENTAL_MODEL: list.removeElement(value) = removes first matching element by value; silent if not found; typed-list mismatch errors
WHEN_TO_USE:
- Remove a specific element from a list by its value (not position).
WHEN_NOT_TO_USE:
- Removing by index position — use `list.remove(index)` instead.
TRIGGER_KEYWORDS:
list.removeElement, removeElement, remove by value, silent not-found, typed list mismatch
RULES:
- Syntax: `<list_variable>.removeElement(<expression>);`
- Element not found in list → no error; list is unchanged (silent no-op).
- For type-specific lists: the data type of `<expression>` must match the list's declared type; mismatch causes a runtime error.
- All data types are applicable for `<expression>`.
CANONICAL_EXAMPLES:
```deluge
days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
days.removeElement("Tuesday");
// list becomes ["Monday", "Wednesday", "Thursday", "Friday"]
days.removeElement("Saturday");
// "Saturday" not found — no error, list unchanged
```
FAILURE_MODES:
- Removing from a typed list with a mismatched data type → runtime error.
- Element not found → silent no-op; no error raised.
RELATED_DOCS:
- Remove by index — list-remove-index.dou.md
SOURCE:
https://www.zoho.com/deluge/help/list-manipulations/remove-element.html
assets/functions/deluge/deluge-tasks/list-manipulations/list-remove-index.dou.md
FILE_PURPOSE: Deluge list.remove(index) — remove and return the element at a specified index
MENTAL_MODEL: list.remove(index) = removes element at 0-based index; returns removed element; errors on invalid index
WHEN_TO_USE:
- Remove a specific element by its position (index) in a list.
- Retrieve and remove an element simultaneously.
WHEN_NOT_TO_USE:
- Removing by element value rather than position — use `list.removeElement()` instead.
TRIGGER_KEYWORDS:
list.remove, remove by index, 0-based index removal, list index remove, remove returns element
RULES:
- Syntax: `<variable> = <list_variable>.remove(<index>);` (return variable is optional).
- Index is 0-based: first element is index 0, last is size−1.
- `<index>` must be a Number; negative values are not allowed.
- Returns the removed element.
CANONICAL_EXAMPLES:
```deluge
days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
days.remove(1);
// removes "Tuesday" (index 1)
// list becomes ["Monday", "Wednesday", "Thursday", "Friday"]
```
FAILURE_MODES:
- Negative index → runtime error.
- Index >= list size → runtime error.
RELATED_DOCS:
- Remove element — list-remove-element.dou.md
SOURCE:
https://www.zoho.com/deluge/help/list-manipulations/remove-index.html
assets/functions/deluge/deluge-tasks/map-manipulations/map-manipulations.dou.md
FILE_PURPOSE: Deluge map-manipulation tasks — create, insert, merge, delete, and clear map entries.
MENTAL_MODEL: Map() = Deluge Key-Value container; insert pairs with put()/putAll(); delete with remove()/clear().
WHEN_TO_USE:
- Build structured key-value containers for API payloads, condition checks, or dynamic data assembly.
- Merge two maps, delete specific entries by key, or reset a map to empty state.
WHEN_NOT_TO_USE:
*(none in source)* — all five tasks are supported in every Deluge workflow event and context.
TRIGGER_KEYWORDS:
Map(), .put(), .putAll(), .remove(), .clear(), key overwrite behavior, map merge putAll
RULES:
- Create empty map: `<var> = Map();`
- Create map with initial pairs: `<var> = {<key1>: <val1>, <key2>: <val2>};`
- Insert or update a single pair: `<map>.put(<key>, <value>);`
- Merge all pairs from map2 into map1: `<map1>.putAll(<map2>);`
- Remove one key and its value: `<map>.remove(<key>);` — if key is not found, no error is raised.
- Empty the entire map permanently: `<map>.clear();`
- Keys must be unique; specifying an existing key in `Map()`, `put()`, or `putAll()` silently OVERWRITES the existing value — no duplicate entry is appended.
- Keys and values can be of any Deluge data type.
- Date keys or values specified without a time component default to `00:00:00` as the time value.
CANONICAL_EXAMPLES:
```deluge
// Create map inline
mapVal = {"Product": "Zoho CRM", "Version": 8};
// Create map with list as value
mapVal = {"Products": {"Zoho CRM", "Zoho Cliq", "Zoho Books"}};
// Create empty map then insert entries
companyDetails = map();
companyDetails.put("Name", "Zoho");
companyDetails.put("Team", {"Zoho CRM", "Zoho Cliq", "Zoho Books"});
// Merge map2 into map1
city_list1 = {"Anchorage": "99501", "Phoenix": "85001"};
city_list2 = {"Little Rock": "72201", "Beverly Hills": "90213"};
city_list1.putAll(city_list2);
// city_list1 now contains all four entries
// Remove a specific key
details = {"Company": "Zoho", "Product": "CRM", "Version": 8};
details.remove("Version");
// details is now {"Company": "Zoho", "Product": "CRM"}
// Clear all entries
Company_details.clear();
```
FAILURE_MODES:
- Inserting a key that already exists silently overwrites the existing value — no error, no second entry with the same key.
- `putAll()` on overlapping keys in map2 silently overwrites those keys in map1 — no merge warning.
- `remove()` on a key that does not exist silently does nothing — no error is raised.
- `clear()` permanently removes all key-value pairs from the variable; there is no undo within the same script.
RELATED_DOCS:
- Key-Value data type — ../../datatypes/key-value.dou.md
- Map Manipulations overview — https://www.zoho.com/deluge/help/map-manipulations/create-map.html
SOURCE:
https://www.zoho.com/deluge/help/map-manipulations/create-map.html, https://www.zoho.com/deluge/help/map-manipulations/put.html, https://www.zoho.com/deluge/help/map-manipulations/put-all-keys.html, https://www.zoho.com/deluge/help/map-manipulations/remove-key.html, https://www.zoho.com/deluge/help/map-manipulations/clear-map.html
assets/functions/deluge/deluge-tasks/misc/break.dou.md
FILE_PURPOSE: Deluge break statement — terminate a for-each loop immediately and resume after the loop
MENTAL_MODEL: break; = exits the current for-each loop entirely; execution continues at first statement after the loop
WHEN_TO_USE:
- Exit a for-each loop as soon as a target condition is met (e.g. first match found).
- Abort loop processing early to avoid unnecessary iterations.
WHEN_NOT_TO_USE:
- Outside a for-each loop — break is only valid inside loops.
TRIGGER_KEYWORDS:
break, break loop Deluge, for each break, loop termination, first match break
RULES:
- Syntax: `break;`
- Terminates execution of the current for-each loop.
- Execution resumes at the first statement after the loop.
- Break terminates the entire loop; contrast with `continue` which only skips the current iteration.
CANONICAL_EXAMPLES:
```deluge
// Exit loop when "Red" is found
colors = List();
colors.add("Green");
colors.add("Red");
colors.add("Blue");
colors.add("Teal");
for each rec in colors {
if(rec == "Red") {
break;
}
info rec;
}
// Returns: Green
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- continue — continue.dou.md
- For each statement — ../list-manipulations/../list-manipulations/for-each-element.dou.md
SOURCE:
https://www.zoho.com/deluge/help/misc-statements/break.html
assets/functions/deluge/deluge-tasks/misc/call-function.dou.md
FILE_PURPOSE: Deluge call function — invoke a named custom function from within another Deluge script
MENTAL_MODEL: function_name(params) or variable = function_name(params); invokes a custom function by name
WHEN_TO_USE:
- Invoke a custom function that returns a value and capture its return.
- Invoke a custom function that does not return a value (void call).
- Reuse shared logic (e.g. send mail, record creation) across multiple workflows.
WHEN_NOT_TO_USE:
- In Zoho Sheets, Zoho Connect, or Zoho Cliq — call function is not supported.
TRIGGER_KEYWORDS:
<function_name>(), namespace.function_name, category.function_name, not-Sheets-Connect-Cliq, parameter-count mismatch
RULES:
- Two syntaxes:
- `<variable> = <function_name>(<parameters>);` — for functions that return a value.
- `<function_name>(<parameters>);` — for functions that do not return a value.
- `<function_name>` must be the complete name including namespace or category if applicable:
- Zoho CRM: `<category>.<function_name>`.
- Number of parameters and their data types must match the arguments defined in the function exactly; mismatch prevents the script from being saved.
- NOT supported in Zoho Sheets, Zoho Connect, and Zoho Cliq.
CANONICAL_EXAMPLES:
```deluge
// Call a function that returns a value
lead_response = create_lead("John", "Doe", "john.doe@zylker.com", "Event Registration");
info lead_response;
// Call a void function
send_mail("emily@zylker.com", "Welcome Aboard", "We welcome you aboard.");
```
FAILURE_MODES:
- Parameter count or data type mismatch with defined function arguments → script will not be saved.
- Used in Zoho Sheets, Zoho Connect, or Zoho Cliq → not supported.
RELATED_DOCS:
*(none in source)*
SOURCE:
https://www.zoho.com/deluge/help/misc-statements/call-function.html
assets/functions/deluge/deluge-tasks/misc/continue.dou.md
FILE_PURPOSE: Deluge continue statement — skip the current loop iteration and proceed to the next one
MENTAL_MODEL: continue; = skips remaining statements in current iteration; for-each loop proceeds with next element
WHEN_TO_USE:
- Skip a specific iteration of a for-each loop based on a condition.
- Filter out unwanted elements in a loop without using deeply nested conditional blocks.
WHEN_NOT_TO_USE:
- Outside a for-each loop — continue is only valid inside loops.
TRIGGER_KEYWORDS:
continue, continue loop Deluge, skip iteration, for each continue, loop iteration skip
RULES:
- Syntax: `continue;`
- Skips the remainder of the current iteration and proceeds with the next iteration of the for-each loop.
- Continue skips only the current iteration; contrast with `break` which terminates the entire loop.
CANONICAL_EXAMPLES:
```deluge
// Skip "Red" in the loop
colors = List();
colors.add("Green");
colors.add("Red");
colors.add("Blue");
colors.add("Teal");
for each rec in colors {
if(rec == "Red") {
continue;
}
info rec;
}
// Returns: Green, Blue, Teal
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
- break — break.dou.md
- For each statement — ../list-manipulations/../list-manipulations/for-each-element.dou.md
SOURCE:
https://www.zoho.com/deluge/help/misc-statements/continue.html
assets/functions/deluge/deluge-tasks/misc/return.dou.md
FILE_PURPOSE: Deluge return statement — specify a typed custom function's return value
MENTAL_MODEL: return <expression>; exits a function and returns expression value; only valid in typed custom functions
WHEN_TO_USE:
- Return a computed value from a custom function that has a declared return type.
WHEN_NOT_TO_USE:
- Functions without a declared return type (void) — return statement is not applicable.
TRIGGER_KEYWORDS:
return, return expression, Custom-Fn only, void-excluded return, return-datatype mismatch
RULES:
- Syntax: `return <expression>;`
- Applicable only inside custom functions that have a declared return type.
- Return value data type must match the declared return type; mismatch → runtime error.
CANONICAL_EXAMPLES:
```deluge
// Function that returns number of days between two dates
int standalone.CalculateDays(date sdate, date edate) {
days = (((edate - sdate) / 86400000)).toLong();
return days;
}
```
FAILURE_MODES:
- Return value data type does not match the declared return type → runtime error.
- Used outside a custom function (e.g. in form workflow) → not applicable.
RELATED_DOCS:
*(none in source)*
SOURCE:
https://www.zoho.com/deluge/help/misc-statements/return-statement.html
assets/functions/deluge/deluge-tasks/misc/send-mail.dou.md
FILE_PURPOSE: Deluge sendmail task — send email to recipients with optional CC, BCC, and attachments
MENTAL_MODEL: sendmail[from:... to:... subject:... message:...] = structured email dispatch; from must be verified or admin/login user ID
WHEN_TO_USE:
- Send emails from Deluge scripts across all Zoho services.
WHEN_NOT_TO_USE:
- Gmail address as sender — not allowed from February 1, 2024.
- Unverified from addresses in restricted services → sendmail task fails.
TRIGGER_KEYWORDS:
sendmail, from zoho.adminuserid, from zoho.loginuserid, verified email sendmail, attachments file, reply to 20 limit
RULES:
- Syntax: `sendmail[from: <from_address> to: <to_address> cc: <cc> bcc: <bcc> reply to: <reply_to_address> subject: <subject> message: <message> content type: <content_type> attachments: <attachment>]`
- `from`: TEXT; must be `zoho.adminuserid`, `zoho.loginuserid`* (not portal users), or a verified email address; format "Name <email>" sets display name in recipient's inbox.
- `to`, `cc`, `bcc`: TEXT or LIST; multiple addresses via comma-separated string or LIST.
- `reply to`: TEXT, LIST, or comma-separated TEXT; maximum 20 reply-to addresses.
- `content type`: default `HTML`; specify `Plain Text` to send HTML tags unrendered.
- `attachments`: FILE; maximum size 15 MB; if exceeded, emails delivered without attachments.
- In services other than Zoho Cliq, Connect, Mail, Sheet, CRM: `from` must be `zoho.adminuserid`, `zoho.loginuserid`*, or verified email; otherwise → fails.
- In Zoho Cliq, Connect, Mail, Sheet, CRM: `from` must be `zoho.loginuserid` or verified email.
CANONICAL_EXAMPLES:
```deluge
// Basic send with CC and reply-to
sendmail [
from: zoho.adminuserid
to: "shawn@zylker.com"
cc: "salesmanager@zoho.com"
reply to: "support@zylker.com"
subject: "Your CRM record is ready"
message: "<p>Registration successful.</p>"
]
// With file attachment fetched via invokeUrl
fileResp = invokeUrl[url:"https://example.com/file.png" type:GET];
sendmail [
from: zoho.adminuserid
to: candidate_email
subject: "Welcome aboard"
message: "Here is your welcome file."
Attachments: file: fileResp
]
```
FAILURE_MODES:
- Gmail address as `from` → fails from February 1, 2024.
- Unverified/non-admin from address in restricted services → error while saving or runtime error.
- Attachments exceed 15 MB → email delivered without attachments.
- More than 20 `reply to` addresses → maximum of 20 accepted.
RELATED_DOCS:
- Send SMS — https://www.zoho.com/deluge/help/misc-statements/send-mail.html
SOURCE:
https://www.zoho.com/deluge/help/misc-statements/send-mail.html
assets/functions/deluge/deluge-tasks/misc/set-variable.dou.md
FILE_PURPOSE: Deluge set variable — declare and assign a local script variable using assignment syntax
MENTAL_MODEL: <variable> = <expression>; creates or overwrites a local variable; returns stored value; no prior declaration needed
WHEN_TO_USE:
- Assign any value or expression to a script-local variable.
- Overwrite a variable's value later in the same script.
WHEN_NOT_TO_USE:
- Cross-script shared state — variables are script-local only; pass values explicitly between functions.
TRIGGER_KEYWORDS:
set variable, variable assignment, declare variable Deluge, local variable overwrite, expression assignment
RULES:
- Syntax: `<variable> = <expression>;`
- Returns the stored value.
- Variables need not be declared before assigning; declaration and assignment happen simultaneously.
- Assigning a new value to an existing variable overwrites the previous value.
CANONICAL_EXAMPLES:
```deluge
temp = 0;
// temp now holds 0
temp = temp + 1;
// temp is now 1; prior value overwritten
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
*(none)*
SOURCE:
https://www.zoho.com/deluge/help/misc-statements/set-variable/declare-variable.html
assets/functions/deluge/deluge-tasks/misc/try-catch.dou.md
FILE_PURPOSE: Deluge try-catch — handle runtime errors with structured exception handling
MENTAL_MODEL: try { risky code } catch(e) { error handler }; catch runs only on error; code after try-catch always executes
WHEN_TO_USE:
- Wrap Deluge code that might throw runtime errors in a try block.
- Inspect error details (line number, message) in the catch block.
- Continue overall function execution even when a section throws a runtime error.
WHEN_NOT_TO_USE:
*(none in source)*
TRIGGER_KEYWORDS:
try catch, exception_variable.lineNo, exception_variable.message, runtime exception handler, catch block Deluge
RULES:
- Syntax: `try { <code> } catch(<exception_variable>) { <code> }`
- Catch block executes only when the try block throws a runtime error; skipped if no error occurs.
- Exception variable attributes:
- `<exception_variable>.lineNo` → line number of the statement that caused the error.
- `<exception_variable>.message` → error message text.
- `<exception_variable>` alone → both line number and error message combined.
- Code written after the try-catch block executes regardless of whether an error was caught.
CANONICAL_EXAMPLES:
```deluge
try {
products = {"Leads", "Contacts", "Deals"};
products = products.get(10); // index out of bounds → error
products.add("Sheet"); // not reached
} catch(e) {
info e.lineNo; // line number of statement that caused error
info e.message; // error message text
info e; // both line number and message
}
// e holds: "Error at line : 6, Given index 10 is greater than the list size"
// Code after try-catch still executes
```
FAILURE_MODES:
*(none in source)*
RELATED_DOCS:
*(none in source)*
SOURCE:
https://www.zoho.com/deluge/help/misc-statements/try-catch.html
assets/functions/deluge/deluge-tasks/open-url/openUrl.dou.md
FILE_PURPOSE: Deluge `openUrl()` — browser-navigate to a URL in a target window.
MENTAL_MODEL: `openUrl(url, window)` = client-side browser navigation; terminal statement — only the last call per action executes.
WHEN_TO_USE:
- Redirect the user to another page or external URL after an action.
- Navigate back in browser history using `#Script:page.back`.
WHEN_NOT_TO_USE:
- In a custom action placed in the View header — `openUrl` does not execute in that context.
- When multiple `openUrl` calls are configured in one action — only the LAST one executes; earlier calls are silently ignored.
TRIGGER_KEYWORDS:
openUrl, same window, new window, parent window, page.back, successive=true
RULES:
- Syntax: `openUrl(<URL>, <window>, [optional_param]);`
- Supported `window` values: `same window`, `new window`, `parent window`.
- All URLs must include a protocol prefix (e.g. `https://`).
- `optional_param` when window is `new window`: omitting it causes all subsequent clicks to reuse the same new window; set `successive=true` to force each call to open a distinct window.
- `openUrl` is a terminal statement — when multiple `openUrl` tasks appear in one action, only the last one executes.
CANONICAL_EXAMPLES:
```deluge
// Open an external URL in a new tab from a CRM Button action
openUrl("https://maps.google.com/?q=" + billingAddress, "new window", "successive=true");
// Navigate back in browser history
openUrl("#Script:page.back", "same window");
```
FAILURE_MODES:
- Multiple `openUrl` tasks in one action: only the last one executes; all earlier `openUrl` calls are silently discarded.
- `openUrl` inside a custom action placed in the View header does not execute — task is not supported in that context.
- Omitting the protocol prefix (e.g. `www.zoho.com` instead of `https://www.zoho.com`) causes the URL to not open correctly.
- When `new window` and no `successive=true`, subsequent `openUrl` calls reuse the same tab instead of opening new ones.
RELATED_DOCS:
*(none)*
SOURCE:
https://www.zoho.com/deluge/help/openurl.html
assets/functions/deluge/deluge-tasks/xml-manipulation/executeXpath.dou.md
FILE_PURPOSE: Deluge `executeXpath()` — query XML or JSON text using XPath expressions to extract node values.
MENTAL_MODEL: `xmlText.executeXPath(xpathExpr)` = evaluate XPath against XML/JSON string; always returns TEXT.
WHEN_TO_USE:
- Extract specific elements from an XML API response using standard XPath expressions.
- Parse a JSON response with XPath by prepending `/root` to the path.
- Chain two `executeXPath()` calls to navigate nested XML structures or extract attribute values.
WHEN_NOT_TO_USE:
- When the response is already a Deluge Key-Value map — access keys directly without XPath.
- When no node selection is needed and the full string is sufficient.
TRIGGER_KEYWORDS:
executeXPath, /root JSON XPath prefix, XPath-attribute extraction, chained-XPath calls, XML node traversal, invalid-XPath runtime
RULES:
- Syntax: `<variable> = <xml_or_json>.executeXPath(<xpath>);`
- Return type: TEXT always.
- JSON input requires `/root` prepended to the XPath; the returned value is wrapped in XML-style tags matching the key (e.g. `<city>Surat</city>`), not a raw string.
- An invalid XPath expression raises a runtime error.
- `executeXPath()` calls can be chained: `response.executeXPath(expr1).executeXPath(expr2)`.
CANONICAL_EXAMPLES:
```deluge
// XML: fetch node values from a web resource
xmlResp = invokeurl[url: "https://www.w3schools.com/xml/cd_catalog.xml" type: GET];
info xmlResp.executexpath("/CATALOG/CD/TITLE");
// JSON: add /root prefix; result is tag-wrapped
jsonResp = invokeurl[url: "https://tools.learningcontainer.com/sample-json.json" type: GET];
info jsonResp.executexpath("/root/address/city");
// returns <city>Surat</city>
// Fetch attribute value (chained calls)
text = "<a href=\"https://example.com/file.pdf\" target=\"_blank\">Title</a>";
info text.executeXPath("/a/@href").executeXpath("/href/text()");
// returns https://example.com/file.pdf
// Fetch 3rd book node then extract its title
info xmlString.executeXPath("/bookstore/book[3]").executeXPath("/book/title/text()");
// returns: Learning XML
```
FAILURE_MODES:
- An invalid XPath expression raises a runtime error at execution time.
- JSON input without the `/root` prefix produces an incorrect or empty result silently.
- JSON return values are wrapped in XML-style tags (e.g. `<city>Surat</city>`); chain `.executeXPath("/tagname/text()")` to extract the raw string without the tags.
RELATED_DOCS:
- XML manipulation overview — https://www.zoho.com/deluge/help/xml-manipulation/executexpath.html
- invokeUrl task — ../../integrations/invokeurl-task.dou.md
SOURCE:
https://www.zoho.com/deluge/help/xml-manipulation/executexpath.html
assets/functions/deluge/errors.dou.md
## FILE_PURPOSE
Deluge error message reference — save errors, runtime errors, and built-in function type errors.
## MENTAL_MODEL
Save errors block script saving (syntax/declaration); runtime errors fire during execution (type/bounds/logic).
## WHEN_TO_USE
- Diagnosing a Deluge script that fails to save or throws a runtime error
- Mapping an error message pattern to its root cause and fix
## WHEN_NOT_TO_USE
- *(none in source)*
## TRIGGER_KEYWORDS deluge-save-errors, deluge-runtime-errors, null-operation-runtime, index-out-of-bounds, cast-mismatch, divide-by-zero, undefined-variable
## RULES
Save Errors — block script save:
1. `Variable '<var>' is not defined` — variable used before it is declared/assigned.
2. `Expecting '}' but found '<EOF>'` — unmatched opening brace or block.
3. `Comment Task is not closed properly` — `/*` not paired with `*/`.
4. `Improper Statement Error might be due to missing ';'` — missing semicolon; unclosed string literal; trailing backslash misinterpreted as escape (e.g. `"test\"` treats `\"` as escaped quote, leaving string unclosed). Also raised by: (a) statement split across multiple lines — every statement including `info`, assignments, and chained method calls must fit on a single line ending with `;`; (b) map literal `{"key": value}` used directly in an `invokeurl parameters` field — build the map as a variable first.
5. `Number of Arguments mismatches` — wrong arg count in a Deluge task.
6. `In Criteria left expression is of type <DT1> and right expression is of type <DT2> and the operator <Op> is not valid` — incompatible operand types for the operator used.
7. `<fn> function needs to be assigned to a variable` — value-returning built-in result is not captured.
8. `<fn> function cant be assigned to variable or used in expression` — void built-in result assigned to a variable.
9. `No. of arguments mismatches for the function <fn>` / `Argument type mismatches for the function <fn> at index <i>` — wrong arg count or data type supplied to a built-in.
10. `<integration_task> is not supported for this function` — service-specific integration task invoked outside its service, OR `connections` param omitted in Zoho Cliq.
sendmail save constraints:
11. FROM and TO are mandatory fields; SUBJECT and MESSAGE are mandatory fields.
12. BCC and CC blocked unless From = `zoho.adminuserid` or `zoho.loginuserid`.
13. `Invalid email address found` — From or To field is not of TEXT data type.
Runtime Errors — fire during execution:
14. `Error in performing the Operation / : Divide by zero error` — division by zero terminates execution.
15. `Null value occurred while performing <Op> operation` — any arithmetic/logical operation on a null variable.
16. `Given string is lesser than the applied boundary, so index out of bounds exception arisen` — text index exceeds string length (subText, mid, indexOf, etc.).
17. `Given index <N> is greater than the list size` — collection or list index out of range.
18. `Invalid JSON Format String` — text supplied to JSON parse or toCollection is not valid JSON.
19. `An error occurred while performing this action` — relational operator applied to two non-numeric operands.
20. `'<Number>' can not be cast to 'MAP'` — number used where key-value collection is expected.
Runtime Errors: Built-In Functions:
21. `'TEXT' can not be cast to '[BIGINT]'` — text supplied where NUMBER expected (text, number, datetime functions).
22. `'TEXT' can not be cast to '[DATE-TIME, DATE]'` — text not in a valid date format for a datetime function.
23. `Incompatible types...Required: [DATE-TIME, DATE] Found: NUMBER` — number supplied where DATE or DATE-TIME expected.
24. `'TEXT' can not be cast to '[BOOLEAN]'` — text supplied where boolean expected (e.g. `sort` direction param).
25. `UnParsable date` — value not in a recognized date format (common functions).
26. `Invalid XML or Invalid XPath` — malformed XML string or XPath expression.
27. Non-ASCII characters (em dash `—`, ellipsis `…`, box-drawing `═`, middot `·`, etc.) inside **string literals** — trigger Improper Statement Error on the **following line**, not the line containing the character; the root cause is non-obvious. ASCII characters only in string literals; non-ASCII is safe in comments.
## CANONICAL_EXAMPLES
```deluge
// Save error: undefined variable
info b; // → save error: "Variable 'b' is not defined"
b = 10; info b; // fix: declare b before use
// Runtime error: divide by zero
a = 20; b = 0;
info a / b; // → runtime: "Divide by zero error"
// Runtime error: text index out of bounds
t = "Hi";
sub = t.subText(0, 10); // → runtime: "string is lesser than applied boundary"
```
## FAILURE_MODES
- String ending with backslash (`"path\"`) causes save error — backslash escapes the closing quote; fix by escaping the backslash (`"path\\"`)
- Divide-by-zero terminates execution at that line — guard with `!= 0` before dividing
- Out-of-bounds error wording differs: text = "string lesser than boundary"; list = "index greater than list size"
- In Zoho Cliq, `connections` param is mandatory for all integration tasks; omitting it raises a save error
- Returning from a function without a value causes `Missing return statement: Provide MAP expression to return`
Runtime Errors: Method calls:
28. `Number of Arguments mismatches for ifnull expecting 2` — `.ifNull(default)` called as a method chain on a value; `ifnull` is a 2-argument standalone function, not a method. Correct form: `ifnull(expression, fallback)` not `expression.ifNull(fallback)`.
## RELATED_DOCS
- Deluge built-in functions — https://www.zoho.com/deluge/help/built-in-functions.html
- Deluge data types — https://www.zoho.com/deluge/help/datatypes.html
- Deluge tasks — https://www.zoho.com/deluge/help/deluge-tasks.html
## SOURCE
https://www.zoho.com/deluge/help/error-messages.html
assets/functions/deluge/file-functions/compress.dou.md
FILE_PURPOSE: Deluge compress — zip one or more FILE objects into a single .zip FILE (max 2 MB)
MENTAL_MODEL: compress = FILE/LIST/KEY-VALUE → FILE (.zip, max 2 MB); optional directory structure via KEY-VALUE
WHEN_TO_USE:
- Zip a single FILE before attaching to email or uploading.
- Zip multiple files (LIST) into one archive.
- Zip files into a nested directory structure (KEY-VALUE map).
WHEN_NOT_TO_USE:
- Output zip would exceed 2 MB — compress throws an error; split into smaller batches.
TRIGGER_KEYWORDS:
compress, file.compress, LIST compress, KEY-VALUE directory zip, 2MB zip limit, getFileName, invokeUrl file only
RULES:
- Syntax: `<zip_file> = <files>.compress([<compressed_file_name>]);`
- `<files>` accepts FILE (single), LIST of FILEs (multiple), or KEY-VALUE (directory structure).
- For KEY-VALUE input: keys are file names (TEXT) or directory names (TEXT); values are FILE or nested KEY-VALUE.
- `<compressed_file_name>` (optional TEXT): name for the returned .zip; avoid restricted characters.
- Return type: FILE (.zip format).
- Output exceeding 2 MB causes an error.
CANONICAL_EXAMPLES:
```deluge
// Single file
zip_file = file1.compress("compressed_file");
// List of files
filesList = List();
filesList.add(file1);
filesList.add(file2);
zip_file = filesList.compress("compressed_file");
// Directory structure: dir1 contains file1 and nested dir2; dir2 contains file2
directory2 = Map();
directory2.put(file2.getFileName(), file2);
directory1 = Map();
directory1.put(file1.getFileName(), file1);
directory1.put("Directory 2", directory2);
zip_file = directory1.compress("Directory 1");
// Returns: Directory 1.zip
```
FAILURE_MODES:
- Compressed output > 2 MB → error; split files across multiple compress calls.
RELATED_DOCS:
- File Methods — ../datatypes/../datatypes/file.dou.md
- extract — extract.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/file/compress.html
assets/functions/deluge/file-functions/convert-to-pdf.dou.md
FILE_PURPOSE: Deluge zoho.file.convertToPDF — convert an HTML string or public URL into a PDF FILE object
MENTAL_MODEL: zoho.file.convertToPDF(html_or_url, options?) → FILE (PDF); one external API call per execution
WHEN_TO_USE:
- Generate a PDF from an HTML string (reports, invoices, certificates, dynamic documents).
- Convert a publicly accessible URL into a PDF.
WHEN_NOT_TO_USE:
- HTML content exceeds 12,000,000 characters → error.
- URL exceeds 25,000 characters → error.
- URL is not publicly accessible → PDF generation fails.
TRIGGER_KEYWORDS:
zoho.file.convertToPDF, options_collection, 12000000 HTML chars, 25000 URL chars, 301 seconds, external-calls quota
RULES:
- Syntax: `zoho.file.convertToPDF(<content_or_url>, <options_collection>);`
- First parameter: HTML STRING or public URL STRING (mutually exclusive).
- `options_collection` (optional KEY-VALUE): 20+ layout parameters — see table below.
- Return type: FILE (PDF).
- Max HTML content: 12,000,000 characters.
- Max URL length: 25,000 characters.
- Max processing time: 301 seconds; conversion exceeding this → timeout error.
- Counts as one external API call per execution (deducted from external calls quota).
- Returned FILE supports setFileName() and other file operations.
- Key options_collection parameters:
- `scale` NUMBER (default 1), `landscape` BOOLEAN (default false).
- `top-margin`, `bottom-margin`, `left-margin`, `right-margin` NUMBER (default 0).
- `page-width` NUMBER (default 210), `page-height` NUMBER (default 297), `units` STRING (default "mm"; accepts px, in, cm, mm).
- `header`/`footer` STRING (HTML content for header/footer sections).
- `single-page-pdf` BOOLEAN (default false) — fits all content into one page.
- `print-background` BOOLEAN (default true) — includes background colors/images.
- `disable-javascript` BOOLEAN (default false).
- `page-ranges` STRING — e.g. "4-8" or "2,3,4" or "2, 4-5".
CANONICAL_EXAMPLES:
```deluge
// Convert HTML string
content = "<html><body>Page One <div style='page-break-after:always'></div> Page Two</body></html>";
file = zoho.file.convertToPDF(content);
info file.isFile(); // true
// Convert URL and rename output
file = zoho.file.convertToPDF("https://www.zoho.com/deluge/");
file.setFileName("DelugeHomepage");
// With custom layout options
params = collection();
params.insert("top-margin":"5");
params.insert("page-width":"3.3");
params.insert("single-page-pdf":true);
file = zoho.file.convertToPDF(content, params);
```
FAILURE_MODES:
- URL length > 25,000 characters → `{"Message":"Unable to process your request","details":{"param name":"url","Error":"More than maximum length","StatusCode":400}}`.
- HTML content > 12,000,000 characters → error.
- Processing exceeds 301 seconds → timeout error.
- Non-public URL → PDF generation fails (explicit error not documented in source).
- Excessive calls exhaust external-calls quota for the invoking service.
RELATED_DOCS:
- Get File Content — get-file-content.dou.md
- setFileName — set-file-name.dou.md
- File Methods — ../datatypes/../datatypes/file.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/file/convert-to-pdf.html
assets/functions/deluge/file-functions/extract.dou.md
FILE_PURPOSE: Deluge extract — unzip a cloud-fetched .zip FILE into a COLLECTION of named file objects
MENTAL_MODEL: extract = zip FILE → COLLECTION {filename TEXT : FILE}; empty COLLECTION if non-zip, empty, or password-protected
WHEN_TO_USE:
- Unzip a .zip file fetched from cloud and access individual files inside it.
- Iterate extracted files for upload, processing, or attachment.
WHEN_NOT_TO_USE:
- Zip is password-protected — extract cannot handle it; returns empty COLLECTION with no error.
- Output would exceed size limit (3 MB) — error.
TRIGGER_KEYWORDS:
extract, zip.extract, COLLECTION key-value extract, 3MB extract limit, password-protected zip
RULES:
- Syntax: `<variable> = <zip_file>.extract();`
- Return type: COLLECTION (key = filename as TEXT, value = FILE object).
- Non-zip input or empty zip → empty COLLECTION returned (no error thrown).
- Password-protected zip → empty COLLECTION returned (no error thrown).
- Size limit: 3 MB; exceeding limit throws an error.
CANONICAL_EXAMPLES:
```deluge
zip = invokeUrl[url:"https://example.com/sample.zip" type:GET];
file_collection = zip.extract();
// key is TEXT (filename), value is FILE object
// e.g.: {"sample.txt":"sample.txt"}
// Upload each extracted file to WorkDrive
for each file in file_collection {
zoho.workdrive.uploadFile(file, "folderID", file.getFileName(), true, "work");
}
```
FAILURE_MODES:
- Extracted content exceeds 3 MB → error.
- Password-protected zip → empty COLLECTION silently; no error raised.
RELATED_DOCS:
- File Methods — ../datatypes/../datatypes/file.dou.md
- compress — compress.dou.md
SOURCE:
https://www.zoho.com/deluge/help/functions/file/extract.html
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
## [0.0.2] — 2026-09-18
### Added
- Added `clientscript-event-type-matrix.dou.md` with a unified **Page → Type → Events** lookup for all 8 ClientScript page surfaces, including Type configuration sources and Field selectors.
- Added `(Type: ...)` annotations to all ClientScript event documentation.
### Fixed
- Corrected `clientscript-creation.dou.md` to reflect Page-specific Type options and clarify Commands, Wizard, and Subform Event behavior.
- Updated `references/clientscript.md` to require Type + Event in ClientScript responses.
- Restricted generated ClientScript code from using top-level `function` wrappers or module-level globals. Named functions must be defined in Static Resource `.js` files.
## [0.0.1] — 2026-08-24
### Added
- Initial release covering all 7 CRM developer primitives: REST API, Deluge Functions, ClientScript, Widgets, COQL, Queries Workbench, and Connections.
- Targets Zoho CRM API v8.0.
- 5-step reasoning flow for primitive selection, code generation, and validation.
- DOU node inventory for authoritative method signatures, parameters, and failure modes.
- Works with or without MCP tools connected.
README.md
# zoho-crm
> Zoho CRM v8.0 orchestration layer — primitive selection, code generation, and validation across all CRM developer tools: REST API, Deluge Functions, ClientScript, Widgets, COQL, Queries Workbench, and Connections.
This repository hosts the `SKILL.md` file for **zoho-crm**, a portable Agent Skill you can drop into any compatible harness (Claude Code, Codex CLI, Cursor, Claude Desktop, and other agents that support the `SKILL.md` convention).
> **Note:** This `README.md` is for human visitors browsing GitHub. It is separate from `SKILL.md`, which the agent actually loads — do not put installation instructions inside `SKILL.md` itself.
## What's inside
```
zoho-crm/
├── SKILL.md # Routing logic, primitive selection, and 5-step reasoning flow
├── references/ # Subskill files per primitive (api, functions, clientscript, widgets, coql, queries, connections)
└── assets/ # DOU nodes — authoritative method signatures, parameters, and failure modes
├── api/
├── functions/
├── clientscript/
├── widgets/
├── coql/
├── queries/
└── connections/
```
The current version is tracked in the YAML frontmatter of `SKILL.md`:
```yaml
---
name: zoho-crm
description: 'Zoho CRM v8.0 orchestration layer...'
metadata:
version: "0.0.1"
---
```
## Quick install (recommended)
The fastest way to install this skill is the community `skills` CLI — no `git clone` needed, it fetches the folder straight from GitHub and places it in the right directory for your agent:
```bash
npx skills add zoho/crm-skills --agent claude-code
```
Replace `claude-code` with the identifier of your preferred [supported agent](https://github.com/vercel-labs/skills#supported-agents). You can also use `--agent '*'` to install the skill for all supported agents.
Add `-g` to install it globally (available in every project) instead of just the current repo:
```bash
npx skills add zoho/crm-skills -g
```
Restart your agent session after adding a skill to ensure the new skill is discovered and available. Some agents may support live reloading of skills without a restart.
To remove this skill from all agents in the current project scope:
```bash
npx skills remove zoho-crm --all
```
## Upgrading with the CLI
Skills don't auto-update. Run either of these when you want the latest version:
```bash
# Update every skill installed via the CLI to its latest version
npx skills update
# Or re-add this one skill explicitly to force a refresh
npx skills add zoho/crm-skills --agent claude-code
```
If you're pinning versions for a team or CI environment, restore from the lockfile instead so everyone stays on the exact same version:
```bash
npx skills experimental_install
```
To check what's currently installed and its pinned version:
```bash
npx skills list
```
> **Tip:** `npx skills` can occasionally run a stale cached CLI version. If an update doesn't seem to take effect, force the latest CLI itself with `npx skills@latest update`.
## Checking your currently installed version
Whether you installed via the CLI or by hand, the source of truth is the `version` field in the installed `SKILL.md`'s frontmatter:
```bash
head -n 10 ~/.claude/skills/zoho-crm/SKILL.md
```
Compare it against the latest tag on this repo's [Releases page](https://github.com/zoho/crm-skills/releases).
## Manual install / upgrade (no Node.js required)
If Node.js isn't available, a skill is just a folder — plain git or a ZIP download both work.
**Option A — Re-clone and overwrite (recommended)**
```bash
git clone https://github.com/zoho/crm-skills.git /tmp/skill-update
rm -rf ~/.claude/skills/zoho-crm
cp -r /tmp/skill-update/zoho-crm ~/.claude/skills/zoho-crm
```
**Option B — Pull if you cloned the repo directly into your skills folder**
```bash
cd ~/.claude/skills/zoho-crm
git pull origin main
```
**Option C — Pin to a specific release tag** (safer for production — avoids picking up unreleased changes on `main`)
```bash
git clone --branch v0.0.1 https://github.com/zoho/crm-skills.git ~/.claude/skills/zoho-crm
```
Or use GitHub's **Code → Download ZIP** button and extract the `zoho-crm` folder directly into your agent's skills directory (see the path table below).
After any manual copy, restart your harness (or hot-reload if your harness supports it) so it picks up the updated `SKILL.md`.
## Skills directory by agent
| Agent | Personal / global path | Project path |
|---|---|---|
| Claude Code | `~/.claude/skills/` | `.claude/skills/` |
| Codex CLI | `~/.codex/skills/` | `.codex/skills/` |
| Cursor | `~/.cursor/skills/` | `.cursor/skills/` |
| Claude Desktop | Settings → Capabilities → Skills (upload) | n/a |
| GitHub Copilot / Windsurf / others | `~/.agents/skills/` | agent-specific subfolder (e.g. `.windsurf/skills/`) |
## Verifying the upgrade worked
1. Re-check the `version` field in the installed `SKILL.md` — it should match the version you just installed.
2. Ask your agent something that exercises the skill and confirm the new behavior is in effect.
3. Fully restart your agent session — most harnesses read skill metadata only once at startup, so a stale session is the most common reason an upgrade "doesn't take."
## Versioning policy
This project follows [semantic versioning](https://semver.org/):
- **Patch** (`0.0.1` → `0.0.2`) — wording / clarity fixes, no behavior change.
- **Minor** (`0.0.1` → `0.1.0`) — new capabilities, backward compatible.
- **Major** (`0.0.1` → `1.0.0`) — breaking changes to expected inputs or behavior; re-read the skill instructions before upgrading.
Check the [CHANGELOG](./CHANGELOG.md) before upgrading across a major version bump.
## Troubleshooting
- **Agent doesn't see the skill at all:** confirm the folder name is `zoho-crm` (matching the `name` field exactly) and that it sits directly under the agent's skills directory — not nested one level too deep, a common ZIP-extraction artifact.
- **Old behavior persists after upgrade:** fully restart your agent session; skill metadata is typically cached for the session.
- **`npx skills` installs an old version:** run `npx skills@latest add ...` to bypass a stale cached CLI version.
- **YAML parse errors:** validate that frontmatter is fenced by `---` on its own line at the very top and bottom of `SKILL.md`, with no tabs (YAML requires spaces).
SKILL.md
---
name: zoho-crm
description: 'Zoho CRM v8.0 orchestration layer — primitive selection, code generation, and validation across all CRM developer tools: REST API, Deluge Functions, ClientScript, Widgets, COQL, Queries Workbench, and Connections. Works whether or not MCP tools are connected. Trigger on "Zoho CRM", "Deluge script", "ClientScript", "COQL", "CRM widget", "zoho.crm.*", "invokeurl", "CRM automation", "CRM integration", "CRM query", "Queries Workbench", "Blueprint", "Workflow Rule", or any CRM module operation. Do NOT use for Catalyst, Zoho Creator, or other Zoho products — use the appropriate product skill instead.'
argument-hint: 'Describe your Zoho CRM use case (e.g. "fetch all open leads", "write a Deluge script to update a field", "create a ClientScript for a custom button")'
metadata:
version: "0.0.2"
---
# zoho-crm
## Purpose
Given any use case, this skill:
1. Selects the right primitive, loads the relevant DOU nodes / reference docs
2. Produces the solution (via MCP tools if connected, or as exact payloads) and validates the output
## When to Use
| Use case | Subskill |
|----------|----------|
| Read, create, update, delete CRM records via HTTP | [API](./references/api.md) |
| Write server-side automation / custom functions | [Functions / Deluge](./references/functions.md) |
| Add UI logic to record pages / list views | [ClientScript](./references/clientscript.md) |
| Build embedded UI components | [Widgets](./references/widgets.md) |
| Build a saved query bound to a Canvas view, Kiosk, or Custom Related List (UI-authored in Workbench) | [Queries](./references/queries.md) |
| Query CRM data using COQL (CRM Object Query Language) | [COQL](./references/coql.md) |
| Connect to external services via OAuth | [Connections](./references/connections.md) |
## Step-by-Step Reasoning Flow
### Step 1 — Understand the Goal
| Dimension | Extract | Examples |
|---|---|---|
| Action | What should happen? | fetch, create, update, trigger, display, query |
| Entity | Which CRM module or object? | Leads, Contacts, Deals, custom modules |
| Context | Where does this run? | server-side, UI page, scheduled, event-triggered |
> **Gate**: If any dimension above cannot be unambiguously inferred from a single reasonable interpretation — stop and ask before proceeding to Step 2. Ask only what is necessary; omit optional parameters with clear defaults.
### Step 2 — Select the Right Primitive
Use the **decision matrix** below as the primary routing source. If the use case doesn't match any row, apply the layer rule: **server-side** (no browser access needed) → Function / REST API / COQL / Queries; **browser-side** → Widget / Client Script.
#### Decision matrix — need → use
**Server-side**
| Need | Use |
|---|---|
| Run Deluge backend code in CRM — process records, perform CRUD operations, call Zoho CRM APIs, send emails/SMS, or execute any server-side business logic | **Function** — trigger via Workflow Rule, Blueprint, Schedule, Button, Validation Rule, or Serverless Endpoint |
| Call an external (non-Zoho) API and write the result to CRM | **Function** via Workflow Rule or Button (use a named Connection for auth) |
| Expose custom Deluge logic as REST API for external systems | **Function** via Serverless Endpoint |
| Run a cron job inside CRM | **Function** via Scheduled Function |
| Convert a Custom Related List to a Function | **Function** via `related_list.*` category — the only category that renders data directly on a record detail page; do not clarify unless a different trigger type (button, workflow, API endpoint) is explicitly stated |
| Read / create / update / delete CRM records, or any CRM-specific operation, from an external system or script | **REST API** |
| Fetch CRM data programmatically from Deluge, external API callers, or ad-hoc code | **COQL** |
| Display query-driven data in a Canvas, Kiosk, or Custom Related List | **Queries** (build in Workbench UI — `Setup → Developer Hub → Queries`) |
| Authenticate to an external OAuth service from a Function or ClientScript | **Connections** (create in `Setup → Developer Hub → Connections`; reference by link name in `invokeurl` or `ZDK.Apps.CRM.Connection.invoke`) |
**Browser-side**
| Need | Use |
|---|---|
| Render or embed custom HTML or a third-party UI component inside CRM | **Widget** |
| Hide a field based on another field's value, or any other web UI customization on a record page | **Client Script** |
| Validate field input before saving a record in CRM web UI | **Client Script** (instant UI feedback) or **Function** via Validation Rule (server-side enforcement) — default to **Client Script** unless the user explicitly requires server-side enforcement |
| Show a confirmation dialog before a button action in web UI | **Client Script** (dialog) + **Function** (backend action) |
> **Tiebreaker**: Default to the first listed option; override only if the user's context explicitly favors the second.
> **Check**: Confirm the selected primitive matches Action + Entity + Context from Step 1. If not — re-check the matrix or loop back to clarification.
> **Universal constraint**: Use API names for all module and field references — never display labels.
## Authoritative Sources Hierarchy
**When in doubt about syntax, signatures, or limits, consult sources in this order** (not by preference or recollection):
1. **DOU Nodes** (`assets/`) — Current method signatures, parameters, return types, and critical failure modes. **Use this first for code generation.**
2. **Official Zoho Public Docs** (zoho.com/deluge/help/, zoho.com/crm/api/) — When a DOU node is partial or missing.
3. **Reference files + inline examples** (api.md, functions.md, etc.) — Curated summaries; may lag behind DOU or official docs.
4. **Recalled knowledge** — Lowest trust; always cross-check against DOU.
> **Critical Rule**: Reference files are curated summaries, not authoritative. DOU nodes are the ground truth. If a reference file example conflicts with a DOU node, the DOU node wins.
### Step 3 — Load the Reference for the Selected Primitive
Open the reference file for the primitive matched in the "When to Use" table above and follow its **Execution Instructions**. (Special case: if the primitive is **Queries** with source type COQL, also open [references/coql.md](./references/coql.md).)
> If a reference file is inaccessible, inform the user, state which file is missing, and do not generate code that depends on its contents.
### Steps 4–5 — Execute and Validate
Follow the **Execution Instructions** in the reference file from Step 3. After presenting the solution, run its **Validation Checklist**; surface errors with plain-language explanations and suggest follow-up steps if the goal is only partially met.
## Auth & Scope Reference
**Always derive scopes from the OAS spec file** (per [api.md](./references/api.md) Step 6, `security` field at operation level). For all standard scopes, use the OAS spec — the table below covers the non-obvious cases only.
| Scope | Note |
|-------|------|
| `ZohoCRM.coql.READ` and `ZohoCRM.modules.{module}.READ` | COQL queries — **two separate scopes required**; `coql.READ` alone → `OAUTH_SCOPE_MISMATCH` |
Base URL: `https://www.zohoapis.com/crm/v8/`