Building Custom Connectors
Create custom connectors to integrate any identity source with TigerIdentity using our powerful SDK.
Overview
The TigerIdentity Connector SDK enables you to build custom connectors for any identity source. Whether you need to integrate with a proprietary system, a legacy database, or a SaaS application without a pre-built connector, the SDK provides all the tools you need.
TypeScript SDK
Fully typed SDK with IntelliSense support.
Local Testing
Test connectors locally before deployment.
Easy Publishing
Publish to the connector registry with one command.
Getting Started
Step 1: Install the SDK
Install the TigerIdentity Connector SDK via npm:
# Create a new connector project
mkdir my-custom-connector
cd my-custom-connector
# Initialize npm project
npm init -y
# Install the SDK
npm install @tigeridentity/connector-sdk
# Install development dependencies
npm install -D typescript @types/node tsxStep 2: Configure TypeScript
Create a tsconfig.json file:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Implementing the Connector Interface
Create your connector by implementing the Connector interface:
// src/index.ts
import {
Connector,
ConnectorConfig,
ConnectorMetadata,
SyncResult,
HealthCheckResult,
WebhookEvent,
Identity,
Group,
} from '@tigeridentity/connector-sdk';
/**
* Custom connector for internal HR system
*/
export class HRSystemConnector implements Connector {
private config: ConnectorConfig;
private apiClient: any; // Your API client
constructor(config: ConnectorConfig) {
this.config = config;
this.initializeClient();
}
/**
* Initialize API client with configuration
*/
private initializeClient(): void {
const { apiUrl, apiKey } = this.config;
// Initialize your API client here
this.apiClient = {
baseURL: apiUrl,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
};
}
/**
* Return connector metadata
*/
getMetadata(): ConnectorMetadata {
return {
name: 'hr-system',
displayName: 'Internal HR System',
description: 'Sync employees and departments from internal HR system',
version: '1.0.0',
author: 'Your Company',
// Configuration schema
configSchema: {
type: 'object',
required: ['apiUrl', 'apiKey'],
properties: {
apiUrl: {
type: 'string',
title: 'API URL',
description: 'Base URL of the HR system API',
},
apiKey: {
type: 'string',
title: 'API Key',
description: 'API key for authentication',
secret: true,
},
syncDepartments: {
type: 'boolean',
title: 'Sync Departments',
description: 'Include department information in sync',
default: true,
},
},
},
// Supported features
capabilities: {
sync: true,
webhook: true,
incremental: true,
realtime: false,
},
// Resource types
resourceTypes: ['users', 'groups'],
};
}
/**
* Perform health check
*/
async healthCheck(): Promise<HealthCheckResult> {
try {
// Test API connectivity
const response = await fetch(`${this.config.apiUrl}/health`, {
headers: this.apiClient.headers,
});
if (!response.ok) {
return {
healthy: false,
message: `API returned status ${response.status}`,
};
}
return {
healthy: true,
message: 'Connection successful',
};
} catch (error) {
return {
healthy: false,
message: `Connection failed: ${error.message}`,
error: error,
};
}
}
/**
* Sync identities from the source system
*/
async onSync(lastSyncTime?: Date): Promise<SyncResult> {
const startTime = Date.now();
const identities: Identity[] = [];
const groups: Group[] = [];
const errors: any[] = [];
try {
// Fetch employees
const employees = await this.fetchEmployees(lastSyncTime);
// Transform to Identity objects
for (const employee of employees) {
try {
const identity = this.transformEmployee(employee);
identities.push(identity);
} catch (error) {
errors.push({
id: employee.id,
error: error.message,
});
}
}
// Fetch departments if enabled
if (this.config.syncDepartments) {
const departments = await this.fetchDepartments();
for (const dept of departments) {
try {
const group = this.transformDepartment(dept);
groups.push(group);
} catch (error) {
errors.push({
id: dept.id,
error: error.message,
});
}
}
}
return {
success: true,
identities,
groups,
syncTime: new Date(),
duration: Date.now() - startTime,
stats: {
identitiesProcessed: identities.length,
groupsProcessed: groups.length,
errors: errors.length,
},
errors,
};
} catch (error) {
return {
success: false,
identities: [],
groups: [],
syncTime: new Date(),
duration: Date.now() - startTime,
error: error.message,
};
}
}
/**
* Handle webhook events
*/
async onWebhook(event: WebhookEvent): Promise<void> {
const { type, payload } = event;
switch (type) {
case 'employee.created':
case 'employee.updated':
const identity = this.transformEmployee(payload);
await this.emitIdentity(identity);
break;
case 'employee.terminated':
await this.emitIdentityDeletion(payload.id);
break;
case 'department.created':
case 'department.updated':
const group = this.transformDepartment(payload);
await this.emitGroup(group);
break;
default:
console.log(`Unhandled webhook event type: ${type}`);
}
}
/**
* Fetch employees from HR system
*/
private async fetchEmployees(since?: Date): Promise<any[]> {
const params = new URLSearchParams();
if (since) {
params.append('updatedSince', since.toISOString());
}
const response = await fetch(
`${this.config.apiUrl}/employees?${params}`,
{
headers: this.apiClient.headers,
}
);
if (!response.ok) {
throw new Error(`Failed to fetch employees: ${response.statusText}`);
}
const data = await response.json();
return data.employees || [];
}
/**
* Fetch departments from HR system
*/
private async fetchDepartments(): Promise<any[]> {
const response = await fetch(`${this.config.apiUrl}/departments`, {
headers: this.apiClient.headers,
});
if (!response.ok) {
throw new Error(`Failed to fetch departments: ${response.statusText}`);
}
const data = await response.json();
return data.departments || [];
}
/**
* Transform employee to Identity
*/
private transformEmployee(employee: any): Identity {
return {
id: employee.id,
email: employee.email,
username: employee.email.split('@')[0],
firstName: employee.firstName,
lastName: employee.lastName,
displayName: `${employee.firstName} ${employee.lastName}`,
department: employee.department?.name,
title: employee.jobTitle,
manager: employee.managerId,
status: employee.status === 'active' ? 'active' : 'inactive',
// Custom attributes
attributes: {
employeeId: employee.employeeNumber,
hireDate: employee.hireDate,
location: employee.office,
employeeType: employee.type,
},
// Group memberships
groups: [
employee.department?.id,
...employee.teams?.map((t: any) => t.id) || [],
].filter(Boolean),
source: 'hr-system',
sourceId: employee.id,
lastSyncTime: new Date(),
};
}
/**
* Transform department to Group
*/
private transformDepartment(dept: any): Group {
return {
id: dept.id,
name: dept.name,
description: dept.description,
// Members
members: dept.employees?.map((e: any) => e.id) || [],
// Parent group
parentGroup: dept.parentDepartmentId,
// Custom attributes
attributes: {
costCenter: dept.costCenter,
location: dept.location,
manager: dept.managerId,
},
source: 'hr-system',
sourceId: dept.id,
lastSyncTime: new Date(),
};
}
/**
* Emit identity update
*/
private async emitIdentity(identity: Identity): Promise<void> {
// SDK handles sending to TigerIdentity
console.log('Emitting identity:', identity.id);
}
/**
* Emit identity deletion
*/
private async emitIdentityDeletion(id: string): Promise<void> {
console.log('Emitting identity deletion:', id);
}
/**
* Emit group update
*/
private async emitGroup(group: Group): Promise<void> {
console.log('Emitting group:', group.id);
}
}
// Export the connector
export default HRSystemConnector;Testing Locally
Test your connector locally before deploying to production.
Create Test Configuration
# test-config.yaml
name: hr-system-test
type: hr-system
enabled: true
config:
apiUrl: https://hr-system.example.com/api/v1
apiKey: test-api-key-12345
syncDepartments: trueRun Local Tests
# Build the connector
npm run build
# Test health check
tiger connector dev test-config.yaml --health-check
# Test sync
tiger connector dev test-config.yaml --sync
# Test with webhook event
tiger connector dev test-config.yaml --webhook \
--event employee.created \
--payload '{"id":"123","email":"[email protected]",...}'
# Run in watch mode (auto-reload on changes)
tiger connector dev test-config.yaml --watchCreate Unit Tests
// src/__tests__/connector.test.ts
import { HRSystemConnector } from '../index';
describe('HRSystemConnector', () => {
let connector: HRSystemConnector;
beforeEach(() => {
connector = new HRSystemConnector({
apiUrl: 'https://hr-system.test',
apiKey: 'test-key',
syncDepartments: true,
});
});
test('metadata is defined', () => {
const metadata = connector.getMetadata();
expect(metadata.name).toBe('hr-system');
expect(metadata.version).toBe('1.0.0');
});
test('health check succeeds with valid config', async () => {
// Mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ status: 'ok' }),
})
) as jest.Mock;
const result = await connector.healthCheck();
expect(result.healthy).toBe(true);
});
test('sync returns identities', async () => {
// Mock API responses
const mockEmployees = [
{
id: '1',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
status: 'active',
},
];
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ employees: mockEmployees }),
})
) as jest.Mock;
const result = await connector.onSync();
expect(result.success).toBe(true);
expect(result.identities).toHaveLength(1);
expect(result.identities[0].email).toBe('[email protected]');
});
});Publishing to Connector Registry
Once your connector is tested and ready, publish it to the TigerIdentity connector registry.
Step 1: Create connector.json
{
"name": "@your-org/tigeridentity-connector-hr-system",
"version": "1.0.0",
"description": "TigerIdentity connector for internal HR system",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"tigeridentity": {
"connectorType": "hr-system",
"displayName": "Internal HR System",
"category": "hr",
"icon": "https://example.com/icon.png",
"documentation": "https://docs.example.com/connector"
},
"keywords": [
"tigeridentity",
"connector",
"hr",
"identity"
],
"author": "Your Company",
"license": "MIT",
"peerDependencies": {
"@tigeridentity/connector-sdk": "^1.0.0"
}
}Step 2: Validate and Publish
# Validate connector package
tiger connector validate
# Build for production
npm run build
# Publish to connector registry
tiger connector publish
# Output:
# ✓ Connector validated successfully
# ✓ Package built
# ✓ Published to registry: [email protected]
#
# Your connector is now available at:
# https://connectors.tigeridentity.com/hr-systemStep 3: Install and Use
Others can now install and use your connector:
# Install the connector
tiger connector install hr-system
# Create configuration
cat > hr-connector.yaml << EOF
name: hr-production
type: hr-system
enabled: true
config:
apiUrl: https://hr.example.com/api
apiKey: ${HR_API_KEY}
syncDepartments: true
EOF
# Deploy the connector
tiger connector create -f hr-connector.yamlComplete Working Example
Here's a complete example project structure for a custom connector:
my-custom-connector/
├── src/
│ ├── index.ts # Main connector implementation
│ ├── types.ts # Type definitions
│ ├── client.ts # API client
│ ├── transforms.ts # Data transformations
│ └── __tests__/
│ ├── connector.test.ts # Connector tests
│ └── transforms.test.ts # Transform tests
├── dist/ # Build output
├── package.json # Package configuration
├── tsconfig.json # TypeScript configuration
├── connector.yaml # Local test config
├── README.md # Documentation
└── LICENSE # License fileBest Practices
- •Error handling: Always handle API errors gracefully and provide meaningful error messages
- •Rate limiting: Respect source system rate limits and implement backoff strategies
- •Incremental sync: Support incremental syncs to minimize API calls and improve performance
- •Logging: Use structured logging to help with debugging and monitoring
- •Testing: Write comprehensive tests including unit tests and integration tests
- •Documentation: Provide clear documentation on configuration, setup, and troubleshooting
Related Documentation
Ready to Build Your Connector?
Join our developer community and start building custom connectors today.