instructure/canvas-lms · error · InvalidIDError

expected an id for #

Error message

expected an id for #{expected_type}

What it means

GraphQLHelpers.parse_relay_id decodes a Relay global id via UniqueWithinType.decode and validates that the embedded type equals expected_type and the id is non-nil. Mismatches raise InvalidIDError 'expected an id for <expected_type>'.

Solutions

  1. Encode the correct global id with GraphQL::Schema::UniqueWithinType.encode(expected_type, id).
  2. Check that the id you pass matches the field's expected argument type in the schema.
  3. If you have a raw db id, wrap it: GraphQL::Schema::UniqueWithinType.encode('User', db_id).

Example fix

// before
user(id: "Q291cnNlLTE=") // decodes to Course-1
// after
const gid = Buffer.from('User-7').toString('base64') // 'VXNlci03'
user(id: "VXNlci03")
Defensive patterns

Strategy: validation

Validate before calling

function decodeGid(gid){ const [t, id] = GraphQL::Schema::UniqueWithinType.decode(gid); return {t, id} }
if (decodeGid(gid).t !== 'Course') throw new Error('wrong gid type for this field')

Type guard

function isCourseGid(gid){ try { return UniqueWithinType.decode(gid)[0] === 'Course' } catch { return false } }

Try / catch

begin
  Helpers.parse_relay_id(gid, 'Course')
rescue InvalidIDError
  # re-encode from raw db id or surface a client error
end

Prevention

When it happens

Trigger: Passing a Relay id of the wrong type to a field expecting a specific type (e.g. a Course gid where a User id is expected), or passing a malformed/garbage base64 string that decodes to nil.

Common situations: Copying ids between different queries; using raw database ids instead of Relay gids; clients constructing gids manually with the wrong type name; cross-shard id formatting mistakes.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/bf1c6bc7341333ba. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/graphql_helpers.rb:56

        parse_relay_or_legacy_id(relay_or_legacy_id, expected_type)
      end
    rescue InvalidIDError => e
      GraphQL::ExecutionError.new(e.message)
    end
  end

  def self.parse_relay_or_legacy_id(relay_or_legacy_id, expected_type)
    if relay_or_legacy_id.nil? || relay_or_legacy_id =~ /\A\d+\Z/
      relay_or_legacy_id
    else
      parse_relay_id(relay_or_legacy_id, expected_type)
    end
  end

  def self.parse_relay_id(relay_id, expected_type)
    type, id = GraphQL::Schema::UniqueWithinType.decode(relay_id)
    if type != expected_type || id.nil?
      raise InvalidIDError, "expected an id for #{expected_type}"
    else
      id
    end
  end

  # TODO: move this into LockType after we switch to the class-based api
  def self.make_lock_resolver(attr)
    lambda do |lock, _, _|
      if lock == false
        nil
      else
        lock[attr]
      end
    end
  end

  class InvalidIDError < StandardError; end
end

View on GitHub (pinned to 1c9f0bb801)