As Administrator I want to be able to upload files for each products in order to show link on frontend.

I created upgrade script to create attribute with this properties:

'type' => 'varchar',
'input' => 'file',
'label' => 'Datasheet',
'required' => false,
'visible_on_front' => true,
'backend' => 'My\Module\Model\Product\Attribute\Backend\Datasheet'

Here is my backend model:

namespace My\Module\Model\Product\Attribute\Backend;

use Magento\Framework\App\Filesystem\DirectoryList;

class Datasheet extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
    const MEDIA_SUBFOLDER = 'datasheet';

    protected $_uploaderFactory;
    protected $_filesystem;
    protected $_fileUploaderFactory;
    protected $_logger;

     * Construct
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Filesystem $filesystem
     * @param \Magento\Framework\File\UploaderFactory $uploaderFactory
    public function __construct(
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Framework\File\UploaderFactory $uploaderFactory
    ) {
        $this->_filesystem = $filesystem;
        $this->_uploaderFactory = $uploaderFactory;
        $this->_logger = $logger;

    public function afterSave($object)
        $attributeName = $this->getAttribute()->getName();
        $fileName = $this->uploadFileAndGetName($attributeName, $this->_filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getAbsolutePath(self::MEDIA_SUBFOLDER));

        if ($fileName) {
            $object->setData($attributeName, $fileName);
            $this->getAttribute()->getEntity()->saveAttribute($object, $attributeName);

        return $this;

    public function uploadFileAndGetName($input, $destinationFolder)
        try {
            $uploader = $this->_uploaderFactory->create(array('fileId' => 'product['.$input.']'));

            return $uploader->getUploadedFileName();
        } catch (\Exception $e) {
            if ($e->getCode() != \Magento\Framework\File\Uploader::TMP_NAME_EMPTY) {
                throw new \FrameworkException($e->getMessage());

        return '';

Attribute is shown in backend and saves value in database.


Attribute value is not shown in backend.


  • Created issue in magento2 github: github.com/magento/magento2/issues/7023 Commented Oct 14, 2016 at 17:09
  • Hmm if that's all you have then you're missing the part to actually make it show the value on the backend. Can you provide the full module? I've done something similar to category attributes, pretty sure it will not be that much different. Commented Oct 17, 2016 at 14:25
  • That's all I have. I assume I need to add some layout xml and create some template for this field type Commented Oct 17, 2016 at 17:02
  • I'll have a look at this when i get some free time today. Commented Oct 18, 2016 at 12:03
  • 1
    @AndréFerraz can you please check my answer? Commented Oct 19, 2016 at 14:13

2 Answers 2



file input is not implemented in Magento 2.1 yet.

in vendor/magento/module-ui/view/base/web/templates/form/element/media.html there is only uploader and no view and delete button:

 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
<input class="admin__control-file" type="file" data-bind="
    hasFocus: focused,
    attr: {
        name: inputName,
        placeholder: placeholder,
        'aria-describedby': noticeId,
        id: uid,
        disabled: disabled,
        form: formId

It will be implemented in next Magento2 release hopefully.

So I thought to implement a custom renderer, but


Magento2 way is to use UI components and PHP Modifiers

Create 3 files:

etc/adminhtml/di.xml - dependency injection configuration for adminhtml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
            <argument name="modifiers" xsi:type="array">
                <item name="your_module_datasheet" xsi:type="array">
                    <item name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Product\Form\Modifier\Datasheet</item>
                    <item name="sortOrder" xsi:type="number">150</item>

your_module_datasheet can be any name.

With sortOrder from 10 to 50 it didn't work for me. $meta variable in Modifier was empty.

Ui/DataProvider/Product/Form/Modifier/Datasheet.php - php modifier

namespace Vendor\Module\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\Framework\UrlInterface;

 * Data provider for "Datasheet" field of product page
class Datasheet extends AbstractModifier
     * @param LocatorInterface            $locator
     * @param UrlInterface                $urlBuilder
     * @param ArrayManager                $arrayManager
    public function __construct(
        LocatorInterface $locator,
        UrlInterface $urlBuilder,
        ArrayManager $arrayManager
    ) {
        $this->locator = $locator;
        $this->urlBuilder = $urlBuilder;
        $this->arrayManager = $arrayManager;

    public function modifyMeta(array $meta)
        $fieldCode = 'datasheet';
        $elementPath = $this->arrayManager->findPath($fieldCode, $meta, null, 'children');
        $containerPath = $this->arrayManager->findPath(static::CONTAINER_PREFIX . $fieldCode, $meta, null, 'children');

        if (!$elementPath) {
            return $meta;

        $meta = $this->arrayManager->merge(
                'children'  => [
                    $fieldCode => [
                        'arguments' => [
                            'data' => [
                                'config' => [
                                    'elementTmpl'   => 'Vendor_Module/grid/filters/elements/datasheet',

        return $meta;

     * {@inheritdoc}
    public function modifyData(array $data)
        return $data;

view/adminhtml/web/template/grid/filters/elements/datasheet.html - knockout js template

<!-- ko if: $parent.source.data.product[code] -->
    <!-- todo: dynamically get path to file from config or controller -->
    <a attr="href: '/pub/media/datasheet'+$parent.source.data.product[code]" text="$parent.source.data.product[code]"></a>
    <label attr="for: uid+'_delete'">
        <!-- todo: generate name -->
        <input type="checkbox" attr="name: 'product['+code + '_delete]', id: uid+'_delete', form: formId">
        <span data-bind="i18n:'Delete'"></span>
<!-- /ko -->
<input class="admin__control-file" type="file" data-bind="
    hasFocus: focused,
    attr: {
        name: inputName,
        placeholder: placeholder,
        'aria-describedby': noticeId,
        id: uid,
        disabled: disabled,
        form: formId

It is still looks dirty to me and a lot of things to improve.


I found some interesting files, which can bring you other ideas how to solve it:


<item name="formElement" xsi:type="string">fileUploader</item>


<fileUploader class="Magento\Ui\Component\Form\Element\DataType\Media">
  • I did the second solution but always return this field is required when save despite uploading file. Also is it possible to have custom controller to save this file your own way. Thanks for sharing Commented Jan 18, 2017 at 8:23
  • What do you mean by second solution? You should use modifier class and you can have different modifiers for different field names. Commented Jan 18, 2017 at 9:54
  • I am getting the Exception "$_FILES array is empty" - did you also encounter this? I am on 2.1.6 Commented Jun 1, 2017 at 12:55
  • i am not able to make this work do it works ? Commented Oct 14, 2018 at 15:28
  • @AlekseyRazbakov can you tell will with above way can i create attribute type file or it adds new attribute as i tried but it did not worked Commented Oct 14, 2018 at 15:32

If you need to upload the files in product page tab, no need to create the product custom attribute. just install the following extension

Then php bin/magento setup:upgrade php bin/magento setup:static-content:deploy en_GB php bin/magento indexer:reindex php bin/magento cache:clean

working perfectly.


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.