An Export CSV button to export filtered post data.
An example of filtered post data exported as CSV and imported in an Excel sheet.
An example of filtered post data exported as CSV and imported in an Excel sheet.

This tutorial shows how to create an “Export CSV” button that exports post data of the resulting posts before or after using facets.

The exported (and automatically downloaded) CSV file can then be used to import the data into Excel or other programs.

The code below adds all functionality needed to do this. The button will export the specified post data of all posts in the listing before or after facet filtering. It works for logged-in and logged-out users. Add the code to your (child) theme’s functions.php, a code snippets plugin, or in the Custom Hooks add-on.

The code consists of five parts:

Part 1 adds an “Export CSV” button above the listing template (the element with class facetwp-template). The button will have class fwp-export-btn, as set in line 18 (and checked in line 16). The class can be used to style the button, in lines 33-38. The button text will be “Export CSV”, as set in line 19.

Part 2 stores the (filtered) post IDs in a transient. The transient needs a $session_id, which we get with the fwp_get_export_session_id() function, which is Part 4.

For logged-in users, this $session_id is generated in line 73, with wp_get_session_token(), which gets the current session token from the logged_in cookie. For logged-out users, the $session_id is taken from a cookie fwp_export_session, which is set in Part 3.

The facetwp_filtered_post_ids hook that we use in Part 2 to get all filtered post IDs, needs a condition to make it run only on the intended listing template or page. In this example, we are using $class->ajax_params[‘template’] to check for the name of a Listing Builder listing template. So if you are using such a listing template, make sure to replace my_template in line 49 with its name. If you are using any other listing template type, you can use $class->http_params['uri'] instead, to check against the page URI (the part of the URL without the domain name and the query variables, and without beginning or ending slashes). For examples of this, see the facetwp_filtered_post_ids hook page.

Part 5 handles the export to CSV. In this example, clicking the button will export a CSV with four columns: the post ID, post title, post date, and an Advanced Custom Fields custom field (which we get with ACF’s get_field() function). You can customize what post data is exported in the CSV, by adapting lines 130-134. Each of these columns in the CSV needs a heading, which can be set in lines 122-125.

If the export button should be available only for logged-in users, you could use an if ( is_user_logged_in() ) condition within the facetwp_scripts hook, surrounding all code within it. In that case you can remove Part 3 entirely, and also the cookie fallback in lines 77-81, and the wp_ajax_nopriv_fwp_export_csv action hook in line 86.

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

<?php // Export filtered post data to a CSV // Part 1 - Add the "Export CSV" button above the listing template add_action( 'facetwp_scripts', function() { ?> <script> var fwp_export = { nonce: '<?php echo wp_create_nonce( 'fwp_export_csv' ); ?>', ajaxurl: '<?php echo admin_url( 'admin-ajax.php' ); ?>' }; </script> <script> (function() { document.addEventListener('facetwp-loaded', function() { // Only inject the button once if (!document.querySelector('.fwp-export-btn')) { var btn = document.createElement('button'); btn.className = 'fwp-export-btn'; btn.textContent = 'Export CSV'; document.querySelector('.facetwp-template').insertAdjacentElement('beforebegin', btn); btn.addEventListener('click', function() { var params = new URLSearchParams(); params.set('action', 'fwp_export_csv'); params.set('nonce', fwp_export.nonce); window.location.href = fwp_export.ajaxurl + '?' + params.toString(); }); } }); })(); </script> <style> /* Style your Export CSV button */ .fwp-export-btn { margin: 20px 0; } </style> <?php }, 100 ); // Part 2 - Store the filtered post IDs in a transient add_filter( 'facetwp_filtered_post_ids', function( $post_ids, $class ) { // Only store the transient on the relevant listing. // Use $class->ajax_params['template'] to check for a FacetWP Listing Builder listing template. // Or use $class->http_params['uri'] to check the URI if it is another listing template type // For examples, see: https://facetwp.com/help-center/developers/hooks/querying-hooks/facetwp_filtered_post_ids/#usage-examples if ( 'my_template' !== $class->ajax_params['template'] ) { // Change "my_template" to the name of your Listing Builder listing template return $post_ids; } $session_id = fwp_get_export_session_id(); if ( empty( $session_id ) ) { return $post_ids; } set_transient( 'fwp_export_ids_' . $session_id, $post_ids, 5 * MINUTE_IN_SECONDS ); return $post_ids; }, 10, 2 ); // Part 3 - Set cookie instead of session ID for logged-out users // Remove this part if the button is admin-only add_action( 'init', function() { if ( ! is_user_logged_in() && empty( $_COOKIE['fwp_export_session'] ) ) { $token = wp_generate_password( 12, false ); setcookie( 'fwp_export_session', $token, time() + 3600, COOKIEPATH, COOKIE_DOMAIN ); $_COOKIE['fwp_export_session'] = $token; // Make it available in the same request } } ); // Part 4 - Get the session ID from the transient (when logged in) or cookie (when logged out) function fwp_get_export_session_id() { $token = wp_get_session_token(); if ( ! empty( $token ) ) { return $token; } // Cookie fallback for logged-out users // Remove these lines if the button is admin-only return ! empty( $_COOKIE['fwp_export_session'] ) ? sanitize_key( $_COOKIE['fwp_export_session'] ) : ''; } // Part 5 - Handle CSV export add_action( 'wp_ajax_fwp_export_csv', 'fwp_handle_export_csv' ); add_action( 'wp_ajax_nopriv_fwp_export_csv', 'fwp_handle_export_csv' ); // Only for logged-out users. Remove if the button is admin-only. function fwp_handle_export_csv() { ob_start(); // Catch any accidental output check_ajax_referer( 'fwp_export_csv', 'nonce' ); $session_id = fwp_get_export_session_id(); if ( empty( $session_id ) ) { wp_die( 'Session not found.' ); } $post_ids = get_transient( 'fwp_export_ids_' . $session_id ); if ( empty( $post_ids ) ) { wp_die( 'No results to export.' ); } $query = new WP_Query( [ 'post__in' => $post_ids, 'orderby' => 'post__in', 'posts_per_page' => - 1, 'post_type' => 'any', ] ); ob_end_clean(); // Discard any buffered output before sending headers header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="export.csv"' ); $out = fopen( 'php://output', 'w' ); if ( ! $out ) { wp_die( 'Could not open output stream.' ); } // Add CSV column headings for each post data output below fputcsv( $out, [ 'Post ID', 'Post Title', 'Post Date', 'My field' ] ); foreach ( $query->posts as $post ) { fputcsv( $out, [ $post->ID, $post->post_title, $post->post_date, get_field( 'my_custom_field', $post->ID ), // example ACF field //.. add any other data you want to export ] ); } fclose( $out ); exit; }

See also

Last updated: May 6, 2026