From hubspot-admin
Ensure all contacts and companies have appropriate lifecycle stages. Backfills missing stages via API, fixes records stuck at disallowed stages, and creates prevention workflows to stop future gaps.
npx claudepluginhub tomgranot/hubspot-admin-skillsThis skill uses the workspace's default tool permissions.
Ensure every contact and company has an appropriate lifecycle stage. This includes backfilling missing stages, correcting disallowed stage values, and creating prevention workflows that automatically assign stages to new records.
Build workflows to automate contact progression through the sales funnel: Lead to MQL to SQL to Opportunity to Customer. Each transition is triggered by a specific event (score threshold, meeting booked, deal created, deal won).
Activate for: enrich, CRM enrichment, update CRM, data enrichment, stale data, missing data, contact data, company data, verify email, verify contact, update record, data hygiene, CRM cleanup, enrich leads, refresh accounts, outdated records, data quality. NOT for: prospect research briefs (use prospect-research), lead scoring (use lead-scoring), bulk automated enrichment scheduling (use crm-hygiene-agent), pipeline analysis (use pipeline).
Mines high-value prospects from CRM lost/churned stages by cross-referencing LinkedIn data, Apify company scrapes, domain filters, and comms history for re-engagement.
Share bugs, ideas, or general feedback.
Ensure every contact and company has an appropriate lifecycle stage. This includes backfilling missing stages, correcting disallowed stage values, and creating prevention workflows that automatically assign stages to new records.
Records without a lifecycle stage are invisible in pipeline reports, excluded from stage-based workflows, and cannot be properly segmented. Even a small percentage of missing lifecycle stages corrupts funnel reporting and makes pipeline analytics unreliable. Lifecycle stage data is also a prerequisite for lead scoring models and lifecycle progression workflows.
HubSpot has forward-only lifecycle progression by default. The built-in order is:
Subscriber > Lead > MQL > SQL > Opportunity > Customer > Evangelist
To move a record from a later stage (e.g., "Other", "Evangelist") to an earlier one (e.g., "Lead"), you must:
A direct set to an earlier stage will be silently rejected — no error, no warning, the value simply does not change. This is the single most common gotcha when fixing lifecycle stages.
# WRONG — silently fails if current stage is "later" than target
api_client.crm.contacts.basic_api.update(
contact_id=contact_id,
simple_public_object_input={"properties": {"lifecyclestage": "lead"}}
)
# CORRECT — clear first, then set
api_client.crm.contacts.basic_api.update(
contact_id=contact_id,
simple_public_object_input={"properties": {"lifecyclestage": ""}}
)
api_client.crm.contacts.basic_api.update(
contact_id=contact_id,
simple_public_object_input={"properties": {"lifecyclestage": "lead"}}
)
import os
from hubspot import HubSpot
from dotenv import load_dotenv
load_dotenv()
api_client = HubSpot(access_token=os.getenv("HUBSPOT_API_TOKEN"))
# Count contacts with no lifecycle stage
result = api_client.crm.contacts.search_api.do_search(
public_object_search_request={
"filterGroups": [{
"filters": [{
"propertyName": "lifecyclestage",
"operator": "NOT_HAS_PROPERTY"
}]
}],
"limit": 0
}
)
print(f"Contacts missing lifecycle stage: {result.total}")
# Count contacts at each stage
stages = ["subscriber", "lead", "marketingqualifiedlead", "salesqualifiedlead",
"opportunity", "customer", "evangelist", "other"]
for stage in stages:
result = api_client.crm.contacts.search_api.do_search(
public_object_search_request={
"filterGroups": [{
"filters": [{
"propertyName": "lifecyclestage",
"operator": "EQ",
"value": stage
}]
}],
"limit": 0
}
)
if result.total > 0:
print(f" {stage}: {result.total}")
# Repeat for companies
result = api_client.crm.companies.search_api.do_search(
public_object_search_request={
"filterGroups": [{
"filters": [{
"propertyName": "lifecyclestage",
"operator": "NOT_HAS_PROPERTY"
}]
}],
"limit": 0
}
)
print(f"\nCompanies missing lifecycle stage: {result.total}")
Decide which lifecycle stage values should not exist in your database. The table below shows common examples -- your disallowed stages and their correct mappings will depend on how your organization uses the CRM. Review your own stage distribution and decide what makes sense for your business:
| Example Disallowed Stage | Common Reason | Example Correct Stage |
|---|---|---|
| (empty/blank) | Invisible to reports | Lead (default) |
| Subscriber | Often misapplied when not used for newsletter-only contacts | Lead |
| Other | Meaningless catch-all | Lead |
| Evangelist | Rarely used correctly in most organizations | Customer (if actual customer) or Lead |
These are starting-point examples only. Your mapping will differ based on your sales process, integrations, and how stages are currently used. Define your specific mapping before executing.
For contacts at "Subscriber", "Other", or "Evangelist" that should be moved to "Lead":
# Pattern: Clear then set (required for backward movement)
DISALLOWED_TO_LEAD = ["subscriber", "other", "evangelist"]
for stage in DISALLOWED_TO_LEAD:
# Search for contacts at this stage
# Paginate through all results
# For each batch:
# 1. Clear lifecycle stage (set to "")
# 2. Set lifecycle stage to "lead"
# Use batch API for efficiency (100 per call)
pass
Important: The clear-then-set must happen as two separate API calls. You cannot clear and set in one call.
Do not set all missing contacts to "Lead" blindly. Check their associated company context:
# Pattern for context-aware assignment:
# 1. Search for contacts with no lifecycle stage
# 2. For each, get their primary associated company
# 3. Check the company's lifecycle stage
# 4. Set the contact's stage to match (or "lead" as default)
Manual approach via lists:
Some records may fail to update due to the forward-only progression rule. Run a "fix stuck" script:
# Pattern: Find records that should be at a stage but are not
# For each:
# 1. Read current lifecycle stage
# 2. If current stage is "later" than target, clear first
# 3. Set the target stage
Contact prevention workflow:
AUTO-FIX: Set Default Lifecycle Stage (Lead)Company prevention workflow:
AUTO-FIX: Set Default Company Lifecycle Stage (Lead)Optional: Disallowed stage correction workflows:
If contacts keep getting set to disallowed stages (e.g., by imports or integrations):
This prevents disallowed stages from recurring.
# Re-run the before-state audit
result = api_client.crm.contacts.search_api.do_search(
public_object_search_request={
"filterGroups": [{
"filters": [{
"propertyName": "lifecyclestage",
"operator": "NOT_HAS_PROPERTY"
}]
}],
"limit": 0
}
)
print(f"Contacts missing lifecycle stage: {result.total} (should be 0)")
result = api_client.crm.companies.search_api.do_search(
public_object_search_request={
"filterGroups": [{
"filters": [{
"propertyName": "lifecyclestage",
"operator": "NOT_HAS_PROPERTY"
}]
}],
"limit": 0
}
)
print(f"Companies missing lifecycle stage: {result.total} (should be 0)")
Verification checklist: