İçeriğe geç
KURUMSAL Kurumsal teknoloji · İşletmenize özel mühendislik Kurumsal Yazılım · Yönetilen Sunucular · Kurumsal E-posta · LiteSpeed + CloudLinux · Imunify360 Güvenlik · cPanel Yönetim · Uygun pakette 3 Gbps'ye kadar ağ profili
AIOR

Ürün Feed'i Hazırlama: Google Merchant Center ve Meta Katalog Rehberi

  • Konu başlatıcısı Konu başlatıcısı Aior
  • Başlangıç tarihi Başlangıç tarihi

Aior

Administrator

Yetkili üye
Katıldı
2 Nis 2023
Mesajlar
929
Tepki puanı
2
Puan
18
Yaş
41
Konum
Turkey
Web sitesi
aior.com
Konu sahibi

Ürün feed'i (product feed), mağazanızda satılan her ürünü fiyatı, stok durumu, görseli ve tanımlayıcılarıyla birlikte makinece okunabilir tek bir listede toplar. Google Merchant Center ve Meta'nın katalog altyapısı ürünlerinizi bu listeden okuyup reklamlarda ve alışveriş yüzeylerinde gösterir; listedeki hatalar da uyarı ya da reddedilen ürün (disapproved item) olarak size geri döner. Bu rehberde iki platformun beklediği alanları, Türkiye'deki mağazalar için ayarları, feed'i üretmenin yollarını ve en sık görülen ret nedenlerini ele alıyoruz. Platform kuralları sık değişiyor; buradaki tespitler Ekim 2026 itibarıyla resmî belgelere dayanıyor.

Ürün feed'i nerede kullanılır?​

  • Google: Merchant Center'a gönderilen ürünler ücretsiz listelemelerde (free listings), Alışveriş reklamlarında (Shopping ads) ve Maksimum Performans (Performance Max) kampanyalarında kullanılır. Türkiye desteklenen ülkeler arasındadır ve listelenen para birimi Türk lirasıdır (TRY). Ürün verisinin dili, açılış sayfasının diliyle aynı olmalıdır.
  • Meta: Ticaret Yöneticisi'ndeki (Commerce Manager) katalog, Facebook ve Instagram'daki Advantage+ katalog reklamlarını (eski adıyla dinamik reklamlar) ve kullanılabildiği pazarlarda mağaza deneyimlerini besler.
  • Diğer kanallar: Fiyat karşılaştırma siteleri, pazar yerleri ve XML ile ürün alan bayiler genellikle kendi şemalarını ister.
Pratik kural: feed'i tek bir doğruluk kaynağından (ERP, PIM veya e-ticaret veritabanı) üretin ve her kanal için ayrı bir çıktı alın. Kanal başına elle düzenlenen tablolar kısa sürede fiyat ve stok uyuşmazlığına dönüşür.

Biçim seçimi: XML, metin dosyası, E-Tablolar veya API​

YöntemNe zaman uygun?Dikkat edilecek nokta
XML (RSS 2.0 veya Atom, g: ad alanı)Kodla üretilen, iç içe alan (shipping gibi) içeren kataloglarTek bir kaçışlama (escaping) veya karakter kodlaması hatası tüm dosyanın okunamamasına yol açabilir
Metin dosyası (Google: sekmeyle ayrılmış .txt veya .tsv; Meta: CSV veya TSV)Dışa aktarımla üretilen, düz yapılı kataloglarAlan içindeki sekme, satır sonu veya virgül sütunları kaydırır
Google E-Tablolar (Google Sheets)Az ürünlü mağazalar ve ek veri kaynaklarıElle bakım hataya açıktır
API (Google Merchant API; Meta'nın katalog ve Batch API'si)Fiyatı ve stoğu gün içinde sık değişen kataloglarKimlik doğrulama, kota ve hata yönetimi sizin sorumluluğunuzdadır
Dosyayı ya kendiniz yüklersiniz (upload) ya da platform belirlediğiniz takvimde bir adresten çeker (scheduled fetch). Google http, https, ftp ve sftp adreslerinden 4 GB'tan küçük dosyaları çeker; parola korumalı adresler için kullanıcı adı ve parola girilebilir, .gz, .zip ve .bz2 ile sıkıştırılmış dosyalar da kabul edilir. Dosyanın bulunduğu dizini Googlebot ve AdsBot-Google için engellemeyin. Meta'da zamanlanmış feed saatlik, günlük veya haftalık olarak ayarlanabilir.

API tarafında önemli bir değişiklik yaşandı: Google'ın Content API for Shopping hizmeti 18 Ağustos 2026'da resmî olarak kullanım ömrünün sonuna ulaştı (sunset). Etkin bir uzatması olmayan istemcilerin istekleri 1 Eylül 2026'dan beri aralıklı olarak HTTP 410 Gone hatasıyla dönüyor; tüm uç noktaların kapatılması 2027 başına planlanmış durumda ve Google bu takvimin değişebileceğini belirtiyor. Yerine Merchant API v1 geldi. Shopify'daki Google & YouTube uygulaması gibi bir platform entegrasyonu kullanıyorsanız geçişi sağlayıcı üstlenir. Google'ın geçiş duyuruları yalnızca API entegrasyonlarını kapsar; dosya yükleme, zamanlanmış getirme ve E-Tablolar kaynakları API çağrısı olmadığından bu geçişten etkilenmez.

Asgari bir XML feed'i şöyle görünür:
Kod:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
  <channel>
    <title>Örnek Mağaza</title>
    <link>https://www.example.com</link>
    <description>Örnek Mağaza ürün feed'i</description>
    <item>
      <g:id>TSH-001-KIR-M</g:id>
      <g:title>Örnek Giyim Basic Tişört %100 Pamuk Kırmızı M</g:title>
      <g:description>Bisiklet yaka, %100 pamuk, 30 °C'de yıkanabilir.</g:description>
      <g:link>https://www.example.com/basic-tisort?renk=kirmizi&amp;beden=m</g:link>
      <g:image_link>https://cdn.example.com/urun/tsh-001-kirmizi.jpg</g:image_link>
      <g:availability>in_stock</g:availability>
      <g:price>449.90 TRY</g:price>
      <g:sale_price>399.90 TRY</g:sale_price>
      <g:sale_price_effective_date>2026-11-27T00:00+0300/2026-11-30T23:59+0300</g:sale_price_effective_date>
      <g:brand>Örnek Giyim</g:brand>
      <g:gtin>8690123456789</g:gtin>
      <g:condition>new</g:condition>
      <g:item_group_id>TSH-001</g:item_group_id>
      <g:color>Kırmızı</g:color>
      <g:size>M</g:size>
      <g:gender>unisex</g:gender>
      <g:age_group>adult</g:age_group>
      <g:google_product_category>212</g:google_product_category>
      <g:product_type>Giyim > Tişört</g:product_type>
    </item>
  </channel>
</rss>
Bağlantıdaki & karakterinin &amp; olarak yazıldığına dikkat edin: kaçışlanmamış tek bir & tüm dosyanın ayrıştırılmasını (parsing) durdurur. 212 numarası, Google ürün kategorisi listesinde (taxonomy) "Kıyafet ve Aksesuarlar > Giyim Eşyaları > Gömlekler ve Üstlükler" kategorisine karşılık gelir. Listenin Türkçe sürümü de var; numara ise dilden bağımsızdır. Örnekteki GTIN yalnızca biçimi göstermek içindir.

Metin dosyasında ilk satır sütun adlarıdır. Aşağıdaki başlık satırı Meta'nın CSV biçimi için yazıldı; Google'a aynı sütunları sekmeyle ayrılmış bir dosyada verin:
Kod:
id,title,description,availability,condition,price,sale_price,sale_price_effective_date,link,image_link,brand,gtin,mpn,item_group_id,color,size,gender,age_group,google_product_category
İki platform stok değerini farklı yazar: Google in_stock, Meta'nın belgeleri ise boşluklu "in stock" biçimini kullanır. Virgül içeren alanları CSV'de çift tırnak içine alın.

Google özellikleri: zorunlu, koşullu, isteğe bağlı​

Merchant Center'ın Ürün verileri spesifikasyonu (Product data specification) her özelliği "Zorunlu", "Duruma göre değişir" veya "İsteğe bağlı" olarak işaretler. Türkiye'yi hedefleyen bir mağaza için özet:
ÖzellikDurumNot
idZorunluEn fazla 50 karakter; benzersiz ve kalıcı. SKU idealdir.
titleZorunluEn fazla 150 karakter; marka, ürün tipi ve ayırt edici özellik (renk, beden, kapasite)
descriptionZorunluEn fazla 5.000 karakter; kampanya metni değil, ürün bilgisi
linkZorunluDoğrulanmış alan adında, oturum açma istemeyen ürün sayfası
image_linkZorunluKalıcı URL; boyut ve içerik kuralları tablonun altında
availabilityZorunluin_stock, out_of_stock, preorder, backorder; son ikisinde availability_date de gönderilir
priceZorunluSayı ve ISO 4217 kodu (449.90 TRY); ABD ve Kanada dışında KDV dâhil
sale_price, sale_price_effective_dateİsteğe bağlıİndirimli fiyat price değerinden düşük olmalı; tarih verilmezse indirim hemen başlar
brandZorunlu (yeni ürünlerde)Film, kitap ve müzik kayıtları hariç; en fazla 70 karakter
gtinDuruma göre değişirÜreticinin atadığı GTIN varsa gönderin; Google bunu özellikle tavsiye ediyor
mpnZorunlu (üretici GTIN'i yoksa)Üreticinin parça numarası; en fazla 70 karakter
identifier_existsİsteğe bağlıVarsayılan değer yes; no yalnızca tanımlayıcı gerçekten yoksa
conditionİkinci el ve yenilenmiş üründe zorunlunew, refurbished, used; yeni ürünlerde isteğe bağlı
google_product_categoryİsteğe bağlıTek kategori; sayısal numara önerilir. Bazı ürünlerde belirli bir kategori şarttır.
product_typeİsteğe bağlıKendi kategori yolunuz, en fazla 750 karakter; kampanyaları bölmekte işe yarar
shippingDuruma göre değişirGoogle'ın listelediği ülkelerde (ABD, Almanya, Birleşik Krallık gibi) kargo ücreti şart; Türkiye listede yok. Kargoyu hesap düzeyinde tanımlayın, bu özelliği yalnızca istisnalar için kullanın.
item_group_idKoşullu zorunluVaryantlı ürünlerde ücretsiz listelemeler için gerekli; reklamlarda Brezilya, Fransa, Almanya, Japonya, Birleşik Krallık ve ABD'de zorunlu
color, size, gender, age_groupKoşullu zorunluGiyim ve aksesuar ürünlerinde aranır; varyantları ayırıyorsa her durumda gönderin
Görsellerde Google, 31 Ocak 2027'den itibaren tüm ürünlerde en az 500×500 piksel şartını uygulayacağını duyurdu ve 1500×1500 piksel ya da üzerini öneriyor; üst sınır 64 megapiksel ve 16 MB. Görselde fiyat, "ücretsiz kargo" gibi tanıtım yazısı, filigran, çerçeve veya ürünün parçası olmayan logo bulunmamalı. Her varyant için yalnızca o varyantı gösteren ayrı bir görsel kullanın.

Ürün tanımlayıcıları: GTIN, MPN, marka ve identifier_exists​

GTIN, barkodun altında yazan küresel ürün numarasıdır; 8, 12, 13 veya 14 haneden oluşur ve son hanesi GS1 kontrol basamağıdır (check digit). Türkiye'de bu numaralar GS1 Türkiye üyeliğiyle alınır ve 868 veya 869 önekiyle başlar. Üyelikte firmanıza, numaralandıracağınız ürün çeşidi sayısına göre uzunluğu değişen bir Firma Önek Numarası (GCP) tahsis edilir: örneğin 9 haneli önek 1.000, 7 haneli önek 100.000 ürün çeşidine yeter.

Hangi alanı göndereceğinize şu sırayla karar verin:
  1. Üreticinin atadığı GTIN var: gtin ve brand gönderin. Bayisi olduğunuz markaların ürünleri çoğunlukla bu gruptadır; numarayı tedarikçinin ürün kartından alın, tahmin etmeyin.
  2. GTIN yok, üretici parça numarası var: brand ve mpn gönderin. Yedek parça ve endüstriyel ürünlerde yaygın durum budur.
  3. Kendi markanız (private label), yalnızca siz satıyorsunuz ve barkod numarası almadınız: Google bu durumda GTIN göndermenizin gerekmediğini, bunun yerine brand ve mpn göndermenizi söylüyor. Ürünü başka perakendecilere veya barkod isteyen pazar yerlerine de verecekseniz GS1 Türkiye üyeliği mantıklıdır.
  4. Hiçbir tanımlayıcı yok: Kişiye özel, el yapımı, tek örnek veya antika ürünlerde identifier_exists değerini no yapın.
Sık yapılan hatalar: GTIN'i olan ürüne identifier_exists=no yazmak (Google uyarı verir), aynı GTIN'i tüm renk ve bedenlere kopyalamak (her varyantın kendi GTIN'i olmalı), mağaza içi terazi ve raf kodlarını göndermek (2, 02 ve 04 önekleri kısıtlıdır; 05, 98 ve 99 kupon aralığıdır) ve numara uydurmak. Elinizdeki barkod listesinin uzunluğunu ve kontrol basamağını aşağıdaki betikteki gtin_ok fonksiyonuyla toplu olarak doğrulayabilirsiniz; kısıtlı önekleri ayrıca elemeniz gerekir.

Açılış sayfasıyla tutarlılık: fiyat, stok ve yapılandırılmış veri​

Google, feed'deki fiyat ve stok bilgisini açılış sayfası ve ödeme adımıyla karşılaştırır; uyuşmazlık bulursa ürünü reddeder. Türkiye'deki mağazalar için dikkat edilecek noktalar:
  • Fiyatı KDV dâhil ve TRY olarak gönderin; ürün sayfası ve ödeme adımı da aynı tutarı göstermeli. Fiyatı veya para birimini ziyaretçinin konumuna göre değiştirmeyin: Googlebot'un varsayılan IP adresleri ABD'de görünür, bu yüzden otomatik kur dönüşümü doğrudan uyuşmazlık üretir.
  • Ondalık ayırıcı olarak nokta kullanın; Google'ın tüm örnekleri bu biçimdedir. Türkçe yerel ayarın ürettiği "1.299,90 TL" feed'de "1299.90 TRY" olarak yazılır; bu dönüşümü biçimlendirilmiş metinden değil, sayısal değerden yapın. Google ondalık kısımda en fazla iki hane istiyor, fazlasını kendisi yuvarlıyor.
  • İndirim tarihlerini saat dilimiyle yazın. Türkiye yıl boyunca UTC+3'tedir; saat farkı (offset) belirtilmeden yazılan "2026-11-27T00:00" UTC kabul edilir ve indirim Türkiye saatiyle 03.00'te başlar.
  • İndirimli fiyat duyurularının hukuki koşullarını, indirim öncesi fiyatın nasıl belirleneceği de dâhil olmak üzere, Ticari Reklam ve Haksız Ticari Uygulamalar Yönetmeliği çerçevesinde hukuk danışmanınızla kontrol edin.
Ürün sayfasındaki schema.org Product ve Offer yapılandırılmış verisi (structured data) feed'le aynı değerleri taşımalıdır. Merchant Center'ın otomatik ürün güncellemeleri (automatic item updates) varsayılan olarak açıktır ve sayfadaki fiyat, indirimli fiyat, stok ve durum (condition) bilgisini okuyarak ürünü güncelleyebilir. Google bunu düzenli veri güncellemesinin yerine koymamanızı ve sayfada birden çok üstü çizili fiyat varsa fiyat için otomatik güncellemeyi kapatmanızı öneriyor. İşaretlemeyi (markup) sunucu tarafında üretin: Google'a göre JavaScript ile oluşturulan işaretleme alışveriş taramalarını seyrekleştirebilir ve güvenilirliğini azaltabilir. İndirimdeki bir varyant için örnek:
JSON:
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Örnek Giyim Basic Tişört Kırmızı M",
  "sku": "TSH-001-KIR-M",
  "gtin13": "8690123456789",
  "brand": {"@type": "Brand", "name": "Örnek Giyim"},
  "image": ["https://cdn.example.com/urun/tsh-001-kirmizi.jpg"],
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/basic-tisort?renk=kirmizi&beden=m",
    "price": 399.90,
    "priceCurrency": "TRY",
    "priceSpecification": {
      "@type": "UnitPriceSpecification",
      "priceType": "https://schema.org/StrikethroughPrice",
      "price": 449.90,
      "priceCurrency": "TRY"
    },
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
Burada price, müşterinin ödediği güncel fiyattır; üstü çizili eski fiyat priceSpecification içinde StrikethroughPrice olarak verilir. Feed'de ise tam fiyat price, indirimli fiyat sale_price alanına yazılır. Google, açılış sayfasında normal ve indirimli fiyatın ikisinin de açıkça gösterilmesini, ödeme adımında ise indirimli fiyatın geçerli olmasını bekliyor.

Varyantlar: item_group_id eşlemesi​

Feed'de her satılabilir SKU ayrı bir öğedir. Ana ürün (parent) ayrıca gönderilmez; varyantlar ortak bir varyant grubu (item_group_id; Google'ın Türkçe belgelerinde "Öğe grubu kodu") altında toplanır:
  • id varyantın SKU'su, item_group_id ana ürünün kodudur. İkisi de kalıcı olmalı; grup kodunu değiştirmek gruplamayı tutarsızlaştırır.
  • Varyantı ayıran özellikler (color, size, material, pattern) her öğede dolu olmalı; aynı gruptaki iki öğe aynı kombinasyonu taşımamalı.
  • Her varyantın kendi URL'si olmalı (/basic-tisort?renk=kirmizi&beden=m gibi) ve sayfa o varyant seçili olarak açılmalı. Varsayılan varyantla açılan sayfa, fiyat ve stok uyuşmazlığının en yaygın nedenlerinden biridir.
  • Varyant olmayan ürünleri, örneğin bir takımın ceketini ve pantolonunu, aynı gruba koymayın.
Varyant eksenlerini ve SKU yapısını katalogda nasıl kuracağınızı ürün kataloğu ve veri modeli yazımızda anlattık; feed bu modelin birebir yansıması olmalı.

Merchant Center hesap ayarları (Türkiye)​

Feed kusursuz olsa bile hesap ayarları eksikse ürünler yayına çıkmaz:
  • İşletme bilgileri: Ticari unvan, adres ve müşteri hizmetleri iletişim bilgisi sitedekiyle tutarlı olmalı.
  • Site doğrulama ve sahiplenme: HTML etiketi veya dosyası, Google Etiket Yöneticisi, Google Analytics, e-ticaret platformunuz ya da e-posta doğrulamasıyla yapılır. Bir alan adını yalnızca bir hesap sahiplenebilir; birden fazla hesabın aynı alan adını kullanması için çoklu müşteri hesabı (MCA) gerekir.
  • Kargo: Kargo ve iade (Shipping and returns) ayarlarında Türkiye için TRY cinsinden ücreti (ücretsiz, sabit, belirli tutarın üzerinde ücretsiz veya ağırlığa göre), sipariş kesim saatini, hazırlık ve taşıma sürelerini tanımlayın. Türkiye, kargo bilgisinin zorunlu olduğu ülkeler arasında değil; ancak teslimat süresini ve ücretini göstermek alışveriş yapanın kararını doğrudan etkiler.
  • İade politikası: Süre, koşullar ve masraf açıkça yazılmalı ve tüketicinin Mesafeli Sözleşmeler Yönetmeliği'nden doğan 14 günlük cayma hakkıyla uyumlu olmalı.
  • Site gereksinimleri: Kolay bulunan iletişim bilgisi, kredi kartı, banka kartı veya kapıda ödeme gibi en az bir yaygın ödeme yöntemi, SSL ile korunan ödeme sayfası, satın almadan önce gösterilen tüm masraflar ve sorunsuz tamamlanan bir ödeme akışı.
Bu maddelerdeki eksikler çoğunlukla yanlış beyan (misrepresentation) politikası kapsamında hesap düzeyinde sorun olarak döner. Dolandırıcılık veya gizlenen masraflar gibi ağır ihlallerde hesap uyarısız askıya alınabilir; diğer durumlarda önce uyarı gelir.

Meta katalog: veri kaynakları ve piksel eşleşmesi​

Meta'da kataloğa ürün eklemenin yolları şunlardır: Ticaret Yöneticisi'nde form doldurarak tek tek ekleme, tek seferlik dosya yükleme, bir adresten zamanlanmış feed, Google E-Tablolar, Shopify veya WooCommerce gibi iş ortağı platform entegrasyonları, Meta pikseliyle sayfalardan okuma ve API (tekil ürün uç noktaları ile Batch API). Mevcut ürünlerin yalnızca bazı alanlarını güncellemek için birincil kaynağa ek veri akışı (supplementary feed) bağlanabilir; bu akış ürün ekleyip silemez ve her id değeri birincil kaynaktaki bir ürünle eşleşmelidir.

Meta, Google biçimindeki XML feed'ini (g: ad alanı) kabul ettiği için birçok mağaza tek dosyayla iki platformu besler. Aradaki farklar:
KonuGoogle Merchant CenterMeta katalog
Temel zorunlu alanlarid, title, description, link, image_link, availability, price; yeni üründe brandid, title, description, availability, condition, price, link, image_link; ayrıca brand, gtin veya mpn'den en az biri
id uzunluğuEn fazla 50 karakterEn fazla 100 karakter
title ve description150 ve 5.000 karakter200 ve 9.999 karakter, düz metin
Stok değerleriin_stock, out_of_stock, preorder, backorderBelgelenen: in stock, out of stock
GörselJPEG, WebP, PNG, GIF, BMP, TIFF; en fazla 16 MBJPEG veya PNG; en az 500×500 piksel, en fazla 8 MB
Kategoriİsteğe bağlıHer üründe gönderilmesi istenir: google_product_category veya fb_product_category
Tek dosya kullanıyorsanız ortak paydada kalın: id en fazla 50, başlık en fazla 150 karakter olsun, açıklamada HTML bulunmasın, görseller en az 500×500 piksel ve en fazla 8 MB büyüklükte JPEG veya PNG olsun. Stok değerlerinin ve indirim tarihlerinin Meta'da doğru okunduğunu yükleme raporundan kontrol edin; Meta'nın belgelerindeki tarih örneği saat farkını iki noktayla yazıyor (Türkiye için +03:00).

Advantage+ katalog reklamlarının çalışması için piksel ve Dönüşümler API'si (Conversions API) olaylarındaki ürün kimlikleri katalogdaki id değerleriyle aynı olmalı ve piksel, kataloğa olay kaynağı olarak bağlanmalıdır. Meta, uygun olduğu sayfalarda Search, ViewCategory, ViewContent, AddToCart ve Purchase olaylarının kurulmasını istiyor; ViewCategory standart bir olay olmadığından fbq('trackCustom', ...) ile gönderilir. Kullanıcı henüz varyant seçmemişse ViewContent olayında item_group_id değerini content_type: 'product_group' ile gönderebilirsiniz; AddToCart ve Purchase olaylarında product_group kullanılmaz.
Kod:
fbq('track', 'ViewContent', {
  content_ids: ['TSH-001-KIR-M'],   // katalogdaki id ile aynı
  content_type: 'product',
  value: 399.90,
  currency: 'TRY'
});

fbq('track', 'Purchase', {
  contents: [{id: 'TSH-001-KIR-M', quantity: 2}],
  content_type: 'product',
  value: 799.80,
  currency: 'TRY'
}, {eventID: 'order-100245'});       // sunucu olayındaki event_id ile aynı
Aynı satın alma sunucudan Dönüşümler API'siyle de gönderiliyorsa olay adı ve event_id eşleşmelidir; Meta, ilk olaydan sonraki 48 saat içinde aynı event_id ile gelen kopyaları ayıklar. Ölçüm ve atıf tarafını ücretli reklamda ölçüm ve atıf yazımızda ayrıntılı olarak ele aldık.

Feed'i üretmek: eklenti mi, özel üretici mi?​

WooCommerce'te resmî Google for WooCommerce ve Meta for WooCommerce eklentileri, Shopify'da ise Google & YouTube ile Facebook & Instagram uygulamaları ürünleri doğrudan eşitler. Katalog düzgün modellenmişse bu yol yeterlidir. Özel üretici şu durumlarda gerekir: ürün verisi ERP'de veya PIM'de duruyorsa, başlıkları kurallarla zenginleştirmek istiyorsanız, aynı veriden pazar yeri ve bayi XML'leri de üretiyorsanız ya da on binlerce ürünlük bir kataloğu bellek sorunu yaşamadan yazmanız gerekiyorsa.

Aşağıdaki Python (3.9 ve üzeri) betiği ürünleri bir üreteçten (generator) okur ve XML'i akış halinde (streaming) yazar. Kaçışlamayı XMLGenerator'a bırakır, XML 1.0'ın kabul etmediği kontrol karakterlerini temizler, fiyatı Decimal ile biçimlendirir ve dosyayı geçici bir adla yazıp sonunda atomik olarak yerine koyar:
Python:
import html, logging, os, re
from decimal import Decimal, ROUND_HALF_UP
from xml.sax.saxutils import XMLGenerator
from zoneinfo import ZoneInfo

log = logging.getLogger("feed")
IST = ZoneInfo("Europe/Istanbul")
BAD_XML = re.compile("[\x00-\x08\x0b\x0c\x0e-\x1f\ufffe\uffff]")  # XML 1.0'da yasak
TAGS = re.compile(r"<[^>]+>")

def text(value, limit):
    s = html.unescape(TAGS.sub(" ", value or ""))       # HTML'i düz metne çevir
    return " ".join(BAD_XML.sub("", s).split())[:limit]

def money(amount, currency="TRY"):                       # "1299.9" -> "1299.90 TRY"
    return f"{Decimal(amount).quantize(Decimal('0.01'), ROUND_HALF_UP)} {currency}"

def gtin_ok(code):                                       # GS1 kontrol basamağı
    if not code.isdigit() or len(code) not in (8, 12, 13, 14):
        return False
    body, check = code[:-1], int(code[-1])
    total = sum(int(d) * (3 if n % 2 == 0 else 1) for n, d in enumerate(reversed(body)))
    return (10 - total % 10) % 10 == check

def fields(p):
    f = {"g:id": p["sku"], "g:title": text(p["title"], 150),
         "g:description": text(p["description"], 5000),
         "g:link": p["url"], "g:image_link": p["image"],
         "g:availability": "in_stock" if p["stock"] > 0 else "out_of_stock",
         "g:price": money(p["price"]), "g:condition": "new",
         "g:brand": text(p["brand"], 70), "g:item_group_id": p.get("parent_sku"),
         "g:color": p.get("color"), "g:size": p.get("size"), "g:mpn": p.get("mpn")}
    if p.get("sale_price"):
        f["g:sale_price"] = money(p["sale_price"])
        if p.get("sale_start") and p.get("sale_end"):    # saat dilimli datetime
            s, e = p["sale_start"].astimezone(IST), p["sale_end"].astimezone(IST)
            f["g:sale_price_effective_date"] = f"{s:%Y-%m-%dT%H:%M%z}/{e:%Y-%m-%dT%H:%M%z}"
    gtin = (p.get("gtin") or "").strip()
    if gtin_ok(gtin):
        f["g:gtin"] = gtin
    elif gtin:
        log.warning("geçersiz GTIN, kaynağı düzeltin: %s %s", p["sku"], gtin)
    elif not p.get("mpn"):
        f["g:identifier_exists"] = "no"      # yalnızca gerçekten kod yoksa
    return f

def el(x, name, value):
    x.startElement(name, {})
    x.characters(str(value))                 # &, < ve > burada kaçışlanır
    x.endElement(name)

def write_feed(products, path, site, shop):
    tmp = path + ".tmp"
    with open(tmp, "wb") as out:
        x = XMLGenerator(out, encoding="utf-8")
        x.startDocument()
        x.startElement("rss", {"version": "2.0", "xmlns:g": "http://base.google.com/ns/1.0"})
        x.startElement("channel", {})
        for name, value in (("title", shop), ("link", site), ("description", shop)):
            el(x, name, value)
        for p in products:                   # üreteç: katalog belleğe alınmaz
            if not p.get("image") or Decimal(p["price"]) <= 0:
                log.warning("atlandı (görsel/fiyat yok): %s", p["sku"])
                continue
            x.startElement("item", {})
            for name, value in fields(p).items():
                if value:
                    el(x, name, value)
            x.endElement("item")
            x.ignorableWhitespace("\n")
        x.endElement("channel")
        x.endElement("rss")
        x.endDocument()
    os.replace(tmp, path)                    # okuyucu yarım dosya görmez
write_feed fonksiyonuna verdiğiniz products parametresi, veritabanından sunucu taraflı imleçle (server-side cursor) satır satır okuyan bir üreteç olmalı; tüm kataloğu önce bir listeye almak yüz binlerce üründe belleği şişirir. sale_start ve sale_end saat dilimi taşıyan (timezone-aware) datetime nesneleri olmalı: astimezone() saat dilimsiz bir değeri sunucunun yerel saati sayar ve sunucu UTC'deyse indirim üç saat kayar. Betiği cron ile düzenli çalıştırın ve dosyayı iki platforma aynı adresten sunun. Google dosyayı yalnızca veri kaynağında seçtiğiniz takvime göre çeker; dosyayı saatte bir üretmeniz, Google'ın onu saatte bir okuduğu anlamına gelmez. Gün içinde sık değişen fiyat ve stok için yalnızca değişen ürünleri Merchant API ile gönderin.

Feed'e dokunmadan düzeltme yapmak için Merchant Center'da ek veri kaynakları (supplemental data sources) ve özellik kuralları (attribute rules) kullanılır. İkisi de Ayarlar > Eklentiler (Settings > Add-ons) bölümünden "Gelişmiş veri kaynağı yönetimi" (Advanced data source management) eklentisinin etkinleştirilmesini gerektirir. Tipik kullanımlar: eksik GTIN'leri bir tablodan eklemek, başlıkların başına marka yazmak, kâr marjı veya sezon gruplarını custom_label_0-4 alanlarına yazıp kampanyaları bunlara göre bölmek. Tükenen ürünü availability ile bildirin; depo kapanışı veya bakım gibi kısa süreli durumlarda ürünü silmeden reklamlardan çekmek için pause özelliği (ads değeriyle) kullanılabilir. Google duraklatmayı en fazla 14 gün tutmanızı istiyor; daha uzun süren duraklatma ürünün reddedilmesine yol açar.

Reddedilen ürünler: sorun giderme tablosu​

BelirtiOlası nedenÇözüm
Fiyat uyuşmazlığıÖnbellekteki eski sayfa, KDV hariç feed fiyatı, konuma göre kur dönüşümü, varsayılan varyantı gösteren sayfa, yalnızca sepette uygulanan indirimFeed'i ve sayfayı aynı kaynaktan üretin, fiyat değişince önbelleği temizleyin, indirimi sale_price ile verin
Stok uyuşmazlığıSeyrek üretilen feed, yapılandırılmış veride sabit InStock, tükenen varyantın seçili açılmasıStok değişiminde feed üretimini veya API çağrısını tetikleyin; işaretlemeyi gerçek stoktan üretin
Eksik veya geçersiz tanımlayıcıHatalı kontrol basamağı, kısıtlı önek, varyantlara kopyalanmış GTIN, GTIN'i olan üründe identifier_exists=noGTIN'i tedarikçiden doğrulayın; yoksa brand ve mpn gönderin ya da identifier_exists=no kullanın
Görsel sorunuKüçük görsel, tanıtım yazısı veya filigran, yer tutucu görsel, görsel sunucusunun Google tarayıcılarını engellemesiTemiz ürün görseli kullanın; CDN ve WAF kurallarında Googlebot-Image'a izin verin
Açılış sayfası taranamıyorrobots.txt engeli, üyelik duvarı, yurt dışı IP engeli (geo-blocking), bot koruması, 404 veya tükenen ürünü ana sayfaya yönlendirmeGooglebot ve Storebot-Google'ı engellemeyin; AdsBot-Google genel * kuralını yok saydığı için onu adıyla engellemediğinizden emin olun; ülke engelinde Google tarayıcılarını istisna tutun
Yanlış beyan veya site gereksinimleriEksik iletişim veya iade bilgisi, gizlenen masraflar, karşılanamayan tekliflerEksikleri tamamlayıp Merchant Center'dan inceleme isteyin
Politika kısıtlamasıÜlkeye göre kısıtlı kategoriler (alkol, reçeteli ilaç, silah gibi)Alışveriş politikalarını okuyun; gerekirse excluded_destination ile ürünü ilgili yüzeyden çıkarın
Meta'da düşük katalog eşleşmesiPiksel olaylarında katalogda bulunmayan kimlikler (ana ürün kodu, veritabanı satır numarası)content_ids alanına feed'deki id değerini yazın ve pikseli kataloğa bağlayın
Ürün verisini düzelttiğinizde ürün otomatik olarak yeniden incelenir. Hesap düzeyindeki sorunlarda Merchant Center'dan inceleme veya itiraz talep edilir; Google'a göre hesap incelemeleri genellikle 7 iş günü sürer.

Sık sorulan sorular​

GTIN zorunlu mu?
Üreticinin atadığı bir GTIN varsa gönderin; göndermemek görünürlüğü sınırlar ve ürünün reddedilmesine yol açabilir. Yalnızca sizin sattığınız ve barkod almadığınız kendi markanızın ürünlerinde brand ve mpn yeterlidir; el yapımı ve kişiye özel ürünlerde identifier_exists=no kullanılır.

XML mi, Google E-Tablolar mı?
Birkaç yüz ürünlük ve elle yönetilen kataloglarda E-Tablolar pratiktir. Ürünler bir veritabanında duruyorsa XML veya metin dosyasını kodla üretin; elle müdahale ve sürüm karmaşası ortadan kalkar.

Feed ne sıklıkla yenilenmeli?
Google'da ürünlerin süresi son yenilemeden 30 gün sonra dolar; bunu hedef değil, en geç sınır olarak görün ve feed'i pratikte en az günde bir kez üretin. Fiyat ve stok gün içinde değişiyorsa değişen ürünleri API ile anında gönderin; Meta'da zamanlanmış feed saatlik de çalışabilir.

Ürünüm neden fiyat uyuşmazlığı yüzünden reddedildi?
Google'ın açılış sayfasında gördüğü fiyat, feed'deki fiyatla aynı değil. Sayfayı oturum açmadan ve Türkiye dışından (örneğin bir VPN ile) açıp hem görünen fiyata hem de yapılandırılmış verideki price değerine bakın; en sık nedenler KDV, kur dönüşümü, önbellek ve varsayılan varyanttır.

Merchant API'ye geçmem gerekiyor mu?
Content API for Shopping'i doğrudan çağıran bir entegrasyonunuz varsa evet. Merchant API v1'i kullanmadan önce Google Cloud projenizi registerGcp yöntemiyle bir kez kaydetmeniz gerekir; ürünler productInputs.insert ile API türündeki bir veri kaynağına yazılır. Dosya, zamanlanmış getirme veya E-Tablolar kullanıyorsanız bir şey yapmanız gerekmez.

Sonuç ve sonraki adımlar​

Sağlam bir feed üç şeye dayanır: tek bir veri kaynağı, açılış sayfasıyla birebir tutarlılık ve doğru tanımlayıcılar. Önerilen sıra şöyle: Merchant Center'daki sorunlu ürünleri ret nedenine göre gruplayıp en çok ürünü etkileyen sorundan başlayın; id, fiyat ve stok alanlarını tek kaynaktan üretin; yapılandırılmış veriyi feed'le eşitleyin; Meta pikselindeki ürün kimliklerini katalogla eşleştirin; Content API kullanıyorsanız Merchant API v1 geçişini tamamlayın. Feed altyapısını ve kampanya ölçümünü birlikte kurmak isterseniz AIOR'un pazarlama ve SEO hizmetlerine göz atabilirsiniz. Kendi feed'inizle ilgili sorularınızı bu konuya yazabilirsiniz.

A product feed is a single machine-readable list of everything you sell, with each item's price, availability, images and identifiers. Google Merchant Center and Meta's catalog read your products from that list and show them in ads and on shopping surfaces, and errors in it come back to you as warnings or disapproved items. This guide covers the attributes each platform expects, the settings that matter for a store in Turkey (Türkiye), the ways to generate a feed, and the disapprovals we see most often. Platform rules change frequently; everything below reflects the official documentation as of October 2026.

Where a product feed is used​

  • Google: products in Merchant Center power free listings, Shopping ads and Performance Max campaigns. Turkey is a supported country and its listed currency is the Turkish lira (TRY). Your product data should be in the same language as your landing pages.
  • Meta: the catalog in Commerce Manager feeds Advantage+ catalog ads (formerly dynamic ads) on Facebook and Instagram, and shop experiences in the markets where they are available.
  • Other channels: price comparison sites, marketplaces and dealers who take XML exports usually want their own schema.
A rule of thumb: generate every feed from one source of truth (ERP, PIM or the store database) and produce a separate output per channel. Spreadsheets edited by hand for each channel turn into price and stock mismatches within weeks.

Choosing a format: XML, delimited text, Google Sheets or an API​

MethodGood fitWatch out for
XML (RSS 2.0 or Atom with the g: namespace)Code-generated catalogs and nested attributes such as shippingOne escaping or encoding mistake can make the whole file unreadable
Delimited text (Google: tab-delimited .txt or .tsv; Meta: CSV or TSV)Flat catalogs produced by an exportTabs, line breaks or commas inside a field shift the columns
Google SheetsSmall stores and supplemental dataManual upkeep invites errors
API (Google Merchant API; Meta's catalog and Batch APIs)Catalogs whose prices and stock change several times a dayAuthentication, quotas and error handling are on you
You either upload the file yourself or let the platform pull it from a URL on a schedule (scheduled fetch). Google fetches from http, https, ftp and sftp addresses, accepts files under 4 GB, supports a username and password for protected locations, and also takes files compressed as .gz, .zip or .bz2. Do not block Googlebot or AdsBot-Google from the directory that holds the file. On Meta, a scheduled feed can run hourly, daily or weekly.

On the API side there has been a major change. Google's Content API for Shopping reached its sunset on 18 August 2026. Since 1 September 2026, requests from clients without an active extension fail intermittently with HTTP 410 Gone, and the full shutdown is planned for early 2027 (Google notes that the timing may still be revised). Merchant API v1 is the replacement. If you sync through a platform integration such as the Google & YouTube app on Shopify, the provider handles the migration. Google's migration notices cover API integrations only; file uploads, scheduled fetches and Google Sheets sources are not API calls, so the switch does not touch them.

A minimal XML feed looks like this:
Kod:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
  <channel>
    <title>Example Store</title>
    <link>https://www.example.com</link>
    <description>Example Store product feed</description>
    <item>
      <g:id>TSH-001-RED-M</g:id>
      <g:title>Example Wear Basic T-Shirt 100% Cotton Red M</g:title>
      <g:description>Crew neck, 100% cotton, machine washable at 30 °C.</g:description>
      <g:link>https://www.example.com/basic-t-shirt?color=red&amp;size=m</g:link>
      <g:image_link>https://cdn.example.com/products/tsh-001-red.jpg</g:image_link>
      <g:availability>in_stock</g:availability>
      <g:price>449.90 TRY</g:price>
      <g:sale_price>399.90 TRY</g:sale_price>
      <g:sale_price_effective_date>2026-11-27T00:00+0300/2026-11-30T23:59+0300</g:sale_price_effective_date>
      <g:brand>Example Wear</g:brand>
      <g:gtin>8690123456789</g:gtin>
      <g:condition>new</g:condition>
      <g:item_group_id>TSH-001</g:item_group_id>
      <g:color>Red</g:color>
      <g:size>M</g:size>
      <g:gender>unisex</g:gender>
      <g:age_group>adult</g:age_group>
      <g:google_product_category>212</g:google_product_category>
      <g:product_type>Clothing > T-Shirts</g:product_type>
    </item>
  </channel>
</rss>
Note that the & in the link is written as &amp;: a single unescaped ampersand stops the whole file from parsing. Category 212 is "Apparel & Accessories > Clothing > Shirts & Tops" in Google's product taxonomy. The taxonomy is also published in Turkish, but a numeric ID is language-neutral. The GTIN in the example only illustrates the format.

In a delimited file, the first row holds the column names. The header below is written for Meta's CSV format; give Google the same columns in a tab-delimited file:
Kod:
id,title,description,availability,condition,price,sale_price,sale_price_effective_date,link,image_link,brand,gtin,mpn,item_group_id,color,size,gender,age_group,google_product_category
The two platforms spell availability differently: Google uses in_stock, while Meta's documentation uses "in stock" with a space. Wrap any field that contains a comma in double quotes.

Google attributes: required, conditional and optional​

Merchant Center's Product data specification labels each attribute as required, "it depends" or optional. A summary for a store selling in Turkey:
AttributeStatusNotes
idRequiredUp to 50 characters, unique and stable. The SKU is ideal.
titleRequiredUp to 150 characters: brand, product type and the distinguishing detail (color, size, capacity)
descriptionRequiredUp to 5,000 characters of product information, not promotional copy
linkRequiredProduct page on your verified domain that needs no sign-in
image_linkRequiredStable URL; size and content rules below the table
availabilityRequiredin_stock, out_of_stock, preorder, backorder; the last two also need availability_date
priceRequiredNumber plus ISO 4217 code (449.90 TRY); VAT included everywhere except the US and Canada
sale_price, sale_price_effective_dateOptionalThe sale price must be lower than price; without dates the sale starts immediately
brandRequired for new productsExcept movies, books and musical recordings; up to 70 characters
gtinIt dependsSubmit the manufacturer-assigned GTIN when one exists; Google strongly recommends it
mpnRequired when there is no manufacturer GTINManufacturer part number, up to 70 characters
identifier_existsOptionalDefaults to yes; set no only when identifiers truly do not exist
conditionRequired for used and refurbished itemsnew, refurbished, used; optional for new products
google_product_categoryOptionalOne category, numeric ID preferred. Some products must use a specific category.
product_typeOptionalYour own category path, up to 750 characters; useful for splitting campaigns
shippingIt dependsShipping costs are required in the countries Google lists (the US, Germany and the UK among them); Turkey is not on the list. Define shipping at account level and keep the attribute for exceptions.
item_group_idConditionally requiredNeeded for variants in free listings; required for ads in Brazil, France, Germany, Japan, the UK and the US
color, size, gender, age_groupConditionally requiredExpected for apparel and accessories; always send them when they distinguish variants
For images, Google has announced that a 500×500 pixel minimum will be enforced for all products from 31 January 2027, and it recommends 1500×1500 pixels or more; the ceiling is 64 megapixels and 16 MB. Images must not carry prices, promotional text such as "free shipping", watermarks, borders or logos that are not part of the product. Give each variant its own image showing only that variant.

Product identifiers: GTIN, MPN, brand and identifier_exists​

A GTIN is the global product number printed under a barcode: 8, 12, 13 or 14 digits with a valid GS1 check digit. In Turkey, these numbers come from a GS1 Türkiye membership and start with the 868 or 869 prefix. On joining, your company receives a GS1 Company Prefix (GCP) whose length depends on how many products you need to number: a 9-digit prefix covers up to 1,000 products, a 7-digit prefix up to 100,000.

Decide what to send in this order:
  1. The manufacturer assigned a GTIN: send gtin and brand. Most branded goods you resell fall here. Take the number from the supplier's product data; never guess.
  2. No GTIN, but a manufacturer part number: send brand and mpn. This is common for spare parts and industrial goods.
  3. Your own private label, sold only by you, with no GTIN assigned: Google says you do not need to submit a GTIN in this case; send brand and mpn instead. If you will also supply other retailers or marketplaces that demand barcodes, a GS1 Türkiye membership makes sense.
  4. No identifiers at all: for custom, handmade, one-of-a-kind or antique products, set identifier_exists to no.
Common mistakes: setting identifier_exists=no on a product that has a GTIN (Google flags it with a warning), copying one GTIN to every color and size (each variant needs its own), submitting in-store scale or shelf codes (the 2, 02 and 04 prefixes are restricted; 05, 98 and 99 are coupon ranges), and inventing numbers. The gtin_ok function in the script further down bulk-checks the length and check digit of an existing barcode list; filter out the restricted prefixes separately.

Landing page consistency: price, availability and structured data​

Google compares the price and availability in your feed with the landing page and checkout, and disapproves the item when they disagree. For stores in Turkey:
  • Send prices in TRY with KDV (Turkish VAT) included, and make the product page and checkout show the same amount. Do not change the price or currency based on the visitor's location: Googlebot's default IP addresses appear to be in the US, so automatic currency conversion produces mismatches by itself.
  • Use a dot as the decimal separator, as all of Google's examples do. The Turkish locale renders "1.299,90 TL", which should become "1299.90 TRY" in the feed; convert from the numeric value, never from the formatted string. Google asks for no more than two decimal places and rounds any extra digits.
  • Write sale dates with a time zone. Turkey is on UTC+3 all year; "2026-11-27T00:00" without an offset is read as UTC, so the sale would start at 03:00 local time.
  • Check the legal conditions for advertising discounts in Turkey, including how the pre-discount price is determined, against the Regulation on Commercial Advertising and Unfair Commercial Practices with your legal adviser.
The schema.org Product and Offer structured data on the product page must carry the same values as the feed. Merchant Center's automatic item updates are on by default and can update price, sale price, availability and condition from what they read on the page. Google advises against treating them as a substitute for regular data updates, and suggests turning off price updates when a page shows several strikethrough prices. Render the markup on the server: Google warns that JavaScript-generated markup can make Shopping crawls less frequent and less reliable. Example for a variant on sale:
JSON:
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Example Wear Basic T-Shirt Red M",
  "sku": "TSH-001-RED-M",
  "gtin13": "8690123456789",
  "brand": {"@type": "Brand", "name": "Example Wear"},
  "image": ["https://cdn.example.com/products/tsh-001-red.jpg"],
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/basic-t-shirt?color=red&size=m",
    "price": 399.90,
    "priceCurrency": "TRY",
    "priceSpecification": {
      "@type": "UnitPriceSpecification",
      "priceType": "https://schema.org/StrikethroughPrice",
      "price": 449.90,
      "priceCurrency": "TRY"
    },
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
Here price is the current price the customer pays, and the struck-through original sits in priceSpecification as a StrikethroughPrice. In the feed, by contrast, the full price goes in price and the discounted one in sale_price. Google also expects the landing page to show both the regular and the sale price clearly, with the sale price applied at checkout.

Variants: mapping item_group_id​

Every sellable SKU is a separate item in the feed. The parent product is not submitted on its own; the variants share one item group (item_group_id):
  • id is the variant SKU and item_group_id is the parent code. Keep both stable; changing the group ID makes the grouping inconsistent.
  • The attributes that distinguish variants (color, size, material, pattern) must be filled on every item, and no two items in a group may share the same combination.
  • Each variant needs its own URL (such as /basic-t-shirt?color=red&size=m), and the page must open with that variant selected. A page that opens on the default variant is one of the most common causes of price and availability mismatches.
  • Do not group products that are not variants, such as the jacket and trousers of a suit.
We covered how to model variant axes and SKUs in the catalog itself in our product catalog data model article; the feed should mirror that model one to one.

Merchant Center account settings for Turkey​

A flawless feed still goes nowhere if the account is incomplete:
  • Business information: legal name, address and customer service contact details that match the website.
  • Website verification and claim: done with an HTML tag or file, Google Tag Manager, Google Analytics, your e-commerce platform or email verification. Only one account can claim a URL; several accounts sharing a domain need a multi-client account (MCA).
  • Shipping: under Shipping and returns, define the cost for Turkey in TRY (free, flat rate, free over a threshold or weight-based), the order cutoff time, and handling and transit times. Turkey is not among the countries where shipping costs are mandatory, but showing delivery time and cost directly affects the shopper's decision.
  • Return policy: state the window, conditions and costs clearly, consistent with the 14-day withdrawal right consumers have under Turkey's Distance Contracts Regulation (Mesafeli Sözleşmeler Yönetmeliği).
  • Website requirements: easy-to-find contact details, at least one conventional payment method, an SSL-protected checkout, all costs disclosed before purchase, and a checkout that actually completes.
Gaps in these items usually come back as account-level issues under the misrepresentation policy. Egregious violations such as scams or hidden costs can lead to suspension without prior warning; in other cases a warning comes first.

Meta catalog: data sources and pixel matching​

Meta offers several ways to put products into a catalog: adding items one by one through a form in Commerce Manager, a one-time file upload, a scheduled feed from a URL, Google Sheets, partner platform integrations such as Shopify or WooCommerce, the Meta Pixel reading your pages, and the API (single-item endpoints and the Batch API). To update only some fields of existing products, attach a supplementary feed to the primary source; it cannot add or remove products, and every id in it must match a product from the primary source.

Because Meta accepts Google-format XML feeds (with the g: namespace), many stores serve both platforms from one file. The differences:
TopicGoogle Merchant CenterMeta catalog
Core required fieldsid, title, description, link, image_link, availability, price; brand for new productsid, title, description, availability, condition, price, link, image_link, plus at least one of brand, gtin or mpn
id lengthUp to 50 charactersUp to 100 characters
title and description150 and 5,000 characters200 and 9,999 characters, plain text
Availability valuesin_stock, out_of_stock, preorder, backorderDocumented: in stock, out of stock
ImagesJPEG, WebP, PNG, GIF, BMP, TIFF; up to 16 MBJPEG or PNG; at least 500×500 pixels, up to 8 MB
CategoryOptionalExpected on every product: google_product_category or fb_product_category
With a single file, stay within the common ground: ids of 50 characters or fewer, titles of 150 or fewer, descriptions without HTML, and JPEG or PNG images of at least 500×500 pixels and no more than 8 MB. Check Meta's upload report to confirm that availability values and sale dates are read correctly; the date example in Meta's documentation writes the offset with a colon (+03:00 for Turkey).

For Advantage+ catalog ads to work, the product IDs in your Pixel and Conversions API events must equal the id values in the catalog, and the pixel must be connected to the catalog as an event source. Meta asks for the Search, ViewCategory, ViewContent, AddToCart and Purchase events on the pages where they apply; ViewCategory is not a standard event, so it is sent with fbq('trackCustom', ...). When the shopper has not picked a variant yet, ViewContent may carry the item_group_id with content_type: 'product_group'; never use product_group with AddToCart or Purchase.
Kod:
fbq('track', 'ViewContent', {
  content_ids: ['TSH-001-RED-M'],   // same as the id in the catalog
  content_type: 'product',
  value: 399.90,
  currency: 'TRY'
});

fbq('track', 'Purchase', {
  contents: [{id: 'TSH-001-RED-M', quantity: 2}],
  content_type: 'product',
  value: 799.80,
  currency: 'TRY'
}, {eventID: 'order-100245'});       // same event_id as the server event
If the same purchase is also sent from your server through the Conversions API, the event name and event_id must match; Meta drops duplicates that arrive with the same event_id within 48 hours of the first event. We go deeper into measurement and attribution in our article on paid ads measurement and attribution.

Generating the feed: plugin or custom generator?​

On WooCommerce, the official Google for WooCommerce and Meta for WooCommerce extensions, and on Shopify the Google & YouTube and Facebook & Instagram apps, sync products directly. If the catalog is modelled properly, that is enough. A custom generator pays off when product data lives in an ERP or PIM, when you want rule-based title enrichment, when the same data must also produce marketplace and dealer XML files, or when tens of thousands of products have to be written without memory trouble.

The Python script below (3.9 or later) reads products from a generator and streams the XML out. It leaves escaping to XMLGenerator, strips the control characters XML 1.0 does not allow, formats prices with Decimal, and writes to a temporary file that atomically replaces the live one at the end:
Python:
import html, logging, os, re
from decimal import Decimal, ROUND_HALF_UP
from xml.sax.saxutils import XMLGenerator
from zoneinfo import ZoneInfo

log = logging.getLogger("feed")
IST = ZoneInfo("Europe/Istanbul")
BAD_XML = re.compile("[\x00-\x08\x0b\x0c\x0e-\x1f\ufffe\uffff]")  # not allowed in XML 1.0
TAGS = re.compile(r"<[^>]+>")

def text(value, limit):
    s = html.unescape(TAGS.sub(" ", value or ""))       # HTML to plain text
    return " ".join(BAD_XML.sub("", s).split())[:limit]

def money(amount, currency="TRY"):                       # "1299.9" -> "1299.90 TRY"
    return f"{Decimal(amount).quantize(Decimal('0.01'), ROUND_HALF_UP)} {currency}"

def gtin_ok(code):                                       # GS1 check digit
    if not code.isdigit() or len(code) not in (8, 12, 13, 14):
        return False
    body, check = code[:-1], int(code[-1])
    total = sum(int(d) * (3 if n % 2 == 0 else 1) for n, d in enumerate(reversed(body)))
    return (10 - total % 10) % 10 == check

def fields(p):
    f = {"g:id": p["sku"], "g:title": text(p["title"], 150),
         "g:description": text(p["description"], 5000),
         "g:link": p["url"], "g:image_link": p["image"],
         "g:availability": "in_stock" if p["stock"] > 0 else "out_of_stock",
         "g:price": money(p["price"]), "g:condition": "new",
         "g:brand": text(p["brand"], 70), "g:item_group_id": p.get("parent_sku"),
         "g:color": p.get("color"), "g:size": p.get("size"), "g:mpn": p.get("mpn")}
    if p.get("sale_price"):
        f["g:sale_price"] = money(p["sale_price"])
        if p.get("sale_start") and p.get("sale_end"):    # timezone-aware datetimes
            s, e = p["sale_start"].astimezone(IST), p["sale_end"].astimezone(IST)
            f["g:sale_price_effective_date"] = f"{s:%Y-%m-%dT%H:%M%z}/{e:%Y-%m-%dT%H:%M%z}"
    gtin = (p.get("gtin") or "").strip()
    if gtin_ok(gtin):
        f["g:gtin"] = gtin
    elif gtin:
        log.warning("invalid GTIN, fix the source: %s %s", p["sku"], gtin)
    elif not p.get("mpn"):
        f["g:identifier_exists"] = "no"      # only when no code exists
    return f

def el(x, name, value):
    x.startElement(name, {})
    x.characters(str(value))                 # &, < and > are escaped here
    x.endElement(name)

def write_feed(products, path, site, shop):
    tmp = path + ".tmp"
    with open(tmp, "wb") as out:
        x = XMLGenerator(out, encoding="utf-8")
        x.startDocument()
        x.startElement("rss", {"version": "2.0", "xmlns:g": "http://base.google.com/ns/1.0"})
        x.startElement("channel", {})
        for name, value in (("title", shop), ("link", site), ("description", shop)):
            el(x, name, value)
        for p in products:                   # generator: catalog never held in memory
            if not p.get("image") or Decimal(p["price"]) <= 0:
                log.warning("skipped (no image/price): %s", p["sku"])
                continue
            x.startElement("item", {})
            for name, value in fields(p).items():
                if value:
                    el(x, name, value)
            x.endElement("item")
            x.ignorableWhitespace("\n")
        x.endElement("channel")
        x.endElement("rss")
        x.endDocument()
    os.replace(tmp, path)                    # readers never see a half-written file
The products argument should be a generator that reads rows from the database through a server-side cursor; loading the whole catalog into a list first will exhaust memory with hundreds of thousands of products. Pass sale_start and sale_end as timezone-aware datetimes: astimezone() treats a naive value as the server's local time, so on a server running in UTC the sale shifts by three hours. Run the script from cron and serve the file to both platforms from the same URL. Google fetches the file only on the schedule set in the data source, so regenerating it every hour does not mean Google reads it every hour. For prices and stock that change several times a day, send just the changed products through Merchant API.

To fix data without touching the feed, Merchant Center offers supplemental data sources and attribute rules. Both require activating the "Advanced data source management" add-on under Settings > Add-ons. Typical uses: adding missing GTINs from a sheet, prefixing titles with the brand, and writing margin or seasonal groups into custom_label_0-4 so campaigns can be split on them. Report sold-out products through availability. For short interruptions such as a warehouse closure or site maintenance, the pause attribute with the value ads takes a product out of ads without deleting it; Google asks you to pause for 14 days or less, and a longer pause leads to disapproval.

Disapproved products: troubleshooting table​

SymptomLikely causeFix
Price mismatchStale cached page, feed price without VAT, currency switched by location, page showing the default variant, a discount applied only in the cartBuild feed and page from the same source, purge the cache on price changes, send discounts as sale_price
Availability mismatchInfrequent feed builds, InStock hard-coded in structured data, a sold-out variant preselectedTrigger a feed build or API call on stock changes; generate the markup from real stock
Missing or invalid identifiersWrong check digit, restricted prefix, one GTIN copied across variants, identifier_exists=no on a product that has a GTINConfirm GTINs with the supplier; otherwise send brand and mpn, or identifier_exists=no
Image issuesImage too small, promotional text or watermark, placeholder image, image server blocking Google's crawlersUse clean product shots; allow Googlebot-Image in CDN and WAF rules
Landing page not crawlablerobots.txt block, login wall, geo-blocking of foreign IPs, bot protection, 404, or sold-out products redirected to the home pageDo not block Googlebot or Storebot-Google; AdsBot-Google ignores the global * group, so make sure it is not blocked by name; exempt Google's crawlers from country blocks
Misrepresentation or website requirementsMissing contact or return information, hidden costs, offers you cannot fulfilFill the gaps, then request a review in Merchant Center
Policy restrictionCategories restricted by country (alcohol, prescription drugs, weapons and the like)Read the Shopping policies; where needed, remove the product from that surface with excluded_destination
Low catalog match on MetaPixel events carrying IDs that are not in the catalog (parent codes, database row numbers)Put the feed's id in content_ids and connect the pixel to the catalog
Updating the product data triggers a new review of the item automatically. For account-level issues you request a review or file an appeal in Merchant Center; according to Google, account reviews typically take 7 business days.

Frequently asked questions​

Do I need GTINs?
If the manufacturer assigned one, yes: leaving it out limits visibility and can get the product disapproved. For a private label sold only by you and never assigned a GTIN, brand and mpn are enough; for handmade and custom goods, use identifier_exists=no.

XML or Google Sheets?
Sheets is practical for a catalog of a few hundred products maintained by hand. If the products live in a database, generate XML or delimited text from code; that removes manual edits and version confusion.

How often should the feed refresh?
Google expires products 30 days after their last refresh; treat that as the outer limit, not the goal, and rebuild the feed at least once a day. If prices or stock change during the day, push the changed products through the API right away; on Meta, a scheduled feed can also run hourly.

Why was my product disapproved for price mismatch?
The price Google saw on the landing page differs from the one in your feed. Open the page signed out and from outside Turkey (through a VPN, for example), then check both the visible price and the price value in the structured data. VAT, currency conversion, caching and the default variant are the usual suspects.

Do I have to migrate to Merchant API?
Yes, if you have an integration that calls the Content API for Shopping directly. Before using Merchant API v1, register your Google Cloud project once with the registerGcp method; products are then written with productInputs.insert into a data source of the API type. If you use files, scheduled fetches or Google Sheets, there is nothing to do.

Conclusion and next steps​

A dependable feed rests on three things: one source of data, exact consistency with the landing page, and correct identifiers. A sensible order of work: group the products Merchant Center flags by disapproval reason and start with the issue that affects the most items; generate id, price and availability from a single source; align the structured data with the feed; match the product IDs in your Meta Pixel events to the catalog; and if you still call the Content API, finish the move to Merchant API v1. If you want help setting up the feed pipeline and campaign measurement together, have a look at AIOR's marketing and SEO services. Questions about your own feed are welcome in this thread.
 
Son düzenleyen:

Forum istatistikleri

Konular
925
Mesajlar
932
Üyeler
26
En son üye
Jessicawatson

Çevrimiçi üyeler

Şu anda çevrimiçi üye yok.

Öne çıkan içerik

AIOR
AIOR TEKNOLOJİ

Tüm ihtiyaçlarınız için Teklif alın

Hosting · Domain · Sunucu · Tasarım · Yazılım · Mühendislik · Sektörel Çözümler

Teklif al

7/24 Destek · Anında yanıt

Geri
Üst