Clarify API
Guides

Build a Workflow

Run JavaScript on CRM events with the Clarify SDK

Workflows are available on request. Contact support@clarify.ai to enable them for your workspace.

A workflow runs a sequence of steps when something happens in your CRM. It starts with a trigger (a record being created, updated, or deleted, added to or removed from a list, an inbound webhook, a schedule, and more) and then runs its steps in order.

For a simple, linear automation you can chain no-code action blocks in the builder: create a record, update a record, send an email, wait, or call an outbound webhook. When a workflow needs real logic (conditionals, loops, validation, custom error handling, or several related reads and writes), reach for the Execute code step and write that logic in JavaScript. A common example is validation: when a deal moves to a new stage, check that the required fields are filled in and flag the record if they aren't.

Choose your workflow structure

No-code action blocks are the right fit when the workflow is short and sequential: a trigger, a handful of actions, and no branching beyond a trigger filter.

For anything more involved, a single Execute code step is usually the clearest way to structure a workflow. Keeping the logic in one place means the run logs point to one step, you can test it as a unit, and branching, loops, and error handling live in code rather than spread across a long chain of blocks. Action blocks have no per-block conditional gating: use trigger filters to decide whether a run starts, and Execute code for any branching after it does.

Use Execute code for workflow logic

Your code is an exported async function with a fixed signature:

export async function handler(input, { clarify, _, workflowContext }) {
  // your code
  return result;
}

Clarify passes four values to your handler: your step's input, plus a context object holding the rest.

ArgumentDescription
inputThe step's own configured input
clarifyA pre-authenticated Clarify SDK client for reading and writing records
_lodash, for convenience
workflowContextThe trigger data and the outputs of earlier steps

Whatever you return is stored as the step's result and is available to later steps. To fail the run, throw an error. The message shows up in the run logs.

Accessing the triggering record

The event that started the run lives on workflowContext.trigger.event:

PathDescription
workflowContext.trigger.event.object.dataThe record that triggered the run (fields read directly, e.g. .amount)
workflowContext.trigger.event.object._idThe triggering record's ID
workflowContext.trigger.event.actorWho made the change: { _id, entity, data }
workflowContext.trigger.event.typeThe trigger event type

To read outputs from an earlier step, use its ID: workflowContext.blocks['my-step'].result.

Example: validate on stage change

This step checks that a deal has the required fields when it enters Closed Won, and writes a plain-language error back to the record if not.

export async function handler(input, { clarify, workflowContext }) {
  const deal = workflowContext.trigger.event.object.data;

  // Only act when the deal is in "Closed Won".
  if (deal.stage !== "closed_won") {
    return { skipped: true };
  }

  const missing = [];
  if (!deal.amount) missing.push("amount");
  if (!deal.close_date) missing.push("close date");
  if (!deal.company_id) missing.push("company");

  const validationError = missing.length
    ? `Cannot close: missing ${missing.join(", ")}`
    : "";

  await clarify.records.update("deal", deal._id, {
    validation_error: validationError,
  });

  return { validationError };
}

A workflow won't re-trigger itself: Clarify skips the run when the triggering change was made by that same workflow, so writing back to the record you're processing is safe. This only covers self-triggering. If your write fires a second workflow whose write fires this one again, that cross-workflow cycle isn't prevented. Watch for it when chaining workflows.

Clarify SDK

The clarify client is already authenticated for your workspace. Don't read an API key or construct a client yourself. Use it for all CRM access; a raw fetch to the Clarify API bypasses authentication and pagination handling.

Read with clarify.resources (responses resolve relationships):

const response = await clarify.resources.list({
  type: "company",
  filter: { industry: "technology" },
  page: { limit: 50, offset: 0 },
});
const companies = response.data; // each has .id and .attributes

Write with clarify.records (create, update, delete, and their createBulk / updateBulk / deleteBulk counterparts):

// Read one record by ID — fields are under .attributes
const { data } = await clarify.records.get("deal", dealId);
const amount = data.attributes.amount;

// Create
await clarify.records.create("task", {
  name: "Follow up",
  due_date: "2026-01-31",
});

// Update (partial — send only the fields you want to change)
await clarify.records.update("deal", dealId, { stage: "negotiation" });

Trigger data (workflowContext.trigger.event.object.data) exposes fields directly (deal.amount), but SDK responses nest them under .attributes (response.data.attributes.amount). Entity slugs are always lowercase: deal, company, person, or a custom object's slug.

Runtime and limits

Code steps run in an isolated sandbox with a few constraints worth knowing:

  • Runtime: Node.js, with a 60-second budget per run. Prefer a few bulk SDK calls over many small ones, and avoid reading a related record per row in a loop; resolve relationships in one clarify.resources.list call instead.
  • Dependencies: only lodash (injected as _) and the clarify SDK are available. import other npm packages and the step fails when it runs. Use the global Date for date math.
  • Return value: must be JSON-serializable. Convert a Date to a string (date.toISOString()) and avoid Map, Set, and class instances.
  • Failure: throw to fail the run with a message in the logs. Return { error: '...' } instead when the failure is expected: the step still succeeds, and you handle the outcome in code (action blocks can't branch on it).

Tips

  • Keep the bulk of your logic in one Execute code step: it's easier to test and debug than the same behavior split across a chain of action blocks.
  • Test against a single record (trigger a manual run) before enabling the workflow for live events.
  • If a run fails, open its logs and check each step's state to see where the data or the code went wrong.
  • Double-check that your trigger filters match exactly the records you intend to process.

On this page