Yes creating a module will solve this.
Tested on magento 2.2.3
The following two files will register your new module with the Magento 2 system.
app/code/Vendor/Package/registration.php
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Vendor_Package', __DIR__ );
app/code/Vendor/Package/etc/module.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Vendor_Package" setup_version="1.0.0" /> </config>
app/code/Vendor/Package/Setup/InstallData.php
Setup new attributes
<?php namespace Vendor\Package\Setup; use Magento\Catalog\Model\Product; use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\Eav\Setup\EavSetupFactory; use Magento\Framework\Setup\InstallDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; class InstallData implements InstallDataInterface { private $eavSetupFactory; public function __construct( EavSetupFactory $eavSetupFactory ) { $this->eavSetupFactory = $eavSetupFactory; } public function install( ModuleDataSetupInterface $setup, ModuleContextInterface $context ) { $setup->startSetup(); /** @var \Magento\Eav\Setup\EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); $eavSetup->addAttribute( Product::ENTITY, 'extra_product_image_1', [ 'type' => 'varchar', 'label' => 'Extra Product Image 1', 'input' => 'media_image', 'frontend' => 'Magento\Catalog\Model\Product\Attribute\Frontend\Image', 'required' => false, 'global' => ScopedAttributeInterface::SCOPE_STORE, 'used_in_product_listing' => true, ]); $eavSetup->addAttribute( Product::ENTITY, 'extra_product_image_2', [ 'type' => 'varchar', 'label' => 'Extra Product Image 2', 'input' => 'media_image', 'frontend' => 'Magento\Catalog\Model\Product\Attribute\Frontend\Image', 'required' => false, 'global' => ScopedAttributeInterface::SCOPE_STORE, 'used_in_product_listing' => true, ]); $eavSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'image_description_1', [ 'type' => 'text', 'backend' => '', 'frontend' => '', 'label' => 'Image Description 1', 'input' => 'text', 'class' => '', 'source' => '', 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, 'visible' => true, 'required' => true, 'user_defined' => false, 'default' => '', 'searchable' => false, 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, 'apply_to' => '' ] ); $eavSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'image_description_2', [ 'type' => 'text', 'backend' => '', 'frontend' => '', 'label' => 'Image Description 2', 'input' => 'text', 'class' => '', 'source' => '', 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, 'visible' => true, 'required' => true, 'user_defined' => false, 'default' => '', 'searchable' => false, 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, 'used_in_product_listing' => true, 'unique' => false, 'apply_to' => '' ] ); $setup->endSetup(); } }
At this point you have created a new module that installs your attributes for all products. You'll be able to see the input fields on the product edit page and when you upload an Image you'll see a new image role to assign -- this is important; generally in this use case you'll want to hide this image as well (an option when assigning image roles).

Now you'll want to create a Block Class.
app/code/Vendor/Package/Block/ProductImage.php
<?php namespace Vendor\Package\Block; use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Framework\App\Filesystem\DirectoryList; class ProductImage extends \Magento\Catalog\Block\Product\ListProduct implements \Magento\Framework\DataObject\IdentityInterface { /** * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory */ protected $_collectionFactory; /** * @var \Magento\Catalog\Helper\ImageFactory */ protected $_imageFactory; /** * @var \Magento\Framework\Registry */ protected $_registry; /** * @var \Magento\Framework\App\Filesystem\DirectoryList */ protected $_filesystem; /** * @var \Magento\Catalog\Helper\Image */ protected $_productImageHelper; /** * @var \Magento\Store\Model\StoreManagerInterface */ protected $_storeManager; /** * ListProduct constructor. * @param \Magento\Catalog\Block\Product\Context $context * @param \Magento\Framework\Data\Helper\PostHelper $postDataHelper * @param \Magento\Catalog\Model\Layer\Resolver $layerResolver * @param CategoryRepositoryInterface $categoryRepository * @param \Magento\Framework\Url\Helper\Data $urlHelper * @param \FishPig\WordPress\Model\ResourceModel\Post\Collection $wpPost * @param \Magento\Catalog\Helper\ImageFactory $imageFactory * @param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collectionFactory * @param array $data */ public function __construct( \Magento\Catalog\Block\Product\Context $context, \Magento\Framework\Data\Helper\PostHelper $postDataHelper, \Magento\Catalog\Model\Layer\Resolver $layerResolver, CategoryRepositoryInterface $categoryRepository, \Magento\Framework\Url\Helper\Data $urlHelper, \Magento\Catalog\Helper\Image $imageFactory, \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collectionFactory, \Magento\Framework\Registry $registry, \Magento\Framework\Filesystem $filesystem, \Magento\Catalog\Helper\Image $productImageHelper, \Magento\Store\Model\StoreManagerInterface $storeManager, array $data = [] ) { $this->_collectionFactory = $collectionFactory; $this->_imageFactory = $imageFactory; $this->_registry = $registry; $this->_filesystem = $filesystem; $this->_storeManager = $storeManager; $this->_productImageHelper = $productImageHelper; parent::__construct( $context, $postDataHelper, $layerResolver, $categoryRepository, $urlHelper, $data ); } public function getCurrentProduct() { return $this->_registry->registry('current_product'); } public function getMediaDir() { return rtrim($this->_storeManager->getStore()->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA), '/'); } }
app/code/Vendor/Package/view/frontend/layout/catalog_product_view.xml
Now lets link up your block class to your template file!
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content.aside"> <container name="extra.product.image.container" htmlTag="div" htmlClass="extra-product-image__outer" before="-"> <block name="extra.product.overview" class="Vendor\Package\Block\ProductImage" template="Vendor_Package::extra-product-image.phtml" /> </container> </referenceContainer> </body> </page>
Vendor/Package/view/frontend/templates/extra-product-image.phtml
Your template file.
<?php $currentProduct = $block->getCurrentProduct(); $mediaDirectory = $block->getMediaDir(); $image1 = $mediaDirectory . "/catalog/product" . $currentProduct->getExtraProductImage1(); $image2 = $mediaDirectory . "/catalog/product" . $currentProduct->getExtraProductImage2(); $description1 = $currentProduct->getImageDescription1() ?? ''; $description2 = $currentProduct->getImageDescription2() ?? ''; ?> <div> <img src="<?=$image1;?>" > <p><?=$description1?></p> <img src="<?=$image2;?>" > <p><?=$description2;?></p> </div>
Using the Magento 2 cli run the following.
magento module:enable Vendor_Package; magento setup:upgrade; magento setup:di:compile; magento setup:static-content:deploy -f;
Now for each product you'll have the ability to control the new data.