Convex 0.17.0 is out and we have a few big changes to improve developer ergonomics!

  • Breaking: Convex document IDs are now strings, not classes.
  • Breaking: References to functions use api objects (useQuery(api.messages.list) not useQuery("messages:list")) and support jump-to-definition in your editor!

and also:

  • Breaking: convex/schema has been merged into convex/server to avoid naming conflicts with imports.
  • ConvexReactClient now includes a query method for performing a one-shot non-reactive query.

These changes are backwards compatible and your current clients will continue to work with Convex, but upgrading to 0.17.0 will require some code changes to please read on for more info.

If you encounter any challenges migrating to 0.17.0 please reach out to us in the Convex Discord Community or at

Breaking: Document IDs are strings

As of version 0.17.0, Document IDs are now strings instead of instances of the Id class. String IDs can now be compared directly with === and are much easier to pass to other services or across JSON-serialized boundaries.

JS .toString()
Old IDs (class) new Id("users", "LZ_YyWnYaZSFKTnOpihMPA") "LZ_YyWnYaZSFKTnOpihMPA"
New IDs (string) “30pszp69d7c6k54554wwx9h89gycxhr” "30pszp69d7c6k54554wwx9h89gycxhr"

Existing clients that still use the Id class will continue to work and existing server functions on 0.16 or earlier will continue to receive the Id class from database calls.

Id<"tableName"> is still the correct way to type IDs since it enables TypeScript to differentiate between ID strings and other strings at compile time.

Client changes

  • doc._id.equals(otherDoc._id) should be replaced with doc._id === otherDoc._id.
  • should be replaced with doc._id.
  • new Id(...) is no longer supported and in general all IDs should come from reading and writing documents from Convex. If a dummy ID is needed for an optimistic update it can be created based on a random string, e.g., _id: crypto.randomUUID() as Id<"tableName">.

Server changes

  • Methods on db accept the new ID format and do not need to be updated.
  • The function db.normalizeId(tableName, idString) can be used to convert an ID in either format to the new format if needed, e.g.:
db.normalizeId("users", "30pszp69d7c6k54554wwx9h89gycxhr") 
// "30pszp69d7c6k54554wwx9h89gycxhr"

db.normalizeId("users", "LZ_YyWnYaZSFKTnOpihMPA") 
// Converts to new format: "30pszp69d7c6k54554wwx9h89gycxhr"

db.normalizeId("messages", "30pszp69d7c6k54554wwx9h89gycxhr") 
// Returns null since ID does not belong to table "messages"

Breaking: Refer to Convex functions with an api object

Convex functions are now referred to from JavaScript/TypeScript clients with an api object instead of strings.

useQuery(api.messages.list)  // instead of the old useQuery("messages:list")

API objects have a variety of advantages over strings: less boilerplate code generation, simpler TypeScript types, and our favorite… jump to definition for Convex functions! (cmd-click/ctrl-click in VS Code)

Applications using Convex 0.17.0 or later will need to be updated to use the new syntax.

Manual migration

  • Import useQuery, usePaginatedQuery, useMutation, useAction, and useQueries from "convex/react".
  • Import the api object from "./convex/_generated/api" and remove old imports of the API type.
  • Update useQuery() and friends (runMutation, scheduler.runAt, etc) from using the string function name "folder/module:function" to api.folder.module.function, e.g., useQuery("messages:list")useQuery(api.messages.list).
  • Import cronJobs from "convex/server" if needed.

If your application isn’t using codegen (this is rare) you can convert a string function name via the makeFunctionReference method, e.g., useQuery(makeFunctionReference<"query">("messages:list")).

Automatic migration

Large applications may benefit from this codemod that should automatically update your code to the new syntax. This conversion is best-effort so you should review these changes after it runs.

Breaking: convex/schema merged into convex/server

defineTable and defineSchema are now exported from convex/server not convex/schema. This fixed ambiguous imports from convex/schema in products with ./convex/schema.ts and a project-level baseUrl.

One-shot queries

The typical way to execute a query in Convex is the useQuery hook which subscribes to updates on the results of the query. For convenience we’ve also added a query method to ConvexReactClient which performs a one-shot non-reactive query.