The Wage Ledger
What jobs in America pay, from the lowest-paid workers to the highest - for all 830 occupations, nationally and in 393 metro areas.
Johnathan Pickens
Published
June 25th 2026
Purpose
This tool answers one practical question: for any occupation that files a W-2, what does the pay actually look like, and how does it compare to every other one?
Median wages get reported all the time. What gets reported far less often is the spread. Two occupations with the same median can be nothing alike in practice. One might pay nearly every worker close to that middle number; the other might run from a low floor to a high ceiling, with the median sitting over a wide gap. That difference is the whole story of a job's pay, and it lives in the 10th and 90th percentiles, not the median. This tool makes the full distribution visible for all 830 occupations BLS tracks, filterable by field and by geography, with each occupation's wage range shown in a single bar.
The national numbers get cited constantly. What's harder to see is how a given occupation's pay is shaped, and how that shape changes from one metro area to the next. A registered nurse, a welder, and a paralegal can share a headline median and live in completely different wage realities. This tool is built to let you see that at a glance, and to move from the national picture down to any of 393 metro areas without losing the comparison.
What this page is
Every chart we publish should be something you can check, question, and rebuild yourself. This tool is the simplest version of that promise we've ever shipped, because it adds almost nothing of our own to the underlying data. It is a restructuring of a single public BLS file into a form you can sort, filter, and read. There is no model, no weighting, no estimation, and no editorial math. Where BLS published a number, we show that number. Where BLS withheld one, we show a blank.
That makes the methodology short, and we'd rather it be short and true than long and padded. This page lays out exactly which file we used, the one place where we compute anything at all, how we handled the values BLS suppresses, and how the whole thing was checked against the source before it went up. None of it is proprietary. The data is public, the occupational classification is industry-standard SOC, and our only contribution is the presentation.
The data source
Everything here comes from one file: the U.S. Bureau of Labor Statistics' Occupational Employment and Wage Statistics (OEWS) May 2025 release, specifically the all_data_M_2025 workbook BLS publishes at bls.gov/oes. OEWS is the federal government's primary source for occupational wage estimates. It covers wage-and-salary workers, the people who receive a W-2, and excludes the self-employed and unincorporated business owners, who don't show up in the underlying employer survey.
We use two slices of that file. The national view is the set of 830 detailed occupations BLS estimates at the national, all-industry level. The metro view is the same occupations as reported for each of 393 metropolitan statistical areas. Both come from the identical source file and the identical columns; the only difference is the geography level. For each occupation in each geography, the file gives us employment, mean and median wages on both an hourly and annual basis, and four points along the wage distribution: the 10th, 25th, 75th, and 90th percentiles. Those are the numbers the tool displays.
Step 1: Restructure, don't transform
The core of this tool is structural. We took the BLS file, which is a flat table of hundreds of thousands of rows, and reorganized it into a national table plus a lookup of 393 metro areas, so a reader can switch geography and re-sort without reloading anything. No value is changed in that process. Employment counts, wages, and percentiles are carried straight across. If you opened the BLS file and found the row for a given occupation in a given area, every number you'd read there is the number this tool shows.
We say this plainly because it's the load-bearing claim of the whole piece: this is a presentation layer over public data, not an analysis that produces new figures. The check described below exists to prove that claim rather than ask you to take it on faith.
Step 2: The one place we calculate anything
There is exactly one spot where the tool produces a number BLS didn't directly print, and we flag every instance of it.
BLS sometimes publishes an annual median wage for an occupation but no hourly median, because for some occupations annual pay is what's collected and hourly is derived rather than measured. BLS's own convention is to relate the two using a 2,080-hour work year (40 hours a week across 52 weeks). Where the file gives us an annual median but leaves the hourly median blank, we fill the hourly figure as the annual median divided by 2,080, rounded to the cent, exactly as BLS would compute it. Every such value is marked with an asterisk and a footnote so you can tell a derived hourly wage from one BLS measured directly. In this release that applies to 7,824 cells across the national and metro views, and the derivation is applied identically in both.
We do this for one reason: it lets the hourly column be sortable and complete without inventing anything. The relationship is definitional, not estimated. We verified before shipping that in every row where BLS published both an hourly and an annual median, the published hourly figure already equals the annual divided by 2,080 within rounding, with no exceptions across the entire file. So the fill reproduces a number BLS would have printed, rather than guessing at one. We never apply it to a value BLS actually suppressed: if both the hourly and the annual median are withheld, the cell stays blank.
Step 3: How we handle suppressed values
BLS suppresses individual estimates when a cell is too thin to report reliably or would risk identifying a specific employer. In the source file these appear as flags rather than numbers. We render every suppressed value as a blank ("—") rather than a zero, because zero would be both wrong and misleading: the jobs and wages exist, BLS just won't publish a figure it can't stand behind. A blank in this tool always means "BLS did not publish this," never "none."
This matters most in small metro areas and thinly staffed occupations, where suppression is most common. It also produces a pattern that surprised us when we first saw it: in some small occupations, the median, 75th, and 90th percentiles all land on the same dollar figure. That isn't an error and it isn't a cap. We spent real time chasing it down, and it gets its own section below.
When wages flatten at the top
The first time we saw an occupation where the median, 75th percentile, and 90th percentile were all the exact same number, our instinct was that something had broken. A median that equals the top of the range looks like a bug. So we treated it as one until we could prove otherwise.
It is not a bug. It is a real feature of how OEWS percentiles work, and once we understood it, it turned into one of the more interesting things the tool can show you.
Here is the mechanism. OEWS doesn't collect exact salaries. Employers report how many of their workers fall into each of a set of wage bands, and the percentiles are interpolated from those banded counts. A percentile is just a cut point: the median is the wage that half the workers fall below, the 75th is where three-quarters fall below, the 90th where nine-tenths fall below. When a large share of a small occupation's workers are paid at or near the same wage, all three of those cut points can land inside the same dense cluster, and the interpolation returns the same number for each. The distribution looks like a wall: a spread at the bottom, then a spike where most people sit. Equal percentiles are mathematically allowed; only a percentile that went down as you moved up the distribution would signal an error, and we checked for that across the whole file and found none.
We didn't want to take the mechanism on faith, so we validated specific cases by hand against the source file. Two are worth describing because they show the two different stories this pattern can tell.
In Lawton, Oklahoma, family medicine physicians (SOC 29-1215) show a median, 75th, and 90th percentile all pinned at $448,460, on a base of about 80 workers. This is the thin-sample version. With a small headcount and a couple of dominant local employers, most of the reported physicians land in the same upper wage band, and the top half of the local distribution collapses to a single point. The number is real and BLS-published; it's telling you there's essentially no measured spread in the upper half of that small local market.
In Seattle, production, planning, and expediting clerks (SOC 43-5061) show the same flattening, with the median, 75th, and 90th all at $81,850, but on a base of more than 13,000 workers. This is not a thin sample. This is genuine wage compression in a large, well-measured occupation, and it has a structural cause: Seattle's planning-clerk workforce is concentrated in a small number of very large, heavily unionized or step-scheduled employers, the kind of administered pay environment where a huge share of workers sit at the same negotiated rate. A telling fingerprint shows up here that doesn't appear in the thin-sample case: the mean wage sits below the median, which is what you get when the mass of workers is bunched near the top of the range rather than trailing off into a high tail.
Across the full file, 819 occupation-and-area combinations show this exact flattening, every one of them at the metro level and none at the national level, which fits the mechanism: national cells are large and well-spread, so their percentiles separate cleanly. The median such cell has about 100 workers, so most of these are thin-sample cases like Lawton, but a meaningful minority are large-headcount cases like Seattle, where the flat top is a real statement about how that local labor market pays.
We left every one of these exactly as BLS reports them. We flag the pattern here because a reader who hits one deserves to know it's neither a glitch nor a ceiling: it's the data telling you, truthfully, that the upper half of that occupation's local pay is flat, and that the why behind it is worth a second look.
How we built and checked it
This tool was produced through a human-directed, AI-assisted process, and we want to be precise about what that means rather than wave at it.
An editor set the structure and reviewed the work at each stage. An AI assistant did the heavy lifting of restructuring the BLS file into the interactive format you see, alongside the editor's own manual spot-checks against the source. Then, in a separate pass working from the original BLS file, an AI assistant independently reconciled the result, value by value, against the source data.
That reconciliation was not a sample. It compared every line item: all 830 national occupations and all 141,015 metropolitan occupation cells across 393 metro areas, roughly 1.1 million individual values covering employment, mean and median wages, and every published percentile. It ran in both directions, confirming that every value in the tool traces back to the BLS file and that nothing in the file's qualifying rows was silently dropped, duplicated, or altered. It confirmed that each BLS suppression flag renders as a blank and each blank corresponds to a genuine suppression, so no withheld value was ever turned into a number and no real number was lost. And it separately verified the 7,824 derived hourly medians, checking that each equals the annual median divided by 2,080 and appears only where BLS published an annual figure but no hourly one. The comparison found an exact match throughout.
We're describing this as an automated, value-by-value comparison rather than a human line-by-line review, because that's what it was, and the distinction is the honest one. The check is thorough, but it is a check of fidelity to the BLS file. The BLS file itself remains the authoritative reference, and if you find a number here that disagrees with the source, the source wins and we want to hear about it.
How to use this tool
The tool opens on national estimates. The Geography box is a combination text field and dropdown: click it to see the full list of areas (the United States plus all 393 metro areas), or start typing a metro name to filter the list as you go, with matches highlighted. Select an area by clicking it, or with the arrow keys and Enter; if your typing narrows the list to a single match, Enter selects it.
When a metro area is selected, a small clear button (✕) appears at the right edge of the Geography box. Clicking it returns the view to national and leaves your occupation search, group filter, and sort untouched. To jump to a different area, click into the box: the current name is selected automatically, so you can type the new one straight over it.
The Reset all button is the bigger hammer. It clears everything at once: geography back to national, occupation search cleared, major-group filter cleared, and sort restored to default. Use the ✕ to change only the geography, and Reset all to start over from a clean state.
The occupation search box filters by occupation title or SOC code, and the major-group dropdown narrows to a single occupational family. On desktop, click any column heading to sort by it. On mobile, use the sort dropdown, and tap a wage-range bar to see the full percentile breakdown for an occupation.
Honest notes and limitations
We'd rather tell you the edges of this than have you find them.
This is a presentation of BLS estimates, not independent measurement. Every number here is BLS's, with all the strengths and caveats of the OEWS survey. OEWS is a model-based estimate built from an employer survey over multiple panels, not a perfect census of paychecks. Small-area, small-occupation cells carry real sampling error, and the published percentiles for a thin cell can be wide. We don't correct or smooth any of this, because the point of the tool is to show you what BLS published, faithfully.
Suppressed cells are genuinely missing, not zero. Where BLS withholds a value, we show a blank. That means some small areas and occupations have gaps, and those gaps are concentrated exactly where you'd expect: small metros and thinly staffed jobs. A blank is never a stand-in for a low number.
The one derived figure is the hourly median, and it's always flagged. We compute it only as annual ÷ 2,080, only where BLS gave us the annual but not the hourly, and never over a suppressed value. It's marked with an asterisk wherever it appears. If you want only BLS-measured figures, read past the asterisks.
Compressed distributions are real, not artifacts. When the median, 75th, and 90th percentiles coincide in an occupation, that's a genuine feature of a flat upper wage distribution, not a data error or a cap, as the "When wages flatten at the top" section explains in full. We leave these exactly as BLS reports them.
It covers W-2 employees only. The self-employed, gig workers paid on a 1099, and unincorporated business owners aren't in the OEWS universe, so they aren't here either. For occupations with heavy self-employment, the picture is partial by construction.
Reproduce it yourself
If you want to rebuild this, you need exactly one thing: the BLS OEWS May 2025 release, the all_data_M_2025 file from bls.gov/oes. Everything in the tool is a restructuring of that file. Filter to the detailed-occupation rows at the national and metropolitan-area levels, carry the employment, wage, and percentile columns across unchanged, render BLS's suppression flags as blanks, and, where you want a complete hourly column, fill any missing hourly median as the annual median ÷ 2,080 and flag it.
That is the whole recipe. There is no proprietary data, no model, and no fitted parameters. If you replicate it and get a number different from ours, the BLS file is the referee, and we want to know.
Enjoying this post?
Tell others about it.
More articles like this
Fetching Related Posts
There are no other articles tagged with Visualization