Skip to main content

Overview

The users table stores user authentication data, subscription status, and project limits. It integrates with Stack Auth for authentication and Autumn for billing.

Schema

stackUserId
string
required
Stack Auth user ID (primary authentication identifier)
clerkId
string
Legacy Clerk user ID - maintained during migration period from Clerk to Stack Auth
email
string
required
User’s email address
autumnCustomerId
string
Autumn billing customer ID for subscription management
subscriptionStatus
union
Current subscription statusPossible values:
  • free - Free tier (10 projects)
  • trialing - In trial period (7 days)
  • active - Active paid subscription
  • paused - Subscription paused
  • canceled - Subscription canceled
  • past_due - Payment failed
subscriptionTier
union
Subscription tier levelPossible values:
  • free - Free tier (10 projects)
  • pro_monthly - Pro monthly ($29/mo, unlimited projects)
  • pro_yearly - Pro yearly ($290/yr, unlimited projects)
subscriptionPlanId
string
Autumn subscription plan identifier
projectLimit
number
required
Maximum number of projects user can create. Set to -1 for unlimited (pro users)
trialEndsAt
number
Unix timestamp (milliseconds) when trial period ends
createdAt
number
required
Unix timestamp (milliseconds) when user was created
updatedAt
number
required
Unix timestamp (milliseconds) when user was last updated

Indexes

by_stack_user
index
Query users by Stack Auth user IDFields: [stackUserId]
by_clerk
index
Legacy index for Clerk migration periodFields: [clerkId]
by_autumn_customer
index
Query users by Autumn customer IDFields: [autumnCustomerId]

Operations

All user operations are defined in convex/users.ts.

Get or Create User

Automatically called when users sign up or sign in.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const getOrCreateUser = useMutation(api.users.getOrCreateUser);

// Called automatically on auth
await getOrCreateUser();
Behavior:
  • Checks for existing user by stackUserId
  • Creates new user with free tier (10 projects) if not found
  • Updates updatedAt timestamp for existing users

Get Subscription Info

Retrieve current user’s subscription details.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const subscription = useQuery(api.users.getSubscription);

// Returns:
// {
//   ...user fields,
//   trialDaysRemaining: number,
//   isInTrial: boolean,
//   isPro: boolean,
//   isFree: boolean,
//   canCreateProject: boolean
// }

Get Project Count

Count how many projects a user has created.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const projectCount = useQuery(api.users.getProjectCount);
// Returns: number

Update Subscription

Sync subscription status from Autumn billing system.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const updateSubscription = useMutation(api.users.updateSubscription);

await updateSubscription({
  stackUserId: 'user_123',
  autumnCustomerId: 'cus_abc',
  subscriptionStatus: 'active',
  subscriptionTier: 'pro_monthly',
  projectLimit: -1 // unlimited
});

Start Trial

Begin a 7-day trial period for pro features.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const startTrial = useMutation(api.users.startTrial);

await startTrial({ tier: 'pro_monthly' });
// Returns: { success: true, trialEndsAt: timestamp }
Rules:
  • Only available to users not already in trial or active subscription
  • Sets projectLimit to -1 (unlimited)
  • Trial lasts 7 days (configurable via TRIAL_DAYS constant)

Cancel Trial

Cancel an active trial and return to free tier.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const cancelTrial = useMutation(api.users.cancelTrial);

await cancelTrial();
// Returns: { success: true }
Behavior:
  • Reverts to free tier with 10 project limit
  • Clears trial end date

Get Billing Status

Retrieve comprehensive billing information.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const billing = useQuery(api.users.getBillingStatus);

// Returns:
// {
//   subscriptionStatus: 'active',
//   subscriptionTier: 'pro_monthly',
//   trialEndsAt: 1234567890,
//   trialDaysRemaining: 5,
//   projectLimit: -1,
//   projectCount: 25,
//   remainingProjects: 'unlimited' | number,
//   autumnCustomerId: 'cus_abc',
//   createdAt: 1234567890
// }

Example Queries

Check if user can create projects

import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

function useCanCreateProject() {
  const subscription = useQuery(api.users.getSubscription);
  const projectCount = useQuery(api.users.getProjectCount);
  
  if (!subscription) return false;
  
  return subscription.canCreateProject && 
    (subscription.projectLimit === -1 || 
     projectCount < subscription.projectLimit);
}

Get user by Stack Auth ID

import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const user = useQuery(api.users.getUserByStackUserId, { 
  stackUserId: 'user_123' 
});

Get user by Autumn Customer ID

import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const user = useQuery(api.users.getUserByAutumnCustomerId, { 
  autumnCustomerId: 'cus_abc' 
});

Subscription Tiers

TierProjectsPriceStatus
Free10$0/mofree
Pro MonthlyUnlimited$29/moactive/trialing
Pro YearlyUnlimited$290/yractive/trialing

Constants

import { FREE_PROJECT_LIMIT, TRIAL_DAYS } from '@/convex/users';

console.log(FREE_PROJECT_LIMIT); // 10
console.log(TRIAL_DAYS); // 7

Best Practices

Always check canCreateProject before allowing users to create new projects.
Use projectLimit === -1 to identify pro users with unlimited projects.
The migration from Clerk to Stack Auth is ongoing. The clerkId field will be removed in a future update.

Build docs developers (and LLMs) love