smplx.
Shopify Architektur

Datenmodellierung in Shopify: Metafields, Metaobjects und Custom Data [2026]

Claudio Gerlich··12 min

Im Shopify-Architektur-Ratgeber besprechen wir die Grundlagen. Heute schauen wir uns an, wie du daten in Shopify modellierst – das Fundament jeder guten Architektur.

Bei smplx. haben wir mit verschiedenen Ansätzen experimentiert. Metafields? Metaobjects? Custom Apps mit separaten Datenbanken? Jede Option hat ihren Platz. Lass uns gemeinsam die richtige Entscheidung treffen.

Das Shopify-Datenmodell

Shopify hat ein starres, aber intelligentes Datenmodell:

Shop
├── Products
│   ├── Variants
│   └── Metafields (seit 2021)
├── Collections
│   └── Metafields
├── Customers
│   └── Metafields
├── Orders
│   └── Metafields
└── Metaobjects (seit 2024)
    └── Fields & Relationships

Die Standard-Entitäten

Products sind das Herzstück:

  • Title, Description, Vendor
  • Published Status
  • Collections (viele-zu-viele Beziehung)
  • Variants (eins-zu-viele Beziehung)

Variants sind die SKUs:

  • Price, SKU, Barcode
  • Inventory
  • Options (Size, Color, etc.)

Collections sind Kategorien:

  • Title, Description
  • Produkte (viele-zu-viele)

Aber was machst du, wenn du mehr brauchst? Das ist, wo Metafields einsteigen.

Metafields: Custom Daten anhängen

Was sind Metafields?

Metafields sind Key-Value Paare, die du an jede Standard-Entität anhängen kannst.

Beispiel: Produkt mit zusätzliche Eigenschaften

query GetProduct($id: ID!) {
  product(id: $id) {
    id
    title
    metafields(first: 10) {
      edges {
        node {
          id
          key
          value
          type
        }
      }
    }
  }
}

Metafield-Typen

Shopify bietet vorgefertigte Typen:

Typ Beispiel Nutzung
single_line_text "Baumwolle" Material
multi_line_text "Langbeschreibung" Detaillierte Info
number_integer "100" Menge
number_decimal "19.99" Preis-Addon
date "2026-02-19" Verfügbarkeitsdatum
date_time "2026-02-19T10:30" Zeitstempel
boolean true/false Ja/Nein Flags
json {...} Beliebige Struktur
list.single_line_text ["rot", "blau"] Array von Text
list.number_integer [10, 20] Array von Numbers
url "https://..." Links
file_reference File ID Datei-Zugang

Metafield-Beispiele

Beispiel 1: Material-Info für Produkt

mutation CreateProductMetafield($id: ID!, $metafield: MetafieldsSetInput!) {
  metafieldsSet(ownerId: $id, metafields: $metafield) {
    metafields {
      id
      key
      value
    }
  }
}

Input:

{
  "id": "gid://shopify/Product/123",
  "metafield": {
    "key": "material_composition",
    "value": "{\"cotton\": 85, \"polyester\": 15}",
    "type": "json"
  }
}

Beispiel 2: Lagerbestands-Warnung

{
  "key": "low_stock_threshold",
  "value": "10",
  "type": "number_integer"
}

Metafields im Theme (Liquid)

<!-- Produkt-Material anzeigen -->
<p>
  Material: {{ product.metafields.custom.material_composition.value }}
</p>

<!-- Mit Fallback -->
{% if product.metafields.custom.material_composition %}
  <p>{{ product.metafields.custom.material_composition.value }}</p>
{% else %}
  <p>Material nicht spezifiziert</p>
{% endif %}

<!-- JSON parsen -->
{% assign material = product.metafields.custom.material_composition.value | parse_json %}
Baumwolle: {{ material.cotton }}%

Metaobjects: Strukturierte Custom Data

Was sind Metaobjects?

Metaobjects sind völlig custom Datenmodelle, die du selbst definierst. Sie sind wie "Custom Tables in Shopify".

Metaobjects vs. Metafields

Aspekt Metafields Metaobjects
Anhängsel Ja (an Produkt, Order, etc.) Nein (standalone)
Struktur Key-Value Paare Vollständiges Datenmodell
Relationships Begrenzt Ja, bidirektional
Komplexität Einfache Daten Komplexe Daten
Abfragen Per Entität GraphQL wie Standard-Entitäten

Metaobject-Beispiel: Produktbundle

# Metaobject Definition (via Admin)
# Typ: "product_bundle"
# Felder:
# - bundle_name (single_line_text)
# - bundle_description (multi_line_text)
# - bundle_price (number_decimal)
# - products (list.product_reference)
# - discount_percentage (number_integer)

In GraphQL abfragen:

query GetBundle($id: ID!) {
  metaobject(id: $id) {
    id
    handle
    fields {
      key
      value
    }
  }
}

Resultat:

{
  "metaobject": {
    "id": "gid://shopify/Metaobject/abc123",
    "handle": "summer-bundle",
    "fields": [
      {
        "key": "bundle_name",
        "value": "Summer Bundle"
      },
      {
        "key": "bundle_price",
        "value": "99.99"
      },
      {
        "key": "products",
        "value": ["gid://shopify/Product/123", "gid://shopify/Product/456"]
      }
    ]
  }
}

Relationships: Metaobjects verbinden

Das Neue in 2024: Metaobjects können sich gegenseitig referenzieren.

Beispiel: Brand ↔ Product

Metaobject "brand":

Fields:
- brand_name (text)
- brand_logo (file_reference)
- brand_description (text)
- featured_products (list.product_reference) ← Relationship!

Im Liquid:

{% if product.metafields.custom.brand %}
  {% assign brand = product.metafields.custom.brand.value %}
  <p>Brand: {{ brand.brand_name }}</p>
  <img src="{{ brand.brand_logo.url }}" alt="">
{% endif %}

Bidirektionale Relationships

query {
  products(first: 5) {
    edges {
      node {
        id
        title
        # Zurück zum Brand (wenn definiert)
        brand: metafield(namespace: "custom", key: "brand") {
          value
        }
      }
    }
  }

  # Umgekehrt
  metaobjects(type: "brand", first: 5) {
    edges {
      node {
        id
        fields {
          value  # Diese Products referenzieren mich
        }
      }
    }
  }
}

Real-World: Bekateq Case Study

Bekateq brauchte ein komplexes Datenmodell:

  • 4400+ Zeilen Code
  • 24 verschiedene Collection Templates
  • Custom Attribute für jeden Product-Typ
  • Null externe Dependencies

Ihre Lösung: Metaobjects + Metafields

Das Datenmodell

Product
├── Metafields
│   ├── technical_specs (json)
│   ├── available_colors (list.text)
│   └── warranty_period (number_integer)
└── Relationships via Metafield
    └── Documentation Metaobject
        ├── manual_pdf
        ├── installation_guide
        └── faq_entries

Brand (Metaobject)
├── brand_name
├── brand_logo
├── products (list.product_reference)
└── certifications (list.text)

Category (Metaobject)
├── category_name
├── parent_category (category_reference)
└── featured_products (list.product_reference)

Warum das funktioniert: Bekateq brauchte keine 5 Custom Apps. Sie brauchten ein sauberes Datenmodell.

Metafields oder Metaobjects? Entscheidungsmatrix

Szenario Lösung
Zusätzliche Produkt-Info (z.B. Material) Metafield
Viele zusätzliche Felder an Produkt Metaobject (dann referenzieren)
Separate "Tabelle" mit eigner Entität Metaobject
Beziehung zwischen Produkten Metaobject mit Relationships
Kunden-Daten erweitern Metafield am Customer
Komplexe Geschäftslogik-Daten Externe DB + Custom App

GraphQL für Custom Data Queries

Metafields abfragen

query GetProductsWithMaterial {
  products(first: 10, query: "has_metafield:custom.material_composition") {
    edges {
      node {
        id
        title
        metafields(namespace: "custom", first: 10) {
          edges {
            node {
              key
              value
              type
            }
          }
        }
      }
    }
  }
}

Metaobjects mit Filtering

query GetBundles {
  metaobjects(type: "product_bundle", first: 10) {
    edges {
      node {
        id
        fields {
          key
          value
        }
      }
    }
  }
}

Komplexe Queries: Produkt + Metaobject + Relationships

query {
  product(id: "gid://shopify/Product/123") {
    id
    title
    variants(first: 5) {
      edges {
        node {
          id
          price
          sku
        }
      }
    }
    # Metafield-Reference zu Brand
    brand: metafield(namespace: "custom", key: "brand") {
      reference {
        ... on Metaobject {
          id
          fields {
            key
            value
          }
          # Back-Reference
          # products (würde funktionieren wenn bidirektional definiert)
        }
      }
    }
  }
}

Praktische Architektur-Entscheidungen

Szenario 1: Product Bundles

Option A: Metafield auf Produkt

{
  "key": "bundle_products",
  "value": "[\"gid://shopify/Product/1\", \"gid://shopify/Product/2\"]",
  "type": "list.product_reference"
}

Problem: Wenn Bundle-Preis ändert, musst du alle verknüpften Produkte updaten.

Option B: Metaobject "Bundle"

Bundle (Metaobject)
├── bundle_name
├── bundle_price
├── bundled_products (list.product_reference)
└── discount_amount

Vorteil: Bundle existiert unabhängig. Klare Verantwortlichkeit.

Unsere Empfehlung: Option B (Metaobject)

Szenario 2: Technische Spezifikationen

Option A: Viele einzelne Metafields

- power_consumption
- operating_temperature
- noise_level
- warranty_months

Problem: Für jede neue Spec brauchst du ein neues Metafield. Unflexibel.

Option B: Ein JSON Metafield

{
  "key": "technical_specs",
  "value": "{\"power\": 100, \"temp_min\": -20, \"temp_max\": 50, \"noise\": 65}",
  "type": "json"
}

Vorteil: Flexibel. Neue Specs ohne Schema-Änderung.

Unsere Empfehlung: Option B (JSON Metafield)

Limits und Workarounds

Limit 1: Kein Zugriff auf externe Daten-Quellen

Problem: Du brauchst Shopify-Daten + ERP-Daten kombiniert in GraphQL.

Lösung: Custom App als Middleware

// Deine Custom App
app.post('/api/product-with-erp', async (req, res) => {
  const { productId } = req.body;

  // 1. Shopify Daten
  const shopifyProduct = await shopify.graphql(`
    query { product(id: "${productId}") { ... } }
  `);

  // 2. ERP Daten
  const erpData = await erp.getProduct(shopifyProduct.sku);

  // 3. Kombinieren
  res.json({
    ...shopifyProduct,
    erpInventory: erpData.inventory,
    erpCost: erpData.cost
  });
});

Limit 2: Performance bei vielen Metafields

Problem: Ein Produkt mit 50 Metafields = langsam zu laden

Lösung: Strategisch Metaobjects nutzen

// Statt:
// Product {
//   metafield_1, metafield_2, ..., metafield_50
// }

// Besser:
Product {
  technical_specs: Metaobject,  // 30 Specs gebündelt
  marketing_data: Metaobject,   // 15 Marketing-Felder
  inventory_settings: Metaobject // 5 Inventory-Felder
}

Limit 3: Keine Transaktionen über Entitäten

Problem: Du brauchst atomare Updates über Produkt + Metaobject.

Lösung: Custom App mit Webhook-Handling

// Wenn Webhook "order.create" kommt
// 1. Update Shopify Product Inventory
// 2. Update Custom Metaobject "sales_stats"
// Mit Retry-Logic falls eines fehlschlägt

try {
  await updateProductInventory(orderId);
  await updateSalesStats(orderId);
} catch (error) {
  // Retry oder Rollback
  await logError(orderId, error);
}

Best Practices

1. Namespace-Konvention

<!-- ✅ RICHTIG: Namespace für Organisation -->
product.metafields.custom.material_composition
product.metafields.marketing.hero_text
product.metafields.technical.power_rating

<!-- ❌ FALSCH: Alles in einem -->
product.metafields.custom.everything

2. Typen korrekt setzen

// ✅ RICHTIG
{
  "key": "quantity",
  "value": "100",
  "type": "number_integer"
}

// ❌ FALSCH
{
  "key": "quantity",
  "value": "\"100\"",  // String statt Integer
  "type": "number_integer"
}

3. Relationships statt komplexe JSONs

// ❌ FALSCH: Stark gekoppelt
{
  "key": "related_products",
  "value": "[{\"id\": \"123\", \"name\": \"Product\"}, ...]",
  "type": "json"
}

// ✅ RICHTIG: Loose Coupling
{
  "key": "related_products",
  "value": "[\"gid://shopify/Product/123\", ...]",
  "type": "list.product_reference"
}

4. Versionierung bei Changes

// Wenn du Metafield-Struktur änderst
// v1: { material: "cotton" }
// v2: { material: "cotton", origin_country: "India" }

// Versioniere!
product.metafields.custom.material_data_v2
// Alte Clients nutzen v1, neue v2

Checkliste: Datenmodell-Design

  • Habe ich die Standard-Felder (Products, Collections, etc.) genutzt?
  • Sind Metafields das richtige für meine Custom Daten?
  • Sollte ich stattdessen Metaobjects nutzen?
  • Sind Relationships definiert?
  • Habe ich Typen korrekt gewählt?
  • Gibt es Limits, die ich überschreite?
  • Brauchst du externe Daten (dann: Custom App)?
  • Sind Namespace-Konventionen konsistent?
  • Habe ich Performance-Implikationen erwogen?

Über den Autor

Claudio Gerlich ist Technical Architect bei smplx. und seit 2020 spezialisiert auf Shopify-Datenmodellierung. Mit Bekateq haben wir ein komplexes Datenmodell mit Metaobjects elegant gelöst – Zero External Dependencies, Pure Shopify.

Datenmodellierung ist das Fundament. Gute Modelle = einfache Features. Schlechte Modelle = Chaos.

smplx. ist Shopify Technical Partner (seit 2020) mit Sitz in Coesfeld, NRW.