Gravity Forms custom field
August 28, 2025 · AI Conversations
Below are the **minimal changes** to make your custom field a proper **multi-input** field so that GF saves every selection/value separately (height unit, height cm/ft+in, weight unit, weight kg/st+lb, and the computed BMI).
—
### 1) Define sub-inputs (so GF knows what to save)
**Add** a constructor inside `GF_Field_Bmi_Calculator`:
public function __construct($data = array()) {
parent::__construct($data);
// Define sub-inputs with labels; IDs are <fieldId>.<index>
$this->inputs = array(
array('id' => "{$this->id}.1", 'label' => __('Height Unit', 'bmi-calculator-gf')),
array('id' => "{$this->id}.2", 'label' => __('Height (cm)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.3", 'label' => __('Height (feet)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.4", 'label' => __('Height (inches)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.5", 'label' => __('Weight Unit', 'bmi-calculator-gf')),
array('id' => "{$this->id}.6", 'label' => __('Weight (kg)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.7", 'label' => __('Weight (stones)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.8", 'label' => __('Weight (pounds)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.9", 'label' => __('BMI', 'bmi-calculator-gf')),
);
}
*(This tells GF to create/save separate entry meta for `.1` … `.9`.)*
—
### 2) Name your inputs with Gravity Forms’ naming convention
**Replace** your `get_field_input()` markup so every control uses `name="input_{$this->id}.X"` (and keep the hidden BMI too). Also prefill checked/values from posted data so it works on validation failures:
public function get_field_input($form, $value = '', $entry = null) {
$field_id = (int) $this->id;
// Pull previously posted values so UI reflects them after validation errors
$h_unit = rgpost("input_{$field_id}.1") ?: 'cm';
$h_cm = rgpost("input_{$field_id}.2");
$h_ft = rgpost("input_{$field_id}.3");
$h_in = rgpost("input_{$field_id}.4");
$w_unit = rgpost("input_{$field_id}.5") ?: 'kg';
$w_kg = rgpost("input_{$field_id}.6");
$w_st = rgpost("input_{$field_id}.7");
$w_lb = rgpost("input_{$field_id}.8");
$bmi_val = rgpost("input_{$field_id}.9");
ob_start(); ?>
<div class="bmi-calc-wrap" data-field-id="<?php echo esc_attr($field_id); ?>">
<div class="bmi-calc-section" style="margin-top:25px;">
<label class='gfield_label gform-field-label'><?php _e('Enter your height', 'bmi-calculator-gf'); ?></label>
<div class="bmi-calc-row">
<label>
<input type="radio"
name="input_<?php echo esc_attr($field_id); ?>.1"
value="cm" <?php checked($h_unit, 'cm'); ?>> cm
</label>
<label>
<input type="radio"
name="input_<?php echo esc_attr($field_id); ?>.1"
value="ft_in" <?php checked($h_unit, 'ft_in'); ?>> feet/inches
</label>
</div>
<div class="bmi-calc-row bmi-height-cm" style="<?php echo ($h_unit==='cm') ? '' : 'display:none;'; ?>">
<input type="number" step="any" min="0"
class="bmi-height-input"
name="input_<?php echo esc_attr($field_id); ?>.2"
placeholder="0"
value="<?php echo esc_attr($h_cm); ?>">
</div>
<div class="bmi-calc-row bmi-height-ftin" style="<?php echo ($h_unit==='ft_in') ? '' : 'display:none;'; ?>">
<input type="number" min="0"
class="bmi-height-feet"
name="input_<?php echo esc_attr($field_id); ?>.3"
placeholder="Feet"
value="<?php echo esc_attr($h_ft); ?>">
<input type="number" min="0"
class="bmi-height-inches"
name="input_<?php echo esc_attr($field_id); ?>.4"
placeholder="Inches"
value="<?php echo esc_attr($h_in); ?>">
</div>
</div>
<div class="bmi-calc-section">
<label class='gfield_label gform-field-label'><?php _e('Enter your weight', 'bmi-calculator-gf'); ?></label>
<div class="bmi-calc-row">
<label>
<input type="radio"
name="input_<?php echo esc_attr($field_id); ?>.5"
value="kg" <?php checked($w_unit, 'kg'); ?>> kg
</label>
<label>
<input type="radio"
name="input_<?php echo esc_attr($field_id); ?>.5"
value="st_lb" <?php checked($w_unit, 'st_lb'); ?>> stones/pounds
</label>
</div>
<div class="bmi-calc-row bmi-weight-kg" style="<?php echo ($w_unit==='kg') ? '' : 'display:none;'; ?>">
<input type="number" step="any" min="0"
class="bmi-weight-input"
name="input_<?php echo esc_attr($field_id); ?>.6"
placeholder="0"
value="<?php echo esc_attr($w_kg); ?>">
</div>
<div class="bmi-calc-row bmi-weight-stlb" style="<?php echo ($w_unit==='st_lb') ? '' : 'display:none;'; ?>">
<input type="number" min="0"
class="bmi-weight-stones"
name="input_<?php echo esc_attr($field_id); ?>.7"
placeholder="Stones"
value="<?php echo esc_attr($w_st); ?>">
<input type="number" min="0"
class="bmi-weight-pounds"
name="input_<?php echo esc_attr($field_id); ?>.8"
placeholder="Pounds"
value="<?php echo esc_attr($w_lb); ?>">
</div>
</div>
<div class="bmi-calc-result">
<label class='gfield_label gform-field-label'><?php _e('Your BMI:', 'bmi-calculator-gf'); ?></label>
<span class="bmi-calc-value"><?php echo $bmi_val !== null && $bmi_val !== '' ? esc_html($bmi_val) : '--'; ?></span>
</div>
<!-- Hidden BMI value saved as sub-input .9 -->
<input type="hidden"
name="input_<?php echo esc_attr($field_id); ?>.9"
class="bmi-calc-hidden"
value="<?php echo esc_attr($bmi_val); ?>">
</div>
<?php
return ob_get_clean();
}
—
### 3) (Optional but nice) show sublabels in the editor
**Extend** your field settings so the sublabels are visible/configurable (optional):
public function get_form_editor_field_settings() {
return array(
'label_setting',
'description_setting',
'css_class_setting',
'sub_labels_setting', // optional
);
}
—
### 4) No change needed to your JS/CSS enqueues
Keep your existing enqueues. Just ensure your `js/bmi-calculator.js` still writes the computed BMI into the hidden input which is now named `input_.9` (your selector `.bmi-calc-hidden` is fine).
If your JS is toggling visibility, it should also toggle based on the **radio values now tied to `input_.1` and `input_.5`** (your current selectors should still work).
—
## What you’ll get in the entry
– `input_.1` → height unit (`cm` or `ft_in`)
– `input_.2` → height (cm)
– `input_.3` → height (feet)
– `input_.4` → height (inches)
– `input_.5` → weight unit (`kg` or `st_lb`)
– `input_.6` → weight (kg)
– `input_.7` → weight (stones)
– `input_.8` → weight (pounds)
– `input_.9` → BMI (your computed value)
Gravity Forms will now save **all** of these automatically—no extra hooks needed.
So the DB entry should have:
– One input for **height** (cm *or* ft+in converted to cm, whichever was used)
– One input for **weight** (kg *or* st+lb converted to kg, whichever was used)
– (Optional) the BMI value
The radio selections are just UI helpers; they don’t need to be saved.
—
### 🔧 How to do it
The cleanest way in GF is to keep your custom radios/fields for the UI, but only save into **two hidden inputs** that follow GF’s naming pattern:
– `input_.1` → Height (normalized, e.g. cm)
– `input_.2` → Weight (normalized, e.g. kg)
– (Optional) `input_.3` → BMI
Then, in your JavaScript (`bmi-calculator.js`), when the user enters data and selects units:
1. Convert height to cm
– If unit = `cm` → just copy.
– If unit = `ft_in` → `(feet * 30.48) + (inches * 2.54)`.
2. Convert weight to kg
– If unit = `kg` → just copy.
– If unit = `st_lb` → `(stones * 6.35029) + (pounds * 0.453592)`.
3. Compute BMI = `kg / (m²)` where `m = cm / 100`.
4. Write the results into the hidden GF inputs (`.bmi-height-hidden`, `.bmi-weight-hidden`, `.bmi-bmi-hidden`).
—
### Example changes
#### PHP — define only 3 sub-inputs
public function __construct($data = array()) {
parent::__construct($data);
$this->inputs = array(
array('id' => "{$this->id}.1", 'label' => __('Height (cm)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.2", 'label' => __('Weight (kg)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.3", 'label' => __('BMI', 'bmi-calculator-gf')),
);
}
#### PHP — in your markup, add hidden fields
<input type="hidden" name="input_<?php echo esc_attr($field_id); ?>.1" class="bmi-height-hidden" value="">
<input type="hidden" name="input_<?php echo esc_attr($field_id); ?>.2" class="bmi-weight-hidden" value="">
<input type="hidden" name="input_<?php echo esc_attr($field_id); ?>.3" class="bmi-bmi-hidden" value="">
*(Keep all your visible radios and number fields the same — they’re just for user input, not saved directly.)*
—
#### JS — on input change, normalize and set hidden values
jQuery(document).on('input change', '.bmi-calc-wrap', function() {
var wrap = jQuery(this);
// Height
var hUnit = wrap.find('input[name^="bmi_height_unit"]:checked').val();
var cm = 0;
if (hUnit === 'cm') {
cm = parseFloat(wrap.find('.bmi-height-input').val()) || 0;
} else {
var ft = parseFloat(wrap.find('.bmi-height-feet').val()) || 0;
var inch = parseFloat(wrap.find('.bmi-height-inches').val()) || 0;
cm = (ft * 30.48) + (inch * 2.54);
}
wrap.find('.bmi-height-hidden').val(cm.toFixed(2));
// Weight
var wUnit = wrap.find('input[name^="bmi_weight_unit"]:checked').val();
var kg = 0;
if (wUnit === 'kg') {
kg = parseFloat(wrap.find('.bmi-weight-input').val()) || 0;
} else {
var st = parseFloat(wrap.find('.bmi-weight-stones').val()) || 0;
var lb = parseFloat(wrap.find('.bmi-weight-pounds').val()) || 0;
kg = (st * 6.35029) + (lb * 0.453592);
}
wrap.find('.bmi-weight-hidden').val(kg.toFixed(2));
// BMI
var bmi = 0;
if (cm > 0 && kg > 0) {
bmi = kg / Math.pow(cm/100, 2);
}
wrap.find('.bmi-bmi-hidden').val(bmi.toFixed(1));
wrap.find('.bmi-calc-value').text(bmi > 0 ? bmi.toFixed(1) : '--');
});
—
✅ Now GF will save just **Height (cm)**, **Weight (kg)**, and **BMI** into the entry.
The radio choices and raw ft/in or st/lb fields are only UI helpers, not stored.
—
Do you also want the **raw values** (like “5ft 7in” / “10st 5lb”) saved alongside the normalized cm/kg values, or just the normalized ones?
`input_` (no `.subId`) in addition to any sub-inputs.
– That root value is what shows up in merge tags like `{Field:1}`.
– Sub-inputs (`.1`, `.2`, `.3`) are stored separately in the entry detail.
So you should **keep your existing hidden**:
<input type="hidden"
name="input_<?php echo esc_attr($this->id); ?>"
class="bmi-calc-hidden"
value="">
and also add the 3 sub-inputs for **height, weight, BMI**:
<input type="hidden" name="input_<?php echo esc_attr($this->id); ?>.1"
class="bmi-height-hidden" value="">
<input type="hidden" name="input_<?php echo esc_attr($this->id); ?>.2"
class="bmi-weight-hidden" value="">
<input type="hidden" name="input_<?php echo esc_attr($this->id); ?>.3"
class="bmi-bmi-hidden" value="">
—
### How to use them together
– **Root hidden (`input_`)** → you can store a **summary string** (e.g. `"Height: 170 cm, Weight: 65 kg, BMI: 22.5"`).
– **Sub-inputs (`.1`, `.2`, `.3`)** → store **normalized numbers** (cm, kg, BMI).
Update your JS so both get filled:
// After calculating cm, kg, bmi…
wrap.find('.bmi-height-hidden').val(cm.toFixed(2));
wrap.find('.bmi-weight-hidden').val(kg.toFixed(2));
wrap.find('.bmi-bmi-hidden').val(bmi.toFixed(1));
// Root field: nice summary string
var summary = 'Height: ' + cm.toFixed(2) + ' cm, ' +
'Weight: ' + kg.toFixed(2) + ' kg, ' +
'BMI: ' + (bmi > 0 ? bmi.toFixed(1) : '--');
wrap.find('.bmi-calc-hidden').val(summary);
—
✅ Result:
– Entry has clean numeric data in sub-inputs (good for reports/exports).
– Entry also has a readable combined string in the main field (good for confirmations/notifications).
Do you want me to adjust the PHP `get_field_input()` code for you so it includes all 4 hidden fields correctly, or do you just need the JS update?
let userEditedWeightStLb = false;
let userEditedHeightFtIn = false;
function kgToStLb(kg) {
const st = Math.floor(kg * 2.20462 / 14);
const lb = (kg * 2.20462) – (st * 14);
return { st: st, lb: Math.round(lb * 100) / 100 };
}
function stLbToKg(st, lb) {
return (parseFloat(st) * 6.35029) + (parseFloat(lb) * 0.453592);
}
function cmToFtIn(cm) {
const totalIn = cm / 2.54;
const ft = Math.floor(totalIn / 12);
const inch = totalIn – (ft * 12);
return { ft: ft, inch: Math.round(inch * 100) / 100 };
}
function ftInToCm(ft, inch) {
return (parseFloat(ft) * 30.48) + (parseFloat(inch) * 2.54);
}
function calcBMI(kg, cm) {
if (!kg || !cm) return '';
const m = cm / 100;
const bmi = kg / (m * m);
return (bmi > 0 && isFinite(bmi)) ? bmi.toFixed(1) : '';
}
function updateBMI($wrap) {
let kg, cm;
// === Weight ===
const weightUnit = $wrap.find('input[name^=bmi_weight_unit_]:checked').val();
if (weightUnit === 'kg') {
kg = parseFloat($wrap.find('.bmi-weight-input').val());
if (!isNaN(kg)) {
const stlb = kgToStLb(kg);
$wrap.find('.bmi-weight-stones').val(stlb.st);
$wrap.find('.bmi-weight-pounds').val(stlb.lb);
}
} else {
const st = parseFloat($wrap.find('.bmi-weight-stones').val()) || 0;
const lb = parseFloat($wrap.find('.bmi-weight-pounds').val()) || 0;
const convertedKg = stLbToKg(st, lb);
if (userEditedWeightStLb && !isNaN(convertedKg)) {
kg = convertedKg;
$wrap.find('.bmi-weight-input').val(kg.toFixed(2));
} else {
kg = parseFloat($wrap.find('.bmi-weight-input').val());
}
}
// === Height ===
const heightUnit = $wrap.find('input[name^=bmi_height_unit_]:checked').val();
if (heightUnit === 'cm') {
cm = parseFloat($wrap.find('.bmi-height-input').val());
if (!isNaN(cm)) {
const ftin = cmToFtIn(cm);
$wrap.find('.bmi-height-feet').val(ftin.ft);
$wrap.find('.bmi-height-inches').val(ftin.inch);
}
} else {
const ft = parseFloat($wrap.find('.bmi-height-feet').val()) || 0;
const inch = parseFloat($wrap.find('.bmi-height-inches').val()) || 0;
const convertedCm = ftInToCm(ft, inch);
if (userEditedHeightFtIn && !isNaN(convertedCm)) {
cm = convertedCm;
$wrap.find('.bmi-height-input').val(cm.toFixed(2));
} else {
cm = parseFloat($wrap.find('.bmi-height-input').val());
}
}
// === BMI ===
const bmi = calcBMI(kg, cm);
console.log('BMI input changed:', { kg, cm, bmi });
$wrap.find('.bmi-calc-value').text(bmi || '–');
$wrap.find('.bmi-calc-hidden').val(bmi);
// Reset user input flags
userEditedWeightStLb = false;
userEditedHeightFtIn = false;
}
// === Track manual edits ===
$(document).on('input', '.bmi-weight-stones, .bmi-weight-pounds', function () {
userEditedWeightStLb = true;
});
$(document).on('input', '.bmi-height-feet, .bmi-height-inches', function () {
userEditedHeightFtIn = true;
});
// === Trigger BMI update on input ===
$(document).on('input change', '.bmi-calc-wrap input', function () {
const $wrap = $(this).closest('.bmi-calc-wrap');
updateBMI($wrap);
});
// === Toggle weight units ===
$(document).on('change', 'input[name^=bmi_weight_unit_]', function () {
const $wrap = $(this).closest('.bmi-calc-wrap');
if ($(this).val() === 'st_lb') {
$wrap.find('.bmi-weight-stlb').show();
$wrap.find('.bmi-weight-kg').hide();
} else {
$wrap.find('.bmi-weight-stlb').hide();
$wrap.find('.bmi-weight-kg').show();
}
updateBMI($wrap);
});
// === Toggle height units ===
$(document).on('change', 'input[name^=bmi_height_unit_]', function () {
const $wrap = $(this).closest('.bmi-calc-wrap');
if ($(this).val() === 'ft_in') {
$wrap.find('.bmi-height-ftin').show();
$wrap.find('.bmi-height-cm').hide();
} else {
$wrap.find('.bmi-height-ftin').hide();
$wrap.find('.bmi-height-cm').show();
}
updateBMI($wrap);
});
// === Initial state setup ===
$('.bmi-calc-wrap').each(function () {
const $wrap = $(this);
// Show/hide fields based on checked units
if ($wrap.find('input[name^=bmi_weight_unit_]:checked').val() === 'st_lb') {
$wrap.find('.bmi-weight-stlb').show();
$wrap.find('.bmi-weight-kg').hide();
}
if ($wrap.find('input[name^=bmi_height_unit_]:checked').val() === 'ft_in') {
$wrap.find('.bmi-height-ftin').show();
$wrap.find('.bmi-height-cm').hide();
}
updateBMI($wrap);
});
});
this is the js update code here
Add the 4 hidden inputs in your PHP markup (if you haven’t already):
<input type="hidden" name="input_<?php echo esc_attr($this->id); ?>" class="bmi-calc-hidden" value="">
<input type="hidden" name="input_<?php echo esc_attr($this->id); ?>.1" class="bmi-height-hidden" value="">
<input type="hidden" name="input_<?php echo esc_attr($this->id); ?>.2" class="bmi-weight-hidden" value="">
<input type="hidden" name="input_<?php echo esc_attr($this->id); ?>.3" class="bmi-bmi-hidden" value="">
Then, in your JS, **patch `updateBMI($wrap)`** (only additions shown):
function updateBMI($wrap) {
let kg, cm;
// ... your existing weight logic ...
// ... your existing height logic ...
// === BMI ===
const bmi = calcBMI(kg, cm);
$wrap.find('.bmi-calc-value').text(bmi || '--');
// ---- NEW: write normalized values into GF hidden sub-inputs ----
const hasKG = Number.isFinite(kg) && kg > 0;
const hasCM = Number.isFinite(cm) && cm > 0;
const hasBMI = !!bmi;
$wrap.find('.bmi-height-hidden').val(hasCM ? cm.toFixed(2) : '');
$wrap.find('.bmi-weight-hidden').val(hasKG ? kg.toFixed(2) : '');
$wrap.find('.bmi-bmi-hidden').val(hasBMI ? bmi : '');
// Root hidden: human-readable summary (optional)
const summary = (hasCM || hasKG || hasBMI)
? `Height: ${hasCM ? cm.toFixed(2) + ' cm' : '--'}, `
+ `Weight: ${hasKG ? kg.toFixed(2) + ' kg' : '--'}, `
+ `BMI: ${hasBMI ? bmi : '--'}`
: '';
$wrap.find('.bmi-calc-hidden').val(summary);
// Reset user input flags
userEditedWeightStLb = false;
userEditedHeightFtIn = false;
}
That’s it.
Gravity Forms will now save **only**:
– `.1` → Height (cm, normalized)
– `.2` → Weight (kg, normalized)
– `.3` → BMI
…and **not** the radio selections or raw ft/in or st/lb values.
function updateBMI($wrap) {
let kg, cm;
// === Weight ===
const weightUnit = $wrap.find('input[name^=bmi_weight_unit_]:checked').val();
if (weightUnit === 'kg') {
kg = parseFloat($wrap.find('.bmi-weight-input').val());
if (!isNaN(kg)) {
const stlb = kgToStLb(kg);
$wrap.find('.bmi-weight-stones').val(stlb.st);
$wrap.find('.bmi-weight-pounds').val(stlb.lb);
}
} else {
const st = parseFloat($wrap.find('.bmi-weight-stones').val()) || 0;
const lb = parseFloat($wrap.find('.bmi-weight-pounds').val()) || 0;
const convertedKg = stLbToKg(st, lb);
if (userEditedWeightStLb && Number.isFinite(convertedKg)) {
kg = convertedKg;
$wrap.find('.bmi-weight-input').val(kg.toFixed(2));
} else {
kg = parseFloat($wrap.find('.bmi-weight-input').val());
}
}
// === Height ===
const heightUnit = $wrap.find('input[name^=bmi_height_unit_]:checked').val();
if (heightUnit === 'cm') {
cm = parseFloat($wrap.find('.bmi-height-input').val());
if (!isNaN(cm)) {
const ftin = cmToFtIn(cm);
$wrap.find('.bmi-height-feet').val(ftin.ft);
$wrap.find('.bmi-height-inches').val(ftin.inch);
}
} else {
const ft = parseFloat($wrap.find('.bmi-height-feet').val()) || 0;
const inch = parseFloat($wrap.find('.bmi-height-inches').val()) || 0;
const convertedCm = ftInToCm(ft, inch);
if (userEditedHeightFtIn && Number.isFinite(convertedCm)) {
cm = convertedCm;
$wrap.find('.bmi-height-input').val(cm.toFixed(2));
} else {
cm = parseFloat($wrap.find('.bmi-height-input').val());
}
}
// === BMI ===
const bmi = calcBMI(kg, cm);
$wrap.find('.bmi-calc-value').text(bmi || '--');
// === Write normalized values to GF hidden sub-inputs ===
const hasKG = Number.isFinite(kg) && kg > 0;
const hasCM = Number.isFinite(cm) && cm > 0;
const hasBMI = !!bmi;
$wrap.find('.bmi-height-hidden').val(hasCM ? cm.toFixed(2) : '');
$wrap.find('.bmi-weight-hidden').val(hasKG ? kg.toFixed(2) : '');
$wrap.find('.bmi-bmi-hidden').val(hasBMI ? bmi : '');
// Root hidden: human-readable summary (optional)
const summary = (hasCM || hasKG || hasBMI)
? `Height: ${hasCM ? cm.toFixed(2) + ' cm' : '--'}, ` +
`Weight: ${hasKG ? kg.toFixed(2) + ' kg' : '--'}, ` +
`BMI: ${hasBMI ? bmi : '--'}`
: '';
$wrap.find('input[name^="input_"][class~="bmi-calc-hidden"]').val(summary);
// Reset user input flags
userEditedWeightStLb = false;
userEditedHeightFtIn = false;
}
Add these methods **inside your `GF_Field_Bmi_Calculator` class** (keep your 3 sub-inputs `.1/.2/.3`):
// 1) Make sure you defined sub-inputs (height cm, weight kg, BMI)
public function __construct($data = array()) {
parent::__construct($data);
$this->inputs = array(
array('id' => "{$this->id}.1", 'label' => __('Height (cm)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.2", 'label' => __('Weight (kg)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.3", 'label' => __('BMI', 'bmi-calculator-gf')),
);
}
// 2) What shows on "View Entry" for the main field row
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
// $value may be an array for multi-input fields. Normalize it.
$field_id = (string) $this->id;
$h = '';
$w = '';
$b = '';
if ( is_array( $value ) ) {
// Try keys like "12.1", "12.2", "12.3"
$h = rgar( $value, "{$field_id}.1" );
$w = rgar( $value, "{$field_id}.2" );
$b = rgar( $value, "{$field_id}.3" );
// Fallback: some GF versions pass simple numeric indexes
if ( $h === null && isset( $value[1] ) ) $h = $value[1];
if ( $w === null && isset( $value[2] ) ) $w = $value[2];
if ( $b === null && isset( $value[3] ) ) $b = $value[3];
} else {
// If a summary string was stored in the root input, just show it.
return esc_html( (string) $value );
}
$parts = array();
if ( $h !== '' && $h !== null ) $parts[] = sprintf( __( 'Height: %s cm', 'bmi-calculator-gf' ), esc_html( $h ) );
if ( $w !== '' && $w !== null ) $parts[] = sprintf( __( 'Weight: %s kg', 'bmi-calculator-gf' ), esc_html( $w ) );
if ( $b !== '' && $b !== null ) $parts[] = sprintf( __( 'BMI: %s', 'bmi-calculator-gf' ), esc_html( $b ) );
$out = implode( $format === 'html' ? '<br/>' : ' | ', $parts );
return $format === 'html' ? $out : wp_strip_all_tags( $out );
}
// 3) What shows in CSV/exports for the main field (one cell)
public function get_value_export( $entry, $input_id = '', $use_text = false, $delimiter = ',' ) {
$h = rgar( $entry, "{$this->id}.1" );
$w = rgar( $entry, "{$this->id}.2" );
$bmi = rgar( $entry, "{$this->id}.3" );
$parts = array();
if ( $h !== '' && $h !== null ) $parts[] = "Height(cm): {$h}";
if ( $w !== '' && $w !== null ) $parts[] = "Weight(kg): {$w}";
if ( $bmi !== '' && $bmi !== null ) $parts[] = "BMI: {$bmi}";
return implode( ' | ', $parts );
}
// 4) What shows for merge tags like {Field:ID}
public function get_value_merge_tag( $value, $input_id = '', $modifiers = null, $field = null, $raw_value = null, $url_encode = false, $esc_html = false, $format = 'text' ) {
// Prefer root summary if you set it via JS; else build from sub-inputs
if ( is_string( $value ) && $value !== '' ) {
return $value;
}
$entry = GFFormsModel::get_current_lead();
$h = is_array($entry) ? rgar( $entry, "{$this->id}.1" ) : '';
$w = is_array($entry) ? rgar( $entry, "{$this->id}.2" ) : '';
$b = is_array($entry) ? rgar( $entry, "{$this->id}.3" ) : '';
$summary = trim( sprintf(
'Height: %s cm, Weight: %s kg, BMI: %s',
$h !== '' ? $h : '--',
$w !== '' ? $w : '--',
$b !== '' ? $b : '--'
), ', ' );
if ( $esc_html ) $summary = esc_html( $summary );
if ( $url_encode ) $summary = urlencode( $summary );
return $summary;
}
// 5) (Nice to have) Entries list table cell (avoid "Array" there too)
add_filter( 'gform_entries_field_value', function( $value, $form_id, $field_id, $entry ) {
// $field_id may be "12" (root). Detect our field by type.
$form = GFAPI::get_form( $form_id );
$field = GFFormsModel::get_field( $form, $field_id );
if ( $field && $field->type === 'bmi_calculator' ) {
$h = rgar( $entry, "{$field_id}.1" );
$w = rgar( $entry, "{$field_id}.2" );
$b = rgar( $entry, "{$field_id}.3" );
$parts = array();
if ( $h !== '' && $h !== null ) $parts[] = "H: {$h}cm";
if ( $w !== '' && $w !== null ) $parts[] = "W: {$w}kg";
if ( $b !== '' && $b !== null ) $parts[] = "BMI: {$b}";
return implode( ' | ', $parts );
}
return $value;
}, 10, 4 );
### What this gives you
– **Single Entry view**: your main field line shows `Height: … / Weight: … / BMI: …` instead of `Array`.
– **Sub-inputs** still appear individually (Height (cm), Weight (kg), BMI) because `$this->inputs` is set.
– **Entries list table**: friendly one-line summary.
– **Exports**: one readable cell for the main field; plus individual columns for `.1/.2/.3` if you include them.
No database/schema change needed—just add these methods and you’re set.
Right now GF shows the **first sub-input** (`.1`, Height) in the entries list column.
To override this so only the **BMI result** shows in the main table column:
—
### ✅ Inside your `GF_Field_Bmi_Calculator` class add:
// Control what shows in the Entries List table (the main column)
public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
// Always show just the BMI sub-input (.3)
$bmi = rgar( $entry, "{$this->id}.3" );
return $bmi !== '' && $bmi !== null ? esc_html( $bmi ) : '--';
}
—
### 🔎 What this does
– The **entry detail page** will still show Height, Weight, BMI separately (because of the `get_value_entry_detail()` override).
– The **Entries List (main table)** will show only the BMI result (e.g. `22.5`).
– Export still has `.1`, `.2`, `.3` individually.
—
👉 If you also want to label that column **“BMI”** instead of the field label “BMI Calculator”, you can just rename your field label in the form editor (e.g. `BMI`). The editor label becomes the table header.
Would you like me to also update the `get_value_entry_detail()` so the **main field line** in the single entry view shows *only BMI* too, or do you want Height/Weight/BMI there together?
### Option A (simplest): make BMI the first sub-input
Reorder `$this->inputs` in your field’s constructor so `.3` (BMI) comes first. The Entries list will then use **BMI** as the column header and value.
public function __construct($data = array()) {
parent::__construct($data);
// Put BMI first so the main entries table uses it
$this->inputs = array(
array('id' => "{$this->id}.3", 'label' => __('BMI', 'bmi-calculator-gf')), // first -> used in entries list
array('id' => "{$this->id}.1", 'label' => __('Height (cm)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.2", 'label' => __('Weight (kg)', 'bmi-calculator-gf')),
);
}
*(Keep your existing save/display logic as is. No other changes needed.)*
—
### Option B (per-form control): force the entries list to show only BMI
If you prefer not to change the input order, target the Entries page columns via filters. Replace `123` with your form ID and `FIELD_ID` with this field’s ID.
// Show only the BMI sub-input (.3) for this field in the entries list, with header "BMI"
add_filter('gform_entry_list_columns', function($columns, $form_id) {
if ((int)$form_id !== 123) return $columns;
// Remove the field's default column if present (e.g. FIELD_ID or FIELD_ID.1)
unset($columns['FIELD_ID']);
unset($columns['FIELD_ID.1']);
unset($columns['FIELD_ID.2']);
// Add BMI as the column
$columns["FIELD_ID.3"] = __('BMI', 'bmi-calculator-gf');
return $columns;
}, 10, 2);
// (Optional) Ensure the BMI value renders cleanly (usually not needed)
add_filter('gform_entries_field_value', function($value, $form_id, $field_id, $entry) {
if ((int)$form_id !== 123) return $value;
// Only intercept our BMI sub-input column
if ((string)$field_id === 'FIELD_ID.3') {
$bmi = rgar($entry, 'FIELD_ID.3');
return $bmi !== '' && $bmi !== null ? esc_html($bmi) : '--';
}
return $value;
}, 10, 4);
—
**Recommendation:** Option A is cleaner and global (no per-form filters). It will make the main entries table show **BMI** as the column header and value, while you still keep Height/Weight saved as other sub-inputs.
### Fix (recommended): put BMI first
Change your constructor to put `.3` (BMI) first:
public function __construct($data = array()) {
parent::__construct($data);
// Put BMI first so the Entries list column header becomes "BMI"
$this->inputs = array(
array('id' => "{$this->id}.3", 'label' => __('BMI', 'bmi-calculator-gf')), // first => used in entries list
array('id' => "{$this->id}.1", 'label' => __('Height (cm)', 'bmi-calculator-gf')),
array('id' => "{$this->id}.2", 'label' => __('Weight (kg)', 'bmi-calculator-gf')),
);
}
Then **open the form in the editor and click Update** (this makes GF regenerate the columns). If you use any persistent/object cache, clear it.
Your existing `get_value_entry_list()` already returns only the BMI value, so after the reorder you’ll get:
– Header: **BMI**
– Cell value: BMI (e.g., `22.5`)
—
### If you can’t reorder inputs
Force the column to use `.3` and label it “BMI” via filters (replace `123` with your form ID and `FIELD_ID` with this field’s numeric ID):
add_filter('gform_entry_list_columns', function($columns, $form_id) {
if ((int) $form_id !== 123) return $columns;
// Remove default column variants Gravity Forms may add for this field
unset($columns['FIELD_ID'], $columns['FIELD_ID.1'], $columns['FIELD_ID.2']);
// Add BMI-only column
$columns['FIELD_ID.3'] = __('BMI', 'bmi-calculator-gf');
return $columns;
}, 10, 2);
add_filter('gform_entries_field_value', function($value, $form_id, $field_id, $entry) {
if ((int) $form_id !== 123) return $value;
if ((string) $field_id === 'FIELD_ID.3') {
$bmi = rgar($entry, 'FIELD_ID.3');
return $bmi !== '' && $bmi !== null ? esc_html($bmi) : '--';
}
return $value;
}, 10, 4);
But the clean, global solution is just **reorder `$this->inputs`** and re-save the form.