# Query results

Retrieve filtered data with items(). Control result ordering with sort, limit data with pagination (start/count), and reduce payload size with projection.

## Syntax

```typescript
list.catalog.query.items({
  sort?: { attributes: string[], order: ('ASC' | 'DESC')[] },
  start?: number,
  count?: number,
  projection?: { [field: string]: boolean }
})
```

## Parameters

- `sort.attributes` (`string[]`, optional) — Fields to sort by (in priority order)
- `sort.order` (`('ASC' | 'DESC')[]`, optional) — Sort direction for each attribute (uppercase; other values are ignored with a warning and the result stays unsorted)
- `start` (`number`, optional) — Zero-based offset for pagination
- `count` (`number`, optional) — Maximum items to return
- `projection` (`object`, optional) — { field: true } to return only these fields, { $: true, field: false } to exclude fields. A nested object resolves a reference: { $: true, customer: { name: true } }

## Return type

Returns an array of items matching the query with applied options.

```typescript
// list.catalog.query
items<T>(options?: DcuplListQueryOptions<T>): T[]; // matching items with the requested fields
```

## Usage notes

- 'ASC' sorts ascending (A-Z, 0-9, oldest first), 'DESC' descending; items without a value sort last in both directions
- Projection (key is always included): { field: true } only this field, { $: true } all fields, { $: true, field: false } all but this one, { $: true, ref: { $: true } } all fields with this reference resolved
- Without count, returns ALL matching items
- Total number of matches (ignoring start/count): list.catalog.fn.metadata().currentSize
- Always sort before paginating for consistent results
- Sort attributes and order arrays must have matching lengths; order values are 'ASC' or 'DESC'
- String sorting is case-insensitive
- Projection with true includes only specified fields (plus key)
- Use { $: true, field: false } to exclude specific fields
- Sort attributes are properties of the item: a dotted attribute such as 'customer.name' is ignored without an error. To sort by a referenced value, add a derived property
- sort.script runs a custom sort script instead (see Scripts)
- Conditions passed in items({ queries }) are applied on top of the list query (AND), they do not replace it. Such a call ignores the start, count, sort and projection applied with applyOptions(): pass them in the same items() call
- Other ways to read results: first(), any(), count(), one({ itemKey }) and many({ itemKeys }) (Catalog API). one() and many() only return items that pass the list query
- Page formula: start = (pageNumber - 1) \* pageSize
- query.count() counts what items() returns, so a count applied with applyOptions() caps it; use fn.metadata().currentSize for the total

## Related methods

- `catalog.fn.facets()` — Get value counts for filter UIs
- `catalog.fn.aggregate()` — Get totals without fetching items

## Related guides/docs

- [Basic filtering](/sdk/queries/query-filter)
- [Catalog API: first(), any(), count(), one(), many()](/sdk/api/catalog#query-operations)
- [Scripts (sort scripts)](/sdk/guides/scripts)
- [Common mistakes](/sdk/guides/common-mistakes)

## Examples

**Basic result retrieval**

Get all items matching current query filters.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

productList.catalog.query.addCondition({
  attribute: 'status',
  operator: 'eq',
  value: 'active',
});

// Get all matching items
const results = productList.catalog.query.items();
console.log('Total items:', results.length);
```

**Pagination with count and start**

Retrieve a specific page of results.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Page 1: items 0-19
const page1 = productList.catalog.query.items({
  start: 0,
  count: 20,
});

// Page 2: items 20-39
const page2 = productList.catalog.query.items({
  start: 20,
  count: 20,
});

// Page calculation helper
function getPage(pageNumber: number, pageSize: number) {
  return productList.catalog.query.items({
    start: (pageNumber - 1) * pageSize,
    count: pageSize,
  });
}
```

**Single property sort**

Sort results by one field.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Sort by price ascending (lowest first)
const cheapest = productList.catalog.query.items({
  sort: {
    attributes: ['price'],
    order: ['ASC'],
  },
});

// Sort by price descending (highest first)
const expensive = productList.catalog.query.items({
  sort: {
    attributes: ['price'],
    order: ['DESC'],
  },
});
```

**Multi-field sort**

Sort by multiple fields with different directions.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Sort by category (A-Z), then price (high to low) within category
const results = productList.catalog.query.items({
  sort: {
    attributes: ['category', 'price'],
    order: ['ASC', 'DESC'],
  },
});

// Featured first, then by rating, then alphabetically
const featured = productList.catalog.query.items({
  sort: {
    attributes: ['featured', 'rating', 'name'],
    order: ['DESC', 'DESC', 'ASC'],
  },
});
```

**Sort with pagination**

Always sort before paginating for consistent results.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// IMPORTANT: Sort must be consistent across pages
const pageSize = 20;
const currentPage = 1;

const results = productList.catalog.query.items({
  sort: {
    attributes: ['createdAt'],
    order: ['DESC'],
  },
  start: (currentPage - 1) * pageSize,
  count: pageSize,
});

// Get total for pagination UI (all matching items, not just this page)
const totalItems = productList.catalog.fn.metadata().currentSize;
const totalPages = Math.ceil(totalItems / pageSize);
```

**Field projection**

Select only specific fields to return.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

// Only return key, name, price, and thumbnail (reduces payload)
const results = productList.catalog.query.items({
  projection: {
    name: true,
    price: true,
    thumbnail: true,
    // All other fields excluded; key is always included
  },
});

// Exclude specific fields
const withoutDescription = productList.catalog.query.items({
  projection: {
    $: true,             // Include all fields...
    description: false,  // ...except this large text field
  },
});
```

**Complete e-commerce listing**

Full example with filters, sort, pagination, and projection.

```typescript
const productList = dcupl.lists.create({ modelKey: 'Product' });

function getProductListing(options: {
  category?: string;
  minPrice?: number;
  maxPrice?: number;
  sortBy: 'price' | 'rating' | 'name';
  sortDir: 'ASC' | 'DESC';
  page: number;
  pageSize: number;
}) {
  const query = productList.catalog.query;
  query.clear();

  // Apply filters
  if (options.category) {
    query.addCondition({
      attribute: 'category',
      operator: 'eq',
      value: options.category,
    });
  }

  // Price range: an 'and' group (two addCondition calls on 'price' would be ORed)
  query.addGroup({
    groupKey: 'price',
    groupType: 'and',
    queries: [
      { attribute: 'price', operator: 'gte', value: options.minPrice ?? 0 },
      { attribute: 'price', operator: 'lte', value: options.maxPrice ?? Number.MAX_SAFE_INTEGER },
    ],
  });

  // Active products only
  query.addCondition({
    attribute: 'status',
    operator: 'eq',
    value: 'active',
  });

  // Sort, paginate, and project
  query.applyOptions({
    sort: {
      attributes: [options.sortBy],
      order: [options.sortDir],
    },
    start: (options.page - 1) * options.pageSize,
    count: options.pageSize,
    projection: {
      name: true,
      price: true,
      thumbnail: true,
      rating: true,
      category: true,
    },
  });
  const results = query.items();

  // Total count for pagination
  const totalItems = productList.catalog.fn.metadata().currentSize;

  return {
    items: results,
    page: options.page,
    pageSize: options.pageSize,
    totalItems,
    totalPages: Math.ceil(totalItems / options.pageSize),
  };
}
```

**Sort by a referenced property**

Sort attributes are read from the item itself, so pull the referenced value in with a derived property first.

```typescript
// A dotted path like 'customer.name' is not a valid sort attribute.
// Derive the customer's name onto the order instead:
dcupl.models.set({
  key: 'Order',
  properties: [
    { key: 'total', type: 'int' },
    {
      key: 'customerName',
      type: 'string',
      derive: { localReference: 'customer', remoteProperty: 'name' },
    },
  ],
  references: [{ key: 'customer', type: 'singleValued', model: 'Customer' }],
});

// ...after dcupl.init():
const orderList = dcupl.lists.create({ modelKey: 'Order' });

// Sort orders by customer name, then by total
const results = orderList.catalog.query.items({
  sort: {
    attributes: ['customerName', 'total'],
    order: ['ASC', 'DESC'],
  },
});
```

**SDK**

```typescript
// Get sorted, paginated results
const list = dcupl.lists.create({ modelKey: 'Article' });

// Apply filter
list.catalog.query.addCondition({
  attribute: 'category',
  operator: 'eq',
  value: 'Running',
});

// Get page 1 with sort
const results = list.catalog.query.items({
  sort: {
    attributes: ['price'],
    order: ['ASC']
  },
  start: 0,
  count: 10
});

// Get total count for pagination
const total = list.catalog.fn.metadata().currentSize;
console.log('Showing', results.length, 'of', total);
```
