Skip to main content

ToolJet 3.21 Migration Guide

ToolJet 3.21 is the new LTS release. It includes breaking changes to environments, GitSync, and the App Builder. This guide lists how each area behaved in the previous LTS, how it behaves in 3.21, and what to do after upgrading.

Before upgrading
  • Back up both PostgreSQL databases, PG_DB and TOOLJET_DB. Refer to Upgrading ToolJet to the LTS Version.
  • Upgrade a staging instance first and check your apps before upgrading production.

Platform Changes​

Environments for Modules, ToolJet Database, and Workflows​

Previous LTSToolJet 3.21Action required
Modules, ToolJet Database, and workflows did not have multiple environments. The single existing environment was used in all environments of an application.The previous single environment is considered the production environment and is released.None. Production apps work as before. To continue editing, create a new draft.

GitSync​

Team Plan
Previous LTSToolJet 3.21Action required
GitSync could connect over SSH.Git SSH is no longer supported. Create a GitHub or GitLab app to connect ToolJet. Support for Bitbucket and Azure is coming soon.If you use GitHub or GitLab, reconnect GitSync by setting up the app. If you use any other provider, stay on the current LTS until support for your provider is available.
The Git repository was a directory structure of apps.The repository is broken down to individual components and queries, so conflicts are raised only when the same element is edited.Create a new, empty repository and sync all your apps to it again to continue using GitSync.
Multiple draft versions were allowed when GitSync was enabled.Only one draft version is allowed when GitSync is enabled, because the draft is the tip of main and there can be only one tip.Save all draft versions except one so that the app syncs to Git.

Refer to GitHub configuration, GitLab configuration, and Version Control with Git.

App Builder Changes​

Mobile Auto Layout (High Severity)​

Mobile auto layout has been reworked.

Previous LTSToolJet 3.21
Auto layout runs only in the App Builder's mobile view. Root-level components are moved up to fill gaps, keeping their desktop position and width, and the result is saved. The app renders the saved mobile layout as is.Every component visible on mobile is stacked full width with 10 px gaps, including inside containers. Components hidden on mobile are left out. The app rebuilds this layout every time it renders auto layout pages, ignoring saved mobile layouts. Opening mobile view in the App Builder overwrites and saves the mobile layout.

Action required:

  1. Check every page that has auto layout turned on, on a phone or by adding ?layout=mobile to the app URL. Auto layout is on by default for pages created since December 2023.
  2. If the full-width stacking doesn't work for a page, turn off auto layout for that page in the App Builder's mobile view and arrange the components manually.

Your previous mobile arrangement isn't restored.

RunJS queries Are Live and Read-Only (High Severity)​

RunJS now gives you direct access to the values that queries expose.

Previous LTSToolJet 3.21
queries in RunJS is a copy taken when the script starts. Values stay stale after await run(), and you can assign to it or change it in place.Fields in queries are read-only and always return the latest value, including after await run(). Assignments are silently ignored, and changing data in place, for example with push, sort, or splice, is likely to throw an error. Resetting or running a query also clears error, metadata, request, and response.

Action required:

  • Find RunJS code that assigns to queries.<name>.* or changes queries.<name>.data in place, and make a copy first:
    const rows = [...queries.getUsers.data];
  • If your code relies on the old value after await queries.getUsers.run(), store it in a variable before running the query:
    const previousRows = queries.getUsers.data;
    await queries.getUsers.run();

The modal's open() now resolves after its children have mounted.

Previous LTSToolJet 3.21
await components.modal1.open() resolves immediately with undefined, before the modal's children mount. components in RunJS is a copy taken when the script starts.open() resolves true after the modal finishes opening (up to 1 second), or false if the modal closes first. components in RunJS always returns the latest value.

Action required:

  • Remove workarounds such as a setTimeout after await components.modal1.open(). Scripts can pause for up to 1 second while the modal opens.
  • If a script uses the value returned by open(), handle true (opened) and false (closed before it finished opening).


Need Help?​