Generating Product Feeds for Facebook/Instagram Catalog
Meta requires feeds in CSV, TSV, or XML (RSS/Atom) format. The catalog is used in dynamic advertising (Dynamic Ads), Instagram Shopping, and Shops — three different placements with partially overlapping field requirements. One error in the price or availability field format results in rejection of the entire product batch on upload.
Required Catalog Fields
| Field | Format | Example |
|---|---|---|
id |
string up to 100 chars | SKU-44231 |
title |
string up to 150 chars | Men's Blue Winter Jacket L |
description |
string up to 9999 chars | — |
availability |
enum | in stock / out of stock / preorder |
condition |
enum | new / refurbished / used |
price |
number + currency code | 4990.00 RUB |
link |
URL | HTTPS required |
image_link |
URL | min. 500×500 px, HTTPS |
brand |
string | — |
Additional Fields for Clothing and Shoes
For Apparel & Accessories category, Meta requires:
-
google_product_category— numeric ID from Google taxonomy (Meta uses it) -
size— size (XL,44,42/34) -
color— color in target audience language -
gender—male/female/unisex -
age_group—adult/kids/newborn -
material— material (optional, but increases relevance score)
CSV Feed Generator
CSV is simpler to generate and debug than XML. Meta accepts both formats equally.
class MetaCatalogFeedGenerator
{
private const HEADERS = [
'id', 'title', 'description', 'availability', 'condition',
'price', 'link', 'image_link', 'additional_image_link',
'brand', 'google_product_category', 'color', 'size',
'gender', 'age_group', 'sale_price', 'sale_price_effective_date',
];
public function generate(string $outputPath): void
{
$file = fopen($outputPath, 'w');
// Don't add BOM — Meta doesn't require it, might break parser
fputcsv($file, self::HEADERS, "\t"); // TSV is more reliable than CSV due to commas in descriptions
Product::with(['images', 'category', 'brand', 'variants'])
->where('is_active', true)
->chunk(200, function ($products) use ($file) {
foreach ($products as $product) {
foreach ($this->expandVariants($product) as $row) {
fputcsv($file, $row, "\t");
}
}
});
fclose($file);
}
private function expandVariants(Product $product): array
{
if ($product->variants->isEmpty()) {
return [$this->buildRow($product, null)];
}
return $product->variants->map(
fn($variant) => $this->buildRow($product, $variant)
)->toArray();
}
private function buildRow(Product $product, ?ProductVariant $variant): array
{
$id = $variant ? $product->sku . '-' . $variant->sku : $product->sku;
$price = $variant?->price ?? $product->price;
$stock = $variant?->stock ?? $product->stock;
$additionalImages = $product->images->skip(1)
->pluck('cdn_url')
->take(9) // Meta allows up to 10 images
->implode(',');
$salePrice = '';
$salePriceDate = '';
if ($product->sale_price && $product->sale_ends_at > now()) {
$salePrice = number_format($product->sale_price, 2, '.', '') . ' RUB';
$salePriceDate = $product->sale_starts_at->toIso8601String()
. '/' . $product->sale_ends_at->toIso8601String();
}
return [
$id,
mb_substr($product->name . ($variant ? ' ' . $variant->name : ''), 0, 150),
strip_tags($product->description),
$stock > 0 ? 'in stock' : 'out of stock',
'new',
number_format($price, 2, '.', '') . ' RUB',
route('products.show', $product->slug) . ($variant ? '?variant=' . $variant->id : ''),
$product->mainImage()?->cdn_url ?? '',
$additionalImages,
$product->brand?->name ?? '',
$product->google_category_id ?? '',
$variant?->color ?? $product->color ?? '',
$variant?->size ?? '',
$product->gender ?? '',
$product->age_group ?? 'adult',
$salePrice,
$salePriceDate,
];
}
}
Setup in Business Manager
After feed generation:
- Commerce Manager → Catalog → Data Sources → Add Data Feed
- Specify feed URL or upload file manually
- Select update schedule: hourly, daily, or manual
- Assign catalog to advertising accounts
Meta provides error diagnostics in Catalog → Issues section — each product with issues is marked with problem description.
Pixel + Catalog for Dynamic Retargeting
For Dynamic Ads, feed alone is not enough — configure Meta Pixel with events:
// Product page
fbq('track', 'ViewContent', {
content_ids: ['SKU-44231'],
content_type: 'product',
value: 4990.00,
currency: 'RUB',
});
// Add to cart
fbq('track', 'AddToCart', {
content_ids: ['SKU-44231'],
content_type: 'product',
value: 4990.00,
currency: 'RUB',
});
content_ids must exactly match the id field in the catalog — otherwise matching doesn't work.







