Generating Product Feeds for Yandex.Market (YML)
YML (Yandex Market Language) is an XML dialect with its own validation rules that are significantly stricter than standard RSS. Yandex regularly updates requirements for categories: clothing, electronics, and auto parts have extended mandatory attribute sets. An improperly formed feed blocks the entire campaign, not just individual items.
Data Transmission Formats to Yandex.Market
Yandex supports two methods:
- YML feed — a file by URL that Yandex downloads on schedule (minimum once per 24 hours, maximum once per hour)
- Price API — a programmatic interface to update only prices and availability without full feed regeneration
For most stores, YML feed is sufficient. Price API is connected additionally if prices change multiple times per day.
YML Document Structure
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE yml_catalog SYSTEM "shops.dtd">
<yml_catalog date="2026-03-28 14:00">
<shop>
<name>Store Name</name>
<company>Company LLC</company>
<url>https://example.com</url>
<currencies>
<currency id="RUR" rate="1"/>
</currencies>
<categories>
<category id="10">Smartphones</category>
<category id="11" parentId="10">Apple iPhone</category>
</categories>
<delivery-options>
<option cost="299" days="1"/>
</delivery-options>
<offers>
<offer id="12345" available="true">
<url>https://example.com/products/iphone-15</url>
<price>89990</price>
<oldprice>99990</oldprice>
<currencyId>RUR</currencyId>
<categoryId>11</categoryId>
<picture>https://cdn.example.com/iphone-15-1.jpg</picture>
<picture>https://cdn.example.com/iphone-15-2.jpg</picture>
<name>Apple iPhone 15 128GB Smartphone Black</name>
<vendor>Apple</vendor>
<vendorCode>MTP03LL/A</vendorCode>
<barcode>0194253401353</barcode>
<description>Apple iPhone 15 smartphone with A16 processor...</description>
<param name="Color">Black</param>
<param name="Storage" unit="GB">128</param>
<param name="Operating System">iOS</param>
</offer>
</offers>
</shop>
</yml_catalog>
Offer Type Specifics
Yandex distinguishes several types of product offers:
| Type | When to Use | Key Additional Fields |
|---|---|---|
vendor.model |
electronics, appliances | typePrefix, vendor, model |
book |
books | author, publisher, ISBN, year |
audiobook |
audiobooks | author, publisher, performed-by |
artist.title |
music, video, games | artist, title, year, media |
tour |
tours | worldRegion, hotel-stars, room, dataTour |
event-ticket |
tickets | place, hall, date, is-premiere |
| simple | everything else | basic fields only |
PHP Feed Generator
class YandexMarketFeedGenerator
{
public function handle(): void
{
$path = storage_path('app/public/feeds/yandex.xml');
$writer = new \XMLWriter();
$writer->openUri($path); // write directly to file, not to memory
$writer->setIndent(true);
$writer->startDocument('1.0', 'UTF-8');
$writer->writeDtd('yml_catalog', null, 'shops.dtd');
$writer->startElement('yml_catalog');
$writer->writeAttribute('date', now()->format('Y-m-d H:i'));
$this->writeShopHeader($writer);
$this->writeCurrencies($writer);
$this->writeCategories($writer);
$this->writeOffers($writer);
$writer->endElement();
$writer->endDocument();
$writer->flush();
}
private function writeOffers(\XMLWriter $w): void
{
$w->startElement('offers');
Product::with(['category', 'brand', 'images', 'attributes'])
->where('is_active', true)
->chunk(500, function ($products) use ($w) {
foreach ($products as $p) {
$w->startElement('offer');
$w->writeAttribute('id', $p->sku);
$w->writeAttribute('available', $p->stock > 0 ? 'true' : 'false');
$w->writeElement('url', route('products.show', $p->slug));
$w->writeElement('price', (string) $p->price);
if ($p->compare_price > $p->price) {
$w->writeElement('oldprice', (string) $p->compare_price);
}
$w->writeElement('currencyId', 'RUR');
$w->writeElement('categoryId', $p->category_id);
$w->writeElement('name', $p->name);
$w->writeElement('vendor', $p->brand?->name ?? '');
$w->writeElement('barcode', $p->barcode ?? '');
foreach ($p->images as $img) {
$w->writeElement('picture', $img->cdn_url);
}
foreach ($p->attributes as $attr) {
$w->startElement('param');
$w->writeAttribute('name', $attr->name);
if ($attr->unit) {
$w->writeAttribute('unit', $attr->unit);
}
$w->text($attr->value);
$w->endElement();
}
$w->endElement(); // offer
}
});
$w->endElement(); // offers
}
}
Update Configuration
Feed is updated via Laravel Scheduler:
// app/Console/Kernel.php
$schedule->job(GenerateYandexFeedJob::class)->hourly()->withoutOverlapping();
The feed file is served via a dedicated route or directly from public/feeds/. If the catalog exceeds 500 MB in XML, Yandex recommends splitting the feed into multiple files and registering each separately in the cabinet.
Error Diagnostics
Yandex.Market returns a detailed report for each offer. Most common issues:
- Price is zero or missing — product is automatically excluded from index
- Price mismatch with website — Yandex compares feed price with product page price. Difference over 1% blocks the offer
- Image unavailable — checked on initial upload and rechecked on each crawl
- Description too long — 3000 characters allowed for most categories
Timeline
Basic generator for standard catalog — 2–4 working days. Complex categories (clothing with size grid, electronics with extended attributes) — 4–6 working days.







