instructure/canvas-lms · error · ActiveRecord::RecordNotFound

invalid media_object_id

Error message

invalid media_object_id

What it means

Raised in MediaObjectsController#load_media_object_from_service when params[:media_object_id] is present but MediaObject.create_if_id_exists returns nil — meaning the id is not a well-formed media object identifier (create_if_id_exists only materializes objects for valid existing media ids). It surfaces to the user as a 404.

Solutions

  1. Verify the media_object_id exists via the media_objects API and use the correct current id
  2. Re-upload/re-create the media object if it was deleted and obtain its new id
  3. Ensure the id belongs to the same shard/account the request is made against
  4. Fix the client to handle 404 and refetch the media id from the source content

Example fix

// before
getMedia('/media_objects/abc123/play') // stale id
// after
const mo = await getMediaObject(courseId); // fetch fresh media_object_id
getMedia(`/media_objects/${mo.id}/play`)
Defensive patterns

Strategy: type-guard

Validate before calling

const exists = await fetch(`/api/v1/media_objects/${id}`).then(r => r.ok).catch(() => false);
if (!exists) throw new Error(`media_object ${id} not found`);

Type guard

const isValidMediaId = (id) => typeof id === 'string' && id.length > 0 && /^\d+$|^mce[a-z0-9]+$/i.test(id);

Try / catch

begin
  media_object = MediaObject.create_if_id_exists(id)
rescue ActiveRecord::RecordNotFound
  render json: { error: 'media not found, refresh from source content' }, status: :not_found
end

Prevention

When it happens

Trigger: Requesting media play/download/stream endpoints with a fabricated, deleted, or cross-shard media_object_id; client caching an id of a MediaObject that was deleted; passing a wrong id type (e.g. course media id vs media object id).

Common situations: Frontend kept an old media id after the attachment was re-uploaded; sharing media URLs between Canvas shards; copying ids from a different environment (test vs production).

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at app/controllers/media_objects_controller.rb:395

    js_env({ media_object: media_api_json }) if media_api_json

    # Add custom body class for responsive padding control
    @body_classes ||= []
    @body_classes.push("immersive-media-view", "content-only")

    deferred_js_bundle :media_immersive_view
    render html: '<div id="immersive_view_container"></div>'.html_safe,
           layout: "layouts/application"
  end

  private

  def load_media_object_from_service
    if params[:media_object_id].present? && !@media_object
      # Unfortunately, we don't have media_object entities created for everything,
      # so we use this opportunity to create the object if it does not exist.
      @media_object = MediaObject.create_if_id_exists(params[:media_object_id])
      raise ActiveRecord::RecordNotFound, "invalid media_object_id" unless @media_object

      @media_object.delay(singleton: "retrieve_media_details:#{@media_object.media_id}").retrieve_details
      increment_request_cost(MISSED_MEDIA_ADDITIONAL_COST)
    end

    @media_object&.viewed!
  end

  def permitted_viewer_restrictions
    @permitted_viewer_restrictions ||=
      params[:viewer_restrictions]&.permit(MediaObject::VIEWER_RESTRICTION_KEYS)
    @permitted_viewer_restrictions ||= {}
  end
end

View on GitHub (pinned to 1c9f0bb801)