Announcing Convex 1.18

Convex 1.18 is here! To upgrade, run npm install convex@1.18.

Be sure to upgrade convex-test and any Convex Components you're using as well.

As always, you can find a full list of changes in the changelog. Before getting to an assortment of (primarily community-requested!) changes, let's start with one change especially worth elaborating on.

Discouraging running Convex functions directly

Starting in convex@1.18.0, a console.warn message fires whenever a Convex Function (mutation, query, action, internalMutation, etc.) is called directly, like

export const foo = mutation({
  args: {},
  returns: v.number(),
  handler: (ctx, args) => {
    return 42;
  })
}

export const bar = mutation({
  args: v.any(),
  returns: v.any(),
  handler: (ctx, args) => {
    const result = await foo(); // this is a "direct" call
    return result;
  })
}

This pattern has never been officially supported but has worked inadvertently at least since Convex function references started supporting jump-to-definition. It has a couple of problems that make it worth discouraging.

  1. Arguments and return values aren't validated at runtime despite the presence of validators at the function definition site.
  2. Functions called this way unexpectedly lack isolation and atomicity. Convex functions may be written assuming they will run as independent transactions, but running these function directly breaks that assumption.

The first issue could be addressed with new APIs, but the second is fundamental. The isolation and atomicity provided by transactions are core to the modularity of Convex. Concretely, running Convex functions defined by customFunctions like triggers cause deadlocks and other bad behavior. For more, see the Convex docs on transactions and the section on transactions in the Aggregate Component docs.

There are two options for how to modify your code to address the warning.

  1. Refactor it out as a helper function, then call that helper function directly. See https://docs.convex.dev/production/best-practices/#use-helper-functions-to-write-shared-code for more.
  2. Use ctx.runMutationctx.runQuery, or ctx.runAction instead of calling the function directly. This has more overhead (it's slower) but you gain isolation and atomicity because it runs as a subtransaction.

You can filter to warnings in the Convex dashboard logs to see if you're using this pattern. For now running functions this way only logs a warning, but this pattern is now deprecated and may be deleted in a future version.

Other changes

Convex 1.18 also:

  • Improves function running syntax in the terminal, like npx convex run api.messages.list '{user: "me"}'
    • npx convex run api.foo.bar is equivalent to npx convex run foo:bar
    • npx convex run convex/foo.ts:bar is equivalent to npx convex run foo:bar
    • npx convex run convex/foo.ts is equivalent to npx convex run foo:default
    • the new --identity flag is similar to dashboard "acting as user" feature, like npx convex run --identity '{ name: "sshader" }'
  • Adds support for non-TypeScript/JavaScript files in the convex directory.
    • Now you can stick a readme, a test, a .json file, or even a binary without a warning. These other file types can't defined Convex functions, but they can be imported by modules that do if esbuild supports it.
  • Makes support for Next.js 15 and Clerk Core 2 official
  • Adds the npx convex import --replace-all flag for removing all existing tables during an import
  • Adds a warning when Convex functions run other Convex functions directly without using helpers

Have a hunch that this or another Convex API could be better? Even if we've heard it before, we'd love to hear from you on GitHub or Discord.