Skip to content

Writing QIF files

new Transaction() takes the account type, a Types case. Every setter returns the transaction, so they chain:

use Silarhi\Qif\Enums\Types;
use Silarhi\Qif\Transaction;
$transaction = (new Transaction(Types::Cash))
->setDate(new DateTimeImmutable('2019-12-31'))
->setDescription('INV666')
->setAmount(18.99)
->setCategory('Sales')
->addSplit('Sales', 18)
->addSplit('Tax', 0.99, 'VAT')
->markAsReconciled();
Method Sets
setDate(DateTimeImmutable $date) The date (D)
setAmount(float|int|string $float) The amount (T): see String amounts
setDescription(?string $description) The payee (P)
setCategory(?string $category) The category (L), e.g. Housing:Rent
addSplit(string $category, float|int|string $amount, ?string $memo = null) Adds a split: category (S), amount ($) and memo (E)
removeSplit(string $category) Removes every split of the category
setStatus(Status $status) The cleared status (C)
markAsReconciled() setStatus(Status::Reconciled)
markAsCleared() setStatus(Status::Cleared)
markAsNotCleared() setStatus(Status::NotCleared), the default
setMemo(?string $memo) The memo (M)
setAddress(?string $address) The address (A), its lines separated by "\n"

Splits are a list: several splits can share a category, and addSplit() adds one at the end each time.

setAmount() and addSplit() take a float, an int or a string. A string is read like the amounts of a QIF file, its decimal separator detected: '1,234.50' and '1.234,50' are both 1234.5, '12,50' is 12.5. A string that is not an amount ('12 EUR') or is ambiguous ('1,234': 1234 or 1.234) throws an InvalidArgumentException. See Amounts.

Writer::addTransaction() collects the transactions. Casting the writer to a string (echo $writer, (string) $writer) returns the QIF content, and saveToFile() writes it to disk, replacing the file if it exists:

writing.php
<?php
use Silarhi\Qif\Enums\Types;
use Silarhi\Qif\Transaction;
use Silarhi\Qif\Writer;
$writer = new Writer();
$writer->addTransaction(
(new Transaction(Types::Bank))
->setDate(new DateTimeImmutable('2026-01-15'))
->setDescription('Example Hardware Store')
->setAddress("1 Market Square\nSpringfield")
->setMemo('Drill and bits')
->setAmount(-118.99)
->setCategory('Home')
->addSplit('Home:Tools', -100)
->addSplit('Tax', -18.99, 'VAT')
->markAsCleared()
);
$writer->addTransaction(
(new Transaction(Types::Bank))
->setDate(new DateTimeImmutable('2026-01-31'))
->setDescription('Acme Corp')
->setAmount('2,400.00')
->setCategory('Income:Salary')
);
echo $writer;
Output
!Type:Bank
D15/01/2026
T-118.99
LHome
SHome:Tools
$-100
STax
$-18.99
EVAT
Cc
PExample Hardware Store
A1 Market Square
ASpringfield
MDrill and bits
^
!Type:Bank
D31/01/2026
T2400
LIncome:Salary
C
PAcme Corp
^
$writer->saveToFile('/path/to/export.qif'); // or write it to disk

getTransactions() returns the transactions added so far. saveToFile() throws a RuntimeException when the directory is not writable or the file cannot be opened: see Error handling.

The writer formats dates as d/m/Y by default, the way the parser reads them by default. Pass DateFormat::MonthFirst to the constructor to write m/d/Y, for the US versions of Quicken: a file read with DateFormat::MonthFirst is then written back with its dates in the same order.

month-first.php
<?php
use Silarhi\Qif\Enums\DateFormat;
use Silarhi\Qif\Parser;
use Silarhi\Qif\Writer;
// A file from a US version of Quicken
$content = "!Type:Bank\nD01/31/2026\nT-42.50\nPDemo Grocery\n^\n";
$writer = new Writer(DateFormat::MonthFirst);
foreach ((new Parser($content, dateFormat: DateFormat::MonthFirst))->parse() as $transaction) {
$writer->addTransaction($transaction);
}
echo $writer;
Output
!Type:Bank
D01/31/2026
T-42.5
C
PDemo Grocery
^

Make sure the application importing the files expects the order you write.

The writer outputs the type, date (d/m/Y, or m/d/Y: see Date format), amount, category, splits, cleared status, payee, address and memo of each transaction, in this order, then ^:

  • every transaction starts with its own !Type: line, empty when the type is null;
  • null fields are left out, empty strings are written (a bare P line);
  • each split is written as its S line, its $ line, then its E line when it has a memo;
  • the C line is always written, empty for Status::NotCleared, c for Cleared and X for Reconciled;
  • the address is written as one A line per line ("\n", "\r\n" or "\r");
  • amounts use PHP’s float to string conversion: 2400, -84.3, no thousands separator;
  • lines end with PHP_EOL, and there is no line break after the last ^.

Each transaction is a Stringable too: (string) $transaction returns its own record, dates day first, and $transaction->toQif(DateFormat::MonthFirst) returns it with the dates month first.

The writer outputs every field the parser reads, so a parsed transaction can be written back as is and parses back to the same values. Pass the writer the DateFormat the file was parsed with to keep its date order. Only the formatting changes: the amounts lose their thousands separators, the * cleared status becomes c, each record gets its own !Type: line, the lines follow the writer’s order, and the !Account list of export.qif is not written. An empty string you set yourself (setDescription('')) is written as a bare P line, which the parser reads back as null:

export.qif
!Account
NChecking
TBank
^
!Type:Bank
D03/01/2026
T-1,250.00
CX
PExample Property Rentals
A12 Sample Street
ASpringfield
MJanuary rent
LHousing:Rent
^
D05/01/2026
T2,400.00
C*
PAcme Corp
MSalary
LIncome:Salary
^
D07/01/2026
T-84.30
PDemo Grocery
LFood
SFood:Groceries
$-64.30
EWeekly groceries
SHousehold
$-20.00
^
round-trip.php
<?php
use Silarhi\Qif\Parser;
use Silarhi\Qif\Writer;
$parser = Parser::parseFile('export.qif');
$writer = new Writer();
foreach ($parser->getTransactions() as $transaction) {
$writer->addTransaction($transaction);
}
echo $writer;
Output
!Type:Bank
D03/01/2026
T-1250
LHousing:Rent
CX
PExample Property Rentals
A12 Sample Street
ASpringfield
MJanuary rent
^
!Type:Bank
D05/01/2026
T2400
LIncome:Salary
Cc
PAcme Corp
MSalary
^
!Type:Bank
D07/01/2026
T-84.3
LFood
SFood:Groceries
$-64.3
EWeekly groceries
SHousehold
$-20
C
PDemo Grocery
^