
A Strapi v5 plugin that provides a custom field for adding interactive hotspots (points and rectangles) to images with various action types.
released December 20, 2025
npm install strapi-plugin-image-hotspotA Strapi v5 plugin that provides a custom field for adding interactive hotspots (points and rectangles) to images with various action types.

This plugin is automatically loaded by Strapi when placed in the src/plugins directory.
The plugin includes server-side middleware that automatically:
This means when you fetch data via the API, the image field in your hotspot data will already contain the full media object, not just an ID.
In your content type schema, use:
{
"attributes": {
"imageHotspot": {
"type": "customField",
"customField": "plugin::image-hotspot::image-hotspot"
}
}
}Navigate to a URL when the hotspot is clicked:
url: The target URLtarget: "_self" (same tab) or "_blank" (new tab)Display content in a modal dialog:
modalTitle: Title of the modalmodalContent: Content to display in the modalShow tooltip text on hover:
tooltipText: Text to display in the tooltipExecute a custom JavaScript function when the hotspot is clicked:
callbackName: Name of the function to call (e.g., "handleHotspotClick")callbackParams: JSON object with parameters to pass to the functionExample:
{
"type": "callback",
"callbackName": "handleHotspotClick",
"callbackParams": {
"hotspotId": "123",
"action": "navigate"
}
}On the frontend, you would implement the callback function:
window.handleHotspotClick = function (params) {
console.log("Hotspot clicked with params:", params);
// Your custom logic here
};Define a custom action with a name and JSON payload for flexible frontend handling:
customName: Name identifier for the custom action (e.g., "product-detail")customPayload: JSON object with any data structure you needExample:
{
"type": "custom",
"customName": "product-detail",
"customPayload": {
"productId": "123",
"variant": "blue",
"showModal": true
}
}On the frontend, you can handle custom actions based on the customName and use the customPayload as needed:
function handleHotspotClick(hotspot) {
if (hotspot.action?.type === "custom") {
switch (hotspot.action.customName) {
case "product-detail":
// Handle product detail action
showProductDetail(hotspot.action.customPayload);
break;
default:
console.log(
"Custom action:",
hotspot.action.customName,
hotspot.action.customPayload,
);
}
}
}When rendering the image with hotspots on your frontend, you'll need to:
Note: The server automatically populates the image field with the full media object, so you can directly access data.image.url without needing to fetch the media separately.
Example React component:
import { ImageHotspotValue } from "./types";
function HotspotImage({ data }: { data: ImageHotspotValue }) {
const handleHotspotClick = (hotspot: Hotspot) => {
if (!hotspot.action) return;
switch (hotspot.action.type) {
case "link":
if (hotspot.action.url) {
window.open(hotspot.action.url, hotspot.action.target || "_self");
}
break;
case "modal":
// Show modal with hotspot.action.modalTitle and hotspot.action.modalContent
break;
case "tooltip":
// Show tooltip with hotspot.action.tooltipText
break;
case "callback":
if (
hotspot.action.callbackName &&
window[hotspot.action.callbackName]
) {
window[hotspot.action.callbackName](hotspot.action.callbackParams);
}
break;
case "custom":
// Handle custom action based on customName and customPayload
console.log(
"Custom action:",
hotspot.action.customName,
hotspot.action.customPayload,
);
// Implement your custom action handling logic here
break;
}
};
return (
<div className="hotspot-container">
<img src={data.image?.url} alt={data.image?.alternativeText} />
{data.hotspots?.map((hotspot) => (
<div
key={hotspot.id}
className={`hotspot hotspot-${hotspot.type}`}
style={{
left: `${hotspot.x}%`,
top: `${hotspot.y}%`,
width: hotspot.width ? `${hotspot.width}%` : undefined,
height: hotspot.height ? `${hotspot.height}%` : undefined,
}}
onClick={() => handleHotspotClick(hotspot)}
>
{hotspot.label}
</div>
))}
</div>
);
}MIT
Share your work with the community and get it listed in the Strapi ecosystem for everyone to discover and use.
Submit
