Semakin
Customer

SemaFeed

Joomla extension (package = component + plugin) that exports a product XML feed from VirtueMart to Google Merchant Center. Prices are calculated by VirtueMart itself, so they match what the customer sees on the product page.

JoomlaVirtueMartPHPCurrent version
4.4, 5.x4.2+8.1+1.1.0

1. Requirements

  • Joomla 4.4 or 5.x (VirtueMart does not support Joomla 6 yet)
  • VirtueMart 4.2 or newer
  • PHP 8.1 or newer
  • MySQL 8 / MariaDB 10.6+
  • A Google Merchant Center account (to submit the feed)

The extension runs on shared hosting without SSH. Generation is split into batches, so it does not hit the script time limit.


2. Installation

Getting the installation package

Download the ZIP file pkg_semafeed-X.Y.Z.zip from SemaHub (Semakin.cz).

Installation steps

  1. Log in to the Joomla administration.
  2. Go to System > Install > Extensions.
  3. Upload the ZIP file.
  4. Joomla installs both parts at once:
    • Component: com_semafeed (Components > SemaFeed)
    • Plugin: plg_task_semafeed (group task, for scheduled generation)
  5. The installer creates six #__sema_feed_* tables and the images/semafeed/ folder where finished feeds are stored.

Upgrade: Same procedure as installation. Joomla uses method="upgrade", so profiles, mappings and the run log are preserved.


3. Enabling the Plugin

The component works right after installation. The plg_task_semafeed plugin has to be enabled manually, and only if you want the feed generated automatically (see section 16):

  1. Go to System > Manage > Plugins.
  2. Search for semafeed.
  3. Click Task – SemaFeed and set Status = Enabled.

Without the plugin the feed can still be generated manually from the administration. The plugin only covers scheduled runs.


4. Your First Feed Profile

A profile is one export configuration: which products, in which format, in which currency and at which address. You can have several – for example one for Merchant Center and one for another site language.

  1. Go to Components > SemaFeed.
  2. Click New.
  3. Fill in the Basic tab and save.

Basic tab

ParameterDefaultDescription
TitleRequired. Used only to tell profiles apart in the administration.
Alias(from title)Used in the feed file name. Empty = generated from the title.
Feed formatGoogleThe target service the feed is generated for.
StatusPublishedAn unpublished profile cannot be generated or scheduled.
Languagecs-CZLanguage tag for product texts. It is also passed into product links.
CurrencyCZKThree-letter ISO 4217 code.
Shopper group(empty)VirtueMart shopper group ID used to calculate prices. Empty = the default group for visitors who are not logged in.
Token(automatic)Random part of the file name. It is the only thing that makes the feed URL unguessable.

The shopper group decides the prices in the feed. Leave it empty and prices are calculated as for a visitor who is not logged in – which is exactly what Google expects. A wholesale group would publish prices an ordinary customer never sees on the site, and Merchant Center reports that as a price mismatch.

Feed address

The file is created in images/semafeed/ under the name {alias}-{token}.xml, so the address looks like this:

https://your-shop.com/images/semafeed/google-3f8c1e9a7b204d55.xml

You will find the address in the Feed URL column of the profile list. That is what you enter in Merchant Center.

The token is the only protection of the address. The feed sits in a public folder so that Google can download it without signing in. The unguessable name is what stops anyone else from downloading your entire price list. The New token button in the profile list changes the address – but then you must change it in Merchant Center as well.

Changing the alias changes the feed URL with the same consequence. The administration warns you about it.

Site base URL

The Site base URL field (Output tab) sets the root of the addresses in product and image links. Empty = the address from the Joomla configuration, which is correct on the vast majority of sites.

Do not add a language prefix such as /en. The feed language is passed in the product link itself; a prefix in the base URL would break image addresses. Fill this field in only on sites behind a reverse proxy where Joomla does not know its own public address.


5. Filters – Which Products Are Exported

Filters tab

ParameterDefaultDescription
Price from / Price to(empty)Products outside the range do not enter the feed. Empty = no limit.
Compare against priceFinal incl. taxWhich price is compared with the range above.
Category modeAllAll = everything except excluded categories. Whitelist = only ticked categories and their subcategories.
Products without a categoryExcludeA product with no category has nothing to fill g:product_type or g:google_product_category with.
In stock onlyYesOff: sold-out products stay in the feed with availability out_of_stock.
Include variant parentsNoRecommended off – see below.

The price filter is evaluated after VirtueMart calculates the price, not in SQL. It therefore takes discounts and tax into account for the selected shopper group.

Sold-out products in the feed

In stock only is on by default, but consider switching it off. An item that disappears from the feed and comes back a week later loses its performance history at Google and starts over. With the switch off the product stays in the feed with availability out_of_stock and the history is preserved.

Product variants

VirtueMart models variants as a parent product with children. SemaFeed sends individual variants to the feed and ties them together with g:item_group_id, so Google recognises them as one product in several versions.

A child inherits images, categories and the manufacturer from the parent – the parent has them, the variant often does not. Custom fields are never inherited: colour and size are exactly what makes the variant different from its parent.

Why not to include variant parents: the parent product has neither its own price nor its own stock. Its price in the feed would not match the product page and Merchant Center reports that as a mismatch.


6. Item Identification (g:id)

The Identification tab decides what the unique item ID in the feed is built from.

SourceWhen to choose it
SKU + ID (default)The safe option. Unique even where SKUs repeat.
SKUOnly when you are certain the SKU is unique across the whole catalogue.
Product IDAlways unique, but tells a human reading the feed nothing.
Custom fieldWhen the shop already has its own identifier for comparison sites.

Do not change the g:id source once the feed is running. Google keeps item history by it – changing the ID looks like deleting the old products and creating new ones.


7. Item Expiration

The Output tab, parameter Item expiration, fills g:expiration_date.

ModeBehaviour
No expiration (default)The attribute is not sent, Google applies its own 30 days.
Number of daysFeed creation date plus the given number of days. The maximum is 29 – Google ignores anything longer.
Custom fieldThe date is taken from a VirtueMart custom field.
Availability dateUses the product availability date from VirtueMart.

Expiration and generation frequency go together. Set 5 days and run the feed weekly and the items expire in between, so the offers stop showing.


8. Output – Batches, gzip, Title Length

ParameterDefaultDescription
ConditionNewThe default g:condition value for all items.
Batch size100How many products are processed in one step. On hosting with a short time limit choose a lower value.
Compress the feed (gzip)NoCreates an .xml.gz file. Google supports compressed feeds and the download is faster.
Maximum title length150Longer titles are truncated at a word boundary. Google accepts at most 150 characters.

Batch size is the first thing to lower if generation ends with a blank page or a 500 error. On ordinary shared hosting 50 works well.


9. Importing the Google Taxonomy

To assign a Google category to your shop categories, the category list published by Google has to be imported into Joomla. It is done once, on the Categories screen:

  1. Choose the taxonomy language – it should match the profile language.
  2. Click Import. The file is downloaded straight from Google.
  3. The number of stored categories is shown.

If your hosting blocks outgoing connections, download the file manually from the link under the form and upload it with the field next to it. The result is the same.


10. Categories

The Categories screen does two things at once: it decides which categories belong in the feed, and it assigns them a Google category.

ColumnDescription
Feed inclusionInherit from parent / Include / Exclude.
Google categoryA category from the imported taxonomy. You can search by name or by number.
Custom g:product_typeEmpty = the VirtueMart category path is used.

Inheritance

Most categories stay on Inherit from parent. It is therefore enough to set the top-level categories and the rest follows. For inherited values the administration shows where they come from (excluded by parent, inherited from parent: 5013).

Copy to subcategories

The Copy to subcategories button saves the table and overwrites the Google category and feed inclusion for the whole branch below that category.

Custom g:product_type is not copied. It is a category path – the parent path is factually wrong for a child.

g:google_product_category and g:product_type are not the same thing. The first is Google's own taxonomy and decides where the offer is classified; the second is your own category path and mainly serves to split campaigns.


11. Attributes

Attributes such as colour or size have no fixed place in VirtueMart – every shop stores them somewhere else. The Attributes screen decides where they are taken from.

Fifteen attributes can be mapped:

  • brand, color, size, material, pattern
  • age_group, gender
  • custom_label_0 to custom_label_4
Source typeWhat it does
Do not fillThe attribute is left out of the feed.
Custom fieldA VirtueMart custom field value. Only fields with a text value are offered.
Product columnA selected column from the product table: product_sku, product_gtin, product_mpn, product_weight, product_length, product_width, product_height, product_unit, product_packaging.
Static valueThe same text for every item. Useful for the brand in a single-brand shop, or for a custom_label.
Category pathThe product category path. Typically used on a custom_label to split campaigns.

Always fill in the brand. Without g:brand and without g:gtin or g:mpn Google usually will not show the item. In that case SemaFeed sends g:identifier_exists = no, which prevents rejection, but it does not replace the performance of a properly identified offer.

Custom labels are a campaign tool, not a Merchant Center one. Google evaluates nothing by them – they serve to split products in Google Ads (margin, season, fast movers).


12. Per-Product Overrides

An override replaces the values of a single product in the feed, or removes it from the feed entirely. It suits individual corrections – blanket changes belong in the attribute mapping.

  1. Open the Overrides screen.
  2. Click Add override and find the product by title, SKU or ID.
  3. Either tick Exclude from feed, or fill in the values to override.
  4. Save.

You can override title, description, link, image_link, brand, gtin, mpn, condition, google_product_category, product_type, color, size, material, pattern, age_group, gender and all five custom labels.

An override is permanent. If you fix the data in the shop later, the override still replaces it. It pays to review the list from time to time.


13. Unit Pricing

When you turn on Unit pricing, the feed adds the volume or weight of the package and Google shows a price per litre or kilogram next to the offer. Google does the division – the feed only states how much product is in the package and which unit it should be related to.

ParameterDescription
Where to take the amount fromCustom field / product column / product title. A number with a unit is recognised (5l, 250 ml, 1 kg).
If the value is missing, look in the titleFor products with an empty source the amount is read from the title.
Convert volume to1 l / 10 l / 100 ml / 750 ml / do not send.
Convert weight to1 kg / 100 g / 10 g / do not send.

Three rules worth knowing

  • Volume and weight have their own conversion unit. A range that sells both needs both options filled in – otherwise half the items end up with no unit price.
  • The amount is converted into the base unit. A 400 g package with a 1 kg base comes out as 0.4 kg; the Google specification wants the same unit on both attributes.
  • VirtueMart columns tend to mislead. product_unit is a weight unit, product_packaging the count per box, and you cannot derive volume from product_weight. Check what a column actually contains before choosing it – usually a custom field or the product title is the only usable source.

Reading from the title is an estimate, not a fact. After turning it on, always check the coverage on the Price Test screen. A wrongly recognised amount produces a nonsensical price per litre and Google rejects the item.


14. Price Test

The Price Test screen shows what the profile will produce before the feed is generated. It is the fastest way to spot a wrong shopper group or filter.

Diagnostics

The top of the screen shows the VirtueMart version, the number of export candidates, the shopper group used for the calculation and the memory usage per batch.

Price table

For the first products the screen lists the base price excluding and including tax, the price after discounts, the tax rate and the result (In feed, or the reason for skipping).

The "Base incl. tax" column is what goes into the feed as g:price. Compare it with the price a visitor who is not logged in sees for the same product – it has to match to the cent. If it does not, the shopper group is wrong.

Unit pricing – what was recognised

With unit pricing on, a coverage summary is added (how many products had an amount recognised and from which source) plus a sample of products with no recognised amount.

Products without a value are sent without a unit price – that is fine. What is dangerous is a value recognised wrongly, and only looking at the sample reveals that.


15. Generating the Feed

  1. Open the profile and go to the Generate screen.
  2. Click Start generating.
  3. A progress bar shows the run; it can be cancelled at any time.
  4. When it finishes, the number of written and skipped items is shown.

The feed is built into a temporary file and moved to the production address only when it is complete. During generation and after a cancellation the previous valid version stays at the URL – Google therefore never downloads a half-written file.


16. Scheduled Generation

Automatic generation is handled by the plg_task_semafeed plugin through Joomla Scheduled Tasks.

  1. Enable the plugin (see section 3).
  2. Go to System > Scheduled Tasks > New.
  3. Choose the type SemaFeed – generate feed.
  4. Fill in the feed profile ID (found in the ID column of the profile list) and the time budget in seconds.
  5. Set the frequency – once a day is usually enough.

Set the time budget safely below the hosting limit. When it runs out, the run stops mid-way and continues on the next execution of the task – so the feed finishes even on hosting where a single run never would.

Joomla scheduled tasks need cron or "lazy cron". For reliable operation set up a server cron:

*/5 * * * * php /path/to/joomla/cli/joomla.php scheduler:run > /dev/null 2>&1

17. Run Log and Feed Check

The Run log screen lists the generation history: state (Finished, Running, Failed, Aborted), the number of written and skipped items and a list of problems with reasons.

  • Problems are split into product skipped and warning only.
  • The Export CSV button downloads the whole list – handy when the data fix is going to happen in the shop.

Check the feed

The Check the feed button walks through the generated file and reports items with missing or nonsensical values.

The check does not replace Merchant Center diagnostics. It cannot verify that the price matches the product page, nor that an image really exists at the given address. It does catch trivial errors that would make Google reject the whole feed.


18. Connecting to Google Merchant Center

  1. Generate the feed and copy its URL from the profile list.
  2. In Merchant Center go to Products > Data sources and add a source of type Scheduled fetch.
  3. Paste the feed address and set the fetch frequency – ideally a little less often than you generate the feed.
  4. Wait for the first processing and go through Diagnostics.

Set the same country and currency in Merchant Center as the profile has. A currency mismatch is the most common reason why not a single item shows up after the first fetch.

What the feed contains

SemaFeed writes these attributes:

  • Required: g:id, title, description, link, g:image_link, g:price, g:availability, g:condition
  • Identification: g:brand, g:gtin, g:mpn, g:identifier_exists
  • Classification: g:google_product_category, g:product_type
  • Variants: g:item_group_id, g:color, g:size, g:material, g:pattern, g:age_group, g:gender
  • Extras: g:sale_price, g:additional_image_link, g:shipping_weight, g:expiration_date, g:unit_pricing_measure, g:unit_pricing_base_measure, g:custom_label_0 to 4

g:sale_price is written only when the discounted price is genuinely lower than the base one. Google renders it as a struck-through original price, so identical values would look like a fake discount.


19. Uninstallation

Procedure

  1. System > Manage > Extensions
  2. Search for SemaFeed.
  3. Uninstall the package pkg_semafeed.

What is removed

  • The com_semafeed component and the plg_task_semafeed plugin
  • All six #__sema_feed_* tables (profiles, category and attribute mappings, overrides, run log, taxonomy)

What is KEPT

  • Generated feed files in the images/semafeed/ folder

The files stay on purpose – as long as the data source in Merchant Center is active, Google keeps fetching them. Delete them only after you switch that data source off.


20. Troubleshooting

Generation ends with a blank page or a 500 error

  1. Lower the batch size (to 50 or 30, for example).
  2. Check the memory usage on the Price Test screen – it is compared with the VirtueMart limit.
  3. For large catalogues use a scheduled task with a time budget: the run is split across several executions.

Feed prices do not match the product page

  1. Open Price Test and compare the "Base incl. tax" column with the price seen by a visitor who is not logged in.
  2. If it does not match, clear the shopper group in the profile – most often a wholesale group is filled in.
  3. Check that the profile has the right currency.

The feed is empty or has fewer items than expected

  1. Price Test shows the number of export candidates and, for the first products, the reason for skipping.
  2. Check the In stock only switch, the price range and the category mode.
  3. In Whitelist mode only ticked categories are exported – a newly created one is easy to forget.
  4. Products with no category are excluded by default.

Merchant Center reports unavailable images

  • Open an image address from the feed in a browser. It is not enough that the address looks right – it has to return an image, not a 404 page.
  • The most common cause is a Site base URL filled in with a language prefix. Clear the field.

Google categories cannot be selected

  • The taxonomy for the chosen language has not been imported – see section 9.
  • If the hosting blocks outgoing connections, download the file manually and upload it.

The scheduled task does not run

  1. Check that the Task – SemaFeed plugin is enabled.
  2. Check that the task has the profile ID filled in.
  3. Check that the profile is published.
  4. Run the task with the Test run button – the result appears in the profile Run log.
  5. Without a cron, Joomla only runs tasks when someone visits the site ("lazy cron") – on a low-traffic site that is not enough.

Variants appear in the feed as separate products

  • Check that the products really are children of one parent in VirtueMart.
  • Map colour and size on the Attributes screen – without them Google cannot tell variants apart even when g:item_group_id is correct.

How the feed is built (overview)

  1. The profile decides which products are candidates (categories, stock, price range).
  2. Products are loaded in batches; prices are calculated by VirtueMart itself, including discounts and tax for the shopper group.
  3. Each product gets its category (from the mapping), attributes (from the mapping) and overrides.
  4. Variants inherit images, categories and the manufacturer from the parent; custom fields are not inherited.
  5. The item is written into a temporary XML file; problems are recorded in the run log.
  6. After the last batch the temporary file is moved to the production address.
  7. Google fetches the feed according to the data source settings in Merchant Center.

Launch Checklist

  • Component installed, Task – SemaFeed plugin enabled (for scheduled runs)
  • Profile created and published
  • Profile language and currency match the shop and the Merchant Center account
  • Shopper group empty (or deliberately set)
  • Google taxonomy imported for the profile language
  • Top-level categories mapped to Google categories
  • Brand mapped on the Attributes screen
  • Colour and size mapped (if the shop sells variants)
  • Price Test: "Base incl. tax" matches the price on the product page
  • Price Test: unit pricing coverage reviewed (if enabled)
  • Feed generated and verified with the Check the feed button
  • A product link and an image address from the feed opened at random
  • Scheduled task created and tried with the Test run button
  • Data source added in Merchant Center and the first processing passed

Interested in working together?

Get in touch and let's discuss your project. We'll be happy to help you choose the right solution.