Skip to content

Developer notes

Technical reference for developers who customize EU Energy Label & EPREL Display, and the sources behind every rule the add-on implements.

This add-on helps a shop show the energy class arrow, the label and the product information sheet the way the EU rules describe. It does not check your products, your registrations or your legal position, and nothing here is legal advice. Read the regulations for your product groups.

Item Value
Add-on id kv_eu_energy_label
Function prefix fn_kv_eu_energy_label_
Language variables kv_eu_energy_label.*
CSS scope .kv-el (storefront), .kv-eu-energy-label (admin)
PHP namespace Tygh\Addons\KvEuEnergyLabel
Table prefix ?:kv_eu_energy_label_

Legend: verified = the text was read in the official publication while building the add-on; unverified = taken from third-party descriptions or from memory of the public pages and must be checked by you; flagged = the add-on lets you change it in Settings.

Rule Source Status
Dealers must display the label “including for online distance selling” and make the product information sheet available Regulation (EU) 2017/1369, Article 5(1)(a) and (b), https://eur-lex.europa.eu/eli/reg/2017/1369/oj Read through the EUR-Lex text, wording summarised: check the exact words on EUR-Lex
Distance selling: “the label and product information sheet are provided” following Annexes VII and VIII; visual advertisements and technical promotional material on the internet show the class and the range Delegated Regulation (EU) 2019/2016 (refrigerating appliances), Article 4(1)(b) to (d), https://eur-lex.europa.eu/eli/reg_del/2019/2016/oj verified (full text read)
Hosting platforms must “enable the showing of the electronic label and electronic product information sheet” 2019/2016, Article 5 verified
Definitions: display mechanism (38), nested display (40, “visual interface where an image or data set is accessed by a mouse click, mouse roll-over or tactile screen expansion”), alternative text (41) 2019/2016, Annex I verified
The seven arrow colours (CMYK): A 100,0,100,0; B 70,0,100,0; C 30,0,100,0; D 0,0,100,0; E 0,30,100,0; F 0,70,100,0; G 0,100,100,0; class letter 100 % white 2019/2016, Annex III verified
Internet display (Annex VIII point 1): the label is shown “in proximity to the price”, clearly visible and legible; it may be shown by a nested display; with a nested display “the label shall appear on the first mouse click, mouse roll-over or tactile screen expansion on the image” 2019/2016, Annex VIII verified
Nested display image (Annex VIII point 2): an arrow in the colour of the class, the class “in 100 % white, Calibri Bold and in a font size equivalent to that of the price”, the range of available classes in 100 % black, one of two formats (left or right arrow), a visible black border around the arrow and the letter, the letter in the centre of the rectangular part 2019/2016, Annex VIII verified
Sequence (Annex VIII point 3): image near the price, linked to the label, label shown after click / roll-over / touch, shown by pop-up, new tab, new page or inset, closed by a close option or other standard closing mechanism, alternative text “the energy efficiency class of the product in a font size equivalent to that of the price” 2019/2016, Annex VIII verified
Product information sheet (Annex VIII point 4): shown in proximity to the price, may be a nested display or a link to the product database; the link “shall clearly and legibly indicate ‘Product information sheet’” 2019/2016, Annex VIII verified
The same Annex structure is used by the other product-group acts: 2019/2013 (electronic displays), 2019/2014 (washing machines), 2019/2015 (light sources), 2019/2017 (dishwashers), 2023/1669 (smartphones and tablets), 2023/2534 (tumble dryers) EUR-Lex unverified: the EUR-Lex pages were not readable at build time for these acts. Check the annex “distance selling through the internet” of each group you sell. Scales and details can differ (for example the lamp label)
Scales per group: A-G (2019/2013, 2019/2014, 2019/2015, 2019/2016, 2019/2017, 2019/2018, 2023/1669, 2023/2534); A+++ to D (old labels 626/2011, 65/2014 ovens and range hoods placed on the market from 1 January 2020, 1059/1060/1061/2010, 392/2012); water heaters (812/2013) A+ to F or A to G by load profile, so no default Delegated acts named The add-on applies the seven colours by position (Annex III) on the scale the register states for the product; a class that is not on the scale is refused, never moved to another scale
Groups the add-on does not draw: tyres (A to E), space heaters and local space heaters, combination heaters, solid fuel boilers, residential ventilation units, water heater packages, professional refrigerated cabinets, heat pumps. A group that the add-on does not list gets no default scale: the register must state the range, or the merchant names the scale. A register answer that states any range other than A-G, A+++ to D, A++ to E, A+ to F is also not drawn (state “Label not supported”) Their own acts have other scales (nine or ten classes, A to E) Excluded on purpose: a seven-class arrow would show a wrong range and a wrong colour
Smartphone and tablet repairability class (A to E) 2023/1669 Not implemented in 1.0
Item Source Status
Registration number is the number in the QR code of the label; scanning the QR code “returns the address of the registration in EPREL” Commission page “EPREL: generated labels and PDFs”, https://energy-efficient-products.ec.europa.eu/suppliers/eprel-generated-labels-and-pdfs_en verified (page read)
Label formats: PDF, SVG, PNG (language neutral). Product information sheet: PDF, in the EU languages the supplier selects same page verified
Label, product information sheet and parameters are available “via specific Application Programming Interfaces (APIs)” same page verified that APIs exist; the page lists no endpoints
Public API terms and key request https://eprel.ec.europa.eu/screen/requestpublicapikey and the “Public API Terms and Conditions” PDF linked from it Page and PDF address verified to exist; the PDF text could not be read at build time: read the terms before use (rate, storage, attribution)
Key header X-API-Key Third-party descriptions and an independent open-source client; the key is sent only to eprel.ec.europa.eu (strict host check on every request and on every redirect hop; a template that names another host is fetched without the key)
Product data: GET https://eprel.ec.europa.eu/api/products/{productGroup}/{registrationNumber} Live register checked against the live register on 2026-11-07 (anonymous read-only requests; release audit); constant Eprel::PRODUCT_PATH
Label image: GET .../api/products/{group}/{reg}/labels?format=PNG Live register checked against the live register on 2026-11-07 (anonymous read-only requests; release audit): answers 301 to /labels/{group}/Label_{reg}_big_color.png; the add-on follows up to 2 redirects on the same host. Flagged: setting “Label address template”
Product information sheet: GET .../api/products/{group}/{reg}/fiches?language=EN Live register checked against the live register on 2026-11-07 (anonymous read-only requests; release audit): the language code must be UPPER case; language=en ends at the register web page, not a PDF. The add-on writes the code in upper case. Flagged: setting “Product information sheet address template”
QR address https://eprel.ec.europa.eu/qr/{reg} Live register checked against the live register on 2026-11-07 (anonymous read-only requests; release audit): 301 to the product screen. Flagged: setting “Registry page address template”
JSON fields read: eprelRegistrationNumber, productGroup, energyClass, energyClassRange, modelIdentifier, supplierOrTrademark (or organisationName), status Live register checked against the live register on 2026-11-07 (anonymous read-only requests; release audit); the parser is tolerant (Eprel::parse), unknown fields are ignored. Fixtures in tests/fixtures are constructed (see tests/README-fixtures.md)
Product group identifiers (electronicdisplays, refrigeratingappliances2019, tumbledryers20232534, refrigeratingappliancesdirectsalesfunction, …) Register group list The list in Eprel::groups() was corrected after the release audit of 2026-11-07 (current and old acts kept apart); the register’s own /api/product-groups list is authoritative and the field accepts any identifier

Official documentation search (2026-11-07, second attempt): the EPREL “Request public API key” page (https://eprel.ec.europa.eu/screen/requestpublicapikey) is a script-rendered portal page whose text could not be read, the Commission pages list no endpoints, and no technical API PDF turned up on eprel.ec.europa.eu, energy-efficient-products.ec.europa.eu or data.europa.eu. So the endpoints stay unverified against an official document. They are, however, corroborated by an independent open-source client (https://github.com/maschmann/eprel-public-api-client, EprelClient.php): base https://eprel.ec.europa.eu/api, header X-Api-Key (HTTP header names are case-insensitive, so X-API-Key is the same), GET products/{group}/{reg}, GET products/{group}/{reg}/labels, GET products/{group}/{reg}/fiches (query language=XX, repeatable; noRedirect=true returns a JSON address instead of a redirect), GET product/{reg} (without group), GET product-groups, GET products?productGroup=&energyClass=&size=&page= (search: totalElements, content[] with brandName, modelIdentifier, registrationNumber, energyClass), GET exportProducts/{group}, GET product/gtin/{gtin}. Detail fields there: brandName, energyLabelUrl, productInformationSheetUrl. The label query parameter format=PNG is not confirmed by that client (it passes caller-chosen parameters). The add-on reads brandName as a supplier-name alternative and keeps all three address templates editable. Always verify with your EPREL API key against the documentation you receive.

Because the endpoint reference is only available to key holders, the first thing to do with a key is to compare the five rows above with the documentation you receive and change the three address templates if they differ. A wrong address does not break the store: the storefront arrow still shows (the class is stored), and the label link then opens whatever address you configured.

Rule Implementation
Arrow in the class colour, white letter, black border, range in black Scales (colours, scales), components/arrow.tpl (inline SVG, class letter in Calibri Bold with fallbacks, stroke="#000"), .kv-el__range (black on white so it stays black in dark themes)
Colours Scales::COLOURS are screen (sRGB) approximations of the CMYK values; a screen cannot reproduce CMYK exactly. Scales::CMYK keeps the regulation values and a unit test pins them
Left or right arrow Setting “Arrow direction”
In proximity to the price Template hook products:main_price (post) on the product page and quick view, products:list_price_block (post) in lists
Letter size “equivalent to that of the price” Setting “Arrow height” (px, product page and lists). The letter is about 60 percent of the height. The shop owner must set a height that gives a letter at least as large as the price in their theme; the add-on cannot read the theme
Nested display js/addons/kv_eu_energy_label/func.js: label opens on click or tap (or roll-over by setting) in a modal or pop-over with a close button, Escape and click-outside closing; without JavaScript the arrow is a link that opens the label in a new tab
Alternative text The label image alt is “Energy efficiency class X”; the arrow has an accessible name with class and range
Product information sheet link Text “Product information sheet” (language variable kv_eu_energy_label.fiche_link) next to the arrow on the product page
Nothing is loaded from EPREL before a click The label image is not in the page; the address sits in data-kv-el-label. A stored copy is served from your own domain
Class Role
Scales Scales, class normalisation, colours, range text
Eprel Address templates, registration and group validation, response parsing
Items Table kv_eu_energy_label_items (one row per product), search, due list for the cron job
Display Storefront view data ($product.kv_el), attached in get_products_post and get_product_data_post
Fetcher, Jobs, Queue Reads one registration per job; back-off 5 min / 15 min / 1 h / 3 h / 12 h; 429 waits for Retry-After; overlap lock
Client\BaseClient, Client\Api cURL with TLS verification, X-API-Key, error mapping, file type sniffing
Accounts, Secret The API key sealed with AES-256-GCM; one account for the store owner, optional per vendor
Upload Label (PNG, JPG, PDF) and sheet (PDF) uploads, type by magic bytes, 3 MB, no SVG
Csv Import and export
Config, Access, Log Settings, who may see what, event log with masking
  • Registrations are stored in the database; one is read again only after “Read each registration again after (days)” (default 7).
  • At most “Products per cron run” (default 20) registrations are queued per run and each job pauses “Pause between requests” (default 300 ms).
  • A 429 answer re-queues the job after the Retry-After time (at least a minute). A 404 or 412 marks the registration “not found” and is not retried until the cache time passes.
  • The Commission’s own rate limit and storage terms are in the Public API terms: check them and lower the numbers if needed.
Table Purpose
?:kv_eu_energy_label_items Registration, group, scale, class, address overrides, stored file names, fetch state
?:kv_eu_energy_label_accounts The sealed API key per company, last test, extra test fields
?:kv_eu_energy_label_log Log
?:kv_eu_energy_label_jobs Queue

Files: images/kv_eu_energy_label/ (label copies <reg>.png, uploads <product>-label-<hash>.<ext>). Removed on uninstall.

PHP: get_products_post, get_product_data_post (storefront data), update_product_post (saves the editor tab and uploads), delete_product_post, clone_product. Admin controller products.post.php adds the tab. Block cache: schemas/block_manager/blocks.post.php adds the items table and a settings key to the cache handlers of the main block and the product list blocks, so cached blocks follow data and settings.

Template hooks: storefront products:main_price (post), products:list_price_block (post), index:styles, index:scripts; admin products:tabs_content (post).

Everything printed depends on the product, the language and the settings only, never on the visitor or the session, so cached pages stay correct. Data changes invalidate the cached blocks through the table handler. This is the design; it has not yet been run with the full page cache enabled on a 4.21 store (kv qa fpc is still to do), so do not read it as a tested claim.

The base URL can be overridden for tests in config.local.php (development only):

$config['kv_eu_energy_label_base_url'] = 'http://127.0.0.1:11864';

node tests/mock-server/server.js 11864 starts a local stand-in (it answers the shapes above: this proves the add-on against the mock, not against the register); node tests/e2e/e2e.mjs --store <url> --db <db> --dir <store root> runs the HTTP end-to-end test; php tests/unit.php runs the unit tests (scale and colour mapping, response parsing, CSV, client).

Admin templates live in design/backend/templates/addons/kv_eu_energy_label/, storefront templates in design/themes/<theme>/templates/addons/kv_eu_energy_label/ (copied from var/themes_repository/responsive). To change the arrow markup in your theme, copy components/arrow.tpl into a child theme; keep the colours, the white letter, the black border and the class text, which the regulation prescribes.