Alex Garcia's Blog

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:

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:

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:

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!