instructure/canvas-lms · error · ArgumentError
message_settings must be an Array if present
Error message
message_settings must be an Array if present
What it means
ContextExternalTool's message_settings= setter raises ArgumentError when the assigned value is present but not an Array. Only arrays of settings hashes (with optional 'enabled' coerced to boolean) are accepted; any other present value is rejected before being stored in settings.
Solutions
- Wrap the settings object in an array: [{type: 'submission_type', enabled: true}].
- If message_settings is genuinely absent, pass nil or [] rather than a truthy non-array.
- Coerce single-object payloads at the API boundary before assignment.
Example fix
// before
tool.message_settings = { type: 'submission_type', enabled: true }
// after
tool.message_settings = [{ type: 'submission_type', enabled: true }] Defensive patterns
Strategy: type-guard
Validate before calling
ms = payload[:message_settings] raise ArgumentError, 'message_settings must be an Array' if ms.present? && !ms.is_a?(Array)
Type guard
def valid_message_settings?(v) = v.nil? || v.is_a?(Array)
Try / catch
begin
tool.message_settings = payload[:message_settings]
rescue ArgumentError => e
render json: { error: e.message }, status: :bad_request
end Prevention
- Always send message_settings as a JSON array of objects
- Normalize single-object configs into arrays at ingestion
- Validate tool config payloads against the API schema before assignment
When it happens
Trigger: Assigning tool.message_settings = {type: 'submission', enabled: true} (a Hash, not Array) or a String; passing a JSON object instead of an array when creating/updating an external tool via API.
Common situations: LTI tool configuration JSON where message_settings was authored as a single object; YAML/XML config converters that don't wrap the setting in an array; hand-written tool provisioning scripts.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Couldn't find valid settings for this link
- invalid scope for hash
- Missing required asset parameter #
- Missing required parameter: copied_at
- Missing required parameter: course
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/7d15463c1b8884eb.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/context_external_tool.rb:1446
def asset_processor_eula_url
Rails.application.routes.url_helpers.update_tool_eula_url(
context_external_tool_id: id,
host: context.root_account.environment_specific_domain
).delete_suffix("/deployment")
end
def message_settings
settings[:message_settings]
end
def message_settings=(value)
if value.is_a?(Array)
value = value.map(&:with_indifferent_access)
value.each do |setting|
setting["enabled"] = Canvas::Plugin.value_to_boolean(setting["enabled"]) if setting.is_a?(Hash) && setting.key?("enabled")
end
elsif value.present?
raise ArgumentError, "message_settings must be an Array if present"
end
settings[:message_settings] = value
end
def message_settings_for(message_type)
ms = (message_settings || []).select { |ms| ms["type"] == message_type.to_s }
ms.is_a?(Array) ? ms.map(&:with_indifferent_access) : ms
end
def eula_settings
message_settings_for(LtiAdvantage::Messages::EulaRequest::MESSAGE_TYPE).first || {}
end
def eula_enabled?
!!eula_settings&.dig("enabled")
end
def eula_launch_urlView on GitHub (pinned to 1c9f0bb801)