View as Markdown

Publisher-Managed Access API

Publisher-managed access lets your site decide exactly what subscribers can see. Ezoic Subscriptions handles checkout, payment infrastructure, subscriber sessions, and access verification. Your code checks a product handle and shows the right content.

Product Handles

A product handle is the stable identifier your site checks with hasAccess(...) and sells with showPaywall({ product }). You author it on the product in your Ezoic dashboard. See Product Handles for the naming rules and examples.

Basic Access Check

Every integration follows the same shape: check a product handle, then deliver the paid benefit when the decision is allowed. The most common benefit is an ad-free experience.

<script>
  window.ezsubscriptions = window.ezsubscriptions || {};
  ezsubscriptions.cmd = ezsubscriptions.cmd || [];
  ezsubscriptions.cmd.push(async function () {
    const access = await ezsubscriptions.hasAccess("remove-ads");
    if (access.decision === "allowed") {
      // Deliver the benefit — e.g. remove ads. See Onsite Script Integration.
      return;
    }

    ezsubscriptions.showPaywall({ product: "remove-ads" });
  });
</script>
<script src="https://sm.ezoic.com/min.js" async defer></script>

Gating content works the same way — reveal a subscriber-only element instead of removing ads. See Gate Premium Content for that variant.

hasAccess(...) checks whether the current visitor has an active entitlement for the given product. showPaywall({ product }) opens Ezoic's pre-built paywall and checkout experience for that product's prices. The same product handle is what you check and what you sell. For the ad-removal specifics (ezsubscriptions.disableAds() / allowAds()), see Onsite Script Integration.

Access Decisions

hasAccess(...) returns an access decision:

  • allowed: The visitor has active access.
  • login_required: The visitor is not signed in to Ezoic Subscriptions on this site.
  • denied: The visitor is signed in but does not have access.
  • expired: The visitor had access, but it is no longer active.
  • revoked: Access was removed.
  • unknown_product: The product handle is not recognized for this site — usually a typo or an inactive product.

For most integrations, show subscriber-only content only when the decision is allowed. Treat every other decision as no current access, then call showPaywall({ product: "your-product-handle" }) or show your own message before opening checkout.

A non-allowed decision is never a reason to sign the visitor out. denied in particular means the visitor is signed in and ready to buy — calling logout() there destroys the session their checkout depends on.

Anonymous Visitors

Anonymous visitors do not require a network request for access checks:

  • hasAccess(...) returns login_required.
  • getProducts() returns an empty list.
  • getPurchases() returns an empty list.

This keeps pages responsive — there is no network round trip for a visitor who has no session to check yet. When an anonymous visitor decides to subscribe, showPaywall(...) handles identity and checkout. With expedited checkout on (the default), a guest pays on a single screen with no separate sign-in step.

Checking Multiple Features

Use getProducts() when your site has several subscriber-only features:

const products = await ezsubscriptions.getProducts();

if (products.includes("pro")) {
  enableProTools();
}

if (products.includes("premium")) {
  showPremiumNavigation();
}

Listing a Visitor's Purchases

getProducts() lists the whole-product access a visitor holds, but not individual item purchases. To build a "your purchases", downloads, or library page that includes per-item purchases, use getPurchases():

const purchases = await ezsubscriptions.getPurchases();
for (const purchase of purchases) {
  if (purchase.status !== "active") continue;
  // purchase.productKey, purchase.item, purchase.expiresAt
}

Each entry is { productKey?, item?, status, expiresAt? }. item is set for a per-item purchase; expiresAt is set only for time-limited access. Anonymous visitors return an empty list.

One-Time Purchases

For per-item purchases — unlocking a single article, download, or other one-off item rather than granting a recurring product — check hasPurchased({ item }) instead of hasAccess:

const access = await ezsubscriptions.hasPurchased({
  item: "article-12345",
});

if (access.decision === "allowed") {
  document.querySelector("[data-premium-content]").hidden = false;
}

Sell the item with ezsubscriptions.openCheckout({ price: "article-unlock", item: "article-12345" }), or let Ezoic's paywall sell it with ezsubscriptions.showPaywall({ product: "premium", item: "article-12345" }). See Products, Prices, and Paid Access for how one-time prices and items work.

Subscribe or Buy This Article

To let a subscription unlock everything or a one-time purchase unlock a single article, check both and open a paywall that offers the article price alongside the subscription:

const item = "article-12345";
const [sub, bought] = await Promise.all([
  ezsubscriptions.hasAccess("premium"),
  ezsubscriptions.hasPurchased({ item }),
]);
if (sub.decision === "allowed" || bought.decision === "allowed") {
  document.querySelector("[data-premium-content]").hidden = false;
} else {
  ezsubscriptions.showPaywall({ product: "premium", item });
}

Selling the Current Article Automatically

If you'd rather not assign each article an item key, configure the one-time price to unlock the current article automatically (see Products, Prices, and Paid Access). Then showPaywall({ product }) sells access to whatever page it runs on, and you reveal an already-bought article with hasPurchased({ page: true }):

const access = await ezsubscriptions.hasPurchased({ page: true });
if (access.decision === "allowed") {
  document.querySelector("[data-premium-content]").hidden = false;
}

{ page: true } checks the current page using the same page key Ezoic stamped at checkout, so you never compute or pass the item yourself.

Granting Access Manually

You can comp a reader access to a product without a payment — for support make-goods, sponsors, or staff. In the Ezoic dashboard, open Subscriptions → Access, click + Grant access, and enter:

  • Email — the reader's email address. It identifies (or creates) the subscriber.
  • Product — the product to grant.
  • Access days (optional) — leave blank for access that does not expire, or enter a number of days for a time-limited grant.

A manual grant charges nothing and creates no subscription — the reader simply passes hasAccess(...) for that product until the grant expires or is revoked. If the reader already holds active access, the grant is a no-op.

Reacting to Access Changes

When the visitor logs in, logs out, or completes checkout, subscribe to access:change so gated UI re-renders without a page reload:

ezsubscriptions.on("access:change", async function () {
  const access = await ezsubscriptions.hasAccess("premium");
  document.querySelector("[data-premium-content]").hidden = access.decision !== "allowed";
});
This event is the only re-check trigger you need — do not poll hasAccess(...) on a setInterval. Login, logout, account switches, and completed checkouts all fire access:change, so a timer only adds requests without catching anything the event misses.

Implementation Notes

  • Keep product and price handles stable after launch.
  • Show teaser content to everyone, then reveal or fetch the protected body only after access is allowed.

For every method's options and callbacks, see JavaScript API Reference.