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

  1. Wrap the settings object in an array: [{type: 'submission_type', enabled: true}].
  2. If message_settings is genuinely absent, pass nil or [] rather than a truthy non-array.
  3. 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

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


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_url

View on GitHub (pinned to 1c9f0bb801)