diff --git a/evolver/device.py b/evolver/device.py
index c51429a..e2b8e94 100644
--- a/evolver/device.py
+++ b/evolver/device.py
@@ -1,6 +1,9 @@
 import logging
 import time
 from collections import defaultdict
+from math import prod
+
+from pydantic import Field, ValidationInfo, field_validator
 
 from evolver.base import BaseInterface, ConfigDescriptor
 from evolver.connection.interface import Connection
@@ -20,6 +23,12 @@ class Evolver(BaseInterface):
     class Config(BaseInterface.Config):
         name: str = "Evolver"
         experiment: str = "unspecified"
+        vial_layout: list[int] = Field(
+            default=settings.DEFAULT_VIAL_LAYOUT,
+            description="The layout of the vials in 2 or 3 dimensions. Always left-to-right bottom-top-top order.",
+            min_length=2,
+            max_length=3,
+        )
         vials: list = list(range(settings.DEFAULT_NUMBER_OF_VIALS_PER_BOX))
         hardware: dict[str, ConfigDescriptor | HardwareDriver] = {}
         controllers: list[ConfigDescriptor | Controller] = []
@@ -33,6 +42,14 @@ class Config(BaseInterface.Config):
         skip_control_on_read_failure: bool = True
         log_level: int = EVENT
 
+        @field_validator("vials", mode="after")
+        @classmethod
+        def validate_vials(cls, value: list[int], info: ValidationInfo):
+            total_slots = prod(info.data["vial_layout"])
+            if len(value) > total_slots or max(value) >= total_slots:
+                raise ValueError(f"Vials configured exceed total available slots from layout {total_slots}")
+            return value
+
     def __init__(self, *args, **kwargs):
         self.last_read = defaultdict(lambda: int(-1))
         super().__init__(*args, evolver=self, **kwargs)
diff --git a/evolver/settings.py b/evolver/settings.py
index 9fa36fa..78dad61 100644
--- a/evolver/settings.py
+++ b/evolver/settings.py
@@ -14,6 +14,7 @@ class Settings(BaseSettings):
     CONNECTION_REUSE_POLICY_DEFAULT: bool = True
     DEFAULT_LOOP_INTERVAL: int = 20
     DEFAULT_NUMBER_OF_VIALS_PER_BOX: int = 16
+    DEFAULT_VIAL_LAYOUT: list[int] = [4, 4]
     LOG_LEVEL: str = "INFO"
     LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
     ROOT_CALIBRATOR_FILE_STORAGE_PATH: Path = Path("calibration_files")
diff --git a/evolver/tests/test_device.py b/evolver/tests/test_device.py
index aeb42da..1ef1d3c 100644
--- a/evolver/tests/test_device.py
+++ b/evolver/tests/test_device.py
@@ -215,3 +215,10 @@ def off(self):
         assert not demo_evolver.enable_control
         assert demo_evolver.hardware["testeffector"].aborted
         assert not demo_evolver.enable_control
+
+    def test_vials_layout_validation(self):
+        with pytest.raises(ValueError, match="Vials configured exceed total available slots"):
+            Evolver.Config(vials=[0, 1, 2, 3, 4], vial_layout=[2, 2])
+        with pytest.raises(ValueError, match="Vials configured exceed total available slots"):
+            Evolver.Config(vials=[0, 1, 2, 8], vial_layout=[2, 2])
+        Evolver.Config(vials=[0, 1, 2, 3], vial_layout=[2, 2])