NACHA File Generator: Create ACH Files from JSON

Turn payroll or supplier payments from your software into a valid ACH file

Download Template

Download data sample

  • 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:

  • 22 pays a checking account, 32 a savings account: :ifEQ(savings):show(32):elseShow(22).
  • 071403623 is the routing number of her bank.
  • :mul(100):round(0):padl(10, '0') turns 2,845.16 into 0000284516.
  • {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')} prints 000005.
  • 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')} prints 000001456987, 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.

Trusted by 800+ paid customers in 40+ countries

Finance
Healthcare
Insurance
Legal
Public
Retail
Energy
Software
Telecom
Dior
Thales
Younited
Danfoss
CMA CGM
Illumina
KparK
Scalingo
French Ministry of the Armed Forces
BMW
Telus
Bestseller
Flagship Biosciences
Insly
Ninox
Sodexo