ash-project / ash-project/ash

Proposal: Add action-level `filter_fields` for read actions

Open
#2,589 2 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Elixir
Stars
2.5k
Forks
422
Avg merge
23h 26m
Merged PRs (30d)
46

Description

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

### AI Policy

- [x] I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.

### Is your feature request related to a problem? Please describe.

Different interface libraries (ash_graphql, ash_ai, ...) each need to control which fields are filterable when exposing read actions. Currently there's no standard way to do this at the Ash core level, which forces each interface to implement its own solution independently.

For example:
- `ash_graphql` has `filterable_fields` at the GraphQL type level to control which attributes are exposed as filter arguments
- `ash_ai` exposes read actions as MCP/LLM tools and needs the same kind of control — certain fields shouldn't be offered as filter options to an LLM

The core problem is that filter field control is fundamentally about "how should this action expose its data" — which is an action-level concern, not an interface-level concern. Having each interface reinvent this leads to duplicated configuration, inconsistency between interfaces, and the risk of accidentally exposing fields in one interface that were intentionally hidden in another.

### Describe the solution you'd like

A `filter_fields` option on read actions that declares which fields are available for filtering:

```elixir
read :list_public do
filter_fields [:name, :bio, genre: [:eq, :in]]
end

read :list_admin do
# all fields filterable (default behavior, no restriction)
end
```

Interface libraries would respect this as the baseline — a field not in `filter_fields` cannot be filtered through any interface. Interfaces could then optionally restrict further (e.g. hiding a field from GraphQL but keeping it available in MCP), but could never expand beyond what the action allows.

This gives action authors a single place to declare the filtering contract, and every interface inherits it automatically.

### Describe alternatives you've considered

1. **Attribute-level `filterable?: false`** — Already exists in Ash for some contexts. Too coarse: it's global across all actions and interfaces. "This field should never be filterable anywhere" is a valid but narrow use case. Doesn't help when you want different filter sets for `:list_public` vs `:list_admin`.

2. **Interface-level configuration (current state)** — Each interface defines its own filter control (e.g. ash_graphql's `filterable_fields`). Works but leads to scattered configuration, duplication, and potential inconsistency. If you add a new interface, you have to remember to configure filtering there too.

### Additional context

_No response_

Contributor guide

Open the contributing guide

Research direction

Start by tracing Ash core read-action configuration and its existing filtering behavior, then compare the interface-level controls described for ash_graphql and ash_ai. Done means a read action can declare allowed filter fields, with interfaces honoring that baseline without exposing fields the action excludes.

Written by the indexing model from the issue text.

Assessment

Tech stack
elixir
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.