How Does Twenty CRM Integrate with Zapier: A Complete Technical Guide
Twenty CRM integrates with Zapier through a dedicated Node.js package (packages/twenty-zapier) that exposes webhook-based triggers for record changes and GraphQL-powered CRUD actions, authenticating via API keys to enable automation with 3,000+ apps.
The twentyhq/twenty repository ships a standalone Zapier integration that connects the open-source CRM to Zapier's ecosystem without modifying the core application. This integration uses the Zapier Platform Core SDK to translate Twenty's GraphQL API into triggers and actions that non-technical users can configure in Zaps, while developers maintain full control via standard CLI commands.
Core Architecture and Package Structure
The integration lives in packages/twenty-zapier and operates independently from Twenty's frontend and backend services. After building the package with yarn build, developers deploy it using standard Zapier CLI commands (zapier login, zapier push).
Key architectural components include:
src/index.ts— The package entry point that exports the version, authentication module, triggers, and creates to the Zapier CLIsrc/authentication.ts— Implements custom API-key authentication with optional self-hosted URL supportsrc/triggers/trigger_record.ts— Defines webhook-based triggers for record lifecycle eventssrc/creates/crud_record.ts— Provides Create, Update, and Delete actions with dynamic field mappingsrc/utils/requestDb.ts— A unified request wrapper that handles GraphQL queries, metadata fetching, and REST calls to Twenty's API
Authentication and Connection Setup
Twenty implements custom authentication in src/authentication.ts that accepts an API key and optional self-hosted instance URL. When a user connects their Twenty account in Zapier, the integration verifies the credentials by executing a lightweight GraphQL query against the currentWorkspace endpoint.
The authentication configuration stores the API key as a Bearer token and constructs the base URL from either the user-provided apiUrl or the default SERVER_BASE_URL environment variable:
// src/authentication.ts
export default {
type: 'custom',
test: async (z, bundle) => {
// Verify the API key by querying the current workspace
return await requestDb({
z,
bundle,
query: 'query currentWorkspace {currentWorkspace {id displayName}}',
endpoint: 'metadata',
});
},
fields: [
{
key: 'apiKey',
required: true,
label: 'Api Key',
type: 'string',
helpText:
'Create an API key in your Twenty workspace (Settings → APIs).',
},
{
key: 'apiUrl',
required: false,
label: 'Self‑hosted instance URL',
type: 'string',
placeholder: 'https://crm.custom-url.com',
helpText: 'Set this only if you self‑host Twenty.',
},
],
connectionLabel: '{{data.currentWorkspace.displayName}}',
};
The connectionLabel dynamically displays the workspace name in Zapier's UI, ensuring users can distinguish between multiple Twenty connections.
Real-Time Triggers via Webhooks
Unlike polling-based integrations, Twenty uses REST Hooks (webhooks) for real-time event delivery. When a user enables a trigger, Zapier registers a unique webhook URL with Twenty's backend via the call-webhook-jobs.job.ts service. Twenty then pushes record change events directly to Zapier as they occur.
The trigger implementation in src/triggers/trigger_record.ts supports four operation types: CREATED, UPDATED, DELETED, and DESTROYED. The operation field allows users to filter which events should start their Zap:
// src/triggers/trigger_record.ts
export default {
key: 'trigger_record',
noun: 'Record',
display: {
label: 'Record Trigger',
description: 'Triggers when a Record is created, updated, deleted or destroyed.',
},
operation: {
inputFields: [
{
key: 'nameSingular',
required: true,
label: 'Record Name',
dynamic: `${findObjectNamesSingularKey}.nameSingular.labelSingular`,
altersDynamicFields: true,
},
{
key: 'operation',
required: true,
label: 'Operation',
choices: { CREATED: 'CREATED', UPDATED: 'UPDATED', DELETED: 'DELETED', DESTROYED: 'DESTROYED' },
altersDynamicFields: true,
},
],
type: 'hook',
performSubscribe,
performUnsubscribe,
perform,
performList,
sample: { id: 'f75f6b2e-9442-4c72-aa95-47d8e5ec8cb3', createdAt: '2023-10-19T07:37:25.306Z' },
outputFields: [{ key: 'id', label: 'ID' }, { key: 'createdAt', label: 'Created At' }],
},
};
The dynamic property on the nameSingular field fetches available object types from Twenty's metadata API at runtime, ensuring the dropdown always reflects the current workspace schema.
CRUD Actions and Dynamic Schema
Twenty exposes Create, Update, and Delete operations through a single configurable action defined in src/creates/crud_record.ts. Rather than hardcoding fields for specific objects, the integration uses dynamic field generation to present form inputs based on the selected record type and operation.
The computeFields function (referenced in the operation configuration) introspects the workspace schema via requestSchema to build GraphQL mutations with the correct input variables:
// src/creates/crud_record.ts
export default {
key: 'crud_record',
noun: 'Record',
display: {
label: 'Create, Update or Delete Record',
description: 'Create, Update or Delete a Record in Twenty.',
},
operation: {
inputFields: [
{
key: 'nameSingular',
required: true,
label: 'Record Name',
dynamic: `${findObjectNamesSingularKey}.nameSingular.labelSingular`,
altersDynamicFields: true,
},
{
key: 'crudZapierOperation',
required: true,
label: 'Operation',
choices: {
CREATED: 'CREATED',
UPDATED: 'UPDATED',
DELETED: 'DELETED',
},
altersDynamicFields: true,
},
// Dynamic fields based on selected operation are computed at runtime
computeFields,
],
perform,
sample: { id: '179ed459-79cf-41d9-ab85-96397fa8e936' },
},
};
When a user selects "CREATED" or "UPDATED", the integration renders input fields matching the target object's schema. For "DELETED" operations, it typically requires only the record ID.
The Request Layer: Connecting to Twenty's API
All network communication flows through src/utils/requestDb.ts, a centralized request wrapper that standardizes headers, error handling, and endpoint selection. This utility supports three endpoints: /graphql, /metadata, and /rest, defaulting to GraphQL for most operations.
The function automatically injects the Bearer token from bundle.authData.apiKey and parses GraphQL error responses into Zapier-friendly exceptions:
// src/utils/requestDb.ts
export const requestDb = async ({ z, bundle, query, endpoint = 'graphql' }) => {
const options = {
url: `${bundle.authData.apiUrl || process.env.SERVER_BASE_URL}/${endpoint}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${bundle.authData.apiKey}`,
},
body: { query },
};
return z.request(options).then(resp => {
const result = resp.json;
if (result.errors) {
throw new z.errors.Error(`GraphQL error: ${JSON.stringify(result.errors)}`);
}
resp.throwForStatus();
return result;
});
};
This layer ensures consistent error handling across triggers and actions, converting HTTP status codes and GraphQL validation errors into actionable messages within the Zapier UI.
Data Flow and Webhook Lifecycle
The complete integration flow follows this sequence when a user activates a Twenty trigger in a Zap:
- Authentication Verification — Zapier calls
authentication.test, which executesrequestDbwith acurrentWorkspacequery to validate the API key before enabling the Zap. - Webhook Registration — When the Zap is turned on, Zapier invokes
performSubscribeintrigger_record.ts, which registers a webhook URL with Twenty's backend jobcall-webhook-jobs.job.ts. - Event Delivery — When a record changes in Twenty, the backend posts the event payload to the registered Zapier webhook URL.
- Data Processing — Zapier receives the payload and executes the trigger's
performorperformListmethod, returning normalized data to subsequent Zap steps. - CRUD Execution — For action steps, Zapier builds a GraphQL mutation via
requestDb, sending a POST request to/graphqlwith the user-supplied field values.
Summary
- Twenty CRM integrates with Zapier via the standalone
packages/twenty-zapierpackage using the Zapier Platform Core SDK. - Authentication uses API keys passed as Bearer tokens, with support for self-hosted Twenty instances via configurable base URLs.
- Triggers are webhook-based (not polling), registering with Twenty's
call-webhook-jobs.job.tsbackend service to receive real-time record change events for create, update, delete, and destroy operations. - Actions support full CRUD operations on any Twenty object, using dynamic field generation to stay synchronized with workspace schemas.
- All API calls route through
src/utils/requestDb.ts, which wrapsz.requestto handle GraphQL queries, metadata fetching, and error normalization.
Frequently Asked Questions
Does Twenty CRM require a paid Zapier plan to integrate?
No, the Twenty CRM integration works with Zapier's free plan, though webhook-based triggers (used by Twenty) require Zapier's "Starter" tier or higher to run in live Zaps. You can build and test Zaps with Twenty on any plan, but automated execution of webhook triggers follows Zapier's standard pricing tiers for multi-step Zaps.
Can I use the Twenty Zapier integration with a self-hosted instance?
Yes, the integration explicitly supports self-hosted Twenty instances. During authentication, users can provide a custom apiUrl pointing to their self-hosted domain (e.g., https://crm.company.com). The requestDb utility then routes all GraphQL and metadata requests to this custom endpoint instead of Twenty's cloud service.
What triggers are available in the Twenty CRM Zapier app?
The integration provides a single Record Trigger that covers four operation types: CREATED, UPDATED, DELETED, and DESTROYED. Users configure the specific record type (e.g., Company, Person, Custom Object) and operation within the Zap setup. The trigger uses dynamic dropdowns populated from the workspace's metadata API to ensure only valid object types appear.
How does the integration handle API authentication securely?
Twenty's Zapier app uses API key authentication stored as a Bearer token in the Authorization header. The keys are encrypted at rest by Zapier's infrastructure and only transmitted over HTTPS to the configured Twenty instance. The integration never logs or exposes the API key in output data, and the authentication.test function validates the key's permissions without storing additional session data.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →