Cantürk Çelikok
Tüm yazılarSEO

Ürün sayfası için yapılandırılmış veri rehberi: Product şeması

Cantürk Çelikok • • 6 dk okuma

Ürün sayfası için yapılandırılmış veri rehberi: Product şeması

Ürün sayfasında yapılandırılmış veri ne işe yarıyor?

Bir ürün sayfasında fiyatı, stok durumunu ve ürün adını görmek bir insan için kolay. Google için ise sayfadaki "1.299 TL" yazısının fiyat mı, eski fiyat mı, yoksa kargo ücreti mi olduğunu anlamak her zaman kolay değil. Yapılandırılmış veri, bu bilgileri sayfanın içine makinenin okuyabileceği bir biçimde yazmanın yolu. Google'ın önerdiği biçim JSON-LD: sayfaya eklenen bir <script> etiketinin içinde duran, Schema.org sözlüğüne göre yazılmış bir JSON nesnesi.

Merchant Center yazılarında Product şemasından birkaç kez kısaca bahsettim. Bu yazıda konuyu baştan sona ele alıyorum: zorunlu alanlar, indirimli fiyat, kargo ve iade bilgisi, yorumlar, varyantlı ürünler ve şemanın ürün verisinden otomatik üretilmesi.

İki farklı zengin sonuç

Google, ürün sayfalarındaki yapılandırılmış veriyi iki tür zengin sonuç için kullanıyor.

Ürün snippet'leri. Ürünün doğrudan satın alınamadığı sayfalar için. Örneğin bir ürün inceleme yazısı. Bu tür sayfalarda puan, yorum ve artı/eksi listesi gibi bilgiler öne çıkıyor.

Satıcı listelemeleri (merchant listings). Ürünün satın alınabildiği sayfalar için. Fiyat, stok, kargo ücreti, iade koşulları ve beden gibi ayrıntılı ürün bilgileri burada kullanılıyor. E-ticaret sitelerini ilgilendiren asıl tür bu.

İkisi birbiriyle örtüşüyor. Satıcı listelemeleri için gereken alanları doldurduğunuzda sayfa çoğu zaman ürün snippet'leri için de uygun hale geliyor.

Google ayrıca ürün bilgisini iki kaynaktan alabiliyor: sayfadaki yapılandırılmış veri ve Merchant Center feed'i. Google'a göre ikisini birlikte kullanmak, ürünün görünebileceği yerleri en geniş hale getiriyor ve Google'ın veriyi doğrulamasını kolaylaştırıyor.

Temel Product şeması

Satıcı listelemeleri için zorunlu alanlar az: Product üzerinde name, image ve offers, Offer üzerinde de price ve priceCurrency. Önerilen alanları da ekleyince temel bir ürün sayfası şöyle görünüyor:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "OrnekMarka K87 Mekanik Klavye, Kahverengi Switch, Türkçe Q",
  "description": "Alüminyum kasalı, 87 tuşlu mekanik klavye.",
  "image": [
    "https://ornekmagaza.com/img/klv-1001-1x1.jpg",
    "https://ornekmagaza.com/img/klv-1001-4x3.jpg",
    "https://ornekmagaza.com/img/klv-1001-16x9.jpg"
  ],
  "sku": "KLV-1001",
  "gtin13": "8690000000012",
  "brand": {
    "@type": "Brand",
    "name": "OrnekMarka"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://ornekmagaza.com/urun/mekanik-klavye",
    "price": 1299.00,
    "priceCurrency": "TRY",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>

Birkaç kuralı bilmek gerekiyor.

Fiyat sıfırdan büyük olmalı ve sembol içermemeli. price alanına "1.299,00 TL" değil, 1299.00 yazılıyor. Para birimi ayrı bir alanda, üç harfli ISO kodu olarak veriliyor: TRY.

Satıcı listelemelerinde offers tek bir Offer olmalı. Bir fiyat aralığını gösteren AggregateOffer bu tür için kabul edilmiyor.

Stok durumu sabit değerlerden biri olmalı. En sık kullanılanlar InStock, OutOfStock, PreOrder ve BackOrder. Ürün durumu için de NewCondition, UsedCondition ve RefurbishedCondition değerleri var.

Görseller taranabilir olmalı ve ürünü göstermeli. Google, en az 50.000 piksellik (genişlik x yükseklik) yüksek çözünürlüklü görselleri 1x1, 4x3 ve 16x9 oranlarında vermeyi öneriyor. Beyaz arka planlı, ürünü net gösteren görseller tercih ediliyor.

Barkod varsa en belirgin olanı kullanılmalı. Türkiye'de ürün barkodları genelde 13 haneli olduğu için gtin13 alanı uygun düşüyor. Değer, URL olarak değil, yalnızca rakamlarla yazılıyor.

İndirimli fiyat

İndirimli bir üründe eski fiyatın üstü çizili gösterilmesini istiyorsanız, eski fiyatı ayrı bir priceSpecification içinde StrikethroughPrice olarak işaretlemeniz gerekiyor. Geçerli satış fiyatı ise her zamanki gibi price alanında kalıyor:

"offers": {
  "@type": "Offer",
  "url": "https://ornekmagaza.com/urun/mekanik-klavye",
  "price": 1099.00,
  "priceCurrency": "TRY",
  "availability": "https://schema.org/InStock",
  "priceValidUntil": "2026-11-30",
  "priceSpecification": {
    "@type": "UnitPriceSpecification",
    "priceType": "https://schema.org/StrikethroughPrice",
    "price": 1299.00,
    "priceCurrency": "TRY"
  }
}

Üstü çizili fiyat yalnızca bir indirim varken kullanılmalı. Geçerli fiyata priceType eklenmiyor, bu alan yalnızca eski fiyatı işaretliyor. İndirimin bitiş tarihini priceValidUntil alanıyla, ISO 8601 biçiminde vermek de mümkün.

Google, fiyat düşüşlerini kendi gözlemlediği fiyat değişimlerinden de hesaplayabiliyor. Bu yüzden ürünün hiç o fiyattan satılmadığı bir "eski fiyat" göstermek, Merchant Center yazısında anlattığım yanlış beyan sorunlarına yol açabilir.

Kargo ve iade bilgisi

Kargo ve iade koşulları her üründe aynıysa, bunları her ürün sayfasında tekrar etmek gerekmiyor. Google, iade politikasının sitenin genelinde geçerli olacak şekilde Organization şeması altında verilmesini öneriyor. Ürün düzeyinde bir politika tanımlanırsa, o ürün için genel politikanın yerine geçiyor.

Ana sayfaya ya da "Hakkımızda" sayfasına eklenecek bir Organization şeması:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "@id": "https://ornekmagaza.com/#organization",
  "name": "Örnek Mağaza",
  "url": "https://ornekmagaza.com",
  "logo": "https://ornekmagaza.com/img/logo.png",
  "hasMerchantReturnPolicy": {
    "@type": "MerchantReturnPolicy",
    "applicableCountry": "TR",
    "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
    "merchantReturnDays": 14,
    "returnMethod": "https://schema.org/ReturnByMail",
    "returnFees": "https://schema.org/FreeReturn"
  }
}
</script>

returnPolicyCategory alanı üç değer alabiliyor: belirli bir süre içinde iade (MerchantReturnFiniteReturnWindow), iade kabul edilmiyor (MerchantReturnNotPermitted) ve süresiz iade (MerchantReturnUnlimitedWindow). Süreli iadede merchantReturnDays alanı zorunlu. İade kargosunu müşteri ödüyorsa returnFees alanına ReturnFeesCustomerResponsibility yazılıyor.

Kargo bilgisi için de benzer bir yapı var. Ürün düzeyinde shippingDetails alanıyla kargo ücretini, gönderim bölgesini ve teslim süresini verebiliyorsunuz. Ücretsiz kargo, ücret alanına 0 yazılarak gösteriliyor. Yanlış beyan yazısında bu alanın tam bir örneğini vermiştim.

Burada önemli bir öncelik sırası var. Google, aynı bilgi birden fazla yerde verildiğinde en güçlüden zayıfa doğru şu sırayı izliyor: Merchant Center'daki ürün düzeyindeki feed verisi, API ile gönderilen ayarlar, Merchant Center ya da Search Console ayarları, ürün düzeyindeki şema ve en sonda Organization düzeyindeki şema. Yani Merchant Center'da kargo ayarı yaptıysanız, sayfadaki şemayla çelişmemesine dikkat etmek gerekiyor.

Yorumlar ve puanlar

Ürün sayfanızda müşteri yorumları varsa, ortalama puanı ve yorum sayısını aggregateRating alanıyla verebiliyorsunuz. Arama sonuçlarındaki yıldızlar bu alandan geliyor:

"aggregateRating": {
  "@type": "AggregateRating",
  "ratingValue": 4.6,
  "reviewCount": 38
},
"review": [
  {
    "@type": "Review",
    "author": {
      "@type": "Person",
      "name": "Ayşe K."
    },
    "datePublished": "2026-10-12",
    "reviewRating": {
      "@type": "Rating",
      "ratingValue": 5
    },
    "reviewBody": "Tuş hissi çok iyi, Türkçe Q dizilimi tam beklediğim gibi."
  }
]

ratingValue ve ratingCount ya da reviewCount alanlarından biri zorunlu. bestRating verilmezse 5, worstRating verilmezse 1 kabul ediliyor, yani 5 üzerinden puan kullanıyorsanız bu alanları yazmanıza gerek yok.

Yorumlarla ilgili iki kural ihlal edildiğinde yıldızlar hiç görünmeyebiliyor:

Puan ve yorumlar sayfada görünür olmalı. Şemada 4,6 puan ve 38 yorum yazıyorsa, kullanıcı da sayfada bu puanı ve yorumları görebilmeli. Yalnızca şemada var olan bir puan, Google'ın kurallarına aykırı.

Başka sitelerden puan toplanmamalı. Pazaryerlerindeki ya da başka platformlardaki puanlarınızı kendi sitenizin şemasına yazamazsınız. Şemadaki puan, sitenizde toplanan yorumlardan hesaplanmalı.

Varyantlı ürünler

Aynı ürünün farklı renk ve bedenleri varsa, bunları birbirinden bağımsız ürünler olarak değil, bir ProductGroup altında toplanan varyantlar olarak işaretlemek gerekiyor. ProductGroup, varyantların ortak özelliklerini (marka, açıklama, yorumlar) taşıyor. Her varyant da kendi fiyatı, stok durumu ve görseliyle ayrı bir Product oluyor.

Varyantların tek bir sayfada, ?renk=beyaz&beden=m gibi URL parametreleriyle seçildiği bir yapı için örnek:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "ProductGroup",
  "name": "OrnekMarka Kadın Keten Gömlek",
  "description": "Uzun kollu, %100 keten kadın gömleği.",
  "url": "https://ornekmagaza.com/urun/keten-gomlek",
  "brand": {
    "@type": "Brand",
    "name": "OrnekMarka"
  },
  "productGroupID": "GML-300",
  "variesBy": [
    "https://schema.org/color",
    "https://schema.org/size"
  ],
  "hasVariant": [
    {
      "@type": "Product",
      "sku": "GML-300-BYZ-M",
      "name": "OrnekMarka Kadın Keten Gömlek, Beyaz, M Beden",
      "image": "https://ornekmagaza.com/img/gml-300-beyaz.jpg",
      "color": "Beyaz",
      "size": "M",
      "offers": {
        "@type": "Offer",
        "url": "https://ornekmagaza.com/urun/keten-gomlek?renk=beyaz&beden=m",
        "price": 899.00,
        "priceCurrency": "TRY",
        "availability": "https://schema.org/InStock"
      }
    },
    {
      "@type": "Product",
      "sku": "GML-300-SYH-L",
      "name": "OrnekMarka Kadın Keten Gömlek, Siyah, L Beden",
      "image": "https://ornekmagaza.com/img/gml-300-siyah.jpg",
      "color": "Siyah",
      "size": "L",
      "offers": {
        "@type": "Offer",
        "url": "https://ornekmagaza.com/urun/keten-gomlek?renk=siyah&beden=l",
        "price": 899.00,
        "priceCurrency": "TRY",
        "availability": "https://schema.org/OutOfStock"
      }
    }
  ]
}
</script>

productGroupID ana ürünün stok kodu. Merchant Center feed'indeki item_group_id değeriyle aynı olması, iki kaynağın aynı ürünü anlattığını Google'a gösteriyor.

variesBy varyantları ayıran özellikleri listeliyor. İzin verilen değerler renk, beden, önerilen yaş, önerilen cinsiyet, malzeme ve desen. Değerler tam Schema.org adresi olarak yazılıyor.

Her varyantın kendi adresi olmalı. Varyantın url değeri, o rengi ve bedeni önceden seçili olarak açan bir adres olmalı. Bu adres açıldığında sayfada doğru görsel, fiyat ve stok durumu görünmeli ve ürün sepete eklenebilmeli.

Varyantlar ayrı sayfalardaysa yapı biraz değişiyor. ProductGroup için tek bir adres olmadığından url alanı kullanılmıyor ve şemanın tamamı her varyant sayfasında tekrar ediliyor.

BreadcrumbList şeması, ürün sayfasının sitedeki konumunu Google'a anlatıyor. Arama sonuçlarında sayfa adresinin yerine bu kategori yolu gösterilebiliyor. En az iki öğe gerekiyor. Son öğe sayfanın kendisi olduğu için item alanı yazılmazsa Google sayfanın adresini kullanıyor:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Bilgisayar",
      "item": "https://ornekmagaza.com/bilgisayar"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Klavye",
      "item": "https://ornekmagaza.com/bilgisayar/klavye"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "OrnekMarka K87 Mekanik Klavye"
    }
  ]
}
</script>

Şemayı ürün verisinden üretmek

Yukarıdaki örneklerin hiçbiri elle yazılmamalı. Elle yazılmış bir şema, fiyat değiştiğinde ya da ürün tükendiğinde eski kalıyor ve sayfa ile şema arasında Merchant Center'ın ürünü reddetmesine yol açan bir uyuşmazlık oluşuyor. Şemayı, sayfayı oluşturan aynı ürün kaydından üretmek bu sorunu baştan çözüyor.

React ya da Next.js kullanan bir projede bunu küçük bir bileşenle yapabilirsiniz:

// UrunSemasi.tsx: ürün kaydından Product şeması üretir
type Urun = {
  ad: string;
  aciklama: string;
  slug: string;
  sku: string;
  barkod?: string;
  marka?: string;
  gorseller: string[];
  fiyatKurus: number;
  eskiFiyatKurus?: number;
  stok: number;
};
 
const SITE = "https://ornekmagaza.com";
 
export function UrunSemasi({ urun }: { urun: Urun }) {
  const indirimde = urun.eskiFiyatKurus !== undefined && urun.eskiFiyatKurus > urun.fiyatKurus;
 
  const sema = {
    "@context": "https://schema.org",
    "@type": "Product",
    name: urun.ad,
    description: urun.aciklama,
    image: urun.gorseller,
    sku: urun.sku,
    ...(urun.barkod && { gtin13: urun.barkod }),
    ...(urun.marka && { brand: { "@type": "Brand", name: urun.marka } }),
    offers: {
      "@type": "Offer",
      url: `${SITE}/urun/${urun.slug}`,
      price: (urun.fiyatKurus / 100).toFixed(2),
      priceCurrency: "TRY",
      availability: urun.stok > 0 ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
      itemCondition: "https://schema.org/NewCondition",
      ...(indirimde && {
        priceSpecification: {
          "@type": "UnitPriceSpecification",
          priceType: "https://schema.org/StrikethroughPrice",
          price: (urun.eskiFiyatKurus! / 100).toFixed(2),
          priceCurrency: "TRY",
        },
      }),
    },
  };
 
  return (
    <script
      type="application/ld+json"
      // "</script>" içeren bir ürün açıklaması etiketi erken kapatmasın
      dangerouslySetInnerHTML={{ __html: JSON.stringify(sema).replace(/</g, "\\u003c") }}
    />
  );
}

Bileşendeki son satır küçük ama önemli. Ürün açıklaması yönetim panelinden girilen bir metin olduğu için, içinde </script> gibi bir ifade geçerse JSON-LD etiketi erken kapanır ve sayfaya istenmeyen bir kod girebilir. < karakterini < olarak yazmak, JSON'un anlamını değiştirmeden bu riski ortadan kaldırıyor.

Eski fiyat yalnızca gerçekten geçerli fiyattan yüksekse şemaya ekleniyor. Barkod ve marka da yalnızca varsa yazılıyor. Böylece veritabanında boş olan bir alan, şemaya boş bir değer olarak girmiyor.

Test etmek

Şemayı yayına almadan önce iki araçla kontrol edebilirsiniz.

Zengin Sonuçlar Testi. Google'ın Zengin Sonuçlar Testi, sayfanın hangi zengin sonuçlara uygun olduğunu ve eksik ya da hatalı alanları gösteriyor. Yayındaki bir adresi ya da doğrudan HTML kodunu test edebiliyorsunuz.

Schema Markup Validator. validator.schema.org şemanın Schema.org sözlüğüne uygun olup olmadığını kontrol ediyor. Google'a özel kuralları değil, genel yapıyı denetliyor.

Yayına aldıktan sonra Search Console'daki Satıcı listelemeleri (Merchant listings) ve Ürün snippet'leri (Product snippets) raporları, sitenizdeki bütün ürün sayfalarındaki hataları ve uyarıları toplu olarak gösteriyor. Yeni bir şablon değişikliğinden sonra bu raporlara bakmak, tek bir sayfayı test ederken gözden kaçan sorunları yakalamanızı sağlıyor.

Alan kuralları ve örnekler 8 Kasım 2026 tarihinde Google Search Central belgelerinden kontrol edildi.

Cantürk Çelikok

Ankara'da freelance full stack yazılım geliştirici. PlusTurk, Nerdesin, Ayda POS ve Songnaria Coffee gibi canlı ürünleri uçtan uca geliştirdi; burada bu projelerden çıkan notları paylaşıyor.

Hakkımda