diff --git a/gui/src/components/search/Query.js b/gui/src/components/search/Query.js
index 972de233c6d389712621db0cd6667f50703489b1..5443482cb228e20c8a3f5e3d9fc193002478fac2 100644
--- a/gui/src/components/search/Query.js
+++ b/gui/src/components/search/Query.js
@@ -198,14 +198,15 @@ export const QueryCurlyBracketRight = React.memo((props) => {
 })
 
 // Custom function for chip creation
-const createChips = (name, filterValue, onDelete, filterData, units) => {
+const createChips = (name, filterValue, onDelete, filterData, units, queryModes) => {
   if (isNil(filterValue)) return []
 
   const { serializerPretty: serializer, customSerialization, queryMode } = filterData[name]
+  const finalQueryMode = queryModes?.[name] || queryMode
   const isArray = Array.isArray(filterValue)
   const isSet = filterValue instanceof Set
   const isObj = isPlainObject(filterValue)
-  const op = queryMode === "any" ? <QueryOr/> : <QueryAnd/>
+  const op = finalQueryMode === "any" ? <QueryOr/> : <QueryAnd/>
   const chips = []
 
   const createChip = (label, onDelete, single = false) => (
@@ -278,7 +279,8 @@ const useStyles = makeStyles(theme => ({
   }
 }))
 const QueryChips = React.memo(({ className, classes }) => {
-  const { filterData, useQuery, useUpdateFilter } = useSearchContext()
+  const { filterData, useQuery, useUpdateFilter, useQueryModes } = useSearchContext()
+  const queryModes = useQueryModes()
   const query = useQuery()
   const updateFilter = useUpdateFilter()
   const theme = useTheme()
@@ -307,7 +309,7 @@ const QueryChips = React.memo(({ className, classes }) => {
             }
             newChips.push(
               <React.Fragment key={`${quantity}.${key}`}>
-                {createChips(`${quantity}.${key}`, value, onDelete, filterData, units)}
+                {createChips(`${quantity}.${key}`, value, onDelete, filterData, units, queryModes)}
               </React.Fragment>
             )
           })
@@ -322,12 +324,12 @@ const QueryChips = React.memo(({ className, classes }) => {
       // Regular chips get their own group
       } else {
         const onDelete = (newValue) => updateFilter([quantity, newValue])
-        chips.push(createChips(quantity, filterValue, onDelete, filterData, units))
+        chips.push(createChips(quantity, filterValue, onDelete, filterData, units, queryModes))
       }
     }
 
     return chips
-  }, [query, filterData, units, updateFilter])
+  }, [query, filterData, units, updateFilter, queryModes])
 
   return (
     <div className={clsx(className, styles.root)}>
diff --git a/gui/src/components/search/SearchContext.js b/gui/src/components/search/SearchContext.js
index 785b63db51490b938515664903199051f58ef3d6..8a4909a550b28baf34e0021af05e4264d6ae7a2f 100644
--- a/gui/src/components/search/SearchContext.js
+++ b/gui/src/components/search/SearchContext.js
@@ -425,7 +425,7 @@ export const SearchContextRaw = React.memo(({
       useFilterMaps,
       useResetFilters,
       useQueryString,
-      useDynamicQueryModes,
+      useQueryModes,
       aggsFamily,
       useWidgetValue,
       useSetWidget,
@@ -550,8 +550,8 @@ export const SearchContextRaw = React.memo(({
       default: (name) => initialQuery[name]
     })
 
-    const dynamicQueryModesFamily = atomFamily({
-      key: `dynamicQueryModesFamily_${contextID}`,
+    const queryModesFamily = atomFamily({
+      key: `queryModesFamily_${contextID}`,
       default: (name) => undefined
     })
 
@@ -637,32 +637,22 @@ export const SearchContextRaw = React.memo(({
     /**
      * A Recoil.js selector that return the dynamic query modes state
      */
-    const dynamicQueryModesState = selector({
-      key: `dynamicQueryModes_${contextID}`,
+    const queryModesState = selector({
+      key: `queryModes_${contextID}`,
       get: ({get}) => {
-        const query = {}
+        const queryModes = {}
         for (const key of get(filterNamesState)) {
-          const filter = get(dynamicQueryModesFamily(key))
-          if (filter !== undefined) {
-            query[key] = filter
-          }
-        }
-        return query
-      },
-      set: ({ set, get }, data) => {
-        for (const filter of get(filterNamesState)) {
-          set(dynamicQueryModesFamily(filter), undefined)
-        }
-        if (data) {
-          for (const [key, value] of Object.entries(data)) {
-            set(dynamicQueryModesFamily(key), value)
+          const queryMode = get(queryModesFamily(key))
+          if (queryMode !== undefined) {
+            queryModes[key] = queryMode
           }
         }
+        return queryModes
       }
     })
 
-    function useDynamicQueryModes() {
-      return useRecoilValue(dynamicQueryModesState)
+    function useQueryModes() {
+      return useRecoilValue(queryModesState)
     }
 
     const widgetFamily = atomFamily({
@@ -898,11 +888,11 @@ export const SearchContextRaw = React.memo(({
       const section = sectionContext?.section
       const subname = useMemo(() => section ? name.slice(section.length + 1) : undefined, [name, section])
       const setter = useSetRecoilState(queryFamily(section || name))
-      const dynamicQueryModeSetter = useSetRecoilState(dynamicQueryModesFamily(section || name))
+      const queryModeSetter = useSetRecoilState(queryModesFamily(section || name))
 
       return useCallback((value, config = undefined) => {
         updatedFilters.current.add(name)
-        dynamicQueryModeSetter(config?.queryMode)
+        queryModeSetter(config?.queryMode)
         section
           ? setter(old => {
             const newValue = isNil(old) ? {} : {...old}
@@ -915,7 +905,7 @@ export const SearchContextRaw = React.memo(({
           : setter(isFunction(value)
             ? (old) => clearEmpty(value(old))
             : clearEmpty(value))
-      }, [name, dynamicQueryModeSetter, section, setter, subname])
+      }, [name, queryModeSetter, section, setter, subname])
     }
 
     /**
@@ -957,7 +947,7 @@ export const SearchContextRaw = React.memo(({
       const reset = useRecoilCallback(({set}) => () => {
         for (const filter of filterNames) {
           set(queryFamily(filter), undefined)
-          set(dynamicQueryModesFamily(filter), undefined)
+          set(queryModesFamily(filter), undefined)
         }
       }, [])
       return reset
@@ -1137,7 +1127,7 @@ export const SearchContextRaw = React.memo(({
     const useUpdateFilter = () => {
       return useRecoilCallback(({set}) => ([key, value, queryMode]) => {
         set(queryFamily(key), value)
-        if (queryMode) set(dynamicQueryModesFamily(key), queryMode)
+        if (queryMode) set(queryModesFamily(key), queryMode)
       }, [])
     }
 
@@ -1167,7 +1157,7 @@ export const SearchContextRaw = React.memo(({
       useFilters,
       useResetFilters,
       useQueryString,
-      useDynamicQueryModes,
+      useQueryModes,
       aggsFamily,
       useWidgetValue,
       useSetWidget,
@@ -1233,7 +1223,7 @@ export const SearchContextRaw = React.memo(({
   const updateAggsResponse = useSetAggsResponse()
   const aggs = useAggs()
   const query = useQuery()
-  const dynamicQueryModes = useDynamicQueryModes()
+  const queryModes = useQueryModes()
   const filtersLocked = useFiltersLocked()
   const required = useRequired()
   const resultsUsed = useResultsUsed()
@@ -1327,8 +1317,8 @@ export const SearchContextRaw = React.memo(({
     // The locked filters are applied as a parallel AND query. This is the only
     // way to consistently apply them. If we mix them inside 'regular' filters,
     // they can be accidentally overwritten with an OR statement.
-    const customQuery = convertQueryGUIToAPI(apiQuery, resource, filtersData, dynamicQueryModes)
-    const lockedQuery = convertQueryGUIToAPI(filtersLocked, resource, filtersData, dynamicQueryModes)
+    const customQuery = convertQueryGUIToAPI(apiQuery, resource, filtersData, queryModes)
+    const lockedQuery = convertQueryGUIToAPI(filtersLocked, resource, filtersData, queryModes)
 
     let finalQuery = customQuery
     if (!isEmpty(lockedQuery)) {
@@ -1418,7 +1408,7 @@ export const SearchContextRaw = React.memo(({
         }
         resolve({undefined, ...resolveArgs})
       })
-  }, [filtersData, filterDefaults, filtersLocked, resource, api, raiseError, resolve, dynamicQueryModes, setApiQuery])
+  }, [filtersData, filterDefaults, filtersLocked, resource, api, raiseError, resolve, queryModes, setApiQuery])
 
   // This is a debounced version of apiCall.
   const apiCallDebounced = useMemo(() => debounce(apiCall, debounceTime), [apiCall])
@@ -1714,6 +1704,7 @@ export const SearchContextRaw = React.memo(({
       useApiData,
       useApiQuery,
       useQuery,
+      useQueryModes,
       useSetPagination,
       useParseQuery,
       useParseQueries,
@@ -1790,6 +1781,7 @@ export const SearchContextRaw = React.memo(({
     useRemoveAgg,
     useAggs,
     useQuery,
+    useQueryModes,
     useSetFilters,
     useUpdateFilter,
     apiCallInterMediate,
diff --git a/gui/src/components/search/input/InputTerms.js b/gui/src/components/search/input/InputTerms.js
index e71af1169764c90f0f993f44a9ed4684627c24cc..72eea032c407c93f55e4441793bee6ef68f86b84 100644
--- a/gui/src/components/search/input/InputTerms.js
+++ b/gui/src/components/search/input/InputTerms.js
@@ -93,6 +93,7 @@ const InputTerms = React.memo(({
   showStatistics,
   showSuggestions,
   options,
+  queryMode,
   sortStatic,
   className,
   classes,
@@ -230,8 +231,8 @@ const InputTerms = React.memo(({
     const checked = Object.entries(newOptions)
       .filter(([key, value]) => value.checked)
       .map(([key, value]) => getFinalKey(key, filterData[searchQuantity]?.dtype))
-    setFilter(new Set(checked))
-  }, [setFilter, visibleOptions, filterData, searchQuantity])
+    setFilter(new Set(checked), {queryMode: {and: 'all', or: 'any'}[queryMode]})
+  }, [setFilter, visibleOptions, filterData, searchQuantity, queryMode])
 
   // Create the search component
   const searchComponent = useMemo(() => {
@@ -396,6 +397,7 @@ InputTerms.propTypes = {
   showHeader: PropTypes.bool, // Whether to show the header
   showStatistics: PropTypes.bool, // Whether to show statistics
   showSuggestions: PropTypes.bool, // Whether to show the text field suggestions
+  queryMode: PropTypes.string,
   className: PropTypes.string,
   classes: PropTypes.object,
   'data-testid': PropTypes.string
diff --git a/gui/src/components/search/widgets/WidgetTerms.js b/gui/src/components/search/widgets/WidgetTerms.js
index 050812a9ce348712f4b9a38e148751af97dda40a..5eeb11cf096dab3952e4fdc40dcb6396f752c350 100644
--- a/gui/src/components/search/widgets/WidgetTerms.js
+++ b/gui/src/components/search/widgets/WidgetTerms.js
@@ -45,6 +45,11 @@ import { scales } from '../../plotting/common'
 // Predefined in order to not break memoization
 const dtypes = new Set([DType.String, DType.Enum, DType.Boolean])
 
+const queryModes = {
+  'and': 'and',
+  'or': 'or'
+}
+
 /**
  * Displays a terms widget.
  */
@@ -105,6 +110,7 @@ export const WidgetTerms = React.memo((
   search_quantity,
   scale,
   show_input,
+  query_mode,
   className,
   'data-testid': testID
 }) => {
@@ -141,8 +147,8 @@ export const WidgetTerms = React.memo((
       const newValue = new Set(old)
       selected ? newValue.add(key) : newValue.delete(key)
       return newValue
-    })
-  }, [setFilter])
+    }, {queryMode: {and: 'all', or: 'any'}[query_mode]})
+  }, [setFilter, query_mode])
 
   const handleEdit = useCallback(() => {
     setWidget(old => { return {...old, editing: true } })
@@ -249,6 +255,7 @@ WidgetTerms.propTypes = {
   scale: PropTypes.string,
   autorange: PropTypes.bool,
   show_input: PropTypes.bool,
+  query_mode: PropTypes.string,
   className: PropTypes.string,
   'data-testid': PropTypes.string
 }
@@ -348,6 +355,20 @@ export const WidgetTermsEdit = React.memo((props) => {
             onChange={(event) => handleChange('title', event.target.value)}
           />
         </WidgetEditOption>
+        <WidgetEditOption>
+          <TextField
+            select
+            fullWidth
+            label="Query mode"
+            variant="filled"
+            value={settings.query_mode}
+            onChange={(event) => { handleChange('query_mode', event.target.value) }}
+          >
+            {Object.keys(queryModes).map((key) =>
+              <MenuItem value={key} key={key}>{key}</MenuItem>
+            )}
+          </TextField>
+        </WidgetEditOption>
         <WidgetEditOption>
           <FormControlLabel
             control={<Checkbox checked={settings.show_input} onChange={(event, value) => handleChange('show_input', value)}/>}
@@ -367,11 +388,13 @@ WidgetTermsEdit.propTypes = {
   nbins: PropTypes.number,
   autorange: PropTypes.bool,
   show_input: PropTypes.bool,
+  query_mode: PropTypes.bool,
   onClose: PropTypes.func
 }
 
 export const schemaWidgetTerms = schemaWidget.shape({
   search_quantity: string().required('Search quantity is required.'),
   scale: string().required('Scale is required.'),
-  show_input: bool()
+  show_input: bool(),
+  query_mode: string()
 })
diff --git a/nomad/config/models/ui.py b/nomad/config/models/ui.py
index 345ead0c6d53a160561c17d0f60c183e7d3ea545..682ec2364b2865d49c64a8858b3403525b3a34b7 100644
--- a/nomad/config/models/ui.py
+++ b/nomad/config/models/ui.py
@@ -574,6 +574,11 @@ class Axis(AxisScale, AxisQuantity):
     """Configuration for a plot axis with limited scaling options."""
 
 
+class QueryModeEnum(str, Enum):
+    AND = 'and'
+    OR = 'or'
+
+
 class TermsBase(ConfigBaseModel):
     """Base model for configuring terms components."""
 
@@ -591,6 +596,10 @@ class TermsBase(ConfigBaseModel):
         None,
         deprecated='The "showinput" field is deprecated, use "show_input" instead.',
     )
+    query_mode: QueryModeEnum | None = Field(
+        None,
+        description='The query mode to use when multiple terms are selected.',
+    )
 
     @model_validator(mode='before')
     @classmethod