ORGANIZATION MANAGEMENT - nself-org/cli GitHub Wiki
nself v0.8.0 - Complete guide to organizations, teams, and RBAC
Note: As of v0.9.6, organization commands have been consolidated under
nself tenant org. Throughout this guide,nself orgrefers tonself tenant orgin the new command structure.
- Overview
- Core Concepts
- Organization Structure
- Team Management
- CLI Usage
- Permission System
- Access Control Patterns
- Integration with Other Systems
- Best Practices
- Advanced Topics
Organizations in nself provide a complete workspace management and access control system. They enable you to:
- Structure teams hierarchically within your company or project
- Manage member access and roles at multiple levels
- Control resource permissions with fine-grained RBAC
- Span multiple tenants under a single organizational umbrella
- Implement enterprise-grade access control patterns
Organizations are the top-level entity in nself's workspace hierarchy. Think of them as:
- A company or business unit
- A project workspace
- A client account
- Any grouping of users and resources that need centralized management
Organizations and tenants have a many-to-many relationship:
Organization "Acme Corp"
โโโ Tenant: Production Database
โโโ Tenant: Staging Environment
โโโ Tenant: Development Sandbox
Organization "Project Phoenix"
โโโ Tenant: Customer Portal
โโโ Tenant: Admin Dashboard
Key difference:
- Tenants = Data isolation (separate database schemas)
- Organizations = Team structure and access control
Corporate Structure
Organization: Acme Corporation
โโโ Team: Engineering
โโโ Team: Sales
โโโ Team: Support
โโโ Members with different roles and permissions
Project Management
Organization: Project Alpha
โโโ Team: Backend Developers
โโโ Team: Frontend Developers
โโโ Team: QA Team
โโโ Each team has access to specific resources
Access Control
Organization: SaaS Platform
โโโ Owner: Full control
โโโ Admins: Manage members and settings
โโโ Members: Standard access
โโโ Guests: Read-only access
Definition: Top-level workspace containing teams, members, and resources.
Properties:
-
id- Unique UUID identifier -
slug- Human-readable URL identifier (e.g., "acme-corp") -
name- Display name (e.g., "Acme Corporation") -
status-active,suspended, ordeleted -
billing_plan-free,pro,enterprise, etc. -
owner_user_id- User who owns the organization -
settings- JSONB metadata for custom configuration -
metadata- Additional JSONB data
Lifecycle:
- Created - Organization is initialized with an owner
- Active - Normal operation with members and teams
- Suspended - Temporarily disabled (e.g., billing issues)
- Deleted - Soft-deleted, can be recovered
Definition: Sub-groups within an organization for logical grouping of members.
Properties:
-
id- Unique UUID identifier -
org_id- Parent organization -
name- Team display name (e.g., "Engineering") -
slug- URL-safe identifier (e.g., "engineering") -
description- Optional team description -
settings- JSONB configuration
Team Roles:
- Lead - Team manager with full team control
- Member - Standard team member
Definition: Users who belong to organizations and teams.
Organization Membership:
-
user_id- Reference to auth.users -
org_id- Organization they belong to -
role- Organization-level role -
joined_at- Timestamp of joining -
invited_by- User who invited them
Team Membership:
-
user_id- Reference to auth.users -
team_id- Team they belong to -
role- Team-level role (lead or member) -
added_by- User who added them to team
Definition: Named collections of permissions that can be assigned to users.
Built-in Organization Roles:
- Owner - Created the org, full control, cannot be removed
- Admin - Nearly full control, can manage members and settings
- Member - Standard access, can use resources
- Guest - Limited read-only access
Custom Roles:
- Created per-organization
- Composed of granular permissions
- Can be assigned to users with scopes
Definition: Granular access controls in resource:action format.
Permission Format:
{resource_type}.{action}
Examples:
-
tenant.create- Can create new tenants -
user.delete- Can delete users -
team.manage- Full team management -
org.billing- Manage billing settings
Permissions can be granted at different levels:
- Global - Applies to all resources in the organization
- Tenant - Scoped to a specific tenant
- Team - Scoped to a specific team
Example:
-- Global permission: can manage ALL teams
scope = 'global', scope_id = NULL
-- Team-specific permission: can only manage Engineering team
scope = 'team', scope_id = 'engineering-team-uuid'# Create with auto-generated slug
nself tenant org create "Acme Corporation"
# Create with custom slug
nself tenant org create "Acme Corporation" --slug acme
# Create with specific owner
nself tenant org create "Acme Corp" --slug acme --owner <user_uuid>What happens:
- Organization record created
- Owner automatically added as member with
ownerrole - Slug generated (or uses your custom slug)
- Default organization created on first run
Organizations include flexible settings stored as JSONB:
-- Example settings
{
"branding": {
"logo_url": "https://...",
"primary_color": "#007bff"
},
"features": {
"sso_enabled": true,
"audit_logs_retention": 90
},
"notifications": {
"email": "[email protected]",
"slack_webhook": "https://..."
}
}# Table format
nself tenant org list
# JSON output
nself tenant org list --jsonOutput:
id | slug | name | status | billing_plan | created_at
--------------------------------------+-------------+-------------------+--------+--------------+---------------------------
a1b2c3d4-... | acme | Acme Corporation | active | free | 2026-01-15 10:30:00+00
e5f6g7h8-... | project-x | Project X | active | pro | 2026-01-20 14:00:00+00
# By ID or slug
nself tenant org show acme
nself tenant org show a1b2c3d4-e5f6-...Output includes:
- Full organization details
- Member count
- Team count
- Billing information
- Creation and update timestamps
nself tenant org delete acmeConfirmation required:
Are you sure you want to delete organization 'acme'? (yes/no): yes
Cascade deletion:
- All teams deleted
- All memberships removed
- All roles and permissions removed
- Tenant relationships preserved (tenants remain)
# Create team in organization
nself tenant org team create acme "Engineering"
nself tenant org team create acme "Sales Team"
nself tenant org team create project-x "Backend Developers"Auto-generated slug:
- "Engineering" โ
engineering - "Sales Team" โ
sales-team - "Backend Developers" โ
backend-developers
# List all teams in organization
nself tenant org team list acmeOutput:
id | slug | name | member_count | created_at
--------------------------------------+---------------------+---------------------+--------------+---------------------------
1a2b3c4d-... | engineering | Engineering | 12 | 2026-01-15 11:00:00+00
5e6f7g8h-... | sales-team | Sales Team | 8 | 2026-01-16 09:30:00+00
# Show team information
nself tenant org team show engineering
nself tenant org team show 1a2b3c4d-...# Add user to team as member (default)
nself tenant org team add engineering user-uuid-123
# Add user as team lead
nself tenant org team add engineering user-uuid-456 lead
# Remove user from team
nself tenant org team remove engineering user-uuid-123nself tenant org team delete engineeringNote: Team members are removed, but users remain in the organization.
nself tenant org initWhat it does:
- Runs migration
010_create_organization_system.sql - Creates database schemas:
organizations,permissions - Sets up tables, functions, triggers, and RLS policies
- Inserts default permissions
- Creates default organization
nself tenant org create <name> [options]
Options:
--slug <slug> Custom slug (auto-generated if omitted)
--owner <user_id> Owner user ID (defaults to first user)
Examples:
nself tenant org create "Acme Corp"
nself tenant org create "Acme Corp" --slug acme
nself tenant org create "Acme Corp" --slug acme --owner a1b2c3d4-...nself tenant org list [--json]
Examples:
nself tenant org list
nself tenant org list --json | jq '.[] | {name, slug, status}'nself tenant org show <id_or_slug>
Examples:
nself tenant org show acme
nself tenant org show a1b2c3d4-e5f6-...nself tenant org delete <id_or_slug>
Examples:
nself tenant org delete acmenself tenant org member add <org> <user_id> [role]
Roles:
owner - Full control (only one per org)
admin - Manage members and settings
member - Standard access (default)
guest - Read-only access
Examples:
nself tenant org member add acme user-123 admin
nself tenant org member add acme user-456 # Defaults to 'member'nself tenant org member remove <org> <user_id>
Examples:
nself tenant org member remove acme user-123nself tenant org member list <org>
Examples:
nself tenant org member list acmeOutput:
user_id | role | joined_at
--------------------------------------+--------+---------------------------
a1b2c3d4-... | owner | 2026-01-15 10:30:00+00
e5f6g7h8-... | admin | 2026-01-15 11:00:00+00
i9j0k1l2-... | member | 2026-01-16 09:00:00+00
nself tenant org member role <org> <user_id> <new_role>
Examples:
nself tenant org member role acme user-123 admin
nself tenant org member role acme user-456 guestnself tenant org team create <org> <name>
Examples:
nself tenant org team create acme "Engineering"
nself tenant org team create acme "Customer Success"nself tenant org team list <org>
Examples:
nself tenant org team list acmenself tenant org team show <team_id_or_slug>
Examples:
nself tenant org team show engineering
nself tenant org team show 1a2b3c4d-...nself tenant org team delete <team_id_or_slug>
Examples:
nself tenant org team delete engineeringnself tenant org team add <team> <user_id> [role]
Roles:
lead - Team manager
member - Standard team member (default)
Examples:
nself tenant org team add engineering user-123 lead
nself tenant org team add engineering user-456 # Defaults to 'member'nself tenant org team remove <team> <user_id>
Examples:
nself tenant org team remove engineering user-123nself tenant org role create <org> <role_name>
Examples:
nself tenant org role create acme "Developer"
nself tenant org role create acme "Support Agent"nself tenant org role list <org>
Examples:
nself tenant org role list acmeOutput:
id | name | description | is_builtin | permission_count | created_at
--------------------------------------+----------------+-------------+------------+------------------+---------------------------
a1b2c3d4-... | Developer | NULL | f | 5 | 2026-01-15 12:00:00+00
e5f6g7h8-... | Support Agent | NULL | f | 3 | 2026-01-16 10:00:00+00
nself tenant org role assign <org> <user_id> <role_name>
Examples:
nself tenant org role assign acme user-123 Developer
nself tenant org role assign acme user-456 "Support Agent"nself tenant org role revoke <org> <user_id> <role_name>
Examples:
nself tenant org role revoke acme user-123 Developernself tenant org permission grant <role_name> <permission>
Examples:
nself tenant org permission grant Developer tenant.create
nself tenant org permission grant "Support Agent" user.readnself tenant org permission revoke <role_name> <permission>
Examples:
nself tenant org permission revoke Developer tenant.deletenself tenant org permission listOutput:
name | resource_type | action | description
------------------+---------------+--------+--------------------------------
tenant.create | tenant | create | Create new tenants
tenant.read | tenant | read | View tenant details
tenant.update | tenant | update | Update tenant settings
tenant.delete | tenant | delete | Delete tenants
tenant.manage | tenant | manage | Full tenant management
user.create | user | create | Create new users
user.read | user | read | View user details
...
Permissions follow the pattern: {resource_type}.{action}
nself ships with these built-in permissions:
-
tenant.create- Create new tenants -
tenant.read- View tenant details -
tenant.update- Update tenant settings -
tenant.delete- Delete tenants -
tenant.manage- Full tenant management
-
user.create- Create new users -
user.read- View user details -
user.update- Update user information -
user.delete- Delete users -
user.manage- Full user management
-
team.create- Create new teams -
team.read- View team details -
team.update- Update team settings -
team.delete- Delete teams -
team.manage- Full team management
-
org.billing- Manage organization billing -
org.settings- Manage organization settings -
org.members- Manage organization members
- Auto-assigned: To organization creator
- Permissions: All permissions (implicit)
- Cannot be: Removed from organization
- Count: One per organization
-
Typical permissions:
org.settingsorg.membersteam.manageuser.managetenant.manage
- Cannot: Change billing or delete organization
-
Typical permissions:
tenant.readuser.readteam.read
- Can: Use resources, view information
-
Typical permissions:
- Very limited read access
- Common for: External contractors, auditors
You can create roles tailored to your needs:
# Create role
nself org role create acme "QA Engineer"
# Grant permissions
nself org permission grant "QA Engineer" tenant.read
nself org permission grant "QA Engineer" tenant.update
nself org permission grant "QA Engineer" user.read
# Assign to users
nself org role assign acme user-123 "QA Engineer"Permissions aggregate across all user roles:
User has roles:
โโโ Developer (permissions: tenant.read, tenant.update)
โโโ QA Engineer (permissions: user.read, tenant.delete)
Effective permissions:
โโโ tenant.read
โโโ tenant.update
โโโ tenant.delete
โโโ user.read
Control where permissions apply:
-- User can manage ALL teams in organization
scope = 'global'
scope_id = NULL-- User can manage specific tenant only
scope = 'tenant'
scope_id = 'tenant-uuid-123'-- User can manage specific team only
scope = 'team'
scope_id = 'team-uuid-456'Use the built-in PostgreSQL function:
-- Check if user has permission
SELECT permissions.has_permission(
'user-uuid-123', -- user_id
'org-uuid-456', -- org_id
'tenant.create', -- permission name
'global', -- scope (optional, default 'global')
NULL -- scope_id (optional)
);-- Get all permissions for user
SELECT *
FROM permissions.get_user_permissions(
'user-uuid-123', -- user_id
'org-uuid-456' -- org_id
);Returns:
permission_name | resource_type | action | scope | scope_id
-----------------+---------------+--------+--------+--------------
tenant.read | tenant | read | global | NULL
tenant.update | tenant | update | team | team-uuid-789
user.read | user | read | global | NULL
Scenario: Different departments need different access levels.
# Create organization
nself tenant org create "Acme Corp" --slug acme
# Create teams for departments
nself tenant org team create acme "Engineering"
nself tenant org team create acme "Sales"
nself tenant org team create acme "Support"
# Create custom roles
nself tenant org role create acme "Engineer"
nself tenant org role create acme "Sales Rep"
nself tenant org role create acme "Support Agent"
# Grant permissions to Engineer role
nself tenant org permission grant Engineer tenant.create
nself tenant org permission grant Engineer tenant.update
nself tenant org permission grant Engineer tenant.delete
nself tenant org permission grant Engineer user.read
# Grant permissions to Sales Rep role
nself tenant org permission grant "Sales Rep" tenant.read
nself tenant org permission grant "Sales Rep" user.create
# Grant permissions to Support Agent role
nself tenant org permission grant "Support Agent" tenant.read
nself tenant org permission grant "Support Agent" user.read
# Add members and assign roles
nself tenant org member add acme user-eng-1 member
nself tenant org role assign acme user-eng-1 Engineer
nself tenant org team add engineering user-eng-1
nself tenant org member add acme user-sales-1 member
nself tenant org role assign acme user-sales-1 "Sales Rep"
nself tenant org team add sales user-sales-1Scenario: Multiple projects under one organization with isolated access.
# Create organization
nself tenant org create "Software Agency" --slug agency
# Create teams for projects
nself tenant org team create agency "Project Alpha"
nself tenant org team create agency "Project Beta"
# Create project-specific roles
nself tenant org role create agency "Alpha Developer"
nself tenant org role create agency "Beta Developer"
# Grant scoped permissions (using SQL for scope)
-- This would be done via database or API, as CLI doesn't support scopes yetScenario: External auditors need read-only access.
# Create organization
nself tenant org create "Financial Corp" --slug fincorp
# Create auditor role
nself tenant org role create fincorp "Auditor"
# Grant read-only permissions
nself tenant org permission grant Auditor tenant.read
nself tenant org permission grant Auditor user.read
nself tenant org permission grant Auditor team.read
# Add auditor as guest
nself tenant org member add fincorp auditor-user-123 guest
nself tenant org role assign fincorp auditor-user-123 AuditorScenario: Users should only access specific tenants.
-- Grant user permission to manage only production tenant
INSERT INTO permissions.user_roles (user_id, role_id, org_id, scope, scope_id)
SELECT
'user-uuid-123',
r.id,
'org-uuid-456',
'tenant',
'prod-tenant-uuid-789'
FROM permissions.roles r
WHERE r.name = 'Developer';Scenario: Team leads can manage their team but not other teams.
# Add user to team as lead
nself tenant org team add engineering user-123 lead
# Create team-specific role
nself tenant org role create acme "Team Manager"
nself tenant org permission grant "Team Manager" team.update
nself tenant org permission grant "Team Manager" user.read
nself tenant org role assign acme user-123 "Team Manager"
# In database, scope to their team
-- UPDATE permissions.user_roles
-- SET scope = 'team', scope_id = 'engineering-team-uuid'
-- WHERE user_id = 'user-123' AND role_id = (SELECT id FROM permissions.roles WHERE name = 'Team Manager');Organizations can span multiple tenants for logical separation:
Organization: Acme Corp
โโโ Tenant: acme_prod (production data)
โโโ Tenant: acme_staging (staging data)
โโโ Tenant: acme_dev (development data)
All tenants accessible to Acme Corp members based on their roles/permissions
Linking organizations to tenants:
-- Link tenant to organization
INSERT INTO organizations.org_tenants (org_id, tenant_id)
VALUES (
(SELECT id FROM organizations.organizations WHERE slug = 'acme'),
(SELECT id FROM tenants.tenants WHERE tenant_key = 'acme_prod')
);Use case:
- Development team has access to all three tenants
- QA team only has staging and dev tenants
- Executives only see production tenant data
Organizations can manage multiple projects:
Organization: Software Agency
โโโ Project: Client A Website
โ โโโ Team: Frontend Developers
โ โโโ Team: Backend Developers
โโโ Project: Client B App
โ โโโ Team: Mobile Developers
โ โโโ Team: API Developers
Organizations integrate with Hasura through session variables:
// JWT token includes
{
"https://hasura.io/jwt/claims": {
"x-hasura-user-id": "user-uuid-123",
"x-hasura-org-id": "org-uuid-456",
"x-hasura-role": "admin",
"x-hasura-allowed-roles": ["admin", "member"],
"x-hasura-team-ids": "{team-uuid-1,team-uuid-2}"
}
}Row-level security uses this:
-- Only show organizations user is member of
CREATE POLICY org_member_select ON organizations.organizations
FOR SELECT
USING (
organizations.is_org_member(id, tenants.current_user_id())
);nHost Auth service can be extended to include organization context:
// On login, include organization memberships
const user = await auth.signIn(email, password);
const orgs = await db.query(`
SELECT o.id, o.slug, om.role
FROM organizations.org_members om
INNER JOIN organizations.organizations o ON om.org_id = o.id
WHERE om.user_id = $1
`, [user.id]);
// Return in session
return {
user,
organizations: orgs
};Create an Organization when:
- You need separate billing
- You need isolated workspace
- You have distinct client/customer accounts
- You need separate compliance or audit trails
Create a Team when:
- You need to group users within the same workspace
- You want departmental separation
- You need project-based groupings
- You want to scope permissions to specific groups
Start with minimal access and grant more as needed:
# Add as member first
nself tenant org member add acme user-123 member
# Grant specific role
nself tenant org role create acme "Junior Developer"
nself tenant org permission grant "Junior Developer" tenant.read
nself tenant org permission grant "Junior Developer" user.read
nself tenant org role assign acme user-123 "Junior Developer"
# Promote later when needed
nself tenant org permission grant "Junior Developer" tenant.updateCreate role templates for common job functions:
# Backend Developer template
nself tenant org role create acme "Backend Developer"
nself tenant org permission grant "Backend Developer" tenant.create
nself tenant org permission grant "Backend Developer" tenant.update
nself tenant org permission grant "Backend Developer" user.read
# Frontend Developer template
nself tenant org role create acme "Frontend Developer"
nself tenant org permission grant "Frontend Developer" tenant.read
nself tenant org permission grant "Frontend Developer" user.read
# Full Stack template = Backend + Frontend
# Assign both roles to full stack developersBuild roles that inherit from each other (conceptually):
Guest โ Member โ Developer โ Senior Developer โ Admin โ Owner
(Each level includes all previous permissions)
Create a matrix of what each role can do:
| Role | tenant.create | tenant.read | tenant.update | tenant.delete | user.manage |
|---|---|---|---|---|---|
| Owner | โ | โ | โ | โ | โ |
| Admin | โ | โ | โ | โ | โ |
| Senior Developer | โ | โ | โ | โ | โ |
| Developer | โ | โ | โ | โ | โ |
| Support Agent | โ | โ | โ | โ | โ |
| Guest | โ | โ | โ | โ | โ |
Use scopes to limit blast radius:
-- Global permission (can manage ALL tenants)
scope = 'global'
-- Tenant-scoped (can only manage specific tenant)
scope = 'tenant', scope_id = 'tenant-uuid'
-- Team-scoped (can only manage team resources)
scope = 'team', scope_id = 'team-uuid'Grant time-limited permissions for specific tasks:
-- Grant elevated access
INSERT INTO permissions.user_roles (...)
VALUES (..., NOW() + INTERVAL '1 hour'); -- Auto-expire after 1 hour
-- Create trigger to auto-revoke
CREATE TRIGGER revoke_expired_roles ...Enable comprehensive audit trails:
-- Automatically logs to permissions.permission_audit
INSERT INTO permissions.permission_audit (
user_id,
org_id,
action,
resource_type,
resource_id,
permission_name,
performed_by,
metadata
) VALUES (
'user-uuid-123',
'org-uuid-456',
'grant',
'role',
'role-uuid-789',
'tenant.delete',
'admin-uuid-000',
'{"reason": "Emergency maintenance", "ticket": "INC-12345"}'
);Query audit logs:
-- View recent permission changes
SELECT
pa.action,
pa.permission_name,
pa.timestamp,
u.email as performed_by_email
FROM permissions.permission_audit pa
LEFT JOIN auth.users u ON pa.performed_by = u.id
WHERE pa.org_id = 'org-uuid-456'
ORDER BY pa.timestamp DESC
LIMIT 100;Slugs should be:
- Lowercase
- Hyphen-separated
- Unique and descriptive
- URL-safe
Examples:
acme-corpproject-phoenixclient-abc-prod
Team slugs should:
- Match organization naming style
- Be descriptive of function
- Avoid abbreviations unless universal
Examples:
engineeringcustomer-successbackend-team
Users can belong to multiple organizations:
-- User in multiple organizations
SELECT
o.slug,
o.name,
om.role
FROM organizations.org_members om
INNER JOIN organizations.organizations o ON om.org_id = o.id
WHERE om.user_id = 'user-uuid-123';Best practice: Applications should allow users to switch active organization context:
// Frontend: Organization switcher
const switchOrganization = (orgId) => {
localStorage.setItem('active_org_id', orgId);
// Refresh session with new org context
window.location.reload();
};Configure Single Sign-On per organization:
-- Add SSO settings to organization
UPDATE organizations.organizations
SET settings = jsonb_set(
settings,
'{sso}',
'{
"enabled": true,
"provider": "okta",
"domain": "acme.okta.com",
"client_id": "...",
"client_secret": "..."
}'::jsonb
)
WHERE slug = 'acme';Auto-join on SSO login:
// On SSO callback
const ssoOrg = await db.query(`
SELECT id FROM organizations.organizations
WHERE settings->>'sso'->>'domain' = $1
`, [userSSODomain]);
if (ssoOrg) {
// Auto-add user to organization
await db.query(`
INSERT INTO organizations.org_members (org_id, user_id, role)
VALUES ($1, $2, 'member')
ON CONFLICT DO NOTHING
`, [ssoOrg.id, user.id]);
}Transfer organization to a new owner:
-- Transfer ownership
BEGIN;
-- Update owner
UPDATE organizations.organizations
SET owner_user_id = 'new-owner-uuid'
WHERE id = 'org-uuid';
-- Update org_members role
UPDATE organizations.org_members
SET role = 'admin'
WHERE org_id = 'org-uuid' AND user_id = 'old-owner-uuid';
UPDATE organizations.org_members
SET role = 'owner'
WHERE org_id = 'org-uuid' AND user_id = 'new-owner-uuid';
COMMIT;Add multiple members at once:
-- Bulk add members
INSERT INTO organizations.org_members (org_id, user_id, role)
SELECT
'org-uuid-456',
user_id,
'member'
FROM (
VALUES
('user-uuid-1'),
('user-uuid-2'),
('user-uuid-3')
) AS users(user_id)
ON CONFLICT (org_id, user_id) DO NOTHING;CSV import example:
# CSV format: email,role
# [email protected],admin
# [email protected],member
# Import script
cat members.csv | while IFS=, read email role; do
user_id=$(psql -t -c "SELECT id FROM auth.users WHERE email = '$email'")
nself org member add acme "$user_id" "$role"
doneAutomate role assignment based on conditions:
-- Trigger: Auto-assign role to new members
CREATE OR REPLACE FUNCTION organizations.auto_assign_default_role()
RETURNS TRIGGER AS $$
BEGIN
-- Assign "Member" role to new organization members
INSERT INTO permissions.user_roles (user_id, role_id, org_id)
SELECT
NEW.user_id,
r.id,
NEW.org_id
FROM permissions.roles r
WHERE r.org_id = NEW.org_id
AND r.name = 'Member'
AND r.is_builtin = true;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER assign_default_role_on_join
AFTER INSERT ON organizations.org_members
FOR EACH ROW
EXECUTE FUNCTION organizations.auto_assign_default_role();Create application-specific permission logic:
-- Function: Check if user can perform action on resource
CREATE OR REPLACE FUNCTION permissions.can_user_perform_action(
p_user_id UUID,
p_org_id UUID,
p_resource_type TEXT,
p_action TEXT,
p_resource_id UUID DEFAULT NULL
)
RETURNS BOOLEAN AS $$
DECLARE
v_permission_name TEXT;
v_has_permission BOOLEAN;
BEGIN
-- Build permission name
v_permission_name := p_resource_type || '.' || p_action;
-- Check global permission
SELECT permissions.has_permission(
p_user_id,
p_org_id,
v_permission_name,
'global'
) INTO v_has_permission;
IF v_has_permission THEN
RETURN true;
END IF;
-- Check resource-specific permission
IF p_resource_id IS NOT NULL THEN
-- Additional resource-level checks here
-- E.g., check if user is owner of resource
END IF;
RETURN false;
END;
$$ LANGUAGE plpgsql STABLE;While not yet implemented, you could extend organizations to support hierarchies:
-- Parent-child organization relationships
ALTER TABLE organizations.organizations
ADD COLUMN parent_org_id UUID REFERENCES organizations.organizations(id);
-- Function: Get all child organizations
CREATE OR REPLACE FUNCTION organizations.get_child_orgs(p_org_id UUID)
RETURNS TABLE (id UUID, name TEXT, depth INT) AS $$
WITH RECURSIVE org_tree AS (
-- Base case
SELECT id, name, 0 as depth
FROM organizations.organizations
WHERE id = p_org_id
UNION ALL
-- Recursive case
SELECT o.id, o.name, ot.depth + 1
FROM organizations.organizations o
INNER JOIN org_tree ot ON o.parent_org_id = ot.id
)
SELECT * FROM org_tree;
$$ LANGUAGE sql STABLE;Sync organization structure from external systems:
// Sync from external HR system
const syncFromHR = async () => {
const departments = await hrSystem.getDepartments();
for (const dept of departments) {
// Create or update organization
const org = await db.query(`
INSERT INTO organizations.organizations (slug, name)
VALUES ($1, $2)
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name
RETURNING id
`, [dept.slug, dept.name]);
// Create teams
for (const team of dept.teams) {
await db.query(`
INSERT INTO organizations.teams (org_id, name, slug)
VALUES ($1, $2, $3)
ON CONFLICT (org_id, slug) DO UPDATE SET name = EXCLUDED.name
`, [org.id, team.name, team.slug]);
}
// Sync members
for (const employee of dept.employees) {
// Get or create user
const user = await getOrCreateUser(employee.email);
// Add to organization
await db.query(`
INSERT INTO organizations.org_members (org_id, user_id, role)
VALUES ($1, $2, $3)
ON CONFLICT (org_id, user_id) DO UPDATE SET role = EXCLUDED.role
`, [org.id, user.id, employee.role]);
}
}
};- Multi-Tenant Guide - Tenant isolation and management
- Database Workflow Guide - Database operations
- Security Guide - Security best practices
- Deployment Architecture - Production setup
Possible causes:
- User is not a member of the organization
- Organization is suspended or deleted
- Row-level security policy blocking access
Solution:
# Check membership
nself tenant org member list acme
# Add user if missing
nself tenant org member add acme user-123
# Check organization status
nself tenant org show acmePossible causes:
- Role doesn't have the required permission
- Permission is scoped to wrong resource
- RLS policy blocking access
Solution:
-- Check user's permissions
SELECT * FROM permissions.get_user_permissions('user-uuid', 'org-uuid');
-- Check role's permissions
SELECT p.name
FROM permissions.role_permissions rp
INNER JOIN permissions.permissions p ON rp.permission_id = p.id
WHERE rp.role_id = (SELECT id FROM permissions.roles WHERE name = 'Developer');
-- Grant missing permission
-- Use CLI: nself tenant org permission grant Developer tenant.createPossible causes:
- User is not owner or admin
- Foreign key constraints (teams, members exist)
Solution:
# Check your role
nself tenant org show acme
# Organization deletion cascades, so this should work
# If not, check database logs
nself tenant org delete acmeOrganizations in nself provide a powerful, flexible system for managing workspaces, teams, and access control:
- Organizations structure your workspace at the highest level
- Teams provide logical groupings within organizations
- Members can have multiple roles with aggregated permissions
- Roles define collections of permissions
- Permissions control fine-grained access to resources
- Scopes limit permissions to specific contexts
By combining these elements, you can implement enterprise-grade access control tailored to your specific needs.
For questions or support, refer to the nself documentation or open an issue on GitHub.