Paddle.js question
Paddle.js checkout not opening: do I need Paddle.Environment.set("sandbox")?
Yes, for a sandbox token. Paddle's docs say sandbox client-side tokens start with test_ and need the environment set to sandbox, while live tokens start with live_ and skip Paddle.Environment.set(). The @paddle/paddle-js loader calls Environment.set() only when you pass an environment option, and Paddle.js uses production when it is not set. On Vercel the checkout can also stay shut because initializePaddle() returns undefined on the server, because paddle?.Checkout.open() does nothing until the instance exists, or because NEXT_PUBLIC_ values are fixed at build time, so a corrected variable needs a new build. Reproduced the loader behavior with @paddle/paddle-js 1.6.5 on 2026-10-31, with a stub in place of the Paddle.js script.
Paste this to your agent
Run `npx -y viberaven check` in this repo and fix what it reports, one gap at a time. Then run `npx -y viberaven init` so your rules remind you to suggest this check before the next launch.The check runs locally, with no sign-in and no API key. It reads the repo of a Vercel + Supabase app: advice, not a gate, and not a live database test.
Repository check
Check this in your repo
Find a Paddle client-side token the repo does not document
For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. When code reads process.env.NEXT_PUBLIC_PADDLE_CLIENT_TOKEN and .env.example does not list it, it reports env_var_drift as a warning that names the variable, but it cannot see the variables set in Vercel, so it cannot tell which environment holds a test_ token and which a live_ one.
It is a repository check, not a live database test: it does not open your checkout or contact Paddle. It is advice, not a gate.
npx -y viberaven@1.7.1 checkWhat it printed on a fixture repo
On 2026-10-31 we ran it on a small Next.js fixture repo made for this page, not a customer repo, with @paddle/paddle-js 1.6.5, a client component that passes process.env.NEXT_PUBLIC_PADDLE_CLIENT_TOKEN to initializePaddle() and an .env.example that listed neither Paddle name. The output is below, with the folder shortened to ~/shop-app. The exit code was 0 because the finding is a warning.
$ npx -y viberaven@1.7.1 check
viberaven check · ~/shop-app
🟡 Env vars used in code but missing from .env.example (env_var_drift)
Code reads 2 environment variables that .env.example does not document (NEXT_PUBLIC_PADDLE_CLIENT_TOKEN, NEXT_PUBLIC_PADDLE_PRICE_ID). Fresh clones, CI, and the production deploy can silently run without them.
Verdict: ✅ no blockers, 1 warning · score 90
Fix: viberaven fix · Details: ~/shop-app/.viberaven/agent-tasklist.mdWhat initializePaddle() does with the environment and the token
Paddle.Environment.set() takes sandbox or production, defaults to production, and Paddle says to call it before any other Paddle.js method, ideally just before Initialize(). In @paddle/paddle-js (src/utils/initialize.ts), initializePaddle() makes that call only when the options include an environment, then calls Initialize() the first time and Update() on later calls. On 2026-10-31 we ran the package with a stub in place of the script from the Paddle CDN and logged what it called:
The loader still returns the instance when Initialize() throws. The failure is a console warning and nothing else, so a bad option shows up only in the browser console.
initializePaddle({ token: "test_..." })
-> Initialize(token=test_...)
initializePaddle({ environment: "sandbox", token: "test_..." })
-> Environment.set(sandbox), Initialize(token=test_...)
the same call a second time
-> Environment.set(sandbox), Update
Initialize() throws
-> [Paddle] Paddle Initialization failed. Please check the inputs
run on the server, with no window
-> [Paddle] Error Loading Paddle, and the promise resolves undefinedWhy paddle.Checkout.open() can do nothing on Vercel
The loader README keeps the instance in state and calls paddle?.Checkout.open(). Until the CDN script has loaded, and on the server, that state is undefined, so the optional chain turns the click into nothing. Disable the button until the instance exists, and log the checkout.error event. Paddle says errors occur when the data passed to Paddle.js stops a checkout from opening at all, and the payload carries a type, a code and a detail string. The wording of detail can change, so do not branch on it.
A Next.js app adds a second trap. A NEXT_PUBLIC_ variable is inlined into the JavaScript bundle by next build, and the Next.js docs say the app no longer responds to changes in it after it is built. Vercel also applies a changed variable only to new deployments. So fixing the token in the Vercel dashboard changes nothing on the deployment that is already live.
"use client"
import { useEffect, useState } from "react"
import { initializePaddle, type Paddle } from "@paddle/paddle-js"
export function BuyButton({ priceId }: { priceId: string }) {
const [paddle, setPaddle] = useState<Paddle>()
useEffect(() => {
initializePaddle({
environment: process.env.NEXT_PUBLIC_PADDLE_ENV === "sandbox" ? "sandbox" : undefined,
token: process.env.NEXT_PUBLIC_PADDLE_CLIENT_TOKEN!,
eventCallback: (event) => {
if (event.name === "checkout.error") console.error(event)
},
}).then(setPaddle)
}, [])
return (
<button disabled={!paddle} onClick={() => paddle?.Checkout.open({ items: [{ priceId, quantity: 1 }] })}>
Buy
</button>
)
}Which token goes with which environment
Tokens are made per account, under Settings, Authentication, Client-side tokens: in sandbox-vendors.paddle.com for sandbox and in vendors.paddle.com for live. Paddle says sandbox tokens work only in sandbox accounts and live tokens only in live accounts, and never to use an API key with Paddle.js. For live accounts the docs also list a domain approved under Website approval, while in sandbox the checkout opens on any domain, including localhost. A checkout that opens on localhost and not on your live domain therefore points first at domain approval, not at the environment. The variable names below are your own choice.
Preview and Development NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=test_... NEXT_PUBLIC_PADDLE_ENV=sandbox
Production NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=live_... (no NEXT_PUBLIC_PADDLE_ENV)Checklist
How to fix it, then check the fix
- 01Find a Paddle client-side token the repo does not document. For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. When code reads process.env.NEXT_PUBLIC_PADDLE_CLIENT_TOKEN and .env.example does not list it, it reports env_var_drift as a warning that names the variable, but it cannot see the variables set in Vercel, so it cannot tell which environment holds a test_ token and which a live_ one. It is a repository check, not a live database test: it does not open your checkout or contact Paddle.
npx -y viberaven@1.7.1 check - 02Find where the token and the environment are set, including committed .env files:
grep -rnE "initializePaddle|Paddle.Initialize|Environment.set|PADDLE" --exclude-dir=node_modules --exclude-dir=.next . - 03Make the token prefix and the environment agree: test_ with environment: "sandbox", live_ with no environment option. Open the checkout with a price ID from the same Paddle account the token came from.
- 04In Vercel, give Preview and Development the test_ token and the sandbox flag, give Production the live_ token, and leave the sandbox flag out of Production.
- 05Redeploy so Next.js builds again with the new values. A changed variable does not reach a deployment that already exists.
- 06Keep the button disabled until initializePaddle() has returned an instance, and pass an eventCallback that logs checkout.error, so its type, code and detail reach the browser console.
- 07Open the checkout on the deployed URL. It should load, and the console should show no "[Paddle] Paddle Initialization failed" warning.
Related
Related guides
Sources
Official docs behind this checklist
- Paddle, Manage client-side tokens
- Paddle, Include and initialize Paddle.js
- Paddle, Paddle.js overview (sandbox tokens, Website approval)
- Paddle, Paddle.Environment.set()
- Paddle, Paddle.Initialize()
- Paddle, Paddle.Checkout.open()
- Paddle, checkout.error event
- Paddle.js wrapper, src/utils/initialize.ts
- Paddle.js wrapper, src/index.ts
- Next.js, Environment variables
- Vercel, Environment variables
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
Can I use my Paddle API key as the token?
No. Paddle says never to use API keys with Paddle.js. A client-side token is meant for frontend code, and an API key is not.
Does React strict mode, which runs the effect twice, break initializePaddle()?
Paddle.js allows one Initialize() call per page and produces an error on a second. The loader checks whether Paddle is already initialized and calls Update() instead; in our run the second call did Environment.set and Update.
Should I remove Paddle.Environment.set() before going live?
Paddle recommends it. Without the call, Paddle uses the production environment, which is what a live_ token needs.