The moment your SaaS gets organic traffic, someone will ask: "Do you have an affiliate program?" Bloggers, newsletter writers, and micro-influencers want to promote products they use -- but only when there is a link to track their sales and a clear way to get paid.
Rewardful charges $49 to $200 a month to answer that question. PartnerStack charges more. Both work well, but paying $49 a month before your first affiliate converts a single customer is real money if you are still validating.
Here is how to build the core mechanics of an affiliate program yourself -- unique links, click tracking, signup attribution, and commission records -- using Next.js, Drizzle ORM, and the database you already have.
These are different things. A referral program rewards your existing users for telling other users about your product -- the referral program post covers that. An affiliate program is for external publishers: bloggers, YouTube creators, and newsletter writers who may never sign up themselves. They share a link, you track conversions, and you pay a commission when their audience buys.
The mechanics are different enough to warrant separate tables and tracking logic.
One row per publisher. The minimum you need:
id
name
email
code -- their unique referral slug, e.g. "sarahblogs"
commission_pct
status -- active or inactive
created_at
The code becomes the query param in their link: https://yourapp.com/?ref=sarahblogs. Keep it short and URL-safe.
When someone lands on any page with ?ref=<code>, write a cookie that lasts 30 days:
const ref = new URL(request.url).searchParams.get('ref')
if (ref) {
response.cookies.set('aff_ref', ref, {
maxAge: 60 * 60 * 24 * 30,
path: '/',
httpOnly: true,
})
}
Put this in Next.js middleware so it fires on any landing page. Store the raw code, not the affiliate ID -- you resolve it at conversion time, which lets you handle renamed or deleted affiliates gracefully.
When a new user registers, read the cookie and record who sent them:
const ref = cookies().get('aff_ref')?.value
if (ref) {
const affiliate = await affiliateRepo.findByCode(ref)
if (affiliate?.status === 'active') {
await userRepo.update(newUserId, { affiliateId: affiliate.id })
}
}
This is one nullable foreign key on your users table: affiliate_id pointing to affiliates.id. Most users arrive without a referral, so keep it optional.
Wire this into your Stripe webhook for checkout.session.completed. When a user with an affiliate_id converts to a paid plan:
const user = await userRepo.findById(session.client_reference_id)
if (user?.affiliateId) {
const affiliate = await affiliateRepo.findById(user.affiliateId)
const commissionCents = Math.floor(amountPaid * (affiliate.commissionPct / 100))
await commissionRepo.create({
affiliateId: affiliate.id,
userId: user.id,
amountCents: commissionCents,
stripePaymentId: session.payment_intent,
status: 'pending',
})
}
Mark commissions pending on creation. Payouts start manual: once a month, pull all pending commissions grouped by affiliate, pay via bank transfer or Stripe Payouts, then mark them paid. Manual payouts are the right starting point -- automating them introduces edge cases faster than the time savings justify at low volume.
A custom setup skips fraud detection, automatic payouts, affiliate-facing dashboards with real-time conversion stats, and tax form collection (US affiliates earning over $600 per year need a W-9). Those are real needs. If your program grows to 20 or more active affiliates, a platform like Rewardful starts making financial sense.
But if you have five to ten publisher relationships you want to track without a monthly platform fee, this is enough to launch, learn, and iterate.
Building this yourself means you own every record and every payout decision. You also own every bug. Most founders who start with a custom affiliate table migrate to a platform around the 15-20 affiliate mark -- by then you have real conversion data and know exactly what features you actually need.
Starting custom gives you that data before you spend money on tooling you might not need.
If you want a starting point that already has Stripe subscriptions wired up, JWT auth, and the Drizzle ORM patterns this feature builds on, the Next.js SaaS boilerplate handles the foundation -- you are adding affiliate tracking, not building a billing system from scratch first.
Get the boilerplate and ship your affiliate program this week.