instructure/canvas-lms · error · ParseError

Invalid fields

Error message

Invalid fields: %{fields}

What it means

Raised by Outcomes::CsvImporter#validate_headers when the header row contains fields that are neither in REQUIRED_FIELDS nor OPTIONAL_FIELDS. The importer whitelists headers; any unrecognized column name before 'ratings' triggers this ParseError listing the invalid fields. This prevents silently ignoring typo'd or unsupported columns.

Solutions

  1. Remove or rename the offending columns listed in the error to match the allowed field names exactly.
  2. Compare your headers against the template's valid fields (REQUIRED_FIELDS + OPTIONAL_FIELDS) and fix typos.
  3. Split data that does not belong in this import into a separate process instead of adding extra columns.

Example fix

// before
vendor_guid,title,display_name,ratings
// after
vendor_guid,title,ratings
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = REQUIRED_FIELDS + OPTIONAL_FIELDS
headers = CSV.open(path, &:first).map(&:to_sym)
invalid = headers - ALLOWED
raise "unknown columns: #{invalid.inspect}" unless invalid.empty?

Try / catch

begin
  importer.parse_file
rescue Outcomes::CsvImporter::ParseError => e
  log_and_report(e.message) # list invalid fields to the uploader
end

Prevention

When it happens

Trigger: Header containing a typo such as 'vendor_Guid' or 'titel'; an extra column like 'display_name' that is not in the whitelist; copy-pasting headers from a different import format with extra columns.

Common situations: Migrating CSVs from another LMS with different column names; manual edits introducing typos; older/newer template versions whose headers no longer match this importer's whitelist.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/e79df189c27bb058. Report an issue: GitHub.

Appendix: source

Thrown at lib/outcomes/csv_importer.rb:149

      valid = (encoded || "").valid_encoding?
      raise ParseError, I18n.t("Not a valid utf-8 string: %{string}", string: str.inspect) unless valid

      encoded
    end

    def validate_headers(row, _index)
      main_columns_end = row.find_index("ratings") || row.length
      headers = row.slice(0, main_columns_end).map(&:to_sym)

      after_ratings = row[(main_columns_end + 1)..] || []
      after_ratings = after_ratings.compact_blank.map(&:to_s)
      raise ParseError, I18n.t("Invalid fields after ratings: %{fields}", fields: after_ratings.inspect) unless after_ratings.empty?

      missing = (REQUIRED_FIELDS - headers).map(&:to_s)
      raise ParseError, I18n.t("Missing required fields: %{fields}", fields: missing.inspect) unless missing.empty?

      invalid = (headers - OPTIONAL_FIELDS - REQUIRED_FIELDS).map(&:to_s)
      raise ParseError, I18n.t("Invalid fields: %{fields}", fields: invalid.inspect) unless invalid.empty?

      headers
    end

    def import_row(headers, row)
      simple = headers.zip(row).to_h
      ratings = row[headers.length..]

      object = simple.to_h
      object[:ratings] = parse_ratings(ratings)
      object[:learning_outcome_group_id] = @import[:learning_outcome_group_id]
      if object[:mastery_points].present?
        object[:mastery_points] = strict_parse_float(object[:mastery_points], I18n.t("mastery points"))
      end
      if object[:friendly_description].present? && object[:friendly_description].length > 255
        raise InvalidDataError, I18n.t("Friendly description is too long (maximum is 255 characters)")
      end

View on GitHub (pinned to 1c9f0bb801)