m m
Technical Support
m
.QQT File Format - Technical Details
Purpose

The QQT file format is used for saving data that was extracted using OfficeQ6 so it can be processed later and/or at a different location. It is a fast and simple format that can be read by virtually any programming language - yet retains ALL record data available from the OfficeQ6 API. Because of the long time it takes for OfficeQ6 to extract data from QuickBooks - it is also very useful for testing. A set of data can be extracted one time into a QQT file, and read from the file several times without needing to run QuickBooks or OfficeQ6 again.

The format is also ideal for applications where the data is to be read at a customer site, transferred, and processed elsewhere - such as over the internet. The QQT file can be created by the customer using a tool with a simple user interface - then uploaded using ftp or http to a web site, then processed. This allows sensitive data (such as payroll) to be remotely transferred.

Because a .QQT file contains all the data from a QuickBooks file at a particular point in time - multiple .QQT files can be compared to identify all changes to a QuickBooks file over a period of time.

Almost any programming environment can be used to read a QQT file.  It needs to be able to read binary ASCII text, read 32 bit binary numbers, and reposition the file pointer to any byte offset.

There are 4 sections to the QQT file format - FILETYPE, STATS, RECSTART, AND RECDATA. Each will be described below.

FILETYPE section. Starts at 0x0000. Length = 16 bytes (0x10)

Purpose is to identify that this is a QQT file and specify the total count of blocks that make up the file header (everything before RECSTART). Breaks down as follows:

offs len prompt

0x0000 8 none, file version (always= QQTD0100)
0x0008 1 none, single ascii digit - count of 4K blocks taken up by header
           (value: always= 4)
0x0009 5 blanks
0x000B 2 crlf
STATS section. Starts at 0x0010. Length = 16368 bytes (0x3FF0)

The purpose of this section is to provide information about the file that might be useful if you have a collection of files for testing - such as how many of each record type or transaction type it contains. For example, if you are looking for a file with a lot of CustInvc and CustPymt transactions, and you have a set of QQT files handy - you can look here in each file to quickly find files that match.

This section is arranged like a text file. It has several variable length lines that are each terminated with 0x0D 0x0A (crlf). Brackets surround captions so code can be used to find particular lines if desired.

Following the [end_trantype_counts] marker - the file is filled with all zeros until offset 16384 (0x4000) - which begins the next section. Note that until the [end_trantype_counts] marker - this file is all text and can be viewed with most text editors. After the marker - the file becomes all binary.

The following is the list of entries (with sample data for each) from a real world (although very large) file. These were copied directly from a QQT file - so formatting, spacings, etc. are correct. Note that record count lines only exist if there are records of that type - whereas tran type count lines exist even if the count is 0.

QQTD01004
[appversion:] 20.0.0.0
[qbproduct:] QuickBooks: Premier Accountant Edition 2010 
[machine:] POTLATCH
[file:] L:\qbdata\2010\1each_prm2010.qbw
[file_unc:] \\POTLATCH\DriveL\qbdata\2010\1each_prm2010.qbw

[extract_start:] 09/28/2009 06:27:30
[extract_end:] 09/28/2009 12:52:14
[first_tran:] 05/31/1989
[last_tran:] 04/10/2009

[start_record_counts:]
Account 198
TranDoc 77261
TranLine 331704
InvtItem 32
PayrollItem 12
Employee 7
CustJob 1268
CustMessage 1
PayMeth 8
Terms 7
Vendor 151
VendType 5
OtherName 1
ToDoNote 11
Company 1
Settings 4
TranLink 91463
SalesRep 2
SalesTax 2
BillRate 2
Currency 164
UnitOfMeas 7
[end_record_counts:]

[start_trantype_counts:]
Customer
CustRfnd 0
CustChrg 0
CustCred 26
Estimate 0
CustInvc 49806
CustPymt 22992
SalOrder 0
CashSale 0
TaxPymt 19
Vendor
VendBill 1689
VendPymt 357
VendCard 0
CardChrg 0
CardRfnd 0
VendCred 15
Inventory
Assembly 0
InvAdjst 46
ItemRcpt 0
PurchOrd 0
Other
Journal 372
Check 414
Deposit 1449
LiaAdjst 7
Paycheck 0
LiabPymt 0
Transfer 69
YtdAdjst 0
[end_trantype_counts:]
RECSTART section. Starts at offset 16384 (0x4000). Length = 4096 bytes (0x1000).

This section provides counts of each record type (in binary form) as well as the offset in the file where records of that type start. It is an array of structs - each 32 bytes long with the following fields (pascal notation):

TRecProps = packed record // each entry 32 bytes long
   nCount :integer;
   nFiller :integer; // 4 0's (could be for 64 bit offset in future)
   nOffset :integer;
   nFiller2 :array [1..20] of char; // could be flags in future
end;

Only fields nCount and nOffset are used. nCount is the count of that record type in the file. nOffset if the offset in the file where the records of that record type begin.

The record type numeric identifier (shown below) is as the index to this array. In other words, if you want to know how many InvtItems are in the file, start with offset 0x4000 - them multiply QB_RS_InvtItem * 32 (7 x 32 = 224 = 0xE0). The record count would be the 32 bit signed integer quantity at file offset 0x40E0 (16608). The 32 bits following that is 0 filler - then the 32 bits following that is the zero based offset in the file where the InvtItem records begin.

                     hex   dec     hex     mdb record name
                                  offset 

QB_RS_Account     = 0x00   {0};   4000    // aAccount
QB_RS_TranDoc     = 0x01   {1};   4020    // aTranDoc
QB_RS_TranLine    = 0x02   {2};   4040    // aTranLine

QB_RS_Budget      = 0x05   {5};   40A0    // aBudget
QB_RS_Class       = 0x06   {6};   40C0    // aClass
QB_RS_InvtItem    = 0x07   {7};   40E0    // vInvtItem
QB_RS_PayrollItem = 0x08   {8};   4100    // pPayrollItem
QB_RS_Employee    = 0x09   {9};   4120    // pEmployee

QB_RS_TimeLine    = 0x0D  {13};   41A0    // pTimeLine
QB_RS_CustJob     = 0x0E  {14};   41C0    // cCustJob
QB_RS_CustType    = 0x0F  {15};   41E0    // cCustType
QB_RS_JobType     = 0x10  {16};   4200    // cJobType
QB_RS_CustMessage = 0x11  {17};   4220    // cCustMessage
QB_RS_ShipVia     = 0x12  {18};   4240    // xShipVia
QB_RS_PayMeth     = 0x13  {19};   4260    // xPayMeth
QB_RS_Terms       = 0x14  {20};   4280    // cTerms
QB_RS_Vendor      = 0x15  {21};   42A0    // vVendor
QB_RS_VendType    = 0x16  {22};   42C0    // vVendType
QB_RS_OtherName   = 0x17  {23};   42E0    // xOtherName

QB_RS_ToDoNote    = 0x19  {25};   4320    // xToDoNote

QB_RS_Company     = 0x1C  {28};   4380    // xCompany
QB_RS_Settings    = 0x1D  {29};   43A0    // xSettings

QB_RS_TranLink    = 0x20  {32};   4400    // aTranLink

QB_RS_SalesRep    = 0x23  {35};   4460    // pSalesRep
QB_RS_PriceLevel  = 0x24  {36};   4480    // vPriceLevel 
QB_RS_SalesTax    = 0x25  {37};   44A0    // cSalesTax

QB_RS_BillRate    = 0x28  {40};   4500    // vBillRate
QB_RS_Currency    = 0x29  {41};   4520    // aCurrency
QB_RS_UnitOfMeas  = 0x2A  {42};   4540    // vUnitOfMeas

... then all 00 bytes till 0x5000
RECDATA section. Starts at offset 20480 (0x5000). Length = variable, to end of file.

This is a series of variable length records. All records of a particular record type are together - in the same order as the record type constants above. (All Account records, them TranDoc records, etc.) The entry for a record type in the RECSTART section is the file offset of the first record of that type. In this section, there are no separators between records of different types.

0x5000: start of record data
  for each record
    32 bit - 0 based index of record (starts at 0 - increments 1 for each)
    32 bit byte count
    bytes that make up the record

To read a series of records of a designated type, get the record count and offset from RECSTART. Starting at that offset, read the 4 byte index, then the 4 byte character count. Then read the record itself (a string) - using that 4 byte count. Make sure to honor the record count - if you overflow, you'll be reading records of the next type. (This could also be detected by the record index resetting to zero).

Format of individual records

The string that makes up a record extracted above is identical to what is returned by ...RecordN and ...RecordK functions in the API.� See record formats.