configureTargetSchema, configureTargetTables), and lifecycle handlers (onRowUpsert, onRowDelete, onWrite) that allow you to define custom logic for specific tables and schemas.
The includes example scenarios that further demonstrate how to use this API. Be sure to follow userscript best practices.
Access this API
To access the userscript API, . The userscript API is accessible via thereplicator library, which you should import at the top of your TypeScript file: import * as api from "replicator@v2";. The replicator library is included in the MOLT Replicator binary itself, so you do not need to install any external packages in order to run userscripts.
In addition to importing the API from the replicator library, you can download the userscript type definitions file and the tsconfig.json file. Place these files in your working directory to enable autocomplete, inline documentation, and real-time error detection directly in your IDE.
Userscript functions list
configureTargetSchema(targetSchemaName, handlers)configureTargetTables(tableNames, configuration)console
Common TypeScript definitions
The following TypeScript definitions are referenced throughout the API and describe the object structures you will deal with when writing userscript handler functions.configureTargetSchema(targetSchemaName, handlers)
configureTargetSchema registers schema-level handlers that run . Use this to transform, filter, or reroute rows broadly across a target database schema.
TypeScript Signature
Row and Metadata are defined in Common TypeScript definitions.
Arguments
targetSchemaName: The (case-sensitive) name of the schema in the target database.handlers: An object containing anonRowUpsertand anonRowDeletehandler function. Both handlers must be defined.
configureTargetSchema functions in your userscript, each with a different targetSchemaName. This enables you to define different functionality for different schemas within the same userscript.
Schema-level onRowUpsert(row, metadata)
onRowUpsert is called when a row is inserted or updated on the source database. Because it’s a handler for the configureTargetSchema function, it is called .
TypeScript Signature
Arguments
- A
Rowobject containing all of the column names for the row in the source database, mapped to their values in the source database. - A
Metadataobject containing the following (case-sensitive):- The
schemaname for the row in the target database. - The
tablename for the row in the source database.
- The
What to return
Return one of the following:- A modified
Rowto write to the target database, mapping all of the column names for the row in the target database to the values that will be written in those columns. { [table: string]: Row[] }, to fan out or reroute any number ofRowsto multiple or differently named tables on the target database.null, to skip writing this source data row modification to the target database.
By default, a single returned
Row will be routed to a table in the target database with the same name and casing as the source table. If the target table is named differently than the source table, you will need to use the mapping return value above, { [table: string]: Row[] }, to reroute the row to the target table. This rerouting is only possible in the configureTargetSchema handler, as the destination tables need to be defined prior to table-level handling.For more information about renaming tables, refer to the cookbook example.Example
The following example demonstrates how to useconfigureTargetSchema with onRowUpsert to transform and filter data during replication.
The onRowUpsert handler is called during upserts on the source database. It uppercases the value in the status field of the orders table, and it prevents the replication of non-target tables (that is, tables other than orders and customers). It still passes deletions to all tables in onRowDelete.
Schema-level onRowDelete(row, metadata)
onRowDelete is called when a row is deleted on the source database. Because it’s a handler for the configureTargetSchema function, it is called .
TypeScript Signature
Arguments
- A
Rowobject containing information about the row’s values. It may also contain information about non-primary key columns, but these are not guaranteed. If the source and target tables have different names for the primary keys, the key/value mapping may come in one of the following forms, depending on the configuration of the source database:- The source primary key name(s) mapped to the source primary key value(s).
- The target primary key name(s) mapped to the source primary key value(s).
- Both of the above.
- A
Metadataobject containing the following (case-sensitive):- The
schemaname for the row in the target database. - The
tablename for the row in the source database.
- The
What to return
Return one of the following:- A
Rowobject containing the target primary keys for the row to delete on the target database. { [table: string]: Row[] }, to fan out or reroute any number ofRowdeletions to multiple target tables.null, to skip writing this source data row deletion to the target database. In other words, this row will not be deleted on the target.
By default, a single returned
Row will be routed to a table in the target database with the same name and casing as the source table. If the target table is named differently than the source table, you will need to use the mapping return value above, { [table: string]: Row[] }, to reroute the row to the target table. This rerouting is only possible in the configureTargetSchema handler, as the destination tables need to be defined prior to table-level handling.For more information, refer to the cookbook example.If the source and target tables name the primary keys differently, you can’t be certain that the
row argument contains the target primary key names (it may contain only the source primary key names).This handler expects the target primary key names in the return value. Therefore, if you need to rename the primary keys between the source and the target, you’ll need to check for the primary key values in the row argument using both the source and the target primary key names, and then make sure that the return value uses the target primary key names. Columns that are included in the return Row that are not in the target table will be ignored.For more information, refer to the cookbook example.Example
The following example demonstrates how to useconfigureTargetSchema with onRowDelete to transform and filter data during replication.
This function is called during deletions. It ensures that any row where id < 100 is not deleted from the target. It passes upserts along unchanged. This is useful when you want to preserve critical reference data or system records in your target database based on their primary key or other columns, even if they’re removed from the source.
configureTargetTables(tableNames, configuration)
configureTargetTables registers table-level handlers that run and ready to be written to the target database. You can use this function to define transformations, filters, or column-level behaviors that are specific to certain tables.
Table-level configuration provides finer control than schema-level handlers. Specifically, the onWrite handler function enables you to perform transactional logic on tables in the target database.
TypeScript signature
Row and Metadata are defined in Common TypeScript definitions.
Arguments
tables: A list of the (case-sensitive) names of the tables to write to in the target database. The handler functions will apply to all rows being routed to the tables in this list. Table names may be (for example,defaultdb.public.table1) or they may include only the name of the table (for example,table1).configuration: An object containing (optionally) anonRowUpsert, anonRowDelete, and anonWritehandler function.- If
onRowUpsertis defined,onRowDeletemust also be defined, and vice versa. - If a handler function isn’t defined, its
roworkeysargument is passed to the target without alteration.
- If
configureTargetTables functions in your userscript, each with different tables included in the tables argument. This enables you to define different functionality for different tables within the same userscript.
Table-level onRowUpsert(row, metadata)
onRowUpsert is called when a row is inserted or updated on the source database. Because it’s a handler for the configureTargetTables function, it is called and ready to be written to the target database.
TypeScript signature
Arguments
- A
Rowobject containing all of the column names for the row in the source database, mapped to their values as supplied by the schema-level handlers. - A
Metadataobject containing the following (case-sensitive):- The
schemaname for the row in the target database. - The
tablename for the row in the target database.
- The
What to return
Return one of the following:- A modified
Rowto write to the target database, mapping all of the column names for the row in the target database to the values supplied by the schema-level handlers. null, to skip writing this source data row modification to the target database.
Example
The following example demonstrates how to useconfigureTargetTables to transform data during upserts, on a per-table basis.
This implementation adds a replicated_at column to the row, and it sets its value to the current time. It also adds a status_description column to the row, deriving its value from the value in the existing status_code field.
Table-level onRowDelete(keys, metadata)
onRowDelete is called when a row is deleted on the source database. Because it’s a handler for the configureTargetTables function, it is called and ready to be written to the target database.
TypeScript signature
Arguments
- A list of the values that define the row to be deleted on the target database, as supplied by the schema-level handlers. This is only a list of the key values, not a mapping of key names to values.
- A
Metadataobject containing the following (case-sensitive):- The
schemaname for the row in the target database. - The
tablename for the row in the target database.
- The
What to return
Return one of the following:- A list of primary key values, defining the row on the target table to delete.
null, to skip writing this data row deletion to the target database. In other words, this row will not be deleted on the target.
Example
The following example demonstrates how to useconfigureTargetTables with onRowDelete to conditionally filter or transform delete operations.
This implementation skips deleting any row in the EU region, or whose orderId is greater than or equal to 1000.
onWrite(rows)
onWrite is called after onRowUpsert and onRowDelete for each of the passed-in rows. The onWrite handler lets you override when rows are written to the target database. It is called right before the final commit, after all schema-level and table-level processing has completed. A will be scheduled once onWrite returns.
Use onWrite when you want full control over how data is applied to the target. Typical use cases include:
- Adding custom validation, auditing, or logging before a write.
- Performing complex cross-table logic.
- Running or custom SQL statements.
- Handling or conditions manually.
- Implementing custom dead-letter queues for failed row writes to the target.
Any data written to the target database is not committed in
onWrite. It will be scheduled to be committed after onWrite returns.TypeScript signature
Arguments
- A list of
RowOpobjects. Together, these describe every upsert and deletion that is planned to be written to the target tables defined by thetablesargument ofconfigureTargetTables. EachRowOpobject includes:- The relevant
action("upsert"or"delete") - The row’s primary keys (
pk) - The row’s metadata (
meta) - In the case of an upsert, the
Rowto be written to the target (data) - Depending on the configuration of the source database, the value of this
Rowon the source database before it was updated/deleted (before)
- The relevant
What to return
onWrite is asynchronous, so it must return a promise once processing is complete. This is typically the result of api.write(rows), which returns a promise that will be resolved on successfully writing rows to the target database.
onWrite functions
onWrite has a set of functions available to it that allows logic to be performed on the target database:
Use api.getTX() to run your own SQL statements within a single . Use api.write(rows) to write the rows to the target database. Mix both approaches by handling some operations manually and passing others to api.write(rows). Read detailed descriptions of these functions below.
The preceding transactional logic helpers can only be used within
onWrite for the table-level handlers. They cannot be used within the schema-level handlers, or within the table-level onRowUpsert or onRowDelete handlers. An error will be returned for api.write(rows) or api.getTx() if they are used in a handler where transactional logic is not supported.getTX()
The getTX function provides access to a managed on the target database that you can use inside your userscript. It allows you to perform direct SQL operations, such as reads, writes, or schema checks, within a single transactional context. This is especially useful when you need to enforce additional constraints, perform cross-table validation, or implement custom transactional logic during replication.
When getTX is called, it returns a transaction object bound to the current replication operation. All operations executed through this object (exec, query, etc.) occur atomically within that transaction. Once your handler finishes execution, the transaction is automatically scheduled to be committed or rolled back depending on whether an error occurred. getTX is only available within onWrite.
getTX().query(sql, ...params)
This function executes a SQL query within the current replication . It returns an async iterable of result rows, allowing you to read data from the target database for validation or lookups.
TypeScript signature
getTX().exec(sql, ...params)
This function runs a SQL statement (such as , , or ) in the current . It does not return results. Use it to modify data or to apply additional logic atomically with replication. Changes will not be until after onWrite returns.
TypeScript signature
getTX().columns()
This function returns an array of column metadata for the target table, including column name, type, primary key status and ignored status. Use this to inspect the schema and column details of the destination table during replication, and to apply logic dynamically based on target columns.
TypeScript signature
Example
getTX().schema()
This function returns the , quoted name of the target schema (for example, db.public). Use this to identify or reference the schema in SQL queries or metadata operations.
TypeScript signature
getTX().table()
This function returns the , quoted name of the target table (for example, db.public.users). Use this to reference the table in SQL queries or for metadata inspection.
TypeScript signature
write(rows)
This function writes the provided rows to the target database using the standard replication logic. Use this to apply inserts, updates, or deletes as part of your custom replication workflow.
write does not commit the changes to the target database. Changes will be committed only after onWrite returns.TypeScript signature
onWrite example
The following example demonstrates how to use the getTX() and write() API functions within onWrite to access the context and execute custom SQL queries during replication. This is useful when you need to:
- Validate referential integrity before inserting data
- Query the target database to make replication decisions
- Maintain denormalized data or aggregate counts
- Execute custom SQL operations within the same transaction as the replication write
console
Userscripts include a built-in global console object that provides standard logging functions for debugging and observability. Messages written through console are captured by replicator and forwarded to its , making them visible in logs, monitoring tools, or local output depending on your deployment.
Further observability can be accessed with the userscript .
Avoid logging sensitive data, as
console output is collected by replicator’s internal logging pipeline.Logging levels
console’s logging functions mirror standard JavaScript console functions:
| Level | Description |
|---|---|
console.log(...args) | General-purpose logging. Use for informational messages. |
console.info(...args) | General-purpose logging. Use for informational messages. |
console.debug(...args) | Verbose debugging output. Typically filtered out unless debug logging is enabled with the Replicator flag. |
console.trace(...args) | Even more verbose trace output. Filtered out unless trace logging is enabled with the Replicator flag. |
console.warn(...args) | Warnings about non-fatal issues or unexpected data. |
console.error(...args) | Errors or exceptions during script execution. |
Example
Toggle logging
If you want to disable logging for a script entirely (for example, in production or high-throughput environments), but still keep the logging lines, callconsole.disable() at the beginning of your script. You can re-enable logging by removing the disable line or calling console.enable().
Example
Best practices
In general, consider the following when writing userscripts:- Prefer built-in database capabilities over userscripts when available. For example, when migrating from PostgreSQL, use PostgreSQL publications to filter tables rather than implementing table filters in a userscript.
- Always remember to import the
replicatorAPI when creating a userscript:import * as api from "replicator@v2";. - Prefer
configureTargetSchemaforonRowUpsertandonRowDelete. - Default to returning the row you received, unless you explicitly want to skip or reroute.
- Numerical columns will be represented as strings. Parse them when reading, convert back to a string after calculations and when returning the row.
- Be mindful of the letter casing for any table and schema names that you provide in a userscript. For example, an Oracle table may be named
EMPLOYEESby default, while a PostgreSQL table may be namedemployees. - Both the
onRowUpsertandonRowDeletehandlers must be provided, not just one. If you only want special handling logic for one, just return the original arguments that are passed in for the other. For instance, this could be as simple asonRowDelete: (row, metadata) => row.

