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.
Unregistered permissions default to false.
Registering permissions
Register permissions from your extension's onServerServicesInit lifecycle hook
using user.permissions.addPermissions():
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,
});
},
},
});
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:
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
}
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 });