Skip to main content

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 Public Metadata

Update user’s public metadata completely (replaces existing metadata).
userId
string
required
Clerk user ID
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()
    });
  }
});
null

Get User Public Metadata

Retrieve user’s complete public metadata.
userId
string
required
Clerk user ID
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 User Public Metadata

Merge new metadata with existing metadata (preserves existing values).
userId
string
required
Clerk user ID
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()
    });
  }
});
null
This function merges new metadata with existing metadata, preserving existing values that aren’t explicitly overridden.

Remove User Public Metadata Keys

Remove specific keys from user’s public metadata.
userId
string
required
Clerk user ID
keysToRemove
Array<string>
required
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'
    ]);
  }
});
null

Set User Public Metadata Key

Set a specific key-value pair in user’s public metadata.
userId
string
required
Clerk user ID
key
string
required
Metadata key to set
value
any
required
Value to set for the key
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);
  }
});
null

Get User Public Metadata Key

Retrieve a specific key from user’s public metadata.
userId
string
required
Clerk user ID
key
string
required
Metadata key to retrieve
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';
  }
});
"store_owner"

Has User Public Metadata Key

Check if user has a specific key in their public metadata.
userId
string
required
Clerk user ID
key
string
required
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
    };
  }
});
true

Clear User Public Metadata

Clear all user’s public metadata (sets to empty object).
userId
string
required
Clerk user ID
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}`);
  }
});
null

Complete User Management Example

Here’s a complete example of user role management using Clerk utilities:
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:
  1. Go to Clerk Dashboard → API Keys
  2. Copy the “Secret key” (starts with sk_test_ or sk_live_)
  3. Add to your Convex environment variables
  4. 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.