I'm working on libfec, a new FEC file parser and CLI tool
2025-10-13 by Alex Garcia
tl;dr —
libfec
is a new tool for working with Federal Election Committee (FEC) data, for exploring and analyzing federal campaign finance reports. It's reminiscent of past tools like FastFEC and fecfile, but is faster, supports exporting to CSV/JSON/ SQLite/Excel, and works directly with the OpenFEC API to simplify common operations. Try it today!
Building a tool for working with FEC data is a rite of passage for data journalists and newsroom engineers.
The earliest open-source FEC parser I could find is FEC-Scraper, created 14 years ago — a collection of Python scripts published by journalists at USA Today. Then a year later Fech (Ruby) from the New York Times, followed by fec-parse (Node.js) in 2015, fecfile (Python) in 2018, and FastFEC (C) from the Washington Post in 2021.
All these tools perform a similar task — converting the FEC's
custom .fec
file format
into more usable formats like CSV or JSON.
And libfec
is no different! It's fast, easy to install, and works directly with the OpenFEC API to make your life easier.
¶ Introducing libfec
libfec
is a single-binary CLI tool written in Rust, with a variety of ways to install.
The libfec export
command allows you to export filing itemizations to CSVs/JSON/SQLite/Excel for easier analysis. Let's try it with FEC-1903265
, the 2025 Q2 financial report for Alex Padilla, senator from California. To export the receipt itemizations from that filing, we can run:
libfec export \
--target receipts \
FEC-1903265 \
-o padilla_contributors-2025-Q2.csv
Behind the scenes, libfec
will download and cache the raw 1903265.fec
file for this filing, and export all receipt itemizations (contributions, incoming transfers) into a CSV file at padilla_contributors-2025-Q2.csv
.
Now we can use other tools for further analysis. For example, let's get all individual contributors from California who donated to Alex Padilla in this reporting period, using csvkit
:
csvgrep padilla_contributors-2025-Q2.csv -c entity_type -m IND \
| csvgrep -c contributor_state -m CA \
| csvcut -c contributor_first_name,contributor_last_name,contributor_city,contribution_amount \
| head -10
contributor_first_name,contributor_last_name,contributor_city,contribution_amount
David,Alexander,Carlsbad,2000.00
Jan,Altemus,Los Angeles,250.00
L Sue,Alza,Los Angeles,250.00
Cynthia,Amador Diaz,Monrovia,50.00
Gilberto,Amparo,Northridge,250.00
John H,Anderson,San Diego,20.00
John H,Anderson,San Diego,20.00
John H,Anderson,San Diego,20.00
Olle,Andersson,Carlsbad,25.00
Or you can use Python, R, Node.js, DuckDB, Excel — any tool that works with CSVs!
Another example: this Politico article from Jessica Piper (January 2023) details how George Santos, former New York Congressman, reported 40 expenditure transactions that were valued between $199.00 and $199.99. Campaigns are required to keep receipts for expenses greater than $200, so having multiple expenditures right below the cutoff is a bit suspicous.
To get a list of all expenditures reported by George Santos' campaign committee C00721365
in 2022, we could run:
$ libfec export C00721365 \
--target disbursements \
--cycle 2022 \
-o santos22.csv
✓ Cached 15 filings, 1.04 MiB
Exported 1558 rows to santos22.csv in 1s 977ms 586µs
Now santos22.csv
contains all Schedule B disbursements from all FEC reports filed by the Santos campaign! Including the 40 suspicous transactions, which we can confirm with a DuckDB query:
$ duckdb :memory: 'SELECT count(*) FROM "santos22.csv" WHERE expenditure_amount BETWEEN 199.0 AND 199.99'
┌──────────────┐ │ count_star() │ │ int64 │ ├──────────────┤ │ 40 │ └──────────────┘
¶ libfec
on commitees & contests
Look at the above example a little closer — all we provided was a committee ID for Santos (libfec export C00721365 ...
). With just the committee ID, libfec
was able to resolve the 15 relevant filings that C00721365
filed for the 2022 cycle, even accounting for amended filings.
This is powerful! Previous open-source FEC tooling required you to supply individual FEC filing IDs, which often meant searching up a committee's filing history, copy+pasting FEC IDs, and manually accounting for amended filings. With libfec
, it's handled for you!
Let's try another sample. I'm tracking the near-to-me 2026 California 40th district, where incumbent Young Kim is runnings to hold onto her seat. If I want to get filings for all candidates running in that race, I can run:
$ libfec export \
--election 2026 \
CA40 \
-o ca40-2026.db
Exporting filings to SQLite database at "ca40-2026.db"
Finished exporting 18 filings into ca40-2026.db, in 3 seconds
libfec
found all the candidates running in CA40 and exported their filinings into a SQLite database at ca40-2026.db
.
¶ SQLite as a first-class target
libfec
was built with SQLite in mind, for many reasons!
For one, FEC filings are not just 1 clean CSV file. A single "filing" can have multiple tabular datasets. A "receipt" itemization has columns like contributor_first_name
and contribution_amount
, while a "disbursement" itemization has columns like payee_first_name
and expenditure_amount
.
With SQLite, libfec
stores these different itemization types into different tables, meaning we can store all itemizations from multiple filings all in a single database file.
Also with SQLite, you can write SQL! Let's find the top 10 cities where individuals contributed to any candidate from the CA40 race:
select
contributor_state,
contributor_city,
sum(contribution_amount)
from libfec_schedule_a
where form_type = 'SA11AI'
and entity_type = 'IND'
group by 1, 2
order by 3 desc
limit 10;
contributor_state contributor_city sum(contribution_amount)
------------------- ------------------ --------------------------
CA Los Angeles 233757
NY New York 111062
DC Washington 79642.7
CA Beverly Hills 64100
CA San Francisco 52590.4
MN Minneapolis 35657.3
TX Dallas 33362.7
TX Houston 31549.2
CA Santa Monica 29218.5
NY Manhattan 28000
I was not expecting so many non-California cities...
Now let's find which candidate raised the most from small individual donors compared to large donors:
select
committee_name,
format(
'%f%%',
100.0 * col_a_individual_contributions_unitemized /
(
col_a_individual_contributions_itemized
+ col_a_individual_contributions_unitemized
)
) as percent_small_donor_raised,
format('$%,.2f', col_a_cash_on_hand_close) as cash_on_hand_close
from libfec_F3
where report_code = 'Q2'
order by 2 desc
committee_name percent_small_donor_raised cash_on_hand_close
------------------------------ ---------------------------- --------------------
Esther Kim Varet for Congress 62.068472% $1,308,999.98
Young Kim for Congress 39.489178% $3,938,779.72
Christina Gagnier for Congress 34.705837% $306,572.33
Joe Kerr for California 29.493767% $11,838.09
NINA LINH FOR CONGRESS 1.394278% $104,300.06
And finally, SQLite is great because every programming language already has a great client library for SQLite!
Python has the sqlite3
module,
Node.js now has the builtin node:sqlite3
module,
RSQLite for R users, sqlite3-ruby
for Ruby, even SQLite WASM for in-browser options!
This means that even if you personally use Python but your coworkers likes R, you can use libfec
as a lingua franca to work together: libfec export
to a SQLite database, write SQL to analyze, then whatever language you like for your own visualizations or analysis work.
¶ libfec
is fast
The previous fastest FEC parsing tool was FastFEC from the Washington Post. While libfec
and FastFEC do different things, there is a FastFEC compatible libfec fastfec
command that runs ~1.8x to ~10x faster than FastFEC, depending on the FastFEC installation.
The libfec export
command is fast too! It can parse FEC-1909062
, the 10GB
Mid-Year 2025 report for ActBlue, in ~30 seconds:
libfec export 1909062.fec \
--target receipts \
-o ab-2025-h1.csv
On my machine (2024 MacBook Pro M4 Pro) this takes 31 seconds, exporting a 5.8 GB
CSV with 25 million rows!
libfec
also caches filing files by default, so subsequent exports on the same filings will be even faster. See The libfec
Cache for more details.
¶ A big thanks
Federal campaign finance data is extremely complex and not well documented. libfec
would not be possible for the countless newsroom developers and volunteers who contributed to dozens of open source projects and resources. I am grateful to all who have contributed to these initiatives, and also to these specific folks in no particular order:
- Chris Zubak-Skees, creator of
fec-parser
, advisor on the FastFEC project - Dylan Freedman, creator of the FastFEC project
- Evan Sonderegger, creator of the fecfile project, maintains the most up-to-fec FEC version mappings
- Derek Willis, creator of the Fech project
- Rachel Shorey, creator oof the fec2json project
And many other contributors, maintainers, and other journalist who have put in countless hours digging into the weird intricacies and nuances of federal campaign finance.
¶ I need help finishing libfec
!
I've been working on libfec
for a long time, but it's nowhere near complete! For example:
- Only FEC filings version
8.1+
are supported, so only filings from the last ~10 years years are supported. See issue #1 for more info. - I want to add bindings for other programming languages (Python, WASM, Node.js, Ruby, etc).
- A "watch" feature that can automically sync newly filed reports, using the FEC RSS feeds or APIs
But most importantly, I need people to use it! If you currently use other FEC parsers, give libfec
a try and let me know how it goes!
A rough roadmap of what I'm planning for the near future:
- Support for all FEC versions (#1)
- Python + WASM support
- Extensive examples, recipes, and story re-creations
Want to get involved? Drop a post in the libfec
forum on GitHub, tell us how you want to use libfec
, and try it out today!