# Vehicle Fitment Table — `custom.fitment` Metafield

## What it is

The `custom.fitment` Shopify metafield stores a JSON array of fitment rows for
a product. The storefront reads this JSON and renders it as an HTML table showing
customers which vehicle variants, engine types, and date ranges the part fits.

This is **separate** from the category-based vehicle tagging system (`l1_` tags /
`custom.part_category`). The fitment table provides detailed compatibility data;
the tags handle collection membership.

---

## Data source

**DB column**: `products_products.vehicle_fitment` (JSON, cast as `array` in model)

**Example value**:
```json
[
  {"vehicle": "DEFENDER 1986 - 2006", "engine": "All", "notes": "Tdi Diesel only", "break_from": "", "break_to": ""},
  {"vehicle": "RANGE ROVER CLASSIC",  "engine": "All", "notes": "",                "break_from": "", "break_to": ""}
]
```

Each row in the array represents one compatibility entry. Fields:

| Field        | Description                                      |
|--------------|--------------------------------------------------|
| `vehicle`    | Vehicle name string (free text)                  |
| `engine`     | Engine variant, or "All"                         |
| `notes`      | Any additional notes (e.g. "Tdi Diesel only")    |
| `break_from` | Year range start (if applicable)                 |
| `break_to`   | Year range end (if applicable)                   |

---

## How it gets to Shopify

**Code path**: `SyncProductToChannels` → `ShopifyClient::pushFitmentMetafield()`

```php
// SyncProductToChannels.php (lines ~323-344)
if ($shopifyProductId) {
    $rawFitment = DB::table('products_products')
        ->where('id', $this->productId)
        ->value('vehicle_fitment');

    if ($rawFitment) {
        $fitmentRows = is_array($rawFitment)
            ? $rawFitment
            : json_decode($rawFitment, true);

        if (! empty($fitmentRows)) {
            $driver->pushFitmentMetafield($shopifyProductId, $fitmentRows);
        }
    }
}
```

Only fires if `vehicle_fitment` is non-null and non-empty. Products without
fitment data (the majority) skip this step silently.

**ShopifyClient method**: `pushFitmentMetafield(string $shopifyProductId, array $fitmentData): bool`
- Located: `plugins/webkul/channels/src/Services/ShopifyClient.php`
- Calls `ensureFitmentMetafieldDefinition()` first (auto-creates definition if missing, cached 7 days)
- Uses GraphQL `metafieldsSet` mutation
- Metafield type: `json`
- Namespace/key: `custom` / `fitment`

---

## Metafield definition auto-creation

`ensureFitmentMetafieldDefinition()` runs before every push and creates the
Shopify metafield definition if it doesn't exist:

```
namespace: custom
key:       fitment
type:      json
ownerType: PRODUCT
```

Cache key: `shopify_fitment_defn_{md5(shopDomain)}` — TTL 7 days.
If the definition is deleted from Shopify, it will be recreated within 7 days
on the next sync of any product that has `vehicle_fitment` data.

---

## Current data status

As of June 2026, only **1 product** has `vehicle_fitment` data: `AEU2147L` (test entry).
The vast majority of products have `NULL` in this column and never trigger a fitment push.

The column is not populated by any automated process currently — it would need to be
filled either by a manual import, an API integration, or a future scraper.

---

## Relationship to the category tag system

These are two **separate, non-conflicting** fitment approaches:

| | `custom.fitment` (JSON) | `l1_` tags + `custom.vehicle_fitment`* |
|---|---|---|
| Purpose | Detailed fitment table on product page | Collection membership + Search & Discovery filter |
| Source | `products.vehicle_fitment` DB column | Category tree (`products_product_categories` pivot) |
| Format | JSON array with engine/notes detail | Simple vehicle name list |
| Currently used | Rarely (1 product) | All categorised products |

*`custom.vehicle_fitment` was removed June 2026 — vehicle collection membership
is handled by `l1_` tags only. `custom.part_category` and `custom.part_subcategory`
are the two active Search & Discovery filter metafields.

---

## Frontend usage

The storefront theme reads `product.metafields.custom.fitment` and renders the
JSON as an HTML compatibility table. The exact template is in the Shopify theme
(not in Cydekick). If the metafield is null/empty, the table section is hidden.

---

## If you want to populate fitment data for more products

Options:
1. **Manual entry** — set `vehicle_fitment` JSON directly on the product record
2. **Import** — add a column to the product CSV importer
3. **PSP scraper** — extend `ScrapeAllmakesPsp` to write fitment rows to this column
   (the scraper currently updates `cost` and supplier `price` but not fitment)

After writing to the DB column, trigger a sync (`SyncProductToChannels::dispatch($productId)`)
and the fitment data will be pushed to Shopify automatically.
