DOCS LLMs

Organizations & Teams

RailsFast comes with a full multi-tenant organizations system out of the box. Users get a personal workspace on signup, can create additional organizations, invite teammates, and manage roles and permissions.

We leverage the organizations gem for this.

What you get out of the box

When a user signs up, they automatically get a personal organization called "My Workspace". No onboarding screen, no friction - they go straight to the dashboard.

From there, users can:

  • Create additional organizations for teams or projects
  • Invite teammates via email with role-based permissions
  • Switch between organizations using the org switcher in the header
  • Manage team members from the Settings page (invite, remove, change roles)

Organization switcher

The dashboard header includes an organization switcher dropdown (both mobile and desktop). Users can quickly switch between their organizations:

Organization switcher

<%# Already included in the dashboard header %>
<%= render "components/railsfast/organizations/switcher" %>

The switcher shows:

  • Current organization with a checkmark
  • Other organizations the user belongs to
  • "Create new organization" link

Team management in Settings

The Settings page includes full team management UI:

Team members list

  • Organization section - rename the organization
  • Team Members section - view all members, invite new ones, change roles, remove members
  • Plan & Billing section - subscription status and billing management (organization-level)

All team management is permission-based. Only owners and admins can invite/remove members and change roles.

Roles and permissions

Member permissions

RailsFast uses the default role hierarchy:

owner > admin > member > viewer

Each role inherits permissions from roles below it:

Permission Viewer Member Admin Owner
View organization ✓ ✓ ✓ ✓
View members ✓ ✓ ✓ ✓
Create resources ✓ ✓ ✓
Edit own resources ✓ ✓ ✓
Invite members ✓ ✓
Remove members ✓ ✓
Edit member roles ✓ ✓
Manage billing ✓
Transfer ownership ✓
Delete organization ✓

You can customize roles in config/initializers/organizations.rb.

Organization-level billing

By default, RailsFast ships with billing at the organization level (not user level). This is the standard B2B SaaS pattern where:

  • Organizations have subscriptions, not individual users
  • All team members share the organization's plan
  • Plan limits apply to the organization (e.g., team member seats)

The default checkout and billing flow is built around current_organization, so this is the path that works out of the box for standard B2B apps.

This is configured in app/models/concerns/organization_extensions.rb:

# Subscriptions are at the organization level
pay_customer default_payment_processor: :stripe

# Plan limits enforcement
include PricingPlans::PlanOwner

Team member seat limits

RailsFast automatically enforces team member limits based on the organization's plan. Configure limits in your pricing_plans.rb:

plan :pro_plan do
  limits :team_members, to: 10
  # ...
end

When an organization reaches their seat limit, invitations are blocked with a friendly upgrade message.

Invitation emails

Team invitations are sent using beautiful Goodmail templates. The invitation email includes:

  • Who invited them
  • Which organization they're joining
  • A secure accept link

When the invitee clicks the link, they see a clean accept page showing the organization, who invited them, and their assigned role:

Accept invitation page

If they're not signed in, they can create an account or sign in to accept. Invitations expire after 7 days by default (configurable).

Using organizations in your code

Current organization

Access the current organization anywhere:

# In controllers
current_organization          # => Organizations::Organization
current_organization.name     # => "Acme Corp"

# Require organization (redirects if none)
before_action :require_organization!

Role checks

# Boolean checks
current_user.is_organization_owner?
current_user.is_organization_admin?
current_user.is_organization_member?

# Permission checks
current_user.has_organization_permission_to?(:invite_members)
current_user.has_organization_permission_to?(:manage_billing)

Scoping resources to organizations

For organization-scoped resources, inherit from OrganizationDashboardController:

class ProjectsController < OrganizationDashboardController
  def index
    @projects = current_organization.projects
  end
end

This automatically:

  • Requires authentication
  • Requires organization membership
  • Uses the dashboard layout

Delegated methods

The User model delegates common organization methods:

# These work on current_user
current_user.organization_name      # => "Acme Corp"
current_user.current_plan           # => PricingPlan
current_user.subscribed?            # => true/false
current_user.current_subscription   # => Pay::Subscription

Configuration

All organization settings live in config/initializers/organizations.rb:

Organizations.configure do |config|
  # Auto-create "My Workspace" on signup
  config.always_create_personal_organization_for_each_user = true
  config.default_organization_name = "My Workspace"

  # Invitation settings
  config.invitation_expiry = 7.days
  config.default_invitation_role = :member

  # Beautiful invitation emails
  config.invitation_mailer = "OrganizationInvitationGoodmailer"

  # Redirects
  config.after_organization_created_redirect_path = "/dashboard"
  config.redirect_path_after_invitation_accepted = "/dashboard"

  # Enforce seat limits
  config.on_member_invited do |ctx|
    limit = ctx.organization.current_plan&.limit_for(:team_members)
    if limit && ctx.organization.memberships_count >= limit
      raise Organizations::InvitationError,
        "You've reached your plan's team member limit."
    end
  end
end

Architecture patterns: User-based vs Organization-based

Before diving into customization, it's worth understanding when resources should belong to organizations vs users. This decision affects your entire data model.

There are really three separate questions here:

  1. Who owns the resource?
  2. Who pays for access?
  3. Are organizations required, optional, or just a reporting layer?

Don't collapse those into one decision. In many apps, the right answer is:

  • resources belong to users
  • organizations are optional
  • billing may exist at the organization level, user level, or both

The key question

Ask yourself: "Who owns this resource semantically?"

Consider a fitness tracking app where users log workouts. If a user leaves their gym, do their workouts stay with the gym or go with the user? Obviously the user—workouts are personal data, not gym assets.

Now consider a project management tool. If an employee leaves the company, do their projects stay with the company or go with them? The company—projects are corporate assets.

This semantic ownership should drive your architecture.

WARNING

Don't add organization_id to personal-domain records just because you might want B2B later. If the resource is fundamentally personal, keep it user-owned and let organizations provide access, reporting, or billing on top.

Pattern 1: Organization-based (RailsFast default)

Resources belong to organizations. Billing is at the organization level. This is the standard B2B SaaS pattern.

# Resources belong to organizations
class Project < ApplicationRecord
  belongs_to :organization
end

# Organization has billing (from organization_extensions.rb)
# pay_customer default_payment_processor: :stripe

Use this when:

  • Companies are your customers
  • Teams share resources (projects, documents, API keys)
  • Resources should stay with the company when employees leave
  • Billing is per-team

Examples: Project management, CRM, analytics dashboards, API platforms

Pattern 2: User-owned resources with optional organizations

Resources belong to users. Organizations exist for grouping, social features, or B2B dashboards—but they don't own the resources.

Real-world example: A fitness app with gym memberships

Imagine building a workout tracking app where:

  • Users log workouts, earn achievements, and track calories and training time
  • Users can join gyms (organizations) to participate in challenges and see leaderboards
  • Gyms pay a subscription for dashboards showing member activity and engagement stats
  • When a user leaves a gym, their workout history goes with them

Here's how to model this:

# Workouts belong to USERS, not organizations
class Workout < ApplicationRecord
  belongs_to :user

  # Workout data
  attribute :exercise_type, :string
  attribute :duration_minutes, :integer
  attribute :calories_burned, :integer
  attribute :completed_at, :datetime
end

class User < ApplicationRecord
  has_organizations  # Can join gyms
  has_many :workouts

  # User's personal stats
  def total_workouts
    workouts.count
  end

  def calories_this_month
    workouts.where(completed_at: Time.current.beginning_of_month..).sum(:calories_burned)
  end
end

The key insight: Users can belong to multiple gyms, but their workouts don't "belong" to any gym. The gym gets a reporting view into member activity, not ownership.

Implementing the gym dashboard

Gyms need to see aggregated member stats. Add reporting methods to Organization via organization_extensions.rb:

# app/models/concerns/organization_extensions.rb
module OrganizationExtensions
  extend ActiveSupport::Concern

  included do
    # Gym pays for dashboard access
    pay_customer default_payment_processor: :stripe
    include PricingPlans::PlanOwner
  end

  # === Member Activity Reporting ===
  # These are READ-ONLY views into member data, not ownership

  def member_workouts(period: nil)
    scope = Workout.where(user_id: users.select(:id))
    scope = scope.where(completed_at: period) if period
    scope
  end

  def gym_stats(period: nil)
    workouts = member_workouts(period: period)
    {
      total_workouts: workouts.count,
      total_calories: workouts.sum(:calories_burned),
      total_minutes: workouts.sum(:duration_minutes),
      active_members: workouts.distinct.count(:user_id),
      avg_workouts_per_member: member_count.positive? ? (workouts.count.to_f / member_count).round(1) : 0
    }
  end

  def leaderboard(period: nil, limit: 10)
    Workout
      .where(user_id: users.select(:id))
      .where(period ? { completed_at: period } : {})
      .group(:user_id)
      .select("user_id, COUNT(*) as workout_count, SUM(calories_burned) as total_calories")
      .order("workout_count DESC")
      .limit(limit)
  end

  def member_engagement_report(period:)
    active_ids = member_workouts(period: period).distinct.pluck(:user_id)
    {
      total_members: member_count,
      active_members: active_ids.count,
      inactive_members: member_count - active_ids.count,
      engagement_rate: member_count.positive? ? ((active_ids.count.to_f / member_count) * 100).round(1) : 0
    }
  end
end

Configuration for User-based apps

If organizations are optional in your app, turn off the personal-workspace default:

# config/initializers/organizations.rb
Organizations.configure do |config|
  # Users can exist without an organization
  config.always_create_personal_organization_for_each_user = false
  config.always_require_users_to_belong_to_one_organization = false

  # ... rest of config
end

This changes the organization behavior, not your whole billing architecture.

Supporting both individual and organization billing

If you want individual users and organizations to have subscriptions:

class User < ApplicationRecord
  has_organizations
  has_many :workouts

  # Individual PRO billing primitive
  pay_customer default_payment_processor: :stripe
end

# Organizations also have subscriptions (dashboard access, analytics, etc.)
# This is already in organization_extensions.rb:
# pay_customer default_payment_processor: :stripe

Now you have two billing paths:

  • Individual PRO: User pays for premium personal features
  • Organization subscription: Organization pays for reporting, admin tools, team access, etc.
IMPORTANT

Adding pay_customer to User is only the billing primitive. RailsFast's default checkout, billing portal, pricing UI, and common subscription delegates are still organization-scoped. If you want real dual billing, you will usually need:

  • a separate checkout / billing flow for User
  • a clear entitlement layer for feature access
  • to adjust the default User delegates that point current_plan, subscribed?, and current_subscription at current_organization
  • to decide whether User should also become a PricingPlans::PlanOwner, instead of assuming that drops in cleanly
  • discipline around not inferring resource ownership from whoever pays

When to use each pattern

Question Organization-based User-based
Who's the primary customer? Companies Individuals
Do resources stay when user leaves? Yes (company assets) No (personal data)
Is billing per-team or per-user? Per-team Per-user (or optional org billing)
Are resources shared or personal? Shared Personal
Example resources Projects, API keys, team docs Workouts, recipes, transactions

The hybrid approach works

The gym example shows you can have both:

  • B2C core: Users own their workout history and can use the app solo
  • B2B layer: Organizations pay for dashboards showing member activity

This is often the right architecture for:

  • Fitness apps with gyms or corporate wellness programs
  • Expense trackers with company reporting
  • Learning platforms with team/school dashboards
  • Any B2C app that might add B2B features later

The organizations gem supports both patterns equally well. The key is deciding upfront:

  • Do organizations own resources?
  • Or do they provide a lens into user-owned resources?
  • And separately, who pays for access?

Danger Zone: Transfer, Delete & Leave

The organization settings page includes a Danger Zone section at the bottom. What users see depends on their role:

  • Owners see Transfer Ownership and Delete Organization options
  • Non-owners see a Leave Organization option

This is required for account closure—users can't delete their account while they still own organizations.

Danger Zone

Transfer ownership

Owners can transfer ownership to any admin in the organization. The transfer flow:

  1. Click "Transfer" in the Danger Zone
  2. Select an eligible admin from the list
  3. Type the organization name to confirm
  4. The previous owner becomes an admin
NOTE

Only admins can receive ownership. If you want to transfer to a member, promote them to admin first using the Team Members section.

Delete organization

Owners can permanently delete an organization:

  1. Click "Delete" in the Danger Zone
  2. Type the organization name to confirm
  3. All data is permanently deleted

Both actions use type-to-confirm modals to prevent accidents.

Account closure flow

When a user tries to close their account while owning organizations, they'll see a message explaining they need to transfer ownership or delete those organizations first. The Danger Zone provides the tools to do this.

Leave organization (for non-owners)

Non-owner members see a Leave Organization panel instead of the Danger Zone. This allows them to remove themselves from an organization they no longer want to be part of.

The leave action:

  1. Removes the user's membership from the organization
  2. Redirects to the organizations list
  3. Auto-switches to another organization if available
NOTE

Owners cannot leave their organization. They must transfer ownership first, or delete the organization entirely.


Hybrid onboarding (invited vs direct signups)

By default, RailsFast creates a "My Workspace" for every new user. But when users sign up via invitation to join an existing organization, creating a personal workspace is unnecessary clutter.

RailsFast implements hybrid onboarding out of the box:

  • Direct signups → Get "My Workspace" immediately
  • Invited signups → Skip personal workspace, join the invited org directly

This is handled automatically via a flag in the User model and Devise's build_resource hook. You don't need to configure anything.

How it works

The User model overrides the gem's should_create_personal_organization? predicate:

# app/models/user.rb
class User < ApplicationRecord
  has_organizations

  # Skip personal org creation for invited signups
  attr_accessor :skip_personal_organization

  def should_create_personal_organization?
    return false if skip_personal_organization
    super
  end
end

The registrations controller sets the flag before user creation:

# app/controllers/users/registrations_controller.rb
def build_resource(hash = {})
  super
  if pending_organization_invitation?
    resource.skip_personal_organization = true
  end
end

This ensures the flag is set before the user is persisted, so the gem's after_create callback respects it.


Customization options

Users with no organization (B2C / consumer apps)

By default, RailsFast auto-creates a "My Workspace" for every new user. If you want users to have no organization by default (e.g., for B2C consumer apps, or if organizations are optional), just change one line in the initializer:

# config/initializers/organizations.rb
config.always_create_personal_organization_for_each_user = false

That's it for the organizations behavior. New users will sign up without an organization. They can create one later if needed.

If you're also using RailsFast's default org-level billing flow, you may still want to adapt the subscription screens and controllers so they don't assume current_organization.

NOTE

The User model's has_organizations block inherits from the initializer settings. You only need to use the block if you want to override the global config for a specific model. For most apps, configure everything in the initializer and use a simple has_organizations without a block.

Custom onboarding flow

If you want users to go through an onboarding screen to create their organization:

# config/initializers/organizations.rb
config.always_create_personal_organization_for_each_user = false
config.redirect_path_when_no_organization = "/onboarding/create_organization"

Then uncomment the onboarding route in config/routes.rb. An OnboardingController and view are already included but disabled by default.

Add custom organization fields

To add fields like logo or billing_email:

  1. Add a migration:
add_column :organizations_organizations, :logo, :string
add_column :organizations_organizations, :billing_email, :string
  1. Permit the params:
# config/initializers/organizations.rb
config.additional_organization_params = [:logo, :billing_email]
  1. Update the organization form partial.

Custom roles

Define custom roles with fine-grained permissions:

config.roles do
  role :viewer do
    can :view_organization
    can :view_members
  end
  role :contributor, inherits: :viewer do
    can :create_resources
    can :edit_own_resources
  end
  role :manager, inherits: :contributor do
    can :invite_members
    can :manage_resources
  end
  role :admin, inherits: :manager do
    can :remove_members
    can :edit_member_roles
  end
  role :owner, inherits: :admin do
    can :manage_billing
    can :transfer_ownership
    can :delete_organization
  end
end

Admin panel

Organizations appear in the Madmin admin panel at /admin/dashboard/organizations. You can view:

  • All organizations with member counts
  • Subscription status and plan
  • Scopes for filtering (active subscribers, on trial, churned, etc.)

Key files

File Purpose
config/initializers/organizations.rb Main configuration
app/models/concerns/organization_extensions.rb Pay + PricingPlans integration
app/models/user.rb has_organizations + hybrid onboarding
app/controllers/users/registrations_controller.rb Invitation-aware signup
app/controllers/organization_dashboard_controller.rb Base controller for org-scoped pages
app/helpers/organizations_helper.rb Role badges and UI helpers
app/mailers/organization_invitation_goodmailer.rb Invitation emails
app/views/components/railsfast/organizations/ UI components (switcher, team list, danger zone)
app/views/demo/settings/ Settings page partials
app/javascript/controllers/transfer_modal_controller.js Transfer ownership modal
app/javascript/controllers/confirm_modal_controller.js Delete confirmation modal

INFO

The organizations gem has many more features! Check out the gem docs for advanced usage like ownership transfer, custom callbacks, Acts As Tenant integration, and more.