# column does not exist when using or() filters

> Fix "column does not exist" and "failed to parse logic tree" errors from supabase-js or() filters, caused by embedded tables, unquoted values and wrong column names.

Source: https://supahost.app/kb/or-filter-column-error · Updated 2026-11-11

`.or()` takes a raw PostgREST filter string, not separate arguments. PostgREST reads every name in that string as a column of the table you're querying, unless it's an embedded resource listed in your `select`. Most `or()` errors come from that rule or from values that contain a comma.

## What you see

A column error (HTTP 400):

```json
{"code":"42703","details":null,"hint":null,"message":"column recipes.chefs does not exist"}
```

or a parse error:

```json
{"code":"PGRST100","details":"unexpected ...","hint":null,"message":"\"failed to parse logic tree ((title.ilike.%apple,pear%))\" (line 1, column ...)"}
```

## Why it happens

The string you pass to `.or()` becomes `or=(...)` in the URL. PostgREST splits it on commas and reads each piece as `column.operator.value`.

1. **Using an embedded table's column in the parent's `or()`.** `chefs.name.ilike.%ann%` isn't valid there: PostgREST expects an operator after `chefs`. And if `chefs` isn't embedded in your `select`, a filter like `chefs.not.is.null` is read as a column named `chefs` on `recipes`.
2. **Values with commas, dots, parentheses or colons** that aren't wrapped in double quotes. `apple,pear` is split into two filters.
3. **Filtering an aliased embed by its table name.** If you select `chef:chefs(name)`, filters must use `chef`, the alias.
4. **The column really doesn't exist**, or the name is spelled differently. `createdAt` and `created_at` are different columns.

## How to fix it

1. **Filter an embedded table with the `referencedTable` option**, which applies the `or` to that table:

   ```js
   const { data } = await supabase
     .from('recipes')
     .select('id, title, chefs(name)')
     .or('name.ilike.%ann%,name.ilike.%bo%', { referencedTable: 'chefs' })
   ```

   This filters the embedded chefs only. To keep only recipes that have a matching chef, use `chefs!inner(name)` in the select.

2. **Quote values that contain reserved characters:**

   ```js
   .or('title.eq."Apple, pear and fig tart",title.eq."Pie (classic)"')
   ```

3. **Clean user input before putting it in the string:**

   ```js
   const term = search.replace(/[",().:]/g, ' ').trim()
   const { data } = await supabase
     .from('recipes')
     .select('id, title')
     .or(`title.ilike.%${term}%,description.ilike.%${term}%`)
   ```

4. **Nest `and` inside `or`** when you need both:

   ```js
   .or(`status.eq.published,and(status.eq.draft,owner_id.eq.${userId})`)
   ```

5. **For conditions across tables** (recipe title *or* chef name), move the logic into a function and call it with RPC:

   ```sql
   create or replace function public.search_recipes(term text)
   returns setof public.recipes
   language sql stable
   set search_path = ''
   as $$
     select r.* from public.recipes r
     join public.chefs c on c.id = r.chef_id
     where r.title ilike '%' || term || '%'
        or c.name ilike '%' || term || '%';
   $$;
   ```

   ```js
   const { data } = await supabase.rpc('search_recipes', { term: 'ann' })
   ```

   The function is `security invoker` by default, so RLS on both tables still applies.

6. **Check the exact column names:**

   ```sql
   select column_name from information_schema.columns
   where table_schema = 'public' and table_name = 'recipes';
   ```

## Related

- [Querying and filtering data](/docs/api/querying)
- [Calling database functions (RPC)](/docs/api/rpc)
- [camelCase table or column names not working](/kb/camelcase-identifiers)
- [Operations too complex for the client library](/kb/complex-operations-rpc)
