Employers that advertise a sales role often state a base salary and on-target earnings. Taken across the postings of many employers, those figures form a distribution that you can hold a compensation plan against. This guide shows how to read Sales Pay Benchmarks (sales_pay_benchmarks), place a plan against its quartiles, cut Job Postings and Sales Team Metrics in finer ways, and judge how much weight the answer can carry.
What Sales Pay Benchmarks hold
Sales Pay Benchmarks have one row per closed week, role and country, written once after the week closes and not revised. The role is one of the eight sales-v1 roles: sdr_bdr, account_executive, account_manager, sales_engineer, sales_leadership, sales_operations, partnerships_channel and other. The country is the posting's country, or ALL for every country together. Every group is published with the number of postings behind it.
| Columns | Meaning |
|---|---|
week_start, role, country | The Monday of the week, the role, and the country or ALL |
postings | Open sales postings in the group with a stated base or on-target earnings |
base_p25_usd, base_median_usd, base_p75_usd | Quartiles of advertised base pay, in yearly US dollars |
ote_p25_usd, ote_median_usd, ote_p75_usd | Quartiles of advertised on-target earnings, in yearly US dollars |
The quartiles are over postings, not companies. The dataset gives the number of postings behind a group, which is why the sections below set floors on postings and companies. Every column is defined in the data dictionary, and the dataset belongs to the hiring dataset.
What each figure is
Everything comes from what an employer writes in its own posting. For a posting in a sales function, the base and the on-target earnings are extracted where the posting names them apart, as written and as yearly US-dollar midpoints at a reference exchange rate. The same extraction takes the split between base and variable pay, whether commission is uncapped, any quota, the ramp period and the stated lead mix. Figures pass plausibility bounds for salaries.
A model then reads the posting under the frozen version sales-v1 to give its role and the segment it sells to, as the methodology describes. Sales Team Metrics (company_sales_weekly) aggregate the same postings company by company, and there a plain salary range with no on-target earnings stated counts as the base. That dataset, with each company's roles, segments and new sales countries, is the subject of tracking sales team build-out.
Placing a plan against the quartiles
The example is illustrative: Acme Robotics is an invented employer, and the benchmark row is invented to show the arithmetic. It is not a finding. Acme's account executive plan in the United Kingdom pays a base of 70,000 US dollars and on-target earnings of 120,000, and the benchmark group has 140 postings.
| Yearly US dollars | Acme plan | p25 | Median | p75 | Position |
|---|---|---|---|---|---|
| Base | 70,000 | 55,000 | 68,000 | 82,000 | Median to p75 |
| On-target earnings | 120,000 | 105,000 | 130,000 | 160,000 | p25 to median |
Read the two together. The base sits above the market median and the on-target earnings below it, so Acme pays 58% of its target as base while the market medians imply about 52%. The plan offers more certainty and less upside than the plans advertised around it, a finding that a comparison of salary alone would hide.
What to do with a position is a policy choice, and the quartiles narrow it. A plan below p25 on both figures competes with the lowest quarter of advertised offers. A plan above the median on base and below it on on-target earnings, as here, has a problem of mix and not of level, so the variable pay is the part to test. Quartiles and a median suit this use better than an average, because a few mislabelled or executive-level postings pull a mean a long way.
The query below places every plan you hold in the latest week. comp_plans is your own table with role, country, base_usd and ote_usd. Convert a plan paid in another currency to US dollars at a reference exchange rate first, and use the ALL row for a country that has no group.
select
p.role,
p.country,
b.postings,
case
when p.base_usd < b.base_p25_usd then 'below p25'
when p.base_usd < b.base_median_usd then 'p25 to median'
when p.base_usd < b.base_p75_usd then 'median to p75'
else 'above p75'
end as base_position,
case
when p.ote_usd < b.ote_p25_usd then 'below p25'
when p.ote_usd < b.ote_median_usd then 'p25 to median'
when p.ote_usd < b.ote_p75_usd then 'median to p75'
else 'above p75'
end as ote_position
from comp_plans p
join sales_pay_benchmarks b
on b.role = p.role
and b.country = p.country
and b.week_start = (select max(week_start) from sales_pay_benchmarks)
where b.postings >= 30;The floor of 30 postings is your own policy and not a property of the data, since a group is written from five. Raise it for a decision that costs money.
Finer cuts from Job Postings
Sales Pay Benchmarks have two dimensions, role and country. For small-business sellers against enterprise sellers, remote roles against onsite ones, or senior roles against junior ones, go to Job Postings (job_postings), where sales_labels carries the role and segment and sales_facts the stated pay as yearly US-dollar midpoints. Keep one row for each posting_id, and count the companies as well as the postings. The query assumes JSON cells are loaded as jsonb and empty cells as null.
with latest as (
select distinct on (posting_id) *
from job_postings
order by posting_id, last_seen_at desc
)
select
sales_labels ->> 'segment' as segment,
count(*) as postings,
count(distinct company_id) as companies,
percentile_cont(0.25) within group (order by (sales_facts ->> 'ote_usd')::numeric) as ote_p25,
percentile_cont(0.50) within group (order by (sales_facts ->> 'ote_usd')::numeric) as ote_median,
percentile_cont(0.75) within group (order by (sales_facts ->> 'ote_usd')::numeric) as ote_p75
from latest
where sales_labels ->> 'role' = 'account_executive'
and country = 'GB'
and closed_at is null
and sales_facts ->> 'ote_usd' is not null
group by 1
order by 1;If one company supplies most of the postings in a cut, the quartiles describe that company's pay bands, which is what the companies count is there to show. Set a floor on companies as well as on postings: a cut from fewer than ten companies describes those companies rather than a market.
Terms beyond the pay figures
A plan is more than two numbers, and sales_facts holds more of what employers state: base_share (0.5 for a 50/50 split), uncapped, variable_pay, quota with quota_usd, ramp_months, lead_source, inbound_share, deal_size and sales_cycle_months. Each is the employer's statement in the posting, and a posting that states none has none, so report how many postings stand behind every figure. The query gives the pay mix, the number of uncapped plans and the ramp for one role and country.
with latest as (
select distinct on (posting_id) *
from job_postings
order by posting_id, last_seen_at desc
)
select
count(*) as postings,
count(*) filter (where sales_facts ->> 'base_share' is not null) as with_split,
percentile_cont(0.5) within group (order by (sales_facts ->> 'base_share')::numeric) as base_share_median,
count(*) filter (where (sales_facts ->> 'uncapped')::boolean) as uncapped,
percentile_cont(0.5) within group (order by (sales_facts ->> 'ramp_months')::numeric) as ramp_months_median
from latest
where sales_labels ->> 'role' = 'account_executive'
and country = 'GB'
and closed_at is null;A comparison of mix is often more useful than a comparison of level. Two plans with the same on-target earnings can differ widely in base share, and the difference decides how much of the pay depends on attainment.
Direction over time
Sales Pay Benchmarks give a level for each week. For movement, the Market Series hold the families sales_ote_median and sales_base_median, by country, industry or company size band, over 7, 30 and 180 days. Each row carries the previous window, the growth between the two and an index that is 100 at the first as-of date. The unit is a median in yearly US dollars, and a row is written only over at least 20 postings.
select as_of, dimension as country, count as median_ote_usd, cohort as postings, growth, index
from market_series
where metric = 'sales_ote_median'
and dimension_kind = 'country'
and dimension in ('US', 'GB', 'DE')
and window_days = 30
order by as_of desc, dimension;For a median metric, count is the median itself. Because pay is converted at a reference exchange rate, a move in a euro or sterling market mixes advertised pay with exchange-rate movement. For a comparison in local currency, work from the pay as written in sales_facts, which keeps the currency and the period.
How to read advertised pay
- Advertised pay is the offer. A posting states what an employer advertises and the targets it states. A range is a negotiating band, and the midpoint used here is a reference point. Pair it with a compensation survey that employers submit payroll data to, and with your own payroll records, for pay actually received.
- Postings that state pay. Employers state pay more often in some countries than in others. A country with no row for a role has fewer than five such postings that week, so use the
ALLrow or a neighbouring country as the reference. - Open postings. A group is the postings open in that week, so a posting that stays open for six weeks sits in six weekly groups. Weeks are therefore not independent samples, and a trend is read over a run of weeks.
- First location. The country is that of the posting's first location, so a role listed in several countries counts in one of them.
- Labels come from a frozen version. Role and segment are assigned under the
sales-v1labels, so they mean the same thing every week. Theaccount_managerrole also takes in account-management titles filed under customer success. - Source. Postings come from each company's own job board and careers pages, dated when first seen and when closed, so every benchmark can be rebuilt as of a past date.
Frequently asked questions
What is the difference between base pay and on-target earnings?
Base pay is the fixed salary. On-target earnings are the base plus the variable pay a seller receives for reaching quota. A posting may give both, only a salary range, or only an on-target figure. Fokals reads each as stated and records the share of on-target earnings that is base, so a 50/50 plan shows a base share of 0.5.
Is advertised pay a reliable guide to what salespeople earn?
It is a reliable guide to what employers offer and a poor one to what people receive. A posting states a range and a target, and neither is attainment: quota may be missed or beaten, and the offer made may sit anywhere in the range. Use advertised pay to see where your plan stands against competing offers, and use payroll data for what your own team earns.
How many postings do I need before I trust a quartile?
Every quartile is published with its postings count, so you set your own standard of evidence. For a pay decision set a floor, such as 30 postings from at least 10 companies, and read the count with every quartile. Where a country sits under your floor, compare against the ALL row or a longer period.
How do I compare sales pay across countries?
Compare each country with its own quartiles, then compare positions rather than dollar amounts. All figures sit on one yearly US-dollar scale at a reference exchange rate, which makes them comparable in unit but not in purchasing power, and a move in an exchange rate shifts a country's figures without any change in advertised pay. For a local comparison, work from the pay as written in the posting's own currency.
Can I benchmark roles outside sales?
Yes. Sales Pay Benchmarks cover sales roles in depth, and for other functions Job Postings carries the advertised pay of every posting on one yearly US-dollar scale with its job function and seniority, Hiring Activity holds the median pay of each company's open postings, and Market Series has a median pay family by industry, country and size band. The guide to benchmarking pay and talent demand covers those.
The queries and code on this page are examples to adapt. Test them in your own environment before you rely on them.