instructure/canvas-lms · error · GraphQL::ExecutionError

filters must be an object

Error message

filters must be an object

What it means

UpdateWidgetDashboardConfig's validate_filters! requires the filters argument (a GraphQL JSON value) to be a Hash or ActionController::Parameters. Any other JSON type — array, string, number, null — raises this error before per-key validation.

Solutions

  1. Send filters as a JSON object whose keys are filter names
  2. Replace filters: [] with filters: {} to clear filters
  3. Coerce/validate the payload client-side before the mutation
  4. Ensure your HTTP layer preserves object shape (no accidental flattening)

Example fix

// before
updateWidgetDashboardConfig(input: {widgetId, filters: []})
// after
updateWidgetDashboardConfig(input: {widgetId, filters: {}}) // clear
Defensive patterns

Strategy: type-guard

Validate before calling

const isFiltersObject = (f) => f !== null && typeof f === 'object' && !Array.isArray(f)

Type guard

const asFilters = (f) => (f !== null && typeof f === 'object' && !Array.isArray(f) ? f : {})

Try / catch

try { await gql(updateWidgetDashboardConfigMutation, {widgetId, filters}) } catch (e) { if (/filters must be an object/.test(e.message)) filters = {}; else throw e }

Prevention

When it happens

Trigger: Calling updateWidgetDashboardConfig with filters set to a JSON array, scalar, or null instead of an object like {"courseIds": [1,2]}.

Common situations: Client sends filters: [] (an empty array) as a 'clear' shortcut; JSON serialization turns an object into a scalar; API consumer misreads schema and sends a list of filters.

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/1505cdeefa314d7c. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/mutations/update_widget_dashboard_config.rb:74

  VALID_ANNOUNCEMENT_FILTERS = %w[unread read all].freeze
  VALID_TODO_FILTERS = %w[incomplete_items complete_items all].freeze
  VALID_DATE_FILTERS = %w[not_submitted missing submitted].freeze

  private

  # TODO: Remove this entire helper method once platform-ui passes dashboard_type (EGG-2539)
  # The resolver should directly use input[:dashboard_type] after that.
  def educator_dashboard?(input)
    return input[:dashboard_type] == "educator" if input[:dashboard_type].present?

    context[:domain_root_account]&.feature_enabled?(:educator_dashboard) &&
      current_user.educator_dashboard_user?
  end

  def validate_filters!(widget_id, filters)
    # Accept Hash or ActionController::Parameters (which GraphQL JSON type may produce)
    unless filters.is_a?(Hash) || filters.is_a?(ActionController::Parameters)
      raise GraphQL::ExecutionError, "filters must be an object"
    end

    filters.each_key do |key|
      unless key.is_a?(String) && !key.empty?
        raise GraphQL::ExecutionError, "filter keys must be non-empty strings"
      end
    end

    validate_filter_structure!(widget_id, filters)
  end

  def validate_filter_structure!(widget_id, filters)
    if widget_id == ANNOUNCEMENTS_WIDGET_ID
      validate_announcements_filters!(filters)
    elsif widget_id == TODO_LIST_WIDGET_ID
      validate_todo_list_filters!(filters)
    elsif COURSE_WORK_WIDGET_IDS.include?(widget_id)
      validate_course_work_filters!(filters)

View on GitHub (pinned to 1c9f0bb801)