Search docs.molo17.com
    Configuration guide Connectors

    Set up a Supabase target and replicate PostgreSQL to it

    A Supabase project is a PostgreSQL database, and the Gluesync Supabase agent writes to it over the PostgreSQL wire protocol, with TLS on. Most of the work is on the Supabase side: a database role that can write your tables and, for bulk load, create one helper schema. This guide creates that role, connects Gluesync, lets Gluesync create the target tables, runs a snapshot and live changes from PostgreSQL, turns on bulk load, and shows the errors you meet when a privilege is missing and how to fix each one.

    In this guide

    You will learn how to create a least-privilege database role for Gluesync, choose between the direct connection and the session pooler, create the Supabase target connection, let Gluesync create the target tables and fix the Missing required field error of the table dialog, run a snapshot and CDC, check inserts, updates and deletes, turn on bulk load and know what it changes on a Supabase project, and recover from the connection and permission errors that customers meet most often. A data type table and a symptom, cause and fix table close the guide.

    Quick information

    Goal

    Replicate PostgreSQL tables to a Supabase project with a snapshot and change data capture (CDC), using a role that has only the privileges Gluesync needs

    Audience

    Data engineers and Supabase project owners who receive data from Gluesync

    Level

    Intermediate

    Time required

    About 45 minutes

    Area

    Supabase → SQL editor; Control Plane → Data pipelines → Supabase target; entity Edit settings → Fine tuning

    Applies to

    Gluesync 2.3

    Before you begin

    Make sure that:

    1. You have a Supabase project, hosted on supabase.com or self-hosted, with Supabase Postgres 15 or later, and you can run SQL as the postgres role (the SQL editor of the dashboard is enough). This guide was tested against self-hosted Supabase Postgres 17.6 that accepts TLS connections only, like a hosted project with Enforce SSL on;

    2. You have a source that Core Hub can read. This guide uses PostgreSQL 16 with WAL CDC, prepared as in PostgreSQL CDC with WAL. Any other source works the same way on the Supabase side. Supabase is a target only;

    3. Core Hub can reach the Supabase host on port 5432. The direct connection resolves to an IPv6 address unless the project has the IPv4 add-on. If Core Hub has no IPv6 route, use the session pooler;

    4. You can sign in to the Control Plane with a role that can create pipelines.

    The Supabase agent is the PostgreSQL target with Supabase’s defaults: database postgres, user postgres, TLS on. The type mapping, the write paths and the bulk COPY path are the PostgreSQL target’s. Read the Supabase agent page for every option. This guide does not repeat it.

    Guide steps

    Optional: create the example source tables

    SQL

    Skip this step if you already have your own tables. On the source PostgreSQL database, run:

    CREATE SCHEMA shop;
    
    CREATE TABLE shop.customers (
      id integer PRIMARY KEY, name varchar(100), notes text, ref uuid,
      payload jsonb, tags text[], credit numeric(12,2),
      created_at timestamp, seen_at timestamptz, active boolean);
    INSERT INTO shop.customers VALUES
      (1,'Acme Spa','first','2f8c7d2e-6b1a-4c5e-9d0f-1a2b3c4d5e6f','{"tier":"gold"}',
       '{a,b}',1500.50,'2026-10-28 10:00:00','2026-10-28 10:00:00+02',true),
      (2,'Beta Srl',NULL,NULL,NULL,NULL,NULL,NULL,NULL,false);
    
    CREATE TABLE shop.orders (id integer PRIMARY KEY, customer_id integer, total numeric(12,2), status varchar(20));
    INSERT INTO shop.orders
      SELECT g, 1 + g % 2, g * 10.5, 'new' FROM generate_series(1, 1000) g;
    Expected result

    SELECT count(*) FROM shop.customers returns 2 and SELECT count(*) FROM shop.orders returns 1000. The Supabase project has no table with these names yet: step 5 creates them from the Control Plane.

    Create a database role for Gluesync

    SQL

    You can connect Gluesync as postgres. A dedicated role is safer, because a leaked password then gives access to the tables Gluesync writes and nothing else. Each privilege below is needed for one thing.

    1. In the SQL editor of the Supabase dashboard, or with psql as postgres, create the role:

      CREATE ROLE gluesync_writer LOGIN PASSWORD '<a strong password>' NOINHERIT;
    2. Let it create the tables that Gluesync creates for you (step 5) in the schema public:

      GRANT USAGE, CREATE ON SCHEMA public TO gluesync_writer;
    3. Let it create the helper schema for bulk load (step 8). Gluesync keeps its staging tables in a schema named GLUESYNC, in capital letters, and creates it the first time an entity uses bulk load:

      GRANT CREATE ON DATABASE postgres TO gluesync_writer;

      If your policy does not allow CREATE on the database, create the schema yourself and give the role the right to use it and to create staging tables in it. The agent used the schema in a test, with no other grant:

      CREATE SCHEMA "GLUESYNC";
      GRANT USAGE, CREATE ON SCHEMA "GLUESYNC" TO gluesync_writer;
    4. If the tables already exist and you do not want Gluesync to create them, grant the privileges on each one instead of CREATE on the schema. TRUNCATE is needed only for the option Empty the table first in step 6:

      GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON public.customers TO gluesync_writer;
    Expected result

    SELECT rolname, rolsuper FROM pg_roles WHERE rolname = 'gluesync_writer' returns the role with rolsuper set to f. The role is not a superuser, like postgres on Supabase.

    A table that gluesync_writer creates belongs to that role. The postgres role of the dashboard cannot change it: in a test, a TRUNCATE run as postgres returned permission denied for table orders. Run GRANT gluesync_writer TO postgres; to let postgres act as the owner, or create the tables yourself as postgres and grant the role on them.

    Choose the connection type and collect the details

    UI

    In the Supabase dashboard, select Connect and copy the host of the connection you will use.

    Connection Host and port User When to use it

    Direct connection

    db.<project-ref>.supabase.co, port 5432

    The role from step 2

    Recommended. IPv6 only, unless the project has the IPv4 add-on

    Session pooler

    aws-0-<region>.pooler.supabase.com, port 5432

    gluesync_writer.<project-ref>: the pooler needs the project reference after the user name

    Core Hub has no IPv6 route

    Transaction pooler

    Port 6543

    Not supported

    The write path uses prepared statements, which the transaction pooler does not support

    The database name is postgres. For a self-hosted project, use the host and port of your Postgres container or proxy.

    Expected result

    You have the host, port 5432, database postgres, the user name for your connection type and the password of the role.

    Create the pipeline and the Supabase connection

    UI

    1. In the Control Plane, select Create your first pipeline, or the + button of the pipeline list. The Agents setup wizard opens;

    2. In 1. Setup source, search for postgres and select the PostgreSQL CDC agent tile. The connection form opens. Enter the Host name / Address, the Port, the Database name, the Username of a user with the CDC grants and the Password. Select Test and continue, then Next;

    3. In 2. Setup target(s), search for supabase and select the Supabase agent tile. The connection form opens with Port 5432, Database name postgres and Username postgres already filled in;

    4. Enter the Host name / Address. Change Username to gluesync_writer (1), or to the pooler form of step 3, and enter the Password;

    5. Under Security, keep Enable TLS and Trust server certificate on (2). Both are on by default. Select Test and continue.

    Supabase target connection form with the user gluesync_writer and a masked password marked 1
    Figure 1. Annotation 1 is the database user and password, and 2 the two TLS switches, on by default.
    1. Select Next, enter a Pipeline name and select Finish.

    Expected result

    After the test, the target shows a green check and the label gluesync_writer@<host>:5432. The connection was made with the role of step 2, over TLS. The pipeline opens with the message No entities found.

    The TLS switches behave as follows:

    • Enable TLS is on by default. In a test with the switch off, the connection to a server that accepts TLS only was still made, and it was encrypted;

    • With Trust server certificate on, the connection is encrypted and the certificate is not checked. With it off, you must upload the CA certificate of the project as the trust store (Upload files or Use a path). Without it, the test fails with Failed to save and Could not open SSL root certificate file.

    Add the entities and let Gluesync create the tables

    UI

    1. In the pipeline, select Entities (the blue button with the plus sign). The Add entities page opens with the Object browser;

    2. Expand the source schema, for example shop, and select the check box of customers. The Field mapping dialog opens;

    3. In Target Schema select public. Select the Target Table field and type customers. Select Create table "customers". The banner New table customers will be created on confirm. appears and every source column gets a proposed type;

    4. Look at the Length column. The cells of notes, ref and payload (text, uuid and jsonb) show 0 with a red mark, the banner says Missing required field and Confirm is disabled.

    Field mapping dialog creating the table customers
    Figure 2. The length cells of text, uuid and jsonb are required (1), so the dialog cannot be confirmed (2).
    1. Select each of the three cells and type -1, then press Tab. The type stays text, uuid and jsonb, and the error clears. The Error tab reads Error (0);

    2. Select Confirm. A second dialog, Create table customers, shows the exact statement (1). Read it, then select Create table customers (2). Select Copy snippet (3) instead if your database administrator must run the statement. Both dialogs close and customers appears under Selected entities.

    Create table customers dialog with the read-only CREATE TABLE statement for public.customers marked 1
    Figure 3. Review the generated CREATE TABLE (1), create the table (2) or copy the statement (3).
    1. Select the check box of orders and repeat. Its columns have lengths, so the dialog has no errors. Then select Confirm and continue.

    Expected result

    Both entities are saved and are on Hold. In Supabase, \d public.customers shows id integer (primary key), name character varying(100), notes text, ref uuid, payload jsonb, tags text[], credit numeric(12,2), created_at timestamp(6) without time zone, seen_at timestamp(6) with time zone and active boolean, and the owner is gluesync_writer.

    Gluesync creates the table with the role of the connection, in the schema you chose. A table created this way has row level security off, grants nothing to the Supabase API roles anon and authenticated, and is not in the supabase_realtime publication. Turn on row level security, add policies and grants, or add the table to the publication with your own migration, as for any table.

    Run the snapshot and start CDC

    UI

    1. In the entity list, select the play button of shop.customers. The Sync changes (CDC) dialog opens;

    2. Keep Full copy and Keep and merge. Full copy copies all rows and then follows the live changes. Keep and merge overwrites rows that have the same key and leaves the others. Choose Empty the table first only if the target table must contain the source rows and nothing else: it runs a TRUNCATE, needs that privilege and asks you to confirm with I understand rows in the target will be deleted;

    3. Select Start entity. Do the same for shop.orders.

    Expected result

    Both entities move to Active. In Supabase, SELECT count(*) FROM public.customers returns 2 and SELECT count(*) FROM public.orders returns 1000. The row of Beta Srl has NULL in every column except id, name and active. The timestamp 2026-10-28 10:00:00+02 of Acme Spa is stored as 2026-10-28 08:00:00+00: Supabase prints a timestamptz in the time zone of the session, which is UTC in a default project.

    Check inserts, updates and deletes

    SQL + UI

    1. On the source, change the data:

      INSERT INTO shop.customers (id, name, notes, payload, tags, credit, seen_at, active)
      VALUES (3, 'Gamma', 'n3', '{"a":1}', '{x,y,z}', 99.99, now(), true);
      UPDATE shop.customers SET name = 'Acme SpA', credit = 2000 WHERE id = 1;
      DELETE FROM shop.customers WHERE id = 2;
    2. After a few seconds, read the target:

      SELECT id, name, credit, tags FROM public.customers ORDER BY id;
    3. In the entity list, select the arrow at the end of the row of shop.orders and then Count both. The page counts the rows of the source and of the target.

    Expected result

    The query returns two rows: 1, Acme SpA, 2000.00, {a,b} and 3, Gamma, 99.99, {x,y,z}. The insert, the update and the delete all arrived, and the array stays an array. Count both shows 1,000 on both sides and 0 over Match. The page also warns that counts keep moving while CDC delivers changes.

    Turn on bulk load

    UI + SQL

    Bulk load stages the rows in a table of the GLUESYNC schema with COPY and merges them into the target table in one statement. It is the fast path for large snapshots and for busy CDC.

    1. Open the entity menu (the three dots on its row) and select Edit settings, then Fine tuning;

    2. Under Bulk load settings, select Bulk for CDC and Bulk for Snapshot. Close the dialog and select Confirm and continue to save the entity;

    3. In Supabase, list the staging tables:

      SELECT schemaname, tablename FROM pg_tables WHERE schemaname = 'GLUESYNC';
    4. On the source, update three rows of orders and delete one, then select Count both on the entity page. The entity keeps running: you do not restart it.

    Expected result

    The query returns one staging table for each entity that uses bulk load, named like pu_ord_ followed by the entity id, owned by gluesync_writer. The entity page shows the BULK LOAD badge, and Count both returns 999 on both sides after the changes.

    Entity page of shop.orders with the BULK LOAD badge marked 1 and 999 rows on both source and target with 0 Match
    Figure 4. The badge (1) shows bulk load, and the counts (2) match.

    Bulk load applies the changes of a batch to the target table in a different way from the plain write path, which matters if the table has triggers, cascading foreign keys or Realtime subscribers. Read Updates with bulk operations during CDC on the agent page before you turn it on for CDC on such a table.

    If the role cannot create the GLUESYNC schema, saving an entity with bulk load on fails with the banner The entity could not be saved. and ERROR: permission denied for database postgres. Grant CREATE on the database or create the schema yourself, as in step 2, and select Confirm and continue again. If the GLUESYNC schema is dropped later, the entity does not stop: the Core Hub log shows a warning, Bulk insert failed …​ falling back to the batch write, with schema "GLUESYNC" does not exist, and the rows are written without bulk load. To bring bulk load back, switch it off, save, switch it on and save again: that recreates the staging table.

    Recover from a missing table privilege

    UI + SQL

    A table that belongs to another role fails on the first write. You can reproduce it and fix it without restarting anything.

    1. In Supabase, as postgres, make postgres the owner of the table orders. Gluesync then writes a table that it does not own and has no privilege on:

      GRANT gluesync_writer TO postgres;
      ALTER TABLE public.orders OWNER TO postgres;
      REVOKE gluesync_writer FROM postgres;
    2. On the source, update a row of orders;

    3. Open the entity shop.orders. The status is Error, the banner reads Target write failed and the message names the SQLSTATE and the table.

    Entity page of shop.orders in Error with the message SQL Error Code 0 State 42501 permission denied for table orders
    Figure 5. The entity is in error (1) because the role cannot write the table (2).
    1. Grant the privileges to the role of the connection:

      GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON public.orders TO gluesync_writer;
    Expected result

    After the grant the entity retries on its own. Within about a minute it is back to Active and the row that failed arrives, without a restart and without a new snapshot.

    The same message, permission denied for table, appears at the start of a snapshot with Empty the table first when the role has no TRUNCATE on the table: the entity goes to Error and the Core Hub log shows that it was running the truncate.

    Data types

    The mapping is the PostgreSQL target’s. When Gluesync creates the table from a PostgreSQL source, it proposes these types. They were checked in the test of this guide.

    PostgreSQL source Type created in Supabase Note

    integer, boolean

    integer, boolean

    Values written unchanged

    varchar(n)

    character varying(n)

    n must cover the longest value

    text, uuid, jsonb

    text, uuid, jsonb

    Set Length to -1 in the dialog (step 5). jsonb is stored as {"tier": "gold"}: PostgreSQL adds the space

    numeric(p,s)

    numeric(p,s)

    Precision and scale kept

    timestamp

    timestamp(6) without time zone

    Microseconds kept

    timestamptz

    timestamp(6) with time zone

    Shown in the time zone of the session

    Arrays such as text[]

    text[]

    The array stays an array

    The agent also writes enum types, money, interval, network address types, geometric types, range and multirange types, bit strings and the types of extensions: vector, geometry, geography, raster and hstore. These are covered by the integration test suite as column values and as primary keys, in snapshot and CDC. The full list is in Data types on the agent page. A numeric value of NaN cannot be written, because the Gluesync decimal type has no such value.

    Tables with row level security are written. The postgres role bypasses row level security on Supabase. A connection role without the BYPASSRLS attribute follows the policies of the table, and needs policies that let it INSERT, UPDATE, DELETE and SELECT. See Row level security.

    Verify the result

    You have set up the Supabase target if:

    • The role gluesync_writer is not a superuser and has CREATE on the schema public, and on the database or on a GLUESYNC schema you created;

    • The target connection tests green over TLS, on port 5432;

    • No Length cell in the Field mapping dialog shows 0, and the Error tab reads Error (0);

    • A snapshot copied every row, and an insert, an update and a delete on the source reached Supabase;

    • Count both shows the same number of rows on both sides, as in Find and fix row differences between source and target;

    • If bulk load is on, the staging tables are in the GLUESYNC schema and you have read the effects of bulk load on triggers and Realtime.

    Troubleshooting

    Problem Possible cause What to do

    Failed to save with Failed to initialize pool: FATAL: password authentication failed for user "…​"

    Wrong password or user name. On the session pooler, the user is missing the project reference

    Check the role and password in Supabase. On the pooler, use <role>.<project-ref> as the user name (step 3)

    Failed to save with Failed to initialize pool: FATAL: database "…​" does not exist

    The database name is not postgres

    Enter postgres in Database name

    Failed to save with Failed to initialize pool: The connection attempt failed.

    The host cannot be reached from Core Hub. The direct connection is IPv6 only without the IPv4 add-on

    Check the host name and the network route. Use the session pooler if Core Hub has no IPv6 route

    Failed to save with Failed to initialize pool: Connection to <host>:<port> refused

    Nothing listens on that port, for example a typo or a closed firewall

    Use port 5432. Do not use 6543: the transaction pooler is not supported

    Failed to save with Failed to initialize pool: Could not open SSL root certificate file …​

    Trust server certificate is off and no trust store is set

    Upload the CA certificate of the project as the trust store, or turn the switch on if encryption without a check is enough

    Missing required field in the Field mapping dialog, with 0 in Length

    The proposal for text, uuid and jsonb has no length

    Type -1 in Length, as in step 5

    The entity could not be saved. with ERROR: permission denied for database postgres when you turn bulk load on

    The role cannot create the GLUESYNC schema

    GRANT CREATE ON DATABASE postgres TO <role>, or create the schema and grant USAGE, CREATE on it (step 2)

    Entity in Error with SQL Error: Code 0, State 42501, Message ERROR: permission denied for table …​

    The role does not own the table and has no privilege on it, or has no TRUNCATE for Empty the table first

    GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON <table> TO <role>. The entity recovers without a restart (step 9)

    Bulk insert failed …​ falling back to the batch write and schema "GLUESYNC" does not exist in the Core Hub log

    The GLUESYNC schema was dropped. The entity keeps running without bulk load

    Switch bulk load off, save, switch it on and save again (step 8)

    The postgres role gets permission denied on a table that Gluesync created

    The table belongs to the Gluesync role

    GRANT <role> TO postgres, or create the tables yourself as postgres and grant the Gluesync role on them

    Contact MOLO17 Support if you need assistance with a Supabase target.