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
- Pass an exact built-in type: 'WorkPackageCustomField', 'ProjectCustomField', 'UserCustomField', 'TimeLineCustomField' etc. — check CustomField.descendants.map(&:name) in console for the supported list.
- If the type came from a plugin, restore/install the plugin that defines that class before creating the field.
- 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
- Derive type dynamically from CustomField.descendants instead of hardcoding a list that goes stale.
- Treat unregistered plugin field types as configuration drift: validate before the import run starts, not mid-job.
- Pin plugin load order so STI subclasses exist before seeds/imports run.
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
- Failed to create custom field '%{name}': %{message}
- Couldn't find WeekDay with day #{day}
- Filter must be a JSON object, got #{filter.class}
- Value for #{name} must be one of #{allowed.join(', ')} but i
AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21).
Data as JSON: /api/errors/310ab0606e0baa1f.
Report an issue: GitHub.