Supabase ExecModule
Overview
SupabaseModule connects ValkyrAI workflows to the Supabase Data API through the native map I/O ExecModule ABI. Workflow Studio discovers it as SupabaseModule. The project URL and API credential resolve only from a READY IntegrationAccount; workflow input cannot supply either value or redirect the module to another host.
The first release implements the official n8n Supabase node's core row lifecycle and adds bounded schema discovery, provider upsert, and PostgreSQL function execution:
get_schemalist_rowsget_rowinsert_rowsupsert_rowsupdate_rowsdelete_rowscall_rpc
Read operations can retry transient failures. Every mutation runs exactly once so a timeout cannot silently duplicate a row, repeat a function side effect, or delete data twice.
Usage
- Create or select a Supabase project and expose only the database schema required by the workflow.
- Keep Row Level Security enabled and define policies for the automation identity.
- Create a ValkyrAI
IntegrationAccountwith the exacthttps://<project-ref>.supabase.coproject URL inaccountIdand the least-privilege Supabase API key in the encryptedapiKeyfield. - Set the account status to
READYand bind it throughExecModuleConfig.authConfig.integrationAccount. - Add
SupabaseModule, select an operation, and map table, filter, row, or RPC inputs from workflow state. - Prefer
upsert_rowswith a durable unique key when a workflow may be resumed after interruption.
Inputs
| Name | Type | Requirement | Default | Description and constraints |
|---|---|---|---|---|
operation | string | Required | None | One of the eight operations above. |
schema | string | Optional | public | Exposed PostgreSQL schema profile; a simple unquoted identifier. |
table | string | Row operations | None | Table or view; a simple unquoted identifier. |
function | string | call_rpc | None | PostgreSQL function exposed through /rpc; a simple unquoted identifier. |
select | string | Read operations | * | PostgREST projection, including bounded relation syntax; maximum 2,048 characters. |
filters | array or JSON string | Required for get/update/delete | None | Up to 20 {column, operator, value} objects. |
order | string | Optional list_rows | Provider default | PostgREST order expression such as created_at.desc; maximum 512 characters. |
offset | integer | Optional list_rows | 0 | Starting range offset, 0–1,000,000. |
limit | integer | Optional list_rows | 100 | Maximum returned rows, 1–10,000. |
returnAll | boolean | Optional list_rows | false | Follow range pages until exhaustion; still capped at 10,000 rows. |
rows | array or JSON string | Insert/upsert | None | 1–1,000 non-empty row objects. |
values | object or JSON string | update_rows | None | Non-empty changed-column object, no larger than 1 MiB. |
onConflict | string | upsert_rows | None | One through ten comma-separated unique-column names. |
args | object or JSON string | Optional call_rpc | {} | Named function arguments, no larger than 1 MiB. |
confirmDelete | boolean | delete_rows | false | Must be true before a delete request is sent. |
Supported filter operators are eq, neq, gt, gte, lt, lte, like, ilike, is, in, cs, cd, ov, fts, plfts, phfts, and wfts. in requires a non-empty array of at most 100 simple scalar values. Update and delete always require at least one structured filter.
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
status | string | Always | success or error. |
operation | string | Always | Normalized operation. |
attempts | integer | Always | Total provider attempts across the execution. |
httpStatus | integer | Provider responded | Last Supabase HTTP status. |
data | object | Schema, single row, or scalar RPC success | Bounded provider representation. |
items | array | List or row mutation success | Bounded rows returned by Supabase. |
count | integer | List or row mutation success | Number of emitted rows. |
hasMore | boolean | list_rows | Whether another range starts at nextOffset. |
nextOffset | integer | Another page exists | Provider row offset for a later execution. |
requestId | string | Provider supplies one | Supabase correlation ID. |
error | object | Failure | Safe {code, message, httpStatus?, retryable} details. |
The API key, Authorization header, service-role material, and unbounded provider bodies are never returned.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | Supabase |
accountId | Exact https://<project-ref>.supabase.co project URL |
apiKey | Preferred encrypted SecureField containing an anon/publishable or service-role/secret key |
password | Legacy key fallback only; prefer apiKey |
status | Must be READY |
| Authorization | Sent as both apikey and Authorization: Bearer per the Data API contract |
Use an anon or publishable key when Row Level Security policies can grant exactly the required access. A service-role or secret key bypasses RLS and should be reserved for tightly controlled server-side automation with minimum database privileges. Never put either key in workflow payloads, examples, logs, launch content, or error messages.
The custom schema must be exposed in Supabase API settings and allowed by the bound principal. Accept-Profile is used for reads and Content-Profile for writes and RPC calls.
Configuration
{
"version": "1.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:supabase-revenue-ops"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffStrategy": "EXPONENTIAL",
"initialDelayMs": 1000,
"maxDelayMs": 60000,
"jitter": false
},
"executionConfig": {"timeoutMs": 30000},
"payloadConfig": {
"parameters": "{\"operation\":\"list_rows\",\"schema\":\"public\",\"table\":\"leads\",\"select\":\"id,email,stage\",\"limit\":100}"
}
}
The account reference is illustrative. Persisted workflows use the generated IntegrationAccount relationship and SecureFields.
Operations
| Operation | Data API behavior | Side effect |
|---|---|---|
get_schema | Reads the bounded PostgREST OpenAPI description for the selected exposed schema. | Read-only; safe retries enabled. |
list_rows | Reads table/view rows with projection, structured filters, order, range, and bounded pagination. | Read-only; safe retries enabled. |
get_row | Reads the first row matching at least one structured filter. | Read-only; safe retries enabled. |
insert_rows | Inserts 1–1,000 rows and requests their representations. | Mutation; never retried automatically. |
upsert_rows | Uses on_conflict plus resolution=merge-duplicates. | Provider upsert; never retried automatically after ambiguity. |
update_rows | Patches rows matching at least one structured filter and returns them. | Mutation; never retried automatically. |
delete_rows | Permanently deletes filtered rows and returns their representations. | Destructive; requires confirmation; never retried. |
call_rpc | Invokes an exposed PostgreSQL function with named JSON arguments. | Function-defined; never retried automatically. |
Errors and Failure Modes
| Code | Typical cause | Retryable | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid project URL, key, identifier, filter, payload, or absent delete confirmation. | No | Correct the named value. No unsafe request was made. |
UNSUPPORTED_OPERATION | Unknown operation. | No | Select a documented operation. |
INTEGRATION_ACCOUNT_REQUIRED | No account is bound. | No | Bind a Supabase IntegrationAccount. |
INTEGRATION_ACCOUNT_NOT_READY | Account status is not READY. | No | Repair or reconnect the account. |
NOT_FOUND | get_row matched no visible row. | No | Verify filters, RLS, schema, and table. |
SUPABASE_HTTP_400 | PostgREST rejected a filter, projection, body, or function signature. | No | Correct the bounded provider error. |
SUPABASE_HTTP_401 | Key is invalid or expired. | No | Rotate the key in IntegrationAccount. |
SUPABASE_HTTP_403 | RLS, grants, or API exposure denies the action. | No | Fix the smallest applicable policy or grant. Do not bypass RLS casually. |
SUPABASE_HTTP_404 | Table, schema, function, or visible route is absent. | No | Verify exposure and identifiers. |
SUPABASE_HTTP_409 | Unique or concurrency conflict. | No | Re-read provider state and reconcile. |
SUPABASE_HTTP_429 | Project rate limit. | Yes for reads only | Honor Retry-After and configured backoff. |
SUPABASE_HTTP_5xx | Transient Data API failure. | Yes for reads only | Retry reads; reconcile mutations before any resubmission. |
NETWORK_ERROR | Timeout, DNS, TLS, or connectivity failure. | Yes for reads only | Verify connectivity. Treat a write timeout as ambiguous. |
RESPONSE_TOO_LARGE | Response exceeded 10 MiB. | No | Narrow the projection/filter or lower the limit. |
INVALID_PROVIDER_RESPONSE | Provider response shape did not match the selected operation. | No | Use the request ID and verify Data API compatibility. |
Example
Upsert an agent-qualified lead using the unique email column:
{
"operation": "upsert_rows",
"schema": "public",
"table": "leads",
"onConflict": "email",
"rows": [
{
"email": "buyer@example.com",
"company": "Example Industries",
"stage": "qualified",
"source": "valkyrai-research"
}
]
}
Expected result shape:
{
"status": "success",
"operation": "upsert_rows",
"httpStatus": 200,
"attempts": 1,
"count": 1,
"items": [
{
"id": 42,
"email": "buyer@example.com",
"company": "Example Industries",
"stage": "qualified",
"source": "valkyrai-research"
}
]
}
Notes
- Pagination uses HTTP
RangeandContent-Range, requests at most 1,000 rows per page, and never emits more than 10,000 rows per execution. Stableorderis strongly recommended for multi-page reads. - Only
get_schema,list_rows, andget_rowretry HTTP 408, 429, and selected 5xx responses. Integer and RFC 1123Retry-Aftervalues are honored before exponential backoff. - Insert, upsert, update, delete, and RPC requests run once. After an ambiguous timeout, query provider state using a durable business key before resubmitting.
- Supabase project quotas, PostgREST maximum-row settings, database statement timeouts, RLS, grants, triggers, and function behavior still apply.
upsert_rowsis provider-idempotent only whenonConflictidentifies a real unique constraint and the row values are safe to merge.delete_rowsis permanent and requires both at least one structured filter andconfirmDelete=true. There is no automatic compensation.call_rpccan be read-only or highly destructive depending on the function. Keep volatile or side-effecting functions narrowly granted and design them to accept caller idempotency keys where practical.- Custom Supabase domains, self-hosted Supabase, Storage, Auth, Realtime, Edge Functions, vector-search helpers, CSV transfer, logical OR/NOT filter groups, and bulk inputs above 1,000 rows are deferred.
- Functional reference: n8n's Supabase node provides row create, delete, get, get-many, and update behavior with schema selection, filters, mapping, ordering, and pagination. ValkyrAI implements those semantics through its own ABI and IntegrationAccount model, then adds explicit upsert, schema, RPC, retry, redaction, and destructive-action controls.