Overview
The Clerk Utilities API provides backend functions for managing Clerk user metadata and authentication-related operations. These functions run in Node.js environment and require Clerk backend SDK access.
Location: convex/utils/clerk.ts
User Metadata Manage user public metadata for role-based access and preferences
Backend Integration Server-side Clerk operations with full SDK access
Update user’s public metadata completely (replaces existing metadata).
metadata
Record<string, any>
required
New metadata object to replace existing metadata
import { updateUserPublicMetadata } from '../convex/utils/clerk' ;
// In a Convex action
export const updateUserRole = action ({
args: { userId: v . string (), role: v . string () },
handler : async ( ctx , args ) => {
await updateUserPublicMetadata ( args . userId , {
role: args . role ,
updatedAt: Date . now ()
});
}
});
Retrieve user’s complete public metadata.
import { getUserPublicMetadata } from '../convex/utils/clerk' ;
export const getUserInfo = action ({
args: { userId: v . string () },
handler : async ( ctx , args ) => {
const metadata = await getUserPublicMetadata ( args . userId );
return {
role: metadata ?. role || 'customer' ,
preferences: metadata ?. preferences || {},
updatedAt: metadata ?. updatedAt
};
}
});
{
"role" : "store_owner" ,
"preferences" : {
"notifications" : true ,
"theme" : "dark"
},
"updatedAt" : 1640995200000
}
Merge new metadata with existing metadata (preserves existing values).
metadata
Record<string, any>
required
New metadata to merge with existing metadata
import { mergeUserPublicMetadata } from '../convex/utils/clerk' ;
export const updateUserPreferences = action ({
args: { userId: v . string (), preferences: v . any () },
handler : async ( ctx , args ) => {
await mergeUserPublicMetadata ( args . userId , {
preferences: args . preferences ,
lastPreferencesUpdate: Date . now ()
});
}
});
This function merges new metadata with existing metadata, preserving existing values that aren’t explicitly overridden.
Remove specific keys from user’s public metadata.
Array of keys to remove from metadata
import { removeUserPublicMetadataKeys } from '../convex/utils/clerk' ;
export const cleanupUserMetadata = action ({
args: { userId: v . string () },
handler : async ( ctx , args ) => {
await removeUserPublicMetadataKeys ( args . userId , [
'temporaryData' ,
'oldPreferences' ,
'debugInfo'
]);
}
});
Set a specific key-value pair in user’s public metadata.
import { setUserPublicMetadataKey } from '../convex/utils/clerk' ;
export const setUserRole = action ({
args: { userId: v . string (), role: v . string () },
handler : async ( ctx , args ) => {
await setUserPublicMetadataKey ( args . userId , 'role' , args . role );
}
});
Retrieve a specific key from user’s public metadata.
import { getUserPublicMetadataKey } from '../convex/utils/clerk' ;
export const getUserRole = action ({
args: { userId: v . string () },
handler : async ( ctx , args ) => {
const role = await getUserPublicMetadataKey ( args . userId , 'role' );
return role || 'customer' ;
}
});
Check if user has a specific key in their public metadata.
Metadata key to check for
import { hasUserPublicMetadataKey } from '../convex/utils/clerk' ;
export const checkUserVerification = action ({
args: { userId: v . string () },
handler : async ( ctx , args ) => {
const isVerified = await hasUserPublicMetadataKey ( args . userId , 'verified' );
const isStoreOwner = await hasUserPublicMetadataKey ( args . userId , 'storeId' );
return {
isVerified ,
isStoreOwner ,
canAccessStoreFeatures: isVerified && isStoreOwner
};
}
});
Clear all user’s public metadata (sets to empty object).
import { clearUserPublicMetadata } from '../convex/utils/clerk' ;
export const resetUserMetadata = action ({
args: { userId: v . string () },
handler : async ( ctx , args ) => {
await clearUserPublicMetadata ( args . userId );
console . log ( `Cleared metadata for user ${ args . userId } ` );
}
});
Complete User Management Example
Here’s a complete example of user role management using Clerk utilities:
User Role Management
JavaScript
import { v } from "convex/values" ;
import { action } from "./_generated/server" ;
import {
getUserPublicMetadata ,
setUserPublicMetadataKey ,
hasUserPublicMetadataKey ,
mergeUserPublicMetadata
} from "../utils/clerk" ;
// Set user role
export const setUserRole = action ({
args: {
userId: v . string (),
role: v . union ( v . literal ( "customer" ), v . literal ( "store_owner" ), v . literal ( "admin" ))
},
handler : async ( ctx , args ) => {
await setUserPublicMetadataKey ( args . userId , 'role' , args . role );
// Set role-specific metadata
if ( args . role === 'store_owner' ) {
await mergeUserPublicMetadata ( args . userId , {
permissions: [ 'manage_store' , 'view_analytics' ],
roleAssignedAt: Date . now ()
});
} else if ( args . role === 'admin' ) {
await mergeUserPublicMetadata ( args . userId , {
permissions: [ 'manage_platform' , 'view_all_data' , 'manage_users' ],
roleAssignedAt: Date . now ()
});
}
}
});
// Check user permissions
export const checkUserPermissions = action ({
args: { userId: v . string (), requiredPermission: v . string () },
handler : async ( ctx , args ) => {
const metadata = await getUserPublicMetadata ( args . userId );
const permissions = metadata ?. permissions || [];
return {
hasPermission: permissions . includes ( args . requiredPermission ),
userRole: metadata ?. role || 'customer' ,
allPermissions: permissions
};
}
});
// Verify store owner
export const verifyStoreOwner = action ({
args: { userId: v . string (), storeId: v . string () },
handler : async ( ctx , args ) => {
const isStoreOwner = await hasUserPublicMetadataKey ( args . userId , 'storeId' );
const metadata = await getUserPublicMetadata ( args . userId );
if ( ! isStoreOwner ) {
throw new Error ( 'User is not a store owner' );
}
const userStoreId = metadata ?. storeId ;
if ( userStoreId !== args . storeId ) {
throw new Error ( 'User does not own this store' );
}
return {
verified: true ,
storeId: userStoreId ,
role: metadata ?. role
};
}
});
Environment Setup
To use Clerk utilities, configure the following environment variable:
Required environment variable: CLERK_SECRET_KEY = sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Setup steps:
Go to Clerk Dashboard → API Keys
Copy the “Secret key” (starts with sk_test_ or sk_live_)
Add to your Convex environment variables
Restart your Convex deployment
Error Handling
Status Code : 400{
"error" : "Failed to update user metadata: User not found"
}
Status Code : 500{
"error" : "Failed to update user metadata: Missing CLERK_SECRET_KEY"
}
Status Code : 500{
"error" : "Failed to update user metadata: Rate limit exceeded"
}
Best Practices
Metadata Structure
Use consistent key naming conventions
Store only necessary data in public metadata
Consider data size limits (2KB total)
Error Handling
Always wrap calls in try-catch blocks
Provide meaningful error messages
Handle rate limiting gracefully
Performance
Cache metadata when possible
Batch operations when updating multiple keys
Use specific key operations instead of full metadata updates
Security
Never store sensitive data in public metadata
Validate user permissions before operations
Use private metadata for sensitive information
All Clerk utility functions require Node.js environment and must be called from Convex actions. They cannot be used in queries or mutations due to the “use node” directive.
Use public metadata for role-based access control, user preferences, and non-sensitive configuration. Store sensitive data in private metadata or your database.