How to Integrate Custom Logic into Twenty CRM Workflows: A Complete Guide to Code Steps

You can integrate custom logic into Twenty CRM workflows by creating Code Steps—sandboxed JavaScript/TypeScript snippets that execute within the Workflow Runner and interact with CRM data via injected services like recordService and emailService.

Twenty CRM (twentyhq/twenty) provides a flexible workflow engine that lets you extend automation beyond standard actions. By leveraging Code Steps in workflow versions, you can inject custom business logic directly into the execution pipeline without redeploying the server. This guide walks through the exact GraphQL mutations, source file locations, and service APIs needed to implement custom logic safely within Twenty's sandboxed environment.

Understanding the Twenty CRM Workflow Engine

The workflow system in twentyhq/twenty rests on three core entities defined in the server's standard objects layer:

The Workflow Runner (packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts) orchestrates execution by iterating over steps and invoking the compiled code inside a sandboxed VM.

Step 1: Create a Workflow Version

Before adding logic, you need a workflow version to hold your steps. Use the GraphQL mutation defined in packages/twenty-server/src/modules/workflow/workflow-version/workflow-version.resolver.ts:

mutation CreateWorkflowVersion($workflowId: ID!, $name: String!) {
  createWorkflowVersion(input: {
    workflowId: $workflowId
    name: $name
    trigger: { type: MANUAL }
  }) {
    id
    name
  }
}

Retain the returned id as your workflowVersionId for the next steps.

Step 2: Add a Code Step with Custom Logic

A Code Step stores executable JavaScript or TypeScript in its source field. When processed, the CodeStepBuildService (packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts) compiles and validates your script, storing the bundle within the version record.

GraphQL mutation to create a code step

mutation AddCodeStep($workflowVersionId: ID!, $name: String!, $source: String!) {
  createWorkflowVersionStep(input: {
    workflowVersionId: $workflowVersionId
    type: CODE
    name: $name
    source: $source
  }) {
    id
    type
  }
}

Example custom logic script

The source field must export an async run function that receives a context object containing payload, record, workflowRun, and services:

export async function run({ payload, services }) {
  // Access the record service to query CRM data
  const contact = await services.recordService.findOne('contact', payload.contactId);
  
  // Implement custom business logic
  if (contact.title === 'CEO') {
    await services.recordService.update('deal', payload.dealId, {
      priority: 'high'
    });
  }
  
  // Return data accessible to subsequent steps
  return { wasUpdated: true };
}

When the workflow triggers, the WorkflowTriggerWorkspaceService (packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts) prepares the context and invokes the compiled code inside a vm2 sandbox.

Step 3: Configure a Trigger to Execute the Workflow

Workflows launch via manual invocation, scheduled cron jobs, or automated database events. To react to record changes automatically, register an automated trigger using the AutomatedTriggerWorkspaceService (packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service.ts):

mutation CreateAutomatedTrigger($workflowVersionId: ID!, $eventName: String!) {
  createAutomatedTrigger(input: {
    type: DATABASE_EVENT
    workflowVersionId: $workflowVersionId
    settings: {
      eventName: $eventName
    }
  }) {
    id
  }
}

Replace $eventName with events like contact.created or deal.updated. The service registers a listener that instantiates a Workflow Run whenever the event fires.

Step 4: Publish and Debug Your Workflow

Workflow versions remain inactive until published. Activate your logic with the publishWorkflowVersion mutation, which triggers WorkflowTriggerWorkspaceService.enableAutomatedTrigger:

mutation PublishWorkflowVersion($workflowVersionId: ID!) {
  publishWorkflowVersion(input: { workflowVersionId: $workflowVersionId }) {
    id
    status
  }
}

Debugging execution

Run the workflow and inspect results via the CLI:

yarn nx run twenty-server:command workflow:run --runId <run-id>

The Workflow Runner executes each step sequentially, capturing returns and errors on the Workflow Run entity. Standard view fields generated by compute-standard-workflow-run-view-fields.util.ts (packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-workflow-run-view-fields.util.ts) expose status, startedAt, endedAt, and related workflow metadata in the UI.

Step 5: Leverage Workspace Services for Advanced Logic

Twenty exposes typed workspace services to Code Steps through the services argument, defined in packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts:

  • recordService – Perform CRUD operations (findOne, create, update, delete) on any standard or custom object.
  • emailService – Send templated emails via the send method.
  • integrationService – Execute calls to external APIs registered in the workspace.
  • workflowService – Trigger child workflows programmatically using run.

Example: Sending notifications based on record state

export async function run({ payload, services }) {
  const { recordService, emailService } = services;
  
  const deal = await recordService.findOne('deal', payload.dealId);
  
  if (deal.stage === 'won') {
    await emailService.send({
      to: deal.ownerEmail,
      template: 'deal-won',
      variables: { dealName: deal.name }
    });
  }
}

These services provide full IntelliSense and type safety, ensuring your custom logic interacts correctly with the CRM data layer.

Summary

  • Workflow architecture relies on three entities—Workflow, Workflow Version, and Workflow Run—managed by the Workflow Runner.
  • Code Steps accept raw JavaScript/TypeScript in the source field, compiled by CodeStepBuildService and executed in a sandboxed VM.
  • Context injection provides payload, record, and typed services (including recordService and emailService) to your logic.
  • Triggers can be manual, scheduled, or automated via AutomatedTriggerWorkspaceService listening to database events.
  • Publishing via publishWorkflowVersion activates the version and enables automated triggers.
  • Debugging is supported through CLI commands and standard view fields that expose run metadata and execution output.

Frequently Asked Questions

What programming language do Code Steps use?

Code Steps accept JavaScript or TypeScript code stored in the source field of a workflow step. The CodeStepBuildService compiles TypeScript automatically, allowing you to use modern syntax and type annotations even though the code executes inside a sandboxed Node.js VM at runtime.

Is the custom logic execution secure?

Yes. Twenty CRM executes Code Steps inside a sandboxed VM using vm2, which isolates the script from the host server environment. The only permitted external interactions occur through the explicitly injected services object (e.g., recordService, emailService), preventing unauthorized system access or code injection vulnerabilities.

Can I call external APIs from a Code Step?

Yes. Use the integrationService available in the services argument to call external APIs registered within your workspace. This service manages authentication and request handling securely, ensuring outbound calls comply with your configured integration settings without exposing raw network access to the sandboxed script.

How do I debug a failed workflow run?

You can debug runs using the CLI command yarn nx run twenty-server:command workflow:run --runId <run-id>. Additionally, the Workflow Run entity stores execution status, error messages, and returned payloads, all viewable in the Twenty CRM UI through standard view fields generated by compute-standard-workflow-run-view-fields.util.ts. Check the metadata field for custom outputs returned by your Code Steps.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →