The Shopify Product Feed API (productFeedCreate plus productFullSync) gives you a structured, channel-aware feed that is tied to a publication and syncs automatically as your catalog changes. A manual CSV or XML feed is simpler to stand up but is a static snapshot — brittle, stale within hours, and entirely manual to refresh. For anything in production, use the API.
This post compares the two approaches the way a developer actually has to decide between them, with runnable GraphQL and a look at why the CSV row you exported this morning is already wrong. For the full catalog model and every related API, start with the pillar Shopify Product Catalog API guide.
Two ways to get catalog data out of Shopify
There are really only two patterns for feeding an external channel — Google Shopping, a Meta catalog, an AI shopping agent — with your Shopify products:
- Export a file. Generate a CSV or XML document of your products and upload it (or host it at a URL the channel fetches on a schedule). This is the classic "product feed" merchants have used for a decade.
- Use the Product Feed API. Have your app call
productFeedCreateto register a feed against a publication, kick off aproductFullSync, and then let Shopify stream incremental updates to the channel as products change.
The file approach is intuitive and requires no app. The API approach requires building (or using) a Shopify app, but it turns a recurring manual chore into a self-maintaining pipeline. Everything below is about which cost you want to pay.
Comparison: Product Feed API vs manual CSV/XML
| Dimension | Product Feed API | Manual CSV / XML feed |
|---|---|---|
| Freshness / sync | Automatic. Incremental updates flow as products change; a full sync re-bases the whole catalog on demand. | Point-in-time snapshot. Stale the moment a price, inventory, or variant changes until you regenerate. |
| Structure | Channel-aware structured objects from Shopify's own data model (title, variants, price, availability, identifiers). | Free-form columns you map by hand; easy to mislabel or omit required fields. |
| Scale | Built for full catalogs; Shopify processes the sync asynchronously server-side. | Large exports get slow and memory-heavy; multi-thousand-SKU files are awkward to build and upload. |
| Maintenance | Self-maintaining once created; webhook tells you when sync finishes. | Ongoing manual or cron-driven regeneration and re-upload forever. |
| Channel awareness | Tied to a Publication, so it respects what is actually published to that channel/market. | No publication awareness — you must filter unpublished/region-specific items yourself. |
| Error handling | Status and the product_feeds/full_sync_finish webhook surface completion and problems. | Errors surface in the destination channel hours later, after the bad file is ingested. |
| Setup effort | Higher: requires a Shopify app, scopes, and a webhook listener. | Lower to start: a script and an upload. The cost arrives later, as drift. |
The short version: a CSV is cheaper on day one and more expensive every day after. The API is the inverse.
The API approach in code
First, create a feed against the publication your channel publishes to. The feed is the durable handle for everything that follows.
mutation CreateProductFeed {
productFeedCreate {
productFeed {
id
status
}
userErrors {
field
message
}
}
}
Then trigger a full sync of the catalog into that feed. This is the operation you run once on setup (and any time you need to re-base the whole catalog).
mutation RunFullSync($id: ID!) {
productFullSync(id: $id) {
userErrors {
field
message
}
}
}
Variables:
{ "id": "gid://shopify/ProductFeed/1234567890" }
productFullSync is asynchronous — Shopify processes the catalog server-side and does not return the synced products inline. To know when the channel is actually ready, subscribe to the product_feeds/full_sync_finish webhook and react to it:
// Express handler for the product_feeds/full_sync_finish webhook
app.post("/webhooks/product-feeds-full-sync-finish", (req, res) => {
// Verify the HMAC header first (omitted here for brevity)
const { product_feed_id, status } = req.body;
if (status === "COMPLETED") {
markChannelReady(product_feed_id);
} else {
flagFeedForRetry(product_feed_id, status);
}
res.sendStatus(200);
});
After the full sync finishes, you do nothing else for the steady state: as products are created, updated, or unpublished, Shopify pushes incremental changes to the feed automatically. The feed stays current because it is wired into the catalog, not copied out of it. For the read queries that back all of this, see the Shopify catalog GraphQL query cookbook.
Why the CSV goes stale
A manual feed is a row of text frozen at export time. A single product line might look like this:
id,title,price,availability,inventory_quantity,link
8123,Merino Wool Beanie,29.00,in stock,42,https://store.com/products/merino-beanie
Now walk through a normal Tuesday:
- 09:00 — You export the file. The beanie costs $29.00 and shows 42 in stock.
- 11:30 — You run a flash sale; the variant drops to $24.00. The file still says $29.00.
- 14:00 — The last unit sells. The file still says
in stock, quantity 42. - 16:00 — Google fetches your hosted file and ingests the wrong price and a sold-out item as available.
Nothing went wrong with your code. The file was simply correct at 09:00 and lying by 11:30. The only fix is to regenerate and re-upload more often — which is just rebuilding a worse version of the incremental sync the API gives you for free. Every static feed degrades the same way; faster-moving catalogs degrade faster.
Why this is really an ads decision
The product feed is the substrate for paid and AI-driven discovery. The same structured data the Product Feed API maintains is what flows into:
- Google Shopping / Merchant Center — titles, prices, availability, and product identifiers that decide whether your items show and at what price.
- Meta catalog ads — Advantage+ catalog and dynamic retargeting read directly from the synced catalog.
- AI shopping agents — assistants like ChatGPT and Perplexity increasingly read product feeds to recommend and compare items.
In every case, a stale or malformed feed silently caps performance: out-of-stock items get clicks you pay for, wrong prices trigger disapprovals, and missing identifiers (GTIN, brand) suppress eligibility. The API approach keeps that surface accurate without anyone remembering to re-upload anything. To read channel feeds you need the read_product_listings scope (alongside read_products); request only what you use, since over-broad scopes draw scrutiny in app review.
If your goal is AI-channel visibility specifically, pair the live feed with feed quality work — structured attributes, rich descriptions, clean identifiers — covered in preparing your product feed for AI agents. And to turn an accurate, auto-syncing catalog into actual campaigns and spend, see the Shopify AI advertising guide.
When a manual feed is still fine
To be fair to CSV: if you have a tiny catalog (a few dozen SKUs), prices and inventory rarely change, and you only feed one undemanding channel, a scheduled CSV export is a reasonable, low-overhead choice. It is also a fine stopgap while you build the proper app. The break-even comes from change frequency and SKU count, not from any single feature — once either rises, the file's staleness becomes the bottleneck.
Bottom line
Use the Product Feed API for anything that matters. productFeedCreate plus productFullSync and the product_feeds/full_sync_finish webhook give you a structured, channel-aware feed that stays current on its own — which is exactly what Google Shopping, Meta catalogs, and AI shopping agents need to spend your budget efficiently. A manual CSV or XML feed is faster to start and slower to trust; reach for it only for small, static catalogs or as a temporary bridge. The extra setup cost of the API buys you a feed that is correct every hour, not just the hour you exported it.