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
}),
};
Navigation and Menu Structure
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