Skip to content
Back to Blog
Managed Packages

How to Enforce Licensing for Lightning Web Components in a Managed Package

Jun 17, 2026
SCSunny Chauhan
How to Enforce Licensing for Lightning Web Components in a Managed Package

Lightning Web Components in a managed package don't check your package license on their own. Salesforce's LWC guide says users "can see and use Lightning web components in a managed package even if they don't have active licenses," and they keep that access "after a trial of that managed package expires" (Salesforce LWC guide). The fix Salesforce recommends is an Apex controller that calls UserInfo.isCurrentUserLicensed(namespace) or UserInfo.isCurrentUserLicensedForPackage(packageID), and a component that renders nothing but a short message when the answer is false.

For a SaaS company selling a paid app per seat, this decides whether the seat count in the contract matches who actually uses the app in the customer's org. It is cheap to build before the first paid install and awkward to add after customers are used to open access. Appnigma's marketplace apps work covers this kind of packaging decision, with "a Salesforce managed package or HubSpot app built around your product and customer workflows."

Updated October 2026. Checked against the Lightning Web Components Developer Guide, the Apex Reference for UserInfo and Packaging, and the second-generation packaging guide. The earlier version shipped a @wire example without cacheable=true, which the wire service requires, called adding the check a patch update, and left out the trial expiry behavior. This update adds the add-on trap with shared namespaces, server-side gating, and how to roll the check out to customers who already installed.

What Salesforce leaves to your component code

Salesforce puts license enforcement for LWCs on the publisher. The guide says "AppExchange partners are responsible for enforcing package licensing in their components" (Add Components to Managed Packages). The second-generation packaging guide repeats the same consideration for the LightningComponentBundle metadata type (Components Available in 2GP).

Two consequences matter commercially. A customer admin can place your exposed components on Lightning pages that every user of the org opens, licensed or not. And when a trial ends without a purchase, the components keep working unless your code checks. The License Management App (LMA) records licenses and seat counts in your partner org, and Salesforce's LMA guidance points you to the API for the check itself, "the isCurrentUserLicensed method determines if a user has a license to a managed package" (LMA best practices).

When to use isCurrentUserLicensed vs isCurrentUserLicensedForPackage

The two methods differ in scope, and the difference only shows once you ship more than one package. A namespace is the prefix your package's components carry, and in second-generation packaging several packages can share one namespace.

isCurrentUserLicensed(namespace)isCurrentUserLicensedForPackage(packageID)
InputNamespace prefix as a StringPackage ID, read at runtime with Packaging.getCurrentPackageId()
Returns true whenThe user has a license to "any managed package denoted by the namespace"The user has a license to "the managed package denoted by the package ID"
Also countsNot statedA namespace permission set license for the package namespace
Fails withTypeException for an invalid namespace typeTypeException for an unlocked or unmanaged package ID
FitsOne paid package per namespaceBase product plus separately sold add-ons in one namespace

A third method, UserInfo.hasPackageLicense(packageID), returns true only "via a package license only," so it ignores namespace permission set licenses. Quoted wording is from the Apex UserInfo class reference.

The trap is the "any managed package" part. If your base app and a paid add-on share a namespace and the add-on's components check isCurrentUserLicensed('acme'), a user with only a base seat passes the add-on check. Gate add-ons with isCurrentUserLicensedForPackage and get the ID at runtime with Packaging.getCurrentPackageId(), which "returns the context packageID in managed and unlocked packages" (Packaging class). Reading the ID at runtime also keeps a hardcoded ID from your packaging org out of the code.

A license check that works with @wire

The license check Salesforce recommends is one Apex method and a component that renders on its result. The version below is written so that the wire service accepts it. Salesforce says "to use @wire to call an Apex method, annotate the Apex method with @AuraEnabled(cacheable=true)" (Wire Apex Methods). A non-cacheable method has to be called imperatively instead.

public with sharing class LicenseGate {
  @AuraEnabled(cacheable=true)
  public static Boolean isLicensed() {
    return UserInfo.isCurrentUserLicensedForPackage(Packaging.getCurrentPackageId());
  }
}
import {
  LightningElement, wire
}
from 'lwc';
import isLicensed from '@salesforce/apex/LicenseGate.isLicensed';
export default class AcmeDashboard extends LightningElement {
  licensed = false;
  checked = false;
  @wire(isLicensed)
  wiredLicense({
    data, error
  }) {
    if (data !== undefined || error) {
      this.licensed = data === true;
      this.checked = true;
    }
  }
}
<template>
<template lwc:if={
  licensed
}
>
<!-- licensed content -->
</template>
<template lwc:elseif={
  checked
}
>
<p>You don't have a license for this app. Ask your Salesforce admin to assign one.</p>
</template>
</template>

The component treats anything other than a plain true as unlicensed, so a failed Apex call shows the message rather than the content. Leave the namespace out of the import path because the class sits in the same namespace as the component, which is what the Import Apex Methods page tells you to do.

Caching has one side effect worth planning for. A cacheable result is served from the client-side cache before the server is called, and Salesforce says to "call refreshApex()" for stale data (Wire Apex Methods). Salesforce also says "don't assume a cache duration" (Client-Side Caching). A user whose seat is removed can keep seeing content until the cache refreshes, so a server-side check in each Apex method should be the one that holds the line.

Gate the Apex methods, not just the markup

Hiding the template stops casual use. It doesn't stop a user with access to your Apex class from calling the @AuraEnabled methods that feed the component, because the LWC guide ties method access to "the user's profile or an assigned permission set" (Secure Apex Classes), not to the package license. Our recommendation is to run the same license check at the top of every method that returns licensed data or does licensed work, and throw an AuraHandledException with the same message the template shows.

That gives you one helper, called from the gate method and from each data method, so the rule lives in one place. It also covers a customer's own LWC that embeds yours, because the check runs as the current user wherever the call comes from. Embedding a packaged LWC from another namespace also needs Lightning Web Security turned on in the customer's org (Add Components to Managed Packages).

Adding the check after customers have installed

A license check added to a package customers already run ships as a minor version, not a patch. Second-generation patch versions can't "add package components" (2GP patch versions), and a new Apex class like LicenseGate is a new component. Patch versioning also has to be switched on by Partner Support and "is available only to packages that have passed AgentExchange security review." First-generation patch orgs carry the same "can't add package components" rule (1GP patches).

The commercial step matters as much as the code. The day the version lands, every user without a seat sees the message instead of the app. Tell each customer's admin before the upgrade, and give them time to assign seats before it reaches their org.

If you push the upgrade, the new class needs one extra step. Salesforce says "by default, any new components included in the push upgrade package version are assigned only to admins" (Assign Access to New and Changed Features). Add LicenseGate to the permission sets your users already hold, or grant it in a post-install script. Otherwise licensed users without class access hit an error, and the component shows them the "no license" message. Follow the push upgrade best practices for the rest of the rollout.

Mistakes that let unlicensed users through

  • Checking the namespace for an add-on. With a shared namespace, any base seat passes isCurrentUserLicensed, so add-on components need the package ID check.
  • Using `@wire` on a method without `cacheable=true`. The wire never provisions data, the flag stays false, and licensed users see the "no license" message instead.
  • Gating only the outer component. Child components that a customer can place on their own pages need their own check if they are exposed with isExposed set to true.
  • Hiding the UI but leaving the Apex open. Data methods without the check return licensed data to anyone with class access.
  • Trusting permission sets as a license signal. A Salesforce Stack Exchange answer notes that when the license is revoked, "the permission set remains assigned to the user" (SFSE 11819), so check the license, not the permission set.
  • Forgetting trial expiry. Users keep using LWCs after a trial ends, per the LWC guide, unless the check is there.

How to test licensed and unlicensed users before release

Testing license enforcement needs a real per-seat license, and a sandbox can't give you one. Salesforce's LMA guide says "when a package is installed in a sandbox org, Site License is the default" (License Object Fields), and a site license makes the package "available to all users in the customer's org" (Modify a License Record). Beta versions don't help either, because "beta package versions don't display in the LMA" (Associate a Package with the LMA).

The test that works is a managed-released version installed in a production-type test org, such as a trial or a partner test org, with its license record set to a small seat count in the LMA. Assign a seat to one user, leave a second user without one, and run both through every component and every Apex method. Then set the license to expired or suspended in the LMA and run the same pass again. Salesforce describes the Suspended status as one that "prohibits the customer from accessing your offering," and only your check makes that true for LWCs.

Test what an unlicensed user sees when the Apex call fails outright, not only when it returns false. Older Salesforce Stack Exchange threads report unlicensed users getting a "requires a license to use" error from a packaged controller rather than a false result (SFSE 186019). These are community reports without accepted answers, so treat an error the same way as a false result, as the sample component does.

Frequently Asked Questions

Do Lightning Web Components in a managed package enforce licensing by default?

No. Salesforce says LWC "doesn't automatically enforce managed package licensing," including after a trial expires, and puts enforcement on the partner (LWC guide).

Does the security review require license enforcement in LWCs?

We found no official Salesforce statement that the AppExchange security review checks license enforcement. Treat it as a commercial control that you own, separate from the CRUD, FLS and sharing checks the review covers.

Do Aura components need the same check?

Salesforce documents the licensing consideration for Lightning Web Components only, and the Aura Components Developer Guide doesn't address it. If your package still ships Aura components, test them with an unlicensed user and put the same Apex check behind them rather than assume either behavior.

Can Appnigma build license enforcement into our managed package?

Yes. Appnigma builds native Salesforce apps as managed packages, including the packaging work and Salesforce security review preparation, and can agree on upgrades and ongoing support after launch.

About the author

Sunny Chauhan is the co-founder and CEO of Appnigma AI, which builds and maintains customer integrations and native Salesforce and HubSpot apps for B2B SaaS teams. Before Appnigma, he was on Salesforce's AppExchange team, where he worked with more than 300 SaaS partners. He has spent close to a decade on the Salesforce platform, including work at Zennify, and holds Salesforce certifications including Platform Developer II, Platform App Builder, Administrator, Data Cloud Consultant, and AI Associate.

Sources

  1. Salesforce Developers, LWC Developer Guide, Add Components to Managed Packages
  2. Salesforce Developers, Apex Reference, UserInfo Class
  3. Salesforce Developers, Apex Reference, Packaging Class
  4. Salesforce Developers, Second-Generation Managed Packaging, Components Available in Managed Packages
  5. Salesforce Developers, Second-Generation Managed Packaging, Best Practices for the License Management App
  6. Salesforce Developers, LWC Developer Guide, Wire Apex Methods to Lightning Web Components
  7. Salesforce Developers, LWC Developer Guide, Import Apex Methods
  8. Salesforce Developers, LWC Developer Guide, Secure Apex Classes
  9. Salesforce Developers, Second-Generation Managed Packaging, Patch Versions
  10. Salesforce Developers, First-Generation Managed Packaging, Create and Upload Patches
  11. Salesforce Developers, Second-Generation Managed Packaging, Push Upgrade Best Practices
  12. Salesforce Developers, Second-Generation Managed Packaging, Assign Access to New and Changed Features
  13. Salesforce Developers, LWC Developer Guide, Client-Side Caching of Apex Method Results
  14. Salesforce Developers, Second-Generation Managed Packaging, License Object Fields
  15. Salesforce Developers, Second-Generation Managed Packaging, Modify a License Record
  16. Salesforce Developers, Second-Generation Managed Packaging, Associate a Package with the LMA

All Blogs

Bring the customer requirements and your current setup. Get a clear scope and delivery timeline to take back to them.