Skip to content

Error messages

This page catalogues the errors the library itself raises. Each entry quotes the message exactly as it appears at runtime, so you can search this page for the text in your console.

Errors that originate in your Convex backend functions are a different category: they surface through the reactive error() Signals rather than being thrown by the library, and are covered in error handling.

injectQuery() must be called from an injection context (for example, a component or service field initializer or constructor), or be given an explicit injector via the `injectRef` option to create it later from plain code.

The message is templated with the name of the helper you called, so the leading identifier varies: injectConvex(), injectQuery(), injectQueries(), injectPaginatedQuery(), injectPrewarmQuery(), injectMutation(), injectAction(), injectConvexConnectionState(), injectAuth(), and injectBetterAuth() each produce their own version of it. The rest of the sentence is identical.

When it happens. Every inject* helper needs an Angular injector to resolve the Convex client, register a DestroyRef cleanup, and create effects. Angular only makes an ambient injection context available during field initialization and inside constructors. Calling a helper from a method, an event handler, a route callback, an RxJS operator, a setTimeout, or module top-level code leaves no ambient context, and the helper reports it instead of failing with Angular’s generic assertInInjectionContext message.

How to fix. Either move the call into a field initializer or constructor, or keep it where it is and pass an explicit EnvironmentInjector through the injectRef option — that injector then owns the helper’s lifecycle. See injection context for both patterns.

Could not find `CONVEX`. Make sure to call `provideConvex(...)` once in your root application providers (for example, in `app.config.ts`).

When it happens. injectConvex() — which every query, mutation, action, pagination and prewarm helper, and convexQueryResolver(), calls internally — looked up the CONVEX token optionally and found nothing. The library injects optionally on purpose so it can raise this focused setup error rather than Angular’s generic NullInjectorError. The usual causes are a missing provideConvex(...) call, a helper resolved from an injector that is not a descendant of the one holding the providers, or a test bed configured without a Convex provider.

How to fix. Add provideConvex(convexUrl) to your application’s root providers, as described in providers. In unit tests, register provideConvexTesting() from convex-angular/testing instead — see testing overview.

provideConvex(...) registered more than once in the same injector

Section titled “provideConvex(...) registered more than once in the same injector”
`provideConvex(...)` was registered more than once in the same injector. Register it exactly once in your root application providers (for example, in `app.config.ts`).

When it happens. provideConvex(...) registers an internal multi-token marker, and a guard counts the markers visible in the current injector. Two or more entries mean the same providers array contains provideConvex(...) twice — typically because a shared “core providers” helper already includes it and the application config adds it again. The guard runs from an environment initializer, so it fires while the injector is being created, before any client is constructed.

How to fix. Remove the duplicate so exactly one provideConvex(...) call survives in the root providers array.

provideConvex(...) configured outside the root injector

Section titled “provideConvex(...) configured outside the root injector”
`provideConvex(...)` must be configured only in your root application providers (for example, in `app.config.ts`). Remove nested or route-level registrations.

When it happens. The same guard also looks up the registration marker with skipSelf. Finding one in a parent injector means Convex was already configured higher up and this is a nested registration — for example provideConvex(...) placed in a lazy route’s providers, a child environment injector, or a component-level providers array. A second client in a child scope would open its own WebSocket and keep a separate query cache, so the library refuses it.

How to fix. Keep a single root-level registration and delete the nested one. Components and lazy routes inherit the root client automatically.

Could not find `CONVEX_AUTH`. Bind an auth provider to `CONVEX_AUTH` alongside `provideConvexAuth()`, or replace the pair with `provideClerkAuth()`, `provideAuth0Auth()`, `provideBetterAuth()` (from `convex-angular/better-auth`), or `provideConvexAuthFromExisting()`.

When it happens. provideConvexAuth() was registered, but no ConvexAuthProvider implementation is bound to the CONVEX_AUTH token in the same injector tree. This surfaces during injector initialization, because provideConvexAuth() eagerly creates the auth state.

How to fix. Bind your provider implementation to CONVEX_AUTH next to provideConvexAuth() — as { provide: CONVEX_AUTH, useExisting: MyAuthService } — or replace the pair with provideConvexAuthFromExisting(MyAuthService), which registers both. Do not use provideConvexAuthFromExisting() and provideConvexAuth() together. See custom auth provider.

Note that provideClerkAuth(), provideAuth0Auth(), and provideBetterAuth() already register a CONVEX_AUTH provider and call provideConvexAuth() internally. If you are using one of them, do not call provideConvexAuth() yourself — see Clerk, Auth0, and Better Auth.

Could not find CONVEX (from provideConvexAuth())

Section titled “Could not find CONVEX (from provideConvexAuth())”
Could not find `CONVEX`. Make sure to call `provideConvex(...)` once in your root application providers before calling `provideConvexAuth()`.

When it happens. Auth sync needs the Convex client to install its token fetcher on. This variant of the missing-client error is raised while building the auth state, which means provideConvexAuth() (or an integration helper that includes it) is registered but provideConvex(...) is not.

How to fix. Add provideConvex(convexUrl) to the root providers, ahead of the auth providers in the same array.

provideConvexAuth() registered more than once in the same injector

Section titled “provideConvexAuth() registered more than once in the same injector”
`provideConvexAuth()` was registered more than once in the same injector. Register it exactly once in your root application providers (for example, in `app.config.ts`).

When it happens. The auth providers use the same duplicate-registration guard as provideConvex(...). The most common cause is calling provideConvexAuth() manually in addition to provideClerkAuth(), provideAuth0Auth(), or provideBetterAuth() — each of those already includes it.

How to fix. Keep exactly one registration. When using an integration helper, remove the standalone provideConvexAuth() call.

provideConvexAuth() configured outside the root injector

Section titled “provideConvexAuth() configured outside the root injector”
`provideConvexAuth()` must be configured only in your root application providers (for example, in `app.config.ts`). Remove nested or route-level registrations.

When it happens. The auth registration marker was found in a parent injector, meaning auth is already configured higher up and this is a nested or route-level registration. A second auth state in a child scope would race the root one for control of the client’s token.

How to fix. Move the auth providers to the root providers array and remove the nested registration.

Could not find Convex auth state. Make sure to call `provideConvexAuth()`, `provideClerkAuth()`, `provideAuth0Auth()`, `provideBetterAuth()` (from `convex-angular/better-auth`), or `provideConvexAuthFromExisting()` in your application providers.

When it happens. injectAuth() ran but no auth state was registered. Everything that depends on auth hits this: the *cvaAuthenticated, *cvaUnauthenticated, *cvaAuthLoading, and *cvaAuthRefreshing directives all call injectAuth() in their constructor, and convexAuthGuard, convexUnauthGuard, and guards built with createConvexAuthGuard() call it on every evaluation. So an unconfigured app typically sees this the first time a guarded route is navigated to or a template using an auth directive renders, rather than at startup.

How to fix. Register one of the auth provider functions in your root providers. See auth overview, auth directives, and route guards.

Could not find BetterAuthService. Call provideBetterAuth(...) in your providers, or pass { injectRef } when calling injectBetterAuth() outside an injection context.

When it happens. injectBetterAuth() resolved BetterAuthService and found nothing. Either provideBetterAuth(...) was never registered, or it was registered in an injector that is not an ancestor of the one the call resolved from, including the injector passed as injectRef. A call from plain code with no injector and no injectRef instead produces the standard injectBetterAuth() must be called from an injection context … error described above.

How to fix. Register provideBetterAuth(() => authClient) in your root providers. When calling from outside an injection context, pass an explicit injector: injectBetterAuth({ injectRef }). See Better Auth and injection context.

Paginated queries require experimental client support

Section titled “Paginated queries require experimental client support”
[convex-angular] `injectPaginatedQuery()` requires a Convex client with experimental paginated query support.

When it happens. injectPaginatedQuery() is built on the Convex client’s onPaginatedUpdate_experimental method. Before subscribing, the helper checks that the method exists on the injected client and throws when it does not. In practice this means the convex package is older than the release that introduced the experimental paginated subscription API, or the CONVEX token has been overridden with a stand-in that does not implement the method.

How to fix. Upgrade the convex dependency to a version that exposes onPaginatedUpdate_experimental. If you replaced the client for testing, use MockConvexClient from convex-angular/testing, which implements the method — see MockConvexClient. Non-paginated helpers (injectQuery(), injectQueries()) are unaffected. See pagination for the helper’s full contract.

ConvexClient is disabled

When it happens. MockConvexClient was constructed with { disabled: true }, and the code under test reached its client getter or its connectionState() method. This deliberately mirrors the real ConvexClient, whose low-level accessors throw when the client is disabled — which is exactly the state provideConvex(...) puts it in during server-side rendering, where no WebSocket is opened.

How to fix. If you are testing the server-side-rendering path, the throw is the behavior under test: assert that your code handles it, the way the library’s own helpers do (injectConvexConnectionState() returns a static disconnected state, and injectAuth() skips token wiring, when the client is disabled). If you are not testing that path, construct the mock without options — new MockConvexClient() — so it behaves like a connected client. See MockConvexClient and hydration semantics.

Expected client.setAuth() to have registered auth

Section titled “Expected client.setAuth() to have registered auth”
Expected client.setAuth() to have registered auth, but no registration was captured.

When it happens. MockConvexClient.requireLastAuthRegistration() was called before anything had called client.setAuth() on the mock. The library installs a token fetcher only once auth sync has run for a provider that reports a signed-in user, so a test that has not signed the provider in (for example provider.signIn() on MockConvexAuthProvider) or has not created the component and flushed effects yet has nothing to drive.

How to fix. Put the provider in the state you are testing, create the component, and flush effects before calling requireLastAuthRegistration(). To assert that no registration happened, read convex.authRegistrations or convex.lastAuthRegistration() instead, which do not throw. See MockConvexClient and driving state.

The following are not thrown. The library constructs them, prefixes them, and publishes them on an error() Signal, so they appear in your UI or logs rather than in a stack trace. Each keeps the original error’s message after the prefix.

Prefix Surfaced on Cause
[convex-angular auth] Token fetch failed: … injectAuth().error() Your provider’s fetchAccessToken rejected. The auth state also drops to unauthenticated.
[convex-angular auth] Convex auth sync failed: … injectAuth().error() Installing or clearing the token on the Convex client threw.
[convex-angular better-auth] Session refresh failed: … injectBetterAuth().error() The Better Auth client’s getSession() returned an error envelope or rejected.
[convex-angular better-auth] Convex token exchange failed: … injectBetterAuth().error() The convexClient() plugin’s convex.token() call returned an error envelope or rejected.

Routine signed-out outcomes are not errors: a fetchAccessToken that resolves null, and the Clerk and Auth0 adapters’ internal token-fetch failures, are all treated as “not signed in” and leave error() undefined. See auth overview and status and state.