diff --git a/changelog/entries/unreleased/feature/2762_builder_improve_data_explorer_performances_and_allow_to_sele.json b/changelog/entries/unreleased/feature/2762_builder_improve_data_explorer_performances_and_allow_to_sele.json
new file mode 100644
index 000000000..9f23a18dc
--- /dev/null
+++ b/changelog/entries/unreleased/feature/2762_builder_improve_data_explorer_performances_and_allow_to_sele.json
@@ -0,0 +1,7 @@
+{
+    "type": "feature",
+    "message": "[Builder] Improve data explorer performances and allow to select any repetition from a source",
+    "issue_number": 2762,
+    "bullet_points": [],
+    "created_at": "2024-07-24"
+}
\ No newline at end of file
diff --git a/web-frontend/modules/builder/dataProviderTypes.js b/web-frontend/modules/builder/dataProviderTypes.js
index 738ff40be..a03092af2 100644
--- a/web-frontend/modules/builder/dataProviderTypes.js
+++ b/web-frontend/modules/builder/dataProviderTypes.js
@@ -515,24 +515,10 @@ export class FormDataProviderType extends DataProviderType {
     )
     return Object.fromEntries(
       accessibleFormElements.map((element) => {
-        let formEntry = {}
-        if (recordIndexPath !== undefined) {
-          const uniqueElementId = this.app.$registry
-            .get('element', element.type)
-            .uniqueElementId(element, recordIndexPath)
-          formEntry = getValueAtPath(formData, uniqueElementId)
-        } else {
-          // When `getDataContent` is called by `getNodes`, we won't have
-          // access to `recordIndexPath`, so we need to find the first *array*
-          // in the form data that corresponds to the element.
-          function _findActualValue(currentValue) {
-            if (Array.isArray(currentValue)) {
-              return _findActualValue(currentValue[0])
-            }
-            return currentValue
-          }
-          formEntry = _findActualValue(formData[element.id])
-        }
+        const uniqueElementId = this.app.$registry
+          .get('element', element.type)
+          .uniqueElementId(element, recordIndexPath)
+        const formEntry = getValueAtPath(formData, uniqueElementId)
         return [element.id, formEntry?.value]
       })
     )
diff --git a/web-frontend/modules/builder/store/element.js b/web-frontend/modules/builder/store/element.js
index 0e7277911..90675b618 100644
--- a/web-frontend/modules/builder/store/element.js
+++ b/web-frontend/modules/builder/store/element.js
@@ -80,15 +80,27 @@ const mutations = {
     updateCachedValues(page)
   },
   UPDATE_ITEM(state, { page, element: elementToUpdate, values }) {
+    let updateCached = false
     page.elements.forEach((element) => {
       if (element.id === elementToUpdate.id) {
+        if (
+          (values.order !== undefined && values.order !== element.order) ||
+          (values.place_in_container !== undefined &&
+            values.place_in_container !== element.place_in_container)
+        ) {
+          updateCached = true
+        }
         Object.assign(element, values)
       }
     })
     if (state.selected?.id === elementToUpdate.id) {
       Object.assign(state.selected, values)
     }
-    updateCachedValues(page)
+    if (updateCached) {
+      // We need to update cached values only if order or place of an element has
+      // changed or if an element has been added or removed.
+      updateCachedValues(page)
+    }
   },
   DELETE_ITEM(state, { page, elementId }) {
     const index = page.elements.findIndex((element) => element.id === elementId)
diff --git a/web-frontend/modules/core/assets/scss/components/all.scss b/web-frontend/modules/core/assets/scss/components/all.scss
index 50098b926..3a601191e 100644
--- a/web-frontend/modules/core/assets/scss/components/all.scss
+++ b/web-frontend/modules/core/assets/scss/components/all.scss
@@ -158,9 +158,8 @@
 @import 'get_formula_component';
 @import 'color_input';
 @import 'group_bys';
-@import 'data_explorer/node';
-@import 'data_explorer/root_node';
 @import 'data_explorer/data_explorer';
+@import 'data_explorer/data_explorer_node';
 @import 'anchor';
 @import 'call_to_action';
 @import 'toast_button';
diff --git a/web-frontend/modules/core/assets/scss/components/data_explorer/data_explorer_node.scss b/web-frontend/modules/core/assets/scss/components/data_explorer/data_explorer_node.scss
new file mode 100644
index 000000000..9ad48d309
--- /dev/null
+++ b/web-frontend/modules/core/assets/scss/components/data_explorer/data_explorer_node.scss
@@ -0,0 +1,64 @@
+.data-explorer-node__content-icon {
+  color: $color-neutral-600;
+}
+
+.data-explorer-node__children {
+  margin-left: 5px;
+}
+
+.data-explorer-node__content {
+  @extend %ellipsis;
+
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  gap: 6px;
+  padding: 0 5px;
+  margin: 0 5px;
+  font-size: 13px;
+  border-radius: 3px;
+  line-height: 24px;
+
+  .data-explorer-node--selected & {
+    background-color: $color-primary-100;
+
+    .data-explorer-node__content-selected-icon {
+      color: $color-success-500;
+    }
+  }
+
+  .data-explorer-node--level-0 > & {
+    font-size: 12px;
+    color: $color-neutral-500;
+    margin-left: 10px;
+
+    &:hover {
+      background-color: initial;
+    }
+  }
+
+  .data-explorer-node--level-0 .data-explorer-node--level-1 & {
+    cursor: pointer;
+
+    &:hover {
+      background-color: $color-neutral-100;
+    }
+  }
+}
+
+.data-explorer-node--level-0 {
+  margin-bottom: 8px;
+}
+
+.data-explorer-node__content-name {
+  flex: 1;
+}
+
+.data-explorer-node__array-node-more {
+  border: none;
+  background: none;
+  margin-left: 10px;
+  padding-top: 3px;
+  color: $color-neutral-600;
+  cursor: pointer;
+}
diff --git a/web-frontend/modules/core/assets/scss/components/data_explorer/node.scss b/web-frontend/modules/core/assets/scss/components/data_explorer/node.scss
deleted file mode 100644
index 5e21b0c4b..000000000
--- a/web-frontend/modules/core/assets/scss/components/data_explorer/node.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-.node {
-  margin: 3px 0 3px 10px;
-
-  &.node--first-indentation {
-    margin-left: 6px;
-    margin-right: 6px;
-  }
-}
-
-.node__content {
-  display: flex;
-  justify-content: space-between;
-  cursor: pointer;
-  padding: 0 5px;
-  font-size: 13px;
-  border-radius: 3px;
-  line-height: 24px;
-
-  &:hover {
-    background-color: $color-neutral-100;
-  }
-
-  &--selected {
-    background-color: $color-primary-100;
-  }
-}
-
-.node__content-name {
-  @extend %ellipsis;
-
-  display: flex;
-  align-items: center;
-}
-
-.node__selected {
-  margin: auto 0 auto 4px;
-  color: $color-success-500;
-  text-align: center;
-}
-
-.node__icon {
-  color: $color-neutral-600;
-  margin-right: 6px;
-}
diff --git a/web-frontend/modules/core/assets/scss/components/data_explorer/root_node.scss b/web-frontend/modules/core/assets/scss/components/data_explorer/root_node.scss
deleted file mode 100644
index 8bd826981..000000000
--- a/web-frontend/modules/core/assets/scss/components/data_explorer/root_node.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-.root-node {
-  margin-bottom: 8px;
-}
-
-.root-node__name {
-  font-size: 12px;
-  color: $color-neutral-500;
-  margin-left: 10px;
-}
diff --git a/web-frontend/modules/core/components/dataExplorer/DataExplorer.vue b/web-frontend/modules/core/components/dataExplorer/DataExplorer.vue
index 79c2b5deb..e8a84d1c3 100644
--- a/web-frontend/modules/core/components/dataExplorer/DataExplorer.vue
+++ b/web-frontend/modules/core/components/dataExplorer/DataExplorer.vue
@@ -6,35 +6,35 @@
     class="data-explorer"
     @shown="onShow"
   >
-    <div
-      ref="wrapper"
-      tabindex="0"
-      @focusin="$emit('focusin')"
-      @focusout="$emit('focusout')"
-    >
+    <div ref="wrapper">
       <div v-if="loading" class="context--loading">
-        <div class="loading"></div>
+        <div class="loading" />
       </div>
       <template v-else>
         <SelectSearch
           v-model="search"
           :placeholder="$t('action.search')"
           class="margin-bottom-1"
-        ></SelectSearch>
-        <RootNode
-          v-for="node in matchingNodes"
+        />
+        <DataExplorerNode
+          v-for="node in nodes"
           :key="node.identifier"
           :node="node"
-          :node-selected="nodeSelected"
           :open-nodes="openNodes"
-          @node-selected="$emit('node-selected', $event)"
+          :path="node.identifier"
+          :search-path="node.identifier"
+          :node-selected="nodeSelected"
+          :search="debouncedSearch"
+          @click="$emit('node-selected', $event)"
           @toggle="toggleNode"
+        />
+        <div
+          v-if="nodes.length === 0 || emptyResults"
+          class="context__description"
         >
-        </RootNode>
-        <div v-if="matchingNodes.length === 0" class="context__description">
-          <span v-if="isSearching">{{
-            $t('dataExplorer.noMatchingNodesText')
-          }}</span>
+          <span v-if="emptyResults">
+            {{ $t('dataExplorer.noMatchingNodesText') }}
+          </span>
           <span v-else>{{ $t('dataExplorer.noProvidersText') }}</span>
         </div>
       </template>
@@ -45,12 +45,13 @@
 <script>
 import context from '@baserow/modules/core/mixins/context'
 import SelectSearch from '@baserow/modules/core/components/SelectSearch'
-import RootNode from '@baserow/modules/core/components/dataExplorer/RootNode'
+import DataExplorerNode from '@baserow/modules/core/components/dataExplorer/DataExplorerNode'
+
 import _ from 'lodash'
 
 export default {
   name: 'DataExplorer',
-  components: { SelectSearch, RootNode },
+  components: { SelectSearch, DataExplorerNode },
   mixins: [context],
   props: {
     nodes: {
@@ -82,6 +83,9 @@ export default {
     isSearching() {
       return Boolean(this.debouncedSearch)
     },
+    emptyResults() {
+      return this.isSearching && this.openNodes.size === 0
+    },
     matchingPaths() {
       if (!this.isSearching) {
         return new Set()
@@ -89,34 +93,26 @@ export default {
         return this.matchesSearch(this.nodes, this.debouncedSearch)
       }
     },
-    matchingNodes() {
-      if (!this.isSearching) {
-        return this.nodes
-      } else {
-        return this.filterNodes(
-          this.nodes,
-          (node, path) => path === '' || this.matchingPaths.has(path)
-        )
-      }
-    },
   },
   watch: {
     /**
      * Debounces the actual search to prevent perf issues
      */
-    search(value) {
+    search(newSearch) {
+      this.$emit('node-unselected')
       clearTimeout(this.debounceSearch)
       this.debounceSearch = setTimeout(() => {
-        this.debouncedSearch = value
+        this.debouncedSearch = newSearch.trim().toLowerCase() || null
       }, 300)
     },
     matchingPaths(value) {
       this.openNodes = value
     },
     nodeSelected: {
-      handler(value) {
-        if (value !== null) {
-          this.toggleNode(value, true)
+      handler(path) {
+        if (path) {
+          this.debouncedSearch = null
+          this.toggleNode(path, true)
         }
       },
       immediate: true,
@@ -146,13 +142,16 @@ export default {
      * @returns A Set of path of nodes that match the search term
      */
     matchesSearch(nodes, search, parentPath = []) {
-      const searchSanitised = search.trim().toLowerCase()
-
       return (nodes || []).reduce((acc, subNode) => {
-        const subNodePath = [...parentPath, subNode.identifier]
-
+        let subNodePath = [...parentPath, subNode.identifier]
         if (subNode.nodes) {
           // It's not a leaf
+          if (subNode.type === 'array') {
+            // For array we have a special case. We need to match any intermediate value
+            // Can be either `*` or an integer. We use the `__any__` placeholder to
+            // achieve that.
+            subNodePath = [...parentPath, subNode.identifier, '__any__']
+          }
           const subSubNodes = this.matchesSearch(
             subNode.nodes,
             search,
@@ -160,10 +159,10 @@ export default {
           )
           acc = new Set([...acc, ...subSubNodes])
         } else {
-          // It's a leaf we check if the name match the search
+          // It's a leaf we check if the name matches the search
           const nodeNameSanitised = subNode.name.trim().toLowerCase()
 
-          if (nodeNameSanitised.includes(searchSanitised)) {
+          if (nodeNameSanitised.includes(search)) {
             // We also add the parents of the node
             acc = new Set([...acc, ...this.getPathAndParents(subNodePath)])
           }
@@ -171,25 +170,6 @@ export default {
         return acc
       }, new Set())
     },
-    /**
-     * Filters the nodes according to the given predicate. The predicate receives the
-     * node itself and the path of the node.
-     * @param {Array} nodes Node tree to filter.
-     * @param {Function} predicate Should return true if the node should be kept.
-     * @param {Array<String>} path Current nodes path part list.
-     */
-    filterNodes(nodes, predicate, path = []) {
-      const result = (nodes || [])
-        .filter((node) => predicate(node, [...path, node.identifier].join('.')))
-        .map((node) => ({
-          ...node,
-          nodes: this.filterNodes(node.nodes, predicate, [
-            ...path,
-            node.identifier,
-          ]),
-        }))
-      return result
-    },
     /**
      * Toggles a node state
      * @param {string} path to open/close.
diff --git a/web-frontend/modules/core/components/dataExplorer/DataExplorerNode.vue b/web-frontend/modules/core/components/dataExplorer/DataExplorerNode.vue
new file mode 100644
index 000000000..e782e0b40
--- /dev/null
+++ b/web-frontend/modules/core/components/dataExplorer/DataExplorerNode.vue
@@ -0,0 +1,223 @@
+<template>
+  <div
+    v-if="showNode"
+    class="data-explorer-node"
+    :class="{
+      [`data-explorer-node--level-${depth}`]: true,
+      'data-explorer-node--selected': isSelected,
+    }"
+  >
+    <div class="data-explorer-node__content" @click="handleClick(node)">
+      <i
+        v-if="depth > 0"
+        class="data-explorer-node__content-icon"
+        :class="getIcon(node)"
+      />
+      <span class="data-explorer-node__content-name">{{ node.name }}</span>
+      <i
+        v-if="isSelected"
+        class="data-explorer-node__content-selected-icon iconoir-check-circle"
+      />
+    </div>
+    <div v-if="isNodeOpen" ref="nodes" class="data-explorer-node__children">
+      <template v-if="node.type !== 'array'">
+        <DataExplorerNode
+          v-for="subNode in sortedNodes"
+          :key="subNode.identifier"
+          :node="subNode"
+          :depth="depth + 1"
+          :open-nodes="openNodes"
+          :node-selected="nodeSelected"
+          :path="`${path}.${subNode.identifier}`"
+          :search-path="`${searchPath}.${subNode.identifier}`"
+          :search="search"
+          @click="$emit('click', $event)"
+          @toggle="$emit('toggle', $event)"
+        />
+      </template>
+      <div v-else>
+        <DataExplorerNode
+          v-for="subNode in arrayNodes"
+          :key="subNode.identifier"
+          :node="subNode"
+          :depth="depth + 1"
+          :open-nodes="openNodes"
+          :node-selected="nodeSelected"
+          :search="search"
+          :path="`${path}.${subNode.identifier}`"
+          :search-path="`${searchPath}.__any__`"
+          @click="$emit('click', $event)"
+          @toggle="$emit('toggle', $event)"
+        />
+        <button
+          v-tooltip="$t('dataExplorerNode.showMore')"
+          class="data-explorer-node__array-node-more"
+          @click="count += nextIncrement"
+        >
+          {{ `[ ${count}...${nextCount - 1} ]` }}
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash'
+
+export default {
+  name: 'DataExplorerNode',
+  props: {
+    node: {
+      type: Object,
+      required: true,
+    },
+    depth: {
+      type: Number,
+      required: false,
+      default: 0,
+    },
+    openNodes: {
+      type: Set,
+      required: true,
+    },
+    path: {
+      type: String,
+      required: true,
+    },
+    searchPath: {
+      type: String,
+      required: true,
+    },
+    nodeSelected: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    search: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
+  data() {
+    return { count: 3 }
+  },
+  computed: {
+    isSelected() {
+      return this.nodeSelected === this.path
+    },
+    showNode() {
+      // we show the node if...
+      return (
+        // We are not searching
+        this.search === null ||
+        // It is selected
+        this.isSelected ||
+        // It has children and at least one children matches
+        (this.hasChildren && this.openNodes.has(this.searchPath)) ||
+        // Or it's a leaf and it matches the search term
+        this.node.name.trim().toLowerCase().includes(this.search)
+      )
+    },
+    hasChildren() {
+      return this.node.nodes?.length > 0
+    },
+    sortedNodes() {
+      if (this.hasChildren) {
+        return [...this.node.nodes].sort((a, b) => a.order - b.order)
+      } else {
+        return []
+      }
+    },
+    isNodeOpen() {
+      return (
+        // It's open if we are the first level
+        this.depth === 0 ||
+        // if it's in open node
+        this.openNodes.has(this.path) ||
+        // or if the search path is in openNodes
+        // The search path is the version with `__any__` instead of array indexes
+        this.openNodes.has(this.searchPath)
+      )
+    },
+    nextCount() {
+      return this.count + 10 - ((this.count + 10) % 10)
+    },
+    nextIncrement() {
+      return this.nextCount - this.count
+    },
+    arrayNodes() {
+      if (this.node.type === 'array') {
+        // In case of array node, we generate the nodes on demand
+        const head = {
+          nodes: this.node.nodes,
+          identifier: '*',
+          name: `[${this.$t('common.all')}]`,
+        }
+        return [
+          head,
+          ...[...Array(this.count).keys()].map((index) => ({
+            nodes: this.node.nodes,
+            identifier: `${index}`,
+            name: `${index}`,
+          })),
+        ]
+      }
+      return []
+    },
+  },
+  watch: {
+    nodeSelected: {
+      handler(newValue) {
+        // Generate enough array nodes to display arbitrary selected data
+        if (this.node.type === 'array' && newValue?.startsWith(this.path)) {
+          const nodeSelectedPath = _.toPath(newValue)
+          const pathParts = _.toPath(this.path)
+          const indexStr = nodeSelectedPath[pathParts.length]
+
+          if (indexStr !== '*') {
+            const index = parseInt(indexStr)
+            if (this.count <= index) {
+              this.count = index + 10 - ((index + 10) % 10)
+            }
+          }
+        }
+      },
+      immediate: true,
+    },
+    isSelected: {
+      async handler(newValue) {
+        if (newValue) {
+          await this.$nextTick()
+          // We scroll it into view when it becomes selected.
+          this.$el.scrollIntoView({ behavior: 'smooth', block: 'center' })
+        }
+      },
+      immediate: true,
+    },
+  },
+  methods: {
+    handleClick(node) {
+      if (this.depth < 1) {
+        // We don't want to click on first level
+        return
+      }
+      if (this.hasChildren) {
+        if (this.search === null) {
+          this.$emit('toggle', this.path)
+        }
+      } else {
+        this.$emit('click', { path: this.path, node })
+      }
+    },
+    getIcon(node) {
+      if (this.hasChildren) {
+        return this.isNodeOpen
+          ? 'iconoir-nav-arrow-down'
+          : 'iconoir-nav-arrow-right'
+      }
+      return node.icon
+    },
+  },
+}
+</script>
diff --git a/web-frontend/modules/core/components/dataExplorer/Node.vue b/web-frontend/modules/core/components/dataExplorer/Node.vue
deleted file mode 100644
index b5103fc38..000000000
--- a/web-frontend/modules/core/components/dataExplorer/Node.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<template functional>
-  <div
-    :data-identifier="props.node.identifier"
-    class="node"
-    :class="{ 'node--first-indentation': props.indentation === 0 }"
-  >
-    <div
-      class="node__content"
-      :class="{
-        'node__content--selected': props.nodeSelected === props.path,
-      }"
-      @click="$options.methods.click(props.node, props.path, listeners)"
-    >
-      <div class="node__content-name">
-        <i
-          class="node__icon"
-          :class="`${$options.methods.getIcon(
-            props.node,
-            props.openNodes.has(props.path)
-          )}`"
-        ></i>
-        {{ props.node.name }}
-      </div>
-      <i
-        v-if="props.nodeSelected === props.path"
-        class="node__selected iconoir-check-circle"
-      ></i>
-    </div>
-
-    <div v-if="props.openNodes.has(props.path)">
-      <Node
-        v-for="subNode in $options.methods.sortNodes(props.node.nodes || [])"
-        :key="subNode.identifier"
-        :node="subNode"
-        :open-nodes="props.openNodes"
-        :node-selected="props.nodeSelected"
-        :indentation="props.indentation + 1"
-        :path="`${props.path}.${subNode.identifier}`"
-        @click="listeners.click && listeners.click($event)"
-        @toggle="listeners.toggle && listeners.toggle($event)"
-      ></Node>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'Node',
-  props: {
-    node: {
-      type: Object,
-      required: true,
-    },
-    openNodes: {
-      type: Set,
-      required: true,
-    },
-    path: {
-      type: String,
-      required: true,
-    },
-    indentation: {
-      type: Number,
-      required: false,
-      default: 0,
-    },
-    nodeSelected: {
-      type: String,
-      required: false,
-      default: null,
-    },
-  },
-  methods: {
-    click(node, path, listeners) {
-      if (node.nodes?.length > 0 && listeners.toggle) {
-        listeners.toggle(path)
-      } else if (listeners.click) {
-        listeners.click({
-          path,
-          node,
-        })
-      }
-    },
-    getIcon(node, isOpen) {
-      if (!node.nodes?.length || node.nodes?.length < 1) {
-        return node.icon
-      }
-
-      return isOpen ? 'iconoir-nav-arrow-down' : 'iconoir-nav-arrow-right'
-    },
-    sortNodes(nodes) {
-      return nodes.sort((a, b) => a.order - b.order)
-    },
-  },
-}
-</script>
diff --git a/web-frontend/modules/core/components/dataExplorer/RootNode.vue b/web-frontend/modules/core/components/dataExplorer/RootNode.vue
deleted file mode 100644
index 3c033c6a6..000000000
--- a/web-frontend/modules/core/components/dataExplorer/RootNode.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-  <div class="root-node">
-    <div class="root-node__name">
-      {{ node.name }}
-    </div>
-    <div ref="nodes">
-      <Node
-        v-for="subNode in sortNodes(node.nodes)"
-        :key="subNode.identifier"
-        :node="subNode"
-        :node-selected="nodeSelected"
-        :open-nodes="openNodes"
-        :path="`${node.identifier}.${subNode.identifier}`"
-        @click="$emit('node-selected', $event)"
-        @toggle="$emit('toggle', $event)"
-      ></Node>
-    </div>
-  </div>
-</template>
-
-<script>
-import Node from '@baserow/modules/core/components/dataExplorer/Node'
-import _ from 'lodash'
-
-export default {
-  name: 'RootNode',
-  components: { Node },
-  props: {
-    node: {
-      type: Object,
-      required: true,
-    },
-    openNodes: {
-      type: Set,
-      required: true,
-    },
-    nodeSelected: {
-      type: String,
-      required: false,
-      default: null,
-    },
-  },
-  watch: {
-    async nodeSelected(path) {
-      if (path === null) {
-        return
-      }
-
-      // Wait for the nodes to be opened
-      await this.$nextTick()
-
-      const element = this.getNodeElementByPath(
-        this.$refs.nodes,
-        // Remove the first part since it is the root node, and we don't need that
-        _.toPath(path).slice(1)
-      )
-
-      if (element) {
-        element.scrollIntoView({ behavior: 'smooth', block: 'center' })
-      }
-    },
-  },
-  methods: {
-    getNodeElementByPath(element, path) {
-      const [identifier, ...rest] = path
-
-      const childMatching = [...element.children].find(
-        (child) => child.dataset.identifier === identifier
-      )
-
-      // We found the final element!
-      if (childMatching && rest.length === 0) {
-        return childMatching
-      }
-
-      // That means we still haven't gone through the whole path
-      if (childMatching && rest.length > 0) {
-        return this.getNodeElementByPath(childMatching, rest)
-      }
-
-      // That means we have gone into a dead end
-      if (!childMatching && !element.children.length) {
-        return null
-      }
-
-      // That means we have to keep searching the children to find the next piece of the
-      // path
-      return (
-        [...element.children]
-          .map((child) => this.getNodeElementByPath(child, path))
-          .find((e) => e !== null) || null
-      )
-    },
-    sortNodes(nodes) {
-      return nodes.sort((a, b) => a.order - b.order)
-    },
-  },
-}
-</script>
diff --git a/web-frontend/modules/core/components/formula/FormulaInputField.vue b/web-frontend/modules/core/components/formula/FormulaInputField.vue
index ad11dbe61..733243d58 100644
--- a/web-frontend/modules/core/components/formula/FormulaInputField.vue
+++ b/web-frontend/modules/core/components/formula/FormulaInputField.vue
@@ -18,16 +18,15 @@
       @data-component-clicked="dataComponentClicked"
     />
     <DataExplorer
+      v-if="isFocused"
       ref="dataExplorer"
       :nodes="nodes"
       :node-selected="nodeSelected"
       :loading="dataExplorerLoading"
       :application-context="applicationContext"
       @node-selected="dataExplorerItemSelected"
-      @node-toggled="editor.commands.focus()"
-      @focusin="dataExplorerFocused = true"
-      @focusout="dataExplorerFocused = false"
-    ></DataExplorer>
+      @node-unselected="unSelectNode()"
+    />
   </div>
 </template>
 
@@ -44,6 +43,7 @@ import { FromTipTapVisitor } from '@baserow/modules/core/formula/tiptap/fromTipT
 import { mergeAttributes } from '@tiptap/core'
 import DataExplorer from '@baserow/modules/core/components/dataExplorer/DataExplorer'
 import { RuntimeGet } from '@baserow/modules/core/runtimeFormulaTypes'
+import { isElement, onClickOutside } from '@baserow/modules/core/utils/dom'
 
 export default {
   name: 'FormulaInputField',
@@ -98,15 +98,11 @@ export default {
       content: null,
       isFormulaInvalid: false,
       dataNodeSelected: null,
-      dataExplorerFocused: false,
-      formulaInputFocused: false,
       valueUpdateTimeout: null,
+      isFocused: false,
     }
   },
   computed: {
-    isFocused() {
-      return this.dataExplorerFocused || this.formulaInputFocused
-    },
     classes() {
       return {
         'form-input--disabled': this.disabled,
@@ -157,9 +153,10 @@ export default {
       return this.editor.getJSON()
     },
     nodes() {
-      return this.dataProviders
+      const nodes = this.dataProviders
         .map((dataProvider) => dataProvider.getNodes(this.applicationContext))
         .filter((dataProviderNodes) => dataProviderNodes.nodes?.length > 0)
+      return nodes
     },
     nodeSelected() {
       return this.dataNodeSelected?.attrs?.path || null
@@ -169,11 +166,14 @@ export default {
     disabled(newValue) {
       this.editor.setOptions({ editable: !newValue })
     },
-    isFocused(value) {
+    async isFocused(value) {
       if (!value) {
         this.$refs.dataExplorer.hide()
         this.unSelectNode()
       } else {
+        // Wait for the data explorer to appear in the DOM.
+        await this.$nextTick()
+
         this.unSelectNode()
 
         /**
@@ -233,7 +233,6 @@ export default {
       editable: !this.disabled,
       onUpdate: this.onUpdate,
       onFocus: this.onFocus,
-      onBlur: this.onBlur,
       extensions: this.extensions,
       parseOptions: {
         preserveWhitespace: 'full',
@@ -260,20 +259,28 @@ export default {
       this.unSelectNode()
       this.emitChange()
     },
-    onFocus() {
+    onFocus(event) {
       // If the input is disabled, we don't want users to be
       // able to open the data explorer and select nodes.
-      this.formulaInputFocused = !this.disabled
-    },
-    onBlur() {
-      // We have to delay the browser here by just a bit, running the below will make
-      // sure the browser will execute all other events first, and then trigger this
-      // function. If we don't do this, the data explorer will be closed before the
-      // focus event can be fired which results in a closed data explorer once you lose
-      // focus on the input.
-      setTimeout(() => {
-        this.formulaInputFocused = false
-      }, 0)
+      if (this.disabled) {
+        return
+      }
+      this.isFocused = true
+
+      this.$el.clickOutsideEventCancel = onClickOutside(
+        this.$el,
+        (target, event) => {
+          if (
+            this.$refs.dataExplorer &&
+            // We ignore clicks inside data explorer
+            !isElement(this.$refs.dataExplorer.$el, target)
+          ) {
+            this.isFocused = false
+            this.editor.commands.blur()
+            this.$el.clickOutsideEventCancel()
+          }
+        }
+      )
     },
     toContent(formula) {
       if (!formula) {
diff --git a/web-frontend/modules/core/components/formula/GetFormulaComponent.vue b/web-frontend/modules/core/components/formula/GetFormulaComponent.vue
index 4a0498891..6b716a06a 100644
--- a/web-frontend/modules/core/components/formula/GetFormulaComponent.vue
+++ b/web-frontend/modules/core/components/formula/GetFormulaComponent.vue
@@ -41,9 +41,6 @@ export default {
   },
   mixins: [formulaComponent],
   inject: ['applicationContext', 'dataProviders'],
-  data() {
-    return { nodes: [], pathParts: [] }
-  },
   computed: {
     availableData() {
       return Object.values(this.dataProviders).map((dataProvider) =>
@@ -68,10 +65,10 @@ export default {
         (dataProvider) => dataProvider.type === pathParts[0]
       )
     },
-  },
-  mounted() {
-    if (this.dataProviderType) {
-      this.nodes = [this.dataProviderType.getNodes(this.applicationContext)]
+    nodes() {
+      return [this.dataProviderType.getNodes(this.applicationContext)]
+    },
+    pathParts() {
       const translatedPathPart = this.rawPathParts.map((_, index) =>
         this.dataProviderType.getPathTitle(
           this.applicationContext,
@@ -80,8 +77,8 @@ export default {
       )
 
       translatedPathPart[0] = this.dataProviderType.name
-      this.pathParts = translatedPathPart
-    }
+      return translatedPathPart
+    },
   },
   methods: {
     findNode(nodes, path) {
@@ -98,7 +95,16 @@ export default {
       }
 
       if (rest.length > 0) {
-        return this.findNode(nodeFound.nodes, rest)
+        if (nodeFound.type === 'array') {
+          const [index, ...afterIndex] = rest
+          // Check that the index is what is expected
+          if (!(index === '*' || /^\d+$/.test(index))) {
+            return null
+          }
+          return this.findNode(nodeFound.nodes, afterIndex)
+        } else {
+          return this.findNode(nodeFound.nodes, rest)
+        }
       }
 
       return nodeFound
diff --git a/web-frontend/modules/core/dataProviderTypes.js b/web-frontend/modules/core/dataProviderTypes.js
index d57c45ca1..7c441bc37 100644
--- a/web-frontend/modules/core/dataProviderTypes.js
+++ b/web-frontend/modules/core/dataProviderTypes.js
@@ -1,6 +1,5 @@
 import { Registerable } from '@baserow/modules/core/registry'
 import { getIconForType } from '@baserow/modules/core/utils/icon'
-import _ from 'lodash'
 
 /**
  * A data provider gets data from the application context and populate the context for
@@ -130,19 +129,13 @@ export class DataProviderType extends Registerable {
    * @returns {{identifier: string, name: string, nodes: []}}
    */
   getNodes(applicationContext) {
-    const content = this.getDataContent(applicationContext)
     const schema = this.getDataSchema(applicationContext)
 
     if (schema === null) {
       return {}
     }
 
-    const result = this._toNode(
-      applicationContext,
-      [this.type],
-      content,
-      schema
-    )
+    const result = this._toNode(applicationContext, [this.type], schema)
     return result
   }
 
@@ -150,11 +143,10 @@ export class DataProviderType extends Registerable {
    * Recursive method to deeply compute the node tree for this data providers.
    * @param {Object} applicationContext the application context.
    * @param {Array<String>} pathParts the path to get to the current node.
-   * @param {*} content the current node content.
    * @param {$schema: string} schema the current node schema.
    * @returns {{identifier: string, name: string, nodes: []}}
    */
-  _toNode(applicationContext, pathParts, content, schema) {
+  _toNode(applicationContext, pathParts, schema) {
     const identifier = pathParts.at(-1)
     const name = this.getPathTitle(applicationContext, pathParts)
     const order = schema?.order || null
@@ -170,44 +162,16 @@ export class DataProviderType extends Registerable {
     }
 
     if (schema.type === 'array') {
-      // When the current node is an array we append a new node to its children
-      // that represents the "whole" array.
-      // This is translated to '*' in the resulting formula and '[All]' in the
-      // data explorer name.
-      // The 'head' object contains all keys of the schema assigned to null
-      let fakeContent = null
-      if (schema.items.type === 'object') {
-        fakeContent = _.mapValues(schema.items.properties, () => null)
-        fakeContent = {}
-      }
-      if (schema.items.type === 'array') {
-        fakeContent = []
-      }
-
-      const head = {
-        ...this._toNode(
-          applicationContext,
-          [...pathParts, '*'],
-          fakeContent,
-          schema.items
-        ),
-        name: `[${this.app.i18n.t('common.all')}]`,
-      }
       return {
         name,
         identifier,
         icon: this.getIconForNode(schema),
-        nodes: [
-          head,
-          ...(content || []).map((item, index) =>
-            this._toNode(
-              applicationContext,
-              [...pathParts, `${index}`],
-              item,
-              schema.items
-            )
-          ),
-        ],
+        type: 'array',
+        nodes: this._toNode(
+          applicationContext,
+          [...pathParts, null],
+          schema.items
+        ).nodes,
       }
     }
 
@@ -222,7 +186,6 @@ export class DataProviderType extends Registerable {
             this._toNode(
               applicationContext,
               [...pathParts, identifier],
-              (content || {})[identifier],
               subSchema
             )
         ),
@@ -234,7 +197,6 @@ export class DataProviderType extends Registerable {
       order,
       type: schema.type,
       icon: this.getIconForNode(schema),
-      value: content,
       identifier,
     }
   }
diff --git a/web-frontend/modules/core/enums.js b/web-frontend/modules/core/enums.js
index d4f98c449..a396eec9a 100644
--- a/web-frontend/modules/core/enums.js
+++ b/web-frontend/modules/core/enums.js
@@ -32,6 +32,7 @@ export const DATA_TYPE_TO_ICON_MAP = {
   string: 'iconoir-text',
   number: 'baserow-icon-hashtag',
   boolean: 'baserow-icon-circle-checked',
+  array: 'iconoir-list',
 }
 
 export const UNKNOWN_DATA_TYPE_ICON = 'iconoir-question-mark'
diff --git a/web-frontend/modules/core/locales/en.json b/web-frontend/modules/core/locales/en.json
index e7359e843..4ef6aca7b 100644
--- a/web-frontend/modules/core/locales/en.json
+++ b/web-frontend/modules/core/locales/en.json
@@ -647,7 +647,7 @@
     "errorInvalidFormula": "The formula is invalid."
   },
   "dataExplorer": {
-    "noMatchingNodesText": "No matching data providers were found.",
+    "noMatchingNodesText": "No matching results were found.",
     "noProvidersText": "No data providers were found. To get started you can, for example, add a data source or page parameter."
   },
   "richTextEditorBubbleMenu": {
@@ -718,5 +718,15 @@
   "workspaceStep": {
     "title": "Create your workspace",
     "workspaceLabel": "Workspace name"
+  },
+  "colorInput": {
+    "default": "Default"
+  },
+  "imageInput": {
+    "labelDescription": "Select an image to upload...",
+    "labelButton": "Upload"
+  },
+  "dataExplorerNode": {
+    "showMore": "Show more repetitions"
   }
 }