diff --git a/src/calng/mdl_geometry_base.py b/src/calng/mdl_geometry_base.py
index 4115e6038263295886a40f498d86c2f3b1724b0c..4b4dad872f4aa1fbeaa21b90e5e733f03e2b0213 100644
--- a/src/calng/mdl_geometry_base.py
+++ b/src/calng/mdl_geometry_base.py
@@ -20,6 +20,7 @@ from karabo.middlelayer import (
     Slot,
     State,
     String,
+    Unit,
     VectorChar,
     VectorHash,
 )
@@ -53,7 +54,8 @@ class GeometryFileNode(Configurable):
         displayedName="Update manual settings",
         description="If this flag is on, the manual settings on this device will be "
         "updated according to the loaded geometry file. This is useful when you want "
-        "to load a file and then tweak the geometry a bit.",
+        "to load a file and then tweak the geometry a bit. This will zero current "
+        "offset.",
         assignment=Assignment.OPTIONAL,
         accessMode=AccessMode.RECONFIGURABLE,
     )
@@ -187,20 +189,40 @@ class ManualGeometryBase(Device):
         self.logger.log(level, text)
 
 
-def makeXYCoordinateNode(default_x, default_y):
+def makeXYCoordinateNode(
+    default_x, default_y, x_args=None, y_args=None, node_args=None
+):
     class XYCoordinate(Configurable):
         x = Double(
             defaultValue=default_x,
             accessMode=AccessMode.RECONFIGURABLE,
             assignment=Assignment.OPTIONAL,
+            **({} if x_args is None else x_args),
         )
         y = Double(
             defaultValue=default_y,
             accessMode=AccessMode.RECONFIGURABLE,
             assignment=Assignment.OPTIONAL,
+            **({} if y_args is None else y_args),
         )
 
-    return Node(XYCoordinate)
+    return Node(XYCoordinate, **({} if node_args is None else node_args))
+
+
+def makeXYOffsetNode():
+    return makeXYCoordinateNode(
+        0,
+        0,
+        x_args={"unitSymbol": Unit.METER},
+        y_args={"unitSymbol": Unit.METER},
+        node_args={
+            "displayedName": "Offset",
+            "description": "See EXtra-geom documentation for details. This offset is "
+            "applied to entire detector after initial geometry is created from manual "
+            "parameters. Example: To move entire geometry up by 2 mm relative to beam, "
+            "set offset.y to 2e-3.",
+        },
+    )
 
 
 def makeQuadrantCornersNode(default_values):
@@ -212,6 +234,7 @@ def makeQuadrantCornersNode(default_values):
         Q2 = makeXYCoordinateNode(*default_values[1])
         Q3 = makeXYCoordinateNode(*default_values[2])
         Q4 = makeXYCoordinateNode(*default_values[3])
+        offset = makeXYOffsetNode()
 
     return Node(QuadrantCornersNode)
 
@@ -237,15 +260,23 @@ class ManualQuadrantsGeometryBase(ManualGeometryBase):
         with self.push_state(State.CHANGING):
             geometry = self.geometry_class.from_quad_positions(
                 [(Q.x.value, Q.y.value) for Q in self._quadrant_corners]
+            ).offset(
+                (
+                    self.quadrantCorners.offset.x.value,
+                    self.quadrantCorners.offset.y.value,
+                )
             )
             await self._set_geometry(geometry)
 
     def _update_manual_from_current(self):
+        # TODO: consider what to do about offset
         for corner, (x, y) in zip(
             self._quadrant_corners, self.geometry.quad_positions()
         ):
             corner.x = x
             corner.y = y
+        self.quadrantCorners.offset.x = 0
+        self.quadrantCorners.offset.y = 0
 
 
 class MdlAgipd1MGeometry(ManualQuadrantsGeometryBase):
@@ -293,6 +324,7 @@ class ModuleListItem(Configurable):
 
 class ManualModuleListGeometryBase(ManualGeometryBase):
     moduleList = None  # subclass must define (with nice defaults)
+    offset = makeXYOffsetNode()
 
     @Slot(
         displayedName="Set from device",
@@ -307,7 +339,7 @@ class ManualModuleListGeometryBase(ManualGeometryBase):
                     (orient_x, orient_y)
                     for (_, _, orient_x, orient_y) in self.moduleList.value
                 ],
-            )
+            ).offset((self.offset.x.value, self.offset.y.value))
             self._pickled_geometry = pickle.dumps(self._geometry)
 
     def _update_manual_from_current(self):