nocobase/nocobase · error · ImportValidationError

Headers not found. Expected headers: {{headers}}

Error message

Headers not found. Expected headers: {{headers}}

What it means

ImportValidationError thrown by getData when the uploaded spreadsheet contains no header row matching the expected headers derived from the import columns. findAndValidateHeaders scans the first rows for a row containing the expected header titles; headerRowIndex === -1 means none matched, and the error lists the expected headers so users can align their file.

Source

Thrown at packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts:652

    const columns = this.getColumnsByPermission(ctx);
    return columns.map((col) => col.title || col.defaultTitle);
  }

  async getData(ctx?: Context) {
    const workbook = this.options.workbook;
    const worksheet = workbook.Sheets[workbook.SheetNames[0]];

    let data = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null, blankrows: false }) as string[][];

    // Release the workbook reference immediately after converting to plain data array,
    // so the large parsed XLSX object can be garbage collected during import.
    this.options.workbook = null;

    // Find and validate header row
    const expectedHeaders = this.getExpectedHeaders(ctx);
    const { headerRowIndex, headers } = this.findAndValidateHeaders({ data, expectedHeaders });
    if (headerRowIndex === -1) {
      throw new ImportValidationError('Headers not found. Expected headers: {{headers}}', {
        headers: expectedHeaders.join(', '),
      });
    }
    data = this.alignWithHeaders({ data, expectedHeaders, headerRowIndex });
    // Extract data rows
    const rows = data.slice(headerRowIndex + 1);

    // if no data rows, throw error
    if (rows.length === 0) {
      throw new ImportValidationError('No data to import');
    }

    return [headers, ...rows];
  }

  private alignWithHeaders(params: {
    data: string[][];
    expectedHeaders: string[];

View on GitHub (pinned to fa42722fef)

Solutions

  1. Re-download the import template and fill it in without editing header row titles.
  2. Ensure the sheet's header row exactly contains the expected headers listed in the error message.
  3. Remove decorative title rows/merged cells above the real header row, or start data at the template's structure.
  4. In code, verify getExpectedHeaders(ctx) titles match the actual sheet before calling getData.

Example fix

// before (Excel row 1)
"User Name | Email Address"
// after
"Username | Email" // exactly matching expected headers: "Username, Email"
Defensive patterns

Strategy: validation

Validate before calling

const expected = importer.getExpectedHeaders(ctx); // or rebuild from columns
const firstRows = data.slice(0, 10).map((r) => r.map((c) => String(c ?? '').trim()));
const matched = firstRows.some((r) => expected.every((h) => r.includes(h)));
if (!matched) throw new Error(`Missing headers; expected: ${expected.join(', ')}`);

Try / catch

try {
  await importer.data();
} catch (e) {
  if (e.name === 'ImportValidationError' && e.message.includes('Headers not found')) {
    console.error('Expected headers:', e.context?.headers);
  }
  throw e;
}

Prevention

When it happens

Trigger: Excel file columns renamed, reordered with different titles, or the user uploads an empty/other sheet; extra title rows or the template's first rows changed; columns configured with custom titles that don't appear in the file.

Common situations: Downloading a template, editing headers in Excel, then importing; uploading the wrong file; localized/translated column titles differing between template generation and header matching; merged/blank top rows pushing headers beyond the scan range.

Related errors


AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01). Data as JSON: /api/errors/432107a1e9ae6574. Report an issue: GitHub.