Skip to main content
Version: next

Permissions

Learn how to use the Front-Commerce permission system to control access to features from your extensions, both server-side and client-side.

Overview

Front-Commerce provides a permission system that lets extensions control access to features. Permissions are boolean flags identified by a name (e.g. "negotiableQuote", "negotiableQuote.public").

Extensions register permissions during application startup, and you can then check them anywhere: in Remix loaders, GraphQL resolvers, and React components.

info

Unregistered permissions default to false.

Registering permissions

Register permissions from your extension's onServerServicesInit lifecycle hook using user.permissions.addPermissions():

src/index.ts
import { defineRemixExtension } from "@front-commerce/remix";

export default defineRemixExtension({
name: "acme-extension",
// ...
unstable_lifecycleHooks: {
onServerServicesInit: async (services, _request, config, user) => {
user.permissions.addPermissions({
"acme.feature": config.acme?.featureEnabled ?? false,
"acme.feature.admin": config.acme?.adminEnabled ?? false,
});
},
},
});
caution

The onServerServicesInit hook is called on every request. To prevent performance issues, it should only perform data mapping from already-available sources (configuration, user session). Avoid heavy computation or additional API calls here.

Checking permissions server-side

Use user.permissions.isAllowedTo() to guard access in loaders, contextEnhancers, or resolvers:

routes/_main.user.acme-feature._index.tsx
import { FrontCommerceApp } from "@front-commerce/remix";

export async function loader({ context }) {
const app = new FrontCommerceApp(context.frontCommerce);

if (!app.user.permissions.isAllowedTo("acme.feature")) {
throw new Response("", { status: 404 });
}

// ... load data
}
modules/acme/runtime.ts
contextEnhancer: ({ loaders, user }) => {
if (!user.permissions.isAllowedTo("acme.feature")) {
// Return a loader that throws for each method so resolvers
// don't break when the feature is disabled.
return {
Acme: {
loadItems: () => {
throw new Error("acme.feature is not enabled");
},
},
};
}
return { Acme: new AcmeLoader(loaders) };
};

Checking permissions client-side

Permissions are automatically available in React components through the usePermissions hook and the <Restricted> component.

usePermissions hook

import { usePermissions } from "@front-commerce/core/react";

function MyComponent() {
const { isAllowedTo } = usePermissions();

return (
<nav>
{isAllowedTo("acme.feature") && <Link to="/acme">Acme Feature</Link>}
</nav>
);
}

<Restricted> component

import { Restricted } from "@front-commerce/core/react";

function MyNavigation() {
return (
<nav>
<Restricted to="acme.feature">
<Link to="/acme">Acme Feature</Link>
</Restricted>
<Restricted to="acme.admin" fallback={<p>Access denied</p>}>
<Link to="/acme/admin">Admin</Link>
</Restricted>
</nav>
);
}

See the PermissionsProvider, usePermissions, and Restricted API references for details.

Server-only permissions

Some sensitive permissions should never be sent to the client (e.g. admin-level checks). Mark them with serverOnly: true:

user.permissions.addPermission("admin.users.manage", isAdmin, {
serverOnly: true,
});

These permissions can only be checked server-side. They return false in React.

Priority

When multiple extensions set the same permission, the one with the higher priority wins (default is 0). Equal priority follows a last-writer-wins strategy.

// Extension A (default priority: 0)
user.permissions.addPermission("feature.enabled", false);

// Extension B (priority: 10) — this value wins
user.permissions.addPermission("feature.enabled", true, { priority: 10 });