Products, Sub-Products, Feature Flags & Presets
A complete guide to structuring your products, gating features with flags, building tiered sub-products, and speeding up issuance with presets.
This page explains how to model your software on the platform. You will learn how products are identified, how to create tiered sub-products, how feature flags control customer-facing capabilities, and how presets save time when issuing licenses.
Product Identity
Every product has three identifiers. Treat the slug as the public API name and the UID as an internal support handle.
| Identifier | Rules | Example |
|---|---|---|
| Name | Globally unique. No spaces. Letters, digits, dashes, and underscores only. | OpenShield-XDP |
| Slug | Globally unique. Lowercase letters, digits, and dashes only. Used in API calls. | openshield-xdp |
| UID | System-generated 16-character hex string. Shown after creation. | a1b2c3d4e5f67890 |
Use the slug as product_slug in POST /api/v1/license/check. The UID is handy for support and for users searching when they request a license.
Product Configuration
Each product has the following configurable properties:
- Name & Description — shown to customers on their dashboard.
- Product type —
native(HWID binding) orweb(hostname binding). This drives the identity type the check endpoint expects. - Default Duration — how long a license lasts (in days), when you set one.
- Max Uses — the maximum number of times a license can be activated or used.
- Protection Profile — controls whether licenses are bound to a device, domain, or left unbound.
- Feature Flags — named boolean/string/number flags that control product capabilities per license.
- Presets — reusable license templates for common configurations.
- Offline Grace — how many days a client can operate without contacting the server.
- Block all until whitelisted — default-deny access rule behavior.
- Let user re-generate their license key — lets the license owner regenerate their own key from the dashboard.
Feature Flags
Feature flags are the simplest way to turn product capabilities on or off per license. When the license-check endpoint returns a signed payload, it includes a features object. Your application reads that object and decides what to expose.
Because the response is Ed25519-signed, the client can trust the flag values without calling home again (until the offline grace period expires).
Common Use Cases
| Flag | Type | What it gates |
|---|---|---|
tier | string | Product tier name: "free", "pro", "studio", "enterprise". |
max_projects | number | How many projects the customer can create inside the app. |
max_seats | number | How many team members can be invited. |
api_access | boolean | Whether programmatic API access is allowed. |
advanced_reporting | boolean | Enables analytics exports and custom dashboards. |
white_label | boolean | Removes platform branding from the customer's instance. |
priority_support | boolean | Routes the customer to a priority support queue. |
offline_mode | boolean | Allows extended offline operation beyond the product default. |
beta_features | boolean | Grants early access to experimental functionality. |
Setting Flags on a Product
When you create or edit a product, set the default feature flags as a single-level JSON object of primitives:
{
"tier": "starter",
"api_access": false,
"max_projects": 3,
"white_label": false
}
These defaults apply to every license unless you override them at license creation time.
Overriding Flags per License
When you issue a license, you can override the product defaults. This is how you upgrade a single customer without changing the product defaults:
{
"tier": "enterprise",
"api_access": true,
"max_projects": 100,
"white_label": true,
"priority_support": true
}
Reading Flags in Your Application
The license-check response includes the resolved features object:
{
"valid": true,
"expires_at": 1750000000,
"product_slug": "openshield-xdp",
"sub_product_slug": "enterprise-tier",
"features": {
"tier": "enterprise",
"api_access": true,
"max_projects": 100,
"white_label": true
},
"signed_at": 1700000000,
"signature": "base64-encoded-ed25519-signature"
}
A typical client flow:
- Call
POST /api/v1/license/checkon app startup. - Verify the Ed25519 signature with the product's public key.
- Cache the
featuresobject in memory for the session. - Gate UI sections, API routes, or export buttons based on
features.tier,features.api_access, etc.
Example in JavaScript:
const canExport = license.features?.advanced_reporting === true;
const maxProjects = license.features?.max_projects ?? 1;
Feature Flag Best Practices
- Keep keys stable. Renaming a flag breaks every customer's cached license until their next check.
- Use booleans for on/off gates and numbers for limits. Avoid nested objects; the platform expects a flat object.
- Default everything to the most restrictive value at the product level, then unlock features per license.
- Document your flags internally. A year from now you will not remember what
feature_xmeant.
Sub-Products
Sub-products let you create tiered or segmented offerings under a single parent product. They are useful when you have one codebase but multiple editions, add-ons, or customer segments.
When to Use Sub-Products
Use sub-products when you need:
- Tiered pricing — Free, Pro, Studio, Enterprise editions of the same app.
- Platform modules — A core product plus optional modules like "Reporting", "SSO", or "Compliance".
- Customer segments — Indie, Team, and Enterprise versions with different support SLAs.
- Whitelabel tiers — A base product plus a whitelabeled reseller version.
Sub-Product Inheritance
A sub-product inherits most settings from its parent product but can override:
- Max Uses
- Default duration
- Protection profile
- Feature flags
- Offline grace
- Access-rule defaults
Think of the parent product as the "template" and the sub-product as the "flavor". If you do not override a setting, the parent value applies.
Example: Video Editor Tiers
| Sub-product | Inherits From Parent | Overrides |
|---|---|---|
video-editor-free | video-editor | max_projects: 1, tier: "free", watermark: true |
video-editor-pro | video-editor | max_projects: 10, tier: "pro", 4k_export: true |
video-editor-studio | video-editor | max_projects: 999, tier: "studio", team_collab: true |
When a customer checks a license, the response includes both product_slug and sub_product_slug, so your app can adapt its behavior:
if (license.sub_product_slug === "video-editor-studio") {
enableTeamCollaboration();
}
Creating a Sub-Product
- Go to Products and open the parent product.
- Click New Sub-Product.
- Enter a name and slug. The slug must be unique within the parent product.
- Choose which parent settings to inherit and which to override.
- Save. The sub-product is now selectable whenever you issue a license.
Issuing a License for a Sub-Product
When you create a license, pick the parent product first, then choose the sub-product from the dropdown. The license key is tied to that sub-product, and the check response will include its resolved settings.
API Behavior
You do not send the sub-product slug in the check request. The platform resolves it from the license key. The response will contain:
{
"valid": true,
"product_slug": "video-editor",
"sub_product_slug": "video-editor-pro",
"features": {
"tier": "pro",
"max_projects": 10,
"4k_export": true
}
}
Presets
Presets are saved license configurations that speed up issuing common license types. Instead of setting duration, max uses, feature flags, and notes every time, apply a preset with one click.
When to Use Presets
- Standard SKUs — "Pro 1-Year", "Enterprise Perpetual", "Trial 14-Day".
- Sales workflows — Your support team can issue consistent licenses without re-entering details.
- Giveaways / events — A preset for "Conference Attendee — 30 Days".
Creating a Preset
- Go to a product and open the Presets tab.
- Click New Preset.
- Name it (e.g., "Pro Annual").
- Set:
- Duration (days) or never expires
- Max uses
- Feature flag overrides
- Notes (optional)
- Save. The preset is scoped to this product.
Applying a Preset
When you create a license:
- Select the product and optional sub-product.
- Choose a preset from the Preset dropdown.
- The form pre-fills duration, max uses, and feature flags.
- Adjust anything for this specific customer, then create the license.
Preset Example: SaaS Tiers
| Preset | Duration | Max Uses | Feature Flags |
|---|---|---|---|
| Trial | 14 days | 1 | {"tier":"trial","api_access":false} |
| Starter Annual | 365 days | 1 | {"tier":"starter","max_projects":3} |
| Pro Annual | 365 days | 5 | {"tier":"pro","max_projects":25,"api_access":true} |
| Enterprise | never | unlimited | {"tier":"enterprise","api_access":true,"white_label":true} |
Putting It All Together
Here is a complete example of how products, sub-products, feature flags, and presets work together.
Scenario
You sell pixel-forge, a design tool. You have three tiers and a trial.
- Parent product:
pixel-forge(typenative, profilehardware). - Sub-products:
pixel-forge-trial— overrides duration to 14 days and flags to{"tier":"trial","export":false}.pixel-forge-pro— overrides flags to{"tier":"pro","export":true,"max_projects":10}.pixel-forge-studio— overrides flags to{"tier":"studio","export":true,"max_projects":999,"team":true}.
- Presets:
- "Trial 14D" → maps to
pixel-forge-trial. - "Pro Annual" → maps to
pixel-forge-pro, 365 days. - "Studio Annual" → maps to
pixel-forge-studio, 365 days.
- "Trial 14D" → maps to
Issuing a License
You select the Pro Annual preset for a customer. The platform creates a license tied to pixel-forge-pro with a 365-day expiry.
Checking the License
curl -X POST https://pingless-license-system.vercel.app/api/v1/license/check \
-H "Content-Type: application/json" \
-d '{
"license_key": "PL-ABCD-EFGH-IJKL-MNOP",
"product_slug": "pixel-forge",
"identity_value": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
}'
Response:
{
"valid": true,
"expires_at": 1750000000,
"product_slug": "pixel-forge",
"sub_product_slug": "pixel-forge-pro",
"features": {
"tier": "pro",
"export": true,
"max_projects": 10
},
"signed_at": 1700000000,
"signature": "..."
}
Enforcing Features Client-Side
const license = await checkLicense({ ... });
if (license.features?.tier === "studio") {
enableTeamWorkspaces();
}
if (license.features?.export === true) {
showExportButton();
} else {
showUpgradePrompt("Export requires Pro or Studio.");
}
Assigning and Revoking Licenses
From the Licenses table you can:
- Assign a license to a user by username or email. The license then appears on their My Licenses page.
- Revoke an active license. Revocation is permanent and stops the license from validating immediately.
- Delete a license. Deletion removes the license row entirely and is useful when you need to remove a product that still has revoked licenses attached.
License owners can also revoke their own licenses from the license detail page.
Next Steps
- Protection Profiles — choose the right binding mode for your product.
- License API — integrate the check endpoint with code examples in your language.
- Access Rules — restrict licenses by IP, CIDR, or FQDN.
