Creating Custom Permission Seeds for Supamode

Understanding how to create a seed for your Supamode application

In this comprehensive guide, we'll explore how to create tailored permission structures that perfectly match your organization's unique access control requirements.

Key Learning Objectives:

  • Master the Supamode seed generation architecture
  • Build custom permission hierarchies from scratch
  • Implement business-specific access control patterns
  • Deploy scalable, maintainable permission structures

Understanding Supamode's Seed Architecture 🏗️

Before diving into custom implementations, let's understand how Supamode's seed system works under the hood.

The default seed schema is a simplified version of the Makerkit SaaS Starter Kit. Therefore, the default seed file is based off of that.

The recommendation is that instead you modify the default seed with mock data created based off your own schema.

The Seed Generator Pattern

Supamode uses a builder pattern to construct permission structures programmatically:

// Core Architecture
const app = new SupamodeSeedGenerator();
// 1. Create entities
const role = Role.create({ app, id: 'custom_role', config: {...} });
const permission = Permission.create({ app, id: 'custom_perm', config: {...} });
// 2. Establish relationships
role.addPermission(permission);
// 3. Generate SQL
const sql = app.generateSql();

Key Benefits:

  • Type Safety - TypeScript ensures correct configuration
  • Relationship Validation - Prevents orphaned permissions
  • SQL Generation - Automatically creates deployment scripts
  • Reusability - Seed templates can be shared across projects

Seed File Structure

Every seed file follows this consistent pattern:

// 1. Import the generator classes
import { Account, Permission, PermissionGroup, Role, SupamodeSeedGenerator } from '../generator';
// 2. Initialize the generator
const app = new SupamodeSeedGenerator();
// 3. Define your structure (accounts, roles, permissions, groups)
// ... your custom definitions ...
// 4. Export the configured generator
export default app;

Building Your First Custom Seed 🚀

Let's create a practical example: a content management system with editorial workflows.

Step 1: Define Your Business Requirements

Scenario: Online magazine with the following roles:

  • Editor-in-Chief - Full editorial control
  • Senior Editor - Manages content and junior staff
  • Staff Writer - Creates and edits own content
  • Freelancer - Limited content creation
  • Subscriber - Read-only access to published content

Step 2: Create the Seed File

  1. Create packages/schema/src/templates/editorial-seed.ts
  2. Add the following code to define roles, permissions, and groups:
import {
Account,
Permission,
PermissionGroup,
Role,
SupamodeSeedGenerator,
} from '../generator';
const app = new SupamodeSeedGenerator();
// ========================================
// SECTION 1: DEFINE ROLES
// ========================================
const editorInChiefRole = Role.create({
app,
id: 'editor_in_chief',
config: {
name: 'Editor-in-Chief',
description: 'Full editorial control and staff management',
priority: 95, // High authority
metadata: {
department: 'Editorial',
can_publish: true
}
},
});
const seniorEditorRole = Role.create({
app,
id: 'senior_editor',
config: {
name: 'Senior Editor',
description: 'Content oversight and team coordination',
priority: 80,
metadata: {
department: 'Editorial',
can_assign_stories: true
}
},
});
const staffWriterRole = Role.create({
app,
id: 'staff_writer',
config: {
name: 'Staff Writer',
description: 'Full-time content creator',
priority: 60,
metadata: {
employment_type: 'full_time'
}
},
});
const freelancerRole = Role.create({
app,
id: 'freelancer',
config: {
name: 'Freelancer',
description: 'Contract-based content contributor',
priority: 40,
metadata: {
employment_type: 'contract'
}
},
});
const subscriberRole = Role.create({
app,
id: 'subscriber',
config: {
name: 'Subscriber',
description: 'Registered reader with premium access',
priority: 20
},
});

Step 3: Create Business-Specific Permissions

Now, let's define the permissions that align with our editorial workflow.

// ========================================
// SECTION 2: CONTENT PERMISSIONS
// ========================================
// Article Management
const createArticles = Permission.createDataPermission({
app,
id: 'create_articles',
name: 'Create Articles',
description: 'Can create new article drafts',
scope: 'table',
schema_name: 'public',
table_name: 'articles',
action: 'insert'
});
const editOwnArticles = Permission.createDataPermission({
app,
id: 'edit_own_articles',
name: 'Edit Own Articles',
description: 'Can edit articles they authored',
scope: 'table',
schema_name: 'public',
table_name: 'articles',
action: 'update',
conditions: { author_id: '$CURRENT_USER_ID' } // Row-level security
});
const editAllArticles = Permission.createDataPermission({
app,
id: 'edit_all_articles',
name: 'Edit All Articles',
description: 'Can edit any article regardless of author',
scope: 'table',
schema_name: 'public',
table_name: 'articles',
action: 'update'
});
const publishArticles = Permission.createDataPermission({
app,
id: 'publish_articles',
name: 'Publish Articles',
description: 'Can change article status to published',
scope: 'column',
schema_name: 'public',
table_name: 'articles',
column_name: 'status',
action: 'update',
conditions: { status: { $in: ['draft', 'review', 'published'] } }
});
// User Management Permissions
const manageEditorialStaff = Permission.createSystemPermission({
app,
id: 'manage_editorial_staff',
resource: 'account',
action: 'update',
name: 'Manage Editorial Staff',
description: 'Can update staff member accounts and assignments'
});
const viewAnalytics = Permission.createDataPermission({
app,
id: 'view_analytics',
name: 'View Analytics',
description: 'Access to content performance metrics',
scope: 'table',
schema_name: 'public',
table_name: 'article_analytics',
action: 'select'
});

Step 4: Organize with Permission Groups

Permission groups help us manage related permissions together, making it easier to assign them to roles.

// ========================================
// SECTION 3: PERMISSION GROUPS
// ========================================
const editorialManagementGroup = PermissionGroup.create({
app,
id: 'editorial_management',
config: {
name: 'Editorial Management',
description: 'Full editorial oversight and staff management',
},
});
editorialManagementGroup.addPermissions([
createArticles,
editAllArticles,
publishArticles,
manageEditorialStaff,
viewAnalytics
]);
const contentCreationGroup = PermissionGroup.create({
app,
id: 'content_creation',
config: {
name: 'Content Creation',
description: 'Core content creation and editing capabilities',
},
});
contentCreationGroup.addPermissions([
createArticles,
editOwnArticles,
viewAnalytics
]);
const readOnlyGroup = PermissionGroup.create({
app,
id: 'subscriber_access',
config: {
name: 'Subscriber Access',
description: 'Read access to published content',
},
});
// Only published articles for subscribers
const readPublishedArticles = Permission.createDataPermission({
app,
id: 'read_published_articles',
name: 'Read Published Articles',
description: 'Can view published articles only',
scope: 'table',
schema_name: 'public',
table_name: 'articles',
action: 'select',
conditions: { status: 'published' }
});
readOnlyGroup.addPermissions([readPublishedArticles]);

Step 5: Assign Groups to Roles

Now that we have our roles, permissions, and groups defined, we can assign them to the appropriate roles.

// ========================================
// SECTION 4: ROLE ASSIGNMENTS
// ========================================
// Editor-in-Chief gets full editorial management
editorInChiefRole.addPermissionGroup({ group: editorialManagementGroup });
// Senior Editors get content creation + some management permissions
seniorEditorRole.addPermissionGroup({ group: contentCreationGroup });
seniorEditorRole.addPermission(editAllArticles); // Can edit any article
seniorEditorRole.addPermission(publishArticles); // Can publish
// Staff Writers get core content creation
staffWriterRole.addPermissionGroup({ group: contentCreationGroup });
// Freelancers get limited content creation (no analytics)
freelancerRole.addPermission(createArticles);
freelancerRole.addPermission(editOwnArticles);
// Subscribers get read-only access
subscriberRole.addPermissionGroup({ group: readOnlyGroup });

Step 6: Create Test Accounts

We can now create test accounts for our test users. We can either reference existing accounts by passing their IDs, or create new ones directly in the seed file.

  • if we pass an ID - we expect the user to exist in the database
  • if the ID does not exist, we will create a new account with the provided metadata.
// ========================================
// SECTION 5: TEST ACCOUNTS
// ========================================
const editorAccount = Account.create({
app,
id: '550e8400-e29b-41d4-a716-446655440001',
config: {
metadata: {
display_name: 'Sarah Johnson',
email: 'sarah.johnson@magazine.com',
department: 'Editorial',
hire_date: '2023-01-15'
},
},
});
const writerAccount = Account.create({
app,
id: '550e8400-e29b-41d4-a716-446655440002',
config: {
metadata: {
display_name: 'Mike Chen',
email: 'mike.chen@magazine.com',
department: 'Editorial',
specialization: 'Technology'
},
},
});
const freelancerAccount = Account.create({
app,
id: '550e8400-e29b-41d4-a716-446655440003',
config: {
metadata: {
display_name: 'Alex Rivera',
email: 'alex.rivera@freelance.com',
contract_type: 'per_article'
},
},
});
// Assign roles to accounts
editorAccount.assignRole(editorInChiefRole);
writerAccount.assignRole(staffWriterRole);
freelancerAccount.assignRole(freelancerRole);
export default app;

Advanced Permission Patterns 🎯

Now let's explore sophisticated patterns for complex business requirements.

Time-Based Access Control

Perfect for temporary permissions or seasonal access:

// Temporary elevated access for special projects
const specialProjectRole = Role.create({
app,
id: 'special_project_lead',
config: {
name: 'Special Project Lead',
description: 'Temporary leadership role for Q4 campaign',
priority: 85,
valid_from: new Date('2025-10-01'),
valid_until: new Date('2025-12-31'), // Expires automatically
metadata: {}
},
});

Conditional Permissions with Row-Level Security

Implement sophisticated access patterns:

// Writers can only edit articles in 'draft' or 'revision' status
const editDraftArticles = Permission.createDataPermission({
app,
id: 'edit_draft_articles',
name: 'Edit Draft Articles',
description: 'Can edit articles that are not yet published',
scope: 'table',
schema_name: 'public',
table_name: 'articles',
action: 'update',
conditions: {
author_id: '$CURRENT_USER_ID',
status: { $in: ['draft', 'revision'] },
created_at: { $gte: '$CURRENT_DATE - INTERVAL 30 days' } // Recent articles only
}
});

Permission Overrides for Exceptional Cases

Handle special circumstances gracefully:

// Grant temporary admin access to a writer for migration
writerAccount.grantPermission({
permission: editAllArticles,
valid_until: new Date('2025-03-31'),
metadata: {
reason: 'Database migration assistance',
approved_by: 'editor_in_chief',
ticket_id: 'MAINT-2025-001'
}
});
// Revoke specific permission (emergency access control)
freelancerAccount.denyPermission({
permission: createArticles,
valid_until: new Date('2025-02-15'),
metadata: {
reason: 'Contract dispute - suspend access pending resolution',
approved_by: 'legal_department'
}
});

Business-Specific Seed Examples 📊

E-Commerce Platform Seed

// Roles for online store management
const storeManagerRole = Role.create({
app,
id: 'store_manager',
config: {
name: 'Store Manager',
description: 'Full store operations management',
priority: 90
}
});
// Inventory permissions
const manageInventory = Permission.createDataPermission({
app,
id: 'manage_inventory',
name: 'Manage Inventory',
description: 'Full inventory control including stock levels',
scope: 'table',
schema_name: 'public',
table_name: 'products',
action: '*'
});
// Customer service permissions
const viewCustomerOrders = Permission.createDataPermission({
app,
id: 'view_customer_orders',
name: 'View Customer Orders',
description: 'Access customer order history for support',
scope: 'table',
schema_name: 'public',
table_name: 'orders',
action: 'select',
conditions: {
status: { $in: ['pending', 'processing', 'shipped'] }
}
});

Healthcare System Seed

// HIPAA-compliant role structure
const doctorRole = Role.create({
app,
id: 'doctor',
config: {
name: 'Doctor',
description: 'Licensed physician with patient care access',
priority: 85,
metadata: {
license_required: true,
hipaa_certified: true
}
}
});
// Restricted patient data access
const viewPatientRecords = Permission.createDataPermission({
app,
id: 'view_patient_records',
name: 'View Patient Records',
description: 'Access to assigned patient medical records',
scope: 'table',
schema_name: 'medical',
table_name: 'patient_records',
action: 'select',
conditions: {
// Only patients assigned to this doctor
assigned_doctor_id: '$CURRENT_USER_ID',
// Only active cases
case_status: 'active'
}
});

SaaS Multi-Tenant Seed

// Tenant-isolated permissions
const tenantAdminRole = Role.create({
app,
id: 'tenant_admin',
config: {
name: 'Tenant Administrator',
description: 'Admin access within tenant boundary',
priority: 80
}
});
const manageTenantData = Permission.createDataPermission({
app,
id: 'manage_tenant_data',
name: 'Manage Tenant Data',
description: 'Full access to tenant-specific data',
scope: 'table',
schema_name: 'public',
table_name: '*',
action: '*',
conditions: {
// Tenant isolation
tenant_id: '$CURRENT_USER_TENANT_ID'
}
});

Generating and Deploying Your Custom Seed 🚀

Step 1: Generate Your Seed

# Generate your custom seed
pnpm --filter '@kit/schema' schema:gen-seed -- --seed editorial --output out/editorial-permissions.sql

The command will:

  1. Execute the schema:gen-seed command from the @kit/schema package.
  2. Use the editorial seed template defined in packages/schema/src/templates/editorial-seed.ts.
  3. Output the generated SQL to out/editorial-permissions.sql.

Step 2: Create Migration

In the next step, we will create a migration to apply this seed to your Supabase database.

# Create Supabase migration
supabase migration new editorial_permissions_setup

Now copy the generated SQL file name into the newly created migration file.

# Add your generated SQL
cp packages/schema/out/editorial-permissions.sql supabase/migrations/[timestamp]_editorial_permissions_setup.sql

NB: Replace [timestamp] with the actual timestamp of your migration file. This is printed after you run the migration new command.

Step 4: Deploy to Database

# Deploy to your Supabase project
supabase db push
# Verify deployment
supabase migration list

Best Practices for Custom Seeds 📋

1. Planning Your Permission Structure

Start with User Stories:

As a [role], I need to [action] so that [business value]
Examples:
- As a Staff Writer, I need to create article drafts so that I can contribute content
- As a Senior Editor, I need to publish articles so that content goes live
- As a Freelancer, I need to edit my drafts so that I can refine my work

Map Business Processes:

  • Who creates content?
  • Who approves it?
  • Who can publish it?
  • What are the approval workflows?

2. Naming Conventions

Consistent Naming Patterns:

// Role naming: [Level]_[Function]
'senior_editor', 'staff_writer', 'content_manager'
// Permission naming: [Action]_[Resource]
'create_articles', 'publish_content', 'manage_users'
// Group naming: [Domain]_[Scope]
'editorial_management', 'content_creation', 'user_administration'

3. Security Considerations

Principle of Least Privilege:

// ❌ Avoid: Too broad permissions
const badPermission = Permission.createDataPermission({
name: 'Manage Everything',
action: '*',
table_name: '*' // Too permissive!
});
// ✅ Good: Specific, scoped permissions
const goodPermission = Permission.createDataPermission({
name: 'Edit Own Articles',
action: 'update',
table_name: 'articles',
conditions: { author_id: '$CURRENT_USER_ID' }
});

Always Include Time Bounds for Elevated Access:

// Temporary elevated permissions should always expire
const tempAdminAccess = {
valid_until: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
metadata: { reason: 'Emergency system maintenance' }
};

Next Steps: Advanced Customization 🎯

Dynamic Permission Assignment

Create permissions that adapt to business logic:

// Example: Seasonal permissions for holiday content
const createHolidayContent = Permission.createDataPermission({
app,
id: 'create_holiday_content',
name: 'Create Holiday Content',
description: 'Can create holiday-themed articles during Q4',
scope: 'table',
schema_name: 'public',
table_name: 'articles',
action: 'insert',
conditions: {
category: 'holiday',
// Only during Q4
created_at: {
$gte: '2025-10-01',
$lte: '2025-12-31'
}
}
});

Remember: A well-designed permission seed is the foundation of a secure, scalable application. Take time to model your organization's actual workflows and access patterns before implementing!