> ## Documentation Index
> Fetch the complete documentation index at: https://learn.nextedy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Side Filtering

> How Nextedy POWERSHEET runs query filters on the server while expanding, so a sheet loads only the slice of Siemens Polarion ALM data it needs instead of fetching everything and filtering in the browser.

export const LastReviewed = ({date}) => {
  if (!date) return null;
  const formatted = new Date(`${date}T00:00:00Z`).toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric",
    timeZone: "UTC"
  });
  return <p className="mt-10 text-sm text-gray-400 dark:text-zinc-500 not-prose">
      Last reviewed on {formatted}
    </p>;
};

A Powersheet filter runs on the server while the query executes -- not in the browser after the data has arrived. Every level of a sheet, the root query and each expanded level, can carry a `where` that narrows what that level returns, so only the matching slice is fetched. On a large dataset this is the difference between a responsive sheet and one that stalls while loading everything before it can show anything.

## Server-side filtering, not client-side filtering

The distinction matters as data grows:

* **Client-side filtering** loads the full result set and then hides rows. The server still reads and transfers everything.
* **Server-side filtering** injects the condition into the query `where` *before* it runs. Polarion returns only the rows that match, so the large dataset is never fetched -- the sheet stays fast and the server stays light.

For example, on a requirements traceability sheet you can show every Chapter and Requirement but only the Electrical design requirements beneath them: the non-Electrical rows are never pulled to the browser at all.

## Filter the root query or any expand level

Add a `query.where` to the level you want to slice. Levels without one behave exactly as before:

```yaml theme={null}
sources:
  - id: rtm
    query:
      from: Chapter
      where:
        title: { contains: Draft }   # (optional) root-level filter
    expand:
      - name: requirements
        expand:
          - name: requirement
            query:
              where:
                type: Electrical      # subquery: only Electrical children load
```

A subquery `where` applies to **its own level only** -- ancestor rows are unaffected -- and works on every relationship type, scalar (N:1, 1:1) and collection (1:N, M:N) alike. For a many-to-many chain, put the `where` on the leaf level (for example `requirements.requirement`), not the junction. Multiple keys in one `where` are combined with AND, and filters can be nested at several levels, each scoping its own. For the full schema of an expand node's `where`, see the [expand clause reference](/powersheet/reference/query-api/expand-clause#expand-subquery-filtering); for the operator vocabulary (`eq`, `contains` as a whole-word token match, `ne`, `in`, `and` / `or`), see [Predicates](/powersheet/reference/query-api/predicates).

## The filter value can be static or come from the URL

A filter condition does **not** have to reference a URL parameter. The value can be a literal (`type: Electrical`), pinning the slice directly in the configuration. A [URL parameter](/powersheet/concepts/url-parameters) is simply one way to *supply* that value at load time: swap the literal for a `() =>` [dynamic expression](/powersheet/concepts/dynamic-expressions) that reads `context.parameters`, and one configuration serves many slices instead of one config per slice.

```yaml theme={null}
query:
  where:
    type: "() => context.parameters.domain"   # from ?domain=…
```

Now `?domain=HW` and `?domain=SW` on the same sheet return different slices, with no duplicate configurations. A static condition and a parameter-driven one can sit side by side -- the fixed part always applies, the dynamic part varies with the URL:

```yaml theme={null}
query:
  where:
    and:
      - postmitigationAP: { in: [M, H] }                          # always applied
      - postmitigationAP: { eq: "() => context.parameters.pmap" } # from ?pmap=
```

See [URL Parameters](/powersheet/concepts/url-parameters) for how values reach `context.parameters`, and [Open a Scoped Sheet with URL Parameters](/powersheet/guides/sheet-configuration/parametrize-sheet-url) for the end-to-end task.

## When a filter value is missing

Filtering degrades optimistically rather than failing:

* A `where` condition whose value resolves to nothing has that single condition **dropped**, and the level loads unfiltered.
* An [`applyCurrentDocumentTo`](/powersheet/reference/query-api/document-filtering) scope resolved from a missing value is likewise **removed**, so the sheet loads unscoped rather than empty.

A malformed `() => …` expression is different: it fails loudly during query construction instead of silently producing a filterless query.

## Pickers follow the filter

A subquery `where` on an expand level also constrains that level's **reference picker**: only candidates that satisfy the same filter are offered, so a value you pick will not vanish on the next load -- what you save is what you get. For a many-to-many chain the picker carries the leaf level's filter (for example `requirement`), not the junction level, since the junction filter cannot apply to a candidate that is not linked yet. Where a target type also has a data-model [pick constraint](/powersheet/guides/customization/configure-picker-filters), the two combine with AND -- a candidate must satisfy both.

## New rows inherit the filter

<Note title="New rows satisfy the filter they were created under">
  Creating an item at a filtered level pre-fills the fields the filter pins by **equality**, so the new row satisfies the filter it was created under instead of disappearing on the next load. Only equality seeds a default (`type: Electrical` or `type: { eq: … }`) -- fuzzy operators (`contains`, `in`, `ne`, `or`) pin no single value and seed nothing. An explicit [`entityFactory`](/powersheet/reference/sheet-config/sources) default always wins over a filter-derived one, and a subquery scoped to the current document also creates the new item in that document.
</Note>

## Related

<Columns cols={2}>
  <Card title="URL Parameters" icon="link" href="/powersheet/concepts/url-parameters">
    Where filter values can come from: named values read from the sheet's URL into `context.parameters`.
  </Card>

  <Card title="Open a Scoped Sheet with URL Parameters" icon="wrench" href="/powersheet/guides/sheet-configuration/parametrize-sheet-url">
    Step-by-step: filter the root query and an expanded level, then drive them from a shareable URL.
  </Card>

  <Card title="Expand Clause" icon="list" href="/powersheet/reference/query-api/expand-clause">
    The expand node schema, including the `query.where` subquery filter and its picker behaviour.
  </Card>

  <Card title="Document Filtering" icon="file" href="/powersheet/reference/query-api/document-filtering">
    `applyCurrentDocumentTo` -- scoping a level to the current LiveDoc on the server.
  </Card>
</Columns>

<LastReviewed date="2026-07-27" />
