# 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. The handle is authored on your product in the Ezoic dashboard and belongs to your site.

Product handles are:

- Domain-scoped.
- Case-insensitive.
- Stored lowercase.
- Limited to letters, numbers, hyphens, and underscores.

Examples:

- `premium`
- `remove-ads`
- `pro`

Use handles that describe the access level, not the current price or promotion.

## 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:

```html
<div data-premium-content hidden>
  Premium content goes here.
</div>

<script>
  window.ezsubscriptions = window.ezsubscriptions || {};
  ezsubscriptions.cmd = ezsubscriptions.cmd || [];
  ezsubscriptions.cmd.push(async function () {
    const access = await ezsubscriptions.hasAccess("premium");
    if (access.decision === "allowed") {
      document.querySelector("[data-premium-content]").hidden = false;
      return;
    }

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

`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.

## 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 sign-in, account creation, and checkout.

## 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.

## 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";
});
```

## Implementation Notes

- The product handle is what you check (`hasAccess`) and what you sell (`showPaywall({ product })`).
- Use `showPaywall({ product })` after `hasAccess(...)` returns anything other than `allowed`.
- 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 method options, callback hooks, and return values, see [JavaScript API Reference](/docs/subscriptions/api-reference/).

