TinyKit Pro Docs

Organization Management

TinyKit Pro provides comprehensive organization management capabilities with role-based access control and organization-scoped features.

TinyKit Pro provides comprehensive organization management capabilities with role-based access control and organization-scoped features.

Feature Toggle

Organization features can be enabled or disabled dynamically through the admin panel. This allows you to control organization functionality at runtime without code changes or redeployment.

Admin Panel Location: Admin > Site Settings > General > Feature Settings

The enableOrganizations setting is stored in the siteSettings database table and accessed via the useFeatureSettings() hook:

import { useFeatureSettings } from "@/lib/features";

function MyComponent() {
  const { enableOrganizations, isLoading } = useFeatureSettings();

  if (isLoading) return <Skeleton />;
  if (!enableOrganizations) return null;

  return <OrganizationSection />;
}

When organizations are disabled:

  • Organization-related UI elements are hidden throughout the application
  • Admin panel hides organization-specific sections and statistics
  • Product listings hide organization plans (personal plans only)
  • Navigation menus exclude organization-related links
  • The Convex backend remains intact for potential future re-enabling
  • Changes take effect immediately for all connected clients (real-time)

Note: Landing page pricing is controlled separately by the defaultSubscriptionType setting in the admin billing settings.

Organization Features

  • 👥 Organization Creation: Users can create and manage organizations
  • 🎭 Role-Based Access: Owner, Admin, Member roles with clear permissions
  • 📨 Organization Invitations: Email-based invitation system with expiration
  • ⚙️ Organization Settings: Customizable organization configuration and branding
  • 🔗 Custom Slugs: Human-readable URLs for organization workspaces

Organization Structure

Better Auth Component Architecture

Organization data is stored in the Better Auth component database (convex/betterAuth/schema.ts), separate from the main application schema. This provides seamless integration with Better Auth's session management and organization features.

Organization Data Model

// Stored in Better Auth component: `organization` table
interface Organization {
  _id: string; // Better Auth component ID (not Convex Id type)
  name: string; // Organization display name
  slug: string; // URL-safe identifier (unique, indexed)
  logo?: string | null; // Logo URL
  description?: string | null; // Optional organization description (TinyKit custom field)
  logoStorageId?: string | null; // Convex storage ID for uploaded logos (TinyKit custom field)
  createdAt: number; // Creation timestamp
  metadata?: string | null; // JSON metadata from Better Auth
}

Organization Membership

// Stored in Better Auth component: `member` table
interface Member {
  _id: string; // Better Auth component ID
  organizationId: string; // Organization reference
  userId: string; // User reference
  role: "owner" | "admin" | "member"; // Organization role
  createdAt: number; // When user joined organization
}

Accessing Organization Data

// From frontend - use Better Auth client
import { authClient } from "@/lib/auth-client";

// List user's organizations
const { data: orgs } = await authClient.organization.list();

// Get full organization details
const { data: fullOrg } = await authClient.organization.getFullOrganization();

// From backend - use internal helpers
import { getOrganizationById } from "./orgs/helpers";
const org = await getOrganizationById(ctx, orgId);

Organization Roles & Permissions

Organization Roles

  • Owner: Full organization control

    • Delete organization
    • Manage billing and subscriptions
    • Change organization settings
    • Manage all members and roles
    • Transfer ownership
    • Cannot leave organization (must transfer ownership or delete organization first)
  • Admin: Organization management

    • Invite and remove members
    • Update organization settings
    • Manage organization content
    • Change member roles (except Owner)
    • Can leave organization (with confirmation dialog)
  • Member: Basic access

    • View organization information
    • Participate in organization chat
    • Access organization content
    • Update own profile within organization
    • Can leave organization (with confirmation dialog)

Permission Patterns

// Backend permission checking
export const updateOrgSettings = mutation({
  args: {
    orgId: v.string(), // Organization IDs are strings (Better Auth component)
    settings: v.object({ name: v.string() })
  },
  handler: async (ctx, args) => {
    // Check if user has permission to update organization
    const { userId, orgRole } = await requireAccess(ctx, {
      orgId: args.orgId,
      orgRole: ["owner", "admin"]
    });

    // Update via Better Auth API or internal helpers
    // (not direct ctx.db.patch - org data is in component database)
    return await updateOrganization(ctx, args.orgId, args.settings);
  },
});

// Frontend permission checking
const { hasAccess } = useAccess();

{hasAccess({
  orgId: org._id,
  orgRole: ["owner", "admin"]
}) && (
  <OrgSettingsButton orgId={org._id} />
)}

{hasAccess({
  orgId: org._id,
  orgRole: "owner"
}) && (
  <DangerZone orgId={org._id} />
)}

Organization Creation & Management

Creating Organizations

// Organization creation with automatic owner role assignment
const createOrg = useMutation(api.orgs.mutations.create);

await createOrg({
  name: "My Awesome Organization",
  description: "Building great products together",
});

Organization Settings

Organizations can be configured through the organization settings interface:

  • Basic Information: Name, description, slug
  • Member Management: View, invite, remove, and change roles
  • Organization Preferences: Notifications, privacy settings
  • Danger Zone: Organization deletion (owner only)

Custom Organization Slugs

  • Auto-generated: Organizations get URL-safe slugs from their names
  • Customizable: Owners can update slugs for better branding
  • Unique: System ensures slug uniqueness across all organizations
  • URL Structure: /orgs/[orgSlug]/feature

Member Management

Leave Organization Feature

Members and admins can leave organizations they belong to, with built-in safeguards:

  • Owner Restrictions: Organization owners cannot leave and must either transfer ownership or delete the organization
  • Confirmation Dialog: Uses AlertDialog component instead of JavaScript confirm() for better UX
  • Immediate Effect: Leaving immediately removes access to all organization resources
  • Data Persistence: Organization data remains intact after members leave
// Leave organization implementation
const leaveOrg = useMutation(api.orgs.private.mutations.leaveOrg);

const handleLeaveOrg = async () => {
  try {
    await leaveOrg({ orgId });
    router.push("/orgs"); // Redirect to organizations list
  } catch (error) {
    console.error("Failed to leave organization:", error);
  }
};

Organization Access Control

  • Middleware Protection: Non-members are automatically redirected when trying to access organization pages
  • Real-time Verification: Organization membership is verified on every protected route
  • Graceful Handling: Invalid organization access redirects to /home for authenticated users or / for unauthenticated users

Organization Invitations

Enhanced Invitation System

  • Email-based: Invitations sent via email to potential members
  • Role Assignment: Specify role when sending invitation
  • Expiration: Invitations expire after 7 days
  • Secure Links: One-time use invitation links
  • Re-invitation Support: Users who previously left can be re-invited without conflicts

Invitation Workflow

  1. Send Invitation: Organization admin/owner sends invitation with role
  2. Email Delivery: Beautiful HTML email with invitation details
  3. Accept/Decline: Recipient clicks link to accept or decline
  4. Auto-Assignment: Accepting automatically adds user to organization
  5. Notification: Organization receives notification of new member

Invitation Management

// Send organization invitation (supports re-inviting previous members)
const sendInvitation = useMutation(api.orgs.private.mutations.inviteMember);

await sendInvitation({
  orgId: org._id,
  email: "newmember@example.com",
  orgRole: "member",
});

// Accept invitation
const acceptInvitation = useMutation(
  api.orgs.private.mutations.acceptInvitation,
);

await acceptInvitation({
  token: invitation.token,
});

Re-invitation Capabilities

The system now supports re-inviting users who have previously:

  • Left the organization: Members who left can be invited back
  • Declined invitations: Users who previously declined can receive new invitations
  • Had expired invitations: No need to clean up expired invitations before re-inviting

Technical Implementation: Only pending status invitations block new invitations, allowing full invitation lifecycle management without conflicts.

Organization-Scoped Development

URL Structure

All organization features follow consistent URL patterns:

/orgs/[orgSlug]/          # Organization dashboard
/orgs/[orgSlug]/settings  # Organization settings
/orgs/[orgSlug]/members   # Member management
/orgs/[orgSlug]/billing   # Billing (owners only)

Organization Context Pattern

// Extract organization from URL params
const OrgPage = ({ params }: { params: { orgSlug: string } }) => {
  const org = useQuery(api.orgs.queries.getBySlug, {
    slug: params.orgSlug,
  });

  const currentUser = useQuery(api.users.private.queries.getMe);
  const { hasAccess } = useAccess();

  if (!org || !currentUser) {
    return <LoadingSpinner />;
  }

  // Check organization access
  const hasOrgAccess = hasAccess({
    orgId: org._id,
    orgRole: ["owner", "admin", "member"]
  });

  if (!hasOrgAccess) {
    return <AccessDenied />;
  }

  return (
    <div>
      <OrgDashboard org={org} />
      {hasAccess({
        orgId: org._id,
        orgRole: "owner"
      }) && <OwnerControls />}
    </div>
  );
};

Backend Organization Scoping

// Always verify organization access in backend functions
export const getOrgData = query({
  args: { orgId: v.string() }, // Organization IDs are strings (Better Auth component)
  handler: async (ctx, args) => {
    // Check if user has access to this organization
    const { userId, orgRole } = await requireAccess(ctx, {
      orgId: args.orgId,
      orgRole: ["owner", "admin", "member"],
    });

    // Return organization-scoped data from main app schema
    // (orgId is stored as string referencing Better Auth org ID)
    return await ctx.db
      .query("orgData")
      .withIndex("by_org", (q) => q.eq("orgId", args.orgId))
      .collect();
  },
});

Organization Analytics & Insights

Member Activity

  • Join/Leave Tracking: Monitor organization membership changes
  • Activity Metrics: Track member participation and engagement
  • Role Changes: Audit trail for permission modifications

Organization Usage

  • Message Counts: Track organization communication activity
  • Feature Usage: Monitor which organization features are used most
  • Growth Metrics: Track organization growth and engagement over time

Best Practices

Organization Management

  1. Clear Role Definitions: Ensure organization members understand their roles and permissions
  2. Regular Cleanup: Remove inactive members periodically
  3. Invitation Hygiene: Monitor and clean up expired invitations
  4. Ownership Transfer: Have processes for transferring organization ownership

Security Considerations

  1. Permission Validation: Always validate permissions on both frontend and backend
  2. Audit Trails: Maintain logs of important organization changes
  3. Data Isolation: Ensure organization data is properly scoped and isolated
  4. Access Reviews: Regularly review organization member access

Development Guidelines

  1. Organization Context: Always extract and validate organization context from URLs
  2. Permission Checks: Implement permission checks for all organization operations
  3. Real-time Updates: Leverage Convex subscriptions for live updates
  4. Error Handling: Provide clear error messages for access denied scenarios

Organization Ownership Transfer

Overview

Organization ownership transfer allows current owners to transfer full control of the organization to another member. This feature is essential for succession planning, role transitions, and account deletion workflows.

Key Features

  • Owner-Only Operation: Only current organization owners can initiate transfers
  • Member-to-Owner Transfer: Transfer ownership to existing organization members
  • Email Notifications: Automated email notifications to both parties
  • Immediate Effect: Ownership transfer takes effect immediately
  • Role Adjustment: Previous owner becomes an admin, new owner gets full control

Transfer Process

// Organization ownership transfer
const transferOwnership = useMutation(
  api.orgs.private.mutations.transferOwnership,
);

await transferOwnership({
  orgId: org._id,
  newOwnerId: selectedMember._id,
});

Transfer Flow:

  1. Validation: Verify current user is organization owner
  2. Member Check: Confirm target user is an organization member
  3. Role Updates:
    • Previous owner → Admin role
    • New owner → Owner role
  4. Email Notifications: Send confirmation emails to both parties
  5. Immediate Access: New owner gains full organization control

Email Notifications

The system sends automated email notifications for ownership transfers:

To Previous Owner:

// Email: "Organization Ownership Transferred"
- Organization name and details
- New owner information
- New role (Admin) confirmation
- Access to organization continues with admin privileges

To New Owner:

// Email: "Organization Ownership Received"
- Organization name and details
- Previous owner information
- New responsibilities as owner
- Links to organization settings and billing

Use Cases

  • Account Deletion: Required before account deletion if user owns organizations
  • Role Transition: When responsibility changes within teams
  • Succession Planning: Planned leadership transitions
  • Business Changes: Organizational restructuring

Organization Deletion

Overview

Organization deletion permanently removes the organization and all associated data. This is a destructive operation with comprehensive safety checks.

Safety Requirements

  • Owner-Only: Only organization owners can delete organizations
  • Confirmation Required: Must type organization name exactly to confirm
  • Billing Checks: Active subscriptions must be cancelled first
  • Member Notification: All members lose access immediately
  • Permanent Action: Cannot be undone once completed

Deletion Process

// Organization deletion
const deleteOrg = useMutation(api.orgs.private.mutations.deleteOrg);

await deleteOrg({
  orgId: org._id,
  confirmationName: org.name, // Must match exactly
});

Deletion Flow:

  1. Permission Check: Verify user is organization owner
  2. Billing Validation: Ensure no active subscriptions
  3. Name Confirmation: Require exact organization name match
  4. Data Cleanup: Remove all organization-related data
  5. Member Removal: Remove all organization memberships
  6. Notification Cleanup: Clean up organization notifications

Data Cleanup Scope

When an organization is deleted, the following data is permanently removed:

// Complete organization data cleanup:
- Organization record (orgs table)
- All memberships (orgMembers table)
- Organization invitations (orgInvitations table)
- Organization-specific notifications
- Organization settings and preferences
- Associated subscription data (if any)
- Organization-specific content and files

Deletion UI

The deletion interface is located in the organization settings "Advanced" tab:

UI Elements:

  • Danger Zone Card: Clearly marked destructive operation section
  • Warning Messages: Multiple warnings about permanent data loss
  • Billing Blocks: Prevents deletion if active subscriptions exist
  • Confirmation Input: Requires typing exact organization name
  • Clear Messaging: Explains what will be deleted and impact on members

Example UI Flow:

// Deletion blocked by subscription
"Active Subscription - You must cancel your organization subscription before deleting this organization."
→ Link to billing portal

// Ready for deletion
"Delete Organization - This will permanently delete [Org Name] and remove all associated data."
→ Confirmation dialog with name input

Integration with Account Deletion

Organization deletion is tightly integrated with the account deletion system:

  • Account Deletion Prerequisite: Users cannot delete their accounts while owning organizations
  • Clear Guidance: Account deletion UI provides direct links to organization settings
  • Alternative Actions: Users can transfer ownership instead of deleting organizations
  • Safety Integration: Both systems share similar safety patterns and confirmation flows

Best Practices for Organization Management

Common Issues

Organization not found errors

  • Verify organization slug is correct and organization exists
  • Check that user has access to the organization

Permission denied errors

  • Ensure user has appropriate organization role for the operation
  • Check that organization membership is active and valid

Invitation issues

  • Verify email addresses are correct and deliverable
  • Check that invitations haven't expired
  • Ensure email system is properly configured

← Previous: Authorization | Next: Billing →

On this page

Ship your startup faster. In minutes.

Get TinyKit Pro