Skip to main content
The browser module provides clients for accessing Convex from browser JavaScript applications without React.

Installation

npm install convex

Importing

import { ConvexClient, ConvexHttpClient } from "convex/browser";
If you are using React, use the convex/react module instead for better integration with React’s rendering and state management.

ConvexClient

A WebSocket-based client that subscribes to queries and executes mutations and actions. Provides reactive updates when query results change.

Basic usage

import { ConvexClient } from "convex/browser";
import { api } from "./convex/_generated/api";

const client = new ConvexClient("https://small-mouse-123.convex.cloud");

const unsubscribe = client.onUpdate(
  api.messages.list,
  {},
  (messages) => {
    console.log(messages);
  }
);

Constructor options

const client = new ConvexClient(address, {
  disabled: false,
  unsavedChangesWarning: true,
  // ... other options
});
Options:
  • disabled - Disables subscriptions (useful for SSR)
  • unsavedChangesWarning - Prompts users about unsaved changes before leaving

Subscribing to queries

The onUpdate method subscribes to a query and calls a callback when results change:
const unsubscribe = client.onUpdate(
  api.messages.list,
  { channel: "#general" },
  (messages) => {
    // Update UI with new messages
    renderMessages(messages);
  },
  (error) => {
    // Handle errors
    console.error("Query error:", error);
  }
);
Return value: The return value is both a function and an object:
// Use as a function
const unsubscribe = client.onUpdate(api.messages.list, {}, callback);
unsubscribe();

// Use as an object
const subscription = client.onUpdate(api.messages.list, {}, callback);
const current = subscription.getCurrentValue();
subscription.unsubscribe();

Executing mutations

const taskId = await client.mutation(api.tasks.create, {
  text: "New task"
});

Executing actions

const result = await client.action(api.ai.generateSummary, {
  text: "Some long text..."
});

One-time queries

const tasks = await client.query(api.tasks.list, { completed: false });

Authentication

Set authentication with an async token fetcher:
client.setAuth(
  async () => {
    const token = await getAuthToken();
    return token; // or null if unavailable
  },
  (isAuthenticated) => {
    console.log("Auth status:", isAuthenticated);
  }
);

Connection state

// Get current state
const state = client.connectionState();
console.log(state.isConnected);

// Subscribe to changes
const unsubscribe = client.subscribeToConnectionState((state) => {
  updateConnectionIndicator(state.isConnected);
});

Paginated queries (experimental)

const subscription = client.onPaginatedUpdate_experimental(
  api.messages.list,
  { channel: "#general" },
  { initialNumItems: 20 },
  (result) => {
    renderMessages(result.results);
    if (result.status === "CanLoadMore") {
      enableLoadMoreButton(() => result.loadMore(10));
    }
  }
);

Cleanup

Always close the client when done:
await client.close();

ConvexHttpClient

An HTTP-based client for executing queries, mutations, and actions without maintaining a WebSocket connection. Suitable for server-side code or applications that don’t need real-time updates.

Basic usage

import { ConvexHttpClient } from "convex/browser";
import { api } from "./convex/_generated/api";

const client = new ConvexHttpClient("https://small-mouse-123.convex.cloud");

const tasks = await client.query(api.tasks.list, {});

Constructor options

const client = new ConvexHttpClient(address, {
  skipConvexDeploymentUrlCheck: false,
  logger: true, // or false, or custom Logger
  auth: "eyJhbGciOi...", // Optional initial auth token
  fetch: customFetch // Optional custom fetch implementation
});

Executing queries

const tasks = await client.query(api.tasks.list, { completed: false });

Executing mutations

Mutations are queued by default to ensure ordered execution:
// Queued mutation (default)
const taskId = await client.mutation(api.tasks.create, {
  text: "New task"
});

// Skip the queue for parallel execution
await client.mutation(
  api.tasks.create,
  { text: "Another task" },
  { skipQueue: true }
);

Executing actions

const result = await client.action(api.ai.generateSummary, {
  text: "Some long text..."
});

Authentication

// Set auth token
client.setAuth("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...");

// Clear auth
client.clearAuth();

Consistent queries (experimental)

Execute multiple queries at the same timestamp:
const client = new ConvexHttpClient(address);

const tasks = await client.consistentQuery(api.tasks.list, {});
const users = await client.consistentQuery(api.users.list, {});
// Both queries see the database at the same point in time
Consistent queries have a 30-second time limit. Create a new client for a fresh timestamp.

Choosing a client

ClientUse caseConnectionReactivity
ConvexClientBrowser appsWebSocketYes
ConvexHttpClientServerless functions, non-reactive appsHTTPNo
ConvexReactClientReact appsWebSocketYes
Use ConvexClient when:
  • Building a browser application without React
  • You need real-time updates
  • You want to subscribe to query changes
Use ConvexHttpClient when:
  • Running in serverless functions or edge workers
  • Making one-off requests
  • You don’t need real-time updates
  • Minimizing connection overhead is important

Error handling

import { ConvexError } from "convex/values";

try {
  await client.mutation(api.tasks.create, { text: "New task" });
} catch (error) {
  if (error instanceof ConvexError) {
    // Handle application errors with data
    console.error("Convex error:", error.data);
  } else {
    // Handle network or other errors
    console.error("Error:", error);
  }
}

Type safety

All clients use generated TypeScript types:
import { api } from "./convex/_generated/api";

// Fully type-safe
const tasks = await client.query(api.tasks.list, {
  completed: false // Type-checked argument
});
// tasks has the correct return type

BaseConvexClient

For advanced use cases, you can extend BaseConvexClient to build custom clients:
import { BaseConvexClient } from "convex/browser";

const client = new BaseConvexClient(
  address,
  (updatedQueries) => {
    // Handle query updates
  },
  options
);

Best practices

  • Store deployment URLs in environment variables
  • Always close clients when done to prevent memory leaks
  • Use ConvexClient for browser apps that need reactivity
  • Use ConvexHttpClient for server-side code
  • Handle errors appropriately for mutations and actions
  • For React applications, use convex/react instead
  • Implement connection state UI for better user experience

Build docs developers (and LLMs) love