The browser module provides clients for accessing Convex from browser JavaScript applications without React.
Installation
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:
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
| Client | Use case | Connection | Reactivity |
|---|
ConvexClient | Browser apps | WebSocket | Yes |
ConvexHttpClient | Serverless functions, non-reactive apps | HTTP | No |
ConvexReactClient | React apps | WebSocket | Yes |
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