I know this question has been asked before:

but I need something different.
The answers in the questions linked above suggest using some core methods that (all of them) load the full product and save the full product, either via repository or via the load/save deprecated methods.
I have a lot of products. Somewhere between 5k and 10k.
Going through all of them with load/save it is not really a viable option.

Any other suggestions?

3 Answers 3


I ended up doing it with the risky inserts directly into the tables.

the tables involved are (in this order)

  • catalog_product_entity_media_gallery
  • catalog_product_entity_media_gallery_value
  • catalog_product_entity_media_gallery_value_to_entity
  • catalog_product_entity_media_gallery_value_video

Pre-explanations: This should happen inside one class.
This class has these members that are added via constructor (di will take care of instantiating them)

// \Magento\Framework\App\ResourceConnection
private $resourceConnection;
// \Magento\Catalog\Api\ProductAttributeRepositoryInterface
private $attributeRepository;

Let's take the tables 1 by 1:


$mediaAttributeId = $this->attributeRepository->get('media_gallery')->getAttributeId();
$image = 'someImage.jpg'; //This is the preview image of the video. the image needs to exist in `/media/catalog/product/`. I'm not going to cover how you add it there
$table = $this->resourceConnection->getTableName('catalog_product_entity_media_gallery');
$data = [
    'attribute_id' => $mediaAttributeId,
    'value' => $image,
    'media_type' => \Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter::MEDIA_TYPE_CODE,
    'disabled' => 0
$this->resourceConnection->getConnection()->insert($table, $data);

Now remember the last added increment id because it is needed in the later inserts.

$valueId = $this->resourceConnection->getConnection()->lastInsertId();


$storeId = the store id for witch the video is available. Use 0 for global;
$productId = the id of the product for which you add the video
$table = $this->resourceConnection->getTableName('catalog_product_entity_media_gallery_value');
$data = [
    'value_id' => $valueId, //the increment id from above
    'store_id' => $storeId,
    'entity_id' => $productId,
    'position' => 1, //any number goes here
    'disabled' => 0
$this->resourceConnection->getConnection()->insert($table, $data);


$table = $this->resourceConnection->getTableName('catalog_product_entity_media_gallery_value_to_entity');
$data = [
    'value_id' => $valueId, // increment id from above
    'entity_id' => $productId, // product id from the step above
$this->resourceConnection->getConnection()->insert($table, $data);


$videoUrl = 'url of the video from youtube or vimeo';  
$table = $this->resourceConnection->getTableName('catalog_product_entity_media_gallery_value_video');
$data = [
    'value_id' => $valueId, //saved increment id from above
    'store_id' => $storeId, // Store id from the steps above
    'provider' => '',
    'url' => $videoUrl,
    'title' => "Video title", //this is mandatory
    'description' => 'Video description', //this can be empty
    'metadata' => '' //I have no idea what this does
$this->resourceConnection->getConnection()->insert($table, $data);

That's it. Some reindexing may be needed.

  • i have insert all these 4 tables directly by sql query. Video showing in admin panel but frontend not working
    – Alam Zaib
    Commented Dec 5, 2019 at 10:57
  • Sorry to hear that.
    – Marius
    Commented Dec 5, 2019 at 11:53
  • This answer works as charm. Thanks Commented Jul 19, 2021 at 18:11

I don't like to overrule Marius' answer because the answer he's given is really excellent & actually works! However, if you or your company isn't so inclined to run direct SQL queries like that I'd suggest something more like this, using Magento 2's API:

$data = [
    'entry' => [
        'id' => null,
        'media_type' => 'external-video',
        'label' => null,
        'position' => $IntToRepresentWhereInGalleryToPlaceIt,
        'types' => ['image', 'small-image', 'thumbnail'],
        'disabled' => false,
        'content' => [
            'base64_encoded_data' => base64_encode(file_get_contents($thumbnailimageUrl)),
            'type' => 'image/jpeg',
            'name' => '0.jpg'
        'extension_attributes' => [
            'video_content' => [
                'media_type' => 'external-video',
                'video_provider' => 'youtube',
                'video_url' => $videoURL,
                'video_title' => $videoTitle,
                'video_description' => $videoDescription,
                'video_metadata' => null //Don't know what this is for, add a value if you like
$ch = curl_init("http://YOURURL.COM/index.php/rest/V1/products/" . $YourProductSku . "/media");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json", "Authorization: Bearer " . $MagentoIntegrationsAccessToken));
$response = curl_exec($ch);
  • 1
    This works, but it's slow. I mean this can be also done without calling the API, by using the code used in the API endpoint. it's clean and most probably upgrade proof (+1 for that). I already tried this, but, like I said in the question, I need to do this for thousands of products and that lasts 2 eternities. And for the record, feel free to overrule any of my answers. I've been known to be wrong many times in the past.
    – Marius
    Commented Aug 23, 2019 at 15:55
  • 1
    Good to hear from you! Thank you, yeah. I guess for those who stumble upon your question who have less products & would rather use something "safer"/upgrade proof, this might well be their end solution. Good point on using the code in the API endpoint, I hadn't even thought of that. Also, Marius you're as legendary on this site as Alan Storm, I don't think there is a single developer who uses this site who hasn't at some point in time benefited from your contributions. Thank you for all you've done.
    – John
    Commented Aug 27, 2019 at 7:50

When it comes to tasks like this one most of the time I always use SQS, rabbitMQ or some sort of queueing system, depending on the requirements, infrastructure of the site and the complexity/intensity of the task. If you're using community edition one of my friends did a rabbitMQ/Sql implementation for it here.

The enterprise edition implements bulk operations based on queueing systems, its not hard to implement something for the CE version.

I find using message queueing system a really good option for bulk operation as they be scalable automatically e.g you can spawn consumers depending on how many messages there in a queue. I've created an integration before with some AWS services where for every 100 messages a new consumer would spawn automatically (magento doesn't do this, you have to do some customisation to it).

Here some references you could have a look:

RabbitMQ in M2.2.*+

RabbitMQ PHP

  • thanks for the effort, but I don't want to introduce a new dependency to the project just for this. I'm asking if there is a built in way to attach videos to products without going through load/save. If there isn't, I can reverse engineer the save process to see what tables are affected and how, and do direct inserts. It shouldn't get any faster than that. But I'm trying to avoid this method since it is risky. I will use it as a last resort.
    – Marius
    Commented Aug 22, 2018 at 13:12
  • No worries. Yeah you could do that too. The other option I can think of is to create a new resource model and only load product data you need and only use this resource model for that operation, can't really think of anything else ... Commented Aug 22, 2018 at 13:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.