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.
Add-on identity
Section titled “Add-on identity”| 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_ |
Verification status of every source
Section titled “Verification status of every source”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.
Rules (law)
Section titled “Rules (law)”| 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 |
EPREL register and API
Section titled “EPREL register and API”| 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.
What the add-on does with those rules
Section titled “What the add-on does with those rules”| 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 |
Structure
Section titled “Structure”| 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 |
Rate limiting and caching
Section titled “Rate limiting and caching”- 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.
Database tables
Section titled “Database tables”| 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.
Hooks used
Section titled “Hooks used”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).
Full page cache (CS-Cart 4.21)
Section titled “Full page cache (CS-Cart 4.21)”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.
Test the HTTP layer without EPREL
Section titled “Test the HTTP layer without EPREL”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).
Template overrides
Section titled “Template overrides”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.