> ## Documentation Index
> Fetch the complete documentation index at: https://twigz.arqqin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Clerk Utilities

> Clerk authentication and user metadata management utilities

## 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`

<CardGroup cols={2}>
  <Card title="User Metadata" icon="user">
    Manage user public metadata for role-based access and preferences
  </Card>

  <Card title="Backend Integration" icon="server">
    Server-side Clerk operations with full SDK access
  </Card>
</CardGroup>

## Update User Public Metadata

Update user's public metadata completely (replaces existing metadata).

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<ParamField path="metadata" type="Record<string, any>" required>
  New metadata object to replace existing metadata
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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()
      });
    }
  });
  ```

  ```javascript JavaScript theme={null}
  // 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()
      });
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  null
  ```
</ResponseExample>

## Get User Public Metadata

Retrieve user's complete public metadata.

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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
      };
    }
  });
  ```

  ```javascript JavaScript theme={null}
  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
      };
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "role": "store_owner",
    "preferences": {
      "notifications": true,
      "theme": "dark"
    },
    "updatedAt": 1640995200000
  }
  ```
</ResponseExample>

## Merge User Public Metadata

Merge new metadata with existing metadata (preserves existing values).

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<ParamField path="metadata" type="Record<string, any>" required>
  New metadata to merge with existing metadata
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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()
      });
    }
  });
  ```

  ```javascript JavaScript theme={null}
  import { mergeUserPublicMetadata } from '../convex/utils/clerk';

  export const updateUserIdPreferences = action({
    args: { userId: v.string(), preferences: v.any() },
    handler: async (ctx, args) => {
      await mergeUserPublicMetadata(args.userId, {
        preferences: args.preferences,
        lastPreferencesUpdate: Date.now()
      });
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  null
  ```
</ResponseExample>

<Note>
  This function merges new metadata with existing metadata, preserving existing values that aren't explicitly overridden.
</Note>

## Remove User Public Metadata Keys

Remove specific keys from user's public metadata.

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<ParamField path="keysToRemove" type="Array<string>" required>
  Array of keys to remove from metadata
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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'
      ]);
    }
  });
  ```

  ```javascript JavaScript theme={null}
  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'
      ]);
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  null
  ```
</ResponseExample>

## Set User Public Metadata Key

Set a specific key-value pair in user's public metadata.

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<ParamField path="key" type="string" required>
  Metadata key to set
</ParamField>

<ParamField path="value" type="any" required>
  Value to set for the key
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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);
    }
  });
  ```

  ```javascript JavaScript theme={null}
  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);
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  null
  ```
</ResponseExample>

## Get User Public Metadata Key

Retrieve a specific key from user's public metadata.

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<ParamField path="key" type="string" required>
  Metadata key to retrieve
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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';
    }
  });
  ```

  ```javascript JavaScript theme={null}
  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';
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  "store_owner"
  ```
</ResponseExample>

## Has User Public Metadata Key

Check if user has a specific key in their public metadata.

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<ParamField path="key" type="string" required>
  Metadata key to check for
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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
      };
    }
  });
  ```

  ```javascript JavaScript theme={null}
  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
      };
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  true
  ```
</ResponseExample>

## Clear User Public Metadata

Clear all user's public metadata (sets to empty object).

<ParamField path="userId" type="string" required>
  Clerk user ID
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  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}`);
    }
  });
  ```

  ```javascript JavaScript theme={null}
  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}`);
    }
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  null
  ```
</ResponseExample>

## Complete User Management Example

Here's a complete example of user role management using Clerk utilities:

<CodeGroup>
  ```typescript User Role Management theme={null}
  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
      };
    }
  });
  ```

  ```javascript JavaScript theme={null}
  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
      };
    }
  });
  ```
</CodeGroup>

## Environment Setup

To use Clerk utilities, configure the following environment variable:

<AccordionGroup>
  <Accordion title="Clerk Secret Key">
    Required environment variable:

    ```bash theme={null}
    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
  </Accordion>
</AccordionGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="Invalid User ID">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Failed to update user metadata: User not found"
    }
    ```
  </Accordion>

  <Accordion title="Missing Secret Key">
    **Status Code**: `500`

    ```json theme={null}
    {
      "error": "Failed to update user metadata: Missing CLERK_SECRET_KEY"
    }
    ```
  </Accordion>

  <Accordion title="Clerk API Error">
    **Status Code**: `500`

    ```json theme={null}
    {
      "error": "Failed to update user metadata: Rate limit exceeded"
    }
    ```
  </Accordion>
</AccordionGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Metadata Structure" icon="database">
    * Use consistent key naming conventions
    * Store only necessary data in public metadata
    * Consider data size limits (2KB total)
  </Card>

  <Card title="Error Handling" icon="shield">
    * Always wrap calls in try-catch blocks
    * Provide meaningful error messages
    * Handle rate limiting gracefully
  </Card>

  <Card title="Performance" icon="zap">
    * Cache metadata when possible
    * Batch operations when updating multiple keys
    * Use specific key operations instead of full metadata updates
  </Card>

  <Card title="Security" icon="lock">
    * Never store sensitive data in public metadata
    * Validate user permissions before operations
    * Use private metadata for sensitive information
  </Card>
</CardGroup>

<Note>
  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.
</Note>

<Tip>
  Use public metadata for role-based access control, user preferences, and non-sensitive configuration. Store sensitive data in private metadata or your database.
</Tip>
