n8n-io/n8n · error
Supabase upsert failed: ${error.message}
Error message
Supabase upsert failed: ${error.message} What it means
Thrown by SupabaseVectorStore.upsert when the PostgREST upsert call returns a non-null `error` object. The underlying message from the Supabase/PostgREST client is appended, so the root cause is typically RLS denial, a missing table, a schema/column mismatch, a unique-constraint violation not handled by onConflict, or a network/auth problem. This is a runtime/backend error, not input validation.
Source
Thrown at packages/@n8n/agents/src/vector-stores/supabase.ts:122
super(name, options);
this.tableName = options.tableName;
this.queryName = options.queryName ?? 'match_documents';
}
async upsert(records: VectorRecord[]): Promise<void> {
if (records.length === 0) return;
const client = await this.getClient();
const { error } = await client.from(this.tableName).upsert(
records.map((record) => ({
id: record.id,
content: record.content,
metadata: record.metadata,
embedding: record.vector,
})),
{ onConflict: 'id' },
);
if (error) throw new Error(`Supabase upsert failed: ${error.message}`);
}
async query(
vector: number[],
opts: { topK: number; filter?: VectorFilter },
): Promise<VectorQueryResult[]> {
const client = await this.getClient();
const rpcCall = client.rpc<string, MatchDocumentsFn>(this.queryName, {
query_embedding: vector,
});
const filtered =
opts.filter && opts.filter.conditions.length > 0
? applySupabaseFilter(rpcCall, opts.filter)
: rpcCall;
const { data, error } = await filtered.limit(opts.topK);
if (error) throw new Error(`Supabase query failed: ${error.message}`);
View on GitHub (pinned to 5ac6606e81)
Solutions
- Read the appended PostgREST message first — it names the concrete failure (e.g. 'relation does not exist', 'permission denied', 'violates unique constraint').
- For RLS, use the service role key for trusted backend writes, or add an INSERT/UPDATE policy for the table.
- Verify the table exists with columns id, content, metadata (jsonb), embedding (vector(n)) matching the embedded dimension.
- For transient/network errors, retry with backoff.
Example fix
// before — anon key blocked by RLS
new SupabaseVectorStore('docs', {
url, apiKey: process.env.SUPABASE_ANON_KEY, tableName: 'docs',
});
// after — service role key for trusted backend writes
new SupabaseVectorStore('docs', {
url, apiKey: process.env.SUPABASE_SERVICE_ROLE_KEY, tableName: 'docs',
}); Defensive patterns
Strategy: retry
Validate before calling
function assertSupabaseWritable(opts: { url: string; apiKey: string; tableName: string }): void {
if (!/^https?:\/\/.+/.test(opts.url)) throw new Error('Supabase url missing/invalid');
if (!opts.apiKey) throw new Error('Supabase apiKey missing (use service role key for writes)');
if (!opts.tableName) throw new Error('Supabase tableName missing');
}
assertSupabaseWritable(opts);
new SupabaseVectorStore('docs', opts); Type guard
function looksLikeServiceRoleKey(key: string): boolean {
// Supabase service role keys are long JWTs; anon keys are too, so this is a heuristic —
// the real test is whether writes succeed (RLS rejects anon-role writes).
return /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(key);
} Try / catch
async function upsertWithRetry(store: SupabaseVectorStore, records: VectorRecord[], attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
await store.upsert(records);
return;
} catch (err) {
const msg = err instanceof Error ? err.message : '';
const transient = /network|timeout|fetch|ECONN|503|504|paused/i.test(msg);
if (!transient || i === attempts - 1) throw err;
await new Promise((r) => setTimeout(r, 2 ** i * 200));
}
}
} Prevention
- Use the service role key for trusted backend writes to bypass RLS; never rely on the anon key for writes.
- Define an INSERT/UPDATE RLS policy if you must use a non-service role.
- Verify the table schema (id, content, metadata jsonb, embedding vector(n)) before deploying.
- Retry only transient/network errors; surface RLS/schema errors to the operator immediately.
When it happens
Trigger: RLS policy blocking inserts/updates for the api key's role; tableName pointing at a non-existent table; embedding column missing or wrong type; metadata column not jsonb; onConflict:'id' failing because `id` is not the primary key; service role key expired/wrong; Supabase project paused/unreachable.
Common situations: Using the anon key instead of the service role key for writes; RLS enabled without a permissive policy for the backend role; migrating the table schema and forgetting the embedding column; Supabase free-tier project auto-pausing; network egress blocked.
Related errors
- Supabase query failed: ${error.message}
- Supabase delete failed: ${error.message}
- Filter operator "${operator}" on key "${key}" requires a non
- Database connection timed out
- Error: ${errorMessage}
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/e08e7c1fb6e4d9e3.
Report an issue: GitHub.