(written by Grok and reviewed by Max Milbers)

Audience: Template builders, extension developers, shop owners who override one JS or CSS file
Status: VirtueMart 5
Resolver: vmJsApi::setPath in administrator/components/com_virtuemart/helpers/vmjsapi.php
Core files: media/com_virtuemart/js, media/com_virtuemart/css, media/com_virtuemart/images

1. Why the files moved

Joomla 4+ wants browser files (CSS, JavaScript, component images) under media/com_*. PHP stays in components/ and administrator/components/. Child templates already keep their own CSS and JS under media/templates/site/yourtemplate/. VirtueMart 5 follows the same split.

That is not a shop-security patch. It does not replace input filtering, tokens, or ACL. It does mean VirtueMart is ready for Joomla’s media layout, child templates, and a public folder that only serves static files.

The nice part for humans — and for anyone who likes a clean puzzle — is the same idea as a Linux overlay: same relative path, higher-priority folder wins. You copy one file, not the whole tree.

VirtueMart’s answer: core static files live in media/com_virtuemart/. A template overrides them by putting a file in the Joomla media-template folder. vmJsApi::setPath (used by addJScript, addvScriptModule, css) looks there first. You do not point assets_general_path at your template any more.

2. Core layout

No extra assets wrapper. js, css and images are siblings, so existing CSS url(../images/…) still works.

media/com_virtuemart/js/
media/com_virtuemart/css/
media/com_virtuemart/images/

Examples:

  • media/com_virtuemart/js/product/vmprices.module.js
  • media/com_virtuemart/js/vm-init-registry.module.js
  • media/com_virtuemart/css/vm-ltr-site.css
  • media/com_virtuemart/css/glightbox.css
  • media/com_virtuemart/images/icons/cart.svg
  • media/com_virtuemart/images/vm-preloader.gif

Shop product photos (images/virtuemart/product/ and the media handler) are a different system. Leave them alone.

Admin chrome CSS for the vmadmin template stays under administrator/templates/vmadmin/. That is a template, not this component media tree. Leftover files under administrator/components/com_virtuemart/assets/ are a second tree (logos and old admin JS) and are not this article.

3. Canonical override (do this)

This is Joomla’s HTMLHelper rule for script('com_virtuemart/product/vmprices.module.js') with relative = true: type folder first (js / css / images), then the component name.

Site template cassiopeia, override the prices module:

media/templates/site/cassiopeia/js/com_virtuemart/product/vmprices.module.js

Same file as CSS:

media/templates/site/cassiopeia/css/com_virtuemart/vm-ltr-site.css

Same idea for an image used through setPath:

media/templates/site/cassiopeia/images/com_virtuemart/vm-preloader.gif

Child template mychild whose parent is cassiopeia: put the file on the child first. VirtueMart then tries the parent:

media/templates/site/mychild/js/com_virtuemart/product/vmprices.module.js
media/templates/site/cassiopeia/js/com_virtuemart/product/vmprices.module.js

Older templates that still keep CSS/JS inside templates/ (not inheritable / no media tree) can use:

templates/cassiopeia/js/com_virtuemart/product/vmprices.module.js
templates/cassiopeia/css/com_virtuemart/vm-ltr-site.css

If you also ship a .min.js / .min.css next to the source, setPath prefers the min file when debug is off (same as core). Edit the unminified file; do not hand-edit .min.js.

Do not set configuration assets_general_path to your template folder. That old trick replaced the whole tree. Folder overlay replaces one file. The config field stays for WordPress and emergencies. On Joomla the default is media/com_virtuemart/.

4. Lookup order in setPath

vmJsApi::setPath builds a list, reverses it, and takes the first file that exists (is_file). Minified candidates are tried first when minified output is on, then the unminified names.

Below is the check order for a site view, shop template cassiopeia, no child parent, file product/vmprices.module.js. First hit wins.

  1. media/templates/site/cassiopeia/js/com_virtuemart/product/vmprices.module.js — Joomla standard
  2. templates/cassiopeia/js/com_virtuemart/product/vmprices.module.js — Joomla, old template folder
  3. media/templates/site/cassiopeia/html/com_virtuemart/assets/js/product/vmprices.module.js — VirtueMart layout-style folder, under media
  4. templates/cassiopeia/html/com_virtuemart/assets/js/product/vmprices.module.js — VirtueMart layout-style folder, classic
  5. templates/cassiopeia/js/product/vmprices.module.jsflat leftover (no com_virtuemart segment)
  6. media/com_virtuemart/js/product/vmprices.module.js — core

If the template is a child mychild with parent cassiopeia, this extra step runs immediately after (1):

media/templates/site/cassiopeia/js/com_virtuemart/product/vmprices.module.js

(the child path in (1) is then media/templates/site/mychild/js/com_virtuemart/product/vmprices.module.js).

CSS, same template, file vm-ltr-site.css:

  1. media/templates/site/cassiopeia/css/com_virtuemart/vm-ltr-site.css
  2. templates/cassiopeia/css/com_virtuemart/vm-ltr-site.css
  3. media/templates/site/cassiopeia/html/com_virtuemart/assets/css/vm-ltr-site.css
  4. templates/cassiopeia/html/com_virtuemart/assets/css/vm-ltr-site.css
  5. templates/cassiopeia/css/vm-ltr-site.css — flat
  6. media/com_virtuemart/css/vm-ltr-site.css — core

Administrator (VirtueMart backend template name vmadmin, file be/vmproducts.module.js):

  1. media/templates/administrator/vmadmin/js/com_virtuemart/be/vmproducts.module.js
  2. administrator/templates/vmadmin/js/com_virtuemart/be/vmproducts.module.js
  3. media/templates/administrator/vmadmin/html/com_virtuemart/assets/js/be/vmproducts.module.js
  4. administrator/templates/vmadmin/html/com_virtuemart/assets/js/be/vmproducts.module.js
  5. administrator/templates/vmadmin/js/be/vmproducts.module.js — flat
  6. media/com_virtuemart/js/be/vmproducts.module.js — core

The flat paths (no com_virtuemart after js/ or css/) are the old VirtueMart shortcut. They sit last among overrides, immediately before core. If you already placed the file on the Joomla standard path, is_file succeeds there and the flat folder is never checked. Please move overrides to the standard path. The flat fallback is compatibility, not the public API.

If a caller passes an extra directory into setPath (unusual for templates), that directory is tried after the layout-style folders and before the flat path.

5. ES modules: import map, not ../

Static import cannot read window.Virtuemart. A relative import is resolved against the file that was loaded. If you override only product/vmprices.module.js, then:

import vmInitRegistry from '../vm-init-registry.module.js';

looks next to the override, not next to core, and breaks.

VirtueMart registers an import map prefix and core modules write:

import vmInitRegistry from 'virtuemart/vm-init-registry.module.js';
import { startVmLoading, stopVmLoading } from 'virtuemart/ajax/vmloading.module.js';

The map key virtuemart/ points at media/com_virtuemart/js/ (including the Joomla subdirectory, for example /joomla5/media/com_virtuemart/js/).

If you override a module on the canonical Joomla path, PHP adds an extra map entry for that filename so other files that import virtuemart/product/vmprices.module.js get your copy. The scanner looks in:

  • media/templates/site/cassiopeia/js/com_virtuemart/
  • templates/cassiopeia/js/com_virtuemart/
  • templates/cassiopeia/html/com_virtuemart/assets/js/
  • media/templates/site/cassiopeia/html/com_virtuemart/assets/js/
  • and the parent’s media/templates/site/cassiopeia/js/com_virtuemart/ when you use a child template

It does not scan the flat folder templates/cassiopeia/js/. Another reason to use the Joomla path.

Keep relative imports only inside a third-party library that always ships as one folder (GLightbox internals, Alpine’s own file, Sortable’s ESM build).

6. Runtime paths on window.Virtuemart

PHP publishes these in vmJsApi::vmVariables() (root includes the Joomla subdirectory):

Property Typical value Use for
Virtuemart.assetsPath /media/com_virtuemart/ Parent of js, css, images
Virtuemart.jsPath /media/com_virtuemart/js/ Dynamic script URLs, lazy loads
Virtuemart.cssPath /media/com_virtuemart/css/ Constructed stylesheet URLs
Virtuemart.imagesPath /media/com_virtuemart/images/ Fetch SVG icons, constructed image URLs

These are the runtime layer. They do not replace static import. Icon loading uses Virtuemart.imagesPath plus icons/, then the shop-template icon folders (see the SVG single-shim article).

7. Quick start

Override the frontend prices module in template cassiopeia:

  1. Copy media/com_virtuemart/js/product/vmprices.module.js to media/templates/site/cassiopeia/js/com_virtuemart/product/vmprices.module.js.
  2. Keep the imports as virtuemart/…. Do not change them to ../.
  3. If you ship minified output, also place vmprices.module.min.js in that same override folder.
  4. Reload with browser cache disabled. Network panel: the script URL should be under media/templates/site/cassiopeia/.

Override one stylesheet:

  1. Copy media/com_virtuemart/css/vm-ltr-site.css to media/templates/site/cassiopeia/css/com_virtuemart/vm-ltr-site.css.
  2. Remember url(../images/…) in that file is relative to the CSS file. If you override CSS only, those images resolve under the template tree, not under media/com_virtuemart/images/. Prefer extra CSS in the template, or copy the images you still reference.

8. assets_general_path

Configuration default on Joomla: media/com_virtuemart/ (parent of js, css, images).

vmJsApi::getAssetsGeneralPath() adds a trailing slash. If an updated shop still stores the old value components/com_virtuemart/assets/, that string is remapped to media/com_virtuemart/. That is not a second search in the old folder. Custom values (for example a WordPress plugin path) are left alone.

On live Joomla shops the old directory components/com_virtuemart/assets/ may still exist after an update (custom scripts, leftover VirtueMart 4 files). VirtueMart does not delete it automatically. A tools cleanup button is the opt-in way to remove leftovers. Do not put new overrides there.

9. Do’s and don’ts

  • Do override on media/templates/site/yourtemplate/js/com_virtuemart/ (and css, images).
  • Do keep import … from 'virtuemart/…' inside overridden modules.
  • Do use Virtuemart.jsPath / Virtuemart.imagesPath for URLs you build at runtime.
  • Do not point assets_general_path at the template “because that is how we did it in VirtueMart 3”.
  • Do not drop files in media/com_virtuemart/ to override core — that is the core tree; updates overwrite it.
  • Do not use the flat path templates/cassiopeia/js/vmprices.module.js for new work.
  • Do not expect a relative import from an overridden file to find core siblings.
  • Do not treat this move as a security fix in shop marketing.
  • Do not hand-edit .min.js or .min.css.

10. FAQ

Why not copy the whole assets folder into media/com_virtuemart/assets?
An extra assets wrapper is not Joomla. With js and css and images as siblings, relative CSS URLs stay valid.

I overrode one CSS file and the background images vanished.
url(../images/foo.png) is relative to the CSS file. Copy the images or add a small extra stylesheet in the template instead of replacing the whole core CSS file.

Does a missing override folder cause extra HTTP 404s?
No. setPath uses is_file on disk. Only the winning path is printed as a URL.

WordPress?
There is no Joomla media-template overlay. Keep assets_general_path pointed at where the plugin puts the files. The same Virtuemart.jsPath object still works.

11. Files

  • Resolver and import map: administrator/components/com_virtuemart/helpers/vmjsapi.php (setPath, getAssetsGeneralPath, emitImportMap, vmVariables)
  • Core JS / CSS / images: media/com_virtuemart/js, media/com_virtuemart/css, media/com_virtuemart/images
  • Default config: assets_general_path=media/com_virtuemart/ in administrator/components/com_virtuemart/virtuemart_defaults.cfg-dist
  • Load API: vmJsApi::addJScript, vmJsApi::addvScriptModule, vmJsApi::css

Related Technics: SVG icon single-shim (template icon drop folders), VMInit registry (how modules register), vmJsApi dual-load pattern.