Metric

What is sales cycle length, and how do you measure it in Metabase?

Sales cycle length is how long a deal takes to close — typically from creation (or first activity) to won. It's the core velocity metric for a sales team, and it's heavily right-skewed, so how you summarize it matters as much as how you measure it. Track it in Metabase from CRM data synced into a database (HubSpot, Salesforce, Pipedrive, and others).

TL;DR — Sales cycle length = created → won, in days. Always reportmedian (p50) and p90, never the plain average. For time-in-stage and stage conversion you need stage-change history, not just a current snapshot.

How is sales cycle length defined?

The elapsed days between a deal's start and its win. Teams vary on the start point:

  • Created → won — simplest and most common; uses the deal's creation timestamp.
  • First activity → won — starts the clock at genuine engagement, ignoring dormant records.
  • Qualified → won — starts when a deal enters a qualified stage; needs stage history.

Pick one start definition and apply it consistently.

Why sales cycle length needs stage history

A current CRM snapshot tells you where a deal is now, not when it entered or left each stage. Without stage-change history you can compute overall cycle length (created → won) but nottime-in-stage or where deals stall. Sync the stage/deal-change log:

  • HubSpot: deal property history (stage change timestamps).
  • Salesforce:OpportunityFieldHistory orOpportunityHistory for stage transitions.
  • Pipedrive: deal flow / changes log.
  • Close: opportunity status changes (webhooks or activity log).

What data does sales cycle length need?

  • A created_at (or first-activity) timestamp for the start.
  • A closed_at / won timestamp and a clear "won" definition.
  • For time-in-stage: full stage-change history per deal.
  • Segmentation columns: pipeline, segment, deal size band, owner.

SQL patterns

Median & p90 cycle length by monthPostgreSQL
SELECT
  date_trunc('month', closed_at) AS month,
  percentile_cont(0.5) WITHIN GROUP (
    ORDER BY EXTRACT(EPOCH FROM (closed_at - created_at)) / 86400.0
  ) AS median_cycle_days,
  percentile_cont(0.9) WITHIN GROUP (
    ORDER BY EXTRACT(EPOCH FROM (closed_at - created_at)) / 86400.0
  ) AS p90_cycle_days
FROM modeled_deals
WHERE is_won
GROUP BY 1
ORDER BY 1;
Median time-in-stage (needs stage history)PostgreSQL
-- Time-in-stage from stage-change history
WITH ordered AS (
  SELECT
    deal_id,
    stage,
    changed_at,
    LEAD(changed_at) OVER (
      PARTITION BY deal_id ORDER BY changed_at
    ) AS next_changed_at
  FROM modeled_stage_history
)
SELECT
  stage,
  percentile_cont(0.5) WITHIN GROUP (
    ORDER BY EXTRACT(EPOCH FROM (COALESCE(next_changed_at, now()) - changed_at)) / 86400.0
  ) AS median_days_in_stage
FROM ordered
GROUP BY stage
ORDER BY median_days_in_stage DESC;

Pitfalls

Reporting the average.→ Cycle length is right-skewed; a few marathon deals drag the mean. Use median and p90.
Computing time-in-stage without history.→ A snapshot can't tell you when a deal entered a stage — sync the stage-change log first.
Mixing deal sizes.→ Enterprise and SMB deals have very different cycles; segment by size band.
Counting only won deals sometimes and all deals other times.→ Decide whether you're measuring won-deal cycle or all-closed cycle, and keep it consistent.

Where this metric applies

Analytics

Metrics

FAQ

Median or average sales cycle length?
Median (p50) plus p90. The distribution is heavily right-skewed, so the average overstates the typical deal.
Can I measure time-in-stage without stage history?
No. A current snapshot only shows the deal's present stage. Sync the stage-change log (HubSpot property history, Salesforce OpportunityFieldHistory, Pipedrive deal flow, Close status changes) to measure time-in-stage.
Where should the clock start?
Created → won is simplest. First-activity → won or qualified → won can be more meaningful if dormant records inflate your creation-based cycle. Choose one and apply it consistently.