Referral System in Rails applications

By Exequiel Rozas

- August 13, 2025

Building a great product is a good starting point for success, but it's rarely enough to generate meaningful traction. There are many channels we can use for distribution: organic search, social media, paid advertising, and PR, among others.

However, if we find success with any of these channels, user acquisition costs usually get higher with time, and that can become a real issue if we're caught off-guard. Adding a referral system is a good way to acquire a portion of our users for a lower price.

In this article, we will learn how to add a referral system to a Rails app using the Refer gem to improve our chances of success.

Let's start by understanding why adding a referral system is desired. Jump to the application setup if you already know about them.

Why add a referral system

The main reason to add a referral system to any product is to reduce customer acquisition costs.

It doesn't matter what our main acquisition channel is, as we go through our product adoption lifecycle, the probability of customer acquisition getting more expensive is high.

Our early customers are more inclined to respond to our early marketing efforts than users who don't know about the problem: they are already aware of the issues we solve, and they may even be aware of our product.

And, while a referral system doesn't solve customer acquisition cost issues alone, it gives users an opportunity to recommend our product to people that might be interested while simultaneously earning a reward.

Considering this, a good referral program is:

  • A virtuous cycle: users should preferably refer users that might actually benefit from our product.
  • Attractive but low cost: the reward users get for referring other users should be attractive to them while at the same time not breaking the bank for us.
  • Frictionless: any friction we add to our referral program is going to worsen our results. We want to give users the ability to refer other users as simply as possible.

Focusing on a referral program before validating an idea is probably not a good use of time. But don't overlook them because the cost/reward ratio can be very high.

How does it work

For a referral system to work, we need to be able to track who referred whom reliably.

In a web application, the best way we have to achieve this is to use cookies, which are a semi-permanent, device-contained storage solution.

To achieve a referral, a user has to share a link that includes a given code with an acquaintance who visits our site. Because the link contains the referral code as a query parameter, we store that code in a cookie in the referred user's browser.

Referral system explained

Then, as the referee has the referrer's code stored in the browser, after completing a signup our application reads the code from the cookies, looks for the referring user, and tracks the referral by persisting it to the database and associating it with the user who owns the code.

Referral flow grant reward

The last step of the process is to provide a reward to the referrer after the referee performs an action in our application.

This step is highly dependent on the business goals. Maybe we want to encourage account creation, or maybe we want to provide the reward after the referee purchases our product, starts a free trial, or does something else of interest for the business.

What we will build

For this tutorial, we will build upon AvoCasts, the fantasy application we built for the multistep forms with the Wicked gem tutorial, by adding a referral system.

We will be using the Refer gem, and our system will have the following features:

  • A user can refer other users by sharing a link.
  • A successful referral is produced when a new user creates an account. Normally, we may want the referral to be successful after a transaction or business goal is produced.
  • If a set amount of time passes after a user was referred, we won't count the referral as valid.
  • A given user can refer as many users as they want, but we will provide the reward for a limited amount of referrals.
  • We will give a reward to both users only after the referred user completes an action.
  • Users can access their referral stats in a dashboard within the application.
  • Referring should be frictionless: the referral view should include features to copy and share the referral codes.

Application setup

We will assume we have an existing application that we want to add a referral system to.

If you already have auth setup, skip this step. Otherwise, we will add it with the Rails auth generator:

bin/rails generate authentication

This will add two database-backed models: User and Session. We will add the referral associations to the user.

Next, let's add the refer gem to our Gemfile and install it:

bundle add refer && bundle install

With the gem installed, let's take a look at how it works and what it does behind the scenes:

The Refer gem

This command adds three migrations that add the following database tables:

Refer gem database structure

The refer_referrals table is responsible for keeping track of who referred whom: it associates a referrer with a referee. For our example application, they would both be users, but because the association is polymorphic, we could have a User as the referrer and an Author as the referee.

The refer_referral_codes keeps track of the codes that the referrer has. Even though it's common for a user to have a single referral code, sometimes we need to have many to track the origin of the referral. For example, the user might want to see which channel performs better for a referral campaign.

The refer_visits table holds information about the visits that had the ref query parameter. They can be useful to analyze why a specific code was successful or not.

The command also adds a has_referrals call to the User model, which is the same as including the Refer::Model concern, which adds the following associations to the model where it's included:

has_many :referral_codes, as: :referrer, class_name: "Refer::ReferralCode", dependent: :destroy
has_many :referrals, as: :referrer, class_name: "Refer::Referral", dependent: :destroy
has_one :referral, as: :referee, class_name: "Refer::Referral", dependent: :destroy
delegate :referrer, to: :referral, allow_nil: true

Referral cookie

The method in charge of adding the referral cookie to the referee's browser is set_referral_cookie .

This method is added to the ApplicationController when installing the gem and is a wrapper around the set_refer_cookie method in a before action:

class_methods do
  def set_referral_cookie(param_name: Refer.param_name, cookie_name: Refer.cookie_name, **options)
    before_action -> { set_refer_cookie(param_name: param_name, cookie_name: cookie_name) }, **options
  end
end

We can customize the name of the parameter, which is set to ref by default, the name of the cookie and whether we want to track the visits that are associated with the code:

def set_refer_cookie(param_name: Refer.param_name, cookie_name: Refer.cookie_name, code: nil, track_visit: Refer.track_visits)
  code ||= params[param_name]
  return if code.blank?

  cookies[cookie_name] = Refer.cookie(code) if Refer.overwrite_cookie || cookies[cookie_name].blank?
  ReferralCode.find_by(code: code)&.track_visit(request) if track_visit
end

Even if the logic is simple and doesn't execute anything if the parameter is not present or it's empty, we can move the set_referral_cookie into a specific controller:

class PagesController < ApplicationController
  set_referral_cookie
end

The cookie method on the Refer class is what gets stored in the cookie, and it returns something like this:

{:value=>"7byZBXjN", :expires=>2025-08-14 01:11:06.580137000 UTC +00:00}

The expiration date is set to 30 days after the method is executed, but we can customize it with the cookie_length method:

# config/initializers/refer.rb
Refer.cookie_length = 15.days

If there's a code in the cookies, the method performs a query to try to find one and, if it does, it tracks a visit to the code.

We can also customize the code generation by modifying the code_generator attribute with a lambda that receives our generator:

# config/initializers/refer.rb
Refer.code_generator = -> (referrer) { "#{referrer.id}-#{SecureRandom.hex(6)}".upcase } #=> "13-507B9DF87033"

By default, the generator uses SecureRandom.alphanumeric(8) which is fine, but your application might need something different.

User referral

The actual user referral is performed by the Refer.refer class method which receives a code and a user instance that represents the referee:

referee = User.last
code = "123456"
Refer.refer(code, referee)  # Creates a Refer::Referral

This method finds the ReferralCode associated with the code and creates a new Refer:Referral which keeps track of the referrer and referee association like we explained above.

There's an alternative Refer.refer! method that takes the same arguments but returns an AlreadyReferred error if the referee user has already been referred by somebody else. This is especially useful if we're not creating the referral at the time of the user sign-up.

Now that we understand how the gem works, let's add a referral feature to our application:

Adding referrals

Let's start by running the gem's installation command which adds the previously mentioned set_referral_cookie method to the ApplicationController:

bin/rails generate refer:install

Then, we run the command to add referrals to our User model:

bin/rails generate refer:model User

Next, we run the migrations to create all the tables:

bin/rails db:migrate

Now, let's add a callback to create a ReferralCode on user creation:

class User < ApplicationRecord
  after_create :create_referral_code

  private
  def create_referral_code
    referral_codes.create
  end
end

To keep our logic simple, we can also add a referral_code method to the user, which is responsible for returning the latest code:

def referral_code
  referral_codes.last&.code
end

If we're starting from an application that already has users, which is pretty probable, we can create a migration to add codes for existing users:

bin/rails generate migration create_referral_codes_for_existing_users

And add the following to the migration:

class CreateReferralCodesForExistingUsers < ActiveRecord::Migration[8.0]
  def up
    User.find_each do |user|
      user.referral_codes.create unless user.referral_codes.any?
    end
  end

  def down
    User.find_each do |user|
      user.referral_codes.destroy_all
    end
  end
end

Now, after running the migration, every user in our application should have an associated referral code that we can use.

Refer a friend view

This view is intended to educate users about our referral program and the steps involved with it.

Let's start by adding a route and a controller for this view:

get "refer-a-friend", to: "pages#refer_a_friend", as: :refer_a_friend
class PagesController < ApplicationController
  def refer_a_friend
  end
end

Then, we add the view code to show the steps for the referral process, a section where users can copy the referral link, and a section for users to send invitations by email.

For brevity purposes, we won't paste the whole view code, but you can check it in the AvoCasts repository. The result for the view looks like this:

Refer a friend view

The main goal for this view is to give our users a concise way to understand the referral process and make it easy for them to share their referral codes with their friends.

To avoid hardcoding the URL, let's add a helper method that receives a User instance and produces the referral code:

module UsersHelper
  def referral_link(user)
    root_url(ref: user.referral_code)
  end
end

With the view in place, let's add the referral flow

Referral flow

We start by adding the set_referral_cookie to our ApplicationController:

class ApplicationController < ActionController::Base
  set_referral_cookie
end

Now, if we open a new browser window and visit the referral link, we should see the Refer::Visit count incrementing for every new visit, which means the feature is working as expected:

Referral visit count

With this working, we can produce a referral on sign-up by adding a call to refer from the RegistrationsController:

class RegistrationsController
  allow_unauthenticated_access only: %i[new create]
  before_action :resume_session, only: [:new, :create]

  def create
    @user = User.new(user_params)
    if @user.save
      refer @user
      start_new_session_for @user
      redirect_to root_path, notice: "Welcome to AvoCasts!"
    else
      flash[:alert] = "Something went wrong. Please try again."
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.expect(user: [:email_address, :password, :password_confirmation])
  end
end

Now, we visit the referral link and create a new account, we should see that the referring user returns the new user as a referral:

New user referral

That's about everything we need to produce referrals using the Refer gem, but we need a way to provide a reward for both users when the referred user completes a given action.

Rewarding referrals

For our use case, let's assume we will give a reward to the referrer and the referee after the referee subscribes to our site.

Because the reward is highly application-dependent we won't be diving too deep on it.

However, we need to take the requirements into consideration, especially making sure the referrer hasn't exceeded the quota and that the referee is subscribing within the required time window.

We can add that code to the User model:

class User < ApplicationRecord
  REFERRAL_WINDOW = 30.days
  MAX_ALLOWED_REFERRALS = 5

  def qualifies_for_referral?
    created_at >= REFERRAL_WINDOW.ago && referrer.referrals.count < MAX_ALLOWED_REFERRALS
  end
end
class SubscriptionsController < ApplicationController
  def create
    user = current_user
    user.subscribe(plan: @plan)
    reward_referral if user.qualifies_for_referral?
    # Rest of the subscription flow
  end

  private
  def reward_referral
    # Reward users here
  end
end

Now, let's add a referrals dashboard so users can see their referral stats:

The user dashboard

To allow users to see how they're doing with their referrals, let's add a dashboard view that includes how many visits their code has, how many sign-ups and how many subscriptions from referred users they have.

Let's start by defining the route:

# config/routes.rb
get "referrals", to: "referrals#index"

Next, let's add the controller and action:

class ReferralsController < ApplicationController
  before_action :resume_session, only: [:index]

  def index
    @referrals = current_user.referrals.order(created_at: :desc).map(&:referee)
    @referral_visits = Refer::Visit.where(referral_code: current_user.referral_code_id)
    @referred_subscribers = 1 # We fake this metric
  end
end

Then, in the view, we add the following:

<%# app/views/referrals/index.html.erb %>
<div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8 pt-6 pb-24">
  <div class="grid grid-cols-12 gap-x-6 gap-y-8 mt-4">
    <h1 class="text-3xl font-bold col-span-12">Referral History</h1>
    <div class="col-span-12 lg:col-span-4 h-full">
      <div class="bg-white rounded-lg border border-gray-200 shadow-xs pt-6 pb-8 px-6 h-full">
        <div class="flex items-center space-x-2">
          <i data-lucide="eye" class="w-6 h-6 text-amber-500"></i>
          <h3 class="text-base font-semibold">Referral Visits</h3>
        </div>

        <div class="mt-4 space-y-2">
          <p class="text-5xl font-medium"><%= @referral_visits.count %></p>
          <p class="text-sm text-gray-500">The number of times your referral code has been visited.</p>
        </div>
      </div>
    </div>

    <div class="col-span-12 lg:col-span-4 h-full">
      <div class="bg-white rounded-lg border border-gray-200 pt-6 pb-8 px-6 h-full">
        <div class="flex items-center space-x-2">
          <i data-lucide="user-plus" class="w-6 h-6 text-blue-500"></i>
          <h3 class="text-base font-semibold">Referred Sign-ups</h3>
        </div>

        <div class="mt-4 space-y-2">
          <p class="text-5xl font-medium"><%= current_user.referrals.count %></p>
          <p class="text-sm text-gray-500">The number of times your referral code has been used to sign up.</p>
        </div>
      </div>
    </div>
    <div class="col-span-12 lg:col-span-4 h-full">
      <div class="bg-white rounded-lg border border-gray-200 pt-6 pb-8 px-6 h-full">
        <div class="flex items-center space-x-2">
          <i data-lucide="gift" class="w-6 h-6 text-emerald-500"></i>
          <h3 class="text-base font-semibold">Referred Subscribers</h3>
        </div>

        <div class="mt-4 space-y-2">
          <p class="text-5xl font-medium"><%= @referred_subscribers %></p>
          <p class="text-sm text-gray-500">The number of referred users that became subscribers.</p>
        </div>
      </div>
    </div>
  </div>

  <div class="mt-8">
    <div class="flex items-center justify-between">
      <h2 class="text-xl font-semibold text-gray-900">Recent Referrals</h2>
    </div>

    <div class="mt-4 bg-white rounded-lg border border-gray-200 overflow-hidden">
      <div class="overflow-x-auto">
        <table class="min-w-full divide-y divide-gray-200">
          <thead>
            <tr class="bg-white">
              <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-600 uppercase tracking-wider">User</th>
              <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-600 uppercase tracking-wider">Referred On</th>
              <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-600 uppercase tracking-wider">Status</th>
              <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-600 uppercase tracking-wider">Plan</th>
            </tr>
          </thead>
          <tbody class="bg-white divide-y divide-gray-200">
            <% @referrals.each do |referral| %>
              <tr class="hover:bg-gray-50 transition-colors">
                <td class="px-6 py-4 whitespace-nowrap">
                  <div class="flex items-center">
                    <div class="h-10 w-10 flex-shrink-0">
                      <img class="h-10 w-10 rounded-full" src="https://ui-avatars.com/api/?name=<%= referral.full_name %>&background=6366f1&color=fff" alt="Sarah Wilson">
                    </div>
                    <div class="ml-4">
                      <div class="text-sm font-medium text-gray-900"><%= referral.full_name %></div>
                      <div class="text-sm text-gray-500"><%= referral.username %></div>
                    </div>
                  </div>
                </td>
                <td class="px-6 py-4 whitespace-nowrap">
                  <div class="text-sm text-gray-900"><%= referral.created_at.strftime("%B %d, %Y") %></div>
                  <div class="text-sm text-gray-500"><%= referral.created_at.strftime("%I:%M %p") %></div>
                </td>
                <td class="px-6 py-4 whitespace-nowrap">
                  <% if referral.subscription_status == "Active" %>
                    <div class="px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
                      <%= referral.subscription_status %>
                    </div>
                  <% else %>
                    <div class="px-3 py-1 inline-flex text-xs leading-5 font-semibold rounded-full bg-amber-100 text-amber-800">
                      <%= referral.subscription_status %>
                    </div>
                  <% end %>
                </td>
                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
                  <%= referral.subscription_plan %>
                </td>
              </tr>
            <% end %>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</div>

Which produces the following result:

Referral index view

Summary

Adding a referral system is a good way to lower customer acquisition costs while also providing value to referrers and new users.

Within the Rails world, keeping track of user referrals and who referred whom can be achieved using cookies and a couple of models.

However, solutions like the Refer gem can help us save at least a couple of hours and make our lives easier with a proven solution.

Adding referrals with the gem is as easy as adding the gem, running the installation command, and adding it to a specific model running the bin/rails generate refer:model User command that adds a has_referrals call to our desired model, responsible for associations.

Then, we add the set_referral_cookie to our ApplicationController or any specific controller we want to. This method is responsible for using the ref=code query parameter and setting a cookie in the referred user's browser which we can use to see who referred new users to our application.

Finally, to complete a referral, we have to use the refer method and pass it a User instance to create an entry in the refer_referrals table that we can later use to provide the adequate rewards to users that successfully referred other users.

I hope this article can help you implement a referral system in your next Rails application.

Have a good one and happy coding!


Build your next rails app 10x faster with Avo

Avo dashboard showcasing data visualizations through area charts, scatterplot, bar chart, pie charts, custom cards, and others.

Find out how Avo can help you build admin experiences with Rails faster, easier and better.