Meta Catalog
Layer 2 destination model for the Meta (Facebook/Instagram) product catalog. Thin projection from product_catalogue_canonical with Meta feed field names, custom_label product sets, and Shopify-native merch dedup.
product_catalogue_meta projects product_catalogue_canonical into the
Meta product catalog feed
format. It is the metadata counterpart to the
Facebook CAPI conversion
model: the catalogue id here is the same value the CAPI event sends as
content_ids.
Meta is the reference implementation — TikTok and Reddit feeds are close variants of this field set.
How the canonical catalogue maps to Meta
| Layer 1 column | Meta feed field | Notes |
|---|---|---|
item_id |
id |
Must match content_ids on the pixel/CAPI event |
title |
title |
≤ 200 chars |
description |
description |
|
availability |
availability |
in stock / out of stock / preorder |
condition |
condition |
new / refurbished / used |
price |
price |
19.99 USD format (amount + ISO currency) |
link |
link |
Product/landing URL |
image_link |
image_link |
≥ 500×500 |
brand |
brand |
|
custom_label_0 |
custom_label_0 |
= group_id — the product-set key |
custom_label_1..4 |
custom_label_1..4 |
optional segmentation |
Template
{{ config(materialized='view', tags=['meta', 'catalogue', 'outputs']) }}
SELECT
item_id as id,
title,
description,
availability,
condition,
-- Meta wants "<amount> <currency>" as a single string
CAST(price AS VARCHAR) || ' ' || currency as price,
link,
image_link,
brand,
custom_label_0,
custom_label_1,
custom_label_2,
custom_label_3,
custom_label_4
FROM {{ ref('product_catalogue_canonical') }}
-- Merch already syncs Shopify -> Meta natively; don't double-list it.
WHERE item_type <> 'merch'
Design notes
id ↔ content_ids
Meta matches a catalogue row to an event by comparing the feed id to the
event's content_ids. The CAPI model
(marketing_conversions_facebook)
must emit the same value. Because both derive from the canonical item_id, they
agree by construction — keep it that way and never let the two pipelines compute
the id differently.
Product sets ride on custom_label_0
Targeting in Meta uses product sets (a saved filter on catalogue fields),
never a raw pixel parameter. A per-group product set is a single filter,
custom_label_0 = '<group_id>'. Because the group is itself a catalogue row
(id = group_id), that set unites the group row and all its child rows —
enabling group-level DPA (e.g. retarget everyone who engaged with a title, then
show them anything tied to that title).
Shopify-native merch dedup
If merch already syncs Shopify → Meta natively, those products are already
catalogue rows. Exclude them from this feed (WHERE item_type <> 'merch') so you
don't create duplicates. Use this model for the rows Shopify doesn't cover
(tickets, VOD, trailers, titles/groups). Confirm the native sync's id matches
your canonical item_id, or the conversion join breaks for merch.
product_name on events is not used here
Meta renders DPA creative from the catalogue row, never from an event's product name field. Nothing downstream should depend on the event's name — the catalogue is the source of creative truth.
Coverage
Meta drops unmatched content_ids silently. Enforce the
coverage check against
marketing_conversions_canonical so every id that can appear on an event exists
here before go-live.
Transport
Push the Layer 2 table to the Catalog Batch API via reverse ETL, or publish it as a scheduled hosted feed. Refresh ahead of new items becoming purchasable.