Browse 354 rules, 42 knowledge articles, and 28 prompt templates across security, performance, architecture, and quality.
BeforeMerge scans your pull requests against these rules automatically. Get actionable feedback before code ships to production.
354 rules
Registering CPTs on plugins_loaded, enqueueing scripts on init, or running admin-only code on every request wastes resources and causes subtle bugs.
Hardcoded URLs and filesystem paths break across environments (local/staging/prod), subdirectory installs, multisite, and custom wp-content directories.
String interpolation in .rpc() calls or custom PostgreSQL functions allows attackers to inject arbitrary SQL. Always use parameterized queries. [CWE-89 · A03:2021]
createServiceRoleClient() bypasses ALL RLS policies. Using it in request handlers lets any authenticated user access or modify all data. [CWE-269 · A04:2021]
Logging OAuth tokens, API keys, passwords, or PII exposes secrets in log aggregation services and crash reporters. Use scoped loggers with sanitization. [CWE-532 · A09:2021]
Tables with RLS enabled but missing policies for certain operations silently deny access. Ensure every table has policies for SELECT, INSERT, UPDATE, and DELETE. [CWE-862 · A01:2021]
Every table must have Row Level Security enabled with at least one policy per operation. Tables without RLS are accessible to any authenticated user. [CWE-862 · A01:2021]
Schema changes via Supabase MCP, SQL editor, or dashboard don't create migration files. This causes schema drift between environments.
getSession() reads from cookies and can be spoofed. getUser() verifies the token with the Supabase Auth server, making it tamper-proof. [CWE-287 · A07:2021]
Using NEXT_PUBLIC_ prefix on SUPABASE_SERVICE_ROLE_KEY or DATABASE_URL embeds secrets into client-side JavaScript bundles, bypassing all RLS. [CWE-798 · A07:2021]
Using 'as' type assertions on external input (params, form data, request bodies) provides zero runtime safety. Use Zod for runtime validation. [CWE-20 · A03:2021]
Destructuring { data } without checking { error } from Supabase queries ignores failures silently. When error is non-null, data is always null. [CWE-252]
Treating all Supabase errors the same (if error, throw) hides whether a record is missing or the query itself failed. Check error codes for proper handling.
Using .select('*') fetches all columns including large text/json fields, wastes bandwidth, leaks data shape, and prevents index-only scans.
Fetching parent records then looping to fetch children creates N+1 HTTP requests. Use Supabase nested .select('*, children(*)') to resolve in a single query. [CWE-400]
Filtering or ordering on unindexed columns causes full table scans. RLS policy columns like user_id and org_id especially need indexes. [CWE-405]
Using .range() offset pagination for large datasets forces PostgreSQL to scan all skipped rows. Use cursor-based pagination with .gt()/.lt() for constant-time page fetches.
Each serverless invocation opening a direct database connection exhausts PostgreSQL's connection limit. Use Supavisor pooler URLs for all serverless environments.
Inserting or updating rows one at a time in a loop creates N HTTP requests. Use .insert([...]) or .upsert([...]) to batch into a single request.
Hand-writing TypeScript interfaces for database tables leads to drift between code and schema. Use supabase gen types to generate types automatically.
Migration files without proper structure (table, indexes, RLS, policies, comments) are harder to review and prone to missing critical steps like RLS.
Using the wrong Supabase client for the context breaks RLS, leaks auth state, or causes hydration errors. Match client type to Next.js rendering context.
Spreading or Object.assign-ing untrusted user input into objects can pollute Object.prototype and lead to security bypasses. [CWE-1321]
Math.random() is not cryptographically secure. Use crypto.randomUUID() or crypto.getRandomValues() for tokens, IDs, and security-sensitive values. [CWE-338]