Skip to content

docs(realtime): add MRE demonstrating JSONB filter limitation and col…#1802

Open
yash07-bit wants to merge 5 commits intosupabase:mainfrom
yash07-bit:fix/realtime-jsonb-filter-mre
Open

docs(realtime): add MRE demonstrating JSONB filter limitation and col…#1802
yash07-bit wants to merge 5 commits intosupabase:mainfrom
yash07-bit:fix/realtime-jsonb-filter-mre

Conversation

@yash07-bit
Copy link
Copy Markdown

What kind of change does this PR introduce?

Bug fix + docs/example update.

What is the current behavior?

Supabase Realtime postgres_changes filtering does not work with JSONB expressions like data->>organization_id=eq.value.
When the demo runs before the migration is applied, it also fails with a confusing schema error instead of guiding the user.

What is the new behavior?

This PR adds a minimal reproducible example showing the limitation and the production-safe workaround:

  • Uses a dedicated organization_id column for Realtime filtering
  • Keeps the JSONB data column for flexible payload storage
  • Syncs organization_id from JSONB via a trigger
  • Adds a clear demo script with structured logs
  • Adds graceful setup checks and helpful error messages when the migration is missing
  • Updates the README and expected output for a PR-ready MRE

Additional context

This change is intended as a professional GitHub PR example for the Supabase Realtime repo.
It demonstrates both:

  • the unsupported JSONB filter behavior
  • the supported direct column filter behavior

Copilot AI review requested due to automatic review settings April 8, 2026 03:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a minimal reproducible example (MRE) to the repo documenting Supabase Realtime’s limitation with JSONB-expression filters and demonstrating the recommended “dedicated scalar column + trigger sync” workaround, plus a top-level README link to the example.

Changes:

  • Add a new example (examples/realtime-jsonb-filter-mre/) with a migration, demo runner, subscription client, and expected output.
  • Document the JSONB filter limitation and the recommended workaround pattern in the example README.
  • Link the example from the root README and ignore the example’s local .env file.

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
README.md Adds an “Examples” section linking to the new MRE.
.gitignore Ignores examples/realtime-jsonb-filter-mre/.env.
examples/realtime-jsonb-filter-mre/subscription-client.mjs Creates a Supabase client and a filtered postgres_changes subscription using a scalar organization_id column.
examples/realtime-jsonb-filter-mre/run-demo.mjs Demo script that checks setup, subscribes, inserts a row, and reports whether events were received.
examples/realtime-jsonb-filter-mre/README.md Documents the limitation, setup steps, and the workaround pattern.
examples/realtime-jsonb-filter-mre/package.json Adds runnable Node demo with Supabase JS + dotenv dependencies.
examples/realtime-jsonb-filter-mre/package-lock.json Lockfile for the example’s Node dependencies.
examples/realtime-jsonb-filter-mre/migration.sql Creates schema/table, adds organization_id, trigger sync, index, RLS, and publication wiring.
examples/realtime-jsonb-filter-mre/expected-output.txt Provides expected demo output for a successful run.
examples/realtime-jsonb-filter-mre/.env.example Template env file for SUPABASE_URL / SUPABASE_ANON_KEY.
Files not reviewed (1)
  • examples/realtime-jsonb-filter-mre/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +84 to +90
console.log(`[INSERT] Row created with ID: ${inserted.id}`)
console.log(`[INSERT] organization_id auto-filled: ${inserted.organization_id}`)

if (!inserted || inserted.organization_id !== ORG_ID) {
throw new Error(
`[ERROR] Trigger sync failed: expected ${ORG_ID}, got ${inserted?.organization_id ?? 'null'}`
)
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inserted is dereferenced (inserted.id / inserted.organization_id) before the !inserted guard runs. If PostgREST returns an empty data array (e.g., due to RLS denying select on the inserted row), this will throw a TypeError and hide the real problem. Move the null/empty check before the logs and consider using .select(...).single() (or equivalent) so the insert either returns one row or errors explicitly.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +59
-- Replace with your JWT structure if needed
drop policy if exists "org based access" on pgboss.job;

create policy "org based access"
on pgboss.job
for select
using (
organization_id = auth.jwt() ->> 'organization_id'
);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The for select RLS policy requires a JWT claim (auth.jwt()->>'organization_id'), but the demo client uses SUPABASE_ANON_KEY and never authenticates, so select will typically be denied and Realtime events won’t be delivered (and inserts may not return rows on .select(...)). For the MRE to work out-of-the-box, either relax/selectively scope the demo select policy (e.g., allow anon/authenticated), or update the demo to sign in / supply a JWT that includes the organization_id claim and document that requirement.

Suggested change
-- Replace with your JWT structure if needed
drop policy if exists "org based access" on pgboss.job;
create policy "org based access"
on pgboss.job
for select
using (
organization_id = auth.jwt() ->> 'organization_id'
);
-- Demo-safe select policy so the MRE works with SUPABASE_ANON_KEY
-- without requiring a JWT that includes organization_id.
drop policy if exists "org based access" on pgboss.job;
create policy "org based access"
on pgboss.job
for select
to anon, authenticated
using (true);

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +57
4. **Expected output:**
```
[SETUP] Supabase Realtime JSONB Filter MRE
[SUBSCRIBED] Ready to receive events
[INSERT] Creating job with JSONB data...
[WAIT] Waiting 8s for realtime event...
[subscription] event received: { ... }
[RESULT] ✅ PASS: Realtime event received with direct column filter
```
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected output snippet doesn’t match what run-demo.mjs prints: the script logs [DEBUG] SUPABASE_URL = ..., the filter line, and a subscription status line before [SUBSCRIBED]. Update this snippet (and/or the script) so the README’s “Expected output” reflects the actual demo logs.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment thread examples/realtime-jsonb-filter-mre/expected-output.txt
Comment thread examples/realtime-jsonb-filter-mre/package.json Outdated
yash07-bit and others added 2 commits April 8, 2026 12:47
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants