opf/openproject · error · ArgumentError

Invalid CF type

Error message

Invalid CF type

What it means

CustomFields::CreateService#instance builds the new record via careful_new_custom_field(params[:type]), which constantizes the STI type string and returns nil when it does not resolve to a loadable CustomField subclass. When nil, the service raises ArgumentError('Invalid CF type') before any validation runs. Valid values are concrete class names like 'WorkPackageCustomField', 'ProjectCustomField', 'UserCustomField'.

Source

Thrown at app/services/custom_fields/create_service.rb:51

    def self.careful_new_custom_field(type)
      if /.+CustomField\z/.match?(type.to_s)
        klass = type.to_s.constantize
        klass.new if klass.ancestors.include? CustomField
      end
    rescue NameError => e
      Rails.logger.error "#{e.message}:\n#{e.backtrace.join("\n")}"
      nil
    end

    def perform
      super
    rescue StandardError => e
      ServiceResult.failure(message: e.message)
    end

    def instance(params)
      cf = self.class.careful_new_custom_field(params[:type])
      raise ArgumentError.new("Invalid CF type") unless cf

      cf
    end

    def after_perform(call)
      cf = call.result

      if cf.field_format_calculated_value? && cf.is_required?
        enqueue_recalculate_values(cf)
      end

      if cf.hierarchical_list?
        CustomFields::Hierarchy::HierarchicalItemService.new.generate_root(cf)
      end

      call
    end

View on GitHub (pinned to d9742c43f3)

Solutions

  1. Pass an exact built-in type: 'WorkPackageCustomField', 'ProjectCustomField', 'UserCustomField', 'TimeLineCustomField' etc. — check CustomField.descendants.map(&:name) in console for the supported list.
  2. If the type came from a plugin, restore/install the plugin that defines that class before creating the field.
  3. Validate the incoming type server-side before calling the service (see validation code) so the failure is a 4xx, not an ArgumentError.

Example fix

# before
CustomFields::CreateService.new(user: user).call(type: 'FooCustomField', name: 'X', field_format: 'string')

# after
unless CustomField.descendants.map(&:name).include?('FooCustomField')
  return ServiceResult.failure(message: 'Unknown custom field type FooCustomField')
end
CustomFields::CreateService.new(user: user).call(type: 'FooCustomField', name: 'X', field_format: 'string')
Defensive patterns

Strategy: validation

Validate before calling

valid_types = CustomField.descendants.map(&:name)
raise ArgumentError, "Unknown custom field type #{params[:type]}" unless valid_types.include?(params[:type].to_s)

Type guard

def custom_field_type?(value)
  value.is_a?(String) && CustomField.descendants.map(&:name).include?(value)
end

Try / catch

begin
  CustomFields::CreateService.new(user: user).call(**params)
rescue ArgumentError => e
  ServiceResult.failure(message: e.message)
end

Prevention

When it happens

Trigger: Calling CustomFields::CreateService.new(user: u).call(type: 'FooCustomField', ...) or type: 'WorkPackageCustomFieldXX' — any type string that is not a name of a loaded CustomField descendant.

Common situations: A plugin that defined a custom field subclass was removed, so re-importing or re-running seeds that reference its type now fails; an API client sends a made-up or misspelled type; a core upgrade renamed/moved an STI class.

Related errors


AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21). Data as JSON: /api/errors/310ab0606e0baa1f. Report an issue: GitHub.