Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

Subscription Pause in Next.js -- How to Let Customers Pause Instead of Cancel and Reduce Churn

August 1, 2026
nextjsstripesaasdrizzle-ormpayments

Every SaaS cancel flow has the same problem: you ask "are you sure?" and then the customer is gone.

Some of those customers do not actually want to leave. They are going through a slow quarter, taking parental leave, or just stretched thin that month. Given the choice between canceling or staying active, many would choose a third option: pause.

A subscription pause lets a customer stop billing temporarily without losing their account, data, or seat. When their situation changes, they resume with one click. For some SaaS products, adding a pause option reduces voluntary cancellations by 20-30% -- without a discount, a concession, or a support ticket.

This post covers when to offer pause, the decisions you need to make upfront, and how to implement it with Stripe and Drizzle ORM in a Next.js SaaS boilerplate.

When a Pause Makes Sense -- and When It Does Not

A pause works when your product has a reason to come back. Seasonal businesses, consultants between projects, and companies going through a budget freeze all have a genuine intent to return. Offering pause to these users keeps the relationship alive.

It does not work as a retention tool if your product is "sticky" only because of the data stored inside it. Users who do not find value will pause and never return -- which costs you nothing more than a delayed cancellation, but sets an expectation that pause is always available.

The rule: offer pause when you genuinely believe the user will come back. Gate it inside your cancel flow so it appears as a save offer after the user initiates cancel, not as a default option in settings.

The Design Decision: How Long Should a Pause Last?

Stripe does not have a native pause endpoint. You handle it yourself, which means you need to decide:

  • Maximum pause duration. 30 days is conservative. 90 days is common. Some SaaS products allow up to 6 months. Pick based on how long a typical seasonal gap lasts for your audience.
  • What happens to data during a pause. Typically: the account stays active, data is preserved, but access to paid features is restricted -- the same behavior as a plan downgrade.
  • Resume behavior. When the pause ends, the user must actively resume. If they do not, treat it as a cancellation.

Decide these before you write a line of code. They affect the UI, the database schema, and the background job that expires stale pauses.

How to Implement It With Stripe and Drizzle ORM

Since Stripe lacks a pause endpoint, you handle it at the subscription level:

  1. Cancel the Stripe subscription at period end -- cancel_at_period_end: true. This stops future billing without terminating the current access period.
  2. Record the pause in your database -- a paused_until column on the subscription row, plus a status field set to paused.
  3. Gate access to paid features -- your feature checks read from the database status, not only from Stripe. During a pause, show a banner with a resume button.
  4. On resume -- create a new Stripe subscription (same plan, same customer ID), update the row to status: active, and clear paused_until.
  5. On expiry -- a background job (or the customer.subscription.deleted Stripe webhook) sets status to canceled when paused_until has passed without a resume.

Here is the database shape using Drizzle ORM:

// modules/subscription/subscription.schema.ts
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core'
 
export const subscriptionTable = pgTable('subscriptions', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').notNull(),
  stripeSubscriptionId: text('stripe_subscription_id'),
  // active | paused | canceled
  status: text('status').notNull().default('active'),
  pausedUntil: timestamp('paused_until'),
  currentPeriodEnd: timestamp('current_period_end'),
})

And the service method that initiates a pause:

// modules/subscription/subscription.service.ts
async function pauseSubscription(userId: string, pauseDays: number) {
  const sub = await subscriptionRepo.findByUserId(userId)
  if (!sub || sub.status !== 'active') throw new HttpError(400, 'No active subscription')
  if (pauseDays > 90) throw new HttpError(400, 'Maximum pause is 90 days')
 
  await stripe.subscriptions.update(sub.stripeSubscriptionId, {
    cancel_at_period_end: true,
  })
 
  const pausedUntil = new Date()
  pausedUntil.setDate(pausedUntil.getDate() + pauseDays)
 
  await subscriptionRepo.update(sub.id, { status: 'paused', pausedUntil })
}

The key insight: cancel_at_period_end: true is reversible. You can undo it by updating the subscription again when the user resumes, which gives you time to collect payment before actually ending access.

Where to Put the Pause Offer in Your Cancel Flow

The pause offer works best as a save step -- shown after the user clicks "Cancel subscription" and before the cancel is confirmed. A practical flow:

  1. User clicks "Cancel plan" in account settings
  2. Exit survey appears: "Before you go, what is your reason for leaving?" (see the churn reduction guide for the full cancel flow pattern)
  3. If the reason is "too expensive", "not using it right now", or "temporary situation" -- show the pause offer: "Pause your plan for 30 days instead. Your data stays safe and you can resume anytime."
  4. If they decline, proceed to the cancel confirmation step

This sequencing shows the pause only to users who might genuinely benefit, not as a default that trains users to pause repeatedly instead of engaging with the product.

The Resume Path

Make resuming frictionless. When a paused-account user logs in, show a top-of-page notice: "Your plan is paused until [date]. Resume now to restore access."

The resume action creates a new Stripe subscription. If the user's card has expired since they paused, this is where you catch it and prompt for an updated payment method. The Stripe subscriptions guide covers the checkout session pattern you can reuse for the resume flow.

What This Saves You

A user who pauses instead of cancels keeps your monthly revenue whole during the pause window -- and more importantly, they are far more likely to reactivate than someone who fully canceled. You do not spend a marketing dollar on re-acquisition because the relationship never ended.

The implementation builds on what is already in a Next.js SaaS boilerplate: Stripe is wired, Drizzle ORM is set up, and Resend can send the pause confirmation and the upcoming-expiry reminder. Pause is an extension of the infrastructure you already have, not a separate system to maintain.

If you are building on this boilerplate, add the pause option to your next cancel flow update -- it is one of the highest-ROI retention features you can ship in an afternoon.