Skip to Content
Iris Saas Kit documentation is under construction.
How-tosCustomizing the App

Customizing the App

Transform the SaaS boilerplate into your unique application by customizing branding, features, and business logic to match your specific requirements.

🎨 Visual Customization

Brand Identity

Update Logo and Branding:

// app/layout.jsx - Update navbar logo const navbar = ( <Navbar logo={<b>Your SaaS Name</b>} // Replace "Iris Saas Kit" // ... other options /> );

Customize Colors and Theme:

/* app/globals.css - Update CSS variables */ @theme inline { --color-primary: your-brand-color; --color-secondary: your-secondary-color; /* Update other brand colors */ }

Update Metadata:

// app/layout.jsx - Update site metadata export const metadata = { title: 'Your SaaS Name', description: 'Your SaaS description', // ... other metadata };

Landing Page Customization

Update Value Proposition:

// Update app/page.mdx with your messaging # Your SaaS Name Your unique value proposition and messaging here. ## What You Get - Your specific features and benefits - Tailored to your market - Focused on your value prop

Customize Pricing Display:

The pricing automatically pulls from your Stripe configuration, but you can customize:

  • Plan names and descriptions
  • Feature lists per plan
  • Promotional messaging
  • Call-to-action buttons

🏗️ Feature Customization

Workspace Terminology

Customize for Your Domain:

// Update terminology throughout the app // Instead of "workspace" → "project", "team", "organization" // Instead of "members" → "users", "collaborators", "teammates" // Example: Update schema labels const coreSchema = { // Rename 'workspaces' to match your domain projects: defineTable({ name: v.string(), slug: v.string(), // ... other fields }), };

Customize Sidebar Navigation:

// Update navigation to match your features // app/(authenticated)/(user-app)/w/[workspace-slug]/_components/AppSidebar/ // Modify navigation items, icons, and organization

Add Custom Pages:

# Create new feature pages mkdir app/(authenticated)/(user-app)/w/[workspace-slug]/your-feature # Add page.tsx, _components/, _hooks/ as needed

Data Models and Schema

Extend Core Tables:

// convex/schema/core.ts - Add custom fields const coreSchema = { users: defineTable({ email: v.string(), name: v.string(), picture: v.optional(v.id('_storage')), // Add your custom user fields department: v.optional(v.string()), role: v.optional(v.string()), preferences: v.optional( v.object({ // Custom user preferences }) ), }), workspaces: defineTable({ name: v.string(), slug: v.string(), // Add your custom workspace fields industry: v.optional(v.string()), size: v.optional(v.string()), settings: v.optional( v.object({ // Custom workspace settings }) ), }), };

Add Custom Tables:

// convex/schema/feat.ts - Add domain-specific tables const featureSchema = { // Example: Project management SaaS projects: defineTable({ workspaceSlug: v.string(), name: v.string(), description: v.optional(v.string()), status: v.string(), // active, completed, on-hold dueDate: v.optional(v.number()), assignedTo: v.optional(v.id('users')), }) .index('workspaceSlug', ['workspaceSlug']) .index('assignedTo', ['assignedTo']), tasks: defineTable({ projectId: v.id('projects'), title: v.string(), description: v.optional(v.string()), status: v.string(), // todo, in-progress, done priority: v.string(), // low, medium, high assignedTo: v.optional(v.id('users')), createdAt: v.number(), dueDate: v.optional(v.number()), }) .index('projectId', ['projectId']) .index('assignedTo', ['assignedTo']), };

🔧 Business Logic Customization

Custom Mutations and Queries

Create Domain-Specific Functions:

// convex/core/projects.queries.ts export const getProjectsByWorkspace = workspaceMemberQuery({ args: { status: v.optional(v.string()), }, handler: async (ctx, args) => { const projects = await ctx.db .query('projects') .withIndex('workspaceSlug', q => q.eq('workspaceSlug', ctx.workspaceSlug)) .collect(); if (args.status) { return projects.filter(p => p.status === args.status); } return projects; }, });

Custom Validation Rules

Add Business-Specific Validation:

// convex/core/projects.validators.ts export const createProjectValidator = v.object({ name: v.string(), description: v.optional(v.string()), dueDate: v.optional(v.number()), // Add custom validation rules }); export const projectStatusValidator = v.union( v.literal('planning'), v.literal('active'), v.literal('completed'), v.literal('on-hold') );

Custom Permissions and Roles

Extend Role System:

// Add custom workspace roles beyond admin/member export const workspaceMemberRoleValidator = v.union( v.literal('admin'), v.literal('manager'), // New role v.literal('member'), v.literal('viewer') // New role ); // Custom permission checking export const requireManagerOrAdmin = (role: string) => { if (!['admin', 'manager'].includes(role)) { throw new ConvexError('Insufficient permissions'); } };

📧 Communication Customization

Email Templates

Customize for Your Brand:

// convex/_emailTemplates/ - Update email templates // Add your branding, messaging, and custom email types // Example: Project invitation email export const ProjectInvitationEmail = (props: { projectName: string; inviterName: string; workspaceName: string; }) => { // Custom email template for your domain };

Notification Types

Add Custom Notifications:

// convex/core/notifications.mutations.ts export const sendProjectDeadlineAlert = internalMutation({ args: { projectId: v.id('projects'), userId: v.id('users'), }, handler: async (ctx, args) => { // Custom notification logic await ctx.db.insert('workspaceNotifications', { workspaceSlug: workspace.slug, type: 'project_deadline', title: 'Project Deadline Approaching', // ... custom notification data }); }, });

🎯 Feature Gates and Subscriptions

Custom Plan Features

Define Plan-Specific Features:

// Customize Stripe metadata for your features const planFeatures = { starter: { maxProjects: 5, maxTeamMembers: 10, advancedReporting: false, customIntegrations: false, }, professional: { maxProjects: 50, maxTeamMembers: 100, advancedReporting: true, customIntegrations: false, }, enterprise: { maxProjects: -1, // unlimited maxTeamMembers: -1, // unlimited advancedReporting: true, customIntegrations: true, }, };

Usage Limits

Implement Custom Limits:

// convex/core/projects.mutations.ts export const createProject = workspaceMemberMutation({ args: createProjectValidator, handler: async (ctx, args) => { // Check subscription limits const subscription = await getWorkspaceSubscription(ctx); const currentProjects = await ctx.db .query('projects') .withIndex('workspaceSlug', q => q.eq('workspaceSlug', ctx.workspaceSlug)) .collect(); const planLimits = getPlanLimits(subscription.planId); if ( planLimits.maxProjects !== -1 && currentProjects.length >= planLimits.maxProjects ) { throw new ConvexError('Project limit reached for your plan'); } // Create project if within limits return await ctx.db.insert('projects', { workspaceSlug: ctx.workspaceSlug, ...args, createdAt: Date.now(), }); }, });

🔌 Integrations

Third-Party Services

Add Custom Integrations:

// convex/core/integrations.actions.ts export const syncWithCRM = action({ args: { workspaceSlug: v.string(), crmData: v.object({ // Custom CRM data structure }), }, handler: async (ctx, args) => { // Custom integration logic const response = await fetch(CRM_API_URL, { method: 'POST', headers: { Authorization: `Bearer ${CRM_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(args.crmData), }); return await response.json(); }, });

📱 Mobile and API Customization

API Endpoints

Create Custom API Routes:

// app/api/webhooks/your-service/route.ts export async function POST(request: Request) { // Custom webhook handling for your integrations const payload = await request.json(); // Process webhook and update Convex await ctx.runMutation(internal.yourDomain.processWebhook, { data: payload, }); return new Response('OK'); }

🚀 Deployment Customization

Environment Configuration

Set Custom Environment Variables:

# Add your service-specific environment variables npx convex env set YOUR_API_KEY=your-api-key npx convex env set CUSTOM_SERVICE_URL=https://api.yourservice.com

Domain and Branding

Configure for Production:

# Set up custom domain vercel domains add yoursaas.com # Update Convex SITE_URL npx convex env set --prod SITE_URL=https://yoursaas.com

📋 Customization Checklist

Brand and Identity

  • Update logo and company name throughout
  • Customize color scheme and theme
  • Update landing page messaging
  • Customize email templates with your branding

Features and Functionality

  • Rename “workspaces” to your domain terminology
  • Add custom database tables for your features
  • Implement domain-specific business logic
  • Create custom user roles and permissions

Integrations and Services

  • Add required third-party integrations
  • Configure custom notification types
  • Set up domain-specific webhooks
  • Implement custom API endpoints

Subscription and Billing

  • Configure Stripe products for your pricing
  • Define plan-specific feature gates
  • Implement usage limits and quotas
  • Customize billing and subscription flows

Ready to start building? Begin with your highest priority feature and use the Task-Driven Development workflow to implement it systematically.


Previous: Setting up Payment completed your SaaS foundation Context: Build Product Context defined your unique features
Next: Start building with Technical Documentation as your reference

Last updated on