# Publisher-Managed Access API

Source: https://docs.ezoic.com/docs/subscriptions/publisher-managed-access/


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](/docs/subscriptions/products/#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.

```html
<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](/docs/subscriptions/site-integration/#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](/docs/subscriptions/site-integration/#removing-ads-for-subscribers).

## 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()`](/docs/subscriptions/api-reference/#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](/docs/subscriptions/visitor-authentication/#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:

```javascript
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()`:

```javascript
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`:

```javascript
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](/docs/subscriptions/products/) 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:

```javascript
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](/docs/subscriptions/products/)). Then `showPaywall({ product })` sells access to whatever page it runs on, and you reveal an already-bought article with `hasPurchased({ page: true })`:

```javascript
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:

```javascript
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](/docs/subscriptions/api-reference/).

