NFL Offensive Personnel Usage, All 32 Teams

What personnel packages do NFL offenses use the most?

The table below tracks how often all 32 teams line up in 11, 12, 13, 21, and 22 personnel, and what each grouping is actually worth per play.

Filter by season, week, down, field zone, score margin, or play type to isolate the situations you care about, then switch to efficiency to see EPA per play, yards per play, and success rate with league ranks.

NFL Offensive Personnel Usage by Team

Personnel Grouping Frequency
Loading play-by-play data…
2025 personnel grouping frequency by offense, all offensive plays, no filters applied. Groupings use the standard code: the first digit is running backs, the second is tight ends (so "12" is 1 RB and 2 TE).
#Offense11121321222+ TE2+ RB3+ WRPlays
1ARI51%36%11%0%0%47%0%49%1,059
2ATL46%38%7%9%0%45%10%46%1,032
3BAL30%35%6%18%9%51%28%31%954
4BUF62%12%5%9%12%29%20%63%1,056
5CAR67%21%8%2%1%30%3%68%987
6CHI53%36%8%0%0%45%0%55%1,086
7CIN66%30%1%2%0%31%2%67%1,041
8CLE46%45%4%2%2%51%4%46%1,013
9DAL68%17%2%8%3%23%12%69%1,100
10DEN65%14%2%8%8%24%16%67%1,072
11DET62%25%5%3%0%30%6%63%1,049
12GB61%33%1%3%0%35%3%57%990
13HOU72%12%1%7%1%14%11%71%1,072
14IND64%26%9%1%0%36%1%64%1,002
15JAX71%22%1%4%0%24%4%68%1,070
16KC58%31%7%4%0%38%5%57%1,042
17LAC58%6%2%15%7%15%29%69%1,075
18LAR60%9%31%0%0%40%0%60%1,056
19LV58%35%6%0%0%42%1%58%936
20MIA42%12%1%30%8%21%44%42%939
21MIN65%20%3%8%4%27%12%65%939
22NE52%21%0%19%6%28%26%51%1,013
23NO69%19%7%0%0%30%1%70%1,030
24NYG61%33%5%1%0%38%1%61%1,073
25NYJ68%20%0%9%2%23%11%68%997
26PHI63%29%6%1%0%36%1%61%988
27PIT49%33%13%2%1%48%4%39%971
28SEA42%28%3%14%9%43%24%45%994
29SF43%11%0%37%7%20%45%43%1,060
30TB70%21%3%5%0%24%5%71%1,056
31TEN73%23%1%3%0%24%3%71%982
32WAS59%30%5%2%1%37%3%60%969
Hover or tap any cell for EPA per play, yards per play and success rate with league ranks. Efficiency needs 20+ plays to show. Kneels, spikes and plays with incomplete personnel data are excluded.
'; }).join('') + '
'+SIX_OL+'
'; tog.addEventListener('click', function(){ var open = box.hasAttribute('hidden'); if (open) box.removeAttribute('hidden'); else box.setAttribute('hidden',''); tog.setAttribute('aria-expanded', open ? 'true' : 'false'); tog.textContent = open ? 'Hide grouping descriptions' : 'What do the groupings mean?'; }); } function wireModes(){ Array.prototype.forEach.call($('pg-modes').children, function(b){ b.addEventListener('click', function(){ S.mode = b.dataset.m; Array.prototype.forEach.call($('pg-modes').children, function(o){ o.setAttribute('aria-pressed', o === b ? 'true' : 'false'); }); hideTip(); render(); }); }); } function clearAll(){ ['weeks','qtrs','downs','zones','rbs','tes','wrs'].forEach(function(k){ S[k] = new Set(); }); S.play = 'all'; S.dist = [null,null]; S.margin = [null,null]; S.neutral = false; S.open = null; var keep = Array.from(S.seasons); S.seasons = new Set(keep.length ? [Math.max.apply(null, keep)] : []); closePop(); hideTip(); syncBar(); render(); } function activeCount(){ var n = 0; DEFS.forEach(function(d){ if (!isDefault(d)) n++; }); return n; } /* ---------- aggregate ---------- */ function keep(D, i, ignoreWeek){ if (!ignoreWeek && S.weeks.size && !S.weeks.has(D.w[i])) return false; if (S.qtrs.size && !S.qtrs.has(D.q[i])) return false; if (S.downs.size && !S.downs.has(D.d[i])) return false; if (S.zones.size && !S.zones.has(D.z[i])) return false; if (S.play === 'drop' && D.b[i] !== 1) return false; if (S.play === 'run' && D.b[i] !== 0) return false; if (S.dist[0] !== null && (D.dist[i] < S.dist[0] || D.dist[i] > S.dist[1])) return false; if (S.margin[0] !== null && (D.m[i] < S.margin[0] || D.m[i] > S.margin[1])) return false; if (S.rbs.size && !(S.rbs.has(D.rb[i]) || (S.rbs.has(3) && D.rb[i] >= 3))) return false; if (S.tes.size && !(S.tes.has(D.te[i]) || (S.tes.has(3) && D.te[i] >= 3))) return false; if (S.wrs.size && !(S.wrs.has(D.wr[i]) || (S.wrs.has(4) && D.wr[i] >= 4))) return false; return true; } function bucket(){ return {n:0, epa:0, yds:0, sc:0}; } function add(b, D, i){ b.n++; b.epa += D.epa[i]; b.yds += D.yds[i]; b.sc += D.sc[i]; } function blank(){ var o = {plays:0, heavy:0, spread:0, other:0, g:{}, exact:{}, cell:{}, all:bucket()}; GROUPS.forEach(function(c){ o.cell['g'+c] = bucket(); }); SUMS.forEach(function(s){ o.cell[s.k] = bucket(); }); return o; } function tally(row, D, i){ row.plays++; add(row.all, D, i); var rb = D.rb[i], te = D.te[i], wr = D.wr[i], g = D.grp[i]; row.g[g] = (row.g[g]||0)+1; if (row.cell['g'+g]) add(row.cell['g'+g], D, i); if (te >= 2) add(row.cell.te2, D, i); if (rb >= 2) add(row.cell.rb2, D, i); if (wr >= 3) add(row.cell.wr3, D, i); if (rb >= 2 || te >= 2) row.heavy++; else if (wr >= 3) row.spread++; else row.other++; var ex = rb+'-'+te+'-'+wr; row.exact[ex] = (row.exact[ex]||0)+1; } function aggregate(){ var teams = {}, league = blank(); Array.from(S.seasons).forEach(function(y){ var D = DB[y]; if (!D) return; for (var i=0;i
'+ '
'+INFO[key]+'
', false); } function showTip(cell, team, key, label, tap){ var A = LAST; if (!A) return; var b = A.teams[team].cell[key]; var html = '
'+team+' \u00b7 '+label+'
'; if (b.n < MIN_EPA_PLAYS){ html += '
Only '+b.n+' play'+(b.n===1?'':'s')+ ' match these filters \u2014 below the '+MIN_EPA_PLAYS+ '-play minimum, so efficiency is not shown.
'; } else { var R = A.ranks[key], rows = [ ['EPA/play', sgn(b.epa/b.n), R.epa[team]], ['Yards/play', (b.yds/b.n).toFixed(1), R.yds[team]], ['Success', (100*b.sc/b.n).toFixed(1)+'%', R.sc[team]] ]; html += '
' + rows.map(function(r){ return ''+r[0]+''+r[1]+''+ord(r[2])+' of '+R.size+''; }).join('') + '
'; if (R.size < 32) html += '
Ranked among the '+R.size+ ' offenses with '+MIN_EPA_PLAYS+'+ plays here.
'; } placeTip(cell, html, tap); } function wireCell(td, team, key, label){ td.classList.add('pt-hascell'); td.tabIndex = 0; /* Hover is mouse-only. On touch, a tap synthesises mouseenter immediately before click, which would open then instantly close the panel — so gate on pointerType and let the click handler own the tap. */ if (window.PointerEvent){ td.addEventListener('pointerenter', function(e){ if (e.pointerType === 'mouse') showTip(td, team, key, label, false); }); td.addEventListener('pointerleave', function(e){ if (e.pointerType === 'mouse') hideTip(); }); } else { td.addEventListener('mouseenter', function(){ showTip(td, team, key, label, false); }); td.addEventListener('mouseleave', hideTip); } td.addEventListener('focus', function(){ showTip(td, team, key, label, false); }); td.addEventListener('blur', hideTip); td.addEventListener('click', function(e){ e.stopPropagation(); if (TIPPED === td) hideTip(); else showTip(td, team, key, label, true); }); } /* ---------- charts ---------- */ /* All three charts share a 300x180 viewBox so they render at identical size next to each other, and the CSS caps them at 340px so they cannot balloon on a very wide screen. */ var CW = 300, CH = 180; function niceStep(max){ var steps = [2, 5, 10, 20, 25, 50]; for (var i = 0; i < steps.length; i++) if (max / steps[i] <= 4) return steps[i]; return 100; } function donut(row){ var slices = [ {k:'heavy', n:row.heavy, c:C_RED, l:'Heavy (2+ RB/TE)'}, {k:'spread', n:row.spread, c:C_G1, l:'3+ WR'}, {k:'other', n:row.other, c:C_G2, l:'Other'} ]; var R = 52, CX = 76, CY = 88, SW = 21, CIRC = 2*Math.PI*R, off = 0, g = ''; slices.forEach(function(sl){ if (!sl.n) return; var len = CIRC * sl.n / row.plays; g += ''; off += len; }); var big = slices.slice().sort(function(a,b){ return b.n-a.n; })[0]; g += ''+ ''; slices.forEach(function(sl, i){ var y = 52 + i*38; g += ''+ ''+ ''; }); return '
' + svg(CW, CH, g) + '
Three mutually exclusive buckets, so these total 100% \u2014 '+ 'unlike the table columns, which overlap.
'; } function trend(pts, team){ var live = pts.filter(function(p){ return p.row && p.row.plays >= 5; }); if (live.length < 2){ return '
Not enough weeks match these '+ 'filters to draw a trend.
'; } var L = 26, R = 8, T = 10, B = 26, iw = CW-L-R, ih = CH-T-B; var x = function(i){ return pts.length < 2 ? L : L + iw*i/(pts.length-1); }; var y = function(v){ return T + ih*(1 - v/100); }; var g = ''; [0,25,50,75,100].forEach(function(v){ g += ''+ ''; }); var series = [{k:'te2',c:C_INK,l:'2+ TE'},{k:'rb2',c:C_RED,l:'2+ RB'},{k:'wr3',c:C_G1,l:'3+ WR'}]; series.forEach(function(sr){ var run = [], segs = []; pts.forEach(function(p, i){ if (p.row && p.row.plays >= 5) run.push([x(i), y(pct(p.row.cell[sr.k].n, p.row.plays))]); else { if (run.length > 1) segs.push(run); run = []; } }); if (run.length > 1) segs.push(run); segs.forEach(function(seg){ g += ''; seg.forEach(function(q){ g += ''; }); }); }); pts.forEach(function(p, i){ if (pts.length <= 10 || i === 0 || i === pts.length-1 || (i+1) % 3 === 0) g += ''; }); var order = []; pts.forEach(function(p){ if (order.indexOf(p.season) < 0) order.push(p.season); }); if (order.length > 1) order.forEach(function(yr){ var idx = []; pts.forEach(function(p, i){ if (p.season === yr) idx.push(i); }); g += ''; }); var key = series.map(function(sr){ return ''+sr.l+''; }).join(''); return '
' + svg(CW, CH, g) + '
'+key+'
' + '
Every filter except week. Weeks under 5 plays are skipped.'+ '
'; } /* Horizontal bars: groupings down the Y axis, share of plays along X, and the gap to league average called out above each bar. */ /* Vertical bars: groupings across the X axis, share of plays up the Y, with the gap to league average called out above each bar. */ function groupBars(row, league){ var ranked = Object.keys(row.g).map(Number) .sort(function(a,b){ return row.g[b]-row.g[a]; }); /* A grouping with three snaps is a rounding error, not a tendency — it would occupy a slot reading "0%". Keep it out unless there is nothing else. */ var codes = ranked.filter(function(c){ return row.g[c] >= 5; }).slice(0, 6); if (codes.length < 3) codes = ranked.slice(0, 3); if (!codes.length) return '
No plays match these filters.
'; var L = 28, R = 8, T = 26, B = 26; var plotW = CW - L - R, plotH = CH - T - B; var top = Math.max.apply(null, codes.map(function(c){ return Math.max(pct(row.g[c], row.plays), pct(league.g[c]||0, league.plays)); })); var step = niceStep(top), axisMax = Math.max(step, Math.ceil(top/step)*step); var y = function(v){ return T + plotH * (1 - v/axisMax); }; var slot = plotW / codes.length, barW = Math.min(38, slot * 0.56); var g = ''; for (var v = 0; v <= axisMax + 0.001; v += step){ g += ''+ ''; } codes.forEach(function(c, i){ var p = pct(row.g[c], row.plays), lp = pct(league.g[c]||0, league.plays); var diff = p - lp; var cx = L + slot*(i + 0.5), x0 = cx - barW/2; var barTop = y(p), base = y(0); g += ''; /* dashed link when the league mark sits clear of the bar top */ if (Math.abs(y(lp) - barTop) > 8){ g += ''; } g += ''; var lab = Math.min(barTop, y(lp)); g += ''; g += ''; g += ''; }); return '
' + svg(CW, CH, g) + '
Share of plays up the side. Bar is this offense, the black '+ 'tick is the league average, and the figure on top is the gap between them.
'; } /* ---------- render ---------- */ /* Rank always means "position when this column is ordered best-first", so it stays correct when the table is sorted ascending. Ties share a rank. */ function rankBy(names, valueOf){ var pool = names.filter(function(t){ return valueOf(t) !== null; }) .sort(function(a, b){ return valueOf(b) - valueOf(a); }); var map = {}, prevVal = null, prevRank = 0; pool.forEach(function(t, i){ var v = valueOf(t); if (prevVal !== null && v === prevVal) map[t] = prevRank; else { map[t] = i + 1; prevRank = i + 1; } prevVal = v; }); return map; } function cellValue(b, teamPlays){ if (S.mode === 'epa') return b.n >= MIN_EPA_PLAYS ? b.epa/b.n : null; return pct(b.n, teamPlays); } function render(){ var A = aggregate(); LAST = A; var L = A.league; var names = Object.keys(A.teams); var yrs = Array.from(S.seasons).sort(); var scope = yrs.length > 1 ? yrs[0]+'\u2013'+yrs[yrs.length-1] : String(yrs[0] || ''); $('pg-sub').innerHTML = ''+fmt(L.plays)+' plays \u00b7 '+names.length+' offenses \u00b7 '+ scope + (activeCount() ? ' \u00b7 filtered' : ' \u00b7 all plays'); var body = $('pg-body'); if (!L.plays){ body.innerHTML = ''; body.appendChild(el('div','pt-msg','No plays match those filters'+ 'Widen the score margin or clear a filter to bring rows back.')); return; } var epaMode = S.mode === 'epa'; function metricOf(t, c){ var r = A.teams[t]; return cellValue(r.cell[c.key], r.plays); } var COLS = GROUPS.map(function(c){ return {key:'g'+c, label:String(c), sort:c, cls:'pt-g'}; }).concat(SUMS.map(function(s, i){ return {key:s.k, label:s.l, sort:s.k, cls:'pt-g'+(i===0?' pt-sfirst':''), summary:true}; })); /* scale for shading */ var maxU = 0, maxE = 0; names.forEach(function(t){ var r = A.teams[t]; COLS.forEach(function(c){ var b = r.cell[c.key]; maxU = Math.max(maxU, pct(b.n, r.plays)); if (b.n >= MIN_EPA_PLAYS) maxE = Math.max(maxE, Math.abs(b.epa/b.n)); }); }); if (!maxE) maxE = 0.1; var sortCol = null; COLS.forEach(function(c){ if (String(c.sort) === String(S.sort)) sortCol = c; }); var rank = (S.sort === 'team' || S.sort === 'plays' || !sortCol) ? rankBy(names, function(t){ return A.teams[t].plays; }) : rankBy(names, function(t){ return metricOf(t, sortCol); }); if (S.sort === 'team') names.sort(function(a,b){ return S.dir*a.localeCompare(b); }); else if (S.sort === 'plays') names.sort(function(a,b){ return S.dir*(A.teams[b].plays - A.teams[a].plays); }); else { var col = sortCol || COLS[0]; names.sort(function(a,b){ var va = metricOf(a, col), vb = metricOf(b, col); if (va === null && vb === null) return a.localeCompare(b); if (va === null) return 1; if (vb === null) return -1; return S.dir*(vb - va) || a.localeCompare(b); }); } var tbl = el('table','pt-grid'); tbl.appendChild(el('caption', null, epaMode ? 'EPA per play by personnel grouping. Positive is good for the offense. '+ 'Cells need '+MIN_EPA_PLAYS+'+ plays; thinner samples show a dash.' : 'Each grouping as a share of the offense\u2019s plays. Hover or tap a cell for '+ 'EPA per play, yards per play and success rate.')); function sortBtn(key, label, cls){ var th = el('th', cls || null); th.scope = 'col'; if (String(S.sort) === String(key)){ var asc = (key === 'team') ? S.dir > 0 : S.dir < 0; th.setAttribute('aria-sort', asc ? 'ascending' : 'descending'); } var b = el('button', null, label); b.type = 'button'; b.addEventListener('click', function(){ if (String(S.sort) === String(key)) S.dir = -S.dir; else { S.sort = key; S.dir = 1; } hideTip(); render(); }); th.appendChild(b); return th; } var thead = el('thead'), hr = el('tr'); hr.appendChild(el('th','pt-rank','#')); hr.appendChild(sortBtn('team','Offense','pt-team')); COLS.forEach(function(c){ var th = sortBtn(c.sort, c.label, c.cls); if (INFO[c.key]){ th.classList.add('pt-hascell'); if (window.PointerEvent){ th.addEventListener('pointerenter', function(e){ if (e.pointerType === 'mouse') showInfo(th, c.label, c.key); }); th.addEventListener('pointerleave', function(e){ if (e.pointerType === 'mouse') hideTip(); }); } else { th.addEventListener('mouseenter', function(){ showInfo(th, c.label, c.key); }); th.addEventListener('mouseleave', hideTip); } } hr.appendChild(th); }); hr.appendChild(sortBtn('plays','Plays','pt-plays')); thead.appendChild(hr); tbl.appendChild(thead); var ncols = COLS.length + 3; var tb = el('tbody'); names.forEach(function(t){ var r = A.teams[t], tr = el('tr','pt-row'); if (S.open === t) tr.classList.add('open'); tr.appendChild(el('td','pt-rank', rank[t] ? String(rank[t]) : '\u2013')); var th = el('th','pt-team'); th.scope = 'row'; var btn = el('button', null, t); btn.type = 'button'; btn.setAttribute('aria-expanded', S.open === t ? 'true' : 'false'); btn.addEventListener('click', function(e){ e.stopPropagation(); hideTip(); S.open = (S.open === t) ? null : t; render(); }); th.appendChild(btn); tr.appendChild(th); COLS.forEach(function(c){ var b = r.cell[c.key]; var td = el('td', c.cls); if (!b.n){ td.className = c.cls + ' pt-nil'; td.innerHTML = '\u2013'; tr.appendChild(td); return; } if (epaMode){ var e = b.n >= MIN_EPA_PLAYS ? b.epa/b.n : null; if (e === null){ td.className = c.cls + ' pt-nil'; td.innerHTML = '\u2013'; } else { td.style.background = heatEpa(e/maxE); if (Math.abs(e)/maxE > 0.62) td.style.color = '#fff'; td.innerHTML = ''+sgn(e)+''; } } else { var pv = pct(b.n, r.plays); td.style.background = heat(pv/(maxU||1)); if (pv/(maxU||1) > 0.6) td.style.color = '#fff'; td.innerHTML = ''+(pv<10?pv.toFixed(1):r0(pv))+'%'+ ''+fmt(b.n)+''; } wireCell(td, t, c.key, c.summary ? c.label : c.label+' personnel'); tr.appendChild(td); }); tr.appendChild(el('td','pt-plays', fmt(r.plays))); tb.appendChild(tr); if (S.open === t) tb.appendChild(detail(t, r, L, ncols)); }); tbl.appendChild(tb); var tf = el('tfoot'), fr = el('tr'); fr.appendChild(el('td','pt-rank','')); var fth = el('th','pt-team','League'); fth.scope = 'row'; fr.appendChild(fth); COLS.forEach(function(c){ var b = L.cell[c.key]; fr.appendChild(el('td', c.cls, epaMode ? (b.n ? sgn(b.epa/b.n) : '\u2013') : r0(pct(b.n, L.plays))+'%')); }); fr.appendChild(el('td','pt-plays', fmt(L.plays))); tf.appendChild(fr); tbl.appendChild(tf); body.innerHTML = ''; body.appendChild(tbl); } function detail(team, r, L, span){ var tr = el('tr','pt-det'), td = el('td'); td.colSpan = span; var wrap = el('div','pt-dwrap'); var head = el('div','pt-dhead'); head.innerHTML = ''+team+''+fmt(r.plays)+' plays match the current filters \u00b7 '+ sgn(r.all.epa/r.all.n)+' EPA/play \u00b7 '+(r.all.yds/r.all.n).toFixed(1)+' yds/play \u00b7 '+ (100*r.all.sc/r.all.n).toFixed(1)+'% success'; wrap.appendChild(head); var c1 = el('div','pt-card'); c1.appendChild(el('div','pt-ctitle','Personnel mix')); c1.appendChild(el('div', null, donut(r))); wrap.appendChild(c1); var c2 = el('div','pt-card'); c2.appendChild(el('div','pt-ctitle','Week by week')); c2.appendChild(el('div', null, trend(weekly(team), team))); wrap.appendChild(c2); var c3 = el('div','pt-card'); c3.appendChild(el('div','pt-ctitle','Against league average')); c3.appendChild(el('div', null, groupBars(r, L))); wrap.appendChild(c3); td.appendChild(wrap); tr.appendChild(td); return tr; } boot(); })();

Which NFL Teams Used 11 Personnel the Most in 2025?

In 2025, the Titans (73%), Texans (72%) and Jaguars (71%) built almost everything out of 11 personnel despite changing trends throughout the league.

At the other end, the Ravens used 11 personnel on 30% of snaps.

That was nearly half of the league average.

Heavy Personnel: 12 and 13 Personnel Usage by Team

The emerging trend in 2025 was a shift toward heavier personnel, led by the Rams utilizing three tight ends on 31% of their offensive snaps.

Look at the league average of plays with two or more tight ends on the field:

2021: 28%
2022: 27%
2023: 26%
2024: 30%
2025: 33%

With the way NFL teams spent money in free agency and used draft capital this offseason, it is clear that the trend toward heavier personnel will stick around in 2026.

What Is 11 Personnel?

11 personnel is a football formation that features one running back and one tight end.

What Is 12 Personnel?

12 personnel is a football formation that features one running back and two tight ends.

What Is 13 Personnel?

13 personnel is a football formation that features one running back and three tight ends.

What Is 21 Personnel?

21 personnel is a football formation that features two running backs and one tight end.

What Is 22 Personnel?

22 personnel is a football formation that features two running backs and two tight ends.

Articles
Share