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'.

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.

Appendix: 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)