Claude-code-plugins-plus-skills hubspot-core-workflow-a
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/hubspot-pack/skills/hubspot-core-workflow-a" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-hubspot-core-workflow-a && rm -rf "$T"
manifest:
plugins/saas-packs/hubspot-pack/skills/hubspot-core-workflow-a/SKILL.mdsource content
HubSpot Core Workflow A: Contact-to-Deal Pipeline
Overview
End-to-end workflow: capture a lead, create/update contact, create company, create deal in pipeline, advance deal stages, and log activities. The primary money-path workflow for HubSpot CRM.
Prerequisites
- Completed
setuphubspot-install-auth - Scopes:
,crm.objects.contacts.write
,crm.objects.companies.writecrm.objects.deals.write - Understanding of your HubSpot deal pipeline and stages
Instructions
Step 1: Capture and Upsert Contact
import * as hubspot from '@hubspot/api-client'; const client = new hubspot.Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN!, numberOfApiCallRetries: 3, }); interface LeadInput { email: string; firstName: string; lastName: string; company: string; phone?: string; source?: string; } async function upsertContact(lead: LeadInput): Promise<string> { // Search for existing contact by email // POST /crm/v3/objects/contacts/search const existing = await client.crm.contacts.searchApi.doSearch({ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: lead.email }], }], properties: ['firstname', 'lastname', 'email'], limit: 1, after: 0, sorts: [], }); if (existing.results.length > 0) { // Update existing contact const contactId = existing.results[0].id; await client.crm.contacts.basicApi.update(contactId, { properties: { firstname: lead.firstName, lastname: lead.lastName, phone: lead.phone || '', hs_lead_status: 'NEW', }, }); console.log(`Updated existing contact: ${contactId}`); return contactId; } // Create new contact const contact = await client.crm.contacts.basicApi.create({ properties: { email: lead.email, firstname: lead.firstName, lastname: lead.lastName, company: lead.company, phone: lead.phone || '', lifecyclestage: 'lead', hs_lead_status: 'NEW', }, associations: [], }); console.log(`Created new contact: ${contact.id}`); return contact.id; }
Step 2: Find or Create Company
async function findOrCreateCompany( domain: string, name: string ): Promise<string> { // Search by domain const existing = await client.crm.companies.searchApi.doSearch({ filterGroups: [{ filters: [{ propertyName: 'domain', operator: 'EQ', value: domain }], }], properties: ['name', 'domain'], limit: 1, after: 0, sorts: [], }); if (existing.results.length > 0) { return existing.results[0].id; } const company = await client.crm.companies.basicApi.create({ properties: { name, domain }, associations: [], }); return company.id; }
Step 3: Create Deal in Pipeline
async function createDeal( contactId: string, companyId: string, dealName: string, amount: number ): Promise<string> { // First, get pipeline stages to find the right stage ID // GET /crm/v3/pipelines/deals const pipelines = await client.crm.pipelines.pipelinesApi.getAll('deals'); const defaultPipeline = pipelines.results.find(p => p.label === 'Sales Pipeline') || pipelines.results[0]; const firstStage = defaultPipeline.stages.sort( (a, b) => Number(a.displayOrder) - Number(b.displayOrder) )[0]; // POST /crm/v3/objects/deals const deal = await client.crm.deals.basicApi.create({ properties: { dealname: dealName, amount: String(amount), pipeline: defaultPipeline.id, dealstage: firstStage.id, closedate: new Date(Date.now() + 30 * 86400000).toISOString(), }, associations: [ { to: { id: contactId }, types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 3 }], }, { to: { id: companyId }, types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 5 }], }, ], }); console.log(`Created deal: ${deal.id} in stage "${firstStage.label}"`); return deal.id; }
Step 4: Advance Deal Stage
async function advanceDealStage(dealId: string, stageName: string): Promise<void> { // Look up stage ID from pipeline const deal = await client.crm.deals.basicApi.getById(dealId, ['pipeline', 'dealstage']); const pipelines = await client.crm.pipelines.pipelinesApi.getAll('deals'); const pipeline = pipelines.results.find(p => p.id === deal.properties.pipeline); const targetStage = pipeline?.stages.find(s => s.label === stageName); if (!targetStage) { throw new Error(`Stage "${stageName}" not found in pipeline "${pipeline?.label}"`); } await client.crm.deals.basicApi.update(dealId, { properties: { dealstage: targetStage.id }, }); console.log(`Deal ${dealId} moved to "${stageName}"`); }
Step 5: Log Activity (Note)
async function logNote(contactId: string, dealId: string, body: string): Promise<void> { // POST /crm/v3/objects/notes await client.crm.objects.notes.basicApi.create({ properties: { hs_note_body: body, hs_timestamp: new Date().toISOString(), }, associations: [ { to: { id: contactId }, types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 202 }], }, { to: { id: dealId }, types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 214 }], }, ], }); }
Complete Pipeline Example
async function processLead(lead: LeadInput) { const contactId = await upsertContact(lead); const domain = lead.email.split('@')[1]; const companyId = await findOrCreateCompany(domain, lead.company); // Associate contact with company await client.crm.associations.v4.basicApi.create( 'contacts', contactId, 'companies', companyId, [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 1 }] ); const dealId = await createDeal( contactId, companyId, `${lead.company} - New Opportunity`, 10000 ); await logNote(contactId, dealId, `Lead captured from ${lead.source || 'website'}`); return { contactId, companyId, dealId }; }
Output
- Contact upserted (create or update based on email)
- Company found or created by domain
- Deal created in the correct pipeline stage with associations
- Deal stage advanced through the pipeline
- Activity note logged on contact and deal
Error Handling
| Error | Code | Cause | Solution |
|---|---|---|---|
| 409 | Duplicate email on create | Use search-then-create pattern above |
| 400 | Invalid stage ID | Fetch pipeline stages first |
| 400 | Wrong | Check default association types |
| 400 | Custom property not created | Create in HubSpot Settings > Properties |
Resources
Next Steps
For marketing automation workflows, see
hubspot-core-workflow-b.