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 PracticeCombine 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 featuresWhich 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:
| Category | Activation Importance | Premium Value | Gate? |
|---|---|---|---|
| Core workflow features | High | Low-Medium | Never gate |
| Advanced exports | Low | High | Gate (ideal) |
| Team collaboration | Low | High | Gate (ideal) |
| API access | Low | High | Gate (ideal) |
| Custom branding | Low | Medium | Gate |
| Onboarding / setup | Critical | Low | Never gate |
| Integrations | Medium | High | Gate 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)
RecommendedShow 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 dashboardDone-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