diff --git a/beat/core/plotterparameter.py b/beat/core/plotterparameter.py
new file mode 100644
index 0000000000000000000000000000000000000000..887ff0530e9f3bfc9db6552f0a3510fb02f31feb
--- /dev/null
+++ b/beat/core/plotterparameter.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+
+###################################################################################
+#                                                                                 #
+# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
+# Contact: beat.support@idiap.ch                                                  #
+#                                                                                 #
+# Redistribution and use in source and binary forms, with or without              #
+# modification, are permitted provided that the following conditions are met:     #
+#                                                                                 #
+# 1. Redistributions of source code must retain the above copyright notice, this  #
+# list of conditions and the following disclaimer.                                #
+#                                                                                 #
+# 2. Redistributions in binary form must reproduce the above copyright notice,    #
+# this list of conditions and the following disclaimer in the documentation       #
+# and/or other materials provided with the distribution.                          #
+#                                                                                 #
+# 3. Neither the name of the copyright holder nor the names of its contributors   #
+# may be used to endorse or promote products derived from this software without   #
+# specific prior written permission.                                              #
+#                                                                                 #
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
+#                                                                                 #
+###################################################################################
+
+
+"""
+================
+plotterparameter
+================
+
+Validation for plotterparameters
+"""
+
+import os
+
+from . import dataformat
+from . import schema
+from . import prototypes
+from . import utils
+from . import loader
+from . import plotter
+
+class Storage(utils.Storage):
+    """Resolves paths for plotterparameters
+
+    Parameters:
+
+      prefix (str): Establishes the prefix of your installation.
+
+      name (str): The name of the plotterparameter object in the format
+        ``<user>/<plotterparameter-name>/<version>``
+    """
+
+    def __init__(self, prefix, name):
+
+        if name.count("/") != 2:
+            raise RuntimeError(f"invalid plotterparameter name: {name}")
+
+        self.username, self.name, self.version = name.split("/")
+        self.fullname = name
+        self.prefix = prefix
+
+        path = utils.hashed_or_simple(self.prefix, "plotterparameters", name, suffix=".json")
+        path = path[:-5]
+
+        super(Storage, self).__init__(path)
+
+
+# ----------------------------------------------------------
+
+
+class Plotterparameter(object):
+    """Each plotterparameter is a specific configuration for the specified
+    plotter. Plotterparameters configure all the parameters of the plotter,
+    much like an experiment contains configurations for the
+    algorithms'/databases' parameters.
+
+    Parameters:
+
+      prefix (str): Establishes the prefix of your installation.
+
+      data (:py:class:`object`, Optional): The piece of data representing the
+        plotterparameter. It must validate against the schema defined for
+        plotterparameters. If a string is passed, it is supposed to be a valid
+        path to a plotterparameter in the designated prefix area.
+
+      plotter_cache (:py:class:`dict`, Optional): A dictionary mapping
+        plotter names to loaded plotters. This parameter is optional and,
+        if passed, may greatly speed-up algorithm loading times as plotters
+        that are already loaded may be re-used.
+
+    Attributes:
+
+      name (str): The plotterparameter name
+
+      description (str): The short description string, loaded from the JSON
+        file if one was set.
+
+      documentation (str): The full-length docstring for this object.
+
+      storage (object): A simple object that provides information about file
+        paths for this plotterparameter
+
+      plotter (object): An object of type :py:class:`.plotter.Plotter`
+        that represents the plotter to which this plotterparameter is applicable.
+
+      errors (list): A list strings containing errors found while loading this
+        plotterparameter.
+
+      data (dict): The original data for this plotterparameter, as loaded by our
+        JSON decoder.
+    """
+
+    def __init__(
+        self,
+        prefix,
+        data,
+        plotter_cache=None,
+    ):
+        self._name = None
+        self.storage = None
+        self.errors = []
+        self.data = None
+        self.plotter = None
+        self.prefix = prefix
+
+        plotter_cache = plotter_cache if plotter_cache is not None else {}
+        self._load(data, plotter_cache)
+
+    def _load(self, data, plotter_cache):
+        """Loads the plotterparameter"""
+
+        self._load_data(data)
+
+        if self.errors:
+            return  # don't proceed with the rest of validation
+
+        self._load_plotter(plotter_cache)
+
+        if self.errors:
+            return  # don't proceed with the rest of validation
+
+        self._validate_data()
+
+    def _load_data(self, data):
+        """Loads given plotterparameter data
+        and the plotterparameter's name
+
+        Parameters:
+
+          data (str): a string (the name of the param),
+            an object (the param data),
+            or a tuple/list (the param data & the plotter data)
+        """
+        # first load the raw plotterparameter data, if data isnt None
+        if isinstance(data, (tuple, list)):  # the user has passed a tuple
+            data, self.plotter = data
+        elif isinstance(data, str):  # user has passed the name
+            self._name = data
+            self.storage = Storage(self.prefix, self._name)
+            if not self.storage.json.exists():
+                self.errors.append(f'Plotterparameter declaration file not found: {data}')
+                return
+            data = self.storage.json.path  # loads data from JSON declaration
+
+        # At this point, `data' can be a dictionary or ``None``
+        # Either way, assign something valid to `self.data'
+        if data is None: # use the dummy plotterparameter
+            self.data, self.errors = prototypes.load("plotterparameter")
+            assert not self.errors, "\n  * %s" % "\n  *".join(self.errors)
+        else:
+            # this runs basic validation, including JSON loading if required
+            self.data, self.errors = schema.validate("plotterparameter", data)
+
+
+    def _load_plotter(self, plotter_cache):
+        """Loads the plotter for the plotterparameter.
+        Assumes that `self.data' has been calculated.
+
+        Parameters:
+
+          plotter_cache (:py:class:`dict`): a dict mapping plotter names
+          to already-loaded plotter objects
+        """
+        # find the plotter if it wasnt given
+        if self.plotter is None:
+            plotter_name = self.data['plotter']
+
+            pl = None
+            if plotter_name in plotter_cache:
+                pl = plotter_cache[plotter_name]
+            else:
+                pl = plotter.Plotter(self.prefix, plotter_name)
+
+            if pl.errors:
+                self.errors.extend(pl.errors)
+                return
+
+            plotter_cache[plotter_name] = pl
+            self.plotter = pl
+
+
+    def _validate_data(self):
+        """Validates that the properties in the plotterparameter's
+        data properly configure the plotter's fields
+        """
+        for key, val in self.data['data'].items():
+            try:
+                self.plotter.clean_parameter(key, val)
+            except KeyError:
+                self.errors.append(f"'{key}' isn't a parameter for plotter {self.plotter.name}")
+                return
+            except ValueError:
+                self.errors.append(f"'{value}' is invalid for parameter {key} of plotter {self.plotter.name}")
+                return
+
+
+    @property
+    def valid(self):
+        """A boolean that indicates if this plotterparameter is valid or not"""
+        return not bool(self.errors)
+
+    @property
+    def name(self):
+        """Returns the name of this object"""
+        return self._name or "__unnamed_plotterparameter__"
+
+    @name.setter
+    def name(self, value):
+        self._name = value
+        self.storage = Storage(self.prefix, value)
+
+    @property
+    def documentation(self):
+        """The full-length description for this object"""
+
+        if not self._name:
+            raise RuntimeError("plotterparameter has no name")
+
+        if self.storage.doc.exists():
+            return self.storage.doc.load()
+        return None
+
+    @documentation.setter
+    def documentation(self, value):
+        """Sets the full-length description for this object"""
+
+        if not self._name:
+            raise RuntimeError("plotterparameter has no name")
+
+        if callable(getattr(value, "read", None)):
+            self.storage.doc.save(value.read())
+        else:
+            self.storage.doc.save(value)
+
+    def hash(self):
+        """Returns the hexadecimal hash for the current plotterparameter"""
+
+        if not self._name:
+            raise RuntimeError("plotterparameter has no name")
+
+        return self.storage.hash()
+
+    def write(self, storage=None):
+        """Writes contents to prefix location
+
+        Parameters:
+
+          storage (:py:class:`.Storage`, Optional): If you pass a new storage,
+            then this object will be written to that storage point rather than
+            its default.
+        """
+
+        if storage is None:
+            if not self._name:
+                raise RuntimeError("plotterparameter has no name")
+            storage = self.storage  # overwrite
+
+        storage.save(str(self), self.code, self.description)
diff --git a/beat/core/prototypes/plotterparameter.json b/beat/core/prototypes/plotterparameter.json
new file mode 100644
index 0000000000000000000000000000000000000000..a2b3ff5ab4f155ae1100706dfd2a2e2ef536c890
--- /dev/null
+++ b/beat/core/prototypes/plotterparameter.json
@@ -0,0 +1,5 @@
+{
+        "plotter": "plot/unknown/1",
+        "description": "",
+        "data": {}
+}
diff --git a/beat/core/schema/plotterparameter/1.json b/beat/core/schema/plotterparameter/1.json
new file mode 100644
index 0000000000000000000000000000000000000000..f99facb4c988bcfb058bfa3983d516a3bcc8282b
--- /dev/null
+++ b/beat/core/schema/plotterparameter/1.json
@@ -0,0 +1,19 @@
+{
+        "$schema": "http://json-schema.org/draft-04/schema#",
+        "title": "Plotter configurator descriptor",
+        "description": "This schema defines the properties of a plotter configuration file",
+
+        "type": "object",
+
+        "properties": {
+                "description": { "$ref": "../common/1.json#/definitions/description" },
+                "plotter": {"$ref": "../common/1.json#/definitions/reference"},
+                "data": {"$ref": "../experiment/common.json#/definitions/parameter_set"},
+                "schema_version": { "$ref": "../common/1.json#/definitions/version" }
+        },
+        "required": [
+                "plotter",
+                "data"
+        ],
+        "additionalProperties": false
+}
diff --git a/beat/core/test/prefix/plotterparameters/plot/config/1.json b/beat/core/test/prefix/plotterparameters/plot/config/1.json
new file mode 100644
index 0000000000000000000000000000000000000000..c349d357fc7c9b51e4caa508a1d8913336e15e8d
--- /dev/null
+++ b/beat/core/test/prefix/plotterparameters/plot/config/1.json
@@ -0,0 +1,11 @@
+{
+        "plotter": "user/scatter/1",
+        "description": "A plotterparameter for the built-in scatter plotter",
+        "data":
+        {
+                "grid": true,
+                "legend": "test data",
+                "mimetype": "image/jpeg",
+                "yaxis_multiplier": 2.5
+        }
+}
diff --git a/beat/core/test/prefix/plotterparameters/plot/invalid/1.json b/beat/core/test/prefix/plotterparameters/plot/invalid/1.json
new file mode 100644
index 0000000000000000000000000000000000000000..ffc03428b15e179aff067e131e1a80ba5137c28e
--- /dev/null
+++ b/beat/core/test/prefix/plotterparameters/plot/invalid/1.json
@@ -0,0 +1,8 @@
+{
+        "plotter": "user/scatter/1",
+        "description": "An invalid plotterparameter for the scatter plotter",
+        "data":
+        {
+                "not_an_option": 2.5
+        }
+}
diff --git a/beat/core/test/prefix/plotterparameters/plot/invalid/2.json b/beat/core/test/prefix/plotterparameters/plot/invalid/2.json
new file mode 100644
index 0000000000000000000000000000000000000000..1fb5230444ed9a4d50b5b4589382ea354fc0dba0
--- /dev/null
+++ b/beat/core/test/prefix/plotterparameters/plot/invalid/2.json
@@ -0,0 +1,7 @@
+{
+        "plotter": "user/not_a_plotter/1",
+        "description": "A plotterparameter for a non-existant plotter",
+        "data":
+        {
+        }
+}
diff --git a/beat/core/test/test_plotterparameter.py b/beat/core/test/test_plotterparameter.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e67deecca7f1fb6e252d9daadf2093f9e1df982
--- /dev/null
+++ b/beat/core/test/test_plotterparameter.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# vim: set fileencoding=utf-8 :
+
+###################################################################################
+#                                                                                 #
+# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
+# Contact: beat.support@idiap.ch                                                  #
+#                                                                                 #
+# Redistribution and use in source and binary forms, with or without              #
+# modification, are permitted provided that the following conditions are met:     #
+#                                                                                 #
+# 1. Redistributions of source code must retain the above copyright notice, this  #
+# list of conditions and the following disclaimer.                                #
+#                                                                                 #
+# 2. Redistributions in binary form must reproduce the above copyright notice,    #
+# this list of conditions and the following disclaimer in the documentation       #
+# and/or other materials provided with the distribution.                          #
+#                                                                                 #
+# 3. Neither the name of the copyright holder nor the names of its contributors   #
+# may be used to endorse or promote products derived from this software without   #
+# specific prior written permission.                                              #
+#                                                                                 #
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
+#                                                                                 #
+###################################################################################
+
+import nose.tools
+
+from . import prefix
+from ..plotterparameter import Plotterparameter
+
+def test_default():
+    # test for the "dummy" plotterparameter
+    p = Plotterparameter(prefix, data=None)
+    nose.tools.assert_false(p.valid)
+
+def test_plot_config_1():
+    # test for a simple plotterparameter for a simple plotter
+    p = Plotterparameter(prefix, "plot/config/1")
+    nose.tools.assert_true(p.valid, "\n  * %s" % "\n  * ".join(p.errors))
+
+def test_plot_invalid_1():
+    # test for invalid parameter name
+    p = Plotterparameter(prefix, "plot/invalid/1")
+    nose.tools.assert_false(p.valid)
+    nose.tools.assert_true(p.errors[0] == "'not_an_option' isn't a parameter for plotter user/scatter/1")
+
+def test_plot_invalid_2():
+    # test for invalid "plotter" field
+    p = Plotterparameter(prefix, "plot/invalid/2")
+    nose.tools.assert_false(p.valid)
+    nose.tools.assert_true(p.errors[0] == "Plotter declaration file not found: user/not_a_plotter/1")