magendoo / module-base-price

magendoo/module-base-price

EU Base Price (Grundpreis) display for Magento 2. Shows price per reference unit (kg, litre, m) on product pages, category listings, search results and cart. Compliant with EU Price Indication Directive 98/6/EC and German PAngV.

magento2-module Compatibility: 2.4.7-2.4.9 Code Quality: Fail Tests: Pass Security: Pass MIT

Are you the maintainer of magendoo?

Packagento pulls magendoo's Composer packages from the public registry so buyers can find them here.

Claim the namespace to take ownership, publish new releases directly, and start charging for premium versions.

Claim this namespace →

Magendoo Base Price — Magento 2 Module

Displays a per-unit reference price (Grundpreis) next to every product's selling price across product pages, category listings, search results, and the shopping cart.

A customer buying a 300 g shampoo at €4.99 also sees "Base price: €16.63 / 1 kilogram", enabling direct price comparison across different pack sizes and brands.


Legal background

Displaying a unit reference price is legally required in several jurisdictions:

  • Germany — Preisangabenverordnung (PAngV §4)
  • EU member states — Price Indication Directive 98/6/EC, updated by Directive 2019/2161 (Omnibus)

The obligation applies to products sold by weight, volume, length, area, or piece count that are offered in different pack sizes. Consult your legal adviser to determine your specific requirements.


Requirements

Dependency Version
Magento Open Source / Adobe Commerce 2.4.x
PHP 8.1 or higher

Installation

Via Composer (recommended)

composer require magendoo/module-base-price
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Manual

mkdir -p app/code/Magendoo/BasePrice
# Copy all module files into that directory
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

setup:upgrade creates the magendoo_baseprice_unit table, adds four EAV product attributes, and seeds nine SI measurement units.


Configuration

Navigate to Stores → Configuration → Magendoo Extensions → Base Price.

General

Field Default Description
Enable Base Price Yes Master switch. Disabling this hides base prices everywhere.

Product Page

Field Default Description
Enable on Product Pages Yes Show base price on individual product pages.
Display Template Base price: {{unit_price}} / {{reference_amount}} {{reference_unit}} Output format. Supports all template variables.
CSS Class base-price Class applied to the wrapping <div>.

Category & Search

Field Default Description
Enable on Category Pages Yes Show base price on category listing pages.
Category Display Template ({{unit_price}} / {{reference_amount}} {{reference_unit}}) Output format for listing cards.
Enable on Search Results Yes Show base price on catalogsearch result pages.

Cart

Field Default Description
Enable on Cart Page Yes Show base price in the shopping cart item rows.
Cart Display Template Base price: {{unit_price}} / {{reference_amount}} {{reference_unit}} Output format for cart rows.
Enable on Mini-Cart Yes Reserved for future Knockout.js integration.

Template variables

All display templates accept the following placeholders:

Variable Description Example output
{{unit_price}} Formatted price per reference unit €16.63
{{reference_amount}} Reference quantity 1
{{reference_unit}} Reference unit label kilogram
{{product_amount}} Numeric amount in the product 300
{{measure_unit}} Name of the product's measurement unit gram
{{product_price}} Selling price formatted via store currency €4.99

Unknown placeholders are left intact, so you can safely mix in static text.


Formula

base_price = (selling_price ÷ product_amount) × (reference_amount × rate)

Where rate is the conversion factor from the product unit to the reference unit (e.g. 1 000 for gram → kilogram).

Examples:

Product Price Amount Unit Calculation Base price
Shampoo €4.99 300 g gram (4.99 ÷ 300) × (1 × 1 000) €16.63 / kg
Wine €9.99 0.75 l litre (9.99 ÷ 0.75) × (1 × 1) €13.32 / litre
Ribbon €5.00 200 cm centimetre (5.00 ÷ 200) × (1 × 100) €2.50 / metre
6-pack €5.94 6 piece (5.94 ÷ 6) × (1 × 1) €0.99 / piece

The result is rounded to 4 decimal places before formatting.

Reference amount override

A product can override the unit's default reference amount. Set Reference Amount to 0.1 on a gram-unit product to show the price per 100 g instead of per kilogram:

(4.99 ÷ 300) × (0.1 × 1 000) = €1.66 / 100 g

Unit management

Navigate to Catalog → Base Price Units in the admin panel.

Nine SI units are pre-installed:

ID Unit Reference unit Rate
1 gram kilogram 1 000
2 milligram kilogram 1 000 000
3 kilogram kilogram 1
4 millilitre litre 1 000
5 centilitre litre 100
6 litre litre 1
7 centimetre metre 100
8 metre metre 1
9 piece piece 1

Adding a custom unit

Click Add New Unit and fill in:

Field Description Example
Name Display name of the product unit fluid ounce
Reference Unit Label The reference unit shown in the output litre
Rate Multiplier to convert to the reference unit 33.814
Reference Amount Default quantity shown in the output 1
Position Sort order in the admin grid 100
Status Active / Inactive Active

Configuring products

Admin UI

Open any product in the admin and locate the Base Price panel (below the standard price fields):

Field Description
Enable Base Price Override the module-level enable/disable for this specific product.
Product Amount Numeric quantity contained in the package (e.g. 300 for 300 g).
Unit Select from the unit list (gram, millilitre, etc.).
Reference Amount Leave blank to use the unit's default. Set a value to show a custom reference (e.g. 0.1 for "per 100 g").

REST API — set a product's base price

TOKEN=$(curl -s -X POST https://your-store.com/rest/V1/integration/admin/token \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"your-password"}' | tr -d '"')

# Set base price (300 g, gram unit = ID 1)
curl -X PUT https://your-store.com/rest/V1/baseprice/products/24-MB01 \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "productBasePrice": {
      "sku": "24-MB01",
      "product_amount": 300,
      "unit_id": 1
    }
  }'

# Enable it on the product
curl -X PUT https://your-store.com/rest/V1/products/24-MB01 \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "product": {
      "sku": "24-MB01",
      "custom_attributes": [
        {"attribute_code": "baseprice_is_enabled", "value": "1"}
      ]
    }
  }'

REST API reference

Units (admin token required)

Method Endpoint Description
GET /V1/baseprice/units List units (supports searchCriteria)
GET /V1/baseprice/units/:unitId Get unit by ID
POST /V1/baseprice/units Create a unit
PUT /V1/baseprice/units/:unitId Update a unit
DELETE /V1/baseprice/units/:unitId Delete a unit

Create a unit:

curl -X POST https://your-store.com/rest/V1/baseprice/units \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "unit": {
      "name": "fluid ounce",
      "reference_unit_label": "litre",
      "rate": 33.814,
      "reference_amount": 1,
      "position": 100,
      "active": true
    }
  }'

Note: The active flag is sent as active, not is_active. Magento's serialiser strips the is prefix from getter method names when building the response body.

Product base price (admin token required)

Method Endpoint Description
GET /V1/baseprice/products/:sku Get base price config for a product
PUT /V1/baseprice/products/:sku Set base price config
DELETE /V1/baseprice/products/:sku Remove base price config

Calculator (anonymous)

GET /V1/baseprice/calculate?price=4.99&productAmount=300&unitId=1

Returns the raw base price as a float. Optional parameter: referenceAmountOverride.

curl 'https://your-store.com/rest/V1/baseprice/calculate?price=4.99&productAmount=300&unitId=1'
# → 16.6333

Architecture notes

  • EAV attributes — Four product attributes (baseprice_is_enabled, baseprice_product_amount, baseprice_unit_id, baseprice_reference_amount) are registered with used_in_product_listing = true so they load in category and search collections.
  • Listing injection — A plugin on Magento\Catalog\Block\Product\AbstractProduct::getProductDetailsHtml() appends the base price HTML to every product card, covering all product types without type-specific renderer registration.
  • Cart injection — The cart block is a child of Magento's additional.product.info block. The cart item template iterates through its children and calls setItem() + toHtml() on each.
  • Product page — A standard Template block is added via catalog_product_view.xml and the product block is retrieved directly from layout.
  • Template renderingModel/TemplateRenderer does a plain str_replace over {{variable}} placeholders. HTML escaping is the caller's responsibility; priceCurrency->format() handles currency formatting.

Releases and Packagist

Creating a release

Tag a version and push — the Release workflow runs tests then creates a GitHub release automatically:

git tag v1.2.0
git push origin v1.2.0

Version numbers follow Semantic Versioning. Update CHANGELOG.md and composer.json ("version") before tagging.

Publishing to Packagist

  1. Go to packagist.org/packages/submit and enter:

    https://github.com/magendooro/module-base-price
    
  2. After submission, enable auto-update so Packagist picks up new tags automatically:

    • In the Packagist package settings, copy the webhook URL
    • In the GitHub repository: Settings → Webhooks → Add webhook → paste the URL
    • Content type: application/json, events: Just the push event

    Alternatively, connect your GitHub account to Packagist (Settings → Profile → GitHub) which handles webhooks automatically.

GitHub Secrets required for CI

The workflows install Magento packages from repo.magento.com, which requires Marketplace credentials. Add these to the repository as Actions secrets (Settings → Secrets and variables → Actions):

Secret Description
MAGENTO_MARKETPLACE_PUBLIC_KEY Public key from marketplace.magento.com/customer/accessKeys
MAGENTO_MARKETPLACE_PRIVATE_KEY Corresponding private key

Without these secrets the CI runs will fail at the composer install step.


Running tests

PHPUnit (unit tests)

# From the Magento root
vendor/bin/phpunit --filter Magendoo\\BasePrice app/code/Magendoo/BasePrice/Test/Unit

Playwright (functional tests)

The functional test suite lives in dev/tests/functional/playwright/tests/baseprice/ and covers:

  • baseprice-calculate-combinations.spec.js — All nine seed units, referenceAmountOverride, edge cases, formula consistency (45 tests, no products or admin setup needed — hits the anonymous /V1/baseprice/calculate endpoint directly)
  • baseprice-product-display.spec.js — Product detail page, category listing, cart page, disabled state (22 tests, sets up three sample products in beforeAll and restores them in afterAll)
cd dev/tests/functional/playwright
npx playwright test tests/baseprice/

Uninstall

bin/magento module:uninstall Magendoo_BasePrice
bin/magento setup:upgrade
bin/magento cache:flush

This removes the magendoo_baseprice_unit table and the four EAV product attributes.


License

MIT — see LICENSE for details.

Changelog

All notable changes to this project will be documented in this file.

The format follows Keep a Changelog.
This project adheres to Semantic Versioning.

Unreleased

1.0.0 - 2026-04-13

Added

  • Base price calculation and display on product detail pages, category listing pages, search result pages, and the shopping cart
  • Formula: (selling_price ÷ product_amount) × (reference_amount × rate), rounded to 4 decimal places
  • Nine pre-installed SI measurement units: gram, milligram, kilogram, millilitre, centilitre, litre, centimetre, metre, piece
  • Configurable display templates with six {{variable}} placeholders: unit_price, reference_amount, reference_unit, measure_unit, product_amount, product_price
  • Admin unit management grid at Catalog → Base Price Units (create, edit, delete, mass-enable/disable)
  • Per-product enable/disable toggle and optional reference amount override (e.g. show per 100 g instead of per kg)
  • REST API: unit CRUD (/V1/baseprice/units), product base price read/write/delete (/V1/baseprice/products/:sku), and an anonymous calculator endpoint (/V1/baseprice/calculate)
  • EAV attributes baseprice_is_enabled, baseprice_product_amount, baseprice_unit_id, baseprice_reference_amount with used_in_product_listing = true for efficient collection loading
  • 32 PHPUnit unit tests covering every calculation combination, reference amount override, edge cases, template rendering, and formatting
  • PHPStan level 2 clean
Versions
Version Stability QA Status Compatibility Released
1.0.0 stable Fail Magento 2.4.7-2.4.9 Details 2026-04-13 13:29:21

Requires 8

Package Constraint
magento/framework >=102.0.0
magento/module-backend *
magento/module-catalog *
magento/module-config *
magento/module-eav *
magento/module-store *
magento/module-ui *
php >=8.1

Requires-dev 2

Package Constraint
phpstan/phpstan ^1.10 || ^2.0
phpunit/phpunit ^10.5

Compatibility

Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.

Compatibility matrix (Magento × PHP)
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Pass Pass
2.4.8 Pass Pass
2.4.9 Pass Pass

Code Quality

Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.

Static analysis

Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.

Static analysis results
Tool Status Findings Summary
PHPCS Warning 89 89 warnings (ruleset: Magento2), 7 auto-fixable with phpcbf
PHPMD Warning 50 50 rule violations (UnusedPrivateField:50)
Cpd Pass 0
Composer validate Info 8 valid; 8 advisory notes (composer validate --strict)

PHPStan

Type-checks the module's PHP against a real Magento install at the configured gate level. Re-runs per Magento and PHP version because resolvable symbols differ between releases.

PHPStan results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 2 2
2.4.8 2 2
2.4.9 2 2

Tests

Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.

Unit tests

Unit tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 Pass Pass
2.4.8 Pass Pass
2.4.9 Pass Pass

Integration tests

Integration tests results by Magento and PHP version
Magento PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
2.4.7 N/A N/A
2.4.8 N/A N/A
2.4.9 N/A N/A

Security

Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.

Security results
Tool Status Findings Summary
Composer audit Pass 0
Malware scan Pass 0
License
MIT
Authors
  • Magendoo

More from magendoo

View vendor
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.