-
-
Notifications
You must be signed in to change notification settings - Fork 2
QUICK START B2B
Build a B2B platform with organization hierarchies, team management, custom branding per client, and usage tracking.
Time Estimate: 20-25 minutes Difficulty: Intermediate Prerequisites: Docker Desktop, basic understanding of multi-tenancy
Note: As of v0.9.6, commands have been consolidated. This guide uses the new v1.0 command structure:
nself org→nself tenant orgnself whitelabel→nself dev whitelabelnself env→nself config env
A complete B2B platform with:
- Organization hierarchies (parent/child accounts)
- Team management with roles & permissions
- Custom branding per organization
- Usage tracking and billing per organization
- Admin dashboard for platform management
B2B Architecture:
┌──────────────────────────────────────────┐
│ Platform (Your Company) │
├──────────────────────────────────────────┤
│ ┌────────────┐ ┌────────────┐ │
│ │ Client A │ │ Client B │ │
│ │ (Acme) │ │ (TechCo) │ │
│ ├────────────┤ ├────────────┤ │
│ │ • Team 1 │ │ • Team 1 │ │
│ │ • Team 2 │ │ • Team 2 │ │
│ └────────────┘ └────────────┘ │
└──────────────────────────────────────────┘
curl -sSL https://install.nself.org | bash
nself versionmkdir my-b2b-platform && cd my-b2b-platform
nself init --template b2bTemplate includes:
- Organization hierarchy schema
- Team & member management
- Role-based permissions
- Usage tracking tables
- Billing per organization
cat schema.dbmlKey tables:
Table organizations {
id uuid [pk]
parent_id uuid [ref: > organizations.id] // For hierarchies
name varchar
slug varchar [unique]
plan varchar
status organization_status
settings jsonb
created_at timestamp
}
Table organization_members {
id uuid [pk]
organization_id uuid [ref: > organizations.id]
user_id uuid [ref: > users.id]
role member_role
permissions jsonb
invited_by uuid
joined_at timestamp
}
Table teams {
id uuid [pk]
organization_id uuid [ref: > organizations.id]
name varchar
description text
settings jsonb
}
Table team_members {
id uuid [pk]
team_id uuid [ref: > teams.id]
user_id uuid [ref: > users.id]
role team_role
added_at timestamp
}
Table organization_usage {
id uuid [pk]
organization_id uuid [ref: > organizations.id]
metric_name varchar
quantity integer
timestamp timestamp
metadata jsonb
}
Table organization_settings {
organization_id uuid [pk, ref: - organizations.id]
branding jsonb
features jsonb
limits jsonb
notifications jsonb
}
nself build
nself startnself db schema apply schema.dbmlUsing Hasura console (https://api.local.nself.org):
mutation CreateParentOrg {
insert_organizations(objects: [
{
name: "Acme Corporation"
slug: "acme"
parent_id: null
plan: "enterprise"
status: "active"
settings: {
allowSubOrganizations: true
maxSubOrganizations: 10
maxMembers: 100
}
},
{
name: "TechCo Industries"
slug: "techco"
parent_id: null
plan: "business"
status: "active"
settings: {
allowSubOrganizations: true
maxSubOrganizations: 5
maxMembers: 50
}
}
]) {
returning {
id
name
slug
}
}
}mutation CreateChildOrgs {
insert_organizations(objects: [
{
name: "Acme Engineering"
slug: "acme-engineering"
parent_id: "acme-org-id-here"
plan: "team"
status: "active"
},
{
name: "Acme Marketing"
slug: "acme-marketing"
parent_id: "acme-org-id-here"
plan: "team"
status: "active"
}
]) {
returning {
id
name
parent_id
}
}
}mutation CreateTeams {
insert_teams(objects: [
{
organization_id: "acme-org-id"
name: "Backend Team"
description: "Backend development team"
settings: {
defaultRole: "developer"
allowExternalMembers: false
}
},
{
organization_id: "acme-org-id"
name: "Frontend Team"
description: "Frontend development team"
settings: {
defaultRole: "developer"
allowExternalMembers: false
}
}
]) {
returning {
id
name
organization_id
}
}
}mutation AddOrganizationMembers {
insert_organization_members(objects: [
{
organization_id: "acme-org-id"
user_id: "user-id-1"
role: "owner"
permissions: {
canInvite: true
canRemove: true
canManageTeams: true
canManageBilling: true
}
},
{
organization_id: "acme-org-id"
user_id: "user-id-2"
role: "admin"
permissions: {
canInvite: true
canRemove: false
canManageTeams: true
canManageBilling: false
}
},
{
organization_id: "acme-org-id"
user_id: "user-id-3"
role: "member"
permissions: {
canInvite: false
canRemove: false
canManageTeams: false
canManageBilling: false
}
}
]) {
returning {
id
role
user {
email
}
}
}
}mutation AddTeamMembers {
insert_team_members(objects: [
{
team_id: "backend-team-id"
user_id: "user-id-2"
role: "lead"
},
{
team_id: "backend-team-id"
user_id: "user-id-3"
role: "member"
}
]) {
returning {
id
team {
name
}
user {
email
}
}
}
}Access Hasura Console: https://api.local.nself.org
Table: organizations
Role: user - Can only see their own organizations
{
"organization_members": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
}Role: owner - Full access to their organization + children
{
"_or": [
{
"id": {
"_eq": "X-Hasura-Organization-Id"
}
},
{
"parent_id": {
"_eq": "X-Hasura-Organization-Id"
}
}
]
}Table: organization_members
Role: owner - Can manage all members
{
"organization_id": {
"_eq": "X-Hasura-Organization-Id"
}
}Role: admin - Can view all, but limited updates
{
"organization_id": {
"_eq": "X-Hasura-Organization-Id"
},
"role": {
"_nin": ["owner"]
}
}Role: member - Read-only
{
"organization_id": {
"_eq": "X-Hasura-Organization-Id"
}
}Table: teams
All roles - Based on organization membership
{
"organization": {
"organization_members": {
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
}
}# As regular user
query GetMyOrganizations {
organizations {
id
name
role: organization_members(where: {user_id: {_eq: "X-Hasura-User-Id"}}) {
role
permissions
}
}
}
# As owner
query GetOrganizationHierarchy {
organizations(where: {parent_id: {_is_null: true}}) {
id
name
child_organizations {
id
name
}
members: organization_members {
user {
email
}
role
}
}
}nself dev whitelabel init# Brand for Acme
nself dev whitelabel branding create "Acme Corporation" \
--tenant acme \
--tagline "Building the future"
nself dev whitelabel branding set-colors \
--tenant acme \
--primary #0066cc \
--secondary #00cc66 \
--accent #ff6600
# Brand for TechCo
nself dev whitelabel branding create "TechCo Industries" \
--tenant techco \
--tagline "Technology that works"
nself dev whitelabel branding set-colors \
--tenant techco \
--primary #6600cc \
--secondary #cc0066 \
--accent #00ccffmutation UpdateOrganizationBranding {
update_organization_settings(
where: {organization_id: {_eq: "acme-org-id"}}
_set: {
branding: {
brandName: "Acme Corporation"
tagline: "Building the future"
colors: {
primary: "#0066cc"
secondary: "#00cc66"
accent: "#ff6600"
}
logo: {
main: "https://cdn.myapp.com/acme/logo-main.png"
icon: "https://cdn.myapp.com/acme/logo-icon.png"
}
domain: "app.acme.com"
}
}
) {
affected_rows
}
}# Add custom domain for Acme
nself dev whitelabel domain add app.acme.com --tenant acme
nself dev whitelabel domain verify app.acme.com
nself dev whitelabel domain ssl app.acme.com --auto-renew
# Add custom domain for TechCo
nself dev whitelabel domain add app.techco.com --tenant techco
nself dev whitelabel domain verify app.techco.com
nself dev whitelabel domain ssl app.techco.com --auto-renewExample: Track API calls
mutation TrackAPICall {
insert_organization_usage_one(object: {
organization_id: "acme-org-id"
metric_name: "api_calls"
quantity: 1
timestamp: "now()"
metadata: {
endpoint: "/api/v1/users"
method: "GET"
userId: "user-id-1"
}
}) {
id
}
}Example: Track storage
mutation TrackStorage {
insert_organization_usage_one(object: {
organization_id: "acme-org-id"
metric_name: "storage_bytes"
quantity: 1048576 # 1 MB
timestamp: "now()"
metadata: {
fileType: "image"
fileName: "logo.png"
}
}) {
id
}
}-- Usage by organization (last 30 days)
SELECT
o.name AS organization,
u.metric_name,
SUM(u.quantity) AS total_usage,
COUNT(*) AS usage_count
FROM organization_usage u
JOIN organizations o ON u.organization_id = o.id
WHERE u.timestamp > NOW() - INTERVAL '30 days'
GROUP BY o.name, u.metric_name
ORDER BY total_usage DESC;
-- Top consumers
SELECT
o.name AS organization,
SUM(u.quantity) AS total_usage
FROM organization_usage u
JOIN organizations o ON u.organization_id = o.id
WHERE u.metric_name = 'api_calls'
AND u.timestamp > NOW() - INTERVAL '7 days'
GROUP BY o.name
ORDER BY total_usage DESC
LIMIT 10;# Install Stripe plugin
nself plugin install stripeEdit .env:
STRIPE_API_KEY=sk_test_PLACEHOLDER_key_hereCreate subscriptions per organization:
mutation CreateSubscription {
insert_subscriptions_one(object: {
organization_id: "acme-org-id"
stripe_subscription_id: "sub_xxxxx"
plan: "enterprise"
status: "active"
current_period_start: "2026-01-01"
current_period_end: "2026-02-01"
price_amount: 9900 # $99.00
currency: "usd"
billing_interval: "month"
}) {
id
}
}Edit .env:
NSELF_ADMIN_ENABLED=truenself build && nself restartOpen: https://admin.local.nself.org
Admin features:
- View all organizations
- Manage members
- Track usage
- View billing
- Monitor health
-- Add admin role to user
UPDATE users
SET role = 'admin'
WHERE email = 'admin@mycompany.com';nself config env create prodEdit .env.prod:
ENV=prod
PROJECT_NAME=my-b2b-platform
BASE_DOMAIN=myplatform.com
# Production database
POSTGRES_DB=b2b_prod
POSTGRES_PASSWORD=generate-secure-password
# Live Stripe
STRIPE_API_KEY=sk_live_your_key_here
# Security
HASURA_GRAPHQL_ADMIN_SECRET=generate-secure-secret
AUTH_JWT_SECRET=generate-jwt-secret
# Multi-tenancy
TENANT_ISOLATION_ENABLED=true
TENANT_COLUMN_NAME=organization_idnself deploy prodquery GetOrganizationTree {
organizations(where: {parent_id: {_is_null: true}}) {
id
name
plan
child_organizations {
id
name
plan
members_aggregate {
aggregate {
count
}
}
}
members_aggregate {
aggregate {
count
}
}
}
}query GetUserOrganizations($userId: uuid!) {
organization_members(where: {user_id: {_eq: $userId}}) {
role
permissions
organization {
id
name
slug
plan
teams {
id
name
team_members(where: {user_id: {_eq: $userId}}) {
role
}
}
}
}
}query GetOrganizationUsage($orgId: uuid!, $startDate: timestamp!, $endDate: timestamp!) {
organization_usage_aggregate(
where: {
organization_id: {_eq: $orgId}
timestamp: {_gte: $startDate, _lte: $endDate}
}
) {
aggregate {
sum {
quantity
}
count
}
nodes {
metric_name
quantity
timestamp
}
}
}// webhooks/organization-created.js
export async function onOrganizationCreated(organization) {
// Create default branding
await createBranding(organization.id, {
brandName: organization.name,
colors: getDefaultColors()
});
// Set up default teams
await createDefaultTeams(organization.id, [
{ name: "General", isDefault: true },
{ name: "Admins", isDefault: false }
]);
// Initialize usage tracking
await initializeUsageMetrics(organization.id);
// Send welcome email to owner
await sendEmail(organization.owner_email, "organization-welcome", {
organizationName: organization.name
});
}// webhooks/member-invited.js
export async function onMemberInvited(invitation) {
const { email, organization, role, invitedBy } = invitation;
// Send invitation email
await sendEmail(email, "team-invitation", {
organizationName: organization.name,
role: role,
inviterName: invitedBy.name,
inviteUrl: generateInviteUrl(invitation.token)
});
// Log activity
await logActivity(organization.id, {
type: "member_invited",
userId: invitedBy.id,
targetEmail: email,
role: role
});
}// Track usage via REST API
const trackUsage = async (organizationId, metric, quantity) => {
const response = await fetch('https://api.myplatform.com/v1/usage', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
organization_id: organizationId,
metric_name: metric,
quantity: quantity,
timestamp: new Date().toISOString()
})
});
return response.json();
};
// Example usage
await trackUsage('acme-org-id', 'api_calls', 1);
await trackUsage('acme-org-id', 'storage_bytes', 1048576);subscription OrganizationActivity($orgId: uuid!) {
organization_usage(
where: {organization_id: {_eq: $orgId}}
order_by: {timestamp: desc}
limit: 10
) {
id
metric_name
quantity
timestamp
metadata
}
}# Check Hasura JWT configuration
nself config show hasura | grep JWT
# Verify user session has correct claims
# Session JWT should include:
# - X-Hasura-User-Id
# - X-Hasura-Organization-Id
# - X-Hasura-Role
# Test permissions
curl -X POST https://api.myplatform.com/v1/graphql \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-d '{"query": "{ organizations { id name } }"}'# Check database connection
nself db query "SELECT COUNT(*) FROM organization_usage"
# Verify trigger exists
nself db query "
SELECT tgname
FROM pg_trigger
WHERE tgname LIKE '%usage%'
"
# Check recent usage
nself db query "
SELECT * FROM organization_usage
ORDER BY timestamp DESC
LIMIT 10
"# Verify DNS
nslookup app.acme.com
# Check nginx config
nself config show nginx | grep acme.com
# Test SSL
curl -I https://app.acme.com
# Check certificate
nself auth ssl check app.acme.com-- Add indexes for common queries
CREATE INDEX idx_org_members_org_id ON organization_members(organization_id);
CREATE INDEX idx_org_members_user_id ON organization_members(user_id);
CREATE INDEX idx_teams_org_id ON teams(organization_id);
CREATE INDEX idx_team_members_team_id ON team_members(team_id);
CREATE INDEX idx_usage_org_id_timestamp ON organization_usage(organization_id, timestamp DESC);
-- Partition usage table by month
CREATE TABLE organization_usage_2026_01 PARTITION OF organization_usage
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');Edit .env:
REDIS_ENABLED=true
HASURA_GRAPHQL_REDIS_URL=redis://redis:6379
HASURA_GRAPHQL_RATE_LIMIT={"unique_params":"IP","max_reqs_per_min":100}- Custom Domains Guide - Full domain setup
- Stripe Integration - Advanced billing
- White-Label System - Complete customization
- Database Workflow - Schema management
- Documentation: https://docs.nself.org
- GitHub: https://github.com/nself-org/cli
- Discord: https://discord.gg/nself
Your B2B platform is ready! Time to onboard your first client.
ɳSelf CLI v1.0.9. MIT licensed. Docs CC BY 4.0.
GitHub · Issues · Discussions · nself.org · nself.org/docs
Getting Started
Commands
- Commands, Overview
- Lifecycle: cmd-init · cmd-build · cmd-start · cmd-stop · cmd-restart · cmd-dev
- Monitoring: cmd-status · cmd-logs · cmd-health · cmd-urls · cmd-doctor · cmd-monitor · cmd-alerts · cmd-sentry · cmd-watchdog
- Data: cmd-db · cmd-backup · cmd-dr · cmd-queue · cmd-webhooks
- Config: cmd-config · cmd-service · cmd-env · cmd-promote
- Networking: cmd-ssl · cmd-trust · cmd-dns-setup
- Security: cmd-access · cmd-security · cmd-secrets
- Tenancy: cmd-tenant · cmd-billing
- Plugins: cmd-plugin · cmd-license · cmd-dogfood (extracted, CLI-R11) · cmd-k8s (extracted, CLI-R11) · cmd-encryption (extracted, CLI-R11) · cmd-waf (extracted, CLI-R11) · cmd-federation (extracted, CLI-R11) · cmd-mail (extracted, CLI-R11) · cmd-dlq (extracted, CLI-R11)
- AI: cmd-ai · cmd-claw · cmd-model
- Templates: cmd-template
- Utilities: cmd-exec · cmd-clean · cmd-reset · cmd-update · cmd-upgrade · cmd-version · cmd-admin · cmd-migrate · cmd-migrate-firebase · cmd-migrate-supabase · cmd-completion
Features
- Features, Overview
- Feature-Auth
- Feature-Storage
- Feature-Search
- Feature-Functions
- Feature-Email
- Feature-Monitoring
- Feature-Plugins
- Feature-nClaw, AI Assistant
- Feature-nChat, Messaging
- Feature-nTV, Media Player
- Feature-nFamily, Family Social
- Feature-nCloud, Managed Hosting
- Feature-Memory-Rooms, Knowledge Organization
- Feature-Agent-Dashboard, Agent Metrics
- Feature-Image-Generation, AI Image Generation
Configuration
- Configuration, Overview
- Config-Env-Vars
- Config-Postgres
- Config-Hasura
- Config-Auth
- Config-Nginx
- Config-Optional-Services
- Config-Custom-Services
- Config-System
Plugins (87 + 10 monitoring)
Free (25)
- plugin-backup
- plugin-content-acquisition
- plugin-content-progress
- plugin-cron
- plugin-donorbox
- plugin-feature-flags
- plugin-github
- plugin-github-runner
- plugin-invitations
- plugin-jobs
- plugin-link-preview
- plugin-mdns
- plugin-mlflow
- plugin-monitoring
- plugin-notifications
- plugin-notify
- plugin-paypal
- plugin-search
- plugin-shopify
- plugin-stripe
- plugin-subtitle-manager
- plugin-tokens
- plugin-torrent-manager
- plugin-vpn
- plugin-webhooks
Pro (62)
- plugin-access-controls
- plugin-activity-feed
- plugin-admin-api
- plugin-nself-ai-gateway
- plugin-nself-ai-mcp
- plugin-nself-ai-mcp
- plugin-analytics
- plugin-auth
- plugin-backup-pro
- plugin-bots
- plugin-browser
- plugin-calendar
- plugin-cdn
- plugin-chat
- plugin-claw
- plugin-claw-budget
- plugin-claw-news
- plugin-claw-web
- plugin-cloudflare
- plugin-cms
- plugin-compliance
- plugin-cron-pro
- plugin-ddns
- plugin-devices
- plugin-documents
- plugin-donorbox-pro
- plugin-entitlements
- plugin-epg
- plugin-file-processing
- plugin-game-metadata
- plugin-geocoding
- plugin-geolocation
- plugin-google
- plugin-home
- plugin-idme
- plugin-knowledge-base
- plugin-linkedin
- plugin-livekit
- plugin-media-processing
- plugin-meetings
- plugin-moderation
- plugin-mux
- plugin-notify-pro
- plugin-object-storage
- plugin-observability
- plugin-paypal-pro
- plugin-photos
- plugin-podcast
- plugin-post
- plugin-realtime
- plugin-recording
- plugin-retro-gaming
- plugin-rom-discovery
- plugin-shopify-pro
- plugin-social
- plugin-sports
- plugin-stream-gateway
- plugin-streaming
- plugin-stripe-pro
- plugin-support
- plugin-tmdb
- plugin-voice
- plugin-web3
- plugin-workflows
Planned (26)
plugin-auditplugin-blogplugin-checkoutplugin-commerceplugin-drmplugin-exportplugin-flowplugin-importplugin-ldapplugin-mailgunplugin-mediaplugin-oauth-providersplugin-pagesplugin-postmarkplugin-rate-limitplugin-reportsplugin-samlplugin-schedulerplugin-sendgridplugin-ssoplugin-subscriptionplugin-thumbplugin-transcoderplugin-twilioplugin-wafplugin-watermark
Guides
- Guide-Production-Deployment
- Guide-SSL-Setup
- Guide-Multi-Tenancy
- Guide-Security-Hardening
- Guide-Monitoring-Setup
- Guide-Backup-Restore
- Guide-Custom-Services
- Guide-Migration-from-v1
Architecture
Reference
- API-Reference
- error-codes, Error Codes
Licensing
Security
Brand
Operations
- operations/release-cascade, Release Cascade
- operations/self-healing, Self-Healing Schema
- operations/redis-tuning, Redis Pool Tuning
- operations/meilisearch-warmup, MeiliSearch Warm-Up
- operations/jwt-rotation, JWT Key Rotation
- operations/windows-wsl2-setup, Windows / WSL2 Setup
- operations/gemini-oauth-reauth, Gemini OAuth Reauth
Contributing
Admin
- USER-ACTION-QUEUE, Pending Admin Actions
All commands (52)
- A: cmd-access · cmd-account · cmd-admin
- B: cmd-backup · cmd-build · cmd-bundle
- C: cmd-ci · cmd-clean · cmd-completion · cmd-config
- D: cmd-db · cmd-deploy · cmd-dev · cmd-doctor
- E: cmd-env · cmd-exec
- F: cmd-functions
- G: cmd-generate
- H: cmd-health · cmd-help-topics
- I: cmd-init · cmd-install
- L: cmd-license · cmd-login · cmd-logout · cmd-logs
- M: cmd-man · cmd-mcp · cmd-migrate
- O: cmd-oauth · cmd-ops
- P: cmd-plugin · cmd-promote
- R: cmd-remove · cmd-reset · cmd-restart · cmd-runner
- S: cmd-secrets · cmd-security · cmd-self-heal · cmd-server · cmd-service · cmd-start · cmd-status · cmd-stop
- T: cmd-telemetry · cmd-template · cmd-trust
- U: cmd-update · cmd-urls
- V: cmd-verify-sbom · cmd-version