# Guarding Team Account Creation with Policies

> Learn how to restrict and validate team account creation using the policy system.

*Canonical: https://makerkit.dev/docs/next-supabase-turbo/recipes/team-account-creation-policies*

---

The Team Account Creation Policies system allows you to define custom business rules that guard when users can create new team accounts using the [Policies API](../api/policies-api). 

Common use cases include:

- Requiring an active subscription to create team accounts
- Requiring a specific subscription plan (e.g., Pro or Enterprise)
- Limiting the number of team accounts per user

{% sequence title="Implementation Steps" description="How to implement team account creation policies" %}

[Understanding Policies](#understanding-policies)

[Registering Policies](#registering-policies)

[Common Policy Examples](#common-policy-examples)

[Evaluating Policies](#evaluating-policies)

{% /sequence %}

## Understanding Policies

Policies are defined using the `definePolicy` function and registered in the `createAccountPolicyRegistry`. Each policy:

1. Has a unique ID
2. Specifies which stages it runs at (`preliminary` or `submission`)
3. Returns `allow()` or `deny()` with an error message

```typescript
import { allow, definePolicy, deny } from '@kit/policies';
import type { FeaturePolicyCreateAccountContext } from '@kit/team-accounts/server';

const myPolicy = definePolicy<FeaturePolicyCreateAccountContext>({
  id: 'my-policy-id',
  stages: ['preliminary', 'submission'],

  async evaluate(context) {
    // Return allow() to permit the action
    // Return deny({ code, message, remediation }) to block it
  },
});
```

### Policy Stages

- **preliminary**: Runs before showing the create account form. Use to check if the user can attempt to create an account.
- **submission**: Runs when the form is submitted. Use to validate the account name and final checks.

## Registering Policies

Create a setup file and import it in your layout to register policies at app startup.

### Step 1: Create the Registration File

```typescript
// apps/web/lib/policies/setup-create-account-policies.ts
import 'server-only';

import { createAccountPolicyRegistry } from '@kit/team-accounts/server';

import { subscriptionRequiredPolicy } from './create-account-policies';

createAccountPolicyRegistry.registerPolicy(subscriptionRequiredPolicy);
```

### Step 2: Import in Layout

```typescript
// apps/web/app/home/layout.tsx
import '~/lib/policies/setup-create-account-policies';

export default function HomeLayout({ children }) {
  return <>{children}</>;
}
```

{% callout type="default" title="Default Behavior" %}
By default, no policies are registered and all users can create team accounts. You must register policies to enforce restrictions.
{% /callout %}

## Common Policy Examples

### Require Active Subscription

Block team account creation unless the user has an active subscription on their personal account:

```typescript
// apps/web/lib/policies/create-account-policies.ts
import 'server-only';

import { allow, definePolicy, deny } from '@kit/policies';
import { getSupabaseServerClient } from '@kit/supabase/server-client';

import type { FeaturePolicyCreateAccountContext } from '@kit/team-accounts/server';

export const subscriptionRequiredPolicy =
  definePolicy<FeaturePolicyCreateAccountContext>({
    id: 'subscription-required',
    stages: ['preliminary', 'submission'],

    async evaluate(context) {
      const client = getSupabaseServerClient();

      const { data: subscription, error } = await client
        .from('subscriptions')
        .select('id, status, active')
        .eq('account_id', context.userId)
        .eq('active', true)
        .maybeSingle();

      if (error) {
        return deny({
          code: 'SUBSCRIPTION_CHECK_FAILED',
          message: 'Failed to verify subscription status',
        });
      }

      if (!subscription) {
        return deny({
          code: 'SUBSCRIPTION_REQUIRED',
          message: 'An active subscription is required to create team accounts',
          remediation: 'Please upgrade your plan to create team accounts',
        });
      }

      return allow();
    },
  });
```

### Require Specific Plan (Price ID)

Only allow users with a specific subscription plan to create team accounts:

```typescript
export const proPlanRequiredPolicy = definePolicy<
  FeaturePolicyCreateAccountContext,
  { allowedPriceIds: string[] }
>({
  id: 'pro-plan-required',
  stages: ['preliminary', 'submission'],

  async evaluate(context, config) {
    const allowedPriceIds = config?.allowedPriceIds ?? [
      'price_pro_monthly',
      'price_pro_yearly',
      'price_enterprise_monthly',
      'price_enterprise_yearly',
    ];

    const client = getSupabaseServerClient();

    const { data: subscription, error } = await client
      .from('subscriptions')
      .select('id, active, subscription_items(price_id)')
      .eq('account_id', context.userId)
      .eq('active', true)
      .maybeSingle();

    if (error) {
      return deny({
        code: 'SUBSCRIPTION_CHECK_FAILED',
        message: 'Failed to verify subscription status',
      });
    }

    if (!subscription) {
      return deny({
        code: 'SUBSCRIPTION_REQUIRED',
        message: 'A subscription is required to create team accounts',
        remediation: 'Please subscribe to a plan to create team accounts',
      });
    }

    const priceIds =
      subscription.subscription_items?.map((item) => item.price_id) ?? [];

    const hasAllowedPlan = priceIds.some((priceId) =>
      allowedPriceIds.includes(priceId ?? '')
    );

    if (!hasAllowedPlan) {
      return deny({
        code: 'PLAN_NOT_ALLOWED',
        message: 'Your current plan does not include team account creation',
        remediation: 'Please upgrade to a Pro or Enterprise plan',
      });
    }

    return allow();
  },
});
```

### Maximum Accounts Per User

Limit how many team accounts a user can own:

```typescript
export const maxAccountsPolicy = definePolicy<
  FeaturePolicyCreateAccountContext,
  { maxAccounts: number }
>({
  id: 'max-accounts-per-user',
  stages: ['preliminary', 'submission'],

  async evaluate(context, config) {
    const maxAccounts = config?.maxAccounts ?? 3;

    const client = getSupabaseServerClient();

    const { count, error } = await client
      .from('accounts')
      .select('*', { count: 'exact', head: true })
      .eq('primary_owner_user_id', context.userId)
      .eq('is_personal_account', false);

    if (error) {
      return deny({
        code: 'MAX_ACCOUNTS_CHECK_FAILED',
        message: 'Failed to check account count',
      });
    }

    const currentCount = count ?? 0;

    if (currentCount >= maxAccounts) {
      return deny({
        code: 'MAX_ACCOUNTS_REACHED',
        message: `You have reached the maximum of ${maxAccounts} team accounts`,
        remediation: 'Delete an existing team account to create a new one',
      });
    }

    return allow();
  },
});
```

### Rate Limiting Account Creation

Prevent users from creating too many accounts in a short period:

```typescript
export const rateLimitPolicy = definePolicy<
  FeaturePolicyCreateAccountContext,
  { maxAccountsPerDay: number }
>({
  id: 'account-creation-rate-limit',
  stages: ['submission'],

  async evaluate(context, config) {
    const maxAccountsPerDay = config?.maxAccountsPerDay ?? 5;

    const client = getSupabaseServerClient();

    const oneDayAgo = new Date();
    oneDayAgo.setDate(oneDayAgo.getDate() - 1);

    const { count, error } = await client
      .from('accounts')
      .select('*', { count: 'exact', head: true })
      .eq('primary_owner_user_id', context.userId)
      .eq('is_personal_account', false)
      .gte('created_at', oneDayAgo.toISOString());

    if (error) {
      return deny({
        code: 'RATE_LIMIT_CHECK_FAILED',
        message: 'Failed to check rate limit',
      });
    }

    if ((count ?? 0) >= maxAccountsPerDay) {
      return deny({
        code: 'RATE_LIMIT_EXCEEDED',
        message: `You can only create ${maxAccountsPerDay} accounts per day`,
        remediation: 'Please wait 24 hours before creating another account',
      });
    }

    return allow();
  },
});
```

### Combining Multiple Policies

Register multiple policies to enforce several rules:

```typescript
// apps/web/lib/policies/setup-create-account-policies.ts
import 'server-only';

import { createAccountPolicyRegistry } from '@kit/team-accounts/server';

import {
  maxAccountsPolicy,
  proPlanRequiredPolicy,
  rateLimitPolicy,
} from './create-account-policies';

createAccountPolicyRegistry
  .registerPolicy(proPlanRequiredPolicy)
  .registerPolicy(maxAccountsPolicy)
  .registerPolicy(rateLimitPolicy);
```

## Evaluating Policies

Use `createAccountCreationPolicyEvaluator` to check policies in your server actions:

```typescript
import { createAccountCreationPolicyEvaluator } from '@kit/team-accounts/server';

async function checkCanCreateAccount(userId: string) {
  const evaluator = createAccountCreationPolicyEvaluator();

  const result = await evaluator.canCreateAccount(
    {
      userId,
      accountName: '',
      timestamp: new Date().toISOString(),
    },
    'preliminary'
  );

  return {
    allowed: result.allowed,
    reason: result.reasons[0] ?? null,
  };
}
```

### Checking if Policies Exist

Before running evaluations, you can check if any policies are registered:

```typescript
const evaluator = createAccountCreationPolicyEvaluator();

const hasPolicies = await evaluator.hasPoliciesForStage('preliminary');

if (hasPolicies) {
  const result = await evaluator.canCreateAccount(context, 'preliminary');
  // Handle result...
}
```
