> For the complete documentation index, see [llms.txt](https://inistate.gitbook.io/home/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://inistate.gitbook.io/home/advanced/module-script-filtering.md).

# Module Script Filtering

## MCP Filter — Scripting Reference

### Overview

The `search` method on `ModuleScripting` accepts MCP-style filters — a human-readable filter format that uses **display names** and natural operator names instead of raw MongoDB queries.

Internally, filters are transformed through the `McpFilterTransformer` → `QueryFilterProcessor` pipeline into MongoDB queries.

```python
# MCP filter (search) — uses display names + operators
Module('Customer').search({ "Status": "Active" })

# Raw MongoDB filter (findAll) — uses internal field paths
Module('Customer').findAll({ "$and": [{ "payload.f_abc123": "Active" }] })
```

Both approaches query the same data. Use `search` when you want readable, maintainable filters. Use `findAll` when you need raw MongoDB control.

***

### API

#### `search(filters, includes?, sort?, direction?, take?)`

Returns a list of `FETIASEntity` matching the MCP filter.

| Parameter   | Type               | Default      | Description                       |
| ----------- | ------------------ | ------------ | --------------------------------- |
| `filters`   | JObject            | *(required)* | MCP-style filter object           |
| `includes`  | string\[]          | `null`       | Fields to include (display names) |
| `sort`      | string             | `null`       | Field display name to sort by     |
| `direction` | QuerySortDirection | `Ascending`  | Sort direction                    |
| `take`      | int                | `0`          | Max results (0 = unlimited)       |

```python
results = Module('Customer').search(
    { "Status": "Active", "Score": { "min": 80 } },
    sort="Score",
    direction=QuerySortDirection.Descending,
    take=10
)
```

***

### Filter Format

#### Simple equality

Pass a plain value to match exactly:

```json
{ "Priority": "High" }
{ "Score": 95 }
{ "state": "Open" }
```

#### Operator objects

Pass an object with operator keys for advanced matching:

```json
{ "Title": { "contains": "report" } }
{ "Score": { "min": 80, "max": 100 } }
{ "DueDate": { "after": "2026-01-01" } }
```

#### Multiple fields (implicit AND)

Multiple fields in the same object are AND-ed together:

```json
{
  "Priority": "High",
  "Status": "Active",
  "Score": { "min": 80 }
}
```

#### Explicit AND/OR grouping

Use `and`/`or` arrays for logical grouping. Nesting is unlimited.

```json
{ "or": [{ "Status": "Active" }, { "Priority": "High" }] }
```

```json
{
  "and": [
    { "state": "Open" },
    { "or": [
      { "Priority": "High" },
      { "Score": { "min": 90 } }
    ]}
  ]
}
```

***

### Operator Reference

#### Text operators

| Operator     | Aliases                                       | Description                        |
| ------------ | --------------------------------------------- | ---------------------------------- |
| `is`         | `equal`, `equals`, `eq`                       | Exact match                        |
| `not`        | `isNot`, `ne`, `neq`, `notEqual`, `notEquals` | Not equal                          |
| `contains`   |                                               | Substring match (case-insensitive) |
| `startsWith` |                                               | Starts with value                  |
| `endsWith`   |                                               | Ends with value                    |
| `excludes`   | `notContains`, `doesNotContain`               | Does not contain substring         |

```json
{ "Title": { "contains": "report" } }
{ "Name": { "startsWith": "A" } }
{ "Status": { "not": "Cancelled" } }
{ "Notes": { "excludes": "draft" } }
```

#### Number operators

| Operator  | Aliases                                                                   | Description                           |
| --------- | ------------------------------------------------------------------------- | ------------------------------------- |
| `min`     | `gte`, `minimum`, `atLeast`, `from`, `greaterThanEqual`, `greaterOrEqual` | Greater than or equal (>=)            |
| `above`   | `gt`, `moreThan`, `greaterThan`                                           | Greater than (>)                      |
| `max`     | `lte`, `maximum`, `atMost`, `to`, `lessThanEqual`, `lessOrEqual`          | Less than or equal (<=)               |
| `below`   | `lt`, `under`, `lessThan`                                                 | Less than (<)                         |
| `between` | `range`                                                                   | Range — pass `{ "min": N, "max": N }` |

```json
{ "Score": { "min": 80 } }
{ "Amount": { "below": 1000 } }
{ "Rating": { "between": { "min": 1, "max": 5 } } }
```

Combine `min` and `max` on the same field for a range:

```json
{ "Amount": { "min": 1000, "max": 5000 } }
```

#### Date operators

| Operator   | Aliases                   | Description                          |
| ---------- | ------------------------- | ------------------------------------ |
| `after`    | `since`, `from_date`      | On or after date (>=)                |
| `before`   | `until`, `to_date`        | On or before date (<=)               |
| `within`   | `around`                  | Current time window (e.g. this week) |
| `upcoming` | `in`, `next`, `coming`    | Next N time units from now           |
| `past`     | `ago`, `previous`, `last` | Previous N time units from now       |

Date range with ISO 8601 strings:

```json
{ "DueDate": { "after": "2026-01-01", "before": "2026-12-31" } }
```

Relative date operators use `{ "duration": "day"|"week"|"month", "offset": N }`:

```json
{ "DueDate": { "upcoming": { "duration": "day", "offset": 7 } } }
{ "CreatedDate": { "past": { "duration": "month", "offset": 3 } } }
{ "UpdatedDate": { "within": { "duration": "week", "offset": 1 } } }
```

#### User operators

Pass `"me"` as a simple string value on any user field (standard or custom):

```json
{ "assignee": "me" }
{ "createdBy": "me" }
{ "updatedBy": "me" }
{ "Reviewer": "me" }
```

The operator object form `{ "me": true }` also works:

```json
{ "Reviewer": { "me": true } }
```

| Syntax           | Aliases                 | Description                         |
| ---------------- | ----------------------- | ----------------------------------- |
| `"me"` (string)  |                         | Shorthand — works on any user field |
| `{ "me": true }` | `myself`, `currentUser` | Operator object form                |

#### Null checks

| Operator | Aliases                                                                          | Description            |
| -------- | -------------------------------------------------------------------------------- | ---------------------- |
| `empty`  | `none`, `null`, `isNull`, `isEmpty`, `blank`, `isBlank`, `isNone`                | Field is null or empty |
| `exists` | `any`, `notNull`, `isNotNull`, `notEmpty`, `isNotEmpty`, `hasValue`, `isNotNone` | Field has a value      |

```json
{ "Notes": { "empty": true } }
{ "Assignee": { "exists": true } }
```

#### Boolean (YesNo) operators

| Operator | Aliases            | Description    |
| -------- | ------------------ | -------------- |
| `yes`    | `true`, `isTrue`   | Field is true  |
| `no`     | `false`, `isFalse` | Field is false |

```json
{ "Active": { "yes": true } }
{ "Archived": { "no": true } }
```

#### Table/List field operators

Filter rows within a Table or List field using the `rows` array. Each element is a sub-field condition using the same syntax as top-level filters.

```json
{
  "Lines": {
    "rows": [
      { "Item": { "contains": "Widget" } },
      { "Quantity": { "min": 5 } }
    ]
  }
}
```

All sub-field conditions are AND-ed — matching rows must satisfy every condition.

| Syntax                                      | Description                                  |
| ------------------------------------------- | -------------------------------------------- |
| `{ "rows": [...] }`                         | Match rows where all sub-conditions are true |
| `{ "rows": [...], "operator": "excludes" }` | Exclude entries where any row matches        |
| `{ "empty": true }`                         | Table/list is empty                          |
| `{ "exists": true }`                        | Table/list has at least one row              |

***

### Standard Fields

Standard fields can be used alongside custom module fields. They are automatically resolved to internal field IDs.

| Field name      | Maps to        | Description                              |
| --------------- | -------------- | ---------------------------------------- |
| `state`         | state          | Entry state                              |
| `documentId`    | documentId     | Document ID                              |
| `createdBy`     | createdBy      | Creator username (supports `"me"`)       |
| `assignee`      | assignTo       | Assigned user (supports `"me"`)          |
| `updatedBy`     | lastChangeBy   | Last modifier username (supports `"me"`) |
| `createdDate`   | createdDate    | Creation date                            |
| `updatedDate`   | lastChangeDate | Last modification date                   |
| `due`           | due            | Due date                                 |
| `date`          | date           | Entry date                               |
| `search`        | documentId     | Text search on document ID               |
| `createdAfter`  | createdDate    | Alias for `createdDate` >=               |
| `createdBefore` | createdDate    | Alias for `createdDate` <=               |
| `updatedAfter`  | lastChangeDate | Alias for `updatedDate` >=               |
| `updatedBefore` | lastChangeDate | Alias for `updatedDate` <=               |

```json
{ "state": "Open", "assignee": "me" }
{ "createdBy": "me" }
{ "createdDate": { "after": "2026-01-01" } }
```

***

### Examples

#### 1. Simple equality

```python
Module('Customer').search({ "Status": "Active" })
```

#### 2. Text search with number range

```python
Module('PurchaseOrder').search({
    "Vendor": { "contains": "Acme" },
    "Amount": { "min": 1000, "max": 5000 }
})
```

#### 3. Date range on module field

```python
Module('Leave').search({
    "Start Date": { "after": "2026-03-01", "before": "2026-03-31" }
})
```

#### 4. Current user's tasks due soon

```python
Module('Task').search({
    "assignee": "me",
    "due": { "upcoming": { "duration": "day", "offset": 7 } }
})
```

#### 5. OR across states

```python
Module('Leave').search({
    "or": [
        { "state": "Approved" },
        { "state": "Submitted" }
    ]
})
```

#### 6. Nested AND/OR

```python
Module('PurchaseOrder').search({
    "and": [
        { "state": "Open" },
        { "or": [
            { "Priority": "High" },
            { "Amount": { "min": 10000 } }
        ]}
    ]
})
```

#### 7. Table row matching

```python
Module('PurchaseOrder').search({
    "Lines": {
        "rows": [
            { "Item": { "contains": "Widget" } },
            { "Quantity": { "min": 5 } }
        ]
    }
})
```

#### 8. Exclude rows + null check

```python
Module('PurchaseOrder').search({
    "Lines": {
        "rows": [{ "Quantity": { "min": 10 } }],
        "operator": "excludes"
    },
    "Approval Note": { "empty": true }
})
```

#### 9. Boolean + text exclusion

```python
Module('PurchaseOrder').search({
    "Urgent": { "yes": true },
    "Category": { "not": "Internal" }
})
```

#### 10. Entries updated in the past 3 months

```python
Module('Customer').search({
    "updatedDate": { "past": { "duration": "month", "offset": 3 } }
})
```

#### 11. With sort, direction, and limit

```python
results = Module('Customer').search(
    { "Status": "Active" },
    sort="Score",
    direction=QuerySortDirection.Descending,
    take=20
)
```

***

### Notes

* All operator names are **case-insensitive**. Use whichever alias feels most natural.
* Custom fields are referenced by **display name** (e.g. `"Priority"`, `"Start Date"`).
* Standard fields (e.g. `"state"`, `"assignee"`) are resolved automatically.
* The `search` method always returns `List<FETIASEntity>`.
* Use `findAll` / `find` / `count` / `iterate` for raw MongoDB filters when needed.
