actualbudget/actual · error

File does not appear to be a valid qif file: ${line}

Error message

File does not appear to be a valid qif file: ${line}

What it means

qif2json requires the first line of a QIF file to be a type header of the form '!Type:Bank' (or similar). If the first line is empty or does not match the expected type pattern, the parser cannot determine the account type and throws this error including the offending line.

Source

Thrown at packages/loot-core/src/server/transactions/import/qif2json.ts:38

};

export function qif2json(qif, options: { dateFormat?: string } = {}) {
  const lines = qif.split('\n').filter(Boolean);
  let line = lines.shift();
  const type = /!Type:([^$]*)$/.exec(line.trim());
  const data: {
    dateFormat: string | undefined;
    type?;
    transactions: QIFTransaction[];
  } = {
    dateFormat: options.dateFormat,
    transactions: [],
  };
  const transactions = data.transactions;
  let transaction: QIFTransaction = {};

  if (!type || !type.length) {
    throw new Error('File does not appear to be a valid qif file: ' + line);
  }
  data.type = type[1];

  let division: Division = {};

  while ((line = lines.shift())) {
    line = line.trim();
    if (line === '^') {
      transactions.push(transaction);
      transaction = {};
      continue;
    }
    switch (line[0]) {
      case 'D':
        transaction.date = line.substring(1);
        break;
      case 'T':
        transaction.amount = line.substring(1);

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Open the file and verify the very first non-empty line starts with '!Type:' (e.g. '!Type:Bank').
  2. Strip any UTF-8 BOM and leading blank lines before parsing.
  3. Confirm the file is actually QIF format, not CSV/OFX renamed to .qif; re-export from the bank in QIF format.
  4. Trim leading whitespace/newlines from the file content before passing it to parseQIF.

Example fix

// before
const data = qif2json.parse(rawFileContents);
// after
const cleaned = rawFileContents.replace(/^\uFEFF/, '').replace(/^\s+/, '');
if (!/^!Type:/im.test(cleaned.split(/\r?\n/, 1)[0])) {
  throw new Error('Missing !Type header — not a valid QIF file');
}
const data = qif2json.parse(cleaned);
Defensive patterns

Strategy: validation

Validate before calling

const firstLine = content.replace(/^\uFEFF/, '').split(/\r?\n/, 1)[0];
if (!/^!Type:/i.test(firstLine.trim())) {
  throw new Error('Not a QIF file: missing !Type header on first line');
}

Type guard

function isQifContent(content: string): boolean {
  const first = content.replace(/^\uFEFF/, '').trimStart().split(/\r?\n/, 1)[0];
  return /^!Type:/i.test(first);
}

Try / catch

try {
  const data = parseQIF(file);
} catch (e) {
  if (e.message.startsWith('File does not appear to be a valid qif file')) {
    showError('This file is not a valid QIF export. Check the first line is !Type:...');
  } else { throw e; }
}

Prevention

When it happens

Trigger: Importing a file whose first line is blank, whose '!Type' header is misspelled (e.g. '!TYPE:Bank' with wrong capitalization is fine but '!Type' missing entirely is not), a file exported with a BOM or leading whitespace, or passing a non-QIF file (CSV, OFX) to parseQIF.

Common situations: Bank exports that prepend metadata lines before the !Type header; files saved with UTF-8 BOM by Windows tools; users renaming a CSV to .qif; truncated downloads missing the header.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/9e884fbd9dd21c70. Report an issue: GitHub.