diff --git a/gui/package.json b/gui/package.json
index 897f36112cf0a908a33cbf8fb25f2992556d9740..a40518630d67b01df6d1571326746a3c2622266a 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -28,6 +28,7 @@
     "dompurify": "^3.0.1",
     "fetch": "^1.1.0",
     "html-to-react": "^1.3.3",
+    "jmespath": "^0.16.0",
     "keycloak-js": "^18.0.1",
     "lodash": "^4.17.15",
     "material-ui-chip-input": "^1.1.0",
@@ -95,13 +96,13 @@
     "eject": "craco eject"
   },
   "devDependencies": {
+    "@babel/eslint-parser": "^7.22.15",
     "@craco/craco": "^6.4.5",
     "@material-ui/codemod": "^4.5.0",
     "@testing-library/jest-dom": "^5.16.4",
     "@testing-library/react": "^12.1.5",
     "@testing-library/user-event": "^14.2.0",
     "@welldone-software/why-did-you-render": "^7.0.1",
-    "@babel/eslint-parser": "^7.22.15",
     "craco-workbox": "^0.2.0",
     "eslint": "^7.11.0",
     "eslint-config-standard": "^17.0.0",
diff --git a/gui/src/components/archive/ArchiveSearchBar.js b/gui/src/components/archive/ArchiveSearchBar.js
index b8b13ff4a33df3746ea400155c972d3b066210f7..675fbc5bc950275f80f25e3e015825a6b4ab87aa 100644
--- a/gui/src/components/archive/ArchiveSearchBar.js
+++ b/gui/src/components/archive/ArchiveSearchBar.js
@@ -22,7 +22,7 @@ import clsx from 'clsx'
 import { Paper, Tooltip, IconButton } from '@material-ui/core'
 import SearchIcon from '@material-ui/icons/Search'
 import { useStyles } from '../search/SearchBar'
-import { InputMetainfo } from '../search/input/InputMetainfo'
+import { InputMetainfoControlled } from '../search/input/InputMetainfo'
 
 /**
  * This component shows a search bar with autocomplete functionality.
@@ -43,7 +43,7 @@ const ArchiveSearchBar = React.memo(({options, group, onChange, className}) => {
   }, [options, onChange])
 
   return <Paper className={clsx(className, styles.root)}>
-    <InputMetainfo
+    <InputMetainfoControlled
       value={value}
       onChange={setValue}
       onSelect={handleSelect}
diff --git a/gui/src/components/editQuantity/NumberEditQuantity.js b/gui/src/components/editQuantity/NumberEditQuantity.js
index 7b51b4084c0da8711589d0eb17f5bafc258297f9..63aee68de621864e1e9df0a0c407195ca22e3e8c 100644
--- a/gui/src/components/editQuantity/NumberEditQuantity.js
+++ b/gui/src/components/editQuantity/NumberEditQuantity.js
@@ -89,7 +89,7 @@ export const NumberField = React.memo((props) => {
     }
 
     // Try to parse the quantity. Value is required, unit is optional.
-    const {unit: parsedUnit, value, valueString, error} = parseQuantity(input, true, false, dimension)
+    const {unit: parsedUnit, value, valueString, error} = parseQuantity(input, dimension, true, false)
     previousNumberPart.current = valueString
     if (parsedUnit) {
       previousUnitLabel.current = parsedUnit.label()
@@ -207,7 +207,7 @@ export const NumberEditQuantity = React.memo((props) => {
   const {quantityDef, value, onChange, ...otherProps} = props
   const {units, isReset} = useUnitContext()
   const defaultUnit = useMemo(() => quantityDef.unit && new Unit(quantityDef.unit), [quantityDef])
-  const dimension = defaultUnit && defaultUnit.dimension(false)
+  const dimension = defaultUnit && defaultUnit.dimension()
   const [checked, setChecked] = useState(true)
   const [displayedValue, setDisplayedValue] = useState(true)
   const {defaultDisplayUnit: deprecatedDefaultDisplayUnit, ...fieldProps} = getFieldProps(quantityDef)
@@ -300,7 +300,7 @@ export const UnitSelect = React.memo(({options, unit, onChange, dimension, disab
 
   // Validate input and submit unit if valid
   const submit = useCallback((val) => {
-    const {unit, error} = parseQuantity(val, false, true, dimension)
+    const {unit, error} = parseQuantity(val, dimension, false, true)
     if (error) {
       setError(error)
     } else {
diff --git a/gui/src/components/plotting/Plot.js b/gui/src/components/plotting/Plot.js
index 0cd785215c60d455ab8f93c92431b66c996d99ad..75f8eee4dc31faa4523533a55aa64f7791120032 100644
--- a/gui/src/components/plotting/Plot.js
+++ b/gui/src/components/plotting/Plot.js
@@ -481,6 +481,14 @@ const Plot = React.memo(forwardRef(({
   // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [layoutSubject, fixedMargins, canvasNode, firstRender, data, finalConfig, finalLayout, sizeReady, canvasRef, onRelayout, onRelayouting, fixMargins, getLayout])
 
+  // If the callbacks change, we need to update them in the canvas as well.
+  // TODO: Do this for other callbacks as well.
+  useEffect(() => {
+    if (onSelected && canvasRef?.current?.on) {
+      canvasRef.current.on('plotly_selected', onSelected)
+    }
+  }, [onSelected, canvasRef])
+
   // For resetting the view.
   const handleReset = useCallback(() => {
     if (canvasRef?.current) {
diff --git a/gui/src/components/plotting/PlotScatter.js b/gui/src/components/plotting/PlotScatter.js
index 33b950ad4c4c15e8db1a9c50bca3d61d77f13114..085e878296416489b228adcfa2c0feae50f566c0 100644
--- a/gui/src/components/plotting/PlotScatter.js
+++ b/gui/src/components/plotting/PlotScatter.js
@@ -18,15 +18,10 @@
 import React, {useState, useEffect, useMemo, useCallback, forwardRef} from 'react'
 import PropTypes from 'prop-types'
 import { makeStyles, useTheme } from '@material-ui/core'
-import { getDeep, hasWebGLSupport, parseQuantityName } from '../../utils'
-import { Quantity } from '../units/Quantity'
-import { Unit } from '../units/Unit'
-import { useUnitContext } from '../units/UnitContext'
+import { hasWebGLSupport } from '../../utils'
 import * as d3 from 'd3'
-import { isArray, isNil } from 'lodash'
 import FilterTitle from '../search/FilterTitle'
 import Plot from './Plot'
-import { useSearchContext } from '../search/SearchContext'
 import { useHistory } from 'react-router-dom'
 import { getUrl } from '../nav/Routes'
 
@@ -93,12 +88,9 @@ const PlotScatter = React.memo(forwardRef((
 {
   data,
   title,
-  x,
-  y,
-  color,
-  unitX,
-  unitY,
-  unitColor,
+  xAxis,
+  yAxis,
+  colorAxis,
   discrete,
   autorange,
   dragmode,
@@ -110,8 +102,6 @@ const PlotScatter = React.memo(forwardRef((
   const styles = useStyles()
   const theme = useTheme()
   const [finalData, setFinalData] = useState(!data ? data : undefined)
-  const {units} = useUnitContext()
-  const { filterData } = useSearchContext()
   const history = useHistory()
 
   // Side effect that runs when the data that is displayed should change. By
@@ -124,12 +114,7 @@ const PlotScatter = React.memo(forwardRef((
       return
     }
 
-    // TODO: The API should support an "exists" query that could be used to
-    // return hits that actually have the requested values. This way we would
-    // get a better match for the query size and we would not need to manually
-    // validate and check the results.
-
-    const hoverTemplate = (xLabel, yLabel, colorLabel, xUnit, yUnit, colorUnit, discrete) => {
+    const hoverTemplate = (xLabel, yLabel, colorLabel, xUnit, yUnit, colorUnit) => {
       let template = `<b>Click to go to entry page</b>` +
         `<br>` +
         `${xLabel || ''}: %{x} ${xUnit === 'dimensionless' ? '' : xUnit}<br>` +
@@ -142,44 +127,27 @@ const PlotScatter = React.memo(forwardRef((
       return template
     }
 
-    const values = data
-      .map((d) => ({
-        x: getDeep(d, parseQuantityName(x).path),
-        y: getDeep(d, parseQuantityName(y).path),
-        color: color && getDeep(d, parseQuantityName(color).path),
-        entry_id: d.entry_id
-      }))
-      // We filter out points that don't have x, y values. Also for continuous
-      // coloring the color property needs to be defined.
-      .filter((d) => !isNil(d.x) && !isNil(d.y) && (discrete || !color || !isNil(d.color)))
-
     // If dealing with a quantized color, each group is separated into it's own
     // trace which has a legend as well.
-    const unitXObj = new Unit(unitX)
-    const unitYObj = new Unit(unitY)
-    const unitColorObj = new Unit(unitColor)
     const traces = []
-    if (color && discrete) {
-      const colorArray = []
-      values.forEach((d) => {
-        if (isNil(d.color)) {
-          d.color = 'undefined'
-        } else if (isArray(d.color)) {
-          d.color = d.color.sort().join(", ")
-        }
-        colorArray.push(d.color)
-      })
-      const options = [...new Set(colorArray)]
+    if (colorAxis?.quantity && discrete) {
+      const options = [...new Set(data.color)]
       const nOptions = options.length
       const scale = d3.scaleSequential([0, 1], d3.interpolateTurbo)
       const offset = 0.1
       for (const option of options) {
-        const optionValues = values.filter((d) => d.color === option)
-        const xArray = optionValues.map((d) => new Quantity(d.x, unitXObj).toSystem(units).value())
-        const yArray = optionValues.map((d) => new Quantity(d.y, unitYObj).toSystem(units).value())
-        const unitLabelX = optionValues[0] ? new Quantity(optionValues[0].x, unitXObj).toSystem(units).label() : ''
-        const unitLabelY = optionValues[0] ? new Quantity(optionValues[0].y, unitYObj).toSystem(units).label() : ''
-        const entryIdArray = optionValues.map((d) => d.entry_id)
+        const xArray = []
+        const yArray = []
+        const colorArray = []
+        const entryIdArray = []
+        for (let i = 0; i < data.color.length; ++i) {
+          if (data.color[i] === option) {
+            xArray.push(data.x[i])
+            yArray.push(data.y[i])
+            colorArray.push(data.color[i])
+            entryIdArray.push(data.id[i])
+          }
+        }
         traces.push({
           x: xArray,
           y: yArray,
@@ -191,13 +159,12 @@ const PlotScatter = React.memo(forwardRef((
           textposition: 'top center',
           showlegend: true,
           hovertemplate: hoverTemplate(
-            filterData[x]?.label,
-            filterData[y]?.label,
-            filterData[color]?.label,
-            unitLabelX,
-            unitLabelY,
-            '',
-            true
+            xAxis.title,
+            yAxis.title,
+            colorAxis.title,
+            xAxis.unit,
+            yAxis.unit,
+            ''
           ),
           marker: {
             size: 8,
@@ -210,37 +177,29 @@ const PlotScatter = React.memo(forwardRef((
         })
       }
     // When dealing with a continuous color, display a colormap
-    } else if (color && !discrete) {
-      const xArray = values.map((d) => new Quantity(d.x, unitXObj).toSystem(units).value())
-      const yArray = values.map((d) => new Quantity(d.y, unitYObj).toSystem(units).value())
-      const colors = values.map((d) => new Quantity(d.color, unitColorObj).toSystem(units).value())
-      const entryIdArray = values.map((d) => d.entry_id)
-      const unitLabelX = values[0] ? new Quantity(values[0].x, unitXObj).toSystem(units).label() : ''
-      const unitLabelY = values[0] ? new Quantity(values[0].y, unitYObj).toSystem(units).label() : ''
-      const unitLabelColors = values[0] ? new Quantity(values[0].color, unitColorObj).toSystem(units).label() : ''
+    } else if (colorAxis?.quantity && !discrete) {
       traces.push({
-        x: xArray,
-        y: yArray,
-        color: colors,
-        text: colors,
-        entry_id: entryIdArray,
-        name: "Test",
+        x: data.x,
+        y: data.y,
+        color: data.color,
+        text: data.color,
+        entry_id: data.id,
         mode: 'markers',
         type: 'scattergl',
         textposition: 'top center',
         showlegend: false,
         hoverinfo: "text",
         hovertemplate: hoverTemplate(
-          filterData[x]?.label,
-          filterData[y]?.label,
-          filterData[color]?.label,
-          unitLabelX,
-          unitLabelY,
-          unitLabelColors
+          xAxis.title,
+          yAxis.title,
+          colorAxis.title,
+          xAxis.unit,
+          yAxis.unit,
+          colorAxis.unit
         ),
         marker: {
           size: 8,
-          color: colors,
+          color: data.color,
           colorscale: 'YlGnBu',
           line: {
             color: theme.palette.grey[800],
@@ -259,26 +218,21 @@ const PlotScatter = React.memo(forwardRef((
     // When color is not set, all points are displayed in a single plot with
     // primary theme color.
     } else {
-      const xArray = values.map((d) => new Quantity(d.x, unitXObj).toSystem(units).value())
-      const yArray = values.map((d) => new Quantity(d.y, unitYObj).toSystem(units).value())
-      const entryIdArray = values.map((d) => d.entry_id)
-      const unitLabelX = values[0] ? new Quantity(values[0].x, unitXObj).toSystem(units).label() : ''
-      const unitLabelY = values[0] ? new Quantity(values[0].y, unitYObj).toSystem(units).label() : ''
       traces.push({
-        x: xArray,
-        y: yArray,
-        entry_id: entryIdArray,
+        x: data.x,
+        y: data.y,
+        entry_id: data.id,
         mode: 'markers',
         type: 'scattergl',
         textposition: 'top center',
         showlegend: false,
         hoverinfo: "text",
         hovertemplate: hoverTemplate(
-          filterData[x]?.label,
-          filterData[y]?.label,
+          xAxis.title,
+          yAxis.title,
           '',
-          unitLabelX,
-          unitLabelY,
+          xAxis.unit,
+          yAxis.unit,
           ''
         ),
         marker: {
@@ -292,7 +246,7 @@ const PlotScatter = React.memo(forwardRef((
       })
     }
     setFinalData(traces)
-  }, [data, x, y, color, discrete, theme, units, unitX, unitY, unitColor, filterData])
+  }, [colorAxis?.quantity, colorAxis?.title, colorAxis?.unit, data, discrete, theme, xAxis.title, xAxis.unit, yAxis.title, yAxis.unit])
 
   const layout = useMemo(() => {
     return {
@@ -356,7 +310,13 @@ const PlotScatter = React.memo(forwardRef((
 
   return <div className={styles.root}>
     <div className={styles.yaxis}>
-      <FilterTitle quantity={y} variant="caption" rotation="up"/>
+      <FilterTitle
+        quantity={yAxis.quantity}
+        label={yAxis.title}
+        unit={yAxis.unit}
+        variant="caption"
+        rotation="up"
+      />
     </div>
     <div className={styles.plot}>
       <Plot
@@ -376,13 +336,20 @@ const PlotScatter = React.memo(forwardRef((
     </div>
     <div className={styles.square} />
     <div className={styles.xaxis}>
-      <FilterTitle quantity={x} variant="caption"/>
+      <FilterTitle
+        quantity={xAxis.quantity}
+        label={xAxis.title}
+        unit={xAxis.unit}
+        variant="caption"
+      />
     </div>
-    {!discrete && color &&
+    {!discrete && colorAxis &&
       <div className={styles.color}>
         <FilterTitle
           rotation="down"
-          quantity={color}
+          quantity={colorAxis.quantity}
+          unit={colorAxis.unit}
+          label={colorAxis.title}
           description=""
           variant="caption"
         />
@@ -392,14 +359,11 @@ const PlotScatter = React.memo(forwardRef((
 }))
 
 PlotScatter.propTypes = {
-  data: PropTypes.arrayOf(PropTypes.object),
+  data: PropTypes.object,
   title: PropTypes.string,
-  x: PropTypes.string,
-  y: PropTypes.string,
-  color: PropTypes.string,
-  unitX: PropTypes.string,
-  unitY: PropTypes.string,
-  unitColor: PropTypes.string,
+  xAxis: PropTypes.object, // Contains x-axis settings
+  yAxis: PropTypes.object, // Contains y-axis settings
+  colorAxis: PropTypes.object, // Contains colorbar settings
   discrete: PropTypes.bool,
   autorange: PropTypes.bool,
   dragmode: PropTypes.string,
diff --git a/gui/src/components/search/Filter.js b/gui/src/components/search/Filter.js
index 27d92211bbdb3c55a92269823df67fbe383f09b4..efd69fd370365a559ea3997eb647d74d9d3464c8 100644
--- a/gui/src/components/search/Filter.js
+++ b/gui/src/components/search/Filter.js
@@ -125,20 +125,20 @@ export class Filter {
     this.quantity = params?.quantity || def?.quantity
     this.schema = params?.schema || def?.schema
 
-    function getRepeats(def) {
+    function getRepeatsSection(def) {
+      return isNil(def?.repeats)
+        ? false
+        : def.repeats
+    }
+    function getRepeatsQuantity(def) {
       if (!isEmpty(def?.shape)) return true
-      if (!isNil(def?.repeats)) {
-        return def.repeats
-      } else if (parent) {
-        return getRepeats(parent)
-      }
-      return false
+      return getRepeatsSection(def)
     }
 
     this.dtype = params?.dtype || getDatatype(def)
     this.description = params?.description || def?.description
     this.unit = params?.unit || def?.unit
-    this.dimension = def?.unit && new Unit(def?.unit).dimension()
+    this.dimension = def?.unit ? new Unit(def?.unit).dimension() : 'dimensionless'
     this.label = params?.label || formatLabel(this.name)
     let parentName
     if (parent) {
@@ -166,7 +166,9 @@ export class Filter {
     this.aggs = params?.aggs
     this.requestQuantity = params?.requestQuantity
     this.nested = params?.nested === undefined ? false : params?.nested
-    this.repeats = params?.repeats === undefined ? getRepeats(def) : params?.repeats
+    this.repeats = params?.repeats === undefined ? getRepeatsQuantity(def) : params?.repeats
+    this.repeats_section = getRepeatsSection(def)
+    this.scalar = isEmpty(def?.shape)
     this.global = params?.global === undefined ? false : params?.global
     this.section = !isNil(def?.nested)
     this.customSerialization = !!params?.serializerExact
diff --git a/gui/src/components/search/FilterRegistry.js b/gui/src/components/search/FilterRegistry.js
index 89b0b7d1427869f48452643583b0090cd16044a5..57ade3c7eec52d0c412d343bb4e5bdee72d070e8 100644
--- a/gui/src/components/search/FilterRegistry.js
+++ b/gui/src/components/search/FilterRegistry.js
@@ -256,12 +256,24 @@ registerFilter(
     {name: 'sbu_coordination_number', ...numberHistogramQuantity}
   ]
 )
+registerFilter(
+  'results.material.elemental_composition',
+  idStructure,
+  nestedQuantity,
+  [
+    {name: 'element', ...termQuantity},
+    {name: 'atomic_fraction', ...numberHistogramQuantity},
+    {name: 'mass_fraction', ...numberHistogramQuantity}
+  ]
+)
 registerFilter(
   'results.material.topology.elemental_composition',
   idStructure,
   nestedQuantity,
   [
-    {name: 'element', ...termQuantity}
+    {name: 'element', ...termQuantity},
+    {name: 'atomic_fraction', ...numberHistogramQuantity},
+    {name: 'mass_fraction', ...numberHistogramQuantity}
   ]
 )
 registerFilter(
@@ -441,6 +453,16 @@ registerFilter(
     {name: 'value', ...numberHistogramQuantity, scale: '1/4'}
   ]
 )
+registerFilter(
+  'results.properties.electronic.band_gap',
+  idElectronic,
+  nestedQuantity,
+  [
+    {name: 'type', ...termQuantity},
+    {name: 'value', ...numberHistogramQuantity, scale: '1/4'}
+  ]
+)
+registerFilter('results.properties.electronic.band_gap.provenance.label', idElectronic, termQuantity)
 registerFilter(
   'results.properties.optoelectronic.solar_cell',
   idSolarCell,
diff --git a/gui/src/components/search/FilterTitle.js b/gui/src/components/search/FilterTitle.js
index 5e53bcbec174c427e7c7ed5545f3965423715b03..e1d3e2c80eb3444e320d7168c29aa92488729d18 100644
--- a/gui/src/components/search/FilterTitle.js
+++ b/gui/src/components/search/FilterTitle.js
@@ -56,6 +56,7 @@ const FilterTitle = React.memo(({
   quantity,
   label,
   description,
+  unit,
   variant,
   full,
   TooltipProps,
@@ -78,14 +79,18 @@ const FilterTitle = React.memo(({
       ? filterData[quantity]?.labelFull
       : filterData[quantity]?.label)
     if (!disableUnit) {
-      const unit = filterData[quantity]?.unit
+      let finalUnit
       if (unit) {
-        const unitDef = new Unit(unit)
-        finalLabel = `${finalLabel} (${unitDef.toSystem(units).label()})`
+        finalUnit = new Unit(unit).label()
+      } else if (filterData[quantity]?.unit) {
+        finalUnit = new Unit(filterData[quantity].unit).toSystem(units).label()
+      }
+      if (finalUnit) {
+        finalLabel = `${finalLabel} (${finalUnit})`
       }
     }
     return finalLabel
-  }, [filterData, quantity, units, label, disableUnit, full])
+  }, [filterData, quantity, units, unit, label, disableUnit, full])
 
   // Determine the final description
   const finalDescription = description || filterData[quantity]?.description || ''
@@ -112,6 +117,7 @@ const FilterTitle = React.memo(({
 FilterTitle.propTypes = {
   quantity: PropTypes.string,
   label: PropTypes.string,
+  unit: PropTypes.string,
   description: PropTypes.string,
   variant: PropTypes.string,
   full: PropTypes.bool,
diff --git a/gui/src/components/search/SearchBar.spec.js b/gui/src/components/search/SearchBar.spec.js
index 3d4b818a896739d8b0797753f25c173eaf3f8a0c..4411240dadbd8e80f5e538cec098f9caba2d1c96 100644
--- a/gui/src/components/search/SearchBar.spec.js
+++ b/gui/src/components/search/SearchBar.spec.js
@@ -62,7 +62,6 @@ describe('searchbar queries', function() {
     const textInput = screen.getByRole('textbox')
     await userEvent.type(textInput, input)
     expect(screen.getByRole('textbox')).toHaveValue(input)
-    expect(screen.getByText(type))
     await userEvent.keyboard('{enter}')
     expect(screen.getByRole('textbox')).toHaveValue('')
   })
diff --git a/gui/src/components/search/conftest.spec.js b/gui/src/components/search/conftest.spec.js
index 36f3ac68c750170b1bb6ccccde10552b4b1ca66c..af45df7b7e6eed0078b56f223853d1d2cf230e12 100644
--- a/gui/src/components/search/conftest.spec.js
+++ b/gui/src/components/search/conftest.spec.js
@@ -27,7 +27,7 @@ import userEvent from '@testing-library/user-event'
 import { SearchContext } from './SearchContext'
 import { defaultFilterData } from './FilterRegistry'
 import { format } from 'date-fns'
-import { DType } from '../../utils'
+import { DType, parseJMESPath } from '../../utils'
 import { Unit } from '../units/Unit'
 import { ui } from '../../config'
 import { menuMap } from './menus/FilterMainMenu'
@@ -75,8 +75,8 @@ export async function expectFilterTitle(quantity, label, description, unit, disa
     )
     if (finalUnit) finalLabel = `${finalLabel} (${finalUnit})`
   }
-  await root.findByText(finalLabel)
-  expect(root.getByTooltip(finalDescription)).toBeInTheDocument()
+  await root.findAllByText(finalLabel)
+  expect(root.getAllByTooltip(finalDescription)[0]).toBeInTheDocument()
 }
 
 /**
@@ -144,10 +144,22 @@ export async function expectWidgetTerms(widget, loaded, items, prompt, root = sc
  * @param {object} widget The widget setup
  * @param {bool} loaded Whether the data is already loaded
  */
-export async function expectWidgetScatterPlot(widget, loaded, root = screen) {
+export async function expectWidgetScatterPlot(widget, loaded, colorTitle, legend, root = screen) {
     // Test immediately displayed elements
-    await expectFilterTitle(widget.x)
-    await expectFilterTitle(widget.y)
+    const {quantity: x} = parseJMESPath(widget.x.quantity)
+    const {quantity: y} = parseJMESPath(widget.y.quantity)
+    await expectFilterTitle(x)
+    await expectFilterTitle(y)
+    if (colorTitle) {
+      if (colorTitle.quantity) {
+        await expectFilterTitle(colorTitle.quantity)
+      } else {
+        await expectFilterTitle(undefined, colorTitle.title, colorTitle.unit)
+      }
+    }
+    for (const label of legend || []) {
+      root.getByText(label)
+    }
 
     // Check that placeholder disappears
     if (!loaded) {
diff --git a/gui/src/components/search/input/InputMetainfo.js b/gui/src/components/search/input/InputMetainfo.js
index 15faa08a9c1be44f0b896f154153dba56debc4eb..ae9304eacc7096603855f9c18b03e88ec9a6ada2 100644
--- a/gui/src/components/search/input/InputMetainfo.js
+++ b/gui/src/components/search/input/InputMetainfo.js
@@ -20,98 +20,29 @@ import React, {
   useCallback,
   useMemo,
   useEffect,
-  useRef,
   useContext,
-  createContext
+  createContext,
+  useRef
 } from 'react'
 import { makeStyles } from '@material-ui/core/styles'
 import PropTypes from 'prop-types'
 import { Tooltip, List, ListItemText, ListSubheader } from '@material-ui/core'
 import HelpOutlineIcon from '@material-ui/icons/HelpOutline'
-import { getSchemaAbbreviation, getSuggestions } from '../../../utils'
+import { getSchemaAbbreviation, getSuggestions, parseJMESPath } from '../../../utils'
 import { useSearchContext } from '../SearchContext'
 import { VariableSizeList } from 'react-window'
 import { InputText } from './InputText'
 
 /**
- * A metainfo option shown as a suggestion.
+ * Wrapper around InputText that is specialized in showing metainfo options. The
+ * allowed options are controlled.
  */
-export const useMetainfoOptionStyles = makeStyles(theme => ({
-  optionText: {
-    flexGrow: 1,
-    overflowX: 'scroll',
-    '&::-webkit-scrollbar': {
-      display: 'none'
-    },
-    '-ms-overflow-style': 'none',
-    scrollbarWidth: 'none'
-  },
-  noWrap: {
-    whiteSpace: 'nowrap'
-  },
-  option: {
-    width: '100%',
-    display: 'flex',
-    alignItems: 'stretch',
-    // The description icon is hidden until the item is hovered. It is not
-    // removed from the document with "display: none" in order for the hover to
-    // not change the layout which may cause other elements to shift around.
-    '& .description': {
-      visibility: "hidden",
-      marginRight: theme.spacing(-0.5),
-      display: 'flex',
-      width: theme.spacing(5),
-      marginLeft: theme.spacing(1),
-      alignItems: 'center',
-      justifyContent: 'center'
-    },
-    '&:hover .description': {
-      visibility: "visible"
-    }
-  }
-}))
-export const MetainfoOption = ({id, options}) => {
-  const styles = useMetainfoOptionStyles()
-  const option = options[id]
-  const primary = option.primary || option.definition?.quantity || option.key
-  const dtype = option.dtype || option.definition?.dtype
-  const schema = getSchemaAbbreviation(option.schema || option.definition?.schema)
-  const secondary = option.secondary || ((dtype || schema)
-    ? `${dtype} ${schema ? `| ${schema}` : ''}`
-    : null)
-  const description = option.description || option.definition?.description
-
-  return <div className={styles.option}>
-    <ListItemText
-      primary={primary}
-      secondary={secondary}
-      className={styles.optionText}
-      primaryTypographyProps={{className: styles.noWrap}}
-      secondaryTypographyProps={{className: styles.noWrap}}
-    />
-    {description &&
-      <Tooltip title={description || ''}>
-        <div className="description">
-          <HelpOutlineIcon fontSize="small" color="action"/>
-        </div>
-      </Tooltip>
-    }
-  </div>
-}
-
-MetainfoOption.propTypes = {
-  id: PropTypes.string,
-  options: PropTypes.object
-}
-
-/**
- * Wrapper around InputText that is specialized in showing metainfo options.
- */
-export const InputMetainfo = React.memo(({
+export const InputMetainfoControlled = React.memo(({
   label,
   value,
   options,
   error,
+  validate,
   onChange,
   onSelect,
   onAccept,
@@ -122,7 +53,9 @@ export const InputMetainfo = React.memo(({
   InputProps,
   PaperComponent,
   group,
-  loading
+  loading,
+  disableValidation,
+  disableValidateOnSelect
 }) => {
   // Predefine all option objects, all option paths and also pre-tokenize the
   // options for faster matching.
@@ -133,32 +66,35 @@ export const InputMetainfo = React.memo(({
     return { keys, keysSet, filter }
   }, [options])
 
+  const textProps = useMemo(() => {
+    return {
+      label,
+      ...TextFieldProps
+    }
+  }, [label, TextFieldProps])
+
   // Used to validate the input and raise errors
-  const validate = useCallback((value) => {
+  const validateFinal = useCallback((value) => {
+    if (disableValidation) {
+      return {valid: true, error: undefined}
+    }
     const empty = !value || value.length === 0
     if (optional && empty) {
       return {valid: true, error: undefined}
     } else if (empty) {
-      return {valid: false, error: 'Please specify a value.'}
+      return {valid: false, error: 'Please specify a value'}
+    }
+    if (validate) {
+      return validate(value)
     } else if (!(keysSet.has(value))) {
-      return {valid: false, error: 'Invalid value for this field.'}
+      return {valid: false, error: 'Invalid value for this field'}
     }
     return {valid: true, error: undefined}
-  }, [keysSet, optional])
-
-  // Handles the final acceptance of a value
-  const handleAccept = useCallback((key) => {
-    const {valid, error} = validate(key)
-    if (valid) {
-      onAccept && onAccept(key, options[key])
-    } else {
-      onError && onError(error)
-    }
-  }, [validate, onError, onAccept, options])
+  }, [validate, keysSet, optional, disableValidation])
 
-  // Handles the final acceptance of a value
+  // Handles the selectance of a suggested value
   const handleSelect = useCallback((key) => {
-    onSelect && onSelect(key, options[key])
+    onSelect?.(key, options[key])
   }, [onSelect, options])
 
   // Used to filter the shown options based on input
@@ -170,16 +106,15 @@ export const InputMetainfo = React.memo(({
 
   return <InputText
     value={value || null}
-    label={label}
     error={error}
     onChange={onChange}
     onSelect={handleSelect}
-    onAccept={handleAccept}
+    onAccept={onAccept}
     onBlur={onBlur}
     onError={onError}
     suggestions={keys}
     ListboxComponent={ListboxMetainfo}
-    TextFieldProps={TextFieldProps}
+    TextFieldProps={textProps}
     PaperComponent={PaperComponent}
     InputProps={InputProps}
     groupBy={group && ((key) => options?.[key]?.group)}
@@ -188,10 +123,12 @@ export const InputMetainfo = React.memo(({
     filterOptions={filterOptions}
     loading={loading}
     renderOption={(id) => <MetainfoOption id={id} options={options} />}
+    validate={validateFinal}
+    disableValidateOnSelect={disableValidateOnSelect}
   />
 })
 
-InputMetainfo.propTypes = {
+InputMetainfoControlled.propTypes = {
   label: PropTypes.string,
   value: PropTypes.string,
   options: PropTypes.objectOf(PropTypes.shape({
@@ -205,6 +142,7 @@ InputMetainfo.propTypes = {
     group: PropTypes.string // Optional group information
   })),
   error: PropTypes.string,
+  validate: PropTypes.func, // Optional custom validation function
   onChange: PropTypes.func,
   onSelect: PropTypes.func,
   onAccept: PropTypes.func,
@@ -215,79 +153,104 @@ InputMetainfo.propTypes = {
   TextFieldProps: PropTypes.object,
   InputProps: PropTypes.object,
   PaperComponent: PropTypes.any,
-  loading: PropTypes.bool
+  loading: PropTypes.bool,
+  disableValidation: PropTypes.bool,
+  disableValidateOnSelect: PropTypes.bool
 }
 
-InputMetainfo.defaultProps = {
-  TextFieldProps: {label: "quantity"}
+InputMetainfoControlled.defaultProps = {
+  label: "quantity"
 }
 
 /**
- * Wrapper around InputMetainfo which automatically shows suggestions and only
- * accepts metainfo that exist in the current search context.
+ * Wrapper around InputText that is specialized in showing metainfo options. The
+ * allowed options are uncontrolled: they are specified by the current
+ * SearchContext.
  */
-export const InputSearchMetainfo = React.memo(({
-  value,
-  label,
-  error,
-  onChange,
-  onSelect,
-  onAccept,
-  onError,
+export const InputMetainfo = React.memo(({
   dtypes,
   dtypesRepeatable,
-  optional,
-  disableNonAggregatable
+  disableNonAggregatable,
+  ...rest
 }) => {
   const { filterData } = useSearchContext()
 
   // Fetch the available metainfo names and create options that are compatible
   // with InputMetainfo.
-  const suggestions = useMemo(() => {
-    const suggestions = Object.fromEntries(
-      Object.entries(filterData)
-        .filter(([key, data]) => {
-          if (disableNonAggregatable && !data.aggregatable) return false
-          const dtype = data?.dtype
-          return data?.repeats
-            ? dtypesRepeatable?.has(dtype)
-            : dtypes?.has(dtype)
-        })
-        .map(([key, data]) => [key, {
-          key: key,
-          definition: data
-      }])
-    )
-    return suggestions
+  const options = useMemo(() => {
+    return getMetainfoOptions(filterData, dtypes, dtypesRepeatable, disableNonAggregatable)
   }, [filterData, dtypes, dtypesRepeatable, disableNonAggregatable])
 
-  return <InputMetainfo
-    options={suggestions}
-    value={value}
-    label={label}
-    error={error}
-    onChange={onChange}
-    onSelect={onSelect}
-    onAccept={onAccept}
-    onError={onError}
-    optional={optional}
+  return <InputMetainfoControlled
+    options={options}
+    {...rest}
   />
 })
 
-InputSearchMetainfo.propTypes = {
-  label: PropTypes.string,
-  value: PropTypes.string,
-  error: PropTypes.string,
-  onChange: PropTypes.func,
-  onSelect: PropTypes.func,
-  onAccept: PropTypes.func,
-  onError: PropTypes.func,
+InputMetainfo.propTypes = {
+  /* List of allowed data types for non-repeatable quantities. */
+  dtypes: PropTypes.object,
+  /* List of allowed data types for repeatable quantities. */
+  dtypesRepeatable: PropTypes.object,
+  /* Whether non-aggregatable values are excluded */
+  disableNonAggregatable: PropTypes.bool
+}
+
+/**
+ * Wrapper around InputText which accepts JMESPath syntax for quantities. The
+ * allowed quantities are uncontrolled: they are specified by the current
+ * SearchContext.
+ */
+export const InputJMESPath = React.memo(React.forwardRef(({
+  dtypes,
+  dtypesRepeatable,
+  disableNonAggregatable,
+  ...rest
+}, ref) => {
+  const { filterData } = useSearchContext()
+
+  // Fetch the available metainfo names and create options that are compatible
+  // with InputMetainfo.
+  const [options, keysSet] = useMemo(() => {
+    const options = getMetainfoOptions(filterData, dtypes, dtypesRepeatable, disableNonAggregatable)
+    const keysSet = new Set(Object.keys(options))
+    return [options, keysSet]
+  }, [filterData, dtypes, dtypesRepeatable, disableNonAggregatable])
+
+  // Used to validate the JMESPath input
+  const validate = useCallback((value) => {
+    const {quantity, path, extras, error: errorParse, schema} = parseJMESPath(value)
+    if (errorParse) {
+      return {valid: false, error: 'Invalid JMESPath query, please check your syntax.'}
+    }
+    if (!(keysSet.has(quantity))) {
+      return {valid: false, error: `The quantity "${quantity}" is not available.`}
+    }
+    for (const extra of extras) {
+      if (!filterData[extra]) {
+        return {valid: false, error: `The quantity "${extra}" is not available.`}
+      }
+    }
+    if (filterData[quantity].repeats_section && quantity === path + schema) {
+      return {valid: false, error: `The quantity "${quantity}" is contained in at least one repeatable section. Please use JMESPath syntax to select one or more target sections.`}
+    }
+    return {valid: true, error: undefined}
+  }, [keysSet, filterData])
+
+  return <InputMetainfoControlled
+    options={options}
+    validate={validate}
+    disableValidateOnSelect
+    ref={ref}
+    {...rest}
+  />
+}))
+
+InputJMESPath.propTypes = {
   /* List of allowed data types for non-repeatable quantities. */
   dtypes: PropTypes.object,
   /* List of allowed data types for repeatable quantities. */
   dtypesRepeatable: PropTypes.object,
-  /* Whether the value is optional */
-  optional: PropTypes.bool,
   /* Whether non-aggregatable values are excluded */
   disableNonAggregatable: PropTypes.bool
 }
@@ -376,3 +339,99 @@ export function renderRow({ data, index, style }) {
     }
   })
 }
+
+/* A metainfo option shown as a suggestion.
+ */
+export const useMetainfoOptionStyles = makeStyles(theme => ({
+  optionText: {
+    flexGrow: 1,
+    overflowX: 'scroll',
+    '&::-webkit-scrollbar': {
+      display: 'none'
+    },
+    '-ms-overflow-style': 'none',
+    scrollbarWidth: 'none'
+  },
+  noWrap: {
+    whiteSpace: 'nowrap'
+  },
+  option: {
+    width: '100%',
+    display: 'flex',
+    alignItems: 'stretch',
+    // The description icon is hidden until the item is hovered. It is not
+    // removed from the document with "display: none" in order for the hover to
+    // not change the layout which may cause other elements to shift around.
+    '& .description': {
+      visibility: "hidden",
+      marginRight: theme.spacing(-0.5),
+      display: 'flex',
+      width: theme.spacing(5),
+      marginLeft: theme.spacing(1),
+      alignItems: 'center',
+      justifyContent: 'center'
+    },
+    '&:hover .description': {
+      visibility: "visible"
+    }
+  }
+}))
+export const MetainfoOption = ({id, options}) => {
+  const styles = useMetainfoOptionStyles()
+  const option = options[id]
+  const primary = option.primary || option.definition?.quantity || option.key
+  const dtype = option.dtype || option.definition?.dtype
+  const schema = getSchemaAbbreviation(option.schema || option.definition?.schema)
+  const secondary = option.secondary || ((dtype || schema)
+    ? `${dtype} ${schema ? `| ${schema}` : ''}`
+    : null)
+  const description = option.description || option.definition?.description
+
+  return <div className={styles.option}>
+    <ListItemText
+      primary={primary}
+      secondary={secondary}
+      className={styles.optionText}
+      primaryTypographyProps={{className: styles.noWrap}}
+      secondaryTypographyProps={{className: styles.noWrap}}
+    />
+    {description &&
+      <Tooltip title={description || ''}>
+        <div className="description">
+          <HelpOutlineIcon fontSize="small" color="action"/>
+        </div>
+      </Tooltip>
+    }
+  </div>
+}
+
+MetainfoOption.propTypes = {
+  id: PropTypes.string,
+  options: PropTypes.object
+}
+
+/**
+ * Used to filter a set of filterData according to the given filtering options.
+ *
+ * @param {*} filterData The filterData to filter
+ * @param {*} dtypes Included data types as a set
+ * @param {*} dtypesRepeatable  Included repeatabel data types as a set
+ * @param {*} disableNonAggregatable  Whether to filter out non-aggregatable filters
+ * @returns Object containing the filtered filters
+ */
+function getMetainfoOptions(filterData, dtypes, dtypesRepeatable, disableNonAggregatable) {
+  return Object.fromEntries(
+    Object.entries(filterData)
+      .filter(([key, data]) => {
+        if (disableNonAggregatable && !data.aggregatable) return false
+        const dtype = data?.dtype
+        return data?.repeats
+          ? dtypesRepeatable?.has(dtype)
+          : dtypes?.has(dtype)
+      })
+      .map(([key, data]) => [key, {
+        key: key,
+        definition: data
+    }])
+  )
+}
diff --git a/gui/src/components/search/input/InputMetainfo.spec.js b/gui/src/components/search/input/InputMetainfo.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..0bc65da2fe6cbb29ca35a3e8602210aa50db9b98
--- /dev/null
+++ b/gui/src/components/search/input/InputMetainfo.spec.js
@@ -0,0 +1,65 @@
+/*
+ * Copyright The NOMAD Authors.
+ *
+ * This file is part of NOMAD. See https://nomad-lab.eu for further info.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react'
+import userEvent from '@testing-library/user-event'
+import { render, screen } from '../../conftest.spec'
+import { InputJMESPath } from './InputMetainfo'
+import { SearchContext } from '../SearchContext'
+import { ui } from '../../../config'
+import { DType } from '../../../utils'
+
+const context = ui.apps.options.entries
+
+test.each([
+  ['filter exists', 'results.material.n_elements', null],
+  ['filter does not exist', 'not.present', "The quantity \"not.present\" is not available."],
+  ['valid jmespath', 'results.material.n_elements[0]', null],
+  ['invalid jmespath', 'results.material.n_elements[0', 'Invalid JMESPath query, please check your syntax.']
+])('%s', async (name, input, error) => {
+  const onErrorMock = jest.fn()
+  const onAcceptMock = jest.fn()
+  const onChangeMock = jest.fn()
+  render(
+    <SearchContext
+        resource={context.resource}
+        initialPagination={context.pagination}
+        initialColumns={context.columns}
+        initialRows={context.rows}
+        initialFilters={context?.filters}
+        initialFilterMenus={context.filter_menus}
+        initialFiltersLocked={context.filters_locked}
+        initialDashboard={context?.dashboard}
+        initialSearchSyntaxes={context?.search_syntaxes}
+    >
+        <InputJMESPath
+          value={input}
+          onAccept={onAcceptMock}
+          onError={onErrorMock}
+          onChange={onChangeMock}
+          dtypes={new Set([DType.String, DType.Int])}
+        />
+    </SearchContext>
+  )
+  const textInput = screen.getByRole('textbox')
+  await userEvent.type(textInput, '{Enter}')
+  if (error) {
+    expect(onErrorMock).toHaveBeenCalledWith(error)
+  } else {
+    expect(onAcceptMock).toHaveBeenCalledWith(input, undefined)
+  }
+})
diff --git a/gui/src/components/search/input/InputText.js b/gui/src/components/search/input/InputText.js
index 23a370c42202fed7b224c974c220687dd9c2c947..ef41ea725b4887040cf3ac73ad86c14958602b4e 100644
--- a/gui/src/components/search/input/InputText.js
+++ b/gui/src/components/search/input/InputText.js
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, { useCallback, useState, useMemo, useRef } from 'react'
+import React, { useCallback, useState, useMemo, useRef, useEffect } from 'react'
 import { makeStyles, useTheme } from '@material-ui/core/styles'
 import PropTypes from 'prop-types'
 import clsx from 'clsx'
@@ -119,7 +119,9 @@ export const InputText = React.memo(({
   InputProps,
   PaperComponent,
   disableClearable,
-  disableAcceptOnBlur
+  disableAcceptOnBlur,
+  validate,
+  disableValidateOnSelect
 }) => {
   const theme = useTheme()
   const styles = useInputTextStyles({classes: classes, theme: theme})
@@ -137,6 +139,36 @@ export const InputText = React.memo(({
     setOpen(false)
   }, [onChange, onError])
 
+  const handleAccept = useCallback((value) => {
+    const {valid, error, data} = validate ? validate(value) : {valid: true, error: undefined, data: undefined}
+    if (valid) {
+      onAccept?.(value && value.trim(), data)
+    } else {
+      onError?.(error)
+    }
+  }, [onAccept, validate, onError])
+
+  // Validate the initial value if it is non-empty.
+  useEffect(() => {
+    if (!(isNil(value) || value?.trim?.() === '')) {
+      handleAccept(value)
+    }
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [])
+
+  const handleSelect = useCallback((value) => {
+    if (disableValidateOnSelect) {
+      onSelect?.(value && value.trim())
+    } else {
+      const {valid, error, data} = validate ? validate(value) : {valid: true, error: undefined, data: undefined}
+      if (valid) {
+        onSelect?.(value && value.trim(), data)
+      } else {
+        onError?.(error)
+      }
+    }
+  }, [onSelect, disableValidateOnSelect, validate, onError])
+
   // Handle item highlighting: items can he highlighted with mouse or keyboard.
   const handleHighlight = useCallback((event, value, reason) => {
     onHighlight?.(value, reason)
@@ -146,8 +178,8 @@ export const InputText = React.memo(({
   // Handle blur
   const handleBlur = useCallback(() => {
     onBlur?.()
-    !disableAcceptOnBlur && onAccept?.(value)
-  }, [onBlur, onAccept, value, disableAcceptOnBlur])
+    !disableAcceptOnBlur && handleAccept(value)
+  }, [onBlur, handleAccept, value, disableAcceptOnBlur])
 
   // Handles special key presses
   const handleKeyDown = useCallback((event) => {
@@ -166,15 +198,15 @@ export const InputText = React.memo(({
     // or if menu is not open submit the value.
     if (event.key === 'Enter') {
       if (open && highlightRef.current) {
-        onSelect?.(getOptionLabel(highlightRef.current).trim())
+        handleSelect?.(getOptionLabel(highlightRef.current).trim())
       } else {
-        onAccept?.(value && value.trim())
+        handleAccept(value && value.trim())
       }
       event.stopPropagation()
       event.preventDefault()
       setOpen(false)
     }
-  }, [open, suggestions, onSelect, onAccept, value, getOptionLabel, clearInputValue, highlightRef])
+  }, [open, suggestions, handleSelect, handleAccept, value, getOptionLabel, clearInputValue, highlightRef])
 
   // Handle input events. Errors are cleaned in input change, regular typing
   // emits onChange, selection with mouse emits onSelect.
@@ -183,12 +215,12 @@ export const InputText = React.memo(({
     onError && onError(undefined)
     if (event) {
       if (reason === 'reset') {
-        onSelect?.(value)
+        handleSelect?.(value)
       } else {
         onChange?.(value)
       }
     }
-  }, [onChange, onSelect, onError])
+  }, [onChange, handleSelect, onError])
 
   return <div className={clsx(className, styles.root)}>
     <Autocomplete
@@ -303,6 +335,8 @@ InputText.propTypes = {
   disableClearable: PropTypes.bool,
   autoHighlight: PropTypes.bool,
   disableAcceptOnBlur: PropTypes.bool,
+  validate: PropTypes.func, // Function that can be used to validate the input
+  disableValidateOnSelect: PropTypes.bool, // Whether validation on selecting autocompletion value should be disabled
   suggestAllOnFocus: PropTypes.bool, // Whether to provide all suggestion values when input is focused
   showOpenSuggestions: PropTypes.bool, // Whether to show button for opening suggestions
   className: PropTypes.string,
diff --git a/gui/src/components/search/menus/FilterSubMenuCustomQuantities.js b/gui/src/components/search/menus/FilterSubMenuCustomQuantities.js
index a7c7bff626715d8af28b9dc7886d0ec1bd179ecc..bff2e151f9b0344d92a7098e57026358ee4cba21 100644
--- a/gui/src/components/search/menus/FilterSubMenuCustomQuantities.js
+++ b/gui/src/components/search/menus/FilterSubMenuCustomQuantities.js
@@ -40,7 +40,7 @@ import { StringEditQuantity } from '../../editQuantity/StringEditQuantity'
 import { EnumEditQuantity } from '../../editQuantity/EnumEditQuantity'
 import { DateTimeEditQuantity, DateEditQuantity } from '../../editQuantity/DateTimeEditQuantity'
 import { editQuantityComponents } from '../../editQuantity/EditQuantity'
-import { InputMetainfo } from '../../search/input/InputMetainfo'
+import { InputMetainfoControlled } from '../../search/input/InputMetainfo'
 import { DType, getDatatype, rsplit, parseQuantityName } from '../../../utils'
 import { InputTextField } from '../input/InputText'
 
@@ -192,7 +192,7 @@ const QuantityFilter = React.memo(({quantities, filter, onChange}) => {
   return (<React.Fragment>
     <Box display="flex" flexWrap="wrap" flexDirection="row" alignItems="flex-start" marginTop={1}>
       <Box marginBottom={1} width="100%">
-        <InputMetainfo
+        <InputMetainfoControlled
           options={options}
           value={id}
           onChange={handleIdChange}
diff --git a/gui/src/components/search/widgets/Dashboard.js b/gui/src/components/search/widgets/Dashboard.js
index 3b4b8b711ebed871aacffd3e3ae1536f20f492f1..a33c766b7041af5fa902f6683c95f29bc0d2c476 100644
--- a/gui/src/components/search/widgets/Dashboard.js
+++ b/gui/src/components/search/widgets/Dashboard.js
@@ -29,6 +29,7 @@ import {
   DialogTitle,
   withStyles
 } from '@material-ui/core'
+import { isArray, cloneDeep } from 'lodash'
 import CodeIcon from '@material-ui/icons/Code'
 import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
 import ExpandLessIcon from '@material-ui/icons/ExpandLess'
@@ -37,13 +38,13 @@ import ReplayIcon from '@material-ui/icons/Replay'
 import WidgetGrid from './WidgetGrid'
 import { Actions, Action } from '../../Actions'
 import { useSearchContext } from '../SearchContext'
-import { WidgetScatterPlotEdit, schemaWidgetScatterPlot } from './WidgetScatterPlot'
+import { WidgetScatterPlotEdit, schemaWidgetScatterPlot } from './WidgetScatterPlotEdit'
 import { WidgetHistogramEdit, schemaWidgetHistogram } from './WidgetHistogram'
 import { WidgetTermsEdit, schemaWidgetTerms } from './WidgetTerms'
 import { WidgetPeriodicTableEdit, schemaWidgetPeriodicTable } from './WidgetPeriodicTable'
 import InputConfig from '../input/InputConfig'
+import { cleanse } from '../../../utils'
 import Markdown from '../../Markdown'
-import { isArray } from 'lodash'
 import { getWidgetsObject } from './Widget'
 import { ContentButton } from '../../buttons/SourceDialogButton'
 
@@ -252,7 +253,7 @@ const Dashboard = React.memo(() => {
     {widgets && Object.entries(widgets).map(([id, value]) => {
       if (!value.editing) return null
       const comp = {
-        scatterplot: <WidgetScatterPlotEdit key={id} {...value}/>,
+        scatterplot: <WidgetScatterPlotEdit key={id} widget={value}/>,
         periodictable: <WidgetPeriodicTableEdit key={id} {...value}/>,
         histogram: <WidgetHistogramEdit key={id} {...value}/>,
         terms: <WidgetTermsEdit key={id} {...value}/>
@@ -290,8 +291,7 @@ const schemas = {
  * A button that displays a dialog that can be used to modify the current
  * dashboard setup.
  */
-export const DashboardExportButton = React.memo((props) => {
-  const {tooltip, title, DialogProps, ButtonProps} = props
+export const DashboardExportButton = React.memo(({tooltip, title, DialogProps, ButtonProps}) => {
   const {useWidgetsState} = useSearchContext()
   const [widgets, setWidgets] = useWidgetsState()
   const [widgetExport, setWidgetExport] = useState()
@@ -302,11 +302,19 @@ export const DashboardExportButton = React.memo((props) => {
   // The widget export data. Only reacts if the menu is open.
   useEffect(() => {
     if (!open) return
+    // Work on a copy: data in Recoil is unmutable
+    const widgetsExport = cloneDeep(widgets)
+
     const exp = Object
-      .values(widgets)
+      .values(widgetsExport)
       .map((widget) => {
         const schema = schemas[widget.type]
         const casted = schema?.cast(widget, {stripUnknown: true})
+
+        // Remove undefined values. YUP cannot do this, and the YAML
+        // serialization will otherwise include these.
+        cleanse(casted)
+
         // Perform custom sort: type first, layout last
         const sorted = {}
         if (casted['type']) sorted['type'] = casted['type']
diff --git a/gui/src/components/search/widgets/Dashboard.spec.js b/gui/src/components/search/widgets/Dashboard.spec.js
index 848e8f476f47fd430f673f522e4e93f1cd167d8b..1ce25ff33819efea48a856e2ddf50faf3f96de96 100644
--- a/gui/src/components/search/widgets/Dashboard.spec.js
+++ b/gui/src/components/search/widgets/Dashboard.spec.js
@@ -89,9 +89,9 @@ describe('displaying an initial widget and removing it', () => {
         type: 'scatterplot',
         label: 'Test label',
         description: 'Custom scatter plot',
-        x: 'results.properties.optoelectronic.solar_cell.open_circuit_voltage',
-        y: 'results.properties.optoelectronic.solar_cell.efficiency',
-        color: 'results.properties.optoelectronic.solar_cell.short_circuit_current_density',
+        x: {quantity: 'results.properties.optoelectronic.solar_cell.open_circuit_voltage'},
+        y: {quantity: 'results.properties.optoelectronic.solar_cell.efficiency'},
+        markers: {color: {quantity: 'results.properties.optoelectronic.solar_cell.short_circuit_current_density'}},
         size: 1000,
         autorange: true,
         editing: false,
diff --git a/gui/src/components/search/widgets/Widget.js b/gui/src/components/search/widgets/Widget.js
index 0eb1da5224b56fdd27d6ba979c536c003091933d..38d9aaf83a5522ba2a0f67267c8402f08c2802e6 100644
--- a/gui/src/components/search/widgets/Widget.js
+++ b/gui/src/components/search/widgets/Widget.js
@@ -18,7 +18,7 @@
 import React, { useCallback, useMemo } from 'react'
 import clsx from 'clsx'
 import PropTypes from 'prop-types'
-import { cloneDeep } from 'lodash'
+import { cloneDeep, isNil } from 'lodash'
 import { object, string, number } from 'yup'
 import { makeStyles } from '@material-ui/core/styles'
 import { Edit } from '@material-ui/icons'
@@ -88,7 +88,7 @@ export const schemaLayout = object({
     'is-integer-or-infinity',
     // eslint-disable-next-line no-template-curly-in-string
     '${path} is not a valid integer number',
-    (value, context) => Number.isInteger(value) || value === Infinity
+    (value, context) => Number.isInteger(value) || value === Infinity || isNil(value)
   ),
   y: number().integer(),
   w: number().integer(),
@@ -109,6 +109,19 @@ export const schemaWidget = object({
   editing: string().strip(),
   visible: string().strip()
 })
+export const schemaAxis = object({
+  quantity: string().required(),
+  unit: string().nullable(),
+  title: string().nullable()
+})
+export const schemaAxisOptional = object({
+  quantity: string().nullable(),
+  unit: string().nullable(),
+  title: string().nullable()
+})
+export const schemaMarkers = object({
+  color: schemaAxisOptional
+})
 
 /**
  * Transforms a list of widgets into the internal object representation.
diff --git a/gui/src/components/search/widgets/WidgetEdit.js b/gui/src/components/search/widgets/WidgetEdit.js
index 706e7b612d505edae39e8e2d922c25f8af537d99..6d5474579bf85c17f33def7b7d60be3c186f2241 100644
--- a/gui/src/components/search/widgets/WidgetEdit.js
+++ b/gui/src/components/search/widgets/WidgetEdit.js
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, {useCallback, useState} from 'react'
+import React, {useCallback} from 'react'
 import PropTypes from 'prop-types'
 import {
   List,
@@ -26,14 +26,11 @@ import {
   DialogContent,
   DialogTitle,
   makeStyles,
-  withStyles,
-  Typography,
-  Accordion as MuiAccordion,
-  AccordionDetails as MuiAccordionDetails,
-  AccordionSummary as MuiAccordionSummary
+  MenuItem,
+  TextField,
+  Typography
 } from '@material-ui/core'
 import { useSearchContext } from '../SearchContext'
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
 
 /**
  * A dialog that is used to configure a widget.
@@ -47,39 +44,46 @@ export const WidgetEditDialog = React.memo(({id, title, open, visible, onAccept,
   const styles = useStyles()
   const { useRemoveWidget } = useSearchContext()
   const removeWidget = useRemoveWidget()
-    const handleClose = useCallback(() => {
-      // If the widget has not bee visualized, then closing the dialog deletes
-      // the widget completely.
-      onClose && onClose()
-      if (!visible) {
-        removeWidget(id)
-      }
-    }, [onClose, removeWidget, id, visible])
 
-    const handleAccept = useCallback(() => {
-      onAccept && onAccept()
-    }, [onAccept])
+  const handleClose = useCallback((event, reason) => {
+    // Do not close dialog on backdrop click: user may lose lot of filled info
+    // accidentally
+    if (reason === 'backdropClick') {
+      return
+    }
 
-    return <Dialog
-      fullWidth={true}
-      maxWidth="sm"
-      open={open}
-      classes={{paperWidthSm: styles.width}}
-      onClose={handleClose}
-    >
-      <DialogTitle>{title || ''}</DialogTitle>
-      <DialogContent>
-        {children}
-      </DialogContent>
-      <DialogActions>
-        <Button onClick={handleClose} color="primary">
-          Cancel
-        </Button>
-        <Button disabled={!!error} onClick={handleAccept} color="primary">
-          Done
-        </Button>
-      </DialogActions>
-    </Dialog>
+    // If the widget has not bee visualized, then closing the dialog deletes
+    // the widget completely.
+    onClose && onClose()
+    if (!visible) {
+      removeWidget(id)
+    }
+  }, [id, onClose, removeWidget, visible])
+
+  const handleAccept = useCallback(() => {
+    onAccept && onAccept()
+  }, [onAccept])
+
+  return <Dialog
+    fullWidth={true}
+    maxWidth="sm"
+    open={open}
+    classes={{paperWidthSm: styles.width}}
+    onClose={handleClose}
+  >
+    <DialogTitle>{title || ''}</DialogTitle>
+    <DialogContent>
+      {children}
+    </DialogContent>
+    <DialogActions>
+      <Button onClick={handleClose} color="primary">
+        Cancel
+      </Button>
+      <Button disabled={!!error} onClick={handleAccept} color="primary">
+        Done
+      </Button>
+    </DialogActions>
+  </Dialog>
 })
 
 WidgetEditDialog.propTypes = {
@@ -91,47 +95,12 @@ WidgetEditDialog.propTypes = {
   onAccept: PropTypes.func,
   error: PropTypes.bool,
   children: PropTypes.node
+
 }
 
 /**
  * A group of options in an edit dialog.
  */
-
-const Accordion = withStyles((theme) => ({
-  root: {
-    boxShadow: 'none',
-    margin: 0,
-    '&$expanded': {
-      margin: 0
-    }
-  },
-  expanded: {
-    margin: 0
-  }
-}))(MuiAccordion)
-
-const AccordionDetails = withStyles((theme) => ({
-  root: {
-    padding: theme.spacing(0)
-  }
-}))(MuiAccordionDetails)
-
-const AccordionSummary = withStyles((theme) => ({
-  root: {
-    minHeight: 24,
-    padding: 0,
-    '&$expanded': {
-      minHeight: 24
-    }
-  },
-  content: {
-    '&$expanded': {
-      margin: '0px 0'
-    }
-  },
-  expanded: {}
-}))(MuiAccordionSummary)
-
 const useEditGroupStyles = makeStyles((theme) => ({
   heading: {
     color: theme.palette.primary.main
@@ -142,23 +111,13 @@ const useEditGroupStyles = makeStyles((theme) => ({
 }))
 export const WidgetEditGroup = React.memo(({title, children}) => {
   const styles = useEditGroupStyles()
-  const [expanded, setExpanded] = useState(true)
 
-  return <Accordion expanded={expanded} onChange={() => { setExpanded(old => !old) }}>
-    <AccordionSummary
-        expandIcon={<ExpandMoreIcon />}
-        IconButtonProps={{
-          size: "small"
-        }}
-    >
-      <Typography variant="button" className={styles.heading}>{title}</Typography>
-    </AccordionSummary>
-    <AccordionDetails>
-      <List dense className={styles.list}>
-        {children}
-      </List>
-    </AccordionDetails>
-  </Accordion>
+  return <>
+    <Typography variant="button" className={styles.heading}>{title}</Typography>
+    <List dense className={styles.list}>
+      {children}
+    </List>
+  </>
 })
 
 WidgetEditGroup.propTypes = {
@@ -178,3 +137,30 @@ export const WidgetEditOption = React.memo(({children}) => {
 WidgetEditOption.propTypes = {
   children: PropTypes.node
 }
+
+/**
+ * Select (=dropdown) component for widget edit.
+ */
+export const WidgetEditSelect = React.memo(({label, disabled, options, value, onChange}) => {
+  return <TextField
+      select
+      fullWidth
+      label={label}
+      variant="filled"
+      value={value || ''}
+      onChange={onChange}
+      disabled={disabled}
+    >
+      {Object.entries(options).map(([key, value]) =>
+        <MenuItem value={value} key={key}>{key}</MenuItem>
+      )}
+    </TextField>
+})
+
+WidgetEditSelect.propTypes = {
+  label: PropTypes.string,
+  disabled: PropTypes.bool,
+  options: PropTypes.object,
+  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+  onChange: PropTypes.func
+}
diff --git a/gui/src/components/search/widgets/WidgetGrid.js b/gui/src/components/search/widgets/WidgetGrid.js
index 8dc16bffca0983c26a1db39dffc94bb03342bbfd..f3b2dcd5831b3613b64e44d6ddd7d312f5f3f9c1 100644
--- a/gui/src/components/search/widgets/WidgetGrid.js
+++ b/gui/src/components/search/widgets/WidgetGrid.js
@@ -291,7 +291,7 @@ const WidgetGrid = React.memo(({
     }
   }, [width])
 
-  // The layouts are stored when they are changed. This allows retaining the the
+  // The layouts are stored when they are changed. This allows retaining the
   // layout for each breakpoint individually. Notice that we need to feed in the
   // changed layout back to the component so that it works in a controlled
   // manner.
diff --git a/gui/src/components/search/widgets/WidgetHistogram.js b/gui/src/components/search/widgets/WidgetHistogram.js
index 3f2d2d8eb3362f7122b1cc067cbfda6f15d6c6b1..34aeab59b0dd6fa1e98af0f45ac5e100f8baff7a 100644
--- a/gui/src/components/search/widgets/WidgetHistogram.js
+++ b/gui/src/components/search/widgets/WidgetHistogram.js
@@ -25,7 +25,7 @@ import {
   FormControlLabel
 } from '@material-ui/core'
 import { useSearchContext } from '../SearchContext'
-import { InputSearchMetainfo } from '../input/InputMetainfo'
+import { InputMetainfo } from '../input/InputMetainfo'
 import { Widget, schemaWidget } from './Widget'
 import { ActionCheckbox, ActionSelect } from '../../Actions'
 import { WidgetEditDialog, WidgetEditGroup, WidgetEditOption } from './WidgetEdit'
@@ -166,7 +166,7 @@ export const WidgetHistogramEdit = React.memo((props) => {
       >
       <WidgetEditGroup title="x axis">
         <WidgetEditOption>
-          <InputSearchMetainfo
+          <InputMetainfo
             label="quantity"
             value={settings.quantity}
             error={errors.quantity}
diff --git a/gui/src/components/search/widgets/WidgetScatterPlot.js b/gui/src/components/search/widgets/WidgetScatterPlot.js
index a7ed2946814ee4cc54e7b5a0d7bda707ea4aea50..87d641ec7b4decba864494d5bc6d8f7760a33c74 100644
--- a/gui/src/components/search/widgets/WidgetScatterPlot.js
+++ b/gui/src/components/search/widgets/WidgetScatterPlot.js
@@ -17,21 +17,20 @@
  */
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
 import PropTypes from 'prop-types'
-import { string, number, bool } from 'yup'
-import { isEmpty } from 'lodash'
+import { number, bool, reach } from 'yup'
+import jmespath from 'jmespath'
+import { isEmpty, isArray, isEqual, cloneDeep, range, isNil, flattenDeep } from 'lodash'
 import {
   Divider,
-  TextField,
-  MenuItem,
   Tooltip,
   makeStyles,
   Checkbox,
   FormControlLabel
 } from '@material-ui/core'
-import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'
-import { InputSearchMetainfo } from '../input/InputMetainfo'
-import { Widget, schemaWidget } from './Widget'
-import { WidgetEditDialog, WidgetEditGroup, WidgetEditOption } from './WidgetEdit'
+import { ToggleButton, ToggleButtonGroup, Alert } from '@material-ui/lab'
+import { InputJMESPath } from '../input/InputMetainfo'
+import { Widget, schemaWidget, schemaAxis, schemaMarkers } from './Widget'
+import { WidgetEditDialog, WidgetEditGroup, WidgetEditOption, WidgetEditSelect } from './WidgetEdit'
 import { useSearchContext } from '../SearchContext'
 import Floatable from '../../visualization/Floatable'
 import PlotScatter from '../../plotting/PlotScatter'
@@ -39,15 +38,21 @@ import { Action, ActionCheckbox } from '../../Actions'
 import { CropFree, PanTool, Fullscreen, Replay } from '@material-ui/icons'
 import { autorangeDescription } from './WidgetHistogram'
 import { styled } from '@material-ui/core/styles'
-import { DType } from '../../../utils'
+import { DType, setDeep, parseJMESPath } from '../../../utils'
 import { Quantity } from '../../units/Quantity'
 import { Unit } from '../../units/Unit'
 import { useUnitContext } from '../../units/UnitContext'
+import { InputTextField } from '../input/InputText'
+import UnitInput from '../../units/UnitInput'
 
 // Predefined in order to not break memoization
 const dtypesNumeric = new Set([DType.Int, DType.Float])
 const dtypesColor = new Set([DType.String, DType.Enum, DType.Float, DType.Int])
-const dtypesColorRepeatable = new Set([DType.String, DType.Enum])
+const nPointsOptions = {
+  100: 100,
+  1000: 1000,
+  10000: 10000
+}
 
 const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({
   '& .MuiToggleButtonGroup-grouped': {
@@ -75,6 +80,9 @@ const useStyles = makeStyles((theme) => ({
   },
   divider: {
     margin: theme.spacing(0.5, 0.5)
+  },
+  alert: {
+    overflow: 'auto'
   }
 }))
 
@@ -85,7 +93,7 @@ export const WidgetScatterPlot = React.memo((
   description,
   x,
   y,
-  color,
+  markers,
   size,
   autorange,
   dragmode,
@@ -98,29 +106,67 @@ export const WidgetScatterPlot = React.memo((
   const [float, setFloat] = useState(false)
   const [loading, setLoading] = useState(true)
   const { useSetWidget, useHits, filterData, useSetFilter } = useSearchContext()
-  const setXFilter = useSetFilter(x)
-  const setYFilter = useSetFilter(y)
-  const discrete = useMemo(() => {
-    return new Set([DType.String, DType.Enum]).has(filterData[color]?.dtype)
-  }, [filterData, color])
-  const filterX = filterData[x]
-  const filterY = filterData[y]
-  const filterColor = filterData[color]
-  const unitX = filterX?.unit || 'dimensionless'
-  const unitY = filterY?.unit || 'dimensionless'
-  const unitColor = filterColor?.unit
+
+  // Parse additional JMESPath config
+  const [xParsed, yParsed, colorParsed, error] = useMemo(() => {
+    const xParsed = parseJMESPath(x?.quantity)
+    const yParsed = parseJMESPath(y?.quantity)
+    const colorParsed = markers?.color?.quantity ? parseJMESPath(markers.color.quantity) : {}
+    if (xParsed.error || yParsed.error || colorParsed.error) {
+      return [{}, {}, {}, 'Invalid JMESPath query, please check your syntax.']
+    }
+    return [xParsed, yParsed, colorParsed, undefined]
+  }, [markers?.color?.quantity, x?.quantity, y?.quantity])
+
+  // Parse units
+  const {unitXObj, unitYObj, unitColorObj, displayUnitX, displayUnitY, displayUnitColor, discrete} = useMemo(() => {
+    if (error) return {}
+    const unitXObj = new Unit(filterData[xParsed.quantity].unit || 'dimensionless')
+    const unitYObj = new Unit(filterData[yParsed.quantity].unit || 'dimensionless')
+    const unitColorObj = new Unit(filterData[colorParsed?.quantity]?.unit || 'dimensionless')
+    const displayUnitX = x.unit ? new Unit(x.unit) : unitXObj.toSystem(units)
+    const displayUnitY = y.unit ? new Unit(y.unit) : unitYObj.toSystem(units)
+    const displayUnitColor = markers?.color?.unit ? new Unit(markers?.color?.unit) : unitColorObj.toSystem(units)
+    const discrete = colorParsed?.quantity && new Set([DType.String, DType.Enum]).has(filterData[colorParsed.quantity]?.dtype)
+    return {unitXObj, unitYObj, unitColorObj, displayUnitX, displayUnitY, displayUnitColor, discrete}
+  }, [filterData, x.unit, xParsed.quantity, y.unit, yParsed.quantity, markers?.color?.unit, colorParsed?.quantity, units, error])
+
+  // Create final axis config for the plot
+  const {xAxis, yAxis, colorAxis} = useMemo(() => {
+    if (error) return {}
+    const xTitle = x.title || filterData[xParsed.quantity]?.label
+    const yTitle = y.title || filterData[yParsed.quantity]?.label
+    const colorTitle = markers?.color?.title || filterData[colorParsed.quantity]?.label
+    const unitLabelX = displayUnitX.label()
+    const unitLabelY = displayUnitY.label()
+    const unitLabelColor = displayUnitColor.label()
+    return {
+      xAxis: {...x, ...xParsed, title: xTitle, unit: unitLabelX},
+      yAxis: {...y, ...yParsed, title: yTitle, unit: unitLabelY},
+      colorAxis: markers?.color ? {...markers.color, ...colorParsed, title: colorTitle, unit: unitLabelColor} : {}
+    }
+  }, [colorParsed, displayUnitColor, displayUnitX, displayUnitY, filterData, markers?.color, x, xParsed, y, yParsed, error])
+
+  const setXFilter = useSetFilter(xParsed.quantity)
+  const setYFilter = useSetFilter(yParsed.quantity)
+
   const setWidget = useSetWidget(id)
   const pagination = useMemo(() => ({
     page_size: size,
     order: 'asc'
   }), [size])
   const required = useMemo(() => {
-    const include = ['entry_id']
-    !isEmpty(x) && include.push(x)
-    !isEmpty(y) && include.push(y)
-    !isEmpty(color) && include.push(color)
-    return {include}
-  }, [x, y, color])
+    const include = new Set(['entry_id'])
+    for (const config of [xParsed, yParsed, colorParsed]) {
+      if (!isEmpty(config)) {
+        include.add(config.quantity)
+        for (const extra of config.extras) {
+          include.add(extra)
+        }
+      }
+    }
+    return {include: [...include]}
+  }, [xParsed, yParsed, colorParsed])
 
   useEffect(() => {
     setLoading(true)
@@ -129,7 +175,126 @@ export const WidgetScatterPlot = React.memo((
   const hitsCallback = useCallback(() => {
     setLoading(false)
   }, [])
+
+  // Fetch the data using the useHits hook that automatically applies the
+  // existing filters. We filter out data that is invalid (e.g. no values or
+  // incompatible sizes between x/y/color). TODO: The API should support an
+  // "exists" query that could be used to return hits that actually have the
+  // requested values.  This way we would get a better match for the query size
+  // and we would not need to manually validate and check the results.
   const hits = useHits(id, required, pagination, hitsCallback)
+  const dataRaw = useMemo(() => {
+    if (!hits || error) return
+    function getData(hit) {
+      const hitData = {}
+
+      // Get each property using JMESPath. Errors at this stage will simply
+      // cause the entry to be ignored.
+      for (const [name, path] of [['x', xParsed.path], ['y', yParsed.path], ['color', colorParsed.path]]) {
+        if (isEmpty(path)) continue
+        let value
+        try {
+          value = jmespath.search(hit, path)
+        } catch (e) {
+          return {error: 'Invalid JMESPATH'}
+        }
+        // Missing x/y/color value will cause an error unless dealing with
+        // discretized colors
+        if (isNil(value)) {
+          if (name === 'color' && discrete) {
+            value = 'undefined'
+          } else {
+            return {error: 'Empty value'}
+          }
+        }
+        hitData[name] = value
+      }
+
+      // Get the shapes
+      const xShape = getShape(hitData.x)
+      const yShape = getShape(hitData.y)
+
+      // Check if x/y leaf shapes match
+      if (xShape[xShape.length - 1] !== yShape[yShape.length - 1]) {
+        return {error: 'Incompatible size for x/y'}
+      }
+
+      // If x/y shapes do not match, extend accordingly
+      const biggestShape = [xShape, yShape].reduce((prev, current) => {
+        return (prev.length > current.length)
+          ? prev
+          : current
+      })
+      hitData.x = extendFront(hitData.x, xShape, biggestShape)
+      hitData.y = extendFront(hitData.y, yShape, biggestShape)
+
+      // Modify color dimensions
+      let colorShape = colorParsed.path && getShape(hitData.color)
+      if (colorShape && !isEqual(colorShape, biggestShape)) {
+        // If color has one more dimension than other arrays and it is discrete,
+        // we reduce the last dimension to a single string
+        if (discrete && colorShape.length === biggestShape.length + 1) {
+          hitData.color = reduceInner(hitData.color)
+          colorShape = colorShape.slice(0, -1)
+        }
+        // Scalar color values are extended
+        if (colorShape.length === 0 || (colorShape.length === 1 && colorShape[0] === 1)) {
+          hitData.color = fill(
+            biggestShape,
+            colorShape.length === 0
+               ? hitData.color
+               : hitData.color[0]
+          )
+        // Colors are extended according to traces
+        } else if ((colorShape.length < biggestShape.length) && colorShape[0] === biggestShape[0]) {
+          hitData.color = extendBack(hitData.color, colorShape, biggestShape)
+        } else {
+          return {error: 'Incompatible size for color'}
+        }
+      }
+
+      // Flatten arrays
+      hitData.x = flatten(hitData.x)
+      hitData.y = flatten(hitData.y)
+      hitData.color = colorParsed.path && flatten(hitData.color)
+
+      // If shapes still don't match, skip entry. TODO: This check is not ideal,
+      // since we may be accepting accidentally mathing sizes. A proper shape
+      // check that would also allow "ragged arrays" would be better.
+      if (hitData.x.length !== hitData.y.length || (colorParsed.path && hitData.x.length !== hitData.color.length)) {
+        return {error: 'Incompatible number of elements'}
+      }
+
+      return {hitData, nPoints: hitData.x.length}
+    }
+    const x = []
+    const y = []
+    const color = colorParsed.path ? [] : undefined
+    const id = []
+    for (const hit of hits) {
+      const {hitData, error, nPoints} = getData(hit)
+      if (error || !nPoints) continue
+      for (const i of range(nPoints)) {
+        x.push(hitData.x[i])
+        y.push(hitData.y[i])
+        colorParsed.path && color.push(hitData.color?.[i])
+        id.push(hit.entry_id)
+      }
+    }
+    return {x, y, color, id}
+  }, [discrete, hits, xParsed.path, yParsed.path, colorParsed.path, error])
+
+  // Perform unit conversion, report errors
+  const data = useMemo(() => {
+    if (!dataRaw) return
+    const x = new Quantity(dataRaw.x, unitXObj).to(displayUnitX).value()
+    const y = new Quantity(dataRaw.y, unitYObj).to(displayUnitY).value()
+    const color = dataRaw.color && (discrete
+      ? dataRaw.color
+      : new Quantity(dataRaw.color, unitColorObj).to(displayUnitColor).value()
+    )
+    return {x, y, color, id: dataRaw.id}
+  }, [dataRaw, displayUnitColor, displayUnitX, displayUnitY, unitColorObj, unitXObj, unitYObj, discrete])
 
   const handleEdit = useCallback(() => {
     setWidget(old => { return {...old, editing: true } })
@@ -154,24 +319,21 @@ export const WidgetScatterPlot = React.memo((
 
   const handleSelected = useCallback((data) => {
     const range = data?.range
-    if (range) {
-      const unitXConverted = new Unit(unitX).toSystem(units)
-      const unitYConverted = new Unit(unitY).toSystem(units)
-      setXFilter({
-        gte: new Quantity(range.x[0], unitXConverted),
-        lte: new Quantity(range.x[1], unitXConverted)
-      })
-      setYFilter({
-        gte: new Quantity(range.y[0], unitYConverted),
-        lte: new Quantity(range.y[1], unitYConverted)
-      })
-      onSelected && onSelected(data)
-    }
-  }, [onSelected, setXFilter, setYFilter, unitX, unitY, units])
+    if (!range) return
+    setXFilter({
+      gte: new Quantity(range.x[0], displayUnitX),
+      lte: new Quantity(range.x[1], displayUnitX)
+    })
+    setYFilter({
+      gte: new Quantity(range.y[0], displayUnitY),
+      lte: new Quantity(range.y[1], displayUnitY)
+    })
+    onSelected?.(data)
+  }, [onSelected, setXFilter, setYFilter, displayUnitX, displayUnitY])
 
-  const handleDeselect = () => {
-    onSelected && onSelected(undefined)
-  }
+  const handleDeselect = useCallback(() => {
+    onSelected?.(undefined)
+  }, [onSelected])
 
   const actions = useMemo(() => {
       return <>
@@ -226,23 +388,25 @@ export const WidgetScatterPlot = React.memo((
       actions={actions}
       className={styles.widget}
     >
-      <PlotScatter
-        data={loading ? undefined : hits}
-        x={x}
-        y={y}
-        color={color}
-        unitX={unitX}
-        unitY={unitY}
-        unitColor={unitColor}
-        discrete={discrete}
-        autorange={autorange}
-        onSelected={handleSelected}
-        onDeselect={handleDeselect}
-        dragmode={dragmode}
-        onNavigateToEntry={handleNavigated}
-        data-testid={id}
-        ref={canvas}
-      />
+      {error
+        ? <Alert severity="error" className={styles.alert}>
+            {error}
+          </Alert>
+        : <PlotScatter
+          data={loading ? undefined : data}
+          xAxis={xAxis}
+          yAxis={yAxis}
+          colorAxis={colorAxis}
+          discrete={discrete}
+          autorange={autorange}
+          onSelected={handleSelected}
+          onDeselect={handleDeselect}
+          dragmode={dragmode}
+          onNavigateToEntry={handleNavigated}
+          data-testid={id}
+          ref={canvas}
+        />
+      }
     </Widget>
   </Floatable>
 })
@@ -251,9 +415,9 @@ WidgetScatterPlot.propTypes = {
   id: PropTypes.string.isRequired,
   label: PropTypes.string,
   description: PropTypes.string,
-  x: PropTypes.string,
-  y: PropTypes.string,
-  color: PropTypes.string,
+  x: PropTypes.object,
+  y: PropTypes.object,
+  markers: PropTypes.object,
   size: PropTypes.number,
   autorange: PropTypes.bool,
   dragmode: PropTypes.string,
@@ -264,110 +428,168 @@ WidgetScatterPlot.propTypes = {
 /**
  * A dialog that is used to configure a scatter plot widget.
  */
-export const WidgetScatterPlotEdit = React.memo((props) => {
-    const { id, editing, visible } = props
-    const { useSetWidget } = useSearchContext()
-    const [settings, setSettings] = useState(props)
+export const WidgetScatterPlotEdit = React.memo(({widget}) => {
+    const { filterData, useSetWidget } = useSearchContext()
+    const [settings, setSettings] = useState(cloneDeep(widget))
     const [errors, setErrors] = useState({})
-    const setWidget = useSetWidget(id)
-    const hasError = useMemo(() => {
-      return Object.values(errors).some((d) => !!d) || !schemaWidgetScatterPlot.isValidSync(settings)
-    }, [errors, settings])
-
-    const handleSubmit = useCallback((settings) => {
-      setWidget(old => ({...old, ...settings}))
-    }, [setWidget])
-
-    const handleChange = useCallback((key, value) => {
-      setSettings(old => ({...old, [key]: value}))
-    }, [setSettings])
+    const [dimensions, setDimensions] = useState({})
+    const setWidget = useSetWidget(widget.id)
 
     const handleError = useCallback((key, value) => {
       setErrors(old => ({...old, [key]: value}))
     }, [setErrors])
 
+    const handleErrorQuantity = useCallback((key, value) => {
+      handleError(key, value)
+      setDimensions((old) => ({...old, [key]: null}))
+    }, [handleError])
+
+    const handleChange = useCallback((key, value) => {
+      setSettings(old => {
+        const newValue = {...old}
+        setDeep(newValue, key, value)
+        return newValue
+      })
+    }, [setSettings])
+
     const handleClose = useCallback(() => {
       setWidget(old => ({...old, editing: false}))
     }, [setWidget])
 
     const handleAccept = useCallback((key, value) => {
       try {
-        schemaWidgetScatterPlot.validateSyncAt(key, {[key]: value})
+        reach(schemaWidgetScatterPlot, key).validateSync(value)
       } catch (e) {
         handleError(key, e.message)
         return
       }
       setErrors(old => ({...old, [key]: undefined}))
-      setSettings(old => ({...old, [key]: value}))
-    }, [handleError, setSettings])
+      handleChange(key, value)
+    }, [handleError, handleChange])
 
+    const handleAcceptQuantity = useCallback((key, value) => {
+      handleAccept(key, value)
+      const { quantity } = parseJMESPath(value)
+      const dimension = filterData[quantity]?.dimension
+      setDimensions((old) => ({...old, [key]: dimension}))
+    }, [handleAccept, filterData])
+
+    // Upon accepting the entire form, we perform final validation that also
+    // takes into account cross-field incompatibilities
     const handleEditAccept = useCallback(() => {
-      handleSubmit({...settings, editing: false, visible: true})
-    }, [handleSubmit, settings])
+      // Check for independent errors from components
+      const independentErrors = Object.values(errors).some(x => !!x)
+      if (!independentErrors) {
+        setWidget(old => ({...old, ...{...settings, editing: false, visible: true}}))
+      }
+    }, [settings, setWidget, errors])
 
     return <WidgetEditDialog
-        id={id}
-        open={editing}
-        visible={visible}
+        id={widget.id}
+        open={widget.editing}
+        visible={widget.visible}
         title="Edit scatter plot widget"
         onClose={handleClose}
         onAccept={handleEditAccept}
-        error={hasError}
       >
       <WidgetEditGroup title="x axis">
         <WidgetEditOption>
-          <InputSearchMetainfo
+          <InputJMESPath
             label="quantity"
-            value={settings.x}
-            error={errors.x}
-            onChange={(value) => handleChange('x', value)}
-            onSelect={(value) => handleAccept('x', value)}
-            onError={(value) => handleError('x', value)}
+            value={settings.x?.quantity}
+            onChange={(value) => handleChange('x.quantity', value)}
+            onSelect={(value) => handleAcceptQuantity('x.quantity', value)}
+            onAccept={(value) => handleAcceptQuantity('x.quantity', value)}
+            error={errors['x.quantity']}
+            onError={(value) => handleErrorQuantity('x.quantity', value)}
             dtypes={dtypesNumeric}
+            dtypesRepeatable={dtypesNumeric}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <InputTextField
+            label="title"
+            fullWidth
+            value={settings.x?.title}
+            onChange={(event) => handleChange('x.title', event.target.value)}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <UnitInput
+            label='unit'
+            value={settings.x?.unit}
+            onChange={(value) => handleChange('x.unit', value)}
+            onSelect={(value) => handleAccept('x.unit', value)}
+            onAccept={(value) => handleAccept('x.unit', value)}
+            error={errors['x.unit']}
+            onError={(value) => handleError('x.unit', value)}
+            dimension={dimensions['x.quantity'] || null}
+            optional
+            disableGroup
           />
         </WidgetEditOption>
       </WidgetEditGroup>
       <WidgetEditGroup title="y axis">
         <WidgetEditOption>
-          <InputSearchMetainfo
+          <InputJMESPath
             label="quantity"
-            value={settings.y}
-            error={errors.y}
-            onChange={(value) => handleChange('y', value)}
-            onSelect={(value) => handleAccept('y', value)}
-            onError={(value) => handleError('y', value)}
+            value={settings.y?.quantity}
+            onChange={(value) => handleChange('y.quantity', value)}
+            onSelect={(value) => handleAcceptQuantity('y.quantity', value)}
+            onAccept={(value) => handleAcceptQuantity('y.quantity', value)}
+            error={errors['y.quantity']}
+            onError={(value) => handleErrorQuantity('y.quantity', value)}
             dtypes={dtypesNumeric}
+            dtypesRepeatable={dtypesNumeric}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <InputTextField
+            label="title"
+            fullWidth
+            value={settings.y?.title}
+            onChange={(event) => handleChange('y.title', event.target.value)}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <UnitInput
+            label='unit'
+            value={settings.y?.unit}
+            onChange={(value) => handleChange('y.unit', value)}
+            onSelect={(value) => handleAccept('y.unit', value)}
+            onAccept={(value) => handleAccept('y.unit', value)}
+            error={errors['y.unit']}
+            onError={(value) => handleError('y.unit', value)}
+            dimension={dimensions['y.quantity'] || null}
+            optional
+            disableGroup
           />
         </WidgetEditOption>
       </WidgetEditGroup>
-      <WidgetEditGroup title="color">
+      <WidgetEditGroup title="marker color">
         <WidgetEditOption>
-          <InputSearchMetainfo
+          <InputJMESPath
             label="quantity"
-            value={settings.color}
-            error={errors.color}
-            onChange={(value) => handleChange('color', value)}
-            onSelect={(value) => handleAccept('color', value)}
-            onError={(value) => handleError('color', value)}
+            value={settings?.markers?.color?.quantity}
+            onChange={(value) => handleChange('markers.color.quantity', value)}
+            onSelect={(value) => handleAccept('markers.color.quantity', value)}
+            onAccept={(value) => handleAccept('markers.color.quantity', value)}
+            error={errors['markers.color.quantity']}
+            onError={(value) => handleError('markers.color.quantity', value)}
             dtypes={dtypesColor}
-            dtypesRepeatable={dtypesColorRepeatable}
+            dtypesRepeatable={dtypesColor}
+            optional
           />
         </WidgetEditOption>
       </WidgetEditGroup>
       <WidgetEditGroup title="general">
         <WidgetEditOption>
-          <TextField
-            select
-            fullWidth
-            label="Maximum number of points"
-            variant="filled"
+          <WidgetEditSelect
+            label="Maximum number of entries to load"
+            options={nPointsOptions}
             value={settings.size}
             onChange={(event) => { handleChange('size', event.target.value) }}
-          >
-            <MenuItem value={100}>100</MenuItem>
-            <MenuItem value={1000}>1000</MenuItem>
-            <MenuItem value={10000}>10000</MenuItem>
-          </TextField>
+          />
         </WidgetEditOption>
         <WidgetEditOption>
           <FormControlLabel
@@ -380,21 +602,125 @@ export const WidgetScatterPlotEdit = React.memo((props) => {
 })
 
 WidgetScatterPlotEdit.propTypes = {
-  id: PropTypes.string.isRequired,
-  editing: PropTypes.bool,
-  visible: PropTypes.bool,
-  x: PropTypes.string,
-  y: PropTypes.string,
-  color: PropTypes.string,
-  size: PropTypes.number,
-  autorange: PropTypes.bool,
-  onClose: PropTypes.func
+  widget: PropTypes.object
 }
 
 export const schemaWidgetScatterPlot = schemaWidget.shape({
-  x: string().required('Quantity for the x axis is required.'),
-  y: string().required('Quantity for the y axis is required.'),
-  color: string(),
+  x: schemaAxis.required('Quantity for the x axis is required.'),
+  y: schemaAxis.required('Quantity for the y axis is required.'),
+  markers: schemaMarkers,
   size: number().integer().required('Size is required.'),
   autorange: bool()
 })
+
+/**
+ * Used to flatten the input into a single array of values.
+ */
+function flatten(input) {
+  return isArray(input)
+    ? flattenDeep(input)
+    : [input]
+}
+
+/**
+ * Gets the shape of an abitrarily nested array.
+ */
+function getShape(input) {
+  if (!isArray(input)) {
+    return []
+  }
+
+  const shape = []
+  let inner = input
+
+  while (isArray(inner)) {
+    shape.push(inner.length)
+    inner = inner[0]
+  }
+
+  return shape
+}
+
+/**
+ * Reduces the innermost dimension into a single value.
+ */
+function reduceInner(input) {
+  function reduceRec(inp) {
+    if (isArray(inp)) {
+      if (isArray(inp[0])) {
+        for (let i = 0; i < inp.length; ++i) {
+          inp[i] = reduceRec(inp[i])
+        }
+      } else {
+        return inp.sort().join(", ")
+      }
+    }
+    return inp
+  }
+  return reduceRec(input)
+}
+
+/**
+ * Resizes the given array to a new size by extending the data to fit the front
+ * dimensions (similar to array = array[None, :] in NumPy).
+ */
+function extendFront(array, oldShape, newShape) {
+  // If shape is already correct, return the input array
+  const diff = newShape.length - oldShape.length
+  if (diff === 0) return array
+
+  // Extend the array
+  const extendedArray = []
+  function extendRec(depth) {
+    const dim = newShape[depth]
+    const hasData = depth === diff
+    if (hasData) {
+      return array
+    } else {
+      for (let j = 0; j < dim; ++j) {
+        extendedArray.push(extendRec(depth + 1))
+      }
+    }
+    return extendedArray
+  }
+  extendRec(0)
+  return extendedArray
+}
+
+/**
+ * Resizes the given array to a new size by extending the data to fit the last
+ * dimensions dimensions (similar to array = array[:, None] in NumPy).
+ */
+function extendBack(array, oldShape, newShape) {
+  // If shape is already correct, return the input array
+  const diff = newShape.length - oldShape.length
+  if (diff === 0) return array
+
+  // Extend the array
+  const extendedArray = []
+  const nTraces = newShape[0]
+  for (let i = 0; i < nTraces; ++i) {
+    const traceArray = []
+    for (let j = 0; j < newShape[1]; ++j) {
+      traceArray.push([array[i]])
+    }
+    extendedArray.push(traceArray)
+  }
+  return extendedArray
+}
+
+/**
+ * Creates a new array with the given shape, filled with the given value.
+ */
+function fill(shape, fillValue) {
+  if (shape.length === 0) {
+    return fillValue
+  } else {
+    const innerShape = shape.slice(1)
+    const innerArray = []
+    for (let i = 0; i < shape[0]; i++) {
+        innerArray.push(fill(innerShape, fillValue))
+    }
+    return innerArray
+  }
+}
diff --git a/gui/src/components/search/widgets/WidgetScatterPlot.spec.js b/gui/src/components/search/widgets/WidgetScatterPlot.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..36fd9c1f9e7ba5d91069ae9a948be0e818578efe
--- /dev/null
+++ b/gui/src/components/search/widgets/WidgetScatterPlot.spec.js
@@ -0,0 +1,180 @@
+/*
+ * Copyright The NOMAD Authors.
+ *
+ * This file is part of NOMAD. See https://nomad-lab.eu for further info.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, { useMemo } from 'react'
+import { screen } from '../../conftest.spec'
+import { expectWidgetScatterPlot, renderSearchEntry } from '../conftest.spec'
+import { WidgetScatterPlot } from './WidgetScatterPlot'
+
+// Mock the resize observer that defines the widget size
+jest.mock('react-resize-detector', () => {
+  return {useResizeDetector: () => {
+    return {height: 400, width: 400, ref: undefined}
+  }}
+})
+
+// Mock the useHits hook that returns the plotted data. Note that the useHits
+// hook needs to memo things to prevent rendering loops.
+const mockUseMemo = useMemo
+jest.mock('../SearchContext', () => ({
+    ...jest.requireActual('../SearchContext'),
+    useSearchContext: () => ({
+      ...jest.requireActual('../SearchContext').useSearchContext(),
+      useHits: (id, required, pagination, callback) => {
+        const response = mockUseMemo(() => {
+          callback()
+          return [
+            {
+              entry_id: 0,
+              results: {
+                material: {n_elements: 1, elements: ['Si'], chemical_formula_hill: 'Si2'},
+                properties: {
+                  electronic: {band_gap: [{value: 0}, {value: 1}]},
+                  catalytic: {
+                    reactivity: {
+                      test_temperatures: [0, 1, 2],
+                      reactants: [
+                        {name: 'CO2', conversion: [0, 1, 2]},
+                        {name: 'NO2', conversion: [3, 4, 5]}
+                      ]
+                    }
+                  }
+                }
+              }
+            },
+            {
+              entry_id: 1,
+              results: {
+                material: {n_elements: 1, elements: ['C', 'O'], chemical_formula_hill: 'CO2'},
+                properties: {
+                  electronic: {band_gap: [{value: 0}, {value: 1}]},
+                  catalytic: {
+                    reactivity: {
+                      test_temperatures: [0, 1, 2],
+                      reactants: [
+                        {name: 'H2O', conversion: [0, 1, 2]},
+                        {name: 'O2', conversion: [3, 4, 5]}
+                      ]
+                    }
+                  }
+                }
+              }
+            }
+          ]
+        }, [])
+        return response
+      }
+    })
+}))
+
+describe('test different combinations of x/y/color produced with JMESPath', () => {
+  test.each([
+    ['scalar quantity', 'results.material.n_elements', 'results.material.n_elements', 'results.material.n_elements'],
+    ['index expression', 'results.properties.electronic.band_gap[0].value', 'results.properties.electronic.band_gap[0].value', 'results.properties.electronic.band_gap[0].value'],
+    ['slicing', 'results.properties.electronic.band_gap[1:2].value', 'results.properties.electronic.band_gap[1:2].value', 'results.properties.electronic.band_gap[1:2].value'],
+    ['function', 'min(results.properties.electronic.band_gap[*].value)', 'min(results.properties.electronic.band_gap[*].value)', 'min(results.properties.electronic.band_gap[*].value)'],
+    ['filter projection', "results.material.topology[?label=='original'].cell.a", "results.material.topology[?label=='original'].cell.a", "results.material.topology[?label=='original'].cell.a"],
+    ['1D array vs 2D array vs 1D color', 'results.properties.catalytic.reactivity.test_temperatures', 'results.properties.catalytic.reactivity.reactants[*].conversion', 'results.properties.catalytic.reactivity.reactants[*].name']
+  ])('%s', async (name, x, y, color) => {
+    const config = {
+      id: '0',
+      scale: 'linear',
+      x: {quantity: x},
+      y: {quantity: y},
+      markers: {color: {quantity: color}}
+    }
+    renderSearchEntry(<WidgetScatterPlot {...config} />)
+    await expectWidgetScatterPlot(config, false)
+  })
+})
+
+describe('test custom axis titles', () => {
+  test.each([
+    ['x, no unit', {x: {title: 'My Title', quantity: 'results.material.n_elements'}}, 'My Title'],
+    ['y, no unit', {y: {title: 'My Title', quantity: 'results.material.n_elements'}}, 'My Title'],
+    ['color, no unit', {markers: {color: {title: 'My Title', quantity: 'results.material.n_elements'}}}, 'My Title'],
+    ['x, with unit', {x: {title: 'My Title', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'My Title (eV)'],
+    ['y, with unit', {y: {title: 'My Title', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'My Title (eV)'],
+    ['color, with unit', {markers: {color: {title: 'My Title', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}}, 'My Title (eV)']
+  ])('%s', async (name, config, title) => {
+    const configFinal = {
+      id: '0',
+      scale: 'linear',
+      x: {quantity: 'results.material.n_elements'},
+      y: {quantity: 'results.material.n_elements'},
+      markers: {color: {quantity: 'results.material.n_elements'}},
+      ...config
+    }
+    renderSearchEntry(<WidgetScatterPlot {...configFinal} />)
+    screen.getByText(title)
+  })
+})
+
+describe('test custom axis units', () => {
+  test.each([
+    ['x', {x: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'Final Energy Difference (Ha)'],
+    ['y', {y: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}, 'Final Energy Difference (Ha)'],
+    ['color', {markers: {color: {unit: 'Ha', quantity: 'results.properties.geometry_optimization.final_energy_difference'}}}, 'Final Energy Difference (Ha)']
+  ])('%s', async (name, config, title) => {
+    const configFinal = {
+      id: '0',
+      scale: 'linear',
+      x: {quantity: 'results.material.n_elements'},
+      y: {quantity: 'results.material.n_elements'},
+      markers: {color: {quantity: 'results.material.n_elements'}},
+      ...config
+    }
+    renderSearchEntry(<WidgetScatterPlot {...configFinal} />)
+    screen.getByText(title)
+  })
+})
+
+describe('test different colors', () => {
+  test.each([
+    ['empty', undefined, undefined, []],
+    ['scalar integer', 'results.material.n_elements', {quantity: 'results.material.n_elements'}, []],
+    ['scalar float', 'results.properties.geometry_optimization.final_energy_difference', {quantity: 'results.properties.geometry_optimization.final_energy_difference'}, []],
+    ['scalar string', 'results.material.chemical_formula_hill', undefined, ['Si2', 'CO2']],
+    ['array string', 'results.material.elements', undefined, ['Si', 'C, O']]
+  ])('%s', async (name, axis, colorTitle, legend) => {
+    const config = {
+      id: '0',
+      scale: 'linear',
+      x: {quantity: 'results.material.n_elements'},
+      y: {quantity: 'results.material.n_elements'},
+      markers: {color: {quantity: axis}}
+    }
+    renderSearchEntry(<WidgetScatterPlot {...config} />)
+    await expectWidgetScatterPlot(config, false, colorTitle, legend)
+  })
+})
+
+describe('test error messages', () => {
+  test.each([
+    ['invalid JMESPath', 'results.properties.electronic.band_gap[*.value', 'Invalid JMESPath query, please check your syntax.']
+  ])('%s', async (name, axis, message) => {
+    const config = {
+      id: '0',
+      scale: 'linear',
+      x: {quantity: axis},
+      y: {quantity: axis},
+      markers: {color: {quantity: axis}}
+    }
+    renderSearchEntry(<WidgetScatterPlot {...config} />)
+    screen.getByText(message, {exact: false})
+  })
+})
diff --git a/gui/src/components/search/widgets/WidgetScatterPlotEdit.js b/gui/src/components/search/widgets/WidgetScatterPlotEdit.js
new file mode 100644
index 0000000000000000000000000000000000000000..383e7d27b1cbd890a54d08d5ec3b3c20729647d8
--- /dev/null
+++ b/gui/src/components/search/widgets/WidgetScatterPlotEdit.js
@@ -0,0 +1,268 @@
+/*
+ * Copyright The NOMAD Authors.
+ *
+ * This file is part of NOMAD. See https://nomad-lab.eu for further info.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, {useCallback, useState} from 'react'
+import PropTypes from 'prop-types'
+import { number, bool, reach } from 'yup'
+import { cloneDeep } from 'lodash'
+import {
+  Checkbox,
+  FormControlLabel
+} from '@material-ui/core'
+import { InputJMESPath } from '../input/InputMetainfo'
+import { schemaWidget, schemaAxis, schemaMarkers } from './Widget'
+import { WidgetEditDialog, WidgetEditGroup, WidgetEditOption, WidgetEditSelect } from './WidgetEdit'
+import { useSearchContext } from '../SearchContext'
+import { autorangeDescription } from './WidgetHistogram'
+import { DType, setDeep, parseJMESPath } from '../../../utils'
+import { InputTextField } from '../input/InputText'
+import UnitInput from '../../units/UnitInput'
+
+// Predefined in order to not break memoization
+const dtypesNumeric = new Set([DType.Int, DType.Float])
+const dtypesColor = new Set([DType.String, DType.Enum, DType.Float, DType.Int])
+const nPointsOptions = {
+  100: 100,
+  1000: 1000,
+  10000: 10000
+}
+function isEmptyString(value) {
+  return value === undefined || value === null || !value?.trim?.()?.length
+}
+/**
+ * A dialog that is used to configure a scatter plot widget.
+ */
+export const WidgetScatterPlotEdit = React.memo(({widget}) => {
+    const { filterData, useSetWidget } = useSearchContext()
+    const [settings, setSettings] = useState(cloneDeep(widget))
+    const [errors, setErrors] = useState({})
+    const [dimensions, setDimensions] = useState({})
+    const setWidget = useSetWidget(widget.id)
+
+    const handleError = useCallback((key, value) => {
+      setErrors(old => ({...old, [key]: value}))
+    }, [setErrors])
+
+    const handleErrorQuantity = useCallback((key, value) => {
+      handleError(key, value)
+      setDimensions((old) => ({...old, [key]: null}))
+    }, [handleError])
+
+    const handleChange = useCallback((key, value) => {
+      setSettings(old => {
+        const newValue = {...old}
+        setDeep(newValue, key, value)
+        return newValue
+      })
+    }, [setSettings])
+
+    const handleClose = useCallback(() => {
+      setWidget(old => ({...old, editing: false}))
+    }, [setWidget])
+
+    const handleAccept = useCallback((key, value) => {
+      try {
+        reach(schemaWidgetScatterPlot, key).validateSync(value)
+      } catch (e) {
+        handleError(key, e.message)
+        return
+      }
+      setErrors(old => ({...old, [key]: undefined}))
+      handleChange(key, value)
+    }, [handleError, handleChange])
+
+    const handleAcceptQuantity = useCallback((key, value) => {
+      handleAccept(key, value)
+      const { quantity } = parseJMESPath(value)
+      const dimension = filterData[quantity]?.dimension
+      setDimensions((old) => ({...old, [key]: dimension}))
+    }, [handleAccept, filterData])
+
+    // Upon accepting the entire form, we perform final validation that also
+    // takes into account cross-field incompatibilities
+    const handleEditAccept = useCallback(() => {
+      // Check for independent errors from components
+      const independentErrors = Object.values(errors).some(x => !!x)
+      if (independentErrors) return
+
+      // Check for missing values: TODO: This kind of check should be replaced
+      // by a dedicated form context. Inputs could automatically register into
+      // this form to enable imperative validation etc.
+      const xEmpty = isEmptyString(settings?.x?.quantity)
+      if (xEmpty) {
+        handleErrorQuantity('x.quantity', 'Please specify a value')
+      }
+      const yEmpty = isEmptyString(settings?.y?.quantity)
+      if (yEmpty) {
+        handleErrorQuantity('y.quantity', 'Please specify a value')
+      }
+
+      if (!independentErrors && !xEmpty && !yEmpty) {
+        setWidget(old => ({...old, ...{...settings, editing: false, visible: true}}))
+      }
+    }, [settings, setWidget, errors, handleErrorQuantity])
+
+    return <WidgetEditDialog
+        id={widget.id}
+        open={widget.editing}
+        visible={widget.visible}
+        title="Edit scatter plot widget"
+        onClose={handleClose}
+        onAccept={handleEditAccept}
+      >
+      <WidgetEditGroup title="x axis">
+        <WidgetEditOption>
+          <InputJMESPath
+            label="quantity"
+            value={settings.x?.quantity}
+            onChange={(value) => handleChange('x.quantity', value)}
+            onSelect={(value) => handleAcceptQuantity('x.quantity', value)}
+            onAccept={(value) => handleAcceptQuantity('x.quantity', value)}
+            error={errors['x.quantity']}
+            onError={(value) => handleErrorQuantity('x.quantity', value)}
+            dtypes={dtypesNumeric}
+            dtypesRepeatable={dtypesNumeric}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <InputTextField
+            label="title"
+            fullWidth
+            value={settings.x?.title}
+            onChange={(event) => handleChange('x.title', event.target.value)}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <UnitInput
+            label='unit'
+            value={settings.x?.unit}
+            onChange={(value) => handleChange('x.unit', value)}
+            onSelect={(value) => handleAccept('x.unit', value)}
+            onAccept={(value) => handleAccept('x.unit', value)}
+            error={errors['x.unit']}
+            onError={(value) => handleError('x.unit', value)}
+            dimension={dimensions['x.quantity'] || null}
+            optional
+            disableGroup
+          />
+        </WidgetEditOption>
+      </WidgetEditGroup>
+      <WidgetEditGroup title="y axis">
+        <WidgetEditOption>
+          <InputJMESPath
+            label="quantity"
+            value={settings.y?.quantity}
+            onChange={(value) => handleChange('y.quantity', value)}
+            onSelect={(value) => handleAcceptQuantity('y.quantity', value)}
+            onAccept={(value) => handleAcceptQuantity('y.quantity', value)}
+            error={errors['y.quantity']}
+            onError={(value) => handleErrorQuantity('y.quantity', value)}
+            dtypes={dtypesNumeric}
+            dtypesRepeatable={dtypesNumeric}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <InputTextField
+            label="title"
+            fullWidth
+            value={settings.y?.title}
+            onChange={(event) => handleChange('y.title', event.target.value)}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <UnitInput
+            label='unit'
+            value={settings.y?.unit}
+            onChange={(value) => handleChange('y.unit', value)}
+            onSelect={(value) => handleAccept('y.unit', value)}
+            onAccept={(value) => handleAccept('y.unit', value)}
+            error={errors['y.unit']}
+            onError={(value) => handleError('y.unit', value)}
+            dimension={dimensions['y.quantity'] || null}
+            optional
+            disableGroup
+          />
+        </WidgetEditOption>
+      </WidgetEditGroup>
+      <WidgetEditGroup title="marker color">
+        <WidgetEditOption>
+          <InputJMESPath
+            label="quantity"
+            value={settings?.markers?.color?.quantity}
+            onChange={(value) => handleChange('markers.color.quantity', value)}
+            onSelect={(value) => handleAcceptQuantity('markers.color.quantity', value)}
+            onAccept={(value) => handleAcceptQuantity('markers.color.quantity', value)}
+            error={errors['markers.color.quantity']}
+            onError={(value) => handleErrorQuantity('markers.color.quantity', value)}
+            dtypes={dtypesColor}
+            dtypesRepeatable={dtypesColor}
+            optional
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <InputTextField
+            label="title"
+            fullWidth
+            value={settings.markers?.color?.title}
+            onChange={(event) => handleChange('markers.color.title', event.target.value)}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <UnitInput
+            label='unit'
+            value={settings.markers?.color?.unit}
+            onChange={(value) => handleChange('markers.color.unit', value)}
+            onSelect={(value) => handleAccept('markers.color.unit', value)}
+            onAccept={(value) => handleAccept('markers.color.unit', value)}
+            error={errors['markers.color.unit']}
+            onError={(value) => handleError('markers.color.unit', value)}
+            dimension={dimensions['markers.color.quantity'] || null}
+            optional
+            disableGroup
+          />
+        </WidgetEditOption>
+      </WidgetEditGroup>
+      <WidgetEditGroup title="general">
+        <WidgetEditOption>
+          <WidgetEditSelect
+            label="Maximum number of entries to load"
+            options={nPointsOptions}
+            value={settings.size}
+            onChange={(event) => { handleChange('size', event.target.value) }}
+          />
+        </WidgetEditOption>
+        <WidgetEditOption>
+          <FormControlLabel
+            control={<Checkbox checked={settings.autorange} onChange={(event, value) => handleChange('autorange', value)}/>}
+            label={autorangeDescription}
+          />
+        </WidgetEditOption>
+      </WidgetEditGroup>
+    </WidgetEditDialog>
+})
+
+WidgetScatterPlotEdit.propTypes = {
+  widget: PropTypes.object
+}
+
+export const schemaWidgetScatterPlot = schemaWidget.shape({
+  x: schemaAxis.required('Quantity for the x axis is required.'),
+  y: schemaAxis.required('Quantity for the y axis is required.'),
+  markers: schemaMarkers,
+  size: number().integer().required('Size is required.'),
+  autorange: bool()
+})
diff --git a/gui/src/components/search/widgets/WidgetScatterPlotEdit.spec.js b/gui/src/components/search/widgets/WidgetScatterPlotEdit.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..04382e408e777565031aabbde5fce54232c96a2f
--- /dev/null
+++ b/gui/src/components/search/widgets/WidgetScatterPlotEdit.spec.js
@@ -0,0 +1,54 @@
+/*
+ * Copyright The NOMAD Authors.
+ *
+ * This file is part of NOMAD. See https://nomad-lab.eu for further info.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react'
+import userEvent from '@testing-library/user-event'
+import { screen } from '../../conftest.spec'
+import { renderSearchEntry } from '../conftest.spec'
+import { WidgetScatterPlotEdit } from './WidgetScatterPlotEdit'
+
+describe('test edit dialog error messages', () => {
+  test.each([
+    ['missing x', {x: {quantity: 'results.material.n_elements'}}, 'Please specify a value'],
+    ['missing y', {y: {quantity: 'results.material.n_elements'}}, 'Please specify a value'],
+    ['unavailable x', {x: {quantity: 'results.material.not_a_quantity'}}, 'The quantity "results.material.not_a_quantity" is not available.'],
+    ['unavailable y', {y: {quantity: 'results.material.not_a_quantity'}}, 'The quantity "results.material.not_a_quantity" is not available.'],
+    ['unavailable color', {markers: {color: {quantity: 'results.material.not_a_quantity'}}}, 'The quantity "results.material.not_a_quantity" is not available.'],
+    ['invalid jmespath x', {x: {quantity: 'results.material.n_elements[*'}}, 'Invalid JMESPath query, please check your syntax.'],
+    ['invalid jmespath y', {y: {quantity: 'results.material.n_elements[*'}}, 'Invalid JMESPath query, please check your syntax.'],
+    ['invalid jmespath color', {markers: {color: {quantity: 'results.material.n_elements[*'}}}, 'Invalid JMESPath query, please check your syntax.'],
+    ['no jmespath for repeating x', {x: {quantity: 'results.material.topology.cell.a'}}, 'The quantity "results.material.topology.cell.a" is contained in at least one repeatable section. Please use JMESPath syntax to select one or more target sections.'],
+    ['no jmespath for repeating y', {y: {quantity: 'results.material.topology.cell.a'}}, 'The quantity "results.material.topology.cell.a" is contained in at least one repeatable section. Please use JMESPath syntax to select one or more target sections.'],
+    ['no jmespath for repeating color', {markers: {color: {quantity: 'results.material.topology.cell.a'}}}, 'The quantity "results.material.topology.cell.a" is contained in at least one repeatable section. Please use JMESPath syntax to select one or more target sections.'],
+    ['invalid x unit', {x: {quantity: 'results.material.topology[0].cell.a', unit: 'nounit'}}, 'Unit "nounit" not found.'],
+    ['invalid y unit', {y: {quantity: 'results.material.topology[0].cell.a', unit: 'nounit'}}, 'Unit "nounit" not found.'],
+    ['invalid color unit', {markers: {color: {quantity: 'results.material.topology[0].cell.a', unit: 'nounit'}}}, 'Unit "nounit" not found.'],
+    ['incompatible x unit', {x: {quantity: 'results.material.topology[0].cell.a', unit: 'joule'}}, 'Unit "joule" is incompatible with dimension "length"'],
+    ['incompatible y unit', {y: {quantity: 'results.material.topology[0].cell.a', unit: 'joule'}}, 'Unit "joule" is incompatible with dimension "length"'],
+    ['incompatible color unit', {markers: {color: {quantity: 'results.material.topology[0].cell.a', unit: 'joule'}}}, 'Unit "joule" is incompatible with dimension "length"']
+  ])('%s', async (name, config, error) => {
+    const finalConfig = {
+      id: '0',
+      editing: true,
+      ...config
+    }
+    renderSearchEntry(<WidgetScatterPlotEdit widget={finalConfig} />)
+    const button = screen.getByText('Done')
+    await userEvent.click(button)
+    screen.getByText(error)
+  })
+})
diff --git a/gui/src/components/search/widgets/WidgetTerms.js b/gui/src/components/search/widgets/WidgetTerms.js
index ea2f0d1ccce6a28247b6ffdc1a936065b35685e9..4eb9dd0135189acaff620c4c5c7ccb4bb6b273e9 100644
--- a/gui/src/components/search/widgets/WidgetTerms.js
+++ b/gui/src/components/search/widgets/WidgetTerms.js
@@ -30,7 +30,7 @@ import {
 } from '@material-ui/core'
 import { useResizeDetector } from 'react-resize-detector'
 import { useSearchContext } from '../SearchContext'
-import { InputSearchMetainfo } from '../input/InputMetainfo'
+import { InputMetainfo } from '../input/InputMetainfo'
 import { InputTextQuantity } from '../input/InputText'
 import InputItem, { inputItemHeight } from '../input/InputItem'
 import InputUnavailable from '../input/InputUnavailable'
@@ -306,7 +306,7 @@ export const WidgetTermsEdit = React.memo((props) => {
       >
       <WidgetEditGroup title="x axis">
         <WidgetEditOption>
-          <InputSearchMetainfo
+          <InputMetainfo
             label="quantity"
             value={settings.quantity}
             error={errors.quantity}
diff --git a/gui/src/components/units/Quantity.js b/gui/src/components/units/Quantity.js
index 803e884c7a31e9249cebe1c1d3c9507586cbbfd4..66217f1beb5d99939321e91e622cdff3c19e8b38 100644
--- a/gui/src/components/units/Quantity.js
+++ b/gui/src/components/units/Quantity.js
@@ -16,8 +16,9 @@
  * limitations under the License.
  */
 
-import {isNumber, isArray, isNil} from 'lodash'
-import {Unit} from './Unit'
+import {isNumber, isArray} from 'lodash'
+import {Unit, normalizeExpression} from './Unit'
+import { Unit as UnitMathJS } from 'mathjs'
 import {mapDeep} from '../../utils'
 
 /**
@@ -35,7 +36,7 @@ export class Quantity {
   constructor(value, unit, normalized = false) {
     this.unit = new Unit(unit)
     if (!isNumber(value) && !isArray(value)) {
-      throw Error('Please provide the the value as a number, or as a multidimensional array of numbers.')
+      throw Error('Please provide the value as a number, or as a multidimensional array of numbers.')
     }
 
     // This attribute stores the quantity value in 'normalized' form that is
@@ -76,7 +77,7 @@ export class Quantity {
     return this.unit.label()
   }
 
-  dimension(base) {
+  dimension(base = false) {
     return this.unit.dimension(base)
   }
 
@@ -96,7 +97,7 @@ export class Quantity {
    * Checks if the given Quantity is equal to this one.
    * @param {Quantity} quantity Quantity to compare to
    * @returns boolean Whether quantities are equal
-   */
+    */
   equal(quantity) {
     if (quantity instanceof Quantity) {
       return this.normalized_value === quantity.normalized_value && this.unit.equalBase(quantity.unit)
@@ -110,44 +111,77 @@ export class Quantity {
  * Convenience function for parsing value and unit information from a string.
  *
  * @param {string} input The input string to parse
- * @param {boolean} requireValue Whether a value is required.
- * @param {boolean} requireUnit Whether a unit is required.
- * @param {string} dimension Dimension for the unit. Nil value means a
- * dimensionless unit.
+ * @param {string} dimension Dimension for the unit. Defaults to 'dimensionless'
+ *    if not specified. If you want to disable dimension checks, use null.
+ * @param {boolean} requireValue Whether an explicit numeric value is required at the start of the input.
+ * @param {boolean} requireUnit Whether an explicit unit in the input is required at the end of the input.
  * @returns Object containing the following properties, if available:
  *  - value: Numerical value as a number
- *  - valueString: Numerical value as a string
+ *  - valueString: The original number input as a string. Note that this can only return
+ *    the number when it is used as a prefix, and does not work with numbers that are
+ *    part of a complex expression, e.g. 300 eV / 1000 K.
  *  - unit: Unit instance
- *  - unitString: Unit as a string
  *  - error: Error messsage
  */
-export function parseQuantity(input, requireValue = true, requireUnit = true, dimension = undefined) {
+export function parseQuantity(input, dimension = 'dimensionless', requireValue = false, requireUnit = false) {
   input = input.trim()
-  const valueString = input.match(/^[+-]?((\d+\.\d+|\d+\.|\.\d?|\d+)(e|e\+|e-)\d+|(\d+\.\d+|\d+\.|\.\d?|\d+))?/)?.[0]
-  if (requireValue && isNil(valueString)) {
-    return {error: 'Enter a valid numerical value'}
-  }
-  const value = Number(valueString)
-  const unitString = input.substring(valueString.length).trim()
-  const dim = isNil(dimension) ? 'dimensionless' : dimension
-  if (unitString === '' && dim !== 'dimensionless' && requireUnit) {
-    return {value, valueString, unitString, error: 'Unit is required'}
+  let error
+  let value
+  let valueString = input.match(/^[+-]?((\d+\.\d+|\d+\.|\.\d?|\d+)(e|e\+|e-)\d+|(\d+\.\d+|\d+\.|\.\d?|\d+))?/)?.[0]
+  const unitString = input.substring(valueString.length)?.trim() || ''
+
+  // Check value if required
+  if (valueString === '') {
+    valueString = undefined
+    value = undefined
+    if (requireValue) {
+       error = 'Enter a valid numerical value'
+    }
+  } else {
+    value = Number(valueString)
   }
-  if (unitString === '' && !requireUnit) {
-    return {value, valueString, unitString}
+
+  // Check unit if required
+  if (requireUnit) {
+    if (unitString === '') {
+      return {valueString, value, error: 'Unit is required'}
+    }
   }
-  if (dim === 'dimensionless' && unitString !== '') {
-    return {value, valueString, unitString, error: 'Enter a numerical value without units'}
+
+  // Try to parse with MathJS: it can extract the unit even when it is mixed
+  // with numbers
+  input = normalizeExpression(input)
+  let unitMathJS
+  try {
+    unitMathJS = UnitMathJS.parse(input, {allowNoUnits: true})
+  } catch (e) {
+    return {valueString, error: e.message}
   }
+
   let unit
+  unitMathJS.value = null
   try {
-    unit = new Unit(dim === 'dimensionless' ? 'dimensionless' : input)
-  } catch {
-    return {valueString, value, unitString, error: `Unit "${unitString}" is not available`}
+    unit = new Unit(unitMathJS)
+  } catch (e) {
+    error = e.msg
+  }
+  if (error) {
+    return {valueString, value, unit, error}
   }
-  const inputDim = unit.dimension(false)
-  if (inputDim !== dimension) {
-    return {valueString, value, unitString, unit, error: `Unit "${unitString}" has incompatible dimension`}
+
+  // If unit is not required and it is dimensionless, return without new unit
+  if (!requireUnit && unit.dimension() === 'dimensionless') {
+    return {valueString, value}
   }
-  return {value, valueString, unit, unitString}
+
+  // TODO: This check is not enough: the input may be compatible after the base
+  // units are compared.
+  if (dimension !== null) {
+    const inputDim = unit.dimension()
+    if (inputDim !== dimension) {
+      error = `Unit "${unit.label(false)}" is incompatible with dimension "${dimension}"`
+    }
+  }
+
+  return {value, valueString, unit, error}
 }
diff --git a/gui/src/components/units/Quantity.spec.js b/gui/src/components/units/Quantity.spec.js
index e0a1f225eed595c1df0780b2521b305f5c1e5251..205326066f8ed5d12e9952d05eb89b776b0470ee 100644
--- a/gui/src/components/units/Quantity.spec.js
+++ b/gui/src/components/units/Quantity.spec.js
@@ -16,7 +16,8 @@
  * limitations under the License.
  */
 
-import { Quantity } from './Quantity'
+import { Unit } from './Unit'
+import { Quantity, parseQuantity } from './Quantity'
 import { dimensionMap } from './UnitContext'
 
 test('conversion works both ways for each compatible unit', async () => {
@@ -121,3 +122,23 @@ test.each([
   }
   expect(valueA).toBeCloseTo(10 * valueB)
 })
+
+test.each([
+  ['number only', '100', undefined, true, false, {valueString: '100', value: 100}],
+  ['unit only', 'joule', null, false, true, {valueString: undefined, value: undefined, unit: new Unit('joule')}],
+  ['number and unit with dimension', '100 joule', 'energy', true, true, {valueString: '100', value: 100, unit: new Unit('joule')}],
+  ['number and unit without dimension', '100 joule', null, true, true, {valueString: '100', value: 100, unit: new Unit('joule')}],
+  ['incorrect dimension', '100 joule', 'length', true, true, {valueString: '100', value: 100, unit: new Unit('joule'), error: 'Unit "joule" is incompatible with dimension "length"'}],
+  ['missing unit', '100', 'length', true, true, {valueString: '100', value: 100, unit: undefined, error: 'Unit is required'}],
+  ['missing value', 'joule', 'energy', true, true, {valueString: undefined, value: undefined, unit: new Unit('joule'), error: 'Enter a valid numerical value'}],
+  ['mixing number and quantity #1', '1 / joule', 'energy^-1', false, false, {valueString: '1', value: 1, unit: new Unit('1 / joule')}],
+  ['mixing number and quantity #2', '100 / joule', 'energy^-1', false, false, {valueString: '100', value: 100, unit: new Unit('1 / joule')}]
+
+]
+)('test parseQuantity: %s', async (name, input, dimension, requireValue, requireUnit, expected) => {
+  const result = parseQuantity(input, dimension, requireValue, requireUnit)
+  expect(result.valueString === expected.valueString).toBe(true)
+  expect(result.value === expected.value).toBe(true)
+  expect(result.unit?.label() === expected.unit?.label()).toBe(true)
+  expect(result.error === expected.error).toBe(true)
+})
diff --git a/gui/src/components/units/Unit.js b/gui/src/components/units/Unit.js
index d0a3e25307dd85036bcce5edcf0641a3a79e9cea..220ac7bc8e3aeb3437b357abfb47510ebb07149c 100644
--- a/gui/src/components/units/Unit.js
+++ b/gui/src/components/units/Unit.js
@@ -19,10 +19,12 @@ import {isNil, has, isString} from 'lodash'
 import {Unit as UnitMathJS} from 'mathjs'
 import {unitToAbbreviationMap} from './UnitContext'
 
+export const DIMENSIONLESS = 'dimensionless'
+
 /**
  * Helper class for persisting unit information.
  *
- * Builds upon the math.js Unit class system, but adds additional functionality,
+ * Builds upon the math.js Unit class, but adds additional functionality,
  * including:
  *  - Ability to convert to any unit system given as an argument
  *  - Abbreviated labels for dense formatting
@@ -33,7 +35,7 @@ export class Unit {
    */
   constructor(unit) {
     if (isString(unit)) {
-      unit = this.normalizeExpression(unit)
+      unit = normalizeExpression(unit)
       unit = new UnitMathJS(undefined, unit)
     } else if (unit instanceof Unit) {
       unit = unit.mathjsUnit.clone()
@@ -47,25 +49,6 @@ export class Unit {
     // this._label = undefined
   }
 
-  /**
-   * Normalizes the given expression into a format that can be parsed by MathJS.
-   *
-   * This function will replace the Pint power symbol of '**' with the symbol
-   * '^' used by MathJS. In addition, we convert any 'delta'-units (see:
-   * https://pint.readthedocs.io/en/stable/nonmult.html) into their regular
-   * counterparts: MathJS will automatically ignore the offset when using
-   * non-multiplicative units in expressions.
-   *
-   * @param {str} expression Expression
-   * @returns string Expression in normalized form
-   */
-  normalizeExpression(expression) {
-    let normalized = expression.replace(/\*\*/g, '^')
-    normalized = normalized.replace(/delta_/g, '')
-    normalized = normalized.replace(/Δ/g, '')
-    return normalized
-  }
-
   /**
    * Checks if the given unit has the same base dimensions as this one.
    * @param {str | Unit} unit Unit to compare to
@@ -73,7 +56,7 @@ export class Unit {
    */
   equalBase(unit) {
     if (isString(unit)) {
-      unit = this.normalizeExpression(unit)
+      unit = normalizeExpression(unit)
       unit = new Unit(unit)
     }
     return this.mathjsUnit.equalBase(unit.mathjsUnit)
@@ -101,7 +84,7 @@ export class Unit {
     let nDen = 0
 
     function getName(unit) {
-      if (unit.base.key === 'dimensionless') return ''
+      if (unit.base.key === DIMENSIONLESS) return ''
       return abbreviate
         ? unitToAbbreviationMap?.[unit.name] || unit.name
         : unit.name
@@ -207,7 +190,7 @@ export class Unit {
    * the original unit dimensions are used.
    * @returns The dimensionality as a string, e.g. 'time^2 energy mass^-2'
    */
-  dimension(base = true) {
+  dimension(base = false) {
     const dimensions = Object.keys(UnitMathJS.BASE_UNITS)
     const dimensionMap = Object.fromEntries(dimensions.map(name => [name, 0]))
 
@@ -227,9 +210,10 @@ export class Unit {
         }
       }
     }
-    return Object.entries(dimensionMap)
-      .filter(d => d[1] !== 0)
-      .map(d => `${d[0]}${((d[1] < 0 || d[1] > 1) && `^${d[1]}`) || ''}`).join(' ')
+    const dims = Object.entries(dimensionMap).filter(d => d[1] !== 0)
+    return dims.length > 0
+      ? dims.map(d => `${d[0]}${((d[1] < 0 || d[1] > 1) && `^${d[1]}`) || ''}`).join(' ')
+      : DIMENSIONLESS
   }
 
   /**
@@ -240,7 +224,7 @@ export class Unit {
    */
   to(unit) {
     if (isString(unit)) {
-      unit = this.normalizeExpression(unit)
+      unit = normalizeExpression(unit)
     } else if (unit instanceof Unit) {
       unit = unit.label()
     } else {
@@ -251,7 +235,7 @@ export class Unit {
     // to parse units like 1/<unit> as Math.js units which have values, and then
     // will raise an exception when converting between valueless and valued
     // unit. The workaround is to explicitly define a valueless unit.
-    unit = new UnitMathJS(undefined, unit)
+    unit = new UnitMathJS(undefined, unit === '' ? DIMENSIONLESS : unit)
     return new Unit(this.mathjsUnit.to(unit))
   }
 
@@ -362,3 +346,22 @@ export class Unit {
     return new Unit(ret)
   }
 }
+
+/**
+ * Normalizes the given expression into a format that can be parsed by MathJS.
+ *
+ * This function will replace the Pint power symbol of '**' with the symbol
+ * '^' used by MathJS. In addition, we convert any 'delta'-units (see:
+ * https://pint.readthedocs.io/en/stable/nonmult.html) into their regular
+ * counterparts: MathJS will automatically ignore the offset when using
+ * non-multiplicative units in expressions.
+ *
+ * @param {str} expression Expression
+ * @returns string Expression in normalized form
+ */
+export function normalizeExpression(expression) {
+  let normalized = expression.replace(/\*\*/g, '^')
+  normalized = normalized.replace(/delta_/g, '')
+  normalized = normalized.replace(/Δ/g, '')
+  return normalized
+}
diff --git a/gui/src/components/units/Unit.spec.js b/gui/src/components/units/Unit.spec.js
index 851b28246b3e9489a5daee9631e5c9b67a5ebb91..7cd88133bc700fbb8a531c4b5726dbc17959d7c9 100644
--- a/gui/src/components/units/Unit.spec.js
+++ b/gui/src/components/units/Unit.spec.js
@@ -63,15 +63,15 @@ test.each([
 })
 
 test.each([
-  ['dimensionless', 'dimensionless', 'dimensionless'],
-  ['single unit', 'meter', 'length'],
-  ['fixed order 1', 'meter * second', 'length time'],
-  ['fixed order 2', 'second * meter', 'length time'],
-  ['power', 'meter^3 * second^-1', 'length^3 time^-1'],
+  ['dimensionless', 'dimensionless', 'dimensionless', false],
+  ['single unit', 'meter', 'length', false],
+  ['fixed order 1', 'meter * second', 'length time', false],
+  ['fixed order 2', 'second * meter', 'length time', false],
+  ['power', 'meter^3 * second^-1', 'length^3 time^-1', false],
   ['in derived', 'joule', 'energy', false],
-  ['in base', 'joule', 'mass length^2 time^-2']
+  ['in base units', 'joule', 'mass length^2 time^-2', true]
 ]
-)('test getting dimension": %s', async (name, unit, dimension, base = true) => {
+)('test getting dimension": %s', async (name, unit, dimension, base) => {
   const a = new Unit(unit)
   expect(a.dimension(base)).toBe(dimension)
 })
diff --git a/gui/src/components/units/UnitDimensionSelect.js b/gui/src/components/units/UnitDimensionSelect.js
index aa7fdbba778f794e77015bd86f6fd267614b24cb..ad06d16703b1a883514205e739445aa625869421 100644
--- a/gui/src/components/units/UnitDimensionSelect.js
+++ b/gui/src/components/units/UnitDimensionSelect.js
@@ -38,7 +38,7 @@ const UnitDimensionSelect = React.memo(({label, dimension, onChange, disabled})
     setInputValue(unit?.definition)
   }, [unit, dimension])
 
-  const handleAccept = useCallback((unit, unitString) => {
+  const handleAccept = useCallback((unitString, unit) => {
     setUnits(old => {
       const newUnits = {
         ...old,
@@ -50,14 +50,10 @@ const UnitDimensionSelect = React.memo(({label, dimension, onChange, disabled})
     oldValue.current = unitString
   }, [dimension, onChange, setUnits])
 
-  const handleSelect = useCallback((unit, unitString) => {
-    handleAccept(unit, unit.label())
+  const handleSelect = useCallback((unitString, unit) => {
+    handleAccept(unit.label(), unit)
   }, [handleAccept])
 
-  const handleBlur = useCallback((onAccept) => {
-    onAccept(inputValue)
-  }, [inputValue])
-
   const handleChange = useCallback((value) => {
     oldValue.current = value
     setInputValue(value)
@@ -70,7 +66,6 @@ const UnitDimensionSelect = React.memo(({label, dimension, onChange, disabled})
       onChange={handleChange}
       onAccept={handleAccept}
       onSelect={handleSelect}
-      onBlur={handleBlur}
       onError={setError}
       dimension={dimension}
       error={error}
diff --git a/gui/src/components/units/UnitInput.js b/gui/src/components/units/UnitInput.js
index 9f84b838fd9a9ca9faa3e1f4ade678a4d3fe22fa..bddff7a034e939ae3b58ce033a27d25297e163d7 100644
--- a/gui/src/components/units/UnitInput.js
+++ b/gui/src/components/units/UnitInput.js
@@ -18,6 +18,7 @@
 
 import React, {useCallback, useEffect, useMemo, useRef, useContext, createContext} from 'react'
 import PropTypes from 'prop-types'
+import {isNil} from 'lodash'
 import {getSuggestions} from '../../utils'
 import {unitMap} from './UnitContext'
 import {parseQuantity} from './Quantity'
@@ -47,7 +48,7 @@ export const useInputStyles = makeStyles(theme => ({
     alignItems: 'stretch'
   }
 }))
-export const UnitInput = React.memo(({value, error, onChange, onAccept, onSelect, onError, onBlur, dimension, options, disabled, label, disableGroup}) => {
+export const UnitInput = React.memo(({value, error, onChange, onAccept, onSelect, onError, dimension, options, disabled, label, disableGroup, optional}) => {
   const styles = useInputStyles()
 
   // Predefine all option objects, all option paths and also pre-tokenize the
@@ -55,11 +56,12 @@ export const UnitInput = React.memo(({value, error, onChange, onAccept, onSelect
   const {keys, filter, finalOptions} = useMemo(() => {
     const finalOptions = {}
     Object.entries(unitMap)
-      .filter(([key, unit]) => unit.dimension === dimension)
+      .filter(([key, unit]) => dimension ? unit.dimension === dimension : true)
       .forEach(([key, unit]) => {
+        const unitAbbreviation = unit.abbreviation ? ` (${unit.abbreviation})` : ''
         finalOptions[key] = {
           key: key,
-          primary: `${unit.label} (${unit.abbreviation})`,
+          primary: `${unit.label}${unitAbbreviation}`,
           secondary: unit.aliases?.splice(1).join(', '),
           dimension: unit.dimension,
           unit: unit
@@ -70,31 +72,27 @@ export const UnitInput = React.memo(({value, error, onChange, onAccept, onSelect
     return {keys, filter, finalOptions}
   }, [dimension])
 
-  const handleChange = useCallback((value) => {
-    onChange?.(value)
-  }, [onChange])
-
-  const handleAccept = useCallback((key) => {
-    const {unit, unitString, error} = parseQuantity(key, false, true, dimension)
-    if (error) {
-      onError(error)
-    } else {
-      onAccept?.(unit, unitString)
+  const validate = useCallback((value) => {
+    if (isNil(value) || value?.trim?.() === '') {
+      if (optional) {
+        return {valid: true}
+      } else {
+        return {valid: false, error: 'Please specify a value'}
+      }
     }
-  }, [onAccept, onError, dimension])
-
-  const handleError = useCallback((value) => {
-    onError?.(value)
-  }, [onError])
+    const {error, unit} = parseQuantity(value, dimension, false, true)
+    return {valid: !error, error, data: unit}
+  }, [optional, dimension])
 
-  const handleSelect = useCallback((key) => {
-    const {unit, unitString, error} = parseQuantity(key, false, true, dimension)
+  // Revalidate input when dimension changes
+  useEffect(() => {
+    if (!value) return
+    const {error} = validate(value)
     if (error) {
-      onError(error)
-    } else {
-      onSelect?.(unit, unitString)
+      onError?.(error)
     }
-  }, [onSelect, onError, dimension])
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [dimension])
 
   // Used to filter the shown options based on input
   const filterOptions = useCallback((opt, { inputValue }) => {
@@ -107,13 +105,12 @@ export const UnitInput = React.memo(({value, error, onChange, onAccept, onSelect
     value={value}
     TextFieldProps={{label, disabled}}
     error={error}
-    onChange={handleChange}
-    onSelect={handleSelect}
-    onAccept={handleAccept}
-    onError={handleError}
-    onBlur={() => { onBlur(handleAccept) }}
+    onChange={onChange}
+    onSelect={onSelect}
+    onAccept={onAccept}
+    onError={onError}
+    validate={validate}
     disableClearable
-    disableAcceptOnBlur
     suggestAllOnFocus
     showOpenSuggestions
     suggestions={keys}
@@ -148,7 +145,8 @@ UnitInput.propTypes = {
   onBlur: PropTypes.func,
   onError: PropTypes.func,
   disabled: PropTypes.bool,
-  disableGroup: PropTypes.bool
+  disableGroup: PropTypes.bool,
+  optional: PropTypes.bool
 }
 
 export default UnitInput
diff --git a/gui/src/utils.js b/gui/src/utils.js
index ee928ab45b22cfaf4b0596a1ceb03438901167b7..c4305bfbebd2e40985616962a758942c342a0e52 100644
--- a/gui/src/utils.js
+++ b/gui/src/utils.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-unused-vars */
 /*
  * Copyright The NOMAD Authors.
  *
@@ -16,9 +17,10 @@
  * limitations under the License.
  */
 import minimatch from 'minimatch'
-import { cloneDeep, merge, isSet, isNil, isArray, isString, isNumber, isPlainObject, startCase, isEmpty } from 'lodash'
+import { cloneDeep, merge, isSet, isNil, isArray, isString, isNumber, isPlainObject, startCase, isEmpty, keys } from 'lodash'
 import { Quantity } from './components/units/Quantity'
 import { format } from 'date-fns'
+import jmespath from 'jmespath'
 import { dateFormat, guiBase, apiBase, searchQuantities, parserMetadata, schemaSeparator, dtypeSeparator, yamlSchemaPrefix } from './config'
 const crypto = require('crypto')
 
@@ -100,6 +102,65 @@ export function getDeep(data, path, separator = '.') {
     return segments.reduce((current, segment) => current && current[segment], data)
 }
 
+/**
+ * Used to retrieve all found options from a nested and repeated structure using
+ * depth-first search.
+ *
+ * @param {object} data The data to traverse
+ * @param {str} path Path to traverse
+ * @return All found values at the given path. Empty list if nothing was found.
+ */
+export function getDeepAll(data, path, separator = '.') {
+  const segments = path.split(separator)
+  const values = []
+
+  function getRecursive(data, segments, values) {
+    let current = data
+    let i = 0
+    for (const segment of segments) {
+      i += 1
+      current = current[segment]
+      if (!current) break
+      if (isArray(current)) {
+        for (const item of current) {
+          getRecursive(item, segments.slice(i), values)
+        }
+      } else if (isPlainObject(current)) {
+        getRecursive(current, segments.slice(i + 1), values)
+      } else {
+        values.push(current)
+      }
+    }
+  }
+
+  getRecursive(data, segments, values)
+  return values
+}
+
+/**
+ * Used to set a value to a nested object. Will create the hierarchy on the go
+ * if it is missing.
+ *
+ * @param {object} data The data to traverse.
+ * @param {str} path Path to use
+ * @param {value} path Value to store
+ */
+export function setDeep(data, path, value, separator = '.') {
+  const segments = path.split(separator)
+  let current = data
+  for (let i = 0; i < segments.length; ++i) {
+    const segment = segments[i]
+    if (i === segments.length - 1) {
+      current[segment] = value
+    } else {
+      if (isNil(current[segment])) {
+        current[segment] = {}
+      }
+      current = current[segment]
+    }
+  }
+}
+
 /**
  * Map that works on n-dimensional arrays. Implemented with simple for loops for
  * performance.
@@ -1555,3 +1616,121 @@ export function glob(path, include, exclude) {
   }
   return match
 }
+
+/**
+ * Used to validate JMESPath.
+ * @param {str} input
+ * @returns
+ */
+export function validateJMESPath(input) {
+  return jmespath.compile(input)
+}
+
+/**
+ * Used to parse a JMESPath input.
+ * @param {str} input
+ * @returns
+ */
+export function parseJMESPath(input) {
+  // Remove possible schema+dtype specification
+  const regexp = /#[a-zA-Z0-9_.#]+/g
+  const match = regexp.exec(input)
+  let schema = ''
+  let path = input
+  if (match) {
+    schema = match[0]
+    path = input.slice(0, match.index) + input.slice(match.index + schema.length)
+  }
+
+  // Try to compile JMESPath call, report error
+  let error
+  let ast
+  try {
+    ast = validateJMESPath(path)
+  } catch (e) {
+    return {quantity: undefined, path: undefined, extras: undefined, error: e.message, schema: ''}
+  }
+
+  // Walk down depth-first the AST to extract the targeted quantity
+  function recurseAST(node) {
+    const type = node?.type
+    const name = node?.name
+    const children = node?.children
+    let field = []
+    let extras = []
+
+    if (children) {
+      const childFields = []
+      const childExtras = []
+      for (const child of children) {
+        const [fieldInner, extrasInner] = recurseAST(child)
+        childFields.push(fieldInner)
+        childExtras.push(extrasInner)
+      }
+      // In filter projections we save the filter field in extras
+      if (type === 'FilterProjection') {
+        for (const childField of childFields.slice(0, 2)) {
+          field = [...field, ...childField]
+        }
+        extras = [...extras, [...childFields[0], ...childFields[2]]]
+      // In *_by we save the referenced variable in extras
+      } else if (type === 'Function' && name === 'min_by') {
+        field = [...field, ...childFields[0]]
+        extras = [...extras, [...childFields[0], ...childFields[1]]]
+      // For other types we simply extend definitions and extras in the order
+      // they are defined in
+      } else {
+        for (const childField of childFields) {
+          field = [...field, ...childField]
+        }
+        for (const childExtra of childExtras) {
+          extras = [...extras, ...childExtra]
+        }
+      }
+    } else {
+      if (type === 'Field') {
+        return [[name], extras]
+      }
+    }
+    return [field, extras]
+  }
+
+  const [field, extrasList] = recurseAST(ast)
+  const quantity = field.join('.') + schema
+  const extras = extrasList.map(x => x.join('.') + schema)
+
+  return {quantity, extras, path, schema, error}
+}
+
+/**
+ * Cleans the given object/array recursively from undefined values and empty
+ * objects.
+ * @param {*} obj The input list or object to clean.
+ */
+export function cleanse(obj) {
+  // Clean array
+  if (isArray(obj)) {
+    for (const item of obj) {
+      cleanse(item)
+    }
+  // Clean object
+  } else {
+    for (const key of keys(obj)) {
+      // Get this value and its type
+      const value = obj[key]
+      const type = typeof value
+      if (isPlainObject(value)) {
+        cleanse(value)
+        if (isEmpty(value)) {
+          delete obj[key]
+        }
+      } else if (isArray(value)) {
+        for (const item of value) {
+          cleanse(item)
+        }
+      } else if (type === "undefined") {
+        delete obj[key]
+      }
+    }
+  }
+}
diff --git a/gui/src/utils.spec.js b/gui/src/utils.spec.js
index 0fc6fdf82a5bb543a51c4de430a954027b53823b..c88944d5eddddce52d06ac12abb708386b974305 100644
--- a/gui/src/utils.spec.js
+++ b/gui/src/utils.spec.js
@@ -24,9 +24,11 @@ import {
   resolveNomadUrl,
   normalizeNomadUrl,
   refType,
-  refRelativeTo
+  refRelativeTo,
+  parseJMESPath
 } from './utils'
 import { apiBase, urlAbs } from './config'
+import { isEqual } from 'lodash'
 
 describe('titleCase', () => {
   it('runs on empty strings', () => {
@@ -557,3 +559,153 @@ test.each([
 ])('absolute url creation: %s ', (id, input, output, base, protocol = undefined) => {
   expect(urlAbs(input, base, protocol)).toBe(output)
 })
+
+test.each([
+  [
+    'simple subexpression',
+    'results.material.n_elements',
+    {
+      quantity: 'results.material.n_elements',
+      path: 'results.material.n_elements',
+      extras: [],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'index expression',
+    'results.material.elements[0]',
+    {
+      quantity: 'results.material.elements',
+      path: 'results.material.elements[0]',
+      extras: [],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'slicing ',
+    'results.material.elements[0:5]',
+    {
+      quantity: 'results.material.elements',
+      path: 'results.material.elements[0:5]',
+      extras: [],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'list projection',
+    'results.properties.electronic.band_gap[*].value',
+    {
+      quantity: 'results.properties.electronic.band_gap.value',
+      path: 'results.properties.electronic.band_gap[*].value',
+      extras: [],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'flatten projection',
+    'results.properties[].electronic.band_gap[].value',
+    {
+      quantity: 'results.properties.electronic.band_gap.value',
+      path: 'results.properties[].electronic.band_gap[].value',
+      extras: [],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'function with one argument',
+    'min(results.properties.electronic.band_gap[*].value)',
+    {
+      quantity: 'results.properties.electronic.band_gap.value',
+      path: 'min(results.properties.electronic.band_gap[*].value)',
+      extras: [],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'function with two arguments',
+    'min_by(results.properties.electronic.band_gap[*], &value).type',
+    {
+      quantity: 'results.properties.electronic.band_gap.type',
+      path: 'min_by(results.properties.electronic.band_gap[*], &value).type',
+      extras: ['results.properties.electronic.band_gap.value'],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'filter projection',
+    "results.material.topology[?label=='original'].cell.a",
+    {
+      quantity: 'results.material.topology.cell.a',
+      path: "results.material.topology[?label=='original'].cell.a",
+      extras: ['results.material.topology.label'],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'pipe',
+    'results.properties.electronic.band_gap[*].value | min(@)',
+    {
+      quantity: 'results.properties.electronic.band_gap.value',
+      path: 'results.properties.electronic.band_gap[*].value | min(@)',
+      extras: [],
+      error: undefined,
+      schema: ''
+    }
+  ],
+  [
+    'schema name and dtype are handled correctly 1',
+    'min_by(results.properties.electronic.band_gap[*], &value).type#MySchema#int',
+    {
+      quantity: 'results.properties.electronic.band_gap.type#MySchema#int',
+      path: 'min_by(results.properties.electronic.band_gap[*], &value).type',
+      extras: ['results.properties.electronic.band_gap.value#MySchema#int'],
+      error: undefined,
+      schema: '#MySchema#int'
+    }
+  ],
+  [
+    'schema name and dtype are handled correctly 2',
+    'results.properties.electronic.band_gap[*].value#MySchema | min(@)',
+    {
+      quantity: 'results.properties.electronic.band_gap.value#MySchema',
+      path: 'results.properties.electronic.band_gap[*].value | min(@)',
+      extras: [],
+      error: undefined,
+      schema: '#MySchema'
+    }
+  ],
+  [
+    'syntax error',
+    'results.material.n_elements[*',
+    {
+      quantity: undefined,
+      path: undefined,
+      extras: undefined,
+      error: 'Expected Rbracket, got: EOF',
+      schema: ''
+    }
+  ]
+  // Object projection is not supported, as we cannot tell ES which properties
+  // to fetch. If the JMESPath query is made by the API, then this might work as
+  // well.
+  // [
+  //   'object projection',
+  //   'results.properties.electronic.*.type',
+  //   {
+  //     quantity: '?',
+  //     path: '?',
+  //     extras: [],
+  //     error: undefined
+  //   }
+  // ],
+])('parseJMESPath: %s ', (id, input, output) => {
+  expect(isEqual(parseJMESPath(input), output)).toBe(true)
+})
diff --git a/gui/tests/artifacts.js b/gui/tests/artifacts.js
index 8b34a7bc18d901928e4761235d59cd0a3dc9aa2a..e58563155dc78e1bef0bc6abfee56e3bd4ac65a1 100644
--- a/gui/tests/artifacts.js
+++ b/gui/tests/artifacts.js
@@ -8,7 +8,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "upload_name": {
       "name": "upload_name",
@@ -19,6 +20,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "upload_create_time": {
@@ -29,7 +31,8 @@ window.nomadArtifacts = {
         "type_data": "nomad.metainfo.metainfo._Datetime"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "entry_id": {
       "name": "entry_id",
@@ -39,7 +42,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "entry_name": {
       "name": "entry_name",
@@ -50,6 +54,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "entry_name.prefix": {
@@ -60,7 +65,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "entry_type": {
       "name": "entry_type",
@@ -70,7 +76,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "calc_id": {
       "name": "calc_id",
@@ -80,7 +87,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "entry_create_time": {
       "name": "entry_create_time",
@@ -90,7 +98,8 @@ window.nomadArtifacts = {
         "type_data": "nomad.metainfo.metainfo._Datetime"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "parser_name": {
       "name": "parser_name",
@@ -100,7 +109,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "mainfile": {
       "name": "mainfile",
@@ -111,6 +121,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "mainfile.path": {
@@ -121,7 +132,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "mainfile_key": {
       "name": "mainfile_key",
@@ -131,7 +143,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "mainfile_key.path": {
       "name": "mainfile_key",
@@ -141,7 +154,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "text_search_contents": {
       "name": "text_search_contents",
@@ -154,7 +168,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "files": {
       "name": "files",
@@ -167,7 +182,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "files.path": {
       "name": "files",
@@ -180,7 +196,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "pid": {
       "name": "pid",
@@ -190,7 +207,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "raw_id": {
       "name": "raw_id",
@@ -200,7 +218,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "external_id": {
       "name": "external_id",
@@ -210,7 +229,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "published": {
       "name": "published",
@@ -220,7 +240,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "publish_time": {
       "name": "publish_time",
@@ -230,7 +251,8 @@ window.nomadArtifacts = {
         "type_data": "nomad.metainfo.metainfo._Datetime"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "with_embargo": {
       "name": "with_embargo",
@@ -240,7 +262,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "processed": {
       "name": "processed",
@@ -250,7 +273,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "last_processing_time": {
       "name": "last_processing_time",
@@ -260,7 +284,8 @@ window.nomadArtifacts = {
         "type_data": "nomad.metainfo.metainfo._Datetime"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "processing_errors": {
       "name": "processing_errors",
@@ -273,7 +298,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "nomad_version": {
       "name": "nomad_version",
@@ -283,7 +309,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "nomad_commit": {
       "name": "nomad_commit",
@@ -293,7 +320,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "comment": {
       "name": "comment",
@@ -303,7 +331,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "references": {
       "name": "references",
@@ -316,7 +345,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "external_db": {
       "name": "external_db",
@@ -333,7 +363,8 @@ window.nomadArtifacts = {
         ]
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "origin": {
       "name": "origin",
@@ -343,7 +374,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "main_author.name": {
       "name": "name",
@@ -353,6 +385,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "main_author.name.text": {
@@ -362,7 +395,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "main_author.user_id": {
       "name": "user_id",
@@ -372,7 +406,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "main_author": {
       "name": "main_author",
@@ -382,7 +417,8 @@ window.nomadArtifacts = {
         "type_data": "User"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "authors.name": {
       "name": "name",
@@ -392,6 +428,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "authors.name.text": {
@@ -401,7 +438,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "authors": {
       "name": "authors",
@@ -414,7 +452,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "writers.name": {
       "name": "name",
@@ -424,6 +463,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "writers.name.text": {
@@ -433,7 +473,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "writers.user_id": {
       "name": "user_id",
@@ -443,7 +484,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "writers": {
       "name": "writers",
@@ -456,7 +498,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "writer_groups": {
       "name": "writer_groups",
@@ -469,7 +512,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "viewers.name": {
       "name": "name",
@@ -479,6 +523,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "viewers.name.text": {
@@ -488,7 +533,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "viewers.user_id": {
       "name": "user_id",
@@ -498,7 +544,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "viewers": {
       "name": "viewers",
@@ -511,7 +558,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "viewer_groups": {
       "name": "viewer_groups",
@@ -524,7 +572,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "datasets.dataset_id": {
       "name": "dataset_id",
@@ -534,7 +583,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "datasets.dataset_name": {
       "name": "dataset_name",
@@ -545,6 +595,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "datasets.doi": {
@@ -555,7 +606,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "datasets.dataset_create_time": {
       "name": "dataset_create_time",
@@ -565,7 +617,8 @@ window.nomadArtifacts = {
         "type_data": "nomad.metainfo.metainfo._Datetime"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "datasets.dataset_modified_time": {
       "name": "dataset_modified_time",
@@ -575,7 +628,8 @@ window.nomadArtifacts = {
         "type_data": "nomad.metainfo.metainfo._Datetime"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "datasets.dataset_type": {
       "name": "dataset_type",
@@ -588,7 +642,8 @@ window.nomadArtifacts = {
         ]
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "datasets": {
       "name": "datasets",
@@ -601,7 +656,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "domain": {
       "name": "domain",
@@ -614,7 +670,8 @@ window.nomadArtifacts = {
         ]
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "n_quantities": {
       "name": "n_quantities",
@@ -624,7 +681,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "quantities": {
       "name": "quantities",
@@ -637,7 +695,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "quantities.path": {
       "name": "quantities",
@@ -650,7 +709,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "sections": {
       "name": "sections",
@@ -663,7 +723,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.elements": {
       "name": "elements",
@@ -796,7 +857,8 @@ window.nomadArtifacts = {
         "1..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.nelements": {
       "name": "nelements",
@@ -806,7 +868,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.elements_ratios": {
       "name": "elements_ratios",
@@ -819,7 +882,8 @@ window.nomadArtifacts = {
         "nelements"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.chemical_formula_descriptive": {
       "name": "chemical_formula_descriptive",
@@ -829,7 +893,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.chemical_formula_reduced": {
       "name": "chemical_formula_reduced",
@@ -839,7 +904,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.chemical_formula_hill": {
       "name": "chemical_formula_hill",
@@ -849,7 +915,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.chemical_formula_anonymous": {
       "name": "chemical_formula_anonymous",
@@ -859,7 +926,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.nperiodic_dimensions": {
       "name": "nperiodic_dimensions",
@@ -869,7 +937,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.nsites": {
       "name": "nsites",
@@ -879,7 +948,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "optimade.structure_features": {
       "name": "structure_features",
@@ -896,7 +966,8 @@ window.nomadArtifacts = {
         "1..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "section_defs.definition_qualified_name": {
       "name": "definition_qualified_name",
@@ -906,7 +977,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "section_defs.definition_id": {
       "name": "definition_id",
@@ -916,7 +988,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "section_defs.used_directly": {
       "name": "used_directly",
@@ -926,7 +999,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.target_reference": {
       "name": "target_reference",
@@ -936,7 +1010,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.target_entry_id": {
       "name": "target_entry_id",
@@ -946,7 +1021,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.target_mainfile": {
       "name": "target_mainfile",
@@ -956,7 +1032,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.target_upload_id": {
       "name": "target_upload_id",
@@ -966,7 +1043,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.target_name": {
       "name": "target_name",
@@ -976,7 +1054,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.target_path": {
       "name": "target_path",
@@ -986,7 +1065,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.source_name": {
       "name": "source_name",
@@ -996,7 +1076,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.source_path": {
       "name": "source_path",
@@ -1006,7 +1087,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "entry_references.source_quantity": {
       "name": "source_quantity",
@@ -1016,7 +1098,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.id": {
       "name": "id",
@@ -1026,7 +1109,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.definition": {
       "name": "definition",
@@ -1036,7 +1120,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.path_archive": {
       "name": "path_archive",
@@ -1046,7 +1131,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.bool_value": {
       "name": "bool_value",
@@ -1056,7 +1142,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.str_value": {
       "name": "str_value",
@@ -1066,7 +1153,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.str_value.keyword": {
       "name": "str_value",
@@ -1076,7 +1164,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.int_value": {
       "name": "int_value",
@@ -1086,7 +1175,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.float_value": {
       "name": "float_value",
@@ -1096,7 +1186,8 @@ window.nomadArtifacts = {
         "type_data": "float"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "search_quantities.datetime_value": {
       "name": "datetime_value",
@@ -1106,7 +1197,8 @@ window.nomadArtifacts = {
         "type_data": "nomad.metainfo.metainfo._Datetime"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.material_id": {
       "name": "material_id",
@@ -1116,7 +1208,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.material.material_name": {
       "name": "material_name",
@@ -1127,6 +1220,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.structural_type": {
@@ -1147,6 +1241,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.dimensionality": {
@@ -1163,6 +1258,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.building_block": {
@@ -1179,6 +1275,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.functional_type": {
@@ -1193,6 +1290,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.compound_type": {
@@ -1207,6 +1305,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.elements": {
@@ -1341,6 +1440,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.n_elements": {
@@ -1351,7 +1451,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.material.elements_exclusive": {
       "name": "elements_exclusive",
@@ -1361,7 +1462,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.material.chemical_formula_descriptive": {
       "name": "chemical_formula_descriptive",
@@ -1372,6 +1474,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.chemical_formula_reduced": {
@@ -1383,6 +1486,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.chemical_formula_hill": {
@@ -1394,6 +1498,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.chemical_formula_iupac": {
@@ -1405,6 +1510,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.chemical_formula_anonymous": {
@@ -1416,6 +1522,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.chemical_formula_reduced_fragments": {
@@ -1429,7 +1536,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.material.elemental_composition.element": {
       "name": "element",
@@ -1559,6 +1667,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.elemental_composition.atomic_fraction": {
@@ -1569,7 +1678,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.elemental_composition.mass_fraction": {
       "name": "mass_fraction",
@@ -1579,7 +1689,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.symmetry.bravais_lattice": {
       "name": "bravais_lattice",
@@ -1606,6 +1717,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.symmetry.crystal_system": {
@@ -1626,6 +1738,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.symmetry.hall_number": {
@@ -1637,7 +1750,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.material.symmetry.hall_symbol": {
       "name": "hall_symbol",
@@ -1649,6 +1763,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.symmetry.point_group": {
@@ -1661,6 +1776,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.symmetry.space_group_number": {
@@ -1672,7 +1788,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.material.symmetry.space_group_symbol": {
       "name": "space_group_symbol",
@@ -1684,6 +1801,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.symmetry.prototype_formula": {
@@ -1694,7 +1812,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.material.symmetry.prototype_aflow_id": {
       "name": "prototype_aflow_id",
@@ -1705,6 +1824,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.symmetry.structure_name": {
@@ -1735,6 +1855,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.symmetry.strukturbericht_designation": {
@@ -1746,6 +1867,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material.topology.system_id": {
@@ -1756,7 +1878,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.label": {
       "name": "label",
@@ -1767,6 +1890,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.method": {
@@ -1783,6 +1907,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.description": {
@@ -1793,7 +1918,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.material_id": {
       "name": "material_id",
@@ -1803,7 +1929,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.material_name": {
       "name": "material_name",
@@ -1814,6 +1941,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.structural_type": {
@@ -1838,6 +1966,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.dimensionality": {
@@ -1854,6 +1983,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.building_block": {
@@ -1870,6 +2000,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.functional_type": {
@@ -1884,6 +2015,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.compound_type": {
@@ -1898,6 +2030,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.elements": {
@@ -2032,6 +2165,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.n_elements": {
@@ -2042,7 +2176,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.elements_exclusive": {
       "name": "elements_exclusive",
@@ -2052,7 +2187,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.chemical_formula_descriptive": {
       "name": "chemical_formula_descriptive",
@@ -2063,6 +2199,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.chemical_formula_reduced": {
@@ -2074,6 +2211,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.chemical_formula_hill": {
@@ -2085,6 +2223,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.chemical_formula_iupac": {
@@ -2096,6 +2235,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.chemical_formula_anonymous": {
@@ -2107,6 +2247,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.chemical_formula_reduced_fragments": {
@@ -2120,7 +2261,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.parent_system": {
       "name": "parent_system",
@@ -2130,7 +2272,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.child_systems": {
       "name": "child_systems",
@@ -2143,7 +2286,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.atomic_fraction": {
       "name": "atomic_fraction",
@@ -2153,7 +2297,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.mass_fraction": {
       "name": "mass_fraction",
@@ -2163,7 +2308,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.n_atoms": {
       "name": "n_atoms",
@@ -2174,7 +2320,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.sbu_type": {
       "name": "sbu_type",
@@ -2186,6 +2333,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.largest_cavity_diameter": {
@@ -2197,7 +2345,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.pore_limiting_diameter": {
       "name": "pore_limiting_diameter",
@@ -2208,7 +2357,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.largest_included_sphere_along_free_sphere_path": {
       "name": "largest_included_sphere_along_free_sphere_path",
@@ -2219,7 +2369,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.accessible_surface_area": {
       "name": "accessible_surface_area",
@@ -2230,7 +2381,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter ** 2",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.accessible_volume": {
       "name": "accessible_volume",
@@ -2241,7 +2393,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter ** 3",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.void_fraction": {
       "name": "void_fraction",
@@ -2251,7 +2404,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.n_channels": {
       "name": "n_channels",
@@ -2262,7 +2416,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.sbu_coordination_number": {
       "name": "sbu_coordination_number",
@@ -2272,7 +2427,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.elemental_composition.element": {
       "name": "element",
@@ -2402,6 +2558,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.elemental_composition.atomic_fraction": {
@@ -2412,7 +2569,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.elemental_composition.mass_fraction": {
       "name": "mass_fraction",
@@ -2422,7 +2580,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.system_relation.type": {
       "name": "type",
@@ -2439,6 +2598,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.cell.a": {
@@ -2450,7 +2610,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.b": {
       "name": "b",
@@ -2461,7 +2622,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.c": {
       "name": "c",
@@ -2472,7 +2634,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.alpha": {
       "name": "alpha",
@@ -2483,7 +2646,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.beta": {
       "name": "beta",
@@ -2494,7 +2658,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.gamma": {
       "name": "gamma",
@@ -2505,7 +2670,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.volume": {
       "name": "volume",
@@ -2516,7 +2682,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter ** 3",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.atomic_density": {
       "name": "atomic_density",
@@ -2527,7 +2694,8 @@ window.nomadArtifacts = {
       },
       "unit": "1 / meter ** 3",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.cell.mass_density": {
       "name": "mass_density",
@@ -2538,7 +2706,8 @@ window.nomadArtifacts = {
       },
       "unit": "kilogram / meter ** 3",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.symmetry.bravais_lattice": {
       "name": "bravais_lattice",
@@ -2565,6 +2734,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.symmetry.crystal_system": {
@@ -2585,6 +2755,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.symmetry.hall_number": {
@@ -2596,7 +2767,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.symmetry.hall_symbol": {
       "name": "hall_symbol",
@@ -2608,6 +2780,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.symmetry.point_group": {
@@ -2620,6 +2793,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.symmetry.space_group_number": {
@@ -2631,7 +2805,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.symmetry.space_group_symbol": {
       "name": "space_group_symbol",
@@ -2643,6 +2818,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.symmetry.strukturbericht_designation": {
@@ -2654,6 +2830,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.symmetry.prototype_label_aflow": {
@@ -2666,6 +2843,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.symmetry.prototype_name": {
@@ -2696,6 +2874,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.material.topology.active_orbitals.n_quantum_number": {
@@ -2707,7 +2886,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.j_quantum_number": {
       "name": "j_quantum_number",
@@ -2720,7 +2900,8 @@ window.nomadArtifacts = {
         "1..2"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.mj_quantum_number": {
       "name": "mj_quantum_number",
@@ -2733,7 +2914,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.degeneracy": {
       "name": "degeneracy",
@@ -2743,7 +2925,8 @@ window.nomadArtifacts = {
         "type_data": "int32"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.n_electrons_excited": {
       "name": "n_electrons_excited",
@@ -2754,7 +2937,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.occupation": {
       "name": "occupation",
@@ -2764,7 +2948,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.l_quantum_symbol": {
       "name": "l_quantum_symbol",
@@ -2774,7 +2959,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.ml_quantum_symbol": {
       "name": "ml_quantum_symbol",
@@ -2784,7 +2970,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.material.topology.active_orbitals.ms_quantum_symbol": {
       "name": "ms_quantum_symbol",
@@ -2794,7 +2981,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.method.method_id": {
       "name": "method_id",
@@ -2804,7 +2992,8 @@ window.nomadArtifacts = {
         "type_data": "str"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.method_name": {
       "name": "method_name",
@@ -2828,6 +3017,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.workflow_name": {
@@ -2838,6 +3028,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.program_name": {
@@ -2849,6 +3040,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.program_version": {
@@ -2860,6 +3052,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.program_version_internal": {
@@ -2871,6 +3064,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dft.basis_set_type": {
@@ -2891,6 +3085,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dft.core_electron_treatment": {
@@ -2907,6 +3102,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dft.spin_polarized": {
@@ -2917,7 +3113,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dft.scf_threshold_energy_change": {
       "name": "scf_threshold_energy_change",
@@ -2929,7 +3126,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dft.van_der_Waals_method": {
       "name": "van_der_Waals_method",
@@ -2941,6 +3139,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dft.relativity_method": {
@@ -2957,6 +3156,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dft.smearing_kind": {
@@ -2969,6 +3169,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dft.smearing_width": {
@@ -2980,7 +3181,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dft.jacobs_ladder": {
       "name": "jacobs_ladder",
@@ -2998,7 +3200,8 @@ window.nomadArtifacts = {
         ]
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dft.xc_functional_type": {
       "name": "xc_functional_type",
@@ -3016,7 +3219,8 @@ window.nomadArtifacts = {
         ]
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dft.xc_functional_names": {
       "name": "xc_functional_names",
@@ -3030,6 +3234,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dft.exact_exchange_mixing_factor": {
@@ -3040,7 +3245,8 @@ window.nomadArtifacts = {
         "type_data": "float64"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dft.hubbard_kanamori_model.u_effective": {
       "name": "u_effective",
@@ -3052,7 +3258,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.method.simulation.dft.hubbard_kanamori_model.u": {
       "name": "u",
@@ -3064,7 +3271,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.method.simulation.dft.hubbard_kanamori_model.j": {
       "name": "j",
@@ -3076,7 +3284,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.method.simulation.tb.type": {
       "name": "type",
@@ -3093,6 +3302,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.tb.localization_type": {
@@ -3107,6 +3317,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.gw.type": {
@@ -3128,6 +3339,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.gw.basis_set_type": {
@@ -3148,6 +3360,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.gw.starting_point_type": {
@@ -3167,7 +3380,8 @@ window.nomadArtifacts = {
         ]
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.gw.starting_point_names": {
       "name": "starting_point_names",
@@ -3181,6 +3395,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.bse.type": {
@@ -3198,6 +3413,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.bse.basis_set_type": {
@@ -3218,6 +3434,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.bse.starting_point_type": {
@@ -3237,7 +3454,8 @@ window.nomadArtifacts = {
         ]
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.bse.starting_point_names": {
       "name": "starting_point_names",
@@ -3251,6 +3469,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.bse.solver": {
@@ -3269,6 +3488,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.bse.gw_type": {
@@ -3289,6 +3509,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dmft.impurity_solver_type": {
@@ -3313,6 +3534,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dmft.inverse_temperature": {
@@ -3325,7 +3547,8 @@ window.nomadArtifacts = {
       "unit": "1 / joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dmft.magnetic_state": {
       "name": "magnetic_state",
@@ -3341,6 +3564,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.dmft.u": {
@@ -3353,7 +3577,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dmft.jh": {
       "name": "jh",
@@ -3365,7 +3590,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.dmft.analytical_continuation": {
       "name": "analytical_continuation",
@@ -3381,7 +3607,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.precision.k_line_density": {
       "name": "k_line_density",
@@ -3393,7 +3620,8 @@ window.nomadArtifacts = {
       "unit": "meter",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.precision.native_tier": {
       "name": "native_tier",
@@ -3404,7 +3632,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.precision.basis_set": {
       "name": "basis_set",
@@ -3429,6 +3658,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.method.simulation.precision.planewave_cutoff": {
@@ -3441,7 +3671,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.simulation.precision.apw_cutoff": {
       "name": "apw_cutoff",
@@ -3452,7 +3683,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.method.measurement.xrd.diffraction_method_name": {
       "name": "diffraction_method_name",
@@ -3471,6 +3703,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.n_calculations": {
@@ -3481,7 +3714,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.available_properties": {
       "name": "available_properties",
@@ -3494,7 +3728,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structural.radial_distribution_function.type": {
       "name": "type",
@@ -3509,6 +3744,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.structural.radial_distribution_function.label": {
@@ -3521,6 +3757,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.structural.radial_distribution_function.provenance.label": {
@@ -3532,7 +3769,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.structural.radial_distribution_function.provenance.molecular_dynamics.time_step": {
       "name": "time_step",
@@ -3544,7 +3782,8 @@ window.nomadArtifacts = {
       "unit": "second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.structural.radial_distribution_function.provenance.molecular_dynamics.ensemble_type": {
       "name": "ensemble_type",
@@ -3560,7 +3799,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.structural.radius_of_gyration.kind": {
       "name": "kind",
@@ -3572,6 +3812,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.structural.radius_of_gyration.label": {
@@ -3584,6 +3825,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.structural.radius_of_gyration.provenance.label": {
@@ -3595,7 +3837,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.structural.radius_of_gyration.provenance.molecular_dynamics.time_step": {
       "name": "time_step",
@@ -3607,7 +3850,8 @@ window.nomadArtifacts = {
       "unit": "second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.structural.radius_of_gyration.provenance.molecular_dynamics.ensemble_type": {
       "name": "ensemble_type",
@@ -3623,7 +3867,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.structural.diffraction_pattern.incident_beam_wavelength": {
       "name": "incident_beam_wavelength",
@@ -3634,7 +3879,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.dynamical.mean_squared_displacement.type": {
       "name": "type",
@@ -3649,6 +3895,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.dynamical.mean_squared_displacement.label": {
@@ -3661,6 +3908,7 @@ window.nomadArtifacts = {
       "shape": [],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.dynamical.mean_squared_displacement.provenance.label": {
@@ -3672,7 +3920,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.dynamical.mean_squared_displacement.provenance.molecular_dynamics.time_step": {
       "name": "time_step",
@@ -3684,7 +3933,8 @@ window.nomadArtifacts = {
       "unit": "second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.dynamical.mean_squared_displacement.provenance.molecular_dynamics.ensemble_type": {
       "name": "ensemble_type",
@@ -3700,7 +3950,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.structures.structure_original.nperiodic_dimensions": {
       "name": "nperiodic_dimensions",
@@ -3710,7 +3961,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.n_sites": {
       "name": "n_sites",
@@ -3720,7 +3972,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.cell_volume": {
       "name": "cell_volume",
@@ -3731,7 +3984,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter ** 3",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.lattice_parameters.a": {
       "name": "a",
@@ -3742,7 +3996,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.lattice_parameters.b": {
       "name": "b",
@@ -3753,7 +4008,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.lattice_parameters.c": {
       "name": "c",
@@ -3764,7 +4020,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.lattice_parameters.alpha": {
       "name": "alpha",
@@ -3775,7 +4032,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.lattice_parameters.beta": {
       "name": "beta",
@@ -3786,7 +4044,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_original.lattice_parameters.gamma": {
       "name": "gamma",
@@ -3797,7 +4056,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.nperiodic_dimensions": {
       "name": "nperiodic_dimensions",
@@ -3807,7 +4067,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.n_sites": {
       "name": "n_sites",
@@ -3817,7 +4078,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.cell_volume": {
       "name": "cell_volume",
@@ -3828,7 +4090,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter ** 3",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.lattice_parameters.a": {
       "name": "a",
@@ -3839,7 +4102,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.lattice_parameters.b": {
       "name": "b",
@@ -3850,7 +4114,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.lattice_parameters.c": {
       "name": "c",
@@ -3861,7 +4126,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.lattice_parameters.alpha": {
       "name": "alpha",
@@ -3872,7 +4138,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.lattice_parameters.beta": {
       "name": "beta",
@@ -3883,7 +4150,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_conventional.lattice_parameters.gamma": {
       "name": "gamma",
@@ -3894,7 +4162,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.nperiodic_dimensions": {
       "name": "nperiodic_dimensions",
@@ -3904,7 +4173,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.n_sites": {
       "name": "n_sites",
@@ -3914,7 +4184,8 @@ window.nomadArtifacts = {
         "type_data": "int"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.cell_volume": {
       "name": "cell_volume",
@@ -3925,7 +4196,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter ** 3",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.lattice_parameters.a": {
       "name": "a",
@@ -3936,7 +4208,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.lattice_parameters.b": {
       "name": "b",
@@ -3947,7 +4220,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.lattice_parameters.c": {
       "name": "c",
@@ -3958,7 +4232,8 @@ window.nomadArtifacts = {
       },
       "unit": "meter",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.lattice_parameters.alpha": {
       "name": "alpha",
@@ -3969,7 +4244,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.lattice_parameters.beta": {
       "name": "beta",
@@ -3980,7 +4256,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.structures.structure_primitive.lattice_parameters.gamma": {
       "name": "gamma",
@@ -3991,7 +4268,8 @@ window.nomadArtifacts = {
       },
       "unit": "radian",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.electronic.band_gap.index": {
       "name": "index",
@@ -4001,7 +4279,8 @@ window.nomadArtifacts = {
         "type_data": "int32"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_gap.value": {
       "name": "value",
@@ -4013,7 +4292,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_gap.type": {
       "name": "type",
@@ -4027,7 +4307,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_gap.provenance.label": {
       "name": "label",
@@ -4038,7 +4319,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic.spin_polarized": {
       "name": "spin_polarized",
@@ -4048,7 +4330,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic.band_gap.index": {
       "name": "index",
@@ -4058,7 +4341,8 @@ window.nomadArtifacts = {
         "type_data": "int32"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic.band_gap.value": {
       "name": "value",
@@ -4070,7 +4354,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic.band_gap.type": {
       "name": "type",
@@ -4084,7 +4369,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic.band_gap.provenance.label": {
       "name": "label",
@@ -4095,7 +4381,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic_new.spin_polarized": {
       "name": "spin_polarized",
@@ -4105,7 +4392,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic_new.has_projected": {
       "name": "has_projected",
@@ -4115,7 +4403,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic_new.data.band_gap.index": {
       "name": "index",
@@ -4125,7 +4414,8 @@ window.nomadArtifacts = {
         "type_data": "int32"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic_new.data.band_gap.value": {
       "name": "value",
@@ -4137,7 +4427,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic_new.data.band_gap.type": {
       "name": "type",
@@ -4151,7 +4442,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.dos_electronic_new.data.band_gap.provenance.label": {
       "name": "label",
@@ -4162,7 +4454,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_structure_electronic.spin_polarized": {
       "name": "spin_polarized",
@@ -4172,7 +4465,8 @@ window.nomadArtifacts = {
         "type_data": "bool"
       },
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_structure_electronic.band_gap.index": {
       "name": "index",
@@ -4182,7 +4476,8 @@ window.nomadArtifacts = {
         "type_data": "int32"
       },
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_structure_electronic.band_gap.value": {
       "name": "value",
@@ -4194,7 +4489,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_structure_electronic.band_gap.type": {
       "name": "type",
@@ -4208,7 +4504,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.electronic.band_structure_electronic.band_gap.provenance.label": {
       "name": "label",
@@ -4219,7 +4516,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.magnetic.spin_spin_coupling.source": {
       "name": "source",
@@ -4233,6 +4531,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.magnetic.magnetic_susceptibility.source": {
@@ -4247,6 +4546,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.efficiency": {
@@ -4258,7 +4558,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.optoelectronic.solar_cell.fill_factor": {
       "name": "fill_factor",
@@ -4269,7 +4570,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.optoelectronic.solar_cell.open_circuit_voltage": {
       "name": "open_circuit_voltage",
@@ -4281,7 +4583,8 @@ window.nomadArtifacts = {
       "unit": "volt",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.optoelectronic.solar_cell.short_circuit_current_density": {
       "name": "short_circuit_current_density",
@@ -4293,7 +4596,8 @@ window.nomadArtifacts = {
       "unit": "ampere / meter ** 2",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.optoelectronic.solar_cell.illumination_intensity": {
       "name": "illumination_intensity",
@@ -4305,7 +4609,8 @@ window.nomadArtifacts = {
       "unit": "watt / meter ** 2",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.optoelectronic.solar_cell.device_area": {
       "name": "device_area",
@@ -4317,7 +4622,8 @@ window.nomadArtifacts = {
       "unit": "meter ** 2",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.optoelectronic.solar_cell.device_architecture": {
       "name": "device_architecture",
@@ -4328,6 +4634,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.device_stack": {
@@ -4342,6 +4649,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.absorber": {
@@ -4356,6 +4664,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.absorber_fabrication": {
@@ -4370,6 +4679,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.electron_transport_layer": {
@@ -4384,6 +4694,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.hole_transport_layer": {
@@ -4398,6 +4709,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.substrate": {
@@ -4412,6 +4724,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.optoelectronic.solar_cell.back_contact": {
@@ -4426,6 +4739,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.properties.catalytic.reactivity.reaction_name": {
@@ -4437,7 +4751,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.reaction_class": {
       "name": "reaction_class",
@@ -4448,7 +4763,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.gas_hourly_space_velocity": {
       "name": "gas_hourly_space_velocity",
@@ -4460,7 +4776,8 @@ window.nomadArtifacts = {
       "unit": "1 / second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.flow_rate": {
       "name": "flow_rate",
@@ -4472,7 +4789,8 @@ window.nomadArtifacts = {
       "unit": "meter ** 3 / second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.test_temperatures": {
       "name": "test_temperatures",
@@ -4486,7 +4804,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.time_on_stream": {
       "name": "time_on_stream",
@@ -4500,7 +4819,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.total_time_on_stream": {
       "name": "total_time_on_stream",
@@ -4512,7 +4832,8 @@ window.nomadArtifacts = {
       "unit": "second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.pressure": {
       "name": "pressure",
@@ -4526,7 +4847,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.reactivity.reactants.name": {
       "name": "name",
@@ -4537,7 +4859,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.reactants.gas_concentration_in": {
       "name": "gas_concentration_in",
@@ -4548,7 +4871,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.reactants.gas_concentration_out": {
       "name": "gas_concentration_out",
@@ -4559,7 +4883,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.reactants.conversion": {
       "name": "conversion",
@@ -4570,7 +4895,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.products.name": {
       "name": "name",
@@ -4581,7 +4907,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.products.gas_concentration_out": {
       "name": "gas_concentration_out",
@@ -4592,7 +4919,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.products.selectivity": {
       "name": "selectivity",
@@ -4603,7 +4931,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.products.space_time_yield": {
       "name": "space_time_yield",
@@ -4615,7 +4944,8 @@ window.nomadArtifacts = {
       "unit": "1 / second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.rates.reaction_rate": {
       "name": "reaction_rate",
@@ -4627,7 +4957,8 @@ window.nomadArtifacts = {
       "unit": "mole / gram / second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.rates.specific_mass_rate": {
       "name": "specific_mass_rate",
@@ -4639,7 +4970,8 @@ window.nomadArtifacts = {
       "unit": "mole / gram / second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.rates.specific_surface_area_rate": {
       "name": "specific_surface_area_rate",
@@ -4651,7 +4983,8 @@ window.nomadArtifacts = {
       "unit": "mole / meter ** 2 / second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.reactivity.rates.turn_over_frequency": {
       "name": "turn_over_frequency",
@@ -4663,7 +4996,8 @@ window.nomadArtifacts = {
       "unit": "1 / second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.catalytic.catalyst_synthesis.catalyst_name": {
       "name": "catalyst_name",
@@ -4674,7 +5008,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.catalyst_synthesis.preparation_method": {
       "name": "preparation_method",
@@ -4685,7 +5020,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.catalyst_synthesis.catalyst_type": {
       "name": "catalyst_type",
@@ -4696,7 +5032,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.catalyst_characterization.surface_area": {
       "name": "surface_area",
@@ -4708,7 +5045,8 @@ window.nomadArtifacts = {
       "unit": "meter ** 2 / gram",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.catalytic.catalyst_characterization.method_surface_area": {
       "name": "method_surface_area",
@@ -4719,7 +5057,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.mechanical.energy_volume_curve.type": {
       "name": "type",
@@ -4740,6 +5079,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.mechanical.bulk_modulus.type": {
@@ -4764,6 +5104,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.mechanical.bulk_modulus.value": {
@@ -4775,7 +5116,8 @@ window.nomadArtifacts = {
       },
       "unit": "pascal",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.mechanical.shear_modulus.type": {
       "name": "type",
@@ -4790,6 +5132,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.mechanical.shear_modulus.value": {
@@ -4801,7 +5144,8 @@ window.nomadArtifacts = {
       },
       "unit": "pascal",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.thermodynamic.trajectory.available_properties": {
       "name": "available_properties",
@@ -4819,7 +5163,8 @@ window.nomadArtifacts = {
         "0..*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.thermodynamic.trajectory.provenance.label": {
       "name": "label",
@@ -4830,7 +5175,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.thermodynamic.trajectory.provenance.molecular_dynamics.time_step": {
       "name": "time_step",
@@ -4842,7 +5188,8 @@ window.nomadArtifacts = {
       "unit": "second",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.thermodynamic.trajectory.provenance.molecular_dynamics.ensemble_type": {
       "name": "ensemble_type",
@@ -4858,7 +5205,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.spectroscopic.spectra.type": {
       "name": "type",
@@ -4878,6 +5226,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.spectroscopic.spectra.label": {
@@ -4892,6 +5241,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.spectroscopic.spectra.provenance.label": {
@@ -4903,7 +5253,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.spectroscopic.spectra.provenance.eels.detector_type": {
       "name": "detector_type",
@@ -4914,6 +5265,7 @@ window.nomadArtifacts = {
       },
       "aggregatable": true,
       "dynamic": false,
+      "repeats": true,
       "suggestion": true
     },
     "results.properties.spectroscopic.spectra.provenance.eels.resolution": {
@@ -4925,7 +5277,8 @@ window.nomadArtifacts = {
       },
       "unit": "joule",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.spectroscopic.spectra.provenance.eels.max_energy": {
       "name": "max_energy",
@@ -4936,7 +5289,8 @@ window.nomadArtifacts = {
       },
       "unit": "joule",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.spectroscopic.spectra.provenance.eels.min_energy": {
       "name": "min_energy",
@@ -4947,7 +5301,8 @@ window.nomadArtifacts = {
       },
       "unit": "joule",
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.spectroscopic.spectra.provenance.electronic_structure.label": {
       "name": "label",
@@ -4958,7 +5313,8 @@ window.nomadArtifacts = {
       },
       "shape": [],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": true
     },
     "results.properties.geometry_optimization.convergence_tolerance_energy_difference": {
       "name": "convergence_tolerance_energy_difference",
@@ -4970,7 +5326,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.geometry_optimization.convergence_tolerance_force_maximum": {
       "name": "convergence_tolerance_force_maximum",
@@ -4982,7 +5339,8 @@ window.nomadArtifacts = {
       "unit": "newton",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.geometry_optimization.final_force_maximum": {
       "name": "final_force_maximum",
@@ -4994,7 +5352,8 @@ window.nomadArtifacts = {
       "unit": "newton",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.geometry_optimization.final_energy_difference": {
       "name": "final_energy_difference",
@@ -5006,7 +5365,8 @@ window.nomadArtifacts = {
       "unit": "joule",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.properties.geometry_optimization.final_displacement_maximum": {
       "name": "final_displacement_maximum",
@@ -5018,7 +5378,8 @@ window.nomadArtifacts = {
       "unit": "meter",
       "shape": [],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.eln.sections": {
       "name": "sections",
@@ -5031,7 +5392,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.eln.tags": {
       "name": "tags",
@@ -5044,7 +5406,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.eln.names": {
       "name": "names",
@@ -5057,7 +5420,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.eln.descriptions": {
       "name": "descriptions",
@@ -5070,7 +5434,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": false,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.eln.instruments": {
       "name": "instruments",
@@ -5083,7 +5448,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.eln.methods": {
       "name": "methods",
@@ -5096,7 +5462,8 @@ window.nomadArtifacts = {
         "*"
       ],
       "aggregatable": true,
-      "dynamic": false
+      "dynamic": false,
+      "repeats": false
     },
     "results.eln.lab_ids": {
       "name": "lab_ids",
@@ -5110,6 +5477,7 @@ window.nomadArtifacts = {
       ],
       "aggregatable": true,
       "dynamic": false,
+      "repeats": false,
       "suggestion": true
     },
     "results.material": {
diff --git a/gui/tests/env.js b/gui/tests/env.js
index bf5a2a57cc5e61b7e835f996b5e89c079fbef21c..3e413294bc539faf2765d9d8bcc051b84c4a0a51 100644
--- a/gui/tests/env.js
+++ b/gui/tests/env.js
@@ -957,44 +957,44 @@ window.nomadEnv = {
                 "type": "periodictable",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 13,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 11,
                     "w": 14,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 11,
                     "w": 14,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.elements",
@@ -1004,44 +1004,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 6,
                     "x": 30,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 24,
-                    "y": 5
+                    "y": 5,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 5,
                     "x": 19,
-                    "y": 6
+                    "y": 6,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 12,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 6,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.symmetry.space_group_symbol",
@@ -1052,44 +1052,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 6,
                     "x": 19,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 11,
                     "w": 5,
                     "x": 19,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 5,
                     "x": 19,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 0,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 6,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.structural_type",
@@ -1100,44 +1100,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 6,
                     "x": 13,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 11,
                     "w": 5,
                     "x": 14,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 5,
                     "x": 14,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 6,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 0,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.method.simulation.program_name",
@@ -1148,44 +1148,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 5,
                     "x": 25,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 24,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 5,
                     "x": 14,
-                    "y": 6
+                    "y": 6,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 6,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 0,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.symmetry.crystal_system",
@@ -1892,44 +1892,44 @@ window.nomadEnv = {
                 "type": "periodictable",
                 "layout": {
                   "xxl": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 13,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "xl": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "lg": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "md": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "sm": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 16
+                    "y": 16,
+                    "minH": 8,
+                    "minW": 12
                   }
                 },
                 "quantity": "results.material.elements",
@@ -1939,49 +1939,57 @@ window.nomadEnv = {
                 "type": "scatterplot",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 12,
                     "x": 24,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 9,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 12,
                     "x": 0,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 9,
                     "x": 0,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
+                  }
+                },
+                "x": {
+                  "quantity": "results.properties.optoelectronic.solar_cell.open_circuit_voltage"
+                },
+                "y": {
+                  "quantity": "results.properties.optoelectronic.solar_cell.efficiency"
+                },
+                "markers": {
+                  "color": {
+                    "quantity": "results.properties.optoelectronic.solar_cell.short_circuit_current_density"
                   }
                 },
-                "x": "results.properties.optoelectronic.solar_cell.open_circuit_voltage",
-                "y": "results.properties.optoelectronic.solar_cell.efficiency",
-                "color": "results.properties.optoelectronic.solar_cell.short_circuit_current_density",
                 "size": 1000,
                 "autorange": true
               },
@@ -1989,49 +1997,57 @@ window.nomadEnv = {
                 "type": "scatterplot",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 11,
                     "x": 13,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 9,
                     "x": 21,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 12,
                     "x": 0,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 9,
                     "x": 9,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 6,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
+                  }
+                },
+                "x": {
+                  "quantity": "results.properties.optoelectronic.solar_cell.open_circuit_voltage"
+                },
+                "y": {
+                  "quantity": "results.properties.optoelectronic.solar_cell.efficiency"
+                },
+                "markers": {
+                  "color": {
+                    "quantity": "results.properties.optoelectronic.solar_cell.device_architecture"
                   }
                 },
-                "x": "results.properties.optoelectronic.solar_cell.open_circuit_voltage",
-                "y": "results.properties.optoelectronic.solar_cell.efficiency",
-                "color": "results.properties.optoelectronic.solar_cell.device_architecture",
                 "size": 1000,
                 "autorange": true
               },
@@ -2039,44 +2055,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 14,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 14,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 4
+                    "y": 4,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 4,
                     "x": 0,
-                    "y": 10
+                    "y": 10,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.optoelectronic.solar_cell.device_stack",
@@ -2087,44 +2103,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 11
+                    "y": 11,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 12,
                     "x": 12,
-                    "y": 12
+                    "y": 12,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 8,
                     "x": 10,
-                    "y": 17
+                    "y": 17,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 8,
                     "x": 4,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.optoelectronic.solar_cell.illumination_intensity",
@@ -2137,44 +2153,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 8,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 8,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 18,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 4,
                     "x": 0,
-                    "y": 5
+                    "y": 5,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.optoelectronic.solar_cell.absorber_fabrication",
@@ -2185,44 +2201,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 11
+                    "y": 11,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 12,
                     "x": 12,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 10,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 4,
-                    "y": 10
+                    "y": 10,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.electronic.band_structure_electronic.band_gap.value",
@@ -2235,44 +2251,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 20,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 5,
                     "x": 25,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 18,
-                    "y": 6
+                    "y": 6,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 5,
                     "x": 0,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 4,
                     "x": 4,
-                    "y": 5
+                    "y": 5,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.optoelectronic.solar_cell.electron_transport_layer",
@@ -2283,44 +2299,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 26,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 5,
                     "x": 20,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 12,
-                    "y": 6
+                    "y": 6,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 5,
                     "x": 5,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 4,
                     "x": 8,
-                    "y": 5
+                    "y": 5,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.optoelectronic.solar_cell.hole_transport_layer",
@@ -2538,44 +2554,44 @@ window.nomadEnv = {
                 "type": "periodictable",
                 "layout": {
                   "xxl": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 5
+                    "y": 5,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "xl": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 5
+                    "y": 5,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "lg": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 6
+                    "y": 6,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "md": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 5
+                    "y": 5,
+                    "minH": 8,
+                    "minW": 12
                   },
                   "sm": {
-                    "minH": 8,
-                    "minW": 12,
                     "h": 8,
                     "w": 12,
                     "x": 0,
-                    "y": 5
+                    "y": 5,
+                    "minH": 8,
+                    "minW": 12
                   }
                 },
                 "quantity": "results.material.elements",
@@ -2585,44 +2601,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 6,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 4,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.reactants.name",
@@ -2633,44 +2649,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 4,
                     "x": 8,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.reaction_name",
@@ -2681,44 +2697,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 12,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 6,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 6,
                     "x": 6,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 6,
                     "x": 6,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 4,
                     "x": 4,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.products.name",
@@ -2729,44 +2745,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 5
+                    "y": 5,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 5
+                    "y": 5,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 6
+                    "y": 6,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 5
+                    "y": 5,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 4,
                     "x": 8,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.catalytic.catalyst_synthesis.preparation_method",
@@ -2777,44 +2793,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 10
+                    "y": 10,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 6,
                     "x": 12,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 4,
                     "x": 8,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.properties.catalytic.catalyst_synthesis.catalyst_type",
@@ -2825,44 +2841,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.test_temperatures",
@@ -2875,44 +2891,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 17
+                    "y": 17,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 18
+                    "y": 18,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 9,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 22
+                    "y": 22,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.gas_hourly_space_velocity",
@@ -2925,44 +2941,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 9,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 9,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 9,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 9,
-                    "y": 13
+                    "y": 13,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.reactants.gas_concentration_in",
@@ -2975,44 +2991,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 9,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 9,
-                    "y": 17
+                    "y": 17,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 9,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.pressure",
@@ -3025,44 +3041,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 19
+                    "y": 19,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 21
+                    "y": 21,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 26
+                    "y": 26,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 22
+                    "y": 22,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 33
+                    "y": 33,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.products.selectivity",
@@ -3075,44 +3091,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 22
+                    "y": 22,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 25
+                    "y": 25,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 22
+                    "y": 22,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 19
+                    "y": 19,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 30
+                    "y": 30,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.reactants.conversion",
@@ -3125,44 +3141,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 8,
-                    "y": 25
+                    "y": 25,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 9,
-                    "y": 29
+                    "y": 29,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 12,
                     "x": 0,
-                    "y": 30
+                    "y": 30,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 25
+                    "y": 25,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 36
+                    "y": 36,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.reactivity.rates.reaction_rate",
@@ -3175,49 +3191,57 @@ window.nomadEnv = {
                 "type": "scatterplot",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 10,
                     "x": 8,
-                    "y": 19
+                    "y": 19,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 9,
                     "x": 9,
-                    "y": 21
+                    "y": 21,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 9,
                     "x": 9,
-                    "y": 22
+                    "y": 22,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 9,
                     "x": 9,
-                    "y": 19
+                    "y": 19,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 8,
                     "x": 9,
-                    "y": 25
+                    "y": 25,
+                    "minH": 3,
+                    "minW": 3
+                  }
+                },
+                "x": {
+                  "quantity": "results.properties.catalytic.reactivity.reactants.conversion"
+                },
+                "y": {
+                  "quantity": "results.properties.catalytic.reactivity.products.selectivity"
+                },
+                "markers": {
+                  "color": {
+                    "quantity": "results.properties.catalytic.catalyst_characterization.surface_area"
                   }
                 },
-                "x": "results.properties.catalytic.reactivity.reactants.conversion",
-                "y": "results.properties.catalytic.reactivity.products.selectivity",
-                "color": "results.properties.catalytic.catalyst_characterization.surface_area",
                 "size": 1000,
                 "autorange": true
               },
@@ -3225,44 +3249,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 25
+                    "y": 25,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 29
+                    "y": 29,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 4,
                     "w": 12,
                     "x": 0,
-                    "y": 34
+                    "y": 34,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 9,
                     "x": 0,
-                    "y": 28
+                    "y": 28,
+                    "minH": 3,
+                    "minW": 8
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 8,
                     "h": 3,
                     "w": 8,
                     "x": 0,
-                    "y": 39
+                    "y": 39,
+                    "minH": 3,
+                    "minW": 8
                   }
                 },
                 "quantity": "results.properties.catalytic.catalyst_characterization.surface_area",
@@ -3394,44 +3418,44 @@ window.nomadEnv = {
                 "type": "periodictable",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 10,
                     "w": 25,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 19,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 15,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 11,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 9,
                     "x": 0,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.elements",
@@ -3441,44 +3465,44 @@ window.nomadEnv = {
                 "type": "terms",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 10,
                     "w": 11,
                     "x": 25,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 11,
                     "x": 19,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 9,
                     "w": 9,
                     "x": 15,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 8,
                     "w": 7,
                     "x": 11,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 3,
                     "x": 9,
-                    "y": 0
+                    "y": 0,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.topology.sbu_type",
@@ -3489,44 +3513,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 19,
                     "x": 0,
-                    "y": 10
+                    "y": 10,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 15,
                     "x": 0,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 12,
                     "x": 0,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 6,
                     "x": 0,
-                    "y": 6
+                    "y": 6,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.topology.pore_limiting_diameter",
@@ -3539,44 +3563,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 17,
                     "x": 19,
-                    "y": 10
+                    "y": 10,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 15,
                     "x": 0,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 12,
                     "x": 0,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 9,
                     "x": 9,
-                    "y": 8
+                    "y": 8,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 6,
                     "x": 6,
-                    "y": 6
+                    "y": 6,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.topology.largest_cavity_diameter",
@@ -3589,44 +3613,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 19,
                     "x": 0,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 15,
                     "x": 15,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 12,
                     "x": 11,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 9,
                     "x": 0,
-                    "y": 12
+                    "y": 12,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 6,
                     "x": 0,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.topology.accessible_surface_area",
@@ -3639,44 +3663,44 @@ window.nomadEnv = {
                 "type": "histogram",
                 "layout": {
                   "xxl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 6,
                     "w": 17,
                     "x": 19,
-                    "y": 16
+                    "y": 16,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "xl": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 15,
                     "x": 15,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "lg": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 5,
                     "w": 12,
                     "x": 11,
-                    "y": 14
+                    "y": 14,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "md": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 4,
                     "w": 9,
                     "x": 9,
-                    "y": 12
+                    "y": 12,
+                    "minH": 3,
+                    "minW": 3
                   },
                   "sm": {
-                    "minH": 3,
-                    "minW": 3,
                     "h": 3,
                     "w": 6,
                     "x": 6,
-                    "y": 9
+                    "y": 9,
+                    "minH": 3,
+                    "minW": 3
                   }
                 },
                 "quantity": "results.material.topology.void_fraction",
diff --git a/gui/yarn.lock b/gui/yarn.lock
index 57b91c21cedeb6435e5b111a04588174d1e261c0..1901854ecba20c9611aca13662baa8669f1b4fb3 100644
--- a/gui/yarn.lock
+++ b/gui/yarn.lock
@@ -10039,6 +10039,11 @@ jest@26.6.0:
     import-local "^3.0.2"
     jest-cli "^26.6.0"
 
+jmespath@^0.16.0:
+  version "0.16.0"
+  resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076"
+  integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==
+
 js-levenshtein@^1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
diff --git a/nomad/cli/dev.py b/nomad/cli/dev.py
index e1e9dbe7f2befaadc2098b58881decca1c73c336..37213205b8313cdf13bd70e7cf91bfba783009b4 100644
--- a/nomad/cli/dev.py
+++ b/nomad/cli/dev.py
@@ -177,12 +177,14 @@ def _generate_search_quantities():
                 'aliases',
                 'aggregatable',
                 'dynamic',
+                'repeats',
             ]
             metadict = search_quantity.definition.m_to_dict(with_meta=True)
             # UI needs to know whether the quantity can be used in
             # aggregations or not.
             metadict['aggregatable'] = search_quantity.aggregatable
             metadict['dynamic'] = search_quantity.dynamic
+            metadict['repeats'] = search_quantity.repeats
             if search_quantity.dynamic:
                 splitted = search_quantity.qualified_name.split(schema_separator, 1)
                 if len(splitted) == 2:
diff --git a/nomad/config/models.py b/nomad/config/models.py
index c911d9c19dcc29977112492d4fe1df1c1faa9965..177cbdd397bd996b96e205b31fbb34e28a0de41d 100644
--- a/nomad/config/models.py
+++ b/nomad/config/models.py
@@ -1300,12 +1300,12 @@ class SearchSyntaxes(StrictSettings):
 class Layout(StrictSettings):
     """Defines widget size and grid positioning for different breakpoints."""
 
-    minH: int = Field(description='Minimum height in grid units.')
-    minW: int = Field(description='Minimum width in grid units.')
     h: int = Field(description='Height in grid units')
     w: int = Field(description='Width in grid units.')
     x: int = Field(description='Horizontal start location in the grid.')
     y: int = Field(description='Vertical start location in the grid.')
+    minH: Optional[int] = Field(3, description='Minimum height in grid units.')
+    minW: Optional[int] = Field(3, description='Minimum width in grid units.')
 
 
 class ScaleEnum(str, Enum):
@@ -1323,6 +1323,31 @@ class BreakpointEnum(str, Enum):
     XXL = 'xxl'
 
 
+class Axis(StrictSettings):
+    """Configuration for a plot axis."""
+
+    title: Optional[str] = Field(description="""Custom title to show for the axis.""")
+    unit: Optional[str] = Field(
+        description="""Custom unit used for displaying the values."""
+    )
+    quantity: str = Field(
+        description="""
+        Path of the targeted quantity. Note that you can most of the features
+        JMESPath syntax here to further specify a selection of values. This
+        becomes especially useful when dealing with repeated sections or
+        statistical values.
+        """
+    )
+
+
+class Markers(StrictSettings):
+    """Configuration for plot markers."""
+
+    color: Optional[Axis] = Field(
+        description='Configures the information source and display options for the marker colors.'
+    )
+
+
 class Widget(StrictSettings):
     """Common configuration for all widgets."""
 
@@ -1366,7 +1391,7 @@ class WidgetHistogram(Widget):
         description="""
         Maximum number of histogram bins. Notice that the actual number of bins
         may be smaller if there are fewer data items available.
-    """
+        """
     )
 
 
@@ -1386,13 +1411,27 @@ class WidgetScatterPlot(Widget):
     type: Literal['scatterplot'] = Field(
         description='Set as `scatterplot` to get this widget type.'
     )
-    x: str = Field(description='X-axis quantity.')
-    y: str = Field(description='Y-axis quantity.')
-    color: Optional[str] = Field(description='Quantity used for coloring points.')
+    x: Union[Axis, str] = Field(
+        description='Configures the information source and display options for the x-axis.'
+    )
+    y: Union[Axis, str] = Field(
+        description='Configures the information source and display options for the y-axis.'
+    )
+    markers: Optional[Markers] = Field(
+        description='Configures the information source and display options for the markers.'
+    )
+    color: Optional[str] = Field(
+        description="""
+        Quantity used for coloring points. Note that this field is deprecated
+        and `markers` should be used instead.
+        """
+    )
     size: int = Field(
         1000,
         description="""
-        Maximum number of data points to fetch. Notice that the actual number may be less.
+        Maximum number of entries to fetch. Notice that the actual number may be
+        more of less, depending on how many entries exist and how many of the
+        requested values each entry contains.
         """,
     )
     autorange: bool = Field(
@@ -1400,6 +1439,21 @@ class WidgetScatterPlot(Widget):
         description='Whether to automatically set the range according to the data limits.',
     )
 
+    @root_validator(pre=True)
+    def backwards_compatibility(cls, values):
+        """Ensures backwards compatibility of x, y, and color."""
+        color = values.get('color')
+        if color is not None:
+            values['markers'] = {'color': {'quantity': color}}
+            del values['color']
+        x = values.get('x')
+        if isinstance(x, str):
+            values['x'] = {'quantity': x}
+        y = values.get('y')
+        if isinstance(y, str):
+            values['y'] = {'quantity': y}
+        return values
+
 
 # The 'discriminated union' feature of Pydantic is used here:
 # https://docs.pydantic.dev/usage/types/#discriminated-unions-aka-tagged-unions
@@ -2527,9 +2581,17 @@ class UI(StrictSettings):
                                     'type': 'scatterplot',
                                     'autorange': True,
                                     'size': 1000,
-                                    'color': 'results.properties.optoelectronic.solar_cell.short_circuit_current_density',
-                                    'y': 'results.properties.optoelectronic.solar_cell.efficiency',
-                                    'x': 'results.properties.optoelectronic.solar_cell.open_circuit_voltage',
+                                    'x': {
+                                        'quantity': 'results.properties.optoelectronic.solar_cell.open_circuit_voltage'
+                                    },
+                                    'y': {
+                                        'quantity': 'results.properties.optoelectronic.solar_cell.efficiency'
+                                    },
+                                    'markers': {
+                                        'color': {
+                                            'quantity': 'results.properties.optoelectronic.solar_cell.short_circuit_current_density'
+                                        }
+                                    },
                                     'layout': {
                                         'xxl': {
                                             'minH': 3,
@@ -2577,9 +2639,17 @@ class UI(StrictSettings):
                                     'type': 'scatterplot',
                                     'autorange': True,
                                     'size': 1000,
-                                    'color': 'results.properties.optoelectronic.solar_cell.device_architecture',
-                                    'y': 'results.properties.optoelectronic.solar_cell.efficiency',
-                                    'x': 'results.properties.optoelectronic.solar_cell.open_circuit_voltage',
+                                    'y': {
+                                        'quantity': 'results.properties.optoelectronic.solar_cell.efficiency'
+                                    },
+                                    'x': {
+                                        'quantity': 'results.properties.optoelectronic.solar_cell.open_circuit_voltage',
+                                    },
+                                    'markers': {
+                                        'color': {
+                                            'quantity': 'results.properties.optoelectronic.solar_cell.device_architecture',
+                                        }
+                                    },
                                     'layout': {
                                         'xxl': {
                                             'minH': 3,
diff --git a/nomad/metainfo/elasticsearch_extension.py b/nomad/metainfo/elasticsearch_extension.py
index 3c11639ecaa2130e38dde33cda87d16fff93919e..57b43e68f3334176756025faf5e6e87bb9978bc5 100644
--- a/nomad/metainfo/elasticsearch_extension.py
+++ b/nomad/metainfo/elasticsearch_extension.py
@@ -381,6 +381,7 @@ class DocumentType:
         section_def: Section,
         prefix: str = None,
         auto_include_subsections: bool = False,
+        repeats: bool = False,
     ):
         mappings: Dict[str, Any] = {}
 
@@ -415,6 +416,7 @@ class DocumentType:
                     reference_mapping = self._create_mapping_recursive(
                         cast(Section, quantity_def.type.target_section_def),
                         prefix=qualified_name,
+                        repeats=repeats,
                     )
                     if len(reference_mapping['properties']) > 0:
                         mappings[quantity_def.name] = reference_mapping
@@ -429,7 +431,7 @@ class DocumentType:
                         mapping.update(**elasticsearch_annotation.mapping)
 
                 self.indexed_properties.add(quantity_def)
-                self._register(elasticsearch_annotation, prefix)
+                self._register(elasticsearch_annotation, prefix, repeats)
 
         for sub_section_def in section_def.all_sub_sections.values():
             annotation = sub_section_def.m_get_annotations(Elasticsearch)
@@ -456,6 +458,7 @@ class DocumentType:
                 sub_section_def.sub_section,
                 prefix=qualified_name,
                 auto_include_subsections=continue_with_auto_include_subsections,
+                repeats=repeats or sub_section_def.repeats,
             )
 
             nested = annotation is not None and annotation.nested
@@ -512,21 +515,22 @@ class DocumentType:
         # creating the dynamic quantities, this is the only way to prevent
         # infinite recursion, but it should be made possible in the GUI + search
         # API to query arbitrarily deep into the data structure.
-        def get_all_quantities(m_def, prefix=None, branch=None):
+        def get_all_quantities(m_def, prefix=None, branch=None, repeats=False):
             if branch is None:
                 branch = set()
             for quantity_name, quantity in m_def.all_quantities.items():
                 quantity_name = f'{prefix}.{quantity_name}' if prefix else quantity_name
-                yield quantity, quantity_name
+                yield quantity, quantity_name, repeats
             for sub_section_def in m_def.all_sub_sections.values():
                 if sub_section_def in branch:
                     continue
                 new_branch = set(branch)
                 new_branch.add(sub_section_def)
                 name = sub_section_def.name
+                repeats = sub_section_def.repeats
                 full_name = f'{prefix}.{name}' if prefix else name
                 for item in get_all_quantities(
-                    sub_section_def.sub_section, full_name, new_branch
+                    sub_section_def.sub_section, full_name, new_branch, repeats
                 ):
                     yield item
 
@@ -544,7 +548,7 @@ class DocumentType:
                         section.section_cls, EntryData
                     ):
                         schema_name = section.qualified_name()
-                        for quantity_def, path in get_all_quantities(section):
+                        for quantity_def, path, repeats in get_all_quantities(section):
                             annotation = create_dynamic_quantity_annotation(
                                 quantity_def
                             )
@@ -552,13 +556,15 @@ class DocumentType:
                                 continue
                             full_name = f'data.{path}{schema_separator}{schema_name}'
                             search_quantity = SearchQuantity(
-                                annotation, qualified_name=full_name
+                                annotation, qualified_name=full_name, repeats=repeats
                             )
                             quantities_dynamic[full_name] = search_quantity
         self.quantities.update(quantities_dynamic)
 
-    def _register(self, annotation, prefix):
-        search_quantity = SearchQuantity(annotation=annotation, prefix=prefix)
+    def _register(self, annotation, prefix, repeats):
+        search_quantity = SearchQuantity(
+            annotation=annotation, prefix=prefix, repeats=repeats
+        )
         name = search_quantity.qualified_name
 
         assert (
@@ -1016,6 +1022,7 @@ class SearchQuantity:
     a qualified name that pin points its place in the sub-section hierarchy.
 
     Attributes:
+        annotation: The ES annotation that this search quantity is based on
         qualified_field:
             The full qualified name of the resulting elasticsearch field in the entry
             document type. This will be the quantity name (plus additional
@@ -1026,11 +1033,15 @@ class SearchQuantity:
         qualified_name:
             Same name as qualified_field. This will be used to address the search
             property in our APIs.
-        definition: The metainfo quantity definition that this search quantity is based on
+        repeats: Whether this quantity is inside at least one repeatable section
     """
 
     def __init__(
-        self, annotation: Elasticsearch, prefix: str = None, qualified_name: str = None
+        self,
+        annotation: Elasticsearch,
+        prefix: str = None,
+        qualified_name: str = None,
+        repeats: bool = False,
     ):
         """
         Args:
@@ -1056,6 +1067,7 @@ class SearchQuantity:
 
         self.qualified_field = qualified_field
         self.qualified_name = qualified_field
+        self.repeats = repeats
 
         if annotation.dynamic:
             self.qualified_name = qualified_name
diff --git a/tests/data/schemas/nomadschemaexample/schema.py b/tests/data/schemas/nomadschemaexample/schema.py
index 0e93759294e66817482a54c91a16c81dffecfb44..6d93b8d258549b38265c622ede60334f594baa69 100644
--- a/tests/data/schemas/nomadschemaexample/schema.py
+++ b/tests/data/schemas/nomadschemaexample/schema.py
@@ -20,7 +20,18 @@ class MySection(MSection):
     name = Quantity(
         type=str,
         a_eln=ELNAnnotation(component=ELNComponentEnum.StringEditQuantity),
-        description='For testing subsection quantity.',
+        description='For testing subsection string quantity.',
+    )
+    count = Quantity(
+        type=int,
+        a_eln=ELNAnnotation(component=ELNComponentEnum.NumberEditQuantity),
+        description='For testing subsection integer quantity.',
+    )
+    frequency = Quantity(
+        type=float,
+        unit='1/s',
+        a_eln=ELNAnnotation(component=ELNComponentEnum.NumberEditQuantity),
+        description='For testing subsection floating point quantity.',
     )