diff --git a/CHANGELOG.md b/CHANGELOG.md
index c92c3ea6d6..d82248011d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
+### Added
+- Add the `custom_data` argument to `px.histogram`, so extra columns can be attached to histogram traces for use in Dash callbacks and selection events, as in the other Plotly Express functions [[#3859](https://github.com/plotly/plotly.py/issues/3859)]
+
### Fixed
- Fix `mpl_to_plotly` tick marker mirroring, axis positioning, and trace visibility for twinned and overlaid subplots (such as `twinx` and `twiny`) [[#5310](https://github.com/plotly/plotly.py/pull/5310)], with thanks to @robertoffmoura for the contribution!
- Fix `mpl_to_plotly` losing matplotlib axis line colors by exporting the color of each axis's displayed spine to the plotly x and y axis `linecolor` [[#5311](https://github.com/plotly/plotly.py/pull/5311)], with thanks to @robertoffmoura for the contribution!
diff --git a/plotly/express/_chart_types.py b/plotly/express/_chart_types.py
index f78a257cab..d9115745cc 100644
--- a/plotly/express/_chart_types.py
+++ b/plotly/express/_chart_types.py
@@ -462,6 +462,7 @@ def histogram(
facet_col_spacing=None,
hover_name=None,
hover_data=None,
+ custom_data=None,
animation_frame=None,
animation_group=None,
category_orders=None,
diff --git a/tests/test_optional/test_px/test_px.py b/tests/test_optional/test_px/test_px.py
index d4773de66f..88f20b6fed 100644
--- a/tests/test_optional/test_px/test_px.py
+++ b/tests/test_optional/test_px/test_px.py
@@ -68,6 +68,28 @@ def test_custom_data_scatter(backend):
)
+def test_custom_data_histogram(backend):
+ tips = nw.from_native(px.data.tips(return_type=backend))
+ # No custom data
+ fig = px.histogram(tips.to_native(), x="total_bill")
+ assert fig.data[0].customdata is None
+ # Custom data is carried per row of the input, not per bin
+ fig = px.histogram(tips.to_native(), x="total_bill", custom_data=["tip", "day"])
+ assert fig.data[0].customdata.shape == (len(tips), 2)
+ assert np.all(fig.data[0].customdata[:, 0] == tips.get_column("tip").to_numpy())
+ assert np.all(fig.data[0].customdata[:, 1] == tips.get_column("day").to_numpy())
+ # Bins are aggregates, so custom data does not show up in the hover label
+ assert fig.data[0].hovertemplate == "total_bill=%{x}
count=%{y}"
+ # Each colored trace carries the custom data of its own rows
+ fig = px.histogram(
+ tips.to_native(), x="total_bill", color="sex", custom_data=["tip"]
+ )
+ for trace in fig.data:
+ group = tips.filter(nw.col("sex") == trace.name)
+ assert trace.customdata.shape == (len(group), 1)
+ assert np.all(trace.customdata[:, 0] == group.get_column("tip").to_numpy())
+
+
def test_labels(backend):
tips = nw.from_native(px.data.tips(return_type=backend))
fig = px.scatter(