Create PSL Feature Tiers and License Management CLI with the Dreamhouse App
Use this guide to practice:
- Structuring feature tiers using PSLs.
- Gating features in Apex, LWC, and Flows using custom permissions.
- Installing and configuring the license management CLI plugin.
- Provisioning PSL seats for development and testing.
Get Started With the Dreamhouse App
Dreamhouse is a fully formed sample app that serves as a reference implementation for building apps on the Salesforce platform. Today, we're using it as a reference for building modular, tiered-based features that you can use in your managed packages and sell on AgentExchange. For more information about the Dreamhouse App, see Quick Start: Explore the Dreamhouse Sample App.
- Find the source repository: The Go-To-Market app specific version of the Dreamhouse app with PSL support is available at https://github.com/sf-partner-solutions/dreamhouse-psl. The repository is maintained by Salesforce and contains PSL tier gating, custom permissions, and CI integration references. You can use it as a starting point for practicing and creating your own implementation.
- Review the package structure:
Property Value Namespace dreamhouse Package DreamhouseLWC (single package directory under force-app) Package Type Second-Generation Managed Package (2GP) Scratch Org Feature PartnerLicensingPlatform (enabled in config/project-scratch-def.json)
Understand Feature Tier Architecture
The Dreamhouse app implements three feature tiers, each driven by a PSL and corresponding custom permissions. Each PSL definition is stored in permissionSetLicenseDefinitions/ metadata. When you assign a PSL to a user, they're assigned a custom permission and the code checks for the custom permission to gate access to features. You no longer need a separate permission set.
| Tier | PSL Name (for CLI) | Custom Permission | Features Included |
|---|---|---|---|
| Starter | base | ViewProperties | View Properties (list, Property Explorer, Property Finder). Read-only access. |
| Premium | premium | ViewPropertyMap | Everything in the Starter tier + Property Map component. |
| Manage Properties | full | ManageProperties | View Brokers, edit Properties and Brokers, run Create Property flow, Sample Data Import. |
Gate Features in Your Code
The following patterns show you how to gate features using custom permissions in Apex, LWC, and Flow.
Pattern 1: Apex Gating
Use FeatureManagement.checkPermission() to gate server-side logic.
Keep in mind:
- Always use the fully qualified API name: namespace__PermissionName.
- Return empty, null, or throw an exception when the permission is missing.
- Apex gating enforces access at the data layer, not just from the UI.
Example from Dreamhouse (PropertyController.cls)
1public static List<Property__c> getPagedPropertyList() {
2 if (!FeatureManagement.checkPermission('dreamhouse__ViewProperties')) {
3 return null; // User doesn't have the Starter tier
4 }
5 // ... return property data
6}Pattern 2: LWC Gating
Import the custom permission and use it in a getter with lwc:if.
Keep in mind:
- Use the import syntax: @salesforce/customPermission/PermissionName.
- The import returns a boolean at runtime.
- Use lwc:if or lwc:else for conditional rendering.
- For standard user permissions, use @salesforce/userPermission.
Example from Dreamhouse (propertyMap.js)
1import ViewPropertyMap from '@salesforce/customPermission/ViewPropertyMap';
2
3export default class PropertyMap extends LightningElement {
4 get isPremium() {
5 return ViewPropertyMap;
6 }
7}Template (propertyMap.html):
1<template>
2 <template lwc:if={isPremium}>
3 <!-- Premium map component -->
4 </template>
5 <template lwc:else>
6 <p>This is a Premium feature. Please upgrade your license.</p>
7 </template>
8</template>Pattern 3: Flow Gating
Use a formula resource with $Permission and a decision element.
Key points:
- Use $Permission.namespace__PermissionName in formula resources.
- Place the permission check at the start of the flow.
- Create a clear "No Access" message for the user.
Example
1<!-- Formula resource: evaluates the custom permission -->
2<formulas>
3 <name>user_has_manage_properties</name>
4 <dataType>Boolean</dataType>
5 <expression>$Permission.dreamhouse__ManageProperties</expression>
6</formulas>
7
8<!-- Decision element: first step in the flow -->
9<decisions>
10 <name>Check_Manage_Properties</name>
11 <label>Check Manage Properties Permission</label>
12 <defaultConnector>
13 <targetReference>no_access</targetReference>
14 </defaultConnector>
15 <defaultConnectorLabel>No Access</defaultConnectorLabel>
16 <rules>
17 <name>Manage_Properties_Access_Rule</name>
18 <conditions>
19 <leftValueReference>user_has_manage_properties</leftValueReference>
20 <operator>EqualTo</operator>
21 <rightValue>
22 <booleanValue>true</booleanValue>
23 </rightValue>
24 </conditions>
25 <connector>
26 <targetReference>new_property</targetReference>
27 </connector>
28 <label>Has Access</label>
29 </rules>
30</decisions>Install the License Management CLI Plugin
Before you begin, make sure you have the Salesforce CLI installed and access to a Dev Hub org with 2GP capability in your scratch org. For more info, see Select and Enable a Dev Hub Org.
-
Install the plugin.
1sf plugins install @salesforce/plugin-license-management -
Verify installation.
1sf license provision --help
Set Up Dreamhouse for Development
-
Clone the Repository.
1git clone https://github.com/sf-partner-solutions/dreamhouse-psl 2cd dreamhouse-psl -
Authorize Your Dev Hub.
If you haven't already done so, authorize your hub org and provide it with an alias. The command uses myhuborg as the example alias.
1sf org login web -d -a myhuborg -
Create a Scratch Org.
Create a scratch org and provide it with an alias. The command below uses dreamhouse as the example alias. The scratch org definition (config/project-scratch-def.json) must have PartnerLicensingPlatform enabled for PSL support.
1sf org create scratch -d -f config/project-scratch-def.json -a dreamhouse -
Deploy the Source.
Push the app to your scratch org to deploy it.
1sf project deploy start -
Assign Permissions.
Assign the dreamhouse permission set to the default user providing full access, or use the sf license provision CLI command to provision PSL-based tiers.
Full access (no PSL, for basic development):
1sf org assign permset -n dreamhousePSL-based tiers (recommended for testing feature gating): Follow the steps in Provision PSL Seats with the sf license provision to create feature gating.
(Optional) Assign the Walkthroughs permission set to the default user: The Walkthroughs permission allows your user to use In-App Guidance Walkthroughs, providing a guided tour of the sample app. The permission set gets auto-created with In-App guidance activation.
1sf org assign permset -n Walkthroughs -
Import Sample Data.
1sf data tree import -p data/sample-data-plan.json -
Open the Org.
1sf org openActivate the Lightning Lite theme in , and open the Dreamhouse app from App Launcher.
Provision PSL Seats with the sf license provision
-
Use the sf license provision CLI command to provision PSLs within your org.
1sf license provision -o <target-org> [-n namespace] [-l license] [-q quantity] [-s start-date] [-e end-date] [-f definition-file] -
Provision Individual Tiers
-
Starter tier (view Properties only):
1sf license provision --target-org dreamhouse --namespace dreamhouse --license base --quantity 1 -
Premium tier (adds Property Map):
1sf license provision --target-org dreamhouse --namespace dreamhouse --license premium --quantity 1 -
Manage Properties tier (full access — edit, create, import):
1sf license provision --target-org dreamhouse --namespace dreamhouse --license full --quantity 1
-
Starter tier (view Properties only):
Bulk Provision Licenses with a Definition File
-
Create a JSON file (e.g., provisionPSLs.json):
1[ 2 { 3 "namespace": "dreamhouse", 4 "license": "base", 5 "quantity": 5 6 }, 7 { 8 "namespace": "dreamhouse", 9 "license": "premium", 10 "quantity": 3 11 }, 12 { 13 "namespace": "dreamhouse", 14 "license": "full", 15 "quantity": 1 16 } 17] -
Then run:
1sf license provision --target-org dreamhouse --definition-file ./provisionPSLs.json
Use the CI/CD Integration
Add PSL Provisioning to Your CI Pipeline. After you deploy your source to a scratch org in the CI, and before running tests, add the provisioning step. A base CI script is provided at scripts/ci.sh in the Dreamhouse repository.
Example
1# 1. Create scratch org
2sf org create scratch -d -f config/project-scratch-def.json -a ci-org
3
4# 2. Deploy source
5sf project deploy start -o ci-org
6
7# 3. Provision PSLs (before tests!)
8sf license provision -o ci-org -n dreamhouse -l full -q 1
9
10# 4. Assign permission to test user (if needed)
11sf org assign permset -n dreamhouse -o ci-org
12
13# 5. Run tests
14sf apex run test -o ci-org --wait 10Create Your Own Package
- Define your feature tiers and decide which features belong to each tier.
- Create custom permissions, using one permission per gated feature or tier.
- Create PSL definitions and link each PSL to the appropriate custom permissions.
- Enable PartnerLicensingAndProvisioningPlatform and add to your scratch org definition.
- Gate Apex methods using FeatureManagement.checkPermission().
- Gate LWC components and import @salesforce/customPermission/YourPermission.
- Gate Flows using $Permission.namespace__PermissionName in decisions.
- Update CI scripts, adding the sf license provision command after you deploy, but before you run tests.
- Test each tier and verify users see only their tier's features.
- Document for customers by creating clear tier descriptions and provisioning instructions.
Use the Recommended Best Practices
- Defense in depth: Gate at both UI (LWC/Flow) and server (Apex) layers.
- Graceful degradation: Show informative "upgrade" messages, not errors.
- Use a single package: You can manage multiple tiers within one 2GP package.
- Namespace consistency: Always prefix custom permissions with your namespace in code references.
Troubleshoot Your Implementation
If you encounter issues with PSL provisioning or feature gating, check:
- That PartnerLicensingAndProvisioningPlatform is enabled in your scratch org definition.
- That the --license value matches the definition file name, not the label.
- That you're using the correct namespace prefix in permission checks.
- That the user has been assigned the provisioned PSL in Setup > Users > Permission Set License Assignments.
For CLI plugin issues, file a GitHub issue at: https://github.com/salesforcecli/plugin-license-management/issues