Skip to main content
Returns a fluent builder for presenting offers with chainable callback methods. Provides an expressive alternative to presentOffer().

Signature

function placement(): PlacementBuilder

Return Value

Type: PlacementBuilder
interface PlacementBuilder {
  onGranted(callback: (entitlement: EntitlementType) => void): PlacementBuilder
  onNotGranted(callback: (reason: NotGrantedReason) => void): PlacementBuilder
  onLoadingStateChange(callback: (isLoading: boolean) => void): PlacementBuilder
  show(): Promise<PresentationResult>
}

PlacementBuilder Methods

onGranted()
method
Set callback for when entitlement is granted
onGranted(callback: (entitlement: EntitlementType) => void): PlacementBuilder
Returns: PlacementBuilder for chaining
onNotGranted()
method
Set callback for when entitlement is not granted
onNotGranted(callback: (reason: NotGrantedReason) => void): PlacementBuilder
Returns: PlacementBuilder for chaining
onLoadingStateChange()
method
Set callback for loading state changes
onLoadingStateChange(callback: (isLoading: boolean) => void): PlacementBuilder
Returns: PlacementBuilder for chaining
show()
method
Display the offer modal
show(): Promise<PresentationResult>
Returns: Promise<PresentationResult>

Examples

Basic Usage

import Encore from '@encore/web-sdk';

const result = await Encore.placement()
  .onGranted((entitlement) => {
    console.log('Granted:', entitlement);
  })
  .onNotGranted((reason) => {
    console.log('Not granted:', reason);
  })
  .show();

With Loading State

const result = await Encore.placement()
  .onLoadingStateChange((isLoading) => {
    setLoading(isLoading);
  })
  .onGranted((entitlement) => {
    unlockFeature();
    trackEvent('offer_accepted');
  })
  .onNotGranted((reason) => {
    if (reason === 'noOffersAvailable') {
      showAlternativeFlow();
    }
  })
  .show();

Complete Example

import Encore from '@encore/web-sdk';

async function presentOfferWithCallbacks() {
  const result = await Encore.placement()
    .onLoadingStateChange((isLoading) => {
      document.getElementById('btn').disabled = isLoading;
      document.getElementById('btn').textContent = 
        isLoading ? 'Loading...' : 'Get Premium';
    })
    .onGranted((entitlement) => {
      console.log('User granted:', entitlement);
      showSuccessMessage('Premium access unlocked!');
      unlockPremiumFeatures();
      analytics.track('offer_accepted', {
        type: entitlement.type
      });
    })
    .onNotGranted((reason) => {
      console.log('Not granted:', reason);
      
      if (reason === 'noOffersAvailable') {
        showMessage('No offers available right now');
      } else if (reason === 'userDeclinedLastOffer') {
        showMessage('Check out our pricing plans');
        redirectToPricing();
      }
      
      analytics.track('offer_declined', { reason });
    })
    .show();
  
  return result;
}

Framework Integration

  • React
  • Vue
  • Angular
import { useState } from 'react';
import Encore from '@encore/web-sdk';

function OfferButton() {
  const [loading, setLoading] = useState(false);
  
  const handleClick = async () => {
    await Encore.placement()
      .onLoadingStateChange(setLoading)
      .onGranted((entitlement) => {
        console.log('Granted:', entitlement);
      })
      .onNotGranted((reason) => {
        console.log('Not granted:', reason);
      })
      .show();
  };
  
  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? 'Loading...' : 'Get Premium'}
    </button>
  );
}

Comparison with presentOffer()

Both methods are equivalent, choose based on your preference:
  • placement() - Fluent
  • presentOffer() - Options
// Fluent, chainable style
await Encore.placement()
  .onGranted(handleGranted)
  .onNotGranted(handleNotGranted)
  .show();

Callback Execution

Callbacks are executed in this order:
  1. onLoadingStateChange(true) - When modal starts loading
  2. onGranted() OR onNotGranted() - Based on result
  3. onLoadingStateChange(false) - When modal closes
await Encore.placement()
  .onLoadingStateChange((isLoading) => {
    console.log('Loading:', isLoading);
    // Called twice: true (start), false (end)
  })
  .onGranted((entitlement) => {
    console.log('Granted:', entitlement);
    // Only if user claims offer
  })
  .onNotGranted((reason) => {
    console.log('Not granted:', reason);
    // Only if user doesn't claim offer
  })
  .show();

Method Chaining

All builder methods return this, allowing method chaining:
// All methods can be chained
Encore.placement()
  .onGranted(callback1)
  .onNotGranted(callback2)
  .onLoadingStateChange(callback3)
  .show();

// Order doesn't matter (except .show() must be last)
Encore.placement()
  .onLoadingStateChange(callback3)
  .onGranted(callback1)
  .onNotGranted(callback2)
  .show();

Best Practices

1. Always Call show()

The builder doesn’t present anything until show() is called:
// Good
await Encore.placement()
  .onGranted(handler)
  .show();  // ✅ Presents modal

// Doesn't work
Encore.placement()
  .onGranted(handler);  // ❌ Nothing happens

2. Use Loading State Callback

Provide feedback during async operations:
// Good
Encore.placement()
  .onLoadingStateChange(setLoading)  // ✅ Shows loading state
  .show();

3. Handle All Outcomes

// Good
Encore.placement()
  .onGranted(handleSuccess)
  .onNotGranted(handleDecline)  // ✅ Handles both cases
  .show();

4. Callbacks Are Optional

You can use the builder without callbacks:
// Still works - just uses the promise result
const result = await Encore.placement().show();

if (result.granted) {
  // Handle result
}

Complete Example

class OfferManager {
  async showOffer() {
    const result = await Encore.placement()
      .onLoadingStateChange((isLoading) => {
        this.updateLoadingUI(isLoading);
      })
      .onGranted((entitlement) => {
        this.handleGranted(entitlement);
      })
      .onNotGranted((reason) => {
        this.handleNotGranted(reason);
      })
      .show();
    
    return result;
  }
  
  updateLoadingUI(isLoading) {
    const button = document.getElementById('offerBtn');
    button.disabled = isLoading;
    button.textContent = isLoading ? 'Loading...' : 'Get Premium';
  }
  
  handleGranted(entitlement) {
    console.log('Access granted:', entitlement);
    
    // Update UI
    this.showSuccessMessage('Premium unlocked!');
    this.unlockFeatures();
    
    // Track analytics
    this.trackEvent('offer_accepted', {
      entitlementType: entitlement.type,
      value: entitlement.value
    });
  }
  
  handleNotGranted(reason) {
    console.log('Not granted:', reason);
    
    if (reason === 'noOffersAvailable') {
      this.showMessage('No offers available');
    } else if (reason === 'userDeclinedLastOffer') {
      this.showPricingAlternative();
    }
    
    // Track analytics
    this.trackEvent('offer_declined', { reason });
  }
}

Next Steps