Skip to content

Instantly share code, notes, and snippets.

@westcoastdigital
Created September 2, 2025 01:52
Show Gist options
  • Save westcoastdigital/dd58040befba8c0a109a7ad6081c42a5 to your computer and use it in GitHub Desktop.
Save westcoastdigital/dd58040befba8c0a109a7ad6081c42a5 to your computer and use it in GitHub Desktop.
This plugin adds functionality to export Gravity Forms entries and then import into another site. Ensures no duplicates
<?php
/**
* Plugin Name: Gravity Forms Entry Transfer
* Plugin URI: https://jonmather.au
* Description: Export and import Gravity Forms entries between WordPress sites with duplicate prevention
* Version: 1.0.0
* Author: Jon Mather
* License: GPL v2 or later
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class GF_Entry_Transfer {
private $plugin_url;
private $plugin_path;
public function __construct() {
$this->plugin_url = plugin_dir_url(__FILE__);
$this->plugin_path = plugin_dir_path(__FILE__);
add_action('init', array($this, 'init'));
}
public function init() {
// Check if Gravity Forms is active
if (!class_exists('GFForms')) {
add_action('admin_notices', array($this, 'gf_not_active_notice'));
return;
}
// Add admin menu
add_action('admin_menu', array($this, 'add_admin_menu'));
// Handle AJAX requests
add_action('wp_ajax_gf_export_entries', array($this, 'export_entries'));
add_action('wp_ajax_gf_import_entries', array($this, 'import_entries'));
// Enqueue scripts and styles
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
}
public function gf_not_active_notice() {
?>
<div class="notice notice-error">
<p><?php _e('Gravity Forms Entry Transfer requires Gravity Forms to be installed and activated.', 'gf-entry-transfer'); ?></p>
</div>
<?php
}
public function add_admin_menu() {
add_submenu_page(
'gf_edit_forms',
'Entry Transfer',
'Entry Transfer',
'manage_options',
'gf-entry-transfer',
array($this, 'admin_page')
);
}
public function enqueue_scripts($hook) {
if ($hook !== 'forms_page_gf-entry-transfer') {
return;
}
// Enqueue jQuery
wp_enqueue_script('jquery');
// Localize script for AJAX
wp_localize_script('jquery', 'gf_transfer_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('gf_entry_transfer_nonce')
));
// Add inline styles
$this->add_admin_styles();
}
public function add_admin_styles() {
?>
<style>
.gf-transfer-section {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
margin: 20px 0;
padding: 20px;
}
.gf-transfer-section h3 {
margin-top: 0;
color: #23282d;
}
.form-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
background: #f9f9f9;
}
.form-list label {
display: block;
margin: 5px 0;
}
.gf-btn {
background: #0073aa;
color: white;
padding: 8px 16px;
border: none;
border-radius: 3px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.gf-btn:hover {
background: #005a87;
}
#import-file {
margin: 10px 0;
}
</style>
<?php
}
public function admin_page() {
$forms = GFAPI::get_forms();
?>
<div class="wrap">
<h1>Gravity Forms Entry Transfer</h1>
<!-- Export Section -->
<div class="gf-transfer-section">
<h3>Export Entries</h3>
<p>Select the forms you want to export entries from:</p>
<div class="form-list">
<label>
<input type="checkbox" id="select-all-export"> <strong>Select All</strong>
</label>
<hr>
<?php foreach ($forms as $form): ?>
<label>
<input type="checkbox" name="export_forms[]" value="<?php echo esc_attr($form['id']); ?>">
<?php echo esc_html($form['title']); ?> (ID: <?php echo esc_html($form['id']); ?>) -
<?php echo GFAPI::count_entries($form['id']); ?> entries
</label>
<?php endforeach; ?>
</div>
<p>
<button id="export-btn" class="gf-btn">Export Selected Forms</button>
</p>
<div id="export-status"></div>
</div>
<!-- Import Section -->
<div class="gf-transfer-section">
<h3>Import Entries</h3>
<p>Select the JSON export file to import:</p>
<input type="file" id="import-file" accept=".json" />
<p>
<button id="import-btn" class="gf-btn">Import Entries</button>
</p>
<div id="import-status"></div>
<div style="margin-top: 20px; padding: 15px; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px;">
<h4>Important Notes:</h4>
<ul>
<li>Entries will only be imported if a form with the same ID exists on this site</li>
<li>Duplicate entries are detected by comparing form ID, date created, and field values</li>
<li>File uploads and images will not be transferred (only the original filenames will be preserved)</li>
<li>Make sure you have sufficient server memory and execution time for large imports</li>
</ul>
</div>
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Select all functionality
$('#select-all-export').change(function() {
$('input[name="export_forms[]"]').prop('checked', this.checked);
});
// Export functionality
$('#export-btn').click(function(e) {
e.preventDefault();
var selectedForms = [];
$('input[name="export_forms[]"]:checked').each(function() {
selectedForms.push($(this).val());
});
if (selectedForms.length === 0) {
alert('Please select at least one form to export.');
return;
}
$('#export-status').html('<p>Exporting entries...</p>');
$.ajax({
url: gf_transfer_ajax.ajax_url,
type: 'POST',
data: {
action: 'gf_export_entries',
forms: selectedForms,
nonce: gf_transfer_ajax.nonce
},
success: function(response) {
if (response.success) {
// var debugHtml = response.data.debug ? '<br><strong>Debug Info:</strong><br>' + response.data.debug : '';
var debugHtm = "";
$('#export-status').html('<p style="color: green;">Export completed! ' + response.data.count + ' entries exported from ' + response.data.forms + ' forms.</p>' + debugHtml);
// Create download link
var blob = new Blob([response.data.json], {type: 'application/json'});
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'gf-entries-export-' + new Date().toISOString().slice(0, 10) + '.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
} else {
$('#export-status').html('<p style="color: red;">Export failed: ' + response.data + '</p>');
}
},
error: function() {
$('#export-status').html('<p style="color: red;">Export failed due to server error.</p>');
}
});
});
// Import functionality
$('#import-btn').click(function(e) {
e.preventDefault();
var fileInput = document.getElementById('import-file');
if (!fileInput.files.length) {
alert('Please select a file to import.');
return;
}
var file = fileInput.files[0];
var reader = new FileReader();
reader.onload = function(e) {
try {
var jsonData = JSON.parse(e.target.result);
$('#import-status').html('<p>Importing entries...</p>');
$.ajax({
url: gf_transfer_ajax.ajax_url,
type: 'POST',
data: {
action: 'gf_import_entries',
json_data: JSON.stringify(jsonData),
nonce: gf_transfer_ajax.nonce
},
success: function(response) {
if (response.success) {
$('#import-status').html('<p style="color: green;">Import completed! ' + response.data.imported + ' entries imported, ' + response.data.duplicates + ' duplicates skipped.</p>');
} else {
$('#import-status').html('<p style="color: red;">Import failed: ' + response.data + '</p>');
}
},
error: function() {
$('#import-status').html('<p style="color: red;">Import failed due to server error.</p>');
}
});
} catch (error) {
alert('Invalid JSON file. Please select a valid export file.');
}
};
reader.readAsText(file);
});
});
</script>
<?php
}
public function export_entries() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'gf_entry_transfer_nonce')) {
wp_die('Security check failed');
}
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
return;
}
$form_ids = isset($_POST['forms']) ? $_POST['forms'] : array();
if (empty($form_ids)) {
wp_send_json_error('No forms selected');
return;
}
// Increase memory limit and execution time for large exports
ini_set('memory_limit', '512M');
set_time_limit(300); // 5 minutes
$export_data = array(
'export_date' => current_time('mysql'),
'site_url' => get_site_url(),
'gf_version' => GFForms::$version,
'forms' => array()
);
$total_entries = 0;
$debug_info = array();
foreach ($form_ids as $form_id) {
$form = GFAPI::get_form($form_id);
if (!$form) {
$debug_info[] = "Form ID {$form_id}: Form not found";
continue;
}
// Get entries with no limit to ensure we get all entries
$search_criteria = array();
$sorting = null;
$paging = array('offset' => 0, 'page_size' => 999999); // Get all entries
$entries = GFAPI::get_entries($form_id, $search_criteria, $sorting, $paging);
if (is_wp_error($entries)) {
$debug_info[] = "Form ID {$form_id}: Error retrieving entries - " . $entries->get_error_message();
continue;
}
$entry_count = count($entries);
$debug_info[] = "Form ID {$form_id} ({$form['title']}): Retrieved {$entry_count} entries";
$export_data['forms'][] = array(
'form' => $form,
'entries' => $entries
);
$total_entries += $entry_count;
}
// Add debug information to export
$export_data['debug_info'] = $debug_info;
wp_send_json_success(array(
'json' => json_encode($export_data),
'count' => $total_entries,
'forms' => count($export_data['forms']),
'debug' => implode('<br>', $debug_info)
));
}
public function import_entries() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'gf_entry_transfer_nonce')) {
wp_die('Security check failed');
}
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
return;
}
$json_data = json_decode(stripslashes($_POST['json_data']), true);
if (!$json_data || !isset($json_data['forms'])) {
wp_send_json_error('Invalid import data');
return;
}
$imported_count = 0;
$duplicate_count = 0;
foreach ($json_data['forms'] as $form_data) {
$form_id = $form_data['form']['id'];
// Check if form exists on this site
$existing_form = GFAPI::get_form($form_id);
if (!$existing_form) {
continue; // Skip if form doesn't exist
}
foreach ($form_data['entries'] as $entry_data) {
// Check for duplicate entry
if ($this->is_duplicate_entry($entry_data)) {
$duplicate_count++;
continue;
}
// Prepare entry for import
$entry = $this->prepare_entry_for_import($entry_data, $form_id);
// Import the entry
$result = GFAPI::add_entry($entry);
if (!is_wp_error($result)) {
$imported_count++;
}
}
}
wp_send_json_success(array(
'imported' => $imported_count,
'duplicates' => $duplicate_count
));
}
private function is_duplicate_entry($entry_data) {
global $wpdb;
$form_id = $entry_data['form_id'];
$date_created = $entry_data['date_created'];
// Get existing entries for this form created on the same date
$existing_entries = GFAPI::get_entries($form_id, array(
'start_date' => date('Y-m-d', strtotime($date_created)),
'end_date' => date('Y-m-d', strtotime($date_created . ' +1 day'))
));
if (is_wp_error($existing_entries)) {
return false;
}
foreach ($existing_entries as $existing_entry) {
// Compare key fields to detect duplicates
$is_duplicate = true;
// Compare date created (within 1 minute tolerance)
if (abs(strtotime($existing_entry['date_created']) - strtotime($date_created)) > 60) {
$is_duplicate = false;
continue;
}
// Compare field values
foreach ($entry_data as $key => $value) {
if (is_numeric($key) && isset($existing_entry[$key])) {
if ($existing_entry[$key] !== $value) {
$is_duplicate = false;
break;
}
}
}
if ($is_duplicate) {
return true;
}
}
return false;
}
private function prepare_entry_for_import($entry_data, $form_id) {
// Remove auto-generated fields that should be set by GF
unset($entry_data['id']);
// Ensure form_id is correct
$entry_data['form_id'] = $form_id;
// Set import timestamp but preserve original date in meta if needed
$original_date = $entry_data['date_created'];
$entry_data['date_created'] = current_time('mysql');
$entry_data['date_updated'] = current_time('mysql');
return $entry_data;
}
}
// Initialize the plugin
new GF_Entry_Transfer();
@westcoastdigital
Copy link
Author

Gravity Forms Entry Transfer

Export and import Gravity Forms entries between WordPress sites with built-in duplicate prevention.

Features

  • πŸ”„ Export entries from one WordPress site and import into another
  • βœ… Duplicate prevention (checks form ID, created date, and field values)
  • πŸ“¦ Works with multiple forms in a single export
  • ⚑ Handles large exports (increased memory and execution time)
  • πŸ›‘οΈ Secure with WordPress nonces and permissions
  • 🎨 Simple admin UI under Forms β†’ Entry Transfer

Requirements

  • WordPress 5.0+
  • Gravity Forms (must be installed and active)
  • PHP 7.4+

Installation

Since this plugin is distributed as a single PHP file via a Gist, you’ll need to create the plugin manually.

Option 1: Install directly on the server

  1. Copy the PHP code from the Gist file.

  2. On your WordPress server, navigate to:

    wp-content/plugins/
    
  3. Create a new folder named:

    gf-entry-transfer
    
  4. Inside that folder, create a file named:

    gf-entry-transfer.php
    
  5. Paste the copied PHP code into that file and save it.

  6. In the WordPress admin, go to Plugins β†’ Installed Plugins and activate Gravity Forms Entry Transfer.


Option 2: Create locally and upload as ZIP

  1. On your computer, create a new folder named:

    gf-entry-transfer
    
  2. Inside that folder, create a file named:

    gf-entry-transfer.php
    
  3. Copy the PHP code from the Gist into that file and save it.

  4. Compress the folder into a ZIP file named:

    gf-entry-transfer.zip
    
  5. In the WordPress admin, go to Plugins β†’ Add New β†’ Upload Plugin.

  6. Upload the ZIP file and activate the plugin.


Usage

  1. Go to Forms β†’ Entry Transfer in the WordPress admin menu.
  2. Export:
    • Select one or more forms.
    • Click Export Selected Forms to download a JSON file containing entries.
  3. Import:
    • Choose an exported JSON file from another site.
    • Click Import Entries to bring them in.
    • Duplicate entries are automatically skipped.
screenshot

Notes

  • Entries will only be imported if a form with the same form ID exists on the target site.
  • Duplicate detection compares form ID, date created, and field values.
  • File uploads are not transferred (only filenames are preserved).
  • For large imports/exports, ensure your server has enough memory and execution time.

License

This plugin is licensed under the GPL v2 or later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment