Pluggable product feed generator for 11 marketplaces and price-comparison engines. One module, one feed registry, one extension point to add more.
Warning
Pre-release software — not thoroughly tested.
This module has not yet been validated against a wide range of Magento installs, catalog shapes, or production traffic. Generated feed output has not been independently verified against every platform's spec.
Do not rely on it as the sole feed source for a production store without your own testing first. Verify each generated feed against the target platform's validator before going live, monitor it on the first few cron runs, and keep your previous feed solution as a fallback during evaluation. Bug reports through GitHub Issues are very welcome.
- What it does
- Supported channels
- How it works
- Installation
- Quick start
- Configuration
- CLI usage
- Cron
- Public feed URLs
- Architecture
- Extending: adding a channel
- Troubleshooting
- Requirements
- Contributing
- License
Turns your Magento 2 catalog into XML feeds that marketplaces and price-comparison sites can consume. The module ships writers for 11 platforms and a registry-driven setup so a single store can run multiple feeds per channel (for example: one Google feed for accessories, one for apparel, each filtered to a different category tree).
It is built around a streaming XMLWriter, so feed size is bounded only by disk space, not by PHP memory.
| Channel | Region | Default filename | Format |
|---|---|---|---|
| Skroutz | GR | skroutz.xml |
Custom <mywebstore> (color-grouped, size variations) |
| Google Shopping | Global | google.xml |
RSS 2.0 + xmlns:g (per-variant item_group_id) |
| Facebook / Meta Catalog | Global | facebook.xml |
Google-compatible, Facebook-specific tags |
| Bing Shopping | Global | bing.xml |
Google-compatible |
| Bestprice | GR | bestprice.xml |
Greek price-comparison format |
| Pricerunner | Nordic / UK | pricerunner.xml |
TSV-like XML with stock enums |
| Idealo | DE / EU | idealo.xml |
DE delivery + payment costs |
| Ceneo | PL | ceneo.xml |
Polish availability codes |
| Kelkoo | EU | kelkoo.xml |
Kelkoo Merchant format |
| Shopflix | GR | shopflix.xml |
Skroutz-style with Shopflix extensions |
| eMAG Marketplace | RO / BG / HU | emag.xml |
eMAG offers/products XML |
Admin / CLI / Cron
│
▼
┌──────────────────────┐
│ Feed (DB registry) │ slug, channel_code, store_id, filters
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Generator │ picks writer by channel_code
└──────────┬───────────┘
│
┌───────┴────────────────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ProductCollect│ yields → │ ChannelWriter │ → pub/media/xmlfeed/<file>.xml
└──────────────┘ └──────────────────┘
│
▼
optional gzip if > 10 MB
Every feed lives in the dlabsit_xmlfeed_feed table as a row. The Generator picks the matching writer from WriterPool (assembled via DI), streams products from ProductCollector, and writes the XML to pub/media/xmlfeed/. The frontend controller serves the latest generated file, transparently negotiating gzip via Accept-Encoding.
composer require dlabsit/module-xml-feed
bin/magento module:enable Dlabsit_Core Dlabsit_XmlFeed
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flushdlabsit/module-core is a required dependency and Composer pulls it in automatically. Both modules must be enabled together: Core provides the shared admin tab, module registry, and compatibility helper that XML Feed reads from.
Setup patches will:
- Create
skroutz_eanandskroutz_mpnproduct attributes (grouped under "XML Feed"). - Create
colorandsizeattributes if they are missing (clean Magento installs don't have them).
- Open Stores → Configuration → Dlabsit - XML Feed and set shared defaults (manufacturer attribute, EAN attribute, VAT rate, etc.).
- Open Catalog → XML Feeds in the admin menu and add a feed:
- Pick a channel (e.g. Google Shopping).
- Give it a slug (e.g.
google-main). - Set the store view, filename, and category include/exclude rules.
- Generate it once from the admin grid or via CLI:
bin/magento xml-feed:generate --slug=google-main
- The file is now at
pub/media/xmlfeed/google-main.xmland served athttps://yourstore.com/feed/google-main.
All shared settings live under Stores → Configuration → Dlabsit - XML Feed. Most settings are scope-aware (default / website / store view).
| Setting | Purpose |
|---|---|
Unique ID Source |
Use product ID, SKU, or a custom attribute for the unique identifier emitted by every writer. |
Manufacturer / Brand Attribute |
Which product attribute holds the brand name. |
MPN Attribute |
Source for the Manufacturer Part Number (defaults to SKU). |
EAN / GTIN Attribute |
Source for the barcode. Empty by default. |
Color / Size / Weight Attribute |
Source attribute codes for configurable resolution and apparel feeds. |
Description Source |
description (default) or short_description. |
Category Filter Mode |
all (no filter), include, or exclude shared category set. |
Include Out of Stock |
Off by default. |
Batch Size |
Products per page when streaming. Default 500. Lower for memory-tight servers. |
Default VAT Rate (%) |
Used by Skroutz / Bestprice. Default 24. |
Enable Gzip |
When on, feeds larger than 10 MB are also written as .gz and served gzip-compressed when the client supports it. |
Each channel has its own section with platform-specific knobs:
- Google / Facebook / Bing: condition, default category, gender, age group, shipping country/service/price.
- Skroutz: default availability string, optional Skroutz Analytics shop account ID.
- Bestprice: Greek availability string, warranty provider and duration.
- Pricerunner: stock mode (
enumorquantity), lead time, shipping cost. - Idealo: German delivery time, condition (
Neu/Gebraucht), DHL and prepayment costs. - Ceneo: availability code, basket support flag.
- Kelkoo: default category, warranty, delivery cost and time.
See etc/config.xml for the full default schema.
# Generate one feed by slug
bin/magento xml-feed:generate --slug=google-main
# Generate every active feed in the registry
bin/magento xml-feed:generate --all
# Show available slugs (also printed when neither flag is given)
bin/magento xml-feed:generateEach run logs duration, output path, and final file size to var/log/xmlfeed.log.
A single cron job runs once a day at 23:30 by default:
30 23 * * * (configurable under General → Cron Schedule)
It iterates the feed registry and regenerates every row where is_active = 1, in sort_order ascending. Failures are logged and do not stop the queue.
Every feed is reachable at:
https://yourstore.com/feed/<slug>
Resolved by Dlabsit\XmlFeed\Controller\Feed\Dynamic. Behavior:
- Returns
404if the slug doesn't exist, is disabled, or has not been generated yet. - Serves the gzipped variant when both are present and the client sends
Accept-Encoding: gzip. - Always emits
Content-Type: application/xml; charset=UTF-8andVary: Accept-Encoding.
Api/ Contracts (FeedInterface, FeedRepositoryInterface,
FeedGeneratorInterface, FeedWriterInterface)
Block/ Admin grid, edit form, system config info banner,
frontend analytics blocks
Console/Command/ xml-feed:generate
Controller/ Adminhtml/Feed CRUD + frontend Feed/Dynamic
Cron/ GenerateFeeds — iterates active feeds
Helper/Config Centralized scope-aware config reader
Logger/ Dedicated logger writing to var/log/xmlfeed.log
Model/Feed/ Generator, WriterPool, ProductCollector,
AttributeMapper, FeedRepository, ResourceModel
Model/Feed/Writer/ AbstractWriter + one writer per channel
Setup/Patch/Data/ EAV attribute patches, legacy config migrator
etc/ module.xml, db_schema.xml, di.xml, system.xml,
config.xml, crontab.xml, routes (admin + frontend)
view/ Admin grid layout, frontend analytics templates
FeedWriterInterface— one method (write(string $filePath, \Generator $productSource, int $storeId)) plus identity helpers. Implementations extendAbstractWriter, which owns the XMLWriter lifecycle, configurable-product dispatch, UTF-8 sanitization, and CDATA escape.WriterPool— DI-assembled map ofcode => FeedWriterInterface. Adding a writer means appending to theargumentsarray inetc/di.xml.ProductCollector— paginated generator overMagento\Catalog\Model\ResourceModel\Product\Collectionwith stock, visibility, type, and category filters applied. Used by both legacy single-channel generation and the new per-Feed generation.AttributeMapper— normalizes Magento attributes into values writers can use directly: brand, MPN, EAN, color label, size label, weight in grams, deepest category path, validated image URLs, and stock quantity. Caches category paths within a single run.
A single InnoDB table dlabsit_xmlfeed_feed:
| Column | Purpose |
|---|---|
slug |
URL slug, unique per store. |
channel_code |
Maps to a writer in the pool. |
store_id |
Store view scope. |
is_active |
Cron and --all only touch active feeds. |
filter_mode + category_ids |
all / include / exclude. |
channel_settings |
JSON for per-feed overrides. |
sort_order |
Run order in cron. |
Every channel resolves configurables according to its spec:
- Google / Bing / Facebook: one
<item>per child variant, all sharingg:item_group_id. Variants get per-childg:id, color, size, MPN, EAN. - Skroutz: one
<product>per color group. Sizes inside a color are emitted as<variation>rows with their own MPN / EAN / quantity. Color groups get a unique link via a#skroutz_color=URL fragment so the platform doesn't flag duplicate URLs. - Idealo / Pricerunner / Ceneo / Kelkoo: flatten to one row per child variant.
- Facebook extends
GoogleShoppingWriterand overridesgoogleAvailability()(spaces instead of underscores) plusafterItemTags()forg:fb_product_categoryandg:rich_text_description.
AbstractWriter::sanitizeXmlTextstrips XML 1.0 illegal control characters and recovers from invalid UTF-8 byte sequences without aborting the whole feed.sanitizeCdatabreaks any literal]]>sequence inside CDATA blocks.- Files are written to a
.tmppath, then atomically renamed — clients never see a partially written feed. - Gzip compression streams 8 KB chunks through
gzopen/gzwrite, so memory stays flat for 100+ MB feeds. - Google Shopping feeds run a GTIN check-digit validator before emitting
g:gtin.
Adding a new platform is roughly 100 lines of PHP plus DI wiring.
- Create
Model/Feed/Writer/MyChannelWriter.phpextendingAbstractWriter(orGoogleShoppingWriterif the format is Google-compatible). - Implement
getCode(),getLabel(),getDefaultFilename(),startDocument(),endDocument(), andwriteSimpleProduct(). OverridewriteConfigurableProduct()if the platform needs grouped or split variants. - Register the writer in
etc/di.xml:<type name="Dlabsit\XmlFeed\Model\Feed\WriterPool"> <arguments> <argument name="writers" xsi:type="array"> <item name="mychannel" xsi:type="object">Dlabsit\XmlFeed\Model\Feed\Writer\MyChannelWriter</item> </argument> </arguments> </type>
- Add a config section in
etc/adminhtml/system.xmland defaults inetc/config.xmlif the channel has its own knobs. - Create a feed row through the admin UI (or a data patch) using the new
channel_code.
The writer is now available to CLI, cron, and the public /feed/<slug> endpoint.
Feed file is missing or stale. Check bin/magento cron:run --group=xmlfeed. Look at var/log/xmlfeed.log for errors. The cron schedule is in Configuration → Dlabsit - XML Feed → General → Cron Schedule.
No feed found with slug 'X'. The slug doesn't exist in the registry or you spelled it differently. Run bin/magento xml-feed:generate with no arguments to see the available slugs.
Skroutz reports duplicate links. Configurable products with color attributes get a #skroutz_color=<id> fragment automatically. If you still see duplicates, check that each configurable child has a distinct color option.
Google rejects g:gtin. The built-in GTIN validator enforces correct length and check digit. If the field is dropped, your EAN attribute holds an invalid value for that product.
Out-of-memory during generation. Lower the batch size under Shared Settings → Batch Size. Default 500 is safe for ~512 MB PHP processes on 50k+ catalogs.
Feed file exceeds 10 MB and downloads slowly. Enable Shared Settings → Enable Gzip. The next run will also write a .gz companion, and the frontend controller will serve it to gzip-aware clients automatically.
XML output contains � or strange characters. Source data has invalid UTF-8 bytes. The sanitizer attempts iconv //IGNORE and a fallback mb_convert_encoding; if symptoms persist, clean the product attribute at source.
- PHP 8.2+
- Magento Open Source / Commerce 2.4.7 or 2.4.8
dlabsit/module-core>= 1.0 (admin tab grouping, license validation contract, module registry)
Bug reports and pull requests are welcome at GitHub Issues.
By submitting a pull request you agree that your contribution is licensed under the same FSL-1.1-MIT terms as the rest of this repository (inbound = outbound).
Project conventions:
- PHP 8.2 syntax,
declare(strict_types=1), constructor property promotion. - Magento Coding Standard, severity 8:
composer lint. - PHPStan level matches the project baseline:
composer phpstan. - Both checks are wired into
composer ci.
Functional Source License v1.1 with MIT Future License (FSL-1.1-MIT) Copyright (c) 2026 Dlabsit.
You may:
- Install, run, and use this software on any Magento store you or your company operate, including commercial use.
- Modify the source code for your own use.
- Redistribute unmodified copies free of charge with this license intact.
You may not:
- Sell this software, in original or modified form.
- Offer it as a hosted or managed service that competes with Dlabsit.
- Repackage it as your own product.
Two years after each release date, that release becomes available under the standard MIT license automatically. See LICENSE.md for the full text.
Made by Dlabsit. Other Magento 2 modules in development: Courier Center, Elta Courier, Skroutz Marketplace.