Skip to main content

MySQL integration

Start workflows from new or changed rows and read and write a client's MySQL database from inside a workflow.

What it does

The MySQL integration lets your agency react to and change a client's MySQL (or MariaDB) database from a workflow. Connect the database once with a connection string or discrete host, port, user, password, and database. Triggers start a workflow when a row is added or changed, when your own query returns a new row, or when the schema gains a table or column. Actions find rows without writing SQL, run parameterized queries, insert one or many rows, update, upsert, and delete rows, and run any single statement, including stored procedures. Connection details are encrypted at rest, every value you pass is bound as a parameter rather than spliced into the SQL, and results are capped so a large read cannot overwhelm a run.

Connect a MySQL database

  1. Open your workspace in TaskJuice and add a MySQL connection on the node that uses it.
  2. Paste a connection string, for example mysql://user:password@db.example.com:3306/appdb.
  3. Or fill in the host, port (3306 by default), user, password, and database separately. Connections require TLS and verify the certificate chain by default.
  4. Save the connection. The password and connection string are stored encrypted and are never shown again or written to run output.

Provision a least-privilege database user scoped to the tables the workflow touches, and expose the database on a public host the workflow can reach over TLS. Private and internal addresses are blocked. A user that only needs triggers and reads can be granted SELECT alone.

Network access. TaskJuice reaches the database from more than one network. Workflow steps in a published run connect from a fixed set of outbound addresses. Triggers, which check the database on an interval, and the editor's Test step connect from cloud infrastructure that has no fixed IP addresses. If the database only accepts listed addresses, triggers and editor tests time out, so a step that fails its editor test with a connection error can still work in a published run. Unless you have a reason to restrict it, allow access from anywhere (0.0.0.0/0) and let a long, random password and TLS protect the database.

Triggers

MySQL has no way to call out when data changes, so each trigger checks the database on an interval you choose (every 5 minutes by default, as often as every minute). Each check runs one small query. Rows, tables, and columns that already exist when you turn a trigger on never fire; only changes after that point do. Every check that finds something starts one run whose items array holds everything found, so add a Loop node to handle the items one at a time.

  • mysql/new-row fires when a row is added to a table. Point it at a column whose value only grows for new rows, usually the AUTO_INCREMENT primary key.
  • mysql/new-or-updated-row fires when a row is added or changed. Point it at a DATETIME or TIMESTAMP column your schema sets on every write, for example updated_at DATETIME(3) ON UPDATE CURRENT_TIMESTAMP(3), with a unique ID column as the tie-breaker.
  • mysql/new-row-custom-query fires when your own SELECT returns a row it has not returned before. Name a unique column so a row fires once even if its other values change later.
  • mysql/new-table fires when a table or view is created in the connected database.
  • mysql/new-column fires when a column is added to a table. Name a table to watch just that one, or leave it empty to watch them all.

Actions

  • mysql/find finds rows in a table without writing SQL. Filter with conditions such as status = open or total >= 100 (operators =, !=, >, >=, <, <=, like, not-like, in, not-in, is-null, is-not-null), match all or any of them, pick the columns to return, sort, and cap the row count.
  • mysql/query runs a read-only SELECT with ? placeholders and a matching params array, returning the result rows and the row count.
  • mysql/insert inserts a row from a column-to-value map and returns the new row's insertId.
  • mysql/insert-many inserts up to 1,000 rows in one statement from an array of column-to-value objects, such as the items of a trigger. A column missing from a row is inserted as NULL. It returns the insertId MySQL reports for the statement, the first new row's id.
  • mysql/update updates the rows matching an equality filter, setting new column values, and returns the affected row count.
  • mysql/upsert inserts a row or, on a duplicate unique or primary key, updates it (INSERT ... ON DUPLICATE KEY UPDATE). MySQL chooses the conflicting index; the columns you name select which columns are refreshed. It returns the insertId MySQL reports for the statement, and a rowCount that tells you what happened: 1 when the row was inserted, 2 when an existing row was updated, 0 when the row already held these values.
  • mysql/delete deletes the rows matching an equality filter and returns the deleted row count.
  • mysql/execute runs one SQL statement of any kind: an UPDATE with a custom WHERE, a CALL to a stored procedure (its first result set is returned as rows), or a schema change. Treat it with care; it can change or remove data and cannot be undone.

MySQL cannot return the written row itself. Insert, Insert Many and Upsert return rows holding one object, { insertId }, with the id MySQL reports for the statement (the first id of a multi-row insert) when the table has an AUTO_INCREMENT column, and an empty rows otherwise.

Write a safe query

Always pass values through params, never by building the SQL string yourself:

sql:    SELECT id, email FROM customers WHERE status = ? AND created_at > ?
params: ["active", "2026-01-01"]

Table and column names in the structured actions and triggers are validated against a strict allowlist and quoted for you, so a name containing a backtick, semicolon, or whitespace is rejected before anything runs. The query, custom-query trigger, and execute action accept one statement at a time; a second statement after a ; is rejected.

Known limitations

  • Triggers check on an interval rather than reacting instantly. Keep the watched columns indexed; new-or-updated-row reads best with a composite index on the timestamp and ID columns.
  • new-or-updated-row tracks its position to the millisecond and reads rows that share a millisecond in ID order, so every row is delivered exactly once at any column precision, DATETIME(6) and TIMESTAMP(6) included, even when one bulk UPDATE stamps thousands of rows with the same time. A backlog spread over time is read straight from the composite index, a few milliseconds per check however large the backlog. Rows that share a single millisecond are scanned together on every check, and keep being scanned after they have all been delivered, until a newer row moves the trigger's position past that millisecond: measured at about half a second per million such rows.
  • The ID column of new-row and the timestamp and ID columns of new-or-updated-row must never be NULL. A row with a NULL in one of them is never delivered, and it can stop the trigger with a configuration error. That happens when the table is empty at the moment you turn the trigger on and a row with a NULL timestamp (for new-row, a NULL ID) then arrives, or when the row with the newest timestamp has a NULL ID. A NULL timestamp that is already in a table with other rows when you turn the trigger on does not stop it; that row is simply never delivered. Declare the columns NOT NULL.
  • DATETIME, TIMESTAMP and DATE values are handled as plain wall-clock values: the trigger and the actions read and compare them exactly as the server returns them, with no further time-zone conversion, so values in a daylight-saving changeover hour behave like any other.
  • The custom-query trigger re-runs your whole query on every check. Keep it selective with a WHERE and a LIMIT of a few hundred rows.
  • Find and query return at most 1,000 rows and around 5 MB per call; when a read is clipped the result sets truncated to true.
  • A statement that runs longer than 30 seconds is cancelled.
  • A few statements, such as CREATE PROCEDURE and LOCK TABLES, cannot take ? parameters in MySQL. The execute action runs them when Parameters is empty; write any values into the statement directly.
  • Connections are short-lived: each action and each trigger check opens and closes its own connection. Point the connection at a pooler if your database has a low connection limit.
Was this helpful?