npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@razorpay/razorpay-js

v1.0.0

Published

Official npm loader for Razorpay's browser products — Standard Checkout, Custom Checkout, and RazorpayID

Readme

@razorpay/razorpay-js

Official npm loader for Razorpay's browser products — Standard Checkout, Custom Checkout, and RazorpayId — replacing hand-rolled <script src="https://checkout.razorpay.com/v1/..."> injection.

import RazorpayCheckout from '@razorpay/razorpay-js/checkout';

const checkout = await RazorpayCheckout({
  key: 'rzp_test_xxx',
  order_id: 'order_xxx',
});
checkout.open();

Importing the product also starts downloading its script, so by the time a customer reaches your pay button the await is instant. If they get there first, the await simply waits. There is nothing else to wire up — no readiness flag, no load callback, no stored constructor.

What you get back is a real product instance: the exact object a <script> tag would have produced. Nothing is proxied and no contract is altered.

Not the server-side SDK. This package runs in the browser. For server-side API calls (orders, refunds, payouts) use the razorpay Node package.

Why this package?

  • No <script> tag. Import it like any other npm dependency; scripts are injected for you, at most once per page.
  • Multiple products, one page. Historically every Razorpay script claimed the window.Razorpay global — last one loaded won, so Standard and Custom Checkout could not coexist. Each product function resolves its own product (see How it works).
  • Failures you can act on. A blocked or failed script rejects your await, at the click, where you can still show a fallback.
  • Types included, and an out-of-date type can never break your build.
  • Zero dependencies, a few kilobytes, ESM and CJS.

Install

npm install @razorpay/razorpay-js

Integration styles

There are two, plus one optimisation. The code at the payment moment is identical in all of them — only the moment the download starts differs, so there is no way to wire this up incorrectly, only faster or slower.

Default — import the product where you use it

Recommended.

import RazorpayCheckout from '@razorpay/razorpay-js/checkout';
// the script is already downloading

async function payNow(order) {
  const checkout = await RazorpayCheckout({
    key: 'rzp_live_xxx',
    order_id: order.id,
    handler: (response) => verifyOnServer(response),
  });
  checkout.open();
}

Add a pre-load at your app entry

If your payment code sits in a lazily-loaded route, the import above only runs when that route runs. A bare import at your entry file starts the download at boot instead. Nothing else changes.

// src/main.ts
import '@razorpay/razorpay-js/checkout';

Deferred — nothing loads until you say so

Import from the package root instead. Nothing touches the network until you either call loadRazorpay or await a product.

import { loadRazorpay, RazorpayCheckout } from '@razorpay/razorpay-js';

function onCartOpened() {
  loadRazorpay('checkout'); // optional: start the download now
}

const checkout = await RazorpayCheckout({ key, order_id, handler });
checkout.open();

Products

| Product | Import | Slug (for loadRazorpay) | | ----------------- | --------------------------------------- | ------------------------- | | Standard Checkout | @razorpay/razorpay-js/checkout | checkout | | Custom Checkout | @razorpay/razorpay-js/custom-checkout | custom_checkout | | Magic Checkout | @razorpay/razorpay-js/magic-checkout | magic_checkout | | RazorpayId | @razorpay/razorpay-js/id | id |

Standard Checkout

import RazorpayCheckout from '@razorpay/razorpay-js/checkout';

const checkout = await RazorpayCheckout({
  key: 'rzp_live_xxx',
  order_id: order.id,
  amount: order.amount,
  currency: 'INR',
  handler: (response) => verifyOnServer(response),
});
checkout.on('payment.failed', (e) => showError(e.error.description));
checkout.open();

Magic Checkout

Magic is its own product, not Standard Checkout with an extra option — it is served by a different script, and only that script marks the checkout iframe as Magic and ships the <magic-checkout-btn> element. Import it and the rest is Standard Checkout's API.

import RazorpayMagicCheckout from '@razorpay/razorpay-js/magic-checkout';

const checkout = await RazorpayMagicCheckout({
  key: 'rzp_live_xxx',
  order_id: order.id,
});
checkout.open();

Whether the one-click flow actually runs is decided by your Razorpay preferences and the order — importing this product is what makes it reachable.

Custom Checkout

import RazorpayCustomCheckout from '@razorpay/razorpay-js/custom-checkout';

const rzp = await RazorpayCustomCheckout({ key: 'rzp_live_xxx' });
rzp.on('payment.success', (r) => verifyOnServer(r));
rzp.on('payment.error', (e) => showError(e.error.description));
rzp.createPayment({ amount, currency: 'INR', order_id, method: 'upi', vpa });

Custom Checkout also exposes methods on the constructor, for merchants who need the available payment methods before any payment exists. These are available only after the product has loaded — await anything for this product first:

import RazorpayCustomCheckout from '@razorpay/razorpay-js/custom-checkout';
import { loadRazorpay } from '@razorpay/razorpay-js';

await loadRazorpay('custom_checkout');
RazorpayCustomCheckout.payment.getMethods(renderOptions);

Using them earlier throws a product_not_loaded error naming the fix. Note that on a warm cache the script is usually there already, so code that skips the await can pass local testing and fail for a first-time customer on a slow connection — keep the awaiting line even where it looks redundant.

RazorpayId

RazorpayId reports some failures as return values rather than exceptions. The loader passes whatever the product returns through untouched.

import RazorpayId from '@razorpay/razorpay-js/id';

const id = await RazorpayId({ key: 'rzp_live_xxx', mode: 'widget' });
if ('code' in id) {
  fallbackToOwnLogin(id); // { code, description, reason, source }
} else {
  id.on('login.success', (user) => onLoggedIn(user));
  id.open();
}

Several products on one page

import RazorpayCheckout from '@razorpay/razorpay-js/checkout';
import RazorpayId from '@razorpay/razorpay-js/id';
// both download independently, in parallel

const id = await RazorpayId({ key, mode: 'headless' });
const checkout = await RazorpayCheckout({ key, order_id });

React / Next.js

No useEffect, no readiness state, no stored constructor.

'use client';
import RazorpayCheckout from '@razorpay/razorpay-js/checkout';

export function PayButton({ order }) {
  const [busy, setBusy] = useState(false);

  const pay = async () => {
    setBusy(true);
    try {
      const checkout = await RazorpayCheckout({
        key: 'rzp_live_xxx',
        order_id: order.id,
        handler: onSuccess,
      });
      checkout.open();
    } catch {
      redirectToHostedCheckout(order.id);
    } finally {
      setBusy(false);
    }
  };

  return (
    <button onClick={pay} disabled={busy}>
      Pay
    </button>
  );
}

API

RazorpayCheckout(options) · RazorpayCustomCheckout(options) · RazorpayMagicCheckout(options) · RazorpayId(options)

Returns a promise for a ready product instance. options is the product's own constructor options, unchanged.

Guarantees:

  • Correct whenever you call it. It resolves immediately if the product is already there, waits if a load is in flight, reuses a script tag already on the page, or starts a load — you never have to know which.
  • At most one script per page, however many components import or call it.
  • A new instance per call, built from that call's options. (A Checkout instance belongs to one payment attempt, so reusing one across orders would silently open the wrong order.)
  • Failures are never cached — the next call retries, so a customer who hit a flaky network succeeds when they click again.
  • It never hangs: every load is internally time-bounded and always settles.

Need one shared instance? Make one — with your options, by your choice:

export const rzpPromise = RazorpayCustomCheckout({ key: 'rzp_live_xxx' });
// elsewhere
const rzp = await rzpPromise;

loadRazorpay(slug | slug[], options?)

Optional. Starts one or more products loading, so you control when the download happens. It affects speed, never correctness.

loadRazorpay('checkout'); // fire and forget
await loadRazorpay(['checkout', 'id']); // or wait for both
  • Resolves to nothing — products come from their own functions.
  • Safe to ignore: an ignored failure never becomes an unhandled rejection, and reappears at the product's await, where you can handle it.
  • With several slugs it rejects with the first failure; the others carry on. A later failure is not lost — it surfaces the next time that product is awaited. For one result per product, use Promise.allSettled([loadRazorpay('checkout'), loadRazorpay('id')]).
  • Passing an unknown slug throws immediately — that is a mistake in your code, not a runtime condition, so it is never swallowed by a fire-and-forget call.

Optionsnonce: a CSP nonce for the injected script. Only needed when your Content-Security-Policy is nonce-based and neither allows checkout.razorpay.com nor uses 'strict-dynamic'. The loader detects your page's existing nonce automatically, so this is an override rather than a requirement.

Errors

Every rejection from the loader is a RazorpayLoaderError following Razorpay's standard error format. Branch on reason; description is human-readable and may change.

import RazorpayCheckout from '@razorpay/razorpay-js/checkout';
import { RazorpayLoaderError, ERROR_REASONS } from '@razorpay/razorpay-js';

try {
  const checkout = await RazorpayCheckout(options);
  checkout.open();
} catch (error) {
  if (error instanceof RazorpayLoaderError) {
    redirectToHostedCheckout(order.id); // blocked, offline, or timed out
  }
}

| reason | Meaning | What to do | | ------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------- | | script_load_failed | Network error, content blocker, CSP block, or a CDN error. The most common real failure. | Retriable. Show a fallback. | | script_load_timeout | The script did not arrive in time. | Retriable. | | product_registration_missing | The script loaded but the product was not there afterwards. | Not fixable by you — contact support. | | product_not_loaded | A constructor method was used before the product loaded. | Await the product first. | | unknown_product | Unknown slug passed to loadRazorpay. | Fix the slug. | | input_validation_failed | Bad argument. | Fix the call. | | unsupported_environment | Called outside a browser (typically SSR). | Move the call to client-side code. |

Anything the product itself throws or returns passes through untouched — so a RazorpayLoaderError always means the loader could not deliver the product, and anything else is the product speaking.

Server-side rendering

Every import is inert on the server, including the product paths, so importing this package never breaks a server render. Awaiting a product outside a browser rejects with unsupported_environment — in practice this does not happen, because awaits live in event handlers.

TypeScript

Types ship with the package; each entry point carries its own, so importing /checkout gives you Checkout's types only.

Product types are deliberately loose: verified members are typed strictly (you get autocompletion and signature checks), and anything else is admitted through an index signature. An out-of-date type in this package can never break your build — at worst a newly shipped product method lacks autocompletion until its type lands here. Products deploy independently of this package, so this is on purpose.

How it works

The package contains no product logic. Awaiting a product function:

  1. looks for that product's constructor already on the page;
  2. injects its evergreen CDN script if nothing is loading yet, or joins the load already running, or reuses a script tag already in the DOM;
  3. constructs with the product's real constructor and hands you the result, untouched.

Products publish themselves either under a global named for exactly one product (window.RazorpayId) or, when they share window.Razorpay, under their slug in window.Razorpay._modules. That is what lets two products coexist on a page without one overwriting the other. Existing script-tag integrations are unaffected — window.Razorpay behaves exactly as before.

Support