CRM Integration Type Definitions
Source files:
lib/types/twenty-crm-config.types.ts- Configuration and connection typeslib/types/twenty-crm-entities.types.ts- Person and Company entity typeslib/types/twenty-crm-errors.types.ts- Error codes, structured errors, and type guardslib/types/twenty-crm-sync.types.ts- Upsert operations, cache entries, and sync options
The template integrates with Twenty CRM, an open-source CRM platform. These types define the complete interface for configuration, entity mapping, error handling, and synchronization operations.
Configuration Types (twenty-crm-config.types.ts)
TwentyCrmSyncMode
Sync mode options for the CRM integration:
type TwentyCrmSyncMode = 'disabled' | 'platform' | 'direct_crm';
| Mode | Description |
|---|---|
disabled | CRM sync is turned off |
platform | Sync through the platform's event system |
direct_crm | Direct API calls to Twenty CRM |
TwentyCrmConfig
Full CRM configuration stored in the database:
interface TwentyCrmConfig {
id: string;
baseUrl: string;
apiKey: string;
enabled: boolean;
syncMode: TwentyCrmSyncMode;
createdBy: string | null;
updatedBy: string | null;
createdAt: Date;
updatedAt: Date;
}
TwentyCrmConfigResponse
Configuration response with a masked API key for safe transmission to the admin UI:
interface TwentyCrmConfigResponse {
id: string;
baseUrl: string;
apiKey: string; // Masked value (e.g., "****key123")
enabled: boolean;
syncMode: TwentyCrmSyncMode;
updatedBy: string | null;
updatedAt: Date;
}
UpdateTwentyCrmConfigRequest
Request payload for creating or updating the CRM configuration:
interface UpdateTwentyCrmConfigRequest {
baseUrl: string;
apiKey: string;
enabled: boolean;
syncMode: TwentyCrmSyncMode;
}
TwentyCrmEnvConfig
Configuration from environment variables (used as defaults):
interface TwentyCrmEnvConfig {
baseUrl?: string;
apiKey?: string;
enabled: boolean;
syncMode: TwentyCrmSyncMode;
}
TwentyCrmTestConnectionResult
Result from testing the CRM connection:
interface TwentyCrmTestConnectionResult {
ok: boolean;
latencyMs: number;
message: string;
details?: {
status?: number;
error?: string;
};
}
TwentyCrmClientConfig
Configuration for the REST client that communicates with Twenty CRM:
interface TwentyCrmClientConfig {
baseUrl: string;
apiKey: string;
timeout?: number;
maxRetries?: number;
initialBackoffMs?: number;
maxBackoffMs?: number;
}
Generic Response Types
interface TwentyCrmSuccessResponse<T> {
success: true;
data: T;
}
interface TwentyCrmErrorResponse {
success: false;
error: TwentyCrmError;
}
type TwentyCrmResponse<T> =
TwentyCrmSuccessResponse<T> | TwentyCrmErrorResponse;
Entity Types (twenty-crm-entities.types.ts)
TwentyPerson
Represents a contact/person in Twenty CRM:
interface TwentyPerson {
external_id: string; // Maps to local clientProfile.id
name: string;
email: string;
phone?: string | null;
job_title?: string | null;
company_name?: string | null;
website?: string | null;
city?: string | null;
account_type?: string | null; // individual, business, enterprise
plan?: string | null; // free, standard, premium
total_submissions?: number | null;
}
TwentyCompany
Represents a company/organization in Twenty CRM:
interface TwentyCompany {
external_id: string; // Maps to local company.id
name: string;
domain_name?: string | null; // e.g., example.com
website?: string | null;
status?: string | null; // active, inactive
}
Error Types (twenty-crm-errors.types.ts)
TwentyCrmErrorCode
Error codes enum for categorizing CRM API errors:
enum TwentyCrmErrorCode {
TIMEOUT = 'TIMEOUT',
RATE_LIMIT = 'RATE_LIMIT',
SERVER_ERROR = 'SERVER_ERROR',
AUTH_ERROR = 'AUTH_ERROR',
NETWORK_ERROR = 'NETWORK_ERROR',
VALIDATION_ERROR = 'VALIDATION_ERROR',
NOT_FOUND = 'NOT_FOUND',
OPERATION_FAILED = 'OPERATION_FAILED',
UNKNOWN = 'UNKNOWN',
}
TwentyCrmError
Structured error for CRM API interactions:
interface TwentyCrmError {
code: TwentyCrmErrorCode;
message: string;
status?: number;
isRetryable: boolean;
details?: {
originalError?: string;
attempt?: number;
maxRetries?: number;
[key: string]: unknown;
};
}
| Field | Type | Description |
|---|---|---|
code | TwentyCrmErrorCode | Machine-readable error classification |
message | string | Human-readable error description |
status | number? | HTTP status code (if applicable) |
isRetryable | boolean | Whether the operation can be retried |
details | object? | Additional context including retry info |
Error Functions
isTwentyCrmError
Type guard to check if an unknown value is a TwentyCrmError:
function isTwentyCrmError(error: unknown): error is TwentyCrmError;
createTwentyCrmError
Factory function to create a TwentyCrmError:
function createTwentyCrmError(
code: TwentyCrmErrorCode,
message: string,
options?: {
status?: number;
isRetryable?: boolean;
details?: TwentyCrmError['details'];
}
): TwentyCrmError;
Sync Types (twenty-crm-sync.types.ts)
UpsertResult<T>
Generic result of an upsert (create or update) operation:
interface UpsertResult<T> {
id: string; // CRM UUID assigned by Twenty CRM
externalId: string; // External ID from our system
created: boolean; // True if a new record was created
updated: boolean; // True for both create and update operations
data: T; // Full entity data from the CRM
}
UpsertCompanyInput
Input for upserting a company record:
interface UpsertCompanyInput {
external_id: string;
name: string;
domain_name?: string | null;
website?: string | null;
status?: string | null;
}
UpsertPersonInput
Input for upserting a person/contact record:
interface UpsertPersonInput {
external_id: string;
name: string;
email: string;
phone?: string | null;
job_title?: string | null;
company_name?: string | null;
website?: string | null;
city?: string | null;
account_type?: string | null;
plan?: string | null;
total_submissions?: number | null;
company_external_id?: string | null; // Links person to company
}
Cache Types
CacheEntry
Base cache entry for mapping external IDs to CRM IDs:
interface CacheEntry {
crmId: string; // CRM UUID from Twenty CRM
externalId: string; // External ID from our system
cachedAt: number; // Timestamp (ms since epoch)
}
PersonCacheEntry
Cache entry for Person entities:
interface PersonCacheEntry extends CacheEntry {
type: 'person';
}
CompanyCacheEntry
Cache entry for Company entities:
interface CompanyCacheEntry extends CacheEntry {
type: 'company';
}
AnyCacheEntry
Union of all cache entry types:
type AnyCacheEntry = PersonCacheEntry | CompanyCacheEntry;
UpsertOptions
Options for controlling upsert behavior:
interface UpsertOptions {
useCache?: boolean; // Use cache for lookups (default: true)
maxConflictRetries?: number; // Max retries for 409 conflicts (default: 3)
conflictRetryDelay?: number; // Delay between retries in ms (default: 100)
}
Usage Examples
Testing CRM connection
import type { TwentyCrmTestConnectionResult } from '@/lib/types/twenty-crm-config.types';
async function testConnection(): Promise<TwentyCrmTestConnectionResult> {
const res = await fetch('/api/admin/crm/test-connection', {
method: 'POST',
});
return res.json();
}
const result = await testConnection();
if (result.ok) {
console.log(`Connected in ${result.latencyMs}ms`);
} else {
console.error(`Connection failed: ${result.message}`);
}
Upserting a person
import type { UpsertPersonInput, UpsertOptions } from '@/lib/types/twenty-crm-sync.types';
const personInput: UpsertPersonInput = {
external_id: 'client-profile-uuid',
name: 'Jane Smith',
email: 'jane@example.com',
job_title: 'Product Manager',
company_name: 'Acme Corp',
account_type: 'business',
plan: 'premium',
total_submissions: 5,
company_external_id: 'company-uuid',
};
const options: UpsertOptions = {
useCache: true,
maxConflictRetries: 3,
conflictRetryDelay: 100,
};
Handling CRM errors
import {
TwentyCrmErrorCode,
isTwentyCrmError,
createTwentyCrmError,
} from '@/lib/types/twenty-crm-errors.types';
import type { TwentyCrmError } from '@/lib/types/twenty-crm-errors.types';
function handleCrmError(error: unknown) {
if (isTwentyCrmError(error)) {
switch (error.code) {
case TwentyCrmErrorCode.AUTH_ERROR:
console.error('Authentication failed - check API key');
break;
case TwentyCrmErrorCode.RATE_LIMIT:
console.warn('Rate limited - will retry');
break;
case TwentyCrmErrorCode.TIMEOUT:
if (error.isRetryable) {
console.warn('Timeout - retrying...');
}
break;
default:
console.error(`CRM error: ${error.message}`);
}
}
}
Using the generic response type
import type {
TwentyCrmResponse,
} from '@/lib/types/twenty-crm-config.types';
import type { TwentyPerson } from '@/lib/types/twenty-crm-entities.types';
function handlePersonResponse(
response: TwentyCrmResponse<TwentyPerson>
) {
if (response.success) {
console.log('Person synced:', response.data.name);
} else {
console.error(
`Sync failed [${response.error.code}]: ${response.error.message}`
);
if (response.error.isRetryable) {
// Schedule retry
}
}
}
Configuring the CRM client
import type { TwentyCrmClientConfig } from '@/lib/types/twenty-crm-config.types';
const clientConfig: TwentyCrmClientConfig = {
baseUrl: 'https://crm.example.com',
apiKey: 'your-api-key',
timeout: 10000,
maxRetries: 3,
initialBackoffMs: 200,
maxBackoffMs: 5000,
};
Design Notes
External ID Mapping
The CRM integration uses external_id fields to maintain bidirectional mapping between local entities and CRM records. This enables:
- Idempotent upserts - The same external ID always maps to the same CRM record
- Cache optimization - External-to-CRM ID mappings are cached to reduce API calls
- Conflict resolution - 409 conflicts during parallel upserts are automatically retried
Retry Strategy
The client uses exponential backoff with the following defaults:
- Initial backoff: 200ms
- Max backoff: 5000ms
- Max retries: 3
- The
isRetryableflag on errors indicates whether automatic retry is appropriate
Error Classification
Errors are classified by TwentyCrmErrorCode to enable appropriate handling:
AUTH_ERROR- Invalid or expired API keyRATE_LIMIT- Too many requests (retryable)TIMEOUT- Request timed out (retryable)VALIDATION_ERROR- Invalid data sent to CRMNOT_FOUND- Entity does not existNETWORK_ERROR- Connection issues (retryable)
Related Types
ClientProfileWithAuth- Local client profile that maps toTwentyPersonCreateClientRequest- Client creation fields that feed into CRM sync