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
- Ensure every filter key is a non-empty string before sending
- Filter out empty keys client-side prior to the mutation
- Convert numeric/array-like structures into named-key objects
- 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
- Filter out empty/unnamed keys before sending
- Avoid spreading array-like objects into filters
- Stringify keys when building params dynamically
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
- A maximum of 50 assessees can be provided at once
- A maximum of 50 assessors can be provided at once
- All ConversationMessages must exist within the same…
- Anonymous assignments must be manually posted
- Assignment overrides are not allowed in the parent…
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)