SaaS Trial Gating: Feature Access Control in JavaScript

The technical guide to gating feature access during SaaS trials. Covers three gating strategies, the feature value matrix, gating UX patterns, common pitfalls, and JavaScript implementation. Plus how TrialMoments' Blocked Feature Prompt handles the done-for-you gating experience.

By TrialMoments Team13 min readPublished Mar 2026
35%
Upgrade Rate
3
Gating Strategies
12-22%
Conversion Lift

Feature gating in a SaaS trial is the practice of restricting access to certain features during the free trial period to create upgrade motivation. Instead of giving trial users full access to everything, you strategically limit premium features so users experience your core value while understanding what they'd unlock by upgrading.

Done right, feature gating increases trial-to-paid conversion by 12-22%. Done wrong, it kills activation and drives users away before they experience your product's value. The difference comes down to which features you gate, how you present the gate, and how you measure the impact.

This guide covers the technical implementation of feature gating -- client-side, server-side, and hybrid approaches -- with JavaScript code examples. We'll also cover the strategy layer: which features to gate, UX patterns for the gate itself, and the most common pitfalls that reduce conversion instead of improving it.

Three Feature Gating Strategies

Every feature gate has two concerns: enforcement (preventing access) and experience (what the user sees). Here are three strategies that combine both differently:

Strategy 1: Client-Side Gating

Gate features in the browser by checking the user's plan before showing or enabling UI elements. Simple to implement but easily bypassed. Best for non-sensitive features where the UX matters more than the enforcement.

// Client-side feature gate
const FEATURE_ACCESS = {
  trial: [
    'dashboard', 'basic-reports', 'single-project',
    'email-support', 'basic-export'
  ],
  pro: [
    'dashboard', 'basic-reports', 'single-project',
    'email-support', 'basic-export',
    'advanced-reports', 'unlimited-projects',
    'team-collaboration', 'api-access',
    'custom-branding', 'priority-support',
    'advanced-export', 'integrations'
  ],
};

function hasFeatureAccess(userPlan, featureName) {
  const allowedFeatures = FEATURE_ACCESS[userPlan] || [];
  return allowedFeatures.includes(featureName);
}

// In your UI code:
function renderFeatureButton(featureName, label) {
  const button = document.createElement('button');
  button.textContent = label;

  if (!hasFeatureAccess(currentUser.plan, featureName)) {
    // Option A: Show lock icon
    button.innerHTML = `🔒 ${label} <span class="badge">Pro</span>`;
    button.addEventListener('click', () => {
      showUpgradePrompt(featureName);
    });
  } else {
    button.addEventListener('click', () => {
      executeFeature(featureName);
    });
  }

  return button;
}

Pros

  • Fast to implement
  • Immediate UX feedback
  • No server round-trips

Cons

  • Easily bypassed via DevTools
  • Not secure for sensitive features
  • Feature list embedded in client code

Strategy 2: Server-Side Gating

Enforce feature access at the API level. Every request to a premium endpoint checks the user's plan. Secure and authoritative, but provides poor UX when the client doesn't know about gates until the request fails.

// Server-side feature gate middleware (Node.js/Express)
const PLAN_FEATURES = {
  trial: ['dashboard', 'basic-reports', 'basic-export'],
  pro: ['*'], // all features
};

function requireFeature(featureName) {
  return (req, res, next) => {
    const { plan } = req.user.subscription;
    const allowed = PLAN_FEATURES[plan] || [];

    if (allowed.includes('*') || allowed.includes(featureName)) {
      return next();
    }

    return res.status(403).json({
      error: 'feature_gated',
      feature: featureName,
      requiredPlan: 'pro',
      message: `${featureName} requires a Pro plan.`,
      upgradeUrl: '/upgrade',
    });
  };
}

// Apply to routes:
app.get('/api/reports/advanced',
  authenticate,
  requireFeature('advanced-reports'),
  advancedReportsHandler
);

app.post('/api/export/csv',
  authenticate,
  requireFeature('advanced-export'),
  csvExportHandler
);

app.post('/api/team/invite',
  authenticate,
  requireFeature('team-collaboration'),
  teamInviteHandler
);

Strategy 3: Hybrid Gating (Recommended)

Best Practice

Combine server-side enforcement with client-side UX. The server is the source of truth for access control. The client provides a smooth experience with contextual upgrade prompts instead of error messages.

// Hybrid approach: server tells client what's available,
// client handles the UX, server enforces on every request.

// 1. Server sends feature access list with auth response
app.get('/api/me', authenticate, (req, res) => {
  const { plan } = req.user.subscription;
  res.json({
    user: req.user,
    features: PLAN_FEATURES[plan],
    plan: plan,
    upgradeUrl: '/upgrade',
  });
});

// 2. Client stores and uses the feature list
let userFeatures = [];

async function initFeatureAccess() {
  const res = await fetch('/api/me');
  const data = await res.json();
  userFeatures = data.features;
  return data;
}

function canAccess(featureName) {
  return userFeatures.includes('*')
    || userFeatures.includes(featureName);
}

// 3. Client intercepts gated feature clicks
function onFeatureClick(featureName, callback) {
  if (canAccess(featureName)) {
    callback(); // proceed normally
  } else {
    showUpgradePrompt(featureName); // show upgrade UX
  }
}

// 4. Server still enforces (defense in depth)
// Even if client-side check is bypassed,
// the API returns 403 for gated features

Which Features to Gate: The Value Matrix

Not all features should be gated. Gate the wrong features and you kill activation. Gate the right ones and you create powerful upgrade motivation. Use this value matrix:

CategoryActivation ImportancePremium ValueGate?
Core workflow featuresHighLow-MediumNever gate
Advanced exportsLowHighGate (ideal)
Team collaborationLowHighGate (ideal)
API accessLowHighGate (ideal)
Custom brandingLowMediumGate
Onboarding / setupCriticalLowNever gate
IntegrationsMediumHighGate selectively

The Golden Rule of Feature Gating

Never gate features that are required for the user to reach their "aha moment." If your product's core value requires creating a project, inviting a team member, or running a report, those features must be fully available during the trial. Gate the features that enhance the workflow, not the features that enable it.

Gating UX: Show vs. Hide vs. Tease

How you present a gated feature is as important as which features you gate. There are three UX patterns:

Pattern 1: Hide (Not Recommended)

Remove gated features from the UI entirely during the trial. The user never sees them and doesn't know they exist. This is the worst approach because it creates zero upgrade motivation -- users don't know what they're missing.

// Anti-pattern: hiding gated features
function renderSidebar(userPlan) {
  return `
    <nav>
      <a href="/dashboard">Dashboard</a>
      <a href="/projects">Projects</a>
      ${userPlan === 'pro' ? '<a href="/team">Team</a>' : ''}
      ${userPlan === 'pro' ? '<a href="/api">API</a>' : ''}
    </nav>
  `;
  // Trial users never see Team or API options
  // They have no idea these features exist
  // Zero upgrade motivation created
}

Pattern 2: Disable (Better)

Show gated features in the UI but disable them with a visual indicator (lock icon, "Pro" badge). Users can see premium features exist but can't access them. This creates awareness but provides no contextual value proposition when clicked.

// Better: show but disable with visual indicator
function renderSidebar(userPlan) {
  const isTrialUser = userPlan === 'trial';
  return `
    <nav>
      <a href="/dashboard">Dashboard</a>
      <a href="/projects">Projects</a>
      <a href="/team" class="${isTrialUser ? 'gated' : ''}">
        ${isTrialUser ? '🔒 ' : ''}Team
        ${isTrialUser ? '<span class="badge-pro">Pro</span>' : ''}
      </a>
      <a href="/api" class="${isTrialUser ? 'gated' : ''}">
        ${isTrialUser ? '🔒 ' : ''}API Access
        ${isTrialUser ? '<span class="badge-pro">Pro</span>' : ''}
      </a>
    </nav>
  `;
}

Pattern 3: Tease (Best)

Recommended

Show gated features, let users click on them, and then display a contextual upgrade prompt that explains the value. This is the highest-converting pattern because it lets users express intent (clicking) and then immediately presents the upgrade opportunity with a relevant value proposition.

// Best: tease with contextual upgrade prompt
function renderSidebar(userPlan) {
  return `
    <nav>
      <a href="/dashboard">Dashboard</a>
      <a href="/projects">Projects</a>
      <a href="#" onclick="handleGatedClick('team')">
        Team <span class="badge-pro">Pro</span>
      </a>
      <a href="#" onclick="handleGatedClick('api')">
        API Access <span class="badge-pro">Pro</span>
      </a>
    </nav>
  `;
}

function handleGatedClick(featureName) {
  // With TrialMoments — one line:
  TrialMoments.triggerBlockedFeature(featureName);

  // Or manually — 50+ lines of modal code:
  // showUpgradeModal({
  //   feature: featureName,
  //   title: `Unlock ${featureName}`,
  //   description: getFeatureDescription(featureName),
  //   upgradeUrl: '/upgrade',
  // });
}

Common Pitfalls That Kill Conversion

Over-Gating: Blocking the Core Experience

The most common and most destructive mistake. When you gate more than 30% of features, trial users can't experience enough value to justify upgrading. They churn before reaching their aha moment. Activation rates drop by 40-60% with aggressive gating. Always prioritize letting users succeed with your product first.

Under-Gating: No Upgrade Motivation

The opposite extreme. When trial users have full access to everything, there's no urgency to upgrade. The only conversion driver becomes the time limit, which is the weakest motivator. Users think: "I have everything I need already, why would I pay?" Gate enough features to create a clear value gap between trial and paid.

Silent Gating: No Explanation

Returning a generic 403 error or showing a bare "feature not available" message when a user clicks on a gated feature. This creates frustration instead of motivation. Every gate should include a clear explanation of what the feature does, why it's premium, and a direct path to upgrade. This is where TrialMoments' Blocked Feature Prompt excels.

Inconsistent Gating: Client Allows, Server Blocks

When client-side UI shows a feature as available but the server rejects the request, users experience a broken product. Always sync your client-side feature list with the server-side access control. Use the hybrid approach where the server provides the definitive feature list to the client on authentication.

Measuring Gating Effectiveness

You can't optimize what you don't measure. Track these three metrics to evaluate and improve your gating strategy:

Gate Click Rate

% of trial users who click on gated features

Target: 15-25%

Below 5%? Gates aren't visible enough.

Gate-to-Upgrade Rate

% who upgrade after hitting a gate

Target: 8-15%

Below 3%? Prompt isn't compelling enough.

Overall Conversion Lift

Trial-to-paid rate with vs. without gating

Target: 12-22% lift

Negative? You're over-gating.

// Track gating events for measurement
function trackGateEvent(featureName, action) {
  // Send to your analytics platform
  analytics.track('feature_gate_interaction', {
    feature: featureName,
    action: action, // 'viewed', 'clicked', 'upgraded', 'dismissed'
    userPlan: currentUser.plan,
    daysInTrial: getDaysInTrial(currentUser),
    timestamp: new Date().toISOString(),
  });
}

// Track when user sees a gated feature indicator
trackGateEvent('advanced-export', 'viewed');

// Track when user clicks on the gated feature
trackGateEvent('advanced-export', 'clicked');

// Track when user upgrades from the gate prompt
trackGateEvent('advanced-export', 'upgraded');

// Track when user dismisses the upgrade prompt
trackGateEvent('advanced-export', 'dismissed');

// TrialMoments tracks all of these automatically
// in its analytics dashboard

Done-for-You Gating UX: TrialMoments Blocked Feature Prompt

TrialMoments provides a purpose-built Blocked Feature Prompt that handles the "tease" pattern automatically. When a trial user clicks on a gated feature, one function call displays a professional upgrade modal with the feature name, value explanation, and direct upgrade link.

<!-- 1. Add the TrialMoments SDK -->
<script src="https://cdn.trialmoments.com/sdk.js"></script>
<script>
  TrialMoments.init({
    accountId: 'your-account-id',
    trialEndDate: '2026-04-15',
    upgradeUrl: 'https://yourapp.com/upgrade'
  });
</script>

<!-- 2. Trigger blocked feature prompt anywhere -->
<script>
  // When user clicks a gated feature:
  document.querySelector('#team-invite-btn')
    .addEventListener('click', function() {
      if (!canAccess('team-collaboration')) {
        TrialMoments.triggerBlockedFeature('team-collaboration');
        return;
      }
      openTeamInviteDialog();
    });

  document.querySelector('#export-csv-btn')
    .addEventListener('click', function() {
      if (!canAccess('advanced-export')) {
        TrialMoments.triggerBlockedFeature('advanced-export');
        return;
      }
      exportToCSV();
    });

  document.querySelector('#api-keys-btn')
    .addEventListener('click', function() {
      if (!canAccess('api-access')) {
        TrialMoments.triggerBlockedFeature('api-access');
        return;
      }
      showAPIKeys();
    });
</script>

Contextual messaging -- The prompt includes the feature name and a clear explanation of why it requires upgrading

Direct upgrade path -- One click takes the user to your upgrade page with the feature context preserved

Automatic analytics -- Every blocked feature interaction is tracked in the TrialMoments dashboard with conversion attribution

Dark mode and responsive -- The prompt adapts to your users' system preferences and screen size automatically

Done-for-You Feature Gating UX

TrialMoments' Blocked Feature Prompt replaces dozens of lines of custom modal code with a single function call. It handles the presentation, the styling, and the analytics so you can focus on deciding which features to gate. Ship the "tease" pattern in minutes, not days.

Frequently Asked Questions

What is feature gating in a SaaS trial?

Feature gating in a SaaS trial is the practice of restricting access to certain features during the free trial period to create upgrade motivation. Instead of giving trial users full access to everything, you strategically limit premium features so users experience the value of your core product while understanding what they'd unlock by upgrading. Effective gating increases trial-to-paid conversion by 12-22%.

Should I gate features on the client side or server side?

Use a hybrid approach. Server-side gating is essential for security -- it prevents API access to premium endpoints regardless of what happens in the browser. Client-side gating handles the user experience -- it shows contextual upgrade prompts instead of error messages. Never rely on client-side gating alone for enforcement since it can be bypassed. The ideal pattern is server-side enforcement with client-side UX.

Which features should I gate during a trial?

Gate features that demonstrate premium value but aren't required for initial activation. Use a value matrix: plot features by activation importance and perceived premium value. Features with low activation importance but high premium value are ideal gating candidates. Never gate features needed for the user to reach their "aha moment." Common examples: advanced exports, team collaboration, custom branding, and API access.

What happens to conversion if I gate too many features?

Over-gating is the most common mistake in trial design. When you gate too many features, trial users can't reach their aha moment and churn before ever seeing your product's value. Data shows that trials with more than 30% of features gated see a 40-60% drop in activation rates. The sweet spot is gating 15-25% of features -- enough to create upgrade motivation without blocking the core experience.

How does TrialMoments handle the gating UX?

TrialMoments provides a Blocked Feature Prompt that activates when a trial user clicks on a gated feature. Instead of showing a generic error or disabling the button, it displays a contextual upgrade modal explaining what the feature does and why it requires a paid plan. You trigger it with a single function call: TrialMoments.triggerBlockedFeature('feature-name'). The prompt handles dark mode, responsive design, and analytics automatically.

How do I measure if my feature gating strategy is working?

Track three key metrics: gated feature click rate (how often trial users attempt gated features), gate-to- upgrade conversion rate (percentage who upgrade after hitting a gate), and overall trial-to-paid rate segmented by gate exposure. If your gated feature click rate is below 5%, the gate isn't visible enough. If your gate-to-upgrade conversion is below 3%, the upgrade prompt isn't compelling.

Gate Smarter. Convert More.

TrialMoments provides the done-for-you gating experience with its Blocked Feature Prompt. One function call per gated feature. Dark mode, responsive, analytics included. 30KB total SDK.

Get Started with TrialMoments