diff --git a/program/serializers.py b/program/serializers.py
index 1cef88141e360fa12078ee23dddd8bca1e54fdd6..1837a54cf526b82ff5cdaf1aa5aa222979e33c70 100644
--- a/program/serializers.py
+++ b/program/serializers.py
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
+import re
 from typing import List, TypedDict
 
 from rest_framework import serializers
@@ -197,6 +198,22 @@ class HostLinkSerializer(serializers.ModelSerializer):
         fields = ("type", "url")
 
 
+class PPOIField(serializers.CharField):
+    def validate_format(self, value: str):
+        if not re.match(r"\d(?:\.\d+)?x\d(?:\.\d+)?", value):
+            raise serializers.ValidationError("PPOI must match format: ${float}x${float}")
+
+    def __init__(self, **kwargs):
+        kwargs["max_length"] = 20
+        kwargs.setdefault("validators", [])
+        kwargs["validators"].append(self.validate_format)
+        super().__init__(**kwargs)
+
+    def to_representation(self, value: tuple[float, float]):
+        [left, top] = value
+        return f"{left}x{top}"
+
+
 class Thumbnail(TypedDict):
     width: float
     height: float
@@ -204,7 +221,7 @@ class Thumbnail(TypedDict):
 
 
 class ImageSerializer(serializers.ModelSerializer):
-    ppoi = serializers.CharField(max_length=20)  # PPOIField max_length
+    ppoi = PPOIField()
     thumbnails = serializers.SerializerMethodField()
 
     @staticmethod