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
- Verify the media_object_id exists via the media_objects API and use the correct current id
- Re-upload/re-create the media object if it was deleted and obtain its new id
- Ensure the id belongs to the same shard/account the request is made against
- 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
- Refetch media ids after re-uploads instead of caching
- Keep media ids shard/environment-specific
- Treat 404 on media objects as refetch-and-retry signal
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
- A student referenced a non-existent user #
- Can't delete a non-existent observer for observer: #
- cannot change column: captions - locked by Master Course
- Do not look up MediaObjects by media_id - use the scope…
- # not found
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)