Teams & Organizations
indiegoon includes a complete multi-tenant team system. Users can create teams, invite members, assign roles, and manage permissions.
Features
- •Create teams with auto-generated slugs
- •Three roles: Owner, Admin, Member
- •Email invitations with 7-day expiry tokens
- •Role-based access control — owners and admins can manage, members can view
- •Team switching — users can belong to multiple teams
- •Team deletion — cascade deletes all members and invitations
Database Schema
Three tables power the team system:
// teams — the workspace itself
{ id, name, slug, logo, createdAt, updatedAt }
// teamMembers — who belongs to what team
{ id, teamId, userId, role, createdAt, updatedAt }
// teamInvitations — pending invites
{ id, teamId, email, role, token, expiresAt, acceptedAt, createdAt }
Using Teams in Your Code
Create a Team
import { createTeam } from "@/lib/teams"
const team = await createTeam("Acme Corp", userId)
// → { id: "...", name: "Acme Corp", slug: "acme-corp", ... }
// Creator is automatically added as "owner"
Get User's Teams
import { getUserTeams } from "@/lib/teams"
const teams = await getUserTeams(userId)
// → [{ team: {...}, role: "owner" }, { team: {...}, role: "member" }]
Check Permissions
import { canManageTeam, getTeamRole } from "@/lib/teams"
// Can this user edit team settings?
const canEdit = await canManageTeam(teamId, userId)
// true for owners and admins, false for members
// Get specific role
const role = await getTeamRole(teamId, userId)
// "owner" | "admin" | "member" | null
Invite a Member
import { inviteToTeam } from "@/lib/teams"
const invitation = await inviteToTeam(teamId, "new@member.com", "member")
// Creates a token, valid for 7 days
// You'd send an email with a link: /teams/invite?token=...
Accept an Invitation
import { acceptInvitation } from "@/lib/teams"
await acceptInvitation(token, userId)
// Validates token, checks expiry, adds user to team
Manage Members
import { updateMemberRole, removeTeamMember } from "@/lib/teams"
// Promote to admin
await updateMemberRole(teamId, memberId, "admin")
// Remove from team
await removeTeamMember(teamId, memberId)
API Routes
| Method | Endpoint | Permission | Action |
|---|---|---|---|
| GET | /api/teams | Authenticated | List user's teams |
| POST | /api/teams | Authenticated | Create team |
| GET | /api/teams/[teamId] | Member | Get team details |
| PATCH | /api/teams/[teamId] | Admin/Owner | Update team |
| DELETE | /api/teams/[teamId] | Owner | Delete team |
| POST | /api/teams/[teamId]/invite | Admin/Owner | Send invitation |
Pre-Built Pages
The template includes these team pages:
- •
/teams— List all your teams, create new ones - •
/teams/new— Create team form - •
/teams/[teamSlug]— Team overview - •
/teams/[teamSlug]/members— View and manage members - •
/teams/[teamSlug]/settings— Team settings (name, logo, danger zone)
Adding Team-Scoped Data
Want projects, documents, or other resources scoped to a team? Add a teamId foreign key:
// src/lib/db/schema/projects.ts
export const projects = pgTable("projects", {
id: text("id").primaryKey(),
name: text("name").notNull(),
teamId: text("team_id").references(() => teams.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow(),
})
Then query by team:
const teamProjects = await db.query.projects.findMany({
where: eq(projects.teamId, teamId),
})
Role-Based UI
Show/hide UI elements based on role:
"use client"
export function TeamSettings({ teamId, userRole }) {
if (userRole === "member") {
return <p>You don't have permission to manage settings.</p>
}
return (
<form>
{/* Admin + Owner can see this */}
<input name="name" />
{/* Only Owner can delete */}
{userRole === "owner" && (
<button className="text-red-500">Delete Team</button>
)}
</form>
)
}
Next Steps
- •Admin Panel — Manage users and subscriptions
- •CLI Reference — All goon commands