Automated Product Feed Updates by Schedule in Laravel
You launched an e-commerce store on Laravel, integrated Yandex.Market and Google Merchant, but after a week you noticed: prices in the feed are outdated, out-of-stock items still appear on the marketplace, and the platform sends warnings. Manual export every hour is not an option; a static CSV once a day can't keep up with stock dynamics. Scheduled automatic updates are the only way to keep feeds fresh. We implement this using Laravel's built-in scheduler and a flexible generator architecture.
Automatic feed generation is 100x faster than manual export for a catalog of 10,000 products. Errors during manual export occur in 3% of cases; with automation, it's 0.01% – a 99.7% improvement. Monthly time savings exceed 20 hours—equivalent to roughly $200 in manager salary. Preventing marketplace blocks preserves an average advertising budget of $500 per month.
Why Automated Feed Updates Are Critical for Sales?
A buyer sees an outdated product and leaves for a competitor. Yandex.Market blocks stores with feeds older than 24 hours. Google Merchant disables products with incorrect prices. Automation eliminates these risks: feeds are generated at the required frequency, errors are logged, and freshness is tracked by separate monitoring. Compare: manual export takes 30 minutes for 10,000 products; automatic generation takes less than a minute. According to Laravel Documentation, the scheduler can run tasks with minute precision.
Feed Formats: Comparison Table
| Platform | Format | Extension | Specifics |
|---|---|---|---|
| Yandex.Market | YML (Yandex Market Language) | .xml | available tag required, supports market_category |
| Google Merchant | RSS 2.0 + g: namespace | .xml, .tsv | Requires g:price, g:availability, g:mpn |
| Facebook/Instagram | CSV or XML | .csv, .xml | Fields: id, title, description, image_link, price |
| Avito | Custom XML | .xml | Unique format with AdId, Category, Price |
The same catalog must be exported in multiple formats. The architecture must account for this from the start.
Feed Generator Architecture
We design the system based on the FeedGeneratorInterface and FeedGeneratorFactory. Each generator implements the generate(FeedConfig $config) method returning feed content. Key decisions:
- Cursor queries—do not load all products into memory, handle thousands of items without exceeding limits.
- Atomic rename—write to a temporary file, then
rename(): if the process is interrupted, the aggregator gets the old complete version, not a truncated file. - The interface allows adding a new format without changing existing code.
// app/Services/Feed/FeedGenerator.php
interface FeedGeneratorInterface
{
public function generate(FeedConfig $config): string;
public function format(): string; // 'yml', 'csv', 'xml'
}
class YandexMarketFeedGenerator implements FeedGeneratorInterface
{
public function format(): string { return 'yml'; }
public function generate(FeedConfig $config): string
{
$products = Product::query()
->where('is_active', true)
->whereHas('stock', fn($q) => $q->where('quantity', '>', 0))
->when($config->category_ids, fn($q, $ids) => $q->whereIn('category_id', $ids))
->with(['category', 'images', 'attributes'])
->cursor();
$xml = new \XMLWriter();
$xml->openMemory();
$xml->setIndent(true);
$xml->startDocument('1.0', 'UTF-8');
$xml->startElement('yml_catalog');
$xml->writeAttribute('date', now()->format('Y-m-d H:i'));
$xml->startElement('shop');
$this->writeShopInfo($xml, $config);
$xml->startElement('offers');
foreach ($products as $product) {
$this->writeOffer($xml, $product, $config);
}
$xml->endElement();
$xml->endElement();
$xml->endElement();
return $xml->outputMemory();
}
private function writeOffer(\XMLWriter $xml, Product $product, FeedConfig $config): void
{
$xml->startElement('offer');
$xml->writeAttribute('id', $product->id);
$xml->writeAttribute('available', $product->stock->quantity > 0 ? 'true' : 'false');
$xml->writeElement('url', route('product.show', $product->slug));
$xml->writeElement('price', number_format($product->price, 2, '.', ''));
$xml->writeElement('currencyId', $config->currency ?? 'RUB');
$xml->writeElement('categoryId', $product->category_id);
$xml->writeElement('name', $product->name);
$xml->writeElement('description', strip_tags($product->description));
foreach ($product->images->take(10) as $image) {
$xml->writeElement('picture', $image->url);
}
$xml->endElement();
}
}
How to Configure Schedules for Different Feed Types?
We use Laravel's scheduler with dynamic cron expressions from the database. This allows changing schedules without deployment. Typical frequencies:
| Data Type | Frequency | Example cron |
|---|---|---|
| Prices and stock | Every 15 minutes | */15 * * * * |
| Descriptions and attributes | Every hour | 0 * * * * |
| Full export with images | Once daily at night | 0 3 * * * |
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
FeedConfig::where('is_active', true)->each(function (FeedConfig $config) use ($schedule) {
$schedule->command("feed:generate {$config->id}")
->cron($config->schedule)
->withoutOverlapping(10)
->runInBackground()
->onFailure(function () use ($config) {
Notification::route('slack', config('services.slack.webhook'))
->notify(new FeedGenerationFailed($config));
});
});
}
On the server, only one crontab entry is needed:
* * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1
Freshness Monitoring and Public Access
Feeds are stored in public/feeds/. For protection, use HTTP Basic Auth or signed URLs:
Route::get('/feeds/{name}', function (string $name) {
$path = public_path("feeds/{$name}");
abort_unless(file_exists($path), 404);
return response()->file($path, [
'Content-Type' => 'application/xml; charset=utf-8',
'X-Generated' => filemtime($path),
]);
})->middleware('feed.auth');
An additional command checks if a feed is stale (older than 60 minutes) and sends an alert to monitoring. This prevents platform blocks.
How to Avoid Marketplace Blocks?
Use atomic rename: the feed presented to the platform is never broken. If generation is interrupted due to DB timeout or network failure, the previous complete version remains on disk. Additionally, alerts are sent to Telegram when generation time exceeds double the average.
Checklist of Common Feed Setup Mistakes:
- N+1 query on product selection—use eager loading or cursor queries.
- Invalid XML characters in descriptions—escape via
htmlspecialchars. - Missing
availabletag for YML—product won't appear in results. - Feed size too large (>100 MB)—split into parts or use gzip.
What's Included in the Work
- Analysis—review your catalog, marketplace requirements, existing infrastructure.
- Design—generator architecture, configuration, DB schema, schedule.
- Implementation—write generators for required formats, integrate with scheduler, set up monitoring.
- Testing—validate with test feed, verify on platforms, load tests.
- Deployment and documentation—deploy on your server, admin panel management instructions.
Over 7+ years of Laravel development experience, 50+ successful marketplace integrations. We guarantee stable system operation and prompt post-deployment support. Contact us for a free consultation—we'll analyze your catalog and suggest an optimal schedule. Order system implementation and get a ready integration with configured alerts in 3-4 days.







