diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js
index cc366193eb3..4888f353cf2 100644
--- a/src/components/fx/hover.js
+++ b/src/components/fx/hover.js
@@ -1073,7 +1073,11 @@ function cleanPoint(d, hovermode) {
         d.yLabel = ('yLabel' in d) ? d.yLabel : Axes.hoverLabelText(d.ya, d.yLabelVal);
         d.yVal = d.ya.c2d(d.yLabelVal);
     }
-    if(d.zLabelVal !== undefined) d.zLabel = String(d.zLabelVal);
+
+    // Traces like heatmaps generate the zLabel in their hoverPoints function
+    if(d.zLabelVal !== undefined && d.zLabel === undefined) {
+        d.zLabel = String(d.zLabelVal);
+    }
 
     // for box means and error bars, add the range to the label
     if(!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) {
diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js
index 5414afe9f48..8db880b2317 100644
--- a/src/traces/contour/attributes.js
+++ b/src/traces/contour/attributes.js
@@ -30,6 +30,7 @@ module.exports = extendFlat({
     transpose: heatmapAttrs.transpose,
     xtype: heatmapAttrs.xtype,
     ytype: heatmapAttrs.ytype,
+    zhoverformat: heatmapAttrs.zhoverformat,
 
     connectgaps: heatmapAttrs.connectgaps,
 
diff --git a/src/traces/contour/defaults.js b/src/traces/contour/defaults.js
index a616f818045..1e30d6b17c5 100644
--- a/src/traces/contour/defaults.js
+++ b/src/traces/contour/defaults.js
@@ -34,4 +34,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
 
     handleContoursDefaults(traceIn, traceOut, coerce);
     handleStyleDefaults(traceIn, traceOut, coerce, layout);
+
+    coerce('zhoverformat');
+    // Needed for formatting of hoverlabel if format is not explicitly specified
+    traceOut._separators = layout.separators;
 };
diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js
index 372197a5f7b..e967f17860a 100644
--- a/src/traces/heatmap/attributes.js
+++ b/src/traces/heatmap/attributes.js
@@ -100,6 +100,17 @@ module.exports = extendFlat({}, {
         editType: 'plot',
         description: 'Sets the vertical gap (in pixels) between bricks.'
     },
+    zhoverformat: {
+        valType: 'string',
+        dflt: '',
+        role: 'style',
+        editType: 'none',
+        description: [
+            'Sets the hover text formatting rule using d3 formatting mini-languages',
+            'which are very similar to those in Python. See:',
+            'https://github.com/d3/d3-format/blob/master/README.md#locale_format'
+        ].join(' ')
+    },
 },
     colorscaleAttrs,
     { autocolorscale: extendFlat({}, colorscaleAttrs.autocolorscale, {dflt: false}) },
diff --git a/src/traces/heatmap/defaults.js b/src/traces/heatmap/defaults.js
index 3dbbaa0f380..b80c76d6e2b 100644
--- a/src/traces/heatmap/defaults.js
+++ b/src/traces/heatmap/defaults.js
@@ -40,4 +40,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
     coerce('connectgaps', hasColumns(traceOut) && (traceOut.zsmooth !== false));
 
     colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
+
+    coerce('zhoverformat');
+    traceOut._separators = layout.separators; // Needed for formatting of hoverlabel if format is not explicitly specified
 };
diff --git a/src/traces/heatmap/hover.js b/src/traces/heatmap/hover.js
index e3a870f3cb7..310f92c0b84 100644
--- a/src/traces/heatmap/hover.js
+++ b/src/traces/heatmap/hover.js
@@ -11,6 +11,7 @@
 
 var Fx = require('../../components/fx');
 var Lib = require('../../lib');
+var Axes = require('../../plots/cartesian/axes');
 
 var MAXDIST = Fx.constants.MAXDIST;
 
@@ -26,6 +27,9 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, contour)
         y = cd0.y,
         z = cd0.z,
         zmask = cd0.zmask,
+        range = [trace.zmin, trace.zmax],
+        zhoverformat = trace.zhoverformat,
+        _separators = trace._separators,
         x2 = x,
         y2 = y,
         xl,
@@ -99,6 +103,17 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, contour)
         text = cd0.text[ny][nx];
     }
 
+    var zLabel;
+    // dummy axis for formatting the z value
+    var dummyAx = {
+        type: 'linear',
+        range: range,
+        hoverformat: zhoverformat,
+        _separators: _separators
+    };
+    var zLabelObj = Axes.tickText(dummyAx, zVal, 'hover');
+    zLabel = zLabelObj.text;
+
     return [Lib.extendFlat(pointData, {
         index: [ny, nx],
         // never let a 2D override 1D type as closest point
@@ -110,6 +125,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, contour)
         xLabelVal: xl,
         yLabelVal: yl,
         zLabelVal: zVal,
+        zLabel: zLabel,
         text: text
     })];
 };
diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js
index b6be796bdbe..b8fa0ad2693 100644
--- a/test/jasmine/tests/hover_label_test.js
+++ b/test/jasmine/tests/hover_label_test.js
@@ -499,6 +499,43 @@ describe('hover info', function() {
             .catch(fail)
             .then(done);
         });
+
+        it('should display correct label content with specified format', function(done) {
+            var gd = createGraphDiv();
+
+            Plotly.plot(gd, [{
+                type: 'heatmap',
+                y: [0, 1],
+                z: [[1.11111, 2.2222, 3.33333], [4.44444, 5.55555, 6.66666]],
+                name: 'one',
+                zhoverformat: '.2f'
+            }, {
+                type: 'heatmap',
+                y: [2, 3],
+                z: [[1, 2, 3], [2, 2, 1]],
+                name: 'two'
+            }], {
+                width: 500,
+                height: 400,
+                margin: {l: 0, t: 0, r: 0, b: 0}
+            })
+            .then(function() {
+                _hover(gd, 250, 100);
+                assertHoverLabelContent({
+                    nums: 'x: 1\ny: 3\nz: 2',
+                    name: 'two'
+                });
+            })
+            .then(function() {
+                _hover(gd, 250, 300);
+                assertHoverLabelContent({
+                    nums: 'x: 1\ny: 1\nz: 5.56',
+                    name: 'one'
+                });
+            })
+            .catch(fail)
+            .then(done);
+        });
     });
 
     describe('hoverformat', function() {
diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js
index 9915cb9583f..72f29593e3f 100644
--- a/test/jasmine/tests/select_test.js
+++ b/test/jasmine/tests/select_test.js
@@ -717,10 +717,11 @@ describe('Test select box and lasso per trace:', function() {
         addInvisible(fig, false);
 
         // add a trace with no locations which will then make trace invisible, lacking DOM elements
-        fig.data.push(Lib.extendDeep({}, fig.data[0]));
-        fig.data[1].text = [];
-        fig.data[1].locations = [];
-        fig.data[1].z = [];
+        var emptyChoroplethTrace = Lib.extendDeep({}, fig.data[0]);
+        emptyChoroplethTrace.text = [];
+        emptyChoroplethTrace.locations = [];
+        emptyChoroplethTrace.z = [];
+        fig.data.push(emptyChoroplethTrace);
 
         Plotly.plot(gd, fig)
         .then(function() {