The mission: We are selling art and want to make it possible to order the products by creation year.
Shopware has an excellent documentation about how to add a new sorting to product listing. But unfortunately no one is telling us how to add a new feature/property/attribute/you name it to a product, so here is our solution (which most likely can me improved a lot -> feel free to comment!)
Extend the product
After this, we have a new database table which contains our creation year.
Edit the creation year on administration
We add it right to the labelling, but feel free to add it somewhere else.

Everything we do now is JavaScript, living in the following directory, for easier reading I’ll use paths relative to the src
directory. I’ll post the files first and afterwards I’ll try to explain what is going on, where are references and why we did some things the way they are.
/custom/static-plugins/WinkelwagenManufactureYear/src/Resources/app/administration/src
We start with the template. It lives in:
extension/sw-product-settings-form/sw-product-settings-form.html.twig
{% block sw_product_settings_form_content %} {% parent %} <sw-container columns="repeat(auto-fit, minmax(250px, 1fr)" gap="0px 30px"> <sw-text-field :label="$tc('ww.product.settingsForm.manufactureYear')" :placeholder="$tc('ww.product.settingsForm.placeholderManufactureYear')" @change="onManufacturerYearChanged($event, product, this)" :value="currentValue" ></sw-text-field> </sw-container> {% endblock %}
And here is the main.js for this:
import template from './extension/sw-product-settings-form/sw-product-settings-form.html.twig'; import deDE from './snippet/de-DE'; import enGB from './snippet/en-GB'; Shopware.Component.override('sw-product-settings-form', { template, inject: ['repositoryFactory'], snippets: { 'de-DE': deDE, 'en-GB': enGB }, data: function () { return { repository: undefined, currentValue: '' } }, created() { this.repository = this.repositoryFactory.create('ww_manufacture_year'); let productLoaded = setInterval(function () { if (!this.product.extensions) { return; } this.currentValue = this.product.extensions.manufactureYear.manufactureYear ? this.product.extensions.manufactureYear.manufactureYear : ''; clearInterval(productLoaded); }.bind(this), 50); }, methods: { onManufacturerYearChanged: function (value, product) { this.currentValue = value; const yearObj = product.extensions.manufactureYear ? product.extensions.manufactureYear : this.repository.create(); yearObj.productId = product.id; yearObj.manufactureYear = value; product.extensions.manufactureYear = yearObj; } } });
The most important thing to note: We did NOT implement our own module, but instead extended/overwrote sw-product-settings-form
. I’m sure this is not ideal and there are other ways, but we are still learning and if it works, it works.
:label="$tc('ww.product.settingsForm.manufactureYear')" :placeholder="$tc('ww.product.settingsForm.placeholderManufactureYear')"
The label and the placeholder are just translated strings. Make sure to load the snippets it in your main.js
. The json files live here:
src/Resources/app/administration/src/snippet/de-DE.json
src/Resources/app/administration/src/snippet/en-GB.json
The interesting part is the behaviour if you @change
the value and the value
itself:
:value="currentValue"
@change="onManufacturerYearChanged($event, product, this)"
The value is a property on the Component
and is watched by VueJS. If we change it, it is automatically updated. Here comes the first problem. The value we want to read and write is this: this.product.extensions.manufactureYear.manufactureYear
but unfortunately I couldn’t make it work, because of at least one reason: When the Component is loaded, product isn’t filled with data yet and therefore accessing this.product.extensions.manufactureYear.manufactureYear
throws an error. So we fixed that by waiting until the product with its extension is loaded (which can be found in created()
). We create an Interval
which checks every 50ms wether product.extension
exists already, if this is the case, we set currentValue
, but only if the product has already a manufactureYear
and stop the retrying.
let productLoaded = setInterval(function () { if (!this.product.extensions) { return; } this.currentValue = this.product.extensions.manufactureYear.manufactureYear ? this.product.extensions.manufactureYear.manufactureYear : ''; clearInterval(productLoaded); }.bind(this), 50);
The change event handler is so long because of the same reason. We had problems accessing product.extensions.manufactureYear
in the beginning and updating it. But this works via our indirection well, too.
We set the currentValue
, so the field is updated, we either create our extension object or update an existing one and set it on the product
, so once the product is saved, our extension is saved as well.
methods: { onManufacturerYearChanged: function (value, product) { this.currentValue = value; const yearObj = product.extensions.manufactureYear ? product.extensions.manufactureYear : this.repository.create(); yearObj.productId = product.id; yearObj.manufactureYear = value; product.extensions.manufactureYear = yearObj; } }
And that’s it, we now have a new field in the Shopware administration to update an extension.
We hope that helps you implementing your own and if you have any comments, feel free to leave them here!