Handling repeated trial periods in Shopify Billing (Remix App)

Topic summary

A developer discovered that Shopify’s Billing API allows shops to repeatedly cancel and re-subscribe to plans, receiving the trial period (e.g., 3 days) each time—creating a loophole for indefinite free access.

Confirmed Default Behavior:

  • Shopify treats each subscription as new and doesn’t prevent repeated trials
  • No built-in API method exists to detect previous trial usage by a shop

Recommended Solution:

  • Manually track trial usage in your backend database (PostgreSQL, MongoDB, etc.)
  • Store shop domain and trial start date on first subscription
  • Before creating new billing plans, query your database to check trial history
  • Set trialDays: 0 for shops that already used their trial
  • Monitor subscription webhooks for real-time database updates on cancellations/re-subscriptions

Implementation in Remix:
The developer shared their current app.upgrade loader code and requested specific code examples for:

  • Database trial tracking logic
  • Subscription webhook integration for real-time updates
  • Conditional trial assignment based on history

The discussion remains open, awaiting detailed code examples for the Remix app implementation.

Summarized with AI on October 27. AI used: claude-sonnet-4-5-20250929.

Here’s the situation:

  • A user installs the app and gets a free plan.

  • They upgrade to a monthly paid plan with a 3-day trial.

  • Before the trial ends, they cancel the subscription.

  • Then they re-subscribe to the same or different plan (like yearly) — and again receive the 3-day trial.

  • This can be repeated indefinitely, giving them free access repeatedly.

Questions:

  • Is this the default behavior of Shopify’s Billing API?

  • How can I prevent repeated trials for the same shop/user?

  • Is there any Shopify-supported way to detect if a shop has already used their trial?

  • Or should I manually track trial usage on my backend?

  • What’s the best way to implement this check in a Shopify Remix app?


Any help, guidance, or code examples would be super appreciated :folded_hands:
Thanks in advance!

1 Like

Hi @damonrcr

Great question — this is a known loophole when using Shopify’s Billing API with trials, and it’s something app developers need to handle carefully.

1. Is this default behavior?
Yes, this is the default behavior of Shopify’s Billing API. Shopify does not prevent shops from getting the trial period again if they cancel and re-subscribe — even on the same plan.

2. Can Shopify detect previous trial usage?
Shopify currently does not provide a built-in way to check whether a shop has previously used a trial for a specific app or plan. The platform treats each subscription as a new record.

3. Recommended approach: Track it manually
The best practice is to track trial usage manually in your backend. When a shop first installs the app or subscribes to a trial, store their shop domain and the date of trial usage in your database.

Then, before creating a new billing plan, check if they’ve already used a trial — and if so, present a plan with trial_days: 0 to skip the trial.

4. How to implement this in a Shopify Remix app?
In your Remix app:

When a shop initiates a plan upgrade, query your backend (e.g., a PostgreSQL or MongoDB record) to check for a previous trial.

Based on that, call the Shopify Billing API with trial_days: 0 if they’ve already used their free trial.

You might also log cancellations and re-subscriptions to make sure you’re handling edge cases properly.

1 Like

Thank you @Dotsquares

Please can you give some code for reference and how i use subscription webhook for real time updation in db? this is my app.upgrade code

export const loader = async ({ request }) => {
await connectDB();
const { billing, session } = await authenticate.admin(request);
const { shop, accessToken } = session;
const myShop = shop.replace(“.myshopify.com”, “”);

const url = new URL(request.url);
const planType = url.searchParams.get(“plan”) || “monthly”;
const selectedPlan = planType === “annual” ? ANNUAL_PLAN : MONTHLY_PLAN;

// Check for active subscription
const check = await billing.check({
plans: [selectedPlan],
isTest: true,
});

if (check.hasActiveSubscription) {
// Already subscribed → assign and return
const subscription = check.appSubscriptions[0];
await assignPaidPlan(shop, accessToken, subscription);
return redirect(“/app/plans”);
}

// Otherwise, initiate billing flow
const { confirmationUrl } = await billing.request({
plan: selectedPlan,
isTest: true,
trialDays: 3,
returnUrl: [https://admin.shopify.com/store/](https://admin.shopify.com/store/)${myShop}/apps/${process.env.APP_NAME}/app/plans,
});

return redirect(confirmationUrl);
};