NACHA File Generator: Create ACH Files from JSON
Turn payroll or supplier payments from your software into a valid ACH file
- Template type document txt simple
- Carbone min. v5.4.3+
- Features loop aggregator padding condition
- Target finance payroll bank
Generate NACHA files from your software
Payroll platforms, accounting software and fintechs pay through US banks with NACHA files, also called ACH files: text files of 94-character records in blocks of 10 lines, in the format set by Nacha. Your application sends its payments to the Carbone API as JSON and gets back the ACH file to upload to the bank. In this example, Acme Corporation pays five employees.
NACHA file layout
The first character of each line is its record code:
| Record | Code | Content |
|---|---|---|
| File Header | 1 |
Who sends the file, to which bank, and when |
| Batch Header | 5 |
The company, the payment type and the pay date |
| Entry Detail | 6 |
One line per employee: bank, account, amount in cents, name |
| Batch Control | 8 |
Batch totals: payment count, entry hash (a checksum), amount paid |
| File Control | 9 |
The same totals for the file, plus the block count |
| Filler lines | 9 |
Lines of 94 nines that complete the last block |
NACHA file example
Template
{o.timezone=America/New_York}101 {d.bank.routing}{d.company.id}{d.file.createdAt:formatD('YYMMDDHHmm')}A094101{d.bank.name:upperCase:padr(23)}{d.company.name:upperCase:padr(23)}{d.file.reference:padr(8)}
5220{d.company.name:upperCase:substr(0, 16):padr(16)} {d.company.id}PPD{d.batch.description:upperCase:padr(10)}{d.batch.effectiveDate:formatD('MMM DD'):upperCase}{d.batch.effectiveDate:formatD('YYMMDD')} 1{d.bank.routing:substr(0, 8)}0000001
{d.entries[i]}6{d.entries[i].accountType:ifEQ(savings):show(32):elseShow(22)}{d.entries[i].routing}{d.entries[i].account:padr(17)}{d.entries[i].amount:mul(100):round(0):padl(10, '0')}{d.entries[i].id:padr(15)}{d.entries[i].name:unaccent:upperCase:substr(0, 22):padr(22)} 0{d.bank.routing:substr(0, 8)}{d.entries[i].id:cumCount:padl(7, '0')}
{d.entries[i+1]}8220{d.entries:len:padl(6, '0')}{d.entries[].routing:substr(0, 8):aggSum:mod(10000000000):padl(10, '0')}000000000000{d.entries[].amount:mul(100):round(0):aggSum:padl(12, '0')}{d.company.id} {d.bank.routing:substr(0, 8)}0000001
9000001{d.entries:len:add(4):div(10):ceil:padl(6, '0')}{d.entries:len:padl(8, '0')}{d.entries[].routing:substr(0, 8):aggSum:mod(10000000000):padl(10, '0')}000000000000{d.entries[].amount:mul(100):round(0):aggSum:padl(12, '0'):padr(51)}{d.entries:len:add(3):mod(10):ifLT(9):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(8):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(7):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(6):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(5):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(4):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(3):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(2):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}{d.entries:len:add(3):mod(10):ifLT(1):showBegin}
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999{d.entries:showEnd}
JSON data
{
"file": { "createdAt": "2026-11-12T14:05:00-05:00", "reference": "PR261113" },
"bank": { "name": "Contoso Bank", "routing": "041108291" },
"company": { "name": "Acme Corporation", "id": "1470259836" },
"batch": { "description": "Payroll", "effectiveDate": "2026-11-13" },
"entries": [
{ "id": "EMP00417", "name": "Olivia Bennett", "accountType": "checking", "routing": "071403623", "account": "3820174956", "amount": 2845.16 },
{ "id": "EMP00422", "name": "José Ramírez", "accountType": "savings", "routing": "263071575", "account": "000738291054", "amount": 1932.5 },
{ "id": "EMP00431", "name": "Hannah Kowalski", "accountType": "checking", "routing": "322374184", "account": "59302817", "amount": 3410 },
{ "id": "EMP00438", "name": "Marcus Thompson", "accountType": "checking", "routing": "062109512", "account": "1100493827", "amount": 2276.84 },
{ "id": "EMP00445", "name": "Priya Raghunathan-Sullivan", "accountType": "savings", "routing": "113017346", "account": "7730015529", "amount": 4105.37 }
]
}
Generated ACH file
101 04110829114702598362611121405A094101CONTOSO BANK ACME CORPORATION PR261113
5220ACME CORPORATION 1470259836PPDPAYROLL NOV 13261113 1041108290000001
6220714036233820174956 0000284516EMP00417 OLIVIA BENNETT 0041108290000001
632263071575000738291054 0000193250EMP00422 JOSE RAMIREZ 0041108290000002
62232237418459302817 0000341000EMP00431 HANNAH KOWALSKI 0041108290000003
6220621095121100493827 0000227684EMP00438 MARCUS THOMPSON 0041108290000004
6321130173467730015529 0000410537EMP00445 PRIYA RAGHUNATHAN-SULL 0041108290000005
822000000500831976220000000000000000014569871470259836 041108290000001
9000001000001000000050083197622000000000000000001456987
9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
How to create a NACHA file
1. Write one line per record
Type the fixed values as they are, like the record size 094, and build the other fields with the fixed-width formatters: {d.company.name:upperCase:substr(0, 16):padr(16)} fits ACME CORPORATION into its 16 characters. {o.timezone=America/New_York} prints the file time in New York time.
2. Add one Entry Detail per payment
The Entry Detail line is a loop on d.entries. On Olivia Bennett's line:
22pays a checking account,32a savings account::ifEQ(savings):show(32):elseShow(22).071403623is the routing number of her bank.:mul(100):round(0):padl(10, '0')turns 2,845.16 into0000284516.{d.entries[i].id:cumCount:padl(7, '0')}ends the trace number with her position:041108290000001.
3. Compute the control totals
The bank recomputes these totals and rejects a file that does not match. Carbone computes them from the payments with aggregators:
- Entry count:
{d.entries:len:padl(6, '0')}prints000005. - Entry hash:
{d.entries[].routing:substr(0, 8):aggSum:mod(10000000000):padl(10, '0')}adds the first 8 digits of each routing number and keeps the last 10 digits:0083197622. - Total credits:
{d.entries[].amount:mul(100):round(0):aggSum:padl(12, '0')}prints000001456987, or 14,569.87.
4. Fill the last block with 9s
The line count must be a multiple of 10. {d.entries:len:add(4):div(10):ceil} gives the block count, and the template ends with nine lines of 9. Each one is a conditional block on {d.entries:len:add(3):mod(10)}, the position of the File Control in its block, so only the missing lines are kept: one with 5 payments, nine with 7.
The ACH file keeps the CRLF line endings of the template.
Related Examples
Trusted by 800+ paid customers in 40+ countries














