IRS 990 Data Download: Nonprofit Financials, Executive Compensation and Grants from the E-File Corpus
Published 2026-08-04 · DataForge team
US tax-exempt organizations file Form 990 annually, and by law those returns are public: revenue, expenses, executive salaries, grants given and received — for every foundation, hospital system, university and local charity in the country. Since 2016 the IRS publishes e-filed returns in bulk. The catch is the format: millions of deeply nested XML files across multiple schema versions. This guide maps the free sources, shows what parsing actually involves, and covers the analyses this data unlocks once it’s tabular.
The official sources
IRS e-file bulk downloads. The main event: zip archives of e-filed 990, 990-EZ and 990-PF returns as XML, published in batches, covering submissions from 2019 onward (earlier years were previously on AWS). Complete and free — and the reason “irs 990 extract” is a search term full of frustration.
Exempt Organizations Business Master File (BMF). A monthly CSV of all ~1.9M recognized tax-exempt orgs: EIN, name, address, NTEE category, ruling date. Easy to use; contains no financials beyond coarse asset/income codes.
Annual Extracts of Tax-Exempt Organization Financial Data. IRS-produced CSVs with ~60 headline financial fields per return. If these fields are all you need, use them — they’re the best free shortcut. No exec-comp detail, no grants, no schedules.
ProPublica Nonprofit Explorer. Excellent free lookup site and API for individual organizations. Not built for bulk analytical export.
Why the XML is hard
Schema versions. The e-file corpus spans years of IRS schema revisions; element paths move (/Return/ReturnData/IRS990/GrossReceiptsAmt vs older variants), and 990 vs 990-EZ vs 990-PF are entirely different documents. A robust parser handles dozens of version/form combinations.
Depth and repetition. Officer compensation is a repeating group (Form990PartVIISectionAGrp) with per-person name, title, hours, and three compensation columns. Grants (Schedule I) and foreign activity (Schedule F) are similar repeating structures. Flattening these into relational tables — returns, officers, grants — is real data modeling, not a one-liner.
Volume. Millions of files, tens of GB compressed. You want a streaming parser and a database, not a folder of XML and xml.etree in a loop… though that is how everyone starts:
import xml.etree.ElementTree as ET
NS = {"irs": "http://www.irs.gov/efile"}
tree = ET.parse("202301349349300000_public.xml")
root = tree.getroot()
ein = root.findtext(".//irs:Filer/irs:EIN", namespaces=NS)
name = root.findtext(".//irs:BusinessNameLine1Txt", namespaces=NS)
revenue = root.findtext(".//irs:IRS990/irs:CYTotalRevenueAmt", namespaces=NS)
for p in root.findall(".//irs:Form990PartVIISectionAGrp", NS):
person = p.findtext("irs:PersonNm", namespaces=NS)
title = p.findtext("irs:TitleTxt", namespaces=NS)
comp = p.findtext("irs:ReportableCompFromOrgAmt", namespaces=NS)
print(name, ein, "|", person, title, comp)
Multiply by ~6 million files and several schema epochs, and you understand the market for pre-parsed 990 data.
Once tabular, the questions are easy. Executive-compensation benchmarking:
-- Median CEO comp among mid-size human-services nonprofits, TY2023
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY o.reportable_comp) AS median_ceo_comp,
COUNT(*) AS orgs
FROM officers o
JOIN returns r USING (return_id)
JOIN orgs g ON g.ein = r.ein
WHERE o.title_norm = 'ceo'
AND r.tax_year = 2023
AND r.total_revenue BETWEEN 1e6 AND 10e6
AND g.ntee_major = 'P'; -- human services
A worked example: building a funder pipeline from Schedule I
To make the grants data concrete, here’s the workflow a development director actually runs. Say you run a youth-services nonprofit in Ohio and need foundations that fund organizations like yours. Step one: find your peer organizations — same NTEE category (O, youth development), same state, similar budget — from the master file joined to returns. Step two: for each peer, pull every grant they received, which means searching grantmakers’ Schedule I rows for the peer’s EIN or name. Step three: aggregate by grantmaker — who funds three or more of your peers is a qualified prospect with demonstrated interest, and the grant amounts calibrate your ask.
-- Foundations funding 3+ Ohio youth-development orgs, with typical grant size
SELECT g.grantmaker_ein,
g.grantmaker_name,
COUNT(DISTINCT g.grantee_ein) AS peers_funded,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY g.grant_amount) AS median_grant
FROM grant_flows g
JOIN orgs p ON p.ein = g.grantee_ein
WHERE p.ntee_major = 'O' AND p.state = 'OH'
GROUP BY g.grantmaker_ein, g.grantmaker_name
HAVING COUNT(DISTINCT g.grantee_ein) >= 3
ORDER BY peers_funded DESC, median_grant DESC;
Doing this from raw XML means parsing every 990-PF in the country to find Schedule I rows that mention your peers — technically possible, practically the reason grant databases (Candid’s Foundation Directory at ~$2,200/year, or our 21.6M-row grant-flows table) exist.
What people build with 990 data
- Grant prospecting. Schedule I rows show exactly which foundations fund which causes at what amounts — fundraisers reverse-engineer funder pipelines from actual grants rather than stated priorities.
- Executive-compensation benchmarks. Boards must justify comp against comparable organizations; 47M officer rows make real comparables possible by size, sector and region.
- Donor due diligence and journalism. Program-expense ratios, related-party transactions, sudden financial swings.
- Market analysis. Nonprofits are the economy’s blind spot — hospitals, universities and associations with billions in revenue that vendors routinely fail to segment because the data lives in XML.
A sizing note for the DIY-curious: the e-file corpus is roughly 30–40GB compressed, several million documents, and a naive single-threaded parse runs for days. The standard approach is a streaming parser (lxml’s iterparse), a version-dispatch table mapping schema epochs to XPath sets, and parallel workers writing Parquet — after which the analytical dataset is surprisingly small (a few GB) and flies in DuckDB. Community projects like the open-source 990 parsers (IRSx, Nonprofit Open Data Collective concordances) are genuinely helpful starting points and deserve credit; they get you the XPath maps, though you still own the pipeline, validation and refresh cycle around them.
Free sources vs. a cleaned dataset
| IRS free files | DataForge dataset | |
|---|---|---|
| Cost | Free | From $149 |
| Headline financials | Annual extract CSVs (~60 fields) | Full parsed financials from 5.8M e-filed returns (2019–2026 submissions) |
| Exec compensation | XML only | 47.5M officer/comp rows, one table |
| Grants | XML only (Schedule I) | 21.6M grant-flow rows, grantmaker↔grantee |
| Org master | BMF CSV | 1.98M orgs joined to returns, plus 214,259 grantmaker profiles |
| Schema handling | Yours | All versions normalized; convention documented |
Honest guidance: if headline financials suffice, the IRS annual extracts are free and genuinely fine. The paid dataset is for the parts locked in XML — executive compensation and grant flows at corpus scale — and for not owning a multi-schema parsing pipeline. Check the free 500-row sample for the exact table shapes.
FAQ
Is 990 data legal to use commercially? Yes. Form 990 is a public document by statute (IRC §6104), and the IRS publishes it for exactly this purpose. Personal donor information is not in the public portion.
Why can’t I find a specific nonprofit? Small orgs (<$50k receipts) file the 990-N postcard with minimal data; churches generally don’t file; paper filers before e-file mandates may be missing from the XML corpus.
How current is it? Organizations file months after fiscal year-end, plus extensions; a tax-year cohort completes roughly 18 months later. Our editions state the submission-date cutoff explicitly.
Which 990 variant will I actually be reading? Three main forms, radically different content. The full 990 (organizations above $200k receipts): complete financials, governance questions, officer compensation, and the schedule system — Schedule I (grants made), Schedule J (detailed exec comp), Schedule L (insider transactions). The 990-EZ (mid-size orgs): abbreviated financials, less schedule detail. The 990-PF (all private foundations regardless of size): investment detail plus the full grants list — the fundraiser’s favorite document. Any corpus-wide analysis must handle all three; a query that only reads full-990 fields silently drops every foundation and small org, which is exactly the kind of bug that produces confident wrong answers.
How do NTEE codes work, and can I trust them? NTEE is the nonprofit sector’s industry taxonomy — a letter (major group: A arts, B education, E health…) plus digits for subcategories. It’s assigned once at recognition, often by an IRS clerk reading a mission statement, and never systematically updated — so a 1985-coded org may have drifted far from its label. Fine for coarse segmentation, dangerous for precise market sizing. Serious segmentation combines NTEE with financial structure (program-revenue share, grant dependence) and, at the high end, mission-text classification.
What can’t I get from 990 data at all? Donor identities (Schedule B is public only in redacted form), anything about churches and most religious orgs (exempt from filing), organizations under $50k receipts (990-N postcard has ~8 fields), and real-time finances — you’re always reading last year at best. Anyone selling “current-year nonprofit revenue” is modeling, not reporting.
Exec comp seems to double-count? People appear in multiple orgs (related entities) and comp splits into columns (base from org, related orgs, other). Sum carefully — our data dictionary documents the convention.