Get Started
How to modify feed writer output with filters - AdTribes
  1. Home
  2. Knowledge Base
  3. Customization
  4. How to modify feed writer output with filters

How to modify feed writer output with filters

This is a developer reference. It assumes you’re comfortable adding code through a custom plugin or your theme functions.php. If you’re new to WordPress hooks, read the WordPress Plugin Handbook on hooks first.

The Field Mapping tab covers most feed customization needs, but sometimes you need to change something it can’t reach: wrap a value in a custom XML node, restructure a CSV column that isn’t exposed as an attribute, or rewrite the header row for a downstream system. Product Feed Pro fires a set of filter hooks inside its XML, CSV, and TSV writers for exactly this. Hook into them from a custom plugin, and you can change what actually gets written to the feed file, without touching the plugin core.

This article covers the writer-level filters and worked code examples for each one. For the plugin’s full hook catalog, including hooks outside the feed writers, see Available hooks and filters in Product Feed Pro.

These are PHP filter hooks, not the same thing as the plugin’s Filters tab. The Filters tab lets you include or exclude products from a feed using conditions in the UI. For that feature, see How to create filters for your product feed.

Prerequisites

RequirementDetails
PluginProduct Feed Pro, version 13.5.7 or later
Where to add codeA custom (site-specific) plugin, or your active theme’s functions.php if you’re not using a child theme
Skills neededComfortable writing PHP and registering WordPress filter hooks

How the feed writer works

Product Feed Pro uses one writer for XML feeds and a shared writer for CSV and TSV feeds (TSV is the same code path as CSV, just with a tab character as the delimiter). During feed generation, the plugin queries a batch of products, builds each product’s data, then hands that data to the writer for the feed’s format. The filters below sit inside that write step: they run once per product, or once per feed generation run for the header and channel-link hooks. Because they fire during writer output, changes made here apply only to the format they target. A filter on the XML writer does not affect a CSV feed, and the reverse is also true.

XML feed filters

adt_product_feed_xml_attribute_value: Filters the value of a single XML attribute right before it’s written into the product’s XML node. Use this to change one attribute’s output for XML feeds without touching any other attribute or format.

add_filter( 'adt_product_feed_xml_attribute_value', 'my_custom_xml_attribute_value', 10, 5 );
function my_custom_xml_attribute_value( $value, $key, $product, $product_data, $feed ) {
    if ( 'title' === $key ) {
        $value = strtoupper( $value );
    }
    return $value;
}

$key is the attribute key being written (for example title), $product is the SimpleXMLElement node being built, and $product_data is the full product data array for that row. Return the value you want written for that attribute.

adt_product_feed_xml_attribute_handling: A short-circuit filter that lets you take over how one attribute is written to XML entirely, instead of just changing its value. Return true to tell the plugin to skip its own handling for that attribute. Your callback is then responsible for writing the node onto $product itself.

add_filter( 'adt_product_feed_xml_attribute_handling', 'my_custom_xml_attribute_handling', 10, 7 );
function my_custom_xml_attribute_handling( $handled, $product, $key, $value, $feed_config, $channel_attributes, $feed ) {
    if ( 'custom_label' !== $key ) {
        return $handled;
    }
    $product->addChild( 'g:custom_label_0', esc_html( $value ) );
    return true;
}

If you return true from adt_product_feed_xml_attribute_handling but don’t write anything onto $product, that attribute is silently dropped from the feed. Only return true once your callback actually adds the node.

adt_pfp_google_shopping_feed_channel_link and adt_pfp_feed_channel_link: Filter the channel-level URL written into the feed’s header. The first applies to feeds on the Google Shopping taxonomy, where it sets <link> inside <channel>. The second applies to Yandex, Zap.co.il, Salidzini.lv and Pinterest RSS Board, and the element differs by format: Yandex writes <url> inside <shop>, Zap.co.il and Salidzini.lv write <link> at the document root, and Pinterest RSS Board writes <link> inside <channel>. Both receive the site’s home URL and the feed being generated, and both must return a URL string.

add_filter( 'adt_pfp_feed_channel_link', 'my_custom_feed_channel_link', 10, 2 );
function my_custom_feed_channel_link( $link, $feed ) {
    return trailingslashit( $link ) . 'shop/';
}

CSV and TSV feed filters

adt_product_feed_csv_header: Filters the header row string right before it’s written as the first line of a CSV or TSV feed. Fires once, on the first batch of a feed generation run.

add_filter( 'adt_product_feed_csv_header', 'my_custom_csv_header', 10, 3 );
function my_custom_csv_header( $header, $feed_attributes, $feed ) {
    return str_replace( "'id'", "'sku'", $header );
}

adt_product_feed_csv_row_data: Filters the array of cell values that make up a single CSV or TSV row, right before it’s written to the feed file. This is the filter to reach for when you need to reshape or recalculate a value that only applies to CSV/TSV output.

add_filter( 'adt_product_feed_csv_row_data', 'my_custom_csv_row_data', 10, 4 );
function my_custom_csv_row_data( $pieces_row, $old_attributes_config, $product_data, $feed ) {
    foreach ( $pieces_row as $index => $value ) {
        $pieces_row[ $index ] = trim( $value );
    }
    return $pieces_row;
}

$pieces_row is a plain array of the row’s cell values in column order, matching the header row from adt_product_feed_csv_header. Because TSV shares this same code path, a callback added here runs for both formats. If you need format-specific behavior, check the feed’s file format on $feed inside your callback.

The empty-batch fix

Versions of Product Feed Pro before 13.5.5 had a bug where a CSV feed with an empty batch (a generation run that returned zero products) could hit XML validation logic and abort with a “not valid XML” error, even though the feed was never meant to produce XML. The empty batch failed the CSV branch’s has-products check and fell through to the XML branch, which then tried to write and validate the CSV feed as XML. Version 13.5.5 restricted that XML branch to feeds whose format is actually XML, so an empty CSV or TSV batch now writes nothing and can no longer be routed through XML-only validation. If you’re on 13.5.5 or later, this can’t recur. If you’re on an earlier version, update the plugin before relying on any of the filters in this article.

Testing your changes safely

  • Use a staging site first. Test writer-level filters somewhere a broken feed won’t affect your live product listings or ad accounts.
  • Test with a small batch. Filter down to a single category, so you can inspect the output quickly instead of waiting on a full catalog run.
  • Regenerate the feed manually from the Manage feeds page after adding or changing a filter, then open the raw feed file to confirm your change appears as expected.
  • Check for filter conflicts. If more than one plugin or snippet hooks the same filter, they run in registration order (or by priority, if you’ve set one). Log the incoming and outgoing values in your callback while testing to confirm nothing upstream is overwriting your change.
  • Re-test after a plugin update. Hook names are part of the plugin’s public API, and AdTribes avoids renaming them without notice, but it’s still good practice to confirm your customizations still work after updating.

FAQ

Where do I add this code?
Add it to a custom (site-specific) plugin, or your active theme’s functions.php if you’re not using a child theme. Don’t edit Product Feed Pro’s own files directly, since any changes there are lost on the next plugin update.
Will my filter survive a plugin update?
Yes, as long as you add it through a custom plugin or your theme, and as long as the hook name stays the same. AdTribes treats hook names as part of the plugin’s public API. Still, test on staging after any update.
Can I add a brand-new attribute to the feed this way?
Not directly with these writer filters. To add a new attribute available for field mapping, use adt_product_feed_attributes or adt_product_feed_custom_attributes, covered in Available hooks and filters in Product Feed Pro. Once an attribute is mapped, the writer filters in this article let you change how its value is written to the file.
Does this work for JSONL feeds?
No. JSONL and JSONL.GZ feeds use a separate writer with their own filter, adt_product_feed_jsonl_product. That’s out of scope for this article, but it’s listed in the hooks reference above.

Need more help?

If you’re on Product Feed Elite or another premium AdTribes plugin and need help with a specific customization, open a support ticket, and the team can point you to the right hook for your use case.

If you’re using the free Product Feed Pro plugin, ask your question on the WordPress.org support forum.

Was this article helpful?

Related Articles

Complete Your Purchase
AdTribes WooCommerce Product Feed

The best WooCommerce product feed plugin

  • AdTribes Pty Ltd
    ABN: 40 675 636 816
Product
Resources & Info
Partner Sites
Rymera