All guides

Open Core and Fair Source: How to Gate Paid Features in Self-Hosted Software with License Keys

Ship a free community edition and a paid one from the same public repo. How license keys gate paid features in open core and Fair Source software.

Self-hosted licensing illustration for Open Core and Fair Source: How to Gate Paid Features in Self-Hosted Software with License Keys

An open core license key lets one self-hosted application enable paid features for the installations entitled to use them. Keep the Community edition usable on its own and check entitlements where the paid operation runs. An operator should be able to see why a feature is unavailable and how to restore access.

Open core vs Fair Source: what changes when your paid code is public

In a common open core layout, the core uses an open source license while a separate commercial directory carries its own terms. A Fair Source model can make commercial code readable under terms that limit some uses of current versions. Keygen announced its move to FCL. Directus announced an FCL-derived license for v12 and introduced software registration keys for paid and grant users, with its Core tier available without registration.

Why a license key is the line, not code secrecy (and why “someone can patch it out” is acceptable)

A self-hosted operator controls the executable and can modify it. Treat a key as the entitlement for a supported distribution, with terms that explain the permitted uses. Sign proof so a modified file cannot grant access in the unmodified product. Keep checks in the server-side paid operations as well as the UI. Avoid mandatory telemetry and remote kill switches. Give operators clear expiry messages, an offline renewal path and continued access to their data.

This is not legal advice. Read the exact terms shipped with the version you use and seek advice for your distribution model.

ModelHigh-level distinction
Separate ee/ termsThe core and commercial modules carry different licenses. The repository should make the boundary clear.
FSLLimits competing use and grants the stated future Apache 2.0 or MIT license after two years.
FCLLimits competing use, protects license-key functionality and grants a future license under the selected variant.
ELv2Restricts offering the software as a managed service, bypassing key functionality and removing notices.

Designing feature flags as entitlements: Community, Pro and Enterprise

Use stable entitlement identifiers. For example, Community can read and edit data, Pro adds exports, and Enterprise adds sso. Check each feature independently and deny paid operations if the proof is invalid or scoped to another product. PermitCore issues and validates keys and entitlements for the licensing model you choose. See the feature-based licensing guide.

Offline-first checks for self-hosted installs: signed license files and grace periods

For an air-gapped installation, supply a signed token file and configure trusted public keys through the application's trusted release channel. The SDK's ActivateOffline validates the token's signature, signed deadline, device and expected product. A pc_offline_v1 token does not include feature flags. Use it for the scoped edition it was issued for or verify separate signed feature grants. The C# offline guide and signing-key rotation guide cover how to provision and rotate trust.

For a normally connected deployment, configure an offline grace period and trusted keys so the SDK can validate signed cached proof during a network outage. OfflineGraceDays describes policy. The signed proof's ExpiresAt controls how long it can be used. IsInGracePeriod describes post-expiry grace on a license result. It does not extend the signed proof locally.

static bool AcceptOffline(PermitCoreClient client, string deviceId, Guid productId)
{
    var path = Environment.GetEnvironmentVariable("PERMITCORE_OFFLINE_FILE");
    if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) return false;
    return client.ActivateOffline(File.ReadAllText(path).Trim(), deviceId, productId).IsValid;
}

Expiry and shutdown: perpetual fallback so customers keep what they paid for

For a perpetual fallback, issue the customer the entitlement and installer access your terms promise. Compare the chosen build's release date with a trusted MaintenanceExpiresAt when deciding whether that build is eligible. Keep this separate from validating the license itself. A subscription does not automatically convert into a perpetual entitlement, and expired proof cannot be treated as permanent. See perpetual fallback and what happens if a license server goes down.

static bool CanUseRelease(LicenseResult entitlement, DateTime releasedAtUtc)
    => entitlement.IsValid
       && (entitlement.MaintenanceExpiresAt is not DateTime end || releasedAtUtc <= end);

Docker, Kubernetes and CI: passing the license without baking it into images

Inject PERMITCORE_LICENSE_KEY from the runtime secret store. Docker can inherit the exported environment variable with --env PERMITCORE_LICENSE_KEY. In Kubernetes, use env.valueFrom.secretKeyRef on the workload. A CI job can map its protected secret to the same environment variable for an integration test. For offline installations, mount the token file read-only and pass its path with PERMITCORE_OFFLINE_FILE. Keep keys and token files out of image layers, build logs and public CI output. See the CLI licensing guide for deployment patterns.

Example: gating two features with PermitCore entitlements

This example uses the current .NET SDK. Supply the vendor's trusted API URL and the expected product ID through your application configuration. Apply the returned checks at the export and SSO operations. A missing or invalid license leaves the Community features available. Compare other implementation approaches in the licensing platform comparison.

using PermitCore;

static async Task<(bool Exports, bool Sso)> CheckFeatures(
    PermitCoreClient client, Guid productId)
{
    var key = Environment.GetEnvironmentVariable("PERMITCORE_LICENSE_KEY");
    if (string.IsNullOrWhiteSpace(key)) return (false, false);
    var result = await client.ValidateAsync(key, expectedProductId: productId);
    return (result.IsValid && result.HasFeature("exports"),
            result.IsValid && result.HasFeature("sso"));
}
Put the guide into practice

Build your first licensed product without building a license server.

Create a free PermitCore account, issue a test license, and validate it from your application.