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
/homefor 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
- Send Invitation: Organization admin/owner sends invitation with role
- Email Delivery: Beautiful HTML email with invitation details
- Accept/Decline: Recipient clicks link to accept or decline
- Auto-Assignment: Accepting automatically adds user to organization
- 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
- Clear Role Definitions: Ensure organization members understand their roles and permissions
- Regular Cleanup: Remove inactive members periodically
- Invitation Hygiene: Monitor and clean up expired invitations
- Ownership Transfer: Have processes for transferring organization ownership
Security Considerations
- Permission Validation: Always validate permissions on both frontend and backend
- Audit Trails: Maintain logs of important organization changes
- Data Isolation: Ensure organization data is properly scoped and isolated
- Access Reviews: Regularly review organization member access
Development Guidelines
- Organization Context: Always extract and validate organization context from URLs
- Permission Checks: Implement permission checks for all organization operations
- Real-time Updates: Leverage Convex subscriptions for live updates
- 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:
- Validation: Verify current user is organization owner
- Member Check: Confirm target user is an organization member
- Role Updates:
- Previous owner → Admin role
- New owner → Owner role
- Email Notifications: Send confirmation emails to both parties
- 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 privilegesTo New Owner:
// Email: "Organization Ownership Received"
- Organization name and details
- Previous owner information
- New responsibilities as owner
- Links to organization settings and billingUse 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:
- Permission Check: Verify user is organization owner
- Billing Validation: Ensure no active subscriptions
- Name Confirmation: Require exact organization name match
- Data Cleanup: Remove all organization-related data
- Member Removal: Remove all organization memberships
- 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 filesDeletion 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 inputIntegration 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
Authorization System
TinyKit Pro implements a simplified role-based authorization system integrated with Better Auth's built-in access control. The system combines Better Auth ro...
Billing & Subscription Management
TinyKit Pro includes comprehensive Stripe integration for subscription billing, customer management, and payment processing.