diff --git a/enterprise/backend/src/baserow_enterprise/api/integrations/local_baserow/serializers.py b/enterprise/backend/src/baserow_enterprise/api/integrations/local_baserow/serializers.py
index d03a09bdf..00dba9b8d 100644
--- a/enterprise/backend/src/baserow_enterprise/api/integrations/local_baserow/serializers.py
+++ b/enterprise/backend/src/baserow_enterprise/api/integrations/local_baserow/serializers.py
@@ -7,7 +7,7 @@ from baserow_enterprise.integrations.local_baserow.models import (
 
 
 class LocalBaserowTableServiceAggregationSeriesSerializer(serializers.ModelSerializer):
-    field_id = serializers.IntegerField()
+    field_id = serializers.IntegerField(allow_null=True)
     order = serializers.IntegerField(read_only=True)
 
     class Meta:
diff --git a/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/models.py b/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/models.py
index 34c0fd49e..00f9c1c83 100644
--- a/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/models.py
+++ b/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/models.py
@@ -83,6 +83,7 @@ class LocalBaserowTableServiceAggregationSeries(models.Model):
     field = models.ForeignKey(
         "database.Field",
         help_text="The aggregated field.",
+        null=True,
         on_delete=models.CASCADE,
     )
     aggregation_type = models.CharField(
diff --git a/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/service_types.py b/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/service_types.py
index 6ce3b5a05..1a670dd06 100644
--- a/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/service_types.py
+++ b/enterprise/backend/src/baserow_enterprise/integrations/local_baserow/service_types.py
@@ -130,36 +130,39 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
             table_field_ids = [field.id for field in table_fields]
 
             def validate_agg_series(agg_series):
-                if agg_series["field_id"] not in table_field_ids:
-                    raise DRFValidationError(
-                        detail=f"The field with ID {agg_series['field_id']} is not "
-                        "related to the given table.",
-                        code="invalid_field",
-                    )
+                if agg_series["aggregation_type"]:
+                    try:
+                        agg_type = field_aggregation_registry.get(
+                            agg_series["aggregation_type"]
+                        )
+                    except AggregationTypeDoesNotExist:
+                        raise DRFValidationError(
+                            detail=f"The aggregation type '{agg_series['aggregation_type']}' "
+                            f"doesn't exist",
+                            code="invalid_aggregation_raw_type",
+                        )
 
-                try:
-                    agg_type = field_aggregation_registry.get(
-                        agg_series["aggregation_type"]
-                    )
-                except AggregationTypeDoesNotExist:
-                    raise DRFValidationError(
-                        detail=f"The aggregation type '{agg_series['aggregation_type']}' "
-                        f"doesn't exist",
-                        code="invalid_aggregation_raw_type",
-                    )
-                field = next(
-                    (
-                        field
-                        for field in table_fields
-                        if field.id == agg_series["field_id"]
-                    )
-                )
-                if not agg_type.field_is_compatible(field):
-                    raise DRFValidationError(
-                        detail=f"The field with ID {agg_series['field_id']} is not compatible "
-                        f"with aggregation type {agg_series['aggregation_type']}.",
-                        code="invalid_aggregation_raw_type",
+                if agg_series["field_id"] is not None:
+                    if agg_series["field_id"] not in table_field_ids:
+                        raise DRFValidationError(
+                            detail=f"The field with ID {agg_series['field_id']} is not "
+                            "related to the given table.",
+                            code="invalid_field",
+                        )
+
+                    field = next(
+                        (
+                            field
+                            for field in table_fields
+                            if field.id == agg_series["field_id"]
+                        )
                     )
+                    if not agg_type.field_is_compatible(field):
+                        raise DRFValidationError(
+                            detail=f"The field with ID {agg_series['field_id']} is not compatible "
+                            f"with aggregation type {agg_series['aggregation_type']}.",
+                            code="invalid_aggregation_raw_type",
+                        )
 
                 return True
 
@@ -446,6 +449,10 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
                 f"There are no aggregation series defined."
             )
         for agg_series in defined_agg_series:
+            if agg_series.field is None:
+                raise ServiceImproperlyConfigured(
+                    f"The aggregation series field has to be set."
+                )
             if agg_series.field.trashed:
                 raise ServiceImproperlyConfigured(
                     f"The field with ID {agg_series.field.id} is trashed."
diff --git a/enterprise/backend/src/baserow_enterprise/migrations/0041_alter_localbaserowtableserviceaggregationseries_field.py b/enterprise/backend/src/baserow_enterprise/migrations/0041_alter_localbaserowtableserviceaggregationseries_field.py
new file mode 100644
index 000000000..e46739def
--- /dev/null
+++ b/enterprise/backend/src/baserow_enterprise/migrations/0041_alter_localbaserowtableserviceaggregationseries_field.py
@@ -0,0 +1,24 @@
+# Generated by Django 5.0.9 on 2025-02-11 10:00
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("baserow_enterprise", "0040_chartwidget"),
+        ("database", "0180_view_allow_public_export"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="localbaserowtableserviceaggregationseries",
+            name="field",
+            field=models.ForeignKey(
+                help_text="The aggregated field.",
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                to="database.field",
+            ),
+        ),
+    ]
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/aggregation_series_form.scss b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/aggregation_series_form.scss
new file mode 100644
index 000000000..bc0d2f204
--- /dev/null
+++ b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/aggregation_series_form.scss
@@ -0,0 +1,6 @@
+.aggregation-series-form {
+  border-radius: 6px;
+  background: $palette-neutral-50;
+  padding: 16px 12px;
+  margin-bottom: 8px;
+}
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss
index 7e9a4aa2d..1c3184f45 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss
+++ b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/all.scss
@@ -19,3 +19,4 @@
 @import 'saml_auth_link';
 @import 'oidc_auth_link';
 @import 'dashboard_chart_widget';
+@import 'aggregation_series_form';
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/data_source/AggregationSeriesForm.vue b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/data_source/AggregationSeriesForm.vue
new file mode 100644
index 000000000..2996efc96
--- /dev/null
+++ b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/data_source/AggregationSeriesForm.vue
@@ -0,0 +1,167 @@
+<template>
+  <div class="aggregation-series-form">
+    <FormSection>
+      <FormGroup
+        small-label
+        :label="$t('aggregationSeriesForm.aggregationTypeLabel')"
+        required
+        horizontal
+        horizontal-narrow
+        class="margin-bottom-2"
+      >
+        <Dropdown
+          v-model="values.aggregation_type"
+          :error="fieldHasErrors('aggregation_type')"
+          @change="$v.values.aggregation_type.$touch()"
+        >
+          <DropdownItem
+            v-for="viewAggregation in viewAggregationTypes"
+            :key="viewAggregation.getType()"
+            :name="viewAggregation.getName()"
+            :value="viewAggregation.getType()"
+          >
+          </DropdownItem>
+        </Dropdown>
+      </FormGroup>
+      <FormGroup
+        small-label
+        :label="$t('aggregationSeriesForm.aggregationFieldLabel')"
+        required
+        horizontal
+        horizontal-narrow
+      >
+        <Dropdown
+          v-model="values.field_id"
+          :error="fieldHasErrors('field_id')"
+          :disabled="compatibleFields.length === 0"
+          @change="$v.values.field_id.$touch()"
+        >
+          <DropdownItem
+            v-for="field in compatibleFields"
+            :key="field.id"
+            :name="field.name"
+            :value="field.id"
+            :icon="fieldIconClass(field)"
+          >
+          </DropdownItem>
+        </Dropdown>
+      </FormGroup>
+    </FormSection>
+    <FormSection>
+      <ButtonText
+        icon="iconoir-bin"
+        type="secondary"
+        @click="$emit('delete-series', seriesIndex)"
+        >{{ $t('aggregationSeriesForm.deleteSeries') }}</ButtonText
+      >
+    </FormSection>
+  </div>
+</template>
+
+<script>
+import form from '@baserow/modules/core/mixins/form'
+import { required } from 'vuelidate/lib/validators'
+
+const includes = (array) => (value) => {
+  return array.includes(value)
+}
+
+export default {
+  name: 'AggregationSeriesForm',
+  mixins: [form],
+  props: {
+    tableFields: {
+      type: Array,
+      required: true,
+    },
+    seriesIndex: {
+      type: Number,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      allowedValues: ['field_id', 'aggregation_type'],
+      values: {
+        field_id: null,
+        aggregation_type: null,
+      },
+      emitValuesOnReset: false,
+    }
+  },
+  computed: {
+    viewAggregationTypes() {
+      return this.$registry.getOrderedList('viewAggregation')
+    },
+    aggregationTypeNames() {
+      return this.viewAggregationTypes.map((aggType) => aggType.getType())
+    },
+    compatibleFields() {
+      if (!this.values.aggregation_type) {
+        return []
+      }
+      const aggType = this.$registry.get(
+        'viewAggregation',
+        this.values.aggregation_type
+      )
+      return this.tableFields.filter((tableField) =>
+        aggType.fieldIsCompatible(tableField)
+      )
+    },
+    compatibleTableFieldIds() {
+      return this.compatibleFields.map((field) => field.id)
+    },
+  },
+  watch: {
+    defaultValues: {
+      handler() {
+        this.reset(true)
+        this.$v.$touch(true)
+      },
+      immediate: true,
+      deep: true,
+    },
+    'values.aggregation_type': {
+      handler(aggregationType) {
+        if (
+          aggregationType !== null &&
+          aggregationType !== this.defaultValues.aggregation_type &&
+          this.values.field_id !== null
+        ) {
+          // If both the field and aggregation type
+          // are selected, check if they are still
+          // compatible.
+          const aggType = this.$registry.get('viewAggregation', aggregationType)
+          const field = this.tableFields.filter(
+            (field) => field.id === this.values.field_id
+          )
+          if (!aggType.fieldIsCompatible(field)) {
+            this.values.field_id = null
+          }
+        }
+      },
+      immediate: true,
+    },
+  },
+  validations() {
+    return {
+      values: {
+        aggregation_type: {
+          required,
+          isValidAggregationType: includes(this.aggregationTypeNames),
+        },
+        field_id: {
+          required,
+          isValidFieldId: includes(this.compatibleTableFieldIds),
+        },
+      },
+    }
+  },
+  methods: {
+    fieldIconClass(field) {
+      const fieldType = this.$registry.get('field', field.type)
+      return fieldType.iconClass
+    },
+  },
+}
+</script>
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/data_source/GroupedAggregateRowsDataSourceForm.vue b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/data_source/GroupedAggregateRowsDataSourceForm.vue
index ee41ede54..6b5b7edde 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/data_source/GroupedAggregateRowsDataSourceForm.vue
+++ b/enterprise/web-frontend/modules/baserow_enterprise/dashboard/components/data_source/GroupedAggregateRowsDataSourceForm.vue
@@ -1,10 +1,115 @@
-<template><div></div></template>
+<template>
+  <form @submit.prevent>
+    <FormSection
+      :title="$t('groupedAggregateRowsDataSourceForm.data')"
+      class="margin-bottom-2"
+    >
+      <FormGroup
+        :label="$t('groupedAggregateRowsDataSourceForm.sourceFieldLabel')"
+        class="margin-bottom-2"
+        small-label
+        required
+        horizontal
+        horizontal-narrow
+      >
+        <Dropdown
+          v-model="values.table_id"
+          :show-search="true"
+          fixed-items
+          :error="fieldHasErrors('table_id')"
+          @change="$v.values.table_id.$touch()"
+        >
+          <DropdownSection
+            v-for="database in databases"
+            :key="database.id"
+            :title="`${database.name} (${database.id})`"
+          >
+            <DropdownItem
+              v-for="table in database.tables"
+              :key="table.id"
+              :name="table.name"
+              :value="table.id"
+              :indented="true"
+            >
+              {{ table.name }}
+            </DropdownItem>
+          </DropdownSection>
+        </Dropdown>
+      </FormGroup>
+      <FormGroup
+        v-if="values.table_id && !fieldHasErrors('table_id')"
+        :label="$t('groupedAggregateRowsDataSourceForm.viewFieldLabel')"
+        class="margin-bottom-2"
+        small-label
+        required
+        horizontal
+        horizontal-narrow
+      >
+        <Dropdown
+          v-model="values.view_id"
+          :show-search="false"
+          fixed-items
+          :error="fieldHasErrors('view_id')"
+          @change="$v.values.view_id.$touch()"
+        >
+          <DropdownItem
+            :name="$t('groupedAggregateRowsDataSourceForm.notSelected')"
+            :value="null"
+            >{{
+              $t('groupedAggregateRowsDataSourceForm.notSelected')
+            }}</DropdownItem
+          >
+          <DropdownItem
+            v-for="view in tableViews"
+            :key="view.id"
+            :name="view.name"
+            :value="view.id"
+          >
+            {{ view.name }}
+          </DropdownItem>
+        </Dropdown>
+      </FormGroup>
+    </FormSection>
+    <FormSection
+      v-if="values.table_id && !fieldHasErrors('table_id')"
+      :title="$t('groupedAggregateRowsDataSourceForm.series')"
+      class="margin-bottom-2"
+    >
+      <template #title-slot>
+        <ButtonText icon="iconoir-plus" type="secondary" @click="addSeries">{{
+          $t('groupedAggregateRowsDataSourceForm.addSeries')
+        }}</ButtonText>
+      </template>
+      <div class="margin-bottom-2"></div>
+      <AggregationSeriesForm
+        v-for="(series, index) in values.aggregation_series"
+        :key="index"
+        :table-fields="tableFields"
+        :series-index="index"
+        :default-values="series"
+        @delete-series="deleteSeries"
+        @values-changed="onAggregationSeriesUpdated(index, $event)"
+      >
+      </AggregationSeriesForm>
+    </FormSection>
+  </form>
+</template>
 
 <script>
 import form from '@baserow/modules/core/mixins/form'
+import { required } from 'vuelidate/lib/validators'
+import AggregationSeriesForm from '@baserow_enterprise/dashboard/components/data_source/AggregationSeriesForm'
+
+const includesIfSet = (array) => (value) => {
+  if (value === null || value === undefined) {
+    return true
+  }
+  return array.includes(value)
+}
 
 export default {
   name: 'GroupedAggregateRowsDataSourceForm',
+  components: { AggregationSeriesForm },
   mixins: [form],
   props: {
     dashboard: {
@@ -25,5 +130,124 @@ export default {
       default: '',
     },
   },
+  data() {
+    return {
+      allowedValues: ['table_id', 'view_id', 'aggregation_series'],
+      values: {
+        table_id: null,
+        view_id: null,
+        aggregation_series: [],
+      },
+      tableLoading: false,
+      databaseSelectedId: null,
+      emitValuesOnReset: false,
+    }
+  },
+  computed: {
+    integration() {
+      return this.$store.getters[
+        `${this.storePrefix}dashboardApplication/getIntegrationById`
+      ](this.dataSource.integration_id)
+    },
+    databases() {
+      return this.integration.context_data.databases
+    },
+    databaseSelected() {
+      return this.databases.find(
+        (database) => database.id === this.databaseSelectedId
+      )
+    },
+    tables() {
+      return this.databases.map((database) => database.tables).flat()
+    },
+    tableIds() {
+      return this.tables.map((table) => table.id)
+    },
+    tableSelected() {
+      return this.tables.find(({ id }) => id === this.values.table_id)
+    },
+    tableFields() {
+      return this.tableSelected?.fields || []
+    },
+    tableFieldIds() {
+      return this.tableFields.map((field) => field.id)
+    },
+    tableViews() {
+      return (
+        this.databaseSelected?.views.filter(
+          (view) => view.table_id === this.values.table_id
+        ) || []
+      )
+    },
+    tableViewIds() {
+      return this.tableViews.map((view) => view.id)
+    },
+  },
+  watch: {
+    dataSource: {
+      handler() {
+        // Reset the form to set default values
+        // again after a different widget is selected
+        this.reset(true)
+        // Run form validation so that
+        // problems are highlighted immediately
+        this.$v.$touch(true)
+      },
+      immediate: true,
+      deep: true,
+    },
+    'values.table_id': {
+      handler(tableId) {
+        if (tableId !== null) {
+          const databaseOfTableId = this.databases.find((database) =>
+            database.tables.some((table) => table.id === tableId)
+          )
+          if (databaseOfTableId) {
+            this.databaseSelectedId = databaseOfTableId.id
+          }
+
+          // If the values are not changed by the user
+          // we don't want to continue with preselecting
+          // default values
+          if (tableId === this.defaultValues.table_id) {
+            return
+          }
+
+          if (
+            !this.tableViews.some((view) => view.id === this.values.view_id)
+          ) {
+            this.values.view_id = null
+          }
+        }
+      },
+      immediate: true,
+    },
+  },
+  validations() {
+    return {
+      values: {
+        table_id: { required, isValidTableId: includesIfSet(this.tableIds) },
+        view_id: { isValidViewId: includesIfSet(this.tableViewIds) },
+      },
+    }
+  },
+  methods: {
+    addSeries() {
+      this.values.aggregation_series.push({
+        field_id: null,
+        aggregation_type: '',
+      })
+    },
+    deleteSeries(index) {
+      this.values.aggregation_series.splice(index, 1)
+    },
+    onAggregationSeriesUpdated(index, aggregationSeriesValues) {
+      const updatedAggregationSeries = this.values.aggregation_series
+      updatedAggregationSeries[index] = aggregationSeriesValues
+      this.$emit('values-changed', {
+        aggregation_series: updatedAggregationSeries,
+      })
+    },
+  },
 }
 </script>
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json b/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json
index 666aded50..e65741b27 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json
+++ b/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json
@@ -442,5 +442,18 @@
   },
   "chartWidget": {
     "name": "Chart"
+  },
+  "groupedAggregateRowsDataSourceForm": {
+    "series": "Series",
+    "addSeries": "Add series",
+    "data": "Data",
+    "sourceFieldLabel": "Source",
+    "viewFieldLabel": "View",
+    "notSelected": "Not selected"
+  },
+  "aggregationSeriesForm": {
+    "aggregationFieldLabel": "Field",
+    "aggregationTypeLabel": "Summary type",
+    "deleteSeries": "Delete series"
   }
 }
diff --git a/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_sidebar.scss b/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_sidebar.scss
index a3b89d76c..4865eec15 100644
--- a/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_sidebar.scss
+++ b/web-frontend/modules/core/assets/scss/components/dashboard/dashboard_sidebar.scss
@@ -2,4 +2,5 @@
   background: #fff;
   padding: 16px 16px 14px;
   border-left: 1px solid $palette-neutral-200;
+  overflow-y: scroll;
 }
diff --git a/web-frontend/modules/core/assets/scss/components/form.scss b/web-frontend/modules/core/assets/scss/components/form.scss
index 9b34863f3..e12ce36b0 100644
--- a/web-frontend/modules/core/assets/scss/components/form.scss
+++ b/web-frontend/modules/core/assets/scss/components/form.scss
@@ -236,3 +236,9 @@
   font-size: 13px;
   font-weight: 500;
 }
+
+.form-section__title-container {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
diff --git a/web-frontend/modules/core/components/FormSection.vue b/web-frontend/modules/core/components/FormSection.vue
index dd0c96fa5..ebad66b57 100644
--- a/web-frontend/modules/core/components/FormSection.vue
+++ b/web-frontend/modules/core/components/FormSection.vue
@@ -1,6 +1,11 @@
 <template>
   <div class="form-section">
-    <h3 v-if="title" class="form-section__title">{{ title }}</h3>
+    <div class="form-section__title-container">
+      <h3 v-if="title" class="form-section__title">{{ title }}</h3>
+      <div>
+        <slot name="title-slot"></slot>
+      </div>
+    </div>
     <slot />
   </div>
 </template>