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

filter keys must be non-empty strings

Error message

filter keys must be non-empty strings

What it means

validate_filters! iterates filters.each_key and requires every key to be a non-empty String. JSON object keys are normally strings, but keys built dynamically (numbers coerced, empty names, or ActionController::Parameters with symbol keys) trigger this error.

Solutions

  1. Ensure every filter key is a non-empty string before sending
  2. Filter out empty keys client-side prior to the mutation
  3. Convert numeric/array-like structures into named-key objects
  4. When building params server-side, stringify keys (e.g. params.to_unsafe_h.deep_stringify_keys)

Example fix

// before
const filters = Object.fromEntries(selected.map((v, i) => [i, v])) // numeric keys
// after
const filters = Object.fromEntries(selected.map(v => [String(v.filterName), v.value]))
Defensive patterns

Strategy: validation

Validate before calling

const clean = Object.fromEntries(Object.entries(filters).filter(([k, v]) => typeof k === 'string' && k.length > 0))

Type guard

const hasValidKeys = (f) => Object.keys(f).every(k => typeof k === 'string' && k.length > 0)

Try / catch

try { await gql(updateWidgetDashboardConfigMutation, {widgetId, filters}) } catch (e) { if (/filter keys must be non-empty/.test(e.message)) { filters = sanitize(filters); /* retry */ } else throw e }

Prevention

When it happens

Trigger: Passing filters with an empty-string key ({"": [...]}) or non-string keys (numeric keys when serialized from an array-like object; symbol keys via ActionController::Parameters).

Common situations: UI builds filter map with an uninitialized filter name; spreading objects with numeric indexes ({...Object.values(x)}); server-side re-invocation of the mutation with unpermitted params.

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/47f1c4c22502605d. Report an issue: GitHub.

Appendix: source

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

  # 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)
    else
      validate_generic_filters!(filters)
    end
  end

View on GitHub (pinned to 1c9f0bb801)