diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/pom.xml b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/pom.xml
index a3c727467e4e8399d1e4f54558e20d1fc16b3c0d..2baa0ead4b47104883f5ee3d72225316a09c4593 100644
--- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/pom.xml
@@ -31,7 +31,7 @@
   <artifactId>xwiki-platform-livedata-api</artifactId>
   <name>XWiki Platform - Live Data - API</name>
   <properties>
-    <xwiki.jacoco.instructionRatio>0.70</xwiki.jacoco.instructionRatio>
+    <xwiki.jacoco.instructionRatio>0.69</xwiki.jacoco.instructionRatio>
     <checkstyle.suppressions.location>${basedir}/src/checkstyle/checkstyle-suppressions.xml</checkstyle.suppressions.location>
     <!-- Name to display by the Extension Manager -->
     <xwiki.extension.name>Live Data API</xwiki.extension.name>
diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/DefaultLiveDataConfigurationResolver.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/DefaultLiveDataConfigurationResolver.java
index 793ff0cb5ac1562ffe9f6f4f88f578e92fee811b..24c90debefbe98f8ba0bd9f17e7436277c368a00 100644
--- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/DefaultLiveDataConfigurationResolver.java
+++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/DefaultLiveDataConfigurationResolver.java
@@ -22,7 +22,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Type;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
@@ -42,6 +45,7 @@
 import org.xwiki.livedata.LiveDataException;
 import org.xwiki.livedata.LiveDataLayoutDescriptor;
 import org.xwiki.livedata.LiveDataMeta;
+import org.xwiki.livedata.LiveDataPaginationConfiguration;
 import org.xwiki.livedata.LiveDataPropertyDescriptor.FilterDescriptor;
 import org.xwiki.livedata.LiveDataPropertyDescriptor.OperatorDescriptor;
 import org.xwiki.livedata.LiveDataQuery.Source;
@@ -113,11 +117,43 @@ private LiveDataConfiguration mergeBaseConfig(LiveDataConfiguration config) thro
         mergedConfig.initialize();
 
         handleLayouts(config.getMeta().getLayouts(), mergedConfig.getMeta());
+        handlePageSizes(mergedConfig);
 
         // Translate using the context locale.
         return translate(mergedConfig);
     }
 
+    /**
+     * If the pagination sizes are missing the limit defined in the query, add it to the allowed page limits.
+     *
+     * @param mergedConfiguration the live data configuration
+     */
+    private void handlePageSizes(LiveDataConfiguration mergedConfiguration)
+    {
+        Integer limit = mergedConfiguration.getQuery().getLimit();
+        if (limit != null) {
+            LiveDataMeta meta = mergedConfiguration.getMeta();
+            if (meta == null) {
+                meta = new LiveDataMeta();
+                mergedConfiguration.setMeta(meta);
+            }
+            LiveDataPaginationConfiguration pagination = meta.getPagination();
+            if (pagination == null) {
+                pagination = new LiveDataPaginationConfiguration();
+                meta.setPagination(pagination);
+            }
+            List<Integer> pageSizes = pagination.getPageSizes();
+            if (pageSizes == null) {
+                pageSizes = new ArrayList<>();
+                pagination.setPageSizes(pageSizes);
+            }
+            if (!pageSizes.contains(limit)) {
+                pageSizes.add(limit);
+                Collections.sort(pageSizes);
+            }
+        }
+    }
+
     /**
      * Filters and updates the layouts in the merged configuration based on the layout descriptors provided by the
      * initial configuration.
diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/LiveDataRendererConfiguration.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/LiveDataRendererConfiguration.java
index 3b42a0f5b89ce00cda24bc4e57c7bd1e3651fbc6..a11cba6c033d7c92baf06249ca1c193b7228da96 100644
--- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/LiveDataRendererConfiguration.java
+++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/main/java/org/xwiki/livedata/internal/LiveDataRendererConfiguration.java
@@ -184,7 +184,7 @@ private LiveDataMeta getMeta(LiveDataRendererParameters parameters)
         LiveDataMeta meta = new LiveDataMeta();
         List<LiveDataLayoutDescriptor> layouts = getLayouts(parameters);
         meta.setLayouts(layouts);
-        // If it exists, use the id of the first layout as the default layout. 
+        // If it exists, use the id of the first layout as the default layout.
         Optional.ofNullable(layouts)
             .flatMap(ls -> ls.stream().findFirst().map(BaseDescriptor::getId))
             .ifPresent(meta::setDefaultLayout);
diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/test/resources/DefaultLiveDataConfigurationResolver.test b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/test/resources/DefaultLiveDataConfigurationResolver.test
index 667b1a405ee2169c9941b9971b6811cdb003ad67..e32f2f128e3ff40522cd21d1c6b642b33e611b8f 100644
--- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/test/resources/DefaultLiveDataConfigurationResolver.test
+++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-api/src/test/resources/DefaultLiveDataConfigurationResolver.test
@@ -4,7 +4,8 @@
 {
   "id":"defaultConfigResolverTest",
   "query":{
-    "source":"test"
+    "source":"test",
+    "limit": 17
   },
   "meta":{
     "propertyDescriptors":[
@@ -45,7 +46,7 @@
     "filters":[],
     "sort":[],
     "offset":0,
-    "limit":15
+    "limit":17
   },
   "data":{
     "count":0,
@@ -160,7 +161,7 @@
     "defaultDisplayer":"text",
     "pagination":{
       "maxShownPages":10,
-      "pageSizes":[15,25,50,100],
+      "pageSizes":[15,17,25,50,100],
       "showEntryRange":true,
       "showNextPrevious":true
     },
diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-macro/src/test/java/org/xwiki/livedata/internal/macro/LiveDataMacroTest.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-macro/src/test/java/org/xwiki/livedata/internal/macro/LiveDataMacroTest.java
index 5a687e41ce8457b24ee05d5c4dec5d0d7e9e33d0..65b5c928f659b0577de04f034a847ab2504947e2 100644
--- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-macro/src/test/java/org/xwiki/livedata/internal/macro/LiveDataMacroTest.java
+++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-macro/src/test/java/org/xwiki/livedata/internal/macro/LiveDataMacroTest.java
@@ -19,9 +19,10 @@
  */
 package org.xwiki.livedata.internal.macro;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
+import javax.inject.Inject;
 import javax.inject.Named;
 
 import org.apache.commons.text.StringEscapeUtils;
@@ -29,16 +30,26 @@
 import org.junit.jupiter.api.Test;
 import org.mockito.Mock;
 import org.xwiki.bridge.DocumentAccessBridge;
+import org.xwiki.component.manager.ComponentManager;
 import org.xwiki.configuration.internal.RestrictedConfigurationSourceProvider;
 import org.xwiki.context.internal.DefaultExecution;
+import org.xwiki.icon.IconManager;
+import org.xwiki.livedata.LiveData;
+import org.xwiki.livedata.LiveDataActionDescriptor;
 import org.xwiki.livedata.LiveDataConfiguration;
-import org.xwiki.livedata.LiveDataConfigurationResolver;
+import org.xwiki.livedata.LiveDataEntryDescriptor;
+import org.xwiki.livedata.LiveDataLayoutDescriptor;
+import org.xwiki.livedata.LiveDataMeta;
+import org.xwiki.livedata.LiveDataPaginationConfiguration;
+import org.xwiki.livedata.LiveDataPropertyDescriptor;
 import org.xwiki.livedata.LiveDataQuery;
-import org.xwiki.livedata.LiveDataQuery.Filter;
-import org.xwiki.livedata.LiveDataQuery.SortEntry;
+import org.xwiki.livedata.LiveDataSelectionConfiguration;
+import org.xwiki.livedata.internal.DefaultLiveDataConfigurationResolver;
 import org.xwiki.livedata.internal.LiveDataRenderer;
 import org.xwiki.livedata.internal.LiveDataRendererConfiguration;
+import org.xwiki.livedata.internal.StringLiveDataConfigurationResolver;
 import org.xwiki.livedata.macro.LiveDataMacroParameters;
+import org.xwiki.localization.ContextualLocalizationManager;
 import org.xwiki.rendering.block.Block;
 import org.xwiki.rendering.internal.renderer.html5.HTML5Renderer;
 import org.xwiki.rendering.internal.renderer.html5.HTML5RendererFactory;
@@ -46,12 +57,13 @@
 import org.xwiki.rendering.internal.renderer.xhtml.image.DefaultXHTMLImageTypeRenderer;
 import org.xwiki.rendering.internal.renderer.xhtml.link.DefaultXHTMLLinkRenderer;
 import org.xwiki.rendering.internal.renderer.xhtml.link.DefaultXHTMLLinkTypeRenderer;
-import org.xwiki.rendering.internal.transformation.DefaultRenderingContext;
 import org.xwiki.rendering.renderer.PrintRendererFactory;
 import org.xwiki.rendering.transformation.MacroTransformationContext;
+import org.xwiki.rendering.transformation.RenderingContext;
 import org.xwiki.rendering.transformation.TransformationContext;
 import org.xwiki.security.authorization.ContextualAuthorizationManager;
 import org.xwiki.skinx.SkinExtension;
+import org.xwiki.test.annotation.BeforeComponent;
 import org.xwiki.test.annotation.ComponentList;
 import org.xwiki.test.junit5.mockito.ComponentTest;
 import org.xwiki.test.junit5.mockito.InjectMockComponents;
@@ -64,7 +76,10 @@
 import org.xwiki.xml.internal.html.SVGDefinitions;
 import org.xwiki.xml.internal.html.SecureHTMLElementSanitizer;
 
-import static org.mockito.ArgumentMatchers.any;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 import static org.mockito.Mockito.when;
 import static org.xwiki.rendering.test.integration.junit5.BlockAssert.assertBlocks;
 
@@ -92,7 +107,8 @@
     DefaultExecution.class,
     LiveDataRendererConfiguration.class,
     LiveDataRenderer.class,
-    DefaultRenderingContext.class
+    DefaultLiveDataConfigurationResolver.class,
+    StringLiveDataConfigurationResolver.class
 })
 class LiveDataMacroTest
 {
@@ -100,87 +116,221 @@ class LiveDataMacroTest
     private LiveDataMacro liveDataMacro;
 
     @MockComponent
-    private LiveDataConfigurationResolver<LiveDataConfiguration> defaultConfigResolver;
+    private DocumentAccessBridge documentAccessBridge;
 
     @MockComponent
-    private LiveDataConfigurationResolver<String> stringConfigResolver;
+    private ContextualAuthorizationManager contextualAuthorizationManager;
 
     @MockComponent
-    private DocumentAccessBridge documentAccessBridge;
+    private IconManager iconManager;
 
     @MockComponent
-    private ContextualAuthorizationManager contextualAuthorizationManager;
+    private ContextualLocalizationManager contextualLocalizationManager;
+
+    @MockComponent
+    private RenderingContext renderingContext;
 
     @MockComponent
     @Named("jsfx")
     private SkinExtension jsfx;
 
+    @Inject
+    @Named("html/5.0")
     private PrintRendererFactory rendererFactory;
 
     @Mock
-    private MacroTransformationContext context;
+    private MacroTransformationContext macroTransformationContext;
 
     @Mock
     private TransformationContext transformationContext;
 
-    @BeforeEach
-    void before(MockitoComponentManager componentManager) throws Exception
+    private LiveDataConfiguration liveDataConfiguration;
+
+    @BeforeComponent
+    void beforeComponent(MockitoComponentManager componentManager) throws Exception
     {
-        this.rendererFactory = componentManager.getInstance(PrintRendererFactory.class, "html/5.0");
+        componentManager.registerComponent(ComponentManager.class, "context", componentManager);
+
+        // setup default LD configuration
+        this.liveDataConfiguration = new LiveDataConfiguration();
+        LiveDataQuery liveDataQuery = new LiveDataQuery();
+        liveDataQuery.setLimit(15);
+        liveDataQuery.setProperties(List.of());
+        liveDataQuery.setSource(new LiveDataQuery.Source());
+        liveDataQuery.setFilters(List.of());
+        liveDataQuery.setSort(List.of());
+        liveDataQuery.setOffset(0L);
+        this.liveDataConfiguration.setQuery(liveDataQuery);
+
+        LiveData liveData = new LiveData();
+        liveData.setCount(0);
+        this.liveDataConfiguration.setData(liveData);
+
+        LiveDataMeta meta = new LiveDataMeta();
+        LiveDataLayoutDescriptor tableLayout = new LiveDataLayoutDescriptor("table");
+        tableLayout.setName("table");
+        tableLayout.setIcon(Map.of());
+        LiveDataLayoutDescriptor cardsLayout = new LiveDataLayoutDescriptor("cards");
+        cardsLayout.setName("cards");
+        cardsLayout.setIcon(Map.of());
+        meta.setLayouts(List.of(
+            tableLayout,
+            cardsLayout
+        ));
+        meta.setDefaultLayout(tableLayout.getId());
+
+        LiveDataPropertyDescriptor.OperatorDescriptor contains =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("contains", "contains");
+        LiveDataPropertyDescriptor.OperatorDescriptor equals =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("equals", "equals");
+        LiveDataPropertyDescriptor.OperatorDescriptor startsWith =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("startsWith", "startsWith");
+        LiveDataPropertyDescriptor.FilterDescriptor textFilter =
+            new LiveDataPropertyDescriptor.FilterDescriptor("text");
+        textFilter.setOperators(List.of(contains, startsWith, equals));
+        textFilter.setDefaultOperator("contains");
+
+        LiveDataPropertyDescriptor.OperatorDescriptor equalsNumber =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("equals", "=");
+        LiveDataPropertyDescriptor.OperatorDescriptor less =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("less", "<");
+        LiveDataPropertyDescriptor.OperatorDescriptor greater =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("greater", ">");
+        LiveDataPropertyDescriptor.FilterDescriptor numberFilter =
+            new LiveDataPropertyDescriptor.FilterDescriptor("number");
+        numberFilter.setOperators(List.of(equalsNumber, less, greater));
+        numberFilter.setDefaultOperator("equals");
+
+        LiveDataPropertyDescriptor.FilterDescriptor booleanFilter =
+            new LiveDataPropertyDescriptor.FilterDescriptor("boolean");
+        booleanFilter.setOperators(List.of(equals));
+        booleanFilter.setDefaultOperator("equals");
+
+        LiveDataPropertyDescriptor.OperatorDescriptor between =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("between", "between");
+        LiveDataPropertyDescriptor.OperatorDescriptor before =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("before", "before");
+        LiveDataPropertyDescriptor.OperatorDescriptor after =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("after", "after");
+        LiveDataPropertyDescriptor.FilterDescriptor dateFilter =
+            new LiveDataPropertyDescriptor.FilterDescriptor("date");
+        dateFilter.setOperators(List.of(between, before, after, contains));
+        dateFilter.setDefaultOperator("between");
+        dateFilter.setParameter("dateFormat", "yyyy/MM/dd HH:mm");
+
+        LiveDataPropertyDescriptor.OperatorDescriptor empty =
+            new LiveDataPropertyDescriptor.OperatorDescriptor("empty", "empty");
+        LiveDataPropertyDescriptor.FilterDescriptor listFilter =
+            new LiveDataPropertyDescriptor.FilterDescriptor("list");
+        listFilter.setOperators(List.of(equals, startsWith, contains, empty));
+        listFilter.setDefaultOperator("contains");
+        meta.setFilters(List.of(
+            textFilter,
+            numberFilter,
+            booleanFilter,
+            dateFilter,
+            listFilter
+        ));
+        meta.setDefaultFilter("text");
+
+        meta.setDisplayers(List.of(
+            new LiveDataPropertyDescriptor.DisplayerDescriptor("text"),
+            new LiveDataPropertyDescriptor.DisplayerDescriptor("link"),
+            new LiveDataPropertyDescriptor.DisplayerDescriptor("html"),
+            new LiveDataPropertyDescriptor.DisplayerDescriptor("actions"),
+            new LiveDataPropertyDescriptor.DisplayerDescriptor("boolean")
+        ));
+        meta.setDefaultDisplayer("text");
 
-        when(this.defaultConfigResolver.resolve(any(LiveDataConfiguration.class)))
-            .thenAnswer(invocation -> invocation.getArgument(0));
+        LiveDataPaginationConfiguration paginationConfiguration = new LiveDataPaginationConfiguration();
+        paginationConfiguration.setMaxShownPages(10);
+        paginationConfiguration.setPageSizes(List.of(15,25,50,100));
+        paginationConfiguration.setShowEntryRange(true);
+        paginationConfiguration.setShowNextPrevious(true);
+        meta.setPagination(paginationConfiguration);
 
-        when(this.stringConfigResolver.resolve("{}")).thenReturn(new LiveDataConfiguration());
-        when(this.context.getTransformationContext()).thenReturn(this.transformationContext);
+        meta.setPropertyDescriptors(List.of());
+        meta.setPropertyTypes(List.of());
+        meta.setEntryDescriptor(new LiveDataEntryDescriptor());
+
+        LiveDataActionDescriptor view = new LiveDataActionDescriptor("view");
+        view.setName("view");
+        view.setIcon(Map.of());
+
+        LiveDataActionDescriptor edit = new LiveDataActionDescriptor("edit");
+        edit.setName("edit");
+        edit.setIcon(Map.of());
+
+        LiveDataActionDescriptor delete = new LiveDataActionDescriptor("delete");
+        delete.setName("delete");
+        delete.setIcon(Map.of("cssClass", "text-danger"));
+
+        LiveDataActionDescriptor copy = new LiveDataActionDescriptor("copy");
+        copy.setName("copy");
+        copy.setIcon(Map.of());
+
+        LiveDataActionDescriptor rename = new LiveDataActionDescriptor("rename");
+        rename.setName("rename");
+        rename.setIcon(Map.of());
+
+        LiveDataActionDescriptor rights = new LiveDataActionDescriptor("rights");
+        rights.setName("rights");
+        rights.setIcon(Map.of());
+        meta.setActions(List.of(view, edit, delete, copy, rename, rights));
+        meta.setSelection(new LiveDataSelectionConfiguration());
+        this.liveDataConfiguration.setMeta(meta);
+    }
+
+    @BeforeEach
+    void before() throws Exception
+    {
+        when(this.macroTransformationContext.getTransformationContext()).thenReturn(this.transformationContext);
     }
 
     @Test
     void executeWithoutParams() throws Exception
     {
-        String expectedConfig = json("{'query':{'source':{}},'meta':{'pagination':{}}}");
-        String expected = "<div class=\"liveData loading\" data-config=\"" + escapeXML(expectedConfig) + "\" "
-            + "data-config-content-trusted=\"true\"></div>";
 
-        List<Block> blocks = this.liveDataMacro.execute(new LiveDataMacroParameters(), null, this.context);
+        String expected =
+            String.format("<div class=\"liveData loading\" data-config=\"%s\" "
+                + "data-config-content-trusted=\"true\"></div>",
+                escapeXML(json(this.liveDataConfiguration)));
+
+        List<Block> blocks =
+            this.liveDataMacro.execute(new LiveDataMacroParameters(), null, this.macroTransformationContext);
         assertBlocks(expected, blocks, this.rendererFactory);
     }
 
     @Test
     void execute() throws Exception
     {
-        StringBuilder expectedConfig = new StringBuilder();
-        expectedConfig.append("{");
-        expectedConfig.append("  'id':'test',".trim());
-        expectedConfig.append("  'query':{".trim());
-        expectedConfig.append("    'properties':['avatar','firstName','lastName','position'],".trim());
-        expectedConfig.append("    'source':{'id':'users','wiki':'dev','group':'apps'},".trim());
-        expectedConfig.append("    'filters':[".trim());
-        expectedConfig.append("      {'property':'firstName','constraints':[{'value':'m'}]},".trim());
-        expectedConfig.append("      {'property':'position','constraints':[{'value':'lead'}]}".trim());
-        expectedConfig.append("    ],".trim());
-        expectedConfig.append("    'sort':[".trim());
-        expectedConfig.append("      {'property':'firstName'},".trim());
-        expectedConfig.append("      {'property':'lastName','descending':true},".trim());
-        expectedConfig.append("      {'property':'position'}],".trim());
-        expectedConfig.append("    'offset':20,".trim());
-        expectedConfig.append("    'limit':10".trim());
-        expectedConfig.append("  },".trim());
-        expectedConfig.append("  'meta':{".trim());
-        expectedConfig.append("    'layouts':[".trim());
-        expectedConfig.append("      {'id':'table'},".trim());
-        expectedConfig.append("      {'id':'cards'}".trim());
-        expectedConfig.append("    ],".trim());
-        expectedConfig.append("    'defaultLayout':'table',".trim());
-        expectedConfig.append("    'pagination':{".trim());
-        expectedConfig.append("      'pageSizes':[15,25,50],".trim());
-        expectedConfig.append("      'showPageSizeDropdown':true".trim());
-        expectedConfig.append("    },'description':'A description'".trim());
-        expectedConfig.append("  }".trim());
-        expectedConfig.append("}");
+        this.liveDataConfiguration.setId("test");
+        LiveDataQuery query = this.liveDataConfiguration.getQuery();
+        query.setProperties(List.of("avatar", "firstName", "lastName", "position"));
+        LiveDataQuery.Source source = new LiveDataQuery.Source("users");
+        source.setParameter("wiki", "dev");
+        source.setParameter("group", "apps");
+        query.setSource(source);
+        query.setFilters(List.of(
+            new LiveDataQuery.Filter("firstName", "m"),
+            new LiveDataQuery.Filter("position", "lead")
+        ));
+        query.setSort(List.of(
+            new LiveDataQuery.SortEntry("firstName"),
+            new LiveDataQuery.SortEntry("lastName", true),
+            new LiveDataQuery.SortEntry("position")
+        ));
+        query.setOffset(20L);
+        query.setLimit(10);
+
+        LiveDataPaginationConfiguration pagination = this.liveDataConfiguration.getMeta().getPagination();
+        pagination.setPageSizes(List.of(10,15,25,50));
+        pagination.setShowPageSizeDropdown(true);
+
+        this.liveDataConfiguration.getMeta().setDescription("A description");
 
         String expected = String.format("<div class=\"liveData loading\" id=\"test\" data-config=\"%s\" "
-            + "data-config-content-trusted=\"true\"></div>", escapeXML(json(expectedConfig.toString())));
+            + "data-config-content-trusted=\"true\"></div>", escapeXML(json(this.liveDataConfiguration)));
 
         LiveDataMacroParameters parameters = new LiveDataMacroParameters();
         parameters.setId("test");
@@ -196,7 +346,7 @@ void execute() throws Exception
         parameters.setPageSizes("15, 25, 50");
         parameters.setDescription("A description");
 
-        List<Block> blocks = this.liveDataMacro.execute(parameters, null, this.context);
+        List<Block> blocks = this.liveDataMacro.execute(parameters, null, this.macroTransformationContext);
         assertBlocks(expected, blocks, this.rendererFactory);
     }
 
@@ -210,39 +360,38 @@ void executeWithContent() throws Exception
         parameters.setSort("firstName");
         parameters.setLimit(10);
 
-        LiveDataConfiguration advancedConfig = new LiveDataConfiguration();
-        advancedConfig.setQuery(new LiveDataQuery());
-        advancedConfig.getQuery().setFilters(new ArrayList<>());
-        advancedConfig.getQuery().getFilters().add(new Filter("position", "R&D"));
-        advancedConfig.getQuery().setSort(new ArrayList<>());
-        advancedConfig.getQuery().getSort().add(new SortEntry("lastName", true));
-        advancedConfig.getQuery().setLimit(15);
-
-        when(this.stringConfigResolver.resolve("{...}")).thenReturn(advancedConfig);
-
-        StringBuilder expectedConfig = new StringBuilder();
-        expectedConfig.append("{");
-        expectedConfig.append("  'id':'test',".trim());
-        expectedConfig.append("  'query':{".trim());
-        expectedConfig.append("    'properties':['avatar','firstName','lastName','position'],".trim());
-        expectedConfig.append("    'source':{'id':'users'},".trim());
-        expectedConfig.append("    'filters':[".trim());
-        expectedConfig.append("      {'property':'position','constraints':[{'value':'R&D'}]}".trim());
-        expectedConfig.append("    ],".trim());
-        expectedConfig.append("    'sort':[".trim());
-        expectedConfig.append("      {'property':'firstName'}".trim());
-        expectedConfig.append("    ],".trim());
-        expectedConfig.append("    'limit':10".trim());
-        expectedConfig.append("  },".trim());
-        expectedConfig.append("  'meta':{".trim());
-        expectedConfig.append("    'pagination':{}".trim());
-        expectedConfig.append("  }".trim());
-        expectedConfig.append("}");
+        StringBuilder advancedConfig = new StringBuilder();
+        advancedConfig.append("{");
+        advancedConfig.append("  'query': {".trim());
+        advancedConfig.append("    'filters': [".trim());
+        advancedConfig.append("      {'property': 'position', 'constraints':[{'value':'R&D'}]}".trim());
+        advancedConfig.append("    ],".trim());
+        advancedConfig.append("    'sort': [{'property': 'lastName', 'descending':true}],".trim());
+        advancedConfig.append("    'limit': 15".trim());
+        advancedConfig.append("  }".trim());
+        advancedConfig.append("}");
+
+        this.liveDataConfiguration.setId("test");
+        LiveDataQuery query = this.liveDataConfiguration.getQuery();
+        query.setProperties(List.of("avatar", "firstName", "lastName", "position"));
+        LiveDataQuery.Source source = new LiveDataQuery.Source("users");
+        query.setSource(source);
+        query.setFilters(List.of(
+            new LiveDataQuery.Filter("position", "R&D")
+        ));
+        query.setSort(List.of(
+            new LiveDataQuery.SortEntry("firstName")
+        ));
+        query.setLimit(10);
+
+        LiveDataPaginationConfiguration pagination = this.liveDataConfiguration.getMeta().getPagination();
+        pagination.setPageSizes(List.of(10,15,25,50,100));
 
         String expected = String.format("<div class=\"liveData loading\" id=\"test\" data-config=\"%s\" "
-            + "data-config-content-trusted=\"false\"></div>", escapeXML(json(expectedConfig.toString())));
+            + "data-config-content-trusted=\"false\"></div>", escapeXML(json(this.liveDataConfiguration)));
 
-        List<Block> blocks = this.liveDataMacro.execute(parameters, "{...}", this.context);
+        List<Block> blocks = this.liveDataMacro.execute(parameters, json(advancedConfig.toString()),
+            this.macroTransformationContext);
         assertBlocks(expected, blocks, this.rendererFactory);
     }
 
@@ -251,6 +400,13 @@ private String json(String text)
         return text.replace('\'', '"');
     }
 
+    private String json(LiveDataConfiguration liveDataConfiguration) throws JsonProcessingException
+    {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        return objectMapper.writeValueAsString(liveDataConfiguration);
+    }
+
     private String escapeXML(String value)
     {
         return StringEscapeUtils.escapeXml10(value).replace("{", "&#123;");
diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-docker/src/test/it/org/xwiki/livedata/test/ui/LiveDataIT.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-docker/src/test/it/org/xwiki/livedata/test/ui/LiveDataIT.java
index e8edacd52759ed94fb18a6f9fb6f063da2bcb33f..054750f66c670a50f216c3ddff0e7017b86e7ae5 100644
--- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-docker/src/test/it/org/xwiki/livedata/test/ui/LiveDataIT.java
+++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-docker/src/test/it/org/xwiki/livedata/test/ui/LiveDataIT.java
@@ -157,6 +157,14 @@ void livedataLivetableTableLayout(TestUtils testUtils, TestReference testReferen
 
         // Test the Live Data content.
         assertEquals(3, tableLayout.countRows());
+
+        assertEquals(List.of(5, 15, 25, 50, 100), liveDataElement.getPaginationPageSizes());
+
+        // Check that the list of page sizes is preserved when we select a standard page size (see XWIKI-20650)
+        liveDataElement = liveDataElement.setPagination(15);
+        assertEquals(3, liveDataElement.getTableLayout().countRows());
+        assertEquals(List.of(5, 15, 25, 50, 100), liveDataElement.getPaginationPageSizes());
+
         tableLayout.assertRow(DOC_TITLE_COLUMN, "O1");
         tableLayout.assertRow(DOC_TITLE_COLUMN, "O2 1");
         tableLayout.assertRow(DOC_TITLE_COLUMN, "O3 1");
@@ -425,6 +433,7 @@ private static void createClassNameLiveDataPage(TestUtils testUtils, TestReferen
             + "{{liveData\n"
             + "  id=\"test\"\n"
             + "  properties=\"" + String.join(",", properties) + "\"\n"
+            + "  limit=\"5\"\n"
             + "  source=\"liveTable\"\n"
             + "  sourceParameters=\"translationPrefix=&className=" + testUtils.serializeReference(
             testReference.getLocalDocumentReference()) + "\"\n"
diff --git a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java
index ebb8af055b8f3db2b5f30756d64fc51d50328ba8..03b82e55277db7faada11b83d37ad411e55dd688 100644
--- a/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java
+++ b/xwiki-platform-core/xwiki-platform-livedata/xwiki-platform-livedata-test/xwiki-platform-livedata-test-pageobjects/src/main/java/org/xwiki/livedata/test/po/LiveDataElement.java
@@ -144,6 +144,22 @@ public LiveDataElement setPagination(int paginationNumber)
         return this;
     }
 
+    /**
+     *
+     * @return the possible pagination sizes.
+     * @since 16.5.0
+     */
+    public List<Integer> getPaginationPageSizes()
+    {
+        WebElement element = getRootElement().findElement(By.cssSelector(".pagination-page-size select"));
+        return new Select(element)
+            .getOptions()
+            .stream()
+            .map(WebElement::getText)
+            .map(Integer::parseInt)
+            .collect(Collectors.toList());
+    }
+
     public void waitUntilReady()
     {
         getDriver().waitUntilCondition(input -> isVueLoaded());