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.
Prerequisites
| Requirement | Details |
|---|---|
| Plugin | Product Feed Pro, version 13.5.7 or later |
| Where to add code | A custom (site-specific) plugin, or your active theme’s functions.php if you’re not using a child theme |
| Skills needed | Comfortable 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;
}
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
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.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.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.

