# Pagination Issue

**URL:** <https://community.forestadmin.com/t/pagination-issue/8282>\
**Category:** Help me!\
**Created:** [August 13, 2025, 2:49pm UTC](https://community.forestadmin.com/t/pagination-issue/8282 "2025-08-13T14:49:12Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![alfaizm](https://dub1.discourse-cdn.com/flex013/user_avatar/community.forestadmin.com/alfaizm/32/7878_2.png) [@alfaizm](https://community.forestadmin.com/u/alfaizm)\
**Post date:** [August 13, 2025, 2:49pm UTC](https://community.forestadmin.com/t/pagination-issue/8282/1 "2025-08-13T14:49:13Z")

</div>

Hi Team,

I added a custom code to display **Subscription Orders** data from my **subscriptions.orders** document using mongoose the data is rendering fine but the issue is in the pagination. Its only displaying a 25 records which I set in the code but unable to display the pagination link. Please help to resolve this. Check the attached screenshot

 ![image](https://europe1.discourse-cdn.com/flex013/uploads/forest/original/2X/0/07d121780b42e3efaf6f53cd30cfc00424581f59.png)

---

<div class="post-metadata">

**Author:** ![Alban\_Bertolini](https://dub1.discourse-cdn.com/flex013/user_avatar/community.forestadmin.com/alban_bertolini/32/1077_2.png) [@Alban\_Bertolini](https://community.forestadmin.com/u/Alban_Bertolini)\
**Post date:** [August 14, 2025, 8:37am UTC](https://community.forestadmin.com/t/pagination-issue/8282/2 "2025-08-14T08:37:53Z")

</div>

Hello,

What do you mean by “displaying a 25 records which I set in the code” ? Could you share your code?

Pagination can be configured in directly on your layout. Please follow this [documentation](https://docs.forestadmin.com/user-guide/getting-started/master-your-ui/the-table-view#pagination).

Best regards,

Alban

---

<div class="post-metadata">

**Author:** ![alfaizm](https://dub1.discourse-cdn.com/flex013/user_avatar/community.forestadmin.com/alfaizm/32/7878_2.png) [@alfaizm](https://community.forestadmin.com/u/alfaizm)\
**Post date:** [August 18, 2025, 5:14am UTC](https://community.forestadmin.com/t/pagination-issue/8282/3 "2025-08-18T05:14:50Z")

</div>

Hi,

I am using custom hosting and want to display my custom data in the layout. I have a document called **subscriptions** , which contains a column named **orders** (as an object). I want to display all the orders in a table format using Node.js. The table and pagination are working fine, and the arrows work correctly. However, the total number of records is not being displayed in the table.

Please check the screenshot. The count API is displaying {“meta”:{“count”:“deactivated”}}

 ![image](https://europe1.discourse-cdn.com/flex013/uploads/forest/original/2X/d/dba72c07406beb7f8edcc8cdb10f8fa7ee4bac7e.png)

**My Code**

```javascript
const {
  BaseCollection,
  BaseDataSource,
} = require('@forestadmin/datasource-toolkit');

const Subscriptions = require('../models/subscriptions');

class SubscriptionOrders extends BaseCollection {
  constructor(dataSource) {
    //super("subscriptionOrders", dataSource);
    super('subscriptionsOrders', dataSource, {
      capabilities: ['List', 'Count'],
    });

    this.addField('id', {
      type: 'Column',
      columnType: 'String',
      isPrimaryKey: true,
    });

    this.addField('subscriptionId', { type: 'Column', columnType: 'String' });
    this.addField('customerEmail', { type: 'Column', columnType: 'String' });
    this.addField('deliveryDate', { type: 'Column', columnType: 'String' });
    this.addField('isPlaced', { type: 'Column', columnType: 'Boolean' });
    this.addField('isSkipped', { type: 'Column', columnType: 'Boolean' });
    this.addField('shopifyOrderId', { type: 'Column', columnType: 'Number' });
    this.addField('shopifyOrderNumber', {
      type: 'Column',
      columnType: 'String',
    });
    this.addField('productTitle', { type: 'Column', columnType: 'String' });
  }

  async list(caller, filter = {}, projection) {
    console.log(filter, 'filter');

    const skip = filter.page?.skip ?? 0;
    const limit = filter.page?.limit ?? 15;
    const pageNumber = Math.floor(skip / limit) + 1;

    const pipeline = [
      { $unwind: { path: '$orders', preserveNullAndEmptyArrays: false } },
      {
        $project: {
          subscriptionId: '$_id',
          customerEmail: 1,
          productTitle: 1,
          deliveryDate: '$orders.deliveryDate',
          isPlaced: '$orders.isPlaced',
          isSkipped: '$orders.isSkipped',
          shopifyOrderId: '$orders.shopifyOrderId',
          shopifyOrderNumber: '$orders.shopifyOrderNumber',
        },
      },
      { $skip: skip },
      { $limit: limit },
    ];

    const results = await Subscriptions.aggregate(pipeline).exec();

    const records = results.map((r, idx) => ({
      id: `${r.subscriptionId}_${skip + idx}`,
      subscriptionId: r.subscriptionId?.toString() || '',
      customerEmail: r.customerEmail || '',
      //deliveryDate: r.deliveryDate || null,
      deliveryDate: r.deliveryDate || '',
      isPlaced: !!r.isPlaced,
      isSkipped: !!r.isSkipped,
      shopifyOrderId: r.shopifyOrderId || null,
      shopifyOrderNumber: r.shopifyOrderNumber || '',
      productTitle: r.productTitle || '',
    }));

    return projection.apply(records);
  }

  async count(caller, filter = {}) {
    console.log('count called with filter:', filter);

    const pipeline = [
      { $unwind: { path: '$orders', preserveNullAndEmptyArrays: false } },
      { $count: 'count' },
    ];

    const res = await Subscriptions.aggregate(pipeline).exec();

    return res?.[0]?.count ?? 0;
  }

  async aggregate() {
    const pipeline = [
      { $unwind: { path: '$orders', preserveNullAndEmptyArrays: false } },
      { $count: 'count' },
    ];

    const res = await Subscriptions.aggregate(pipeline).exec();

    return [{ result: res?.[0]?.count ?? 0 }];
  }
}

class SubscriptionOrdersDataSource extends BaseDataSource {
  constructor() {
    super();

    this.addCollection(new SubscriptionOrders(this));
  }
}

module.exports = () => new SubscriptionOrdersDataSource();

```

---

<div class="post-metadata">

**Author:** ![anon62739609](https://avatars.discourse-cdn.com/v4/letter/a/c2a13f/32.png) [@anon62739609](https://community.forestadmin.com/u/anon62739609)\
**Post date:** [August 19, 2025, 10:22am UTC](https://community.forestadmin.com/t/pagination-issue/8282/4 "2025-08-19T10:22:02Z")

</div>

You can find the relevant documentation for implementing the `count` yourself here: [Capabilities declaration | Node.js Developer Guide](https://docs.forestadmin.com/developer-guide-agents-nodejs/data-sources/custom/translation/capabilities#count)

You are just missing a `this.enableCount()` in your constructor 😉

---

<div class="post-metadata">

**Author:** ![alfaizm](https://dub1.discourse-cdn.com/flex013/user_avatar/community.forestadmin.com/alfaizm/32/7878_2.png) [@alfaizm](https://community.forestadmin.com/u/alfaizm)\
**Post date:** [August 20, 2025, 9:04am UTC](https://community.forestadmin.com/t/pagination-issue/8282/5 "2025-08-20T09:04:09Z")

</div>

Hi,

Now I am following this [documentation](https://docs.forestadmin.com/developer-guide-agents-nodejs/data-sources/custom/translation/read-only) and based on it the count value is coming but still I am getting an error like

error: [500] GET /forest/subscriptionsOrders/count - 2975ms

===== An exception was raised =====  
GET /forest/subscriptionsOrders/count?{  
fields[subscriptionsOrders]: customerEmail,deliveryDate,id,isPlaced,isSkipped,productTitle,shopifyOrderId,shopifyOrderNumber,subscriptionId,  
timezone: Asia/Calcutta  
}

Cannot convert undefined or null to object

# TypeError: Cannot convert undefined or null to object

at Function.entries ()  
at /home/alfaiz/Downloads/projects/shade-of-spring/sos-new-agent/node\_modules/@forestadminforestadmin/datasource-customizer/dist/decorators/binary/collection.js:50:36  
at Array.map ()  
at BinaryCollectionDecorator.aggregate (/home/alfaiz/Downloads/projects/shade-of-spring/sos-new-agent/no@forestadmine\_modules/@forestadmin/datasource-customizer/dist/decorators/binary/collection.js:49:31)  
at process.processTicksAndRejections (node:internal/process/task\_queues:105:5)  
at async RenameFieldCollectionDecorator.aggregate (/home/alfaiz/Downloads/projects/shade-of-spring/sos-n@forestadminw-agent/node\_modules/@forestadmin/datasource-customizer/dist/decorators/rename-field/collection.js:91:22)  
at async CountRoute.handleCount (/home/alfaiz/Downloads/projects/shade-of-s@forestadminring/sos-new-agent/node\_modules/@forestadmin/agent/dist/routes/access/count.js:22:39)  
at async ErrorHandling.errorHandler (/home/alfaiz/Downloads/projects@forestadminshade-of-spring/sos-new-agent/node\_modules/@forestadmin/agent/dist/routes/system/error-handling.js:20:13)  
at async Logger.logger (/home/alfaiz/Downloa@forestadmins/projects/shade-of-spring/sos-new-agent/node\_modules/@forestadmin/agent/dist/routes/system/logger.js:20:13)  
at async cors (/home/alf@koaiz/Downloads/projects/shade-of-spring/sos-new-agent/node\_modules/@koa/cors/index.js:109:16)

H@forestadminre is my code

```javascript
const {
  BaseCollection,
  BaseDataSource,
} = require('@forestadmin/datasource-toolkit');
const Subscriptions = require('../models/subscriptions');

class SubscriptionOrders extends BaseCollection {
  constructor(dataSource) {
    //super("subscriptionOrders", dataSource);super("subscriptionsOrders", dataSource, {capabilities: ["List", "Count"],});

    this.enableCount();
    this.enableSearch();

    this.addField('id', {
      type: 'Column',
      columnType: 'String',
      isPrimaryKey: true,
    });
    this.addField('subscriptionId', { type: 'Column', columnType: 'String' });
    this.addField('customerEmail', { type: 'Column', columnType: 'String' });
    this.addField('deliveryDate', { type: 'Column', columnType: 'String' });
    this.addField('isPlaced', { type: 'Column', columnType: 'Boolean' });
    this.addField('isSkipped', { type: 'Column', columnType: 'Boolean' });
    this.addField('shopifyOrderId', { type: 'Column', columnType: 'Number' });
    this.addField('shopifyOrderNumber', {
      type: 'Column',
      columnType: 'String',
    });
    this.addField('productTitle', { type: 'Column', columnType: 'String' });
  }

  async list(caller, filter = {}, projection) {
    console.log(filter, 'filter');
    const skip = filter.page?.skip ?? 0;
    const limit = filter.page?.limit ?? 15;
    const pageNumber = Math.floor(skip / limit) + 1;

    const pipeline = [
      { $unwind: { path: '$orders', preserveNullAndEmptyArrays: false } },
      {
        $project: {
          subscriptionId: '$_id',
          customerEmail: 1,
          productTitle: 1,
          deliveryDate: '$orders.deliveryDate',
          isPlaced: '$orders.isPlaced',
          isSkipped: '$orders.isSkipped',
          shopifyOrderId: '$orders.shopifyOrderId',
          shopifyOrderNumber: '$orders.shopifyOrderNumber',
        },
      },
      { $skip: skip },
      { $limit: limit },
    ];

    const results = await Subscriptions.aggregate(pipeline).exec();

    const records = results.map((r, idx) => ({
      id: `${r.subscriptionId}_${skip + idx}`,
      subscriptionId: r.subscriptionId?.toString() || '',
      customerEmail: r.customerEmail || '',
      //deliveryDate: r.deliveryDate || null,
      deliveryDate: r.deliveryDate || '',
      isPlaced: !!r.isPlaced,
      isSkipped: !!r.isSkipped,
      shopifyOrderId: r.shopifyOrderId || null,
      shopifyOrderNumber: r.shopifyOrderNumber || '',
      productTitle: r.productTitle || '',
    }));

    //return projection.apply(records);
    return projection ? projection.apply(records) : records;
  }

  async count(caller, filter = {}) {
    console.log('count called with filter:', filter);

    const pipeline = [
      { $unwind: { path: '$orders', preserveNullAndEmptyArrays: false } },
      { $count: 'count' },
    ];

    const res = await Subscriptions.aggregate(pipeline).exec();
    return res?.[0]?.count ?? 0; // Forest expects just a number
  }

  async aggregate(caller, filter = {}, aggregation = {}, limit) {
    const { operation, fields, groups = null } = aggregation || {};
    console.log('aggregate called:', aggregation);

    // Handle COUNT
    if (operation === 'Count' && groups.length === 0 && !fields) {
      const count = await this.count(caller, filter);
      console.log(count, 'my count result');
      return [{ count }]; // ✅ Forest expects { count: <number> }
    }

    // Fallback pipeline if needed
    const pipeline = [
      { $unwind: { path: '$orders', preserveNullAndEmptyArrays: false } },
      { $count: 'count' },
    ];
    const res = await Subscriptions.aggregate(pipeline).exec();
    return [{ count: res?.[0]?.count ?? 0 }];
  }
}

class SubscriptionOrdersDataSource extends BaseDataSource {
  constructor() {
    super();
    this.addCollection(new SubscriptionOrders(this));
  }
}

module.exports = () => new SubscriptionOrdersDataSource();

```

---

<div class="post-metadata">

**Author:** ![anon62739609](https://avatars.discourse-cdn.com/v4/letter/a/c2a13f/32.png) [@anon62739609](https://community.forestadmin.com/u/anon62739609)\
**Post date:** [August 21, 2025, 10:24am UTC](https://community.forestadmin.com/t/pagination-issue/8282/6 "2025-08-21T10:24:48Z")

</div>

In your aggregate function, try the following:

```javascript
return [{ count, group: {} }]

```

---

<div class="post-metadata">

**Author:** ![alfaizm](https://dub1.discourse-cdn.com/flex013/user_avatar/community.forestadmin.com/alfaizm/32/7878_2.png) [@alfaizm](https://community.forestadmin.com/u/alfaizm)\
**Post date:** [August 23, 2025, 12:43pm UTC](https://community.forestadmin.com/t/pagination-issue/8282/7 "2025-08-23T12:43:23Z")

</div>

The pagination issue has been resolved. Now I want to display **Filter** option beside the search bar which is not currently displaying. Please check the attached screenshot and code

 ![image](https://europe1.discourse-cdn.com/flex013/uploads/forest/original/2X/b/bfe0265016d8d6049f18126e3efcfc2a3e9fdc09.png)

const {  
BaseCollection,  
BaseDataSource,  
} = require(“@forestadmin/datasource-toolkit”);  
const Subscriptions = require(“../models/subscriptions”);

class SubscriptionOrders extends BaseCollection {  
constructor(dataSource) {  
//super(“subscriptionOrders”, dataSource);  
super(“subscriptionsOrders”, dataSource, {  
capabilities: [“List”, “Count”, “Filter”, “Search”, “Sort”],  
});

```javascript
this.enableCount();
this.enableSearch();

this.addField("id", {
  type: "Column",
  columnType: "String",
  isPrimaryKey: true,
});
this.addField("subscriptionId", { type: "Column", columnType: "String" });
this.addField("customerEmail", { type: "Column", columnType: "String" });
this.addField("deliveryDate", { type: "Column", columnType: "String" });
this.addField("isPlaced", { type: "Column", columnType: "Boolean" });
this.addField("isSkipped", { type: "Column", columnType: "Boolean" });
this.addField("shopifyOrderId", { type: "Column", columnType: "Number" });
this.addField("shopifyOrderNumber", {
  type: "Column",
  columnType: "String",
});
this.addField("productTitle", { type: "Column", columnType: "String" });

```

}

async list(caller, filter = {}, projection) {  
[//console.log](https://console.log)(filter, “filter”);

```javascript
const skip = filter.page?.skip ?? 0;
const limit = filter.page?.limit ?? 15;
const pageNumber = Math.floor(skip / limit) + 1;

const pipeline = [
  { $unwind: { path: "$orders", preserveNullAndEmptyArrays: false } },
  {
    $project: {
      subscriptionId: "$_id",
      customerEmail: 1,
      productTitle: 1,
      deliveryDate: "$orders.deliveryDate",
      isPlaced: "$orders.isPlaced",
      isSkipped: "$orders.isSkipped",
      shopifyOrderId: "$orders.shopifyOrderId",
      shopifyOrderNumber: "$orders.shopifyOrderNumber",
    },
  },
  { $skip: skip },
  { $limit: limit },
];

const results = await Subscriptions.aggregate(pipeline).exec();

const records = results.map((r, idx) => ({
  id: `${r.subscriptionId}_${skip + idx}`,
  subscriptionId: r.subscriptionId?.toString() || "",
  customerEmail: r.customerEmail || "",
  //deliveryDate: r.deliveryDate || null,
  deliveryDate: r.deliveryDate || "",
  isPlaced: !!r.isPlaced,
  isSkipped: !!r.isSkipped,
  shopifyOrderId: r.shopifyOrderId || null,
  shopifyOrderNumber: r.shopifyOrderNumber || "",
  productTitle: r.productTitle || "",
}));

//return projection.apply(records);
return projection ? projection.apply(records) : records;

```

}

async count(caller, filter = {}) {  
console.log(“count called with filter:”, filter);

```javascript
const pipeline = [
  { $unwind: { path: "$orders", preserveNullAndEmptyArrays: false } },
  { $count: "count" },
];

const res = await Subscriptions.aggregate(pipeline).exec();
return res?.[0]?.count ?? 0; // Forest expects just a number

```

}

async aggregate(caller, filter = {}, aggregation = {}, limit) {  
const { operation, fields, groups = } = aggregation || {};  
console.log(“aggregate called:”, aggregation);

```javascript
// Handle COUNT
if (operation === "Count" && groups.length === 0 && !fields) {
  const count = await this.count(caller, filter);
  console.log(count, "my count result");
  //return [{ count }]; // ✅ Forest expects { count: <number> }
  return [{ value: count, group: {} }];
}

// Fallback pipeline if needed
const pipeline = [
  { $unwind: { path: "$orders", preserveNullAndEmptyArrays: false } },
  { $count: "count" },
];
const res = await Subscriptions.aggregate(pipeline).exec();
return [{ count: res?.[0]?.count ?? 0 }];

```

}  
}

class SubscriptionOrdersDataSource extends BaseDataSource {  
constructor() {  
super();  
this.addCollection(new SubscriptionOrders(this));  
}  
}

module.exports = () =\> new SubscriptionOrdersDataSource();

---

<div class="post-metadata">

**Author:** ![anon62739609](https://avatars.discourse-cdn.com/v4/letter/a/c2a13f/32.png) [@anon62739609](https://community.forestadmin.com/u/anon62739609)\
**Post date:** [August 25, 2025, 10:08am UTC](https://community.forestadmin.com/t/pagination-issue/8282/8 "2025-08-25T10:08:47Z")

</div>

Hi @alfaizm,

If you continue to look the documentation I send you you will see it’’s talking about the filter, you can find it here: [Capabilities declaration | Node.js Developer Guide](https://docs.forestadmin.com/developer-guide-agents-nodejs/data-sources/custom/translation/capabilities#filtering-operators)

---

<div class="post-metadata">

**Author:** ![alfaizm](https://dub1.discourse-cdn.com/flex013/user_avatar/community.forestadmin.com/alfaizm/32/7878_2.png) [@alfaizm](https://community.forestadmin.com/u/alfaizm)\
**Post date:** [August 27, 2025, 10:58am UTC](https://community.forestadmin.com/t/pagination-issue/8282/9 "2025-08-27T10:58:36Z")

</div>

Hi @anon62739609

I checked the documentation and tried implementing it in different ways, but I’m still unable to see the filter option in the UI. Please schedule a call to look into this issue.

---

<div class="post-metadata">

**Author:** ![anon62739609](https://avatars.discourse-cdn.com/v4/letter/a/c2a13f/32.png) [@anon62739609](https://community.forestadmin.com/u/anon62739609)\
**Post date:** [August 27, 2025, 1:02pm UTC](https://community.forestadmin.com/t/pagination-issue/8282/10 "2025-08-27T13:02:14Z")

</div>

Could you please share your code ?

What’s the name of your project ?
