Skip to Content
RecipesRecipes & Examples

Recipes & Examples

Copy-paste code snippets for common tasks with this monorepo’s packages.

Basic Operations

Create a Person

import { PcoClient } from '@rachelallyson/planning-center-people-ts'; const client = new PcoClient({ auth: { type: 'personal_access_token', personalAccessToken: process.env.PCO_TOKEN! } }); const person = await client.people.create({ first_name: 'John', last_name: 'Doe' }); console.log(`Created person: ${person.id}`);

Result: PersonResource with new person data


Find or Create Person

const person = await client.people.findOrCreate({ firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com', phone: '555-1234', matchStrategy: 'fuzzy' // or 'exact' }); if (person.match?.found) { console.log(`Found existing person: ${person.id}`); } else { console.log(`Created new person: ${person.id}`); }

Result: PersonResource with optional match property indicating if person was found


// Multi-step search tries multiple strategies in order until a match is found // This maximizes matching success and prevents duplicate creation const person = await client.people.findOrCreate({ firstName: 'John', lastName: 'Doe', email: 'john@example.com', searchStrategy: 'multi-step', // Tries: fuzzy+age → fuzzy → exact+age → exact agePreference: 'adults', createIfNotFound: true }); console.log(`Person ID: ${person.id}`);

Result: PersonResource found using the first successful strategy


Find or Create with Name Fallback

// Falls back to name search when email/phone search fails // Validates contact info to prevent wrong-person matches const person = await client.people.findOrCreate({ firstName: 'John', lastName: 'Doe', email: 'john@example.com', fallbackToNameSearch: true, contactValidation: 'domain' // 'strict' | 'domain' | 'similarity' });

Result: PersonResource with contact validation preventing wrong matches


Find or Create with Phase-Specific Retries

// Different retry configs for different search phases // Prevents duplicates when PCO hasn't indexed contacts yet const person = await client.people.findOrCreate({ firstName: 'John', lastName: 'Doe', email: 'john@example.com', searchStrategy: 'multi-step', retryConfigs: { initial: { maxRetries: 3, maxWaitTime: 30000 }, // Quick search aggressive: { maxRetries: 6, maxWaitTime: 60000 } // Final search before create }, createIfNotFound: true });

Result: PersonResource with aggressive duplicate prevention


Verify Person Exists

// Check if a cached person ID is still valid (handles merges/deletions) const exists = await client.people.verifyPersonExists('person-123', { timeout: 30000 }); if (!exists) { console.log('Person was merged or deleted - need to search again'); const person = await client.people.findOrCreate({...}); }

Result: boolean - true if person exists, false if not found (404)


Get Person with Relationships

const person = await client.people.getById('person-id', [ 'emails', 'phone_numbers', 'addresses', 'household' ]); console.log('Person:', person.attributes.first_name); console.log('Emails:', person.included?.filter(r => r.type === 'Email'));

Result: PersonResource with related resources in included array


Contact Information

Add Email to Person

const email = await client.contacts.createEmail('person-id', { address: 'john@example.com', location: 'Home', primary: true }); console.log(`Added email: ${email.id}`);

Result: EmailResource


Add Phone to Person

const phone = await client.contacts.createPhone('person-id', { number: '555-1234', location: 'Mobile', primary: true }); console.log(`Added phone: ${phone.id}`);

Result: PhoneNumberResource


Add Address to Person

const address = await client.contacts.createAddress('person-id', { street: '123 Main St', city: 'Springfield', state: 'IL', zip: '62701', location: 'Home', primary: true }); console.log(`Added address: ${address.id}`);

Result: AddressResource


Custom Fields

Set Field by Slug

await client.fields.setPersonFieldBySlug( 'person-id', 'BIRTHDATE', '1990-01-01' );

Input: Person ID, field slug (uppercase), value
Output: Success (no return value)


Set Field by Name

await client.fields.setPersonFieldByName( 'person-id', 'Membership Status', 'Member' );

Input: Person ID, field name, value
Output: Success (no return value)


Set Field with File Upload

await client.fields.setPersonField('person-id', { fieldSlug: 'CUSTOM_FIELD', value: 'https://example.com/file.pdf', handleFileUploads: true // Automatically uploads file to PCO });

Result: File uploaded and field value set to uploaded file URL


Workflows

Add Person to Workflow

const card = await client.workflows.addPersonToWorkflow( 'person-id', 'workflow-id', { note: 'Added via integration', skipIfExists: true } ); console.log(`Workflow card: ${card.id}`);

Result: WorkflowCardResource


Get Person’s Workflow Cards

const cards = await client.workflows.getPersonWorkflowCards('person-id', { perPage: 25 }); cards.data.forEach(card => { console.log(`Card: ${card.id} in workflow ${cardworkflow.data.id}`); });

Result: Paginated<WorkflowCardResource>


Batch Operations

Create Person with Contact Info

const results = await client.batch.execute([ { type: 'people.create', data: { firstName: 'John', lastName: 'Doe' } }, { type: 'people.addEmail', personId: '$0.id', // Reference person from step 0 data: { address: 'john@example.com', primary: true } }, { type: 'people.addPhone', personId: '$0.id', data: { number: '555-1234', primary: true } } ]); console.log(`Successful: ${results.successful.length}`); console.log(`Failed: ${results.failed.length}`);

Result: BatchResult with successful, failed, and summary properties


Batch Create Multiple People

const people = [ { firstName: 'John', lastName: 'Doe', email: 'john@example.com' }, { firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' }, { firstName: 'Bob', lastName: 'Jones', email: 'bob@example.com' } ]; const operations = people.map((person, index) => [ { type: 'people.create', data: { firstName: person.firstName, lastName: person.lastName } }, { type: 'people.addEmail', personId: `$${index * 2}.id`, data: { address: person.email, primary: true } } ]).flat(); const results = await client.batch.execute(operations);

Result: All people created with emails in a single batch


Pagination

Get All People (Automatic)

const allPeople = await client.people.getAllPages({ perPage: 100, include: ['emails', 'phone_numbers'] }); console.log(`Total people: ${allPeople.data.length}`);

Result: Paginated<PersonResource> with all pages fetched


Get People with Manual Pagination

let page = 1; let hasMore = true; const allPeople = []; while (hasMore) { const response = await client.people.getAll({ perPage: 50, page: page, include: ['emails'] }); allPeople.push(...response.data); hasMore = response.links?.next !== undefined; page++; } console.log(`Total people: ${allPeople.length}`);

Result: All people fetched page by page


Error Handling

Try-Catch with Error Categories

import { PcoError, ErrorCategory } from '@rachelallyson/planning-center-people-ts'; try { await client.people.create({ first_name: 'John' }); } catch (error) { if (error instanceof PcoError) { switch (error.category) { case ErrorCategory.AUTHENTICATION: console.error('Auth failed - check token'); break; case ErrorCategory.RATE_LIMIT: const delay = error.getRetryDelay(); await new Promise(resolve => setTimeout(resolve, delay)); // Retry... break; case ErrorCategory.VALIDATION: error.errors.forEach(err => { console.error(`${err.source?.pointer}: ${err.detail}`); }); break; } } }

Result: Handled errors based on category


Retry with Backoff

import { retryWithBackoff } from '@rachelallyson/planning-center-people-ts'; const person = await retryWithBackoff( () => client.people.getById('person-id'), { maxRetries: 3, baseDelay: 1000 } );

Result: Retries on failure with exponential backoff


Event Monitoring

Track All Requests

client.on('request:start', (event) => { console.log(`[${event.requestId}] ${event.method} ${event.endpoint}`); }); client.on('request:complete', (event) => { console.log(`[${event.requestId}] ${event.status} in ${event.duration}ms`); }); client.on('error', (event) => { console.error(`[${event.operation}] Error:`, event.error); });

Result: Console logs for all request activity


Monitor Rate Limits

client.on('rate:limit', (event) => { console.warn(`Rate limit: ${event.remaining}/${event.limit} remaining`); if (event.remaining < 10) { console.warn('Approaching rate limit - slowing down'); } });

Result: Rate limit warnings


Performance

Get Performance Metrics

const metrics = client.getPerformanceMetrics(); console.log('Average response time:', metrics.averageResponseTime, 'ms'); console.log('Success rate:', metrics.successRate); console.log('Total requests:', metrics.totalRequests);

Result: PerformanceMetrics object


Get Rate Limit Info

const info = client.getRateLimitInfo(); console.log(`Remaining: ${info.remaining}/${info.limit}`); console.log(`Resets in: ${info.windowResetsIn}ms`);

Result: RateLimitInfo object


Complete Example: Import People from CSV

import { PcoClient } from '@rachelallyson/planning-center-people-ts'; import { parse } from 'csv-parse/sync'; import * as fs from 'fs'; async function importPeopleFromCSV(filePath: string) { const client = new PcoClient({ auth: { type: 'personal_access_token', personalAccessToken: process.env.PCO_TOKEN! } }); const csvData = parse(fs.readFileSync(filePath, 'utf-8'), { columns: true, skip_empty_lines: true }); const results = { created: 0, found: 0, errors: 0 }; for (const row of csvData) { try { const person = await client.people.findOrCreate({ firstName: row.firstName, lastName: row.lastName, email: row.email, matchStrategy: 'fuzzy' }); if (person.match?.found) { results.found++; } else { results.created++; } // Add email if not present if (row.email && !person.match?.found) { await client.contacts.createEmail(person.id, { address: row.email, primary: true }); } // Set custom field if (row.birthdate) { await client.fields.setPersonFieldBySlug( person.id, 'BIRTHDATE', row.birthdate ); } } catch (error) { console.error(`Error importing ${row.firstName} ${row.lastName}:`, error); results.errors++; } } return results; }

Result: CSV imported with summary of created/found/errors


Contact Validation Utilities

Check Email Domain Match

import { emailDomainsMatch } from '@rachelallyson/planning-center-people-ts'; // Handles aliases (gmail/googlemail) and similar prefixes emailDomainsMatch('user@gmail.com', 'other@googlemail.com'); // true emailDomainsMatch('user@gmail.com', 'other@gmaill.com'); // true (typo) emailDomainsMatch('user@gmail.com', 'other@yahoo.com'); // false

Result: boolean - true if domains match or are similar


Check Phone Number Similarity

import { phoneNumbersSimilar } from '@rachelallyson/planning-center-people-ts'; // Handles format variations and country codes phoneNumbersSimilar('+15551234567', '555-123-4567'); // true phoneNumbersSimilar('15551234567', '5551234567'); // true phoneNumbersSimilar('+15551234567', '+15559876543'); // false

Result: boolean - true if phone numbers are similar


Validate Contact Similarity

import { validateContactSimilarity } from '@rachelallyson/planning-center-people-ts'; // Validate contact info for name-based search matches const validation = validateContactSimilarity( 'jane@example.com', // Search email '+15551234567', // Search phone ['john@example.com'], // Person's emails ['+15551234567'] // Person's phones ); console.log(validation); // { emailMatch: true, phoneMatch: true, isValid: true }

Result: Object with emailMatch, phoneMatch, and isValid booleans


Trust-Based Caching

Calculate Trust for Cached Person ID

import { calculateTrust, DEFAULT_TRUST_WINDOW } from '@rachelallyson/planning-center-people-ts'; // Check if cached personId can be trusted without re-verification const trust = calculateTrust(personIdCreatedAt); if (trust.shouldTrust) { // Use cached personId without API call console.log(`Trusting fresh personId: ${trust.reason}`); return cachedPersonId; } else { // Verify personId still exists (handles merges/deletions) console.log(`Verifying old personId: ${trust.reason}`); const exists = await client.people.verifyPersonExists(cachedPersonId); if (!exists) { // Person was merged or deleted, search again const person = await client.people.findOrCreate({...}); } }

Result: TrustResult with shouldTrust, age, and reason


Custom Trust Window

import { calculateTrust } from '@rachelallyson/planning-center-people-ts'; // Use custom trust window (e.g., 30 minutes instead of default 1 hour) const shortWindow = 30 * 60 * 1000; // 30 minutes const trust = calculateTrust(personIdCreatedAt, shortWindow); if (trust.shouldTrust) { return cachedPersonId; }

Result: Trust calculation with custom window


Building Custom Clients with Base Package

Extend BaseModule

import { BaseModule, PcoHttpClient, PaginationHelper, PcoEventEmitter } from '@rachelallyson/planning-center-base-ts'; class MyApiModule extends BaseModule { async getResource(id: string) { return this.getSingle(`/resources/${id}`); } async getAllResources() { return this.getList('/resources'); } } // Usage const httpClient = new PcoHttpClient(config, eventEmitter); const paginationHelper = new PaginationHelper(httpClient); const myModule = new MyApiModule(httpClient, paginationHelper, eventEmitter);

Result: Custom module following the same patterns as People API modules


More Examples: See People Package Recipes for additional examples.

Last updated on