Frontend Integration Guide
Complete guide for integrating Productify into your frontend applications.
Table of Contents
- Overview
- Installation
- Configuration Injection
- Library Usage
- Framework Integration
- Module Loading
- Security Considerations
- Migration Guide
Overview
The Productify frontend integration consists of two main parts:
- Configuration Injection: The proxy injects configuration data into
window.__PRODUCTIFY__ - Client Library: TypeScript library (
@productifyfw/core) for type-safe access. Framework bindings (@productifyfw/vue,@productifyfw/react) are planned but do not exist — see Known Issues
Installation
Configure npm to resolve @productifyfw packages from GitHub Packages by adding to .npmrc:
@productifyfw:registry=https://npm.pkg.github.comThen install:
npm install @productifyfw/coreOr with other package managers:
yarn add @productifyfw/core
pnpm add @productifyfw/coreThere are no framework-binding packages to install: @productifyfw/vue and @productifyfw/react do not exist yet (Known Issues). Use @productifyfw/core directly.
Configuration Injection
How It Works
When your application is served through the Productify proxy, the HTML response is automatically modified to include a <script> tag that defines window.__PRODUCTIFY__:
<script id="productify-config" type="application/json">
{
"user": {
"id": "user-uuid",
"email": "user@example.com",
"name": "User Name",
"username": "username"
},
"tenant": {
"id": "tenant-uuid",
"name": "Tenant Name"
},
"available_tenants": [
{ "id": "tenant-uuid", "name": "Tenant Name" },
{ "id": "tenant-uuid-2", "name": "Other Org" }
],
"application": {
"id": "application-uuid"
},
"config": {
"enabled_modules": ["example-pilet@1.0.5"],
"default_theme": "light",
"default_locale": "en",
"key_value_pairs": [{ "key": "maxItemsPerPage", "value": "25" }]
}
}
</script>
<script>
window.__PRODUCTIFY__ = JSON.parse(
document.getElementById("productify-config").textContent
);
</script>This injection happens before your application JavaScript loads, ensuring the configuration is available immediately.
Configuration Structure
interface ProductifyFrontendConfig {
// Current authenticated user (from X-User-* headers)
user: {
id: string;
email: string;
name: string;
username: string;
picture?: string; // opt-in: X-User-Picture (forward_role)
};
// Current tenant (from X-Tenant-* headers)
tenant: {
id: string;
name: string;
};
// Tenants this user may act in for this app (id + name only).
// Render your own in-app switcher; POST /_tenant to switch.
available_tenants?: Array<{ id: string; name: string }>;
// Opt-in membership context (forward_role, default off).
// Gate on tenantPermissions (fixed vocabulary), not tenantRole (project-configurable name).
tenantRole?: string;
tenantPermissions?: string[];
// Current application (configured in the proxy)
application: {
id: string;
};
// Authentication type (from X-Auth-Type header)
authType?: string;
// Frontend configuration fetched from the Manager
// (/api/frontend-config/:application_id/:tenant_id)
config?: {
available_modules?: string[]; // format: name@version
enabled_modules?: string[]; // format: name@version
available_themes?: string[];
enabled_themes?: string[];
default_theme?: string;
available_locales?: string[];
enabled_locales?: string[];
default_locale?: string;
key_value_pairs?: Array<{ key: string; value: string }>;
[key: string]: unknown;
};
}Library Usage
Basic Setup
import { ProductifyClient } from "@productifyfw/core";
const pfy = new ProductifyClient();
// Check if configuration is available
if (!pfy.isConfigured()) {
console.warn("Productify configuration not found");
}Accessing Configuration
// Application and tenant identifiers
const appId = pfy.getApplicationId();
const tenantId = pfy.getTenantId();
const authType = pfy.getAuthType();
// User information
const user = pfy.getUser();
const userId = pfy.getUserId();
const userEmail = pfy.getUserEmail();
// Configuration values
const maxItems = pfy.getConfig<number>("maxItemsPerPage", {
defaultValue: 10,
});
const featureEnabled = pfy.isConfigEnabled("newCheckout");
// Nested values with dot notation
const mode = pfy.getNestedConfig<string>("ui.theme.mode");Themes and Locales
// Themes
const enabledThemes = pfy.getEnabledThemes();
const defaultTheme = pfy.getDefaultTheme();
// Locales
const enabledLocales = pfy.getEnabledLocales();
const defaultLocale = pfy.getDefaultLocale();
if (pfy.isLocaleEnabled("hu")) {
// Offer Hungarian in the language switcher
}Module Management
// List modules (format: name@version)
const enabled = pfy.getEnabledModules();
const available = pfy.getAvailableModules();
// Check if a module is enabled (matches the name segment exactly)
if (pfy.isModuleEnabled("checkout")) {
console.log("Checkout module enabled");
}
// Check a specific version
if (pfy.isModuleEnabled("analytics@2.0.0")) {
// Use new analytics features
}Framework Integration
Vue 3 (planned)
Not yet implemented
@productifyfw/vue does not exist — the example below will not resolve. Use @productifyfw/core directly (createI18nComposable is exported from core). See Known Issues.
The intended design: a @productifyfw/vue package providing a plugin and a useProductify() composable over the core client.
Plugin Setup
// main.ts
import { createApp } from "vue";
import { ProductifyPlugin } from "@productifyfw/vue";
import App from "./App.vue";
const app = createApp(App);
app.use(ProductifyPlugin);
app.mount("#app");Composition API Usage
<script setup lang="ts">
import { computed } from "vue";
import { useProductify } from "@productifyfw/vue";
const pfy = useProductify();
const userName = computed(() => pfy.getUserName() ?? "Guest");
const maxItems = computed(
() => pfy.getConfig<number>("maxItemsPerPage", { defaultValue: 10 }) ?? 10
);
const showBeta = computed(() => pfy.isConfigEnabled("betaFeatures"));
</script>
<template>
<div>
<h1>Welcome, {{ userName }}</h1>
<BetaPanel v-if="showBeta" />
<ItemList :max-items="maxItems" />
</div>
</template>React (planned)
Not yet implemented
@productifyfw/react does not exist — the example below will not resolve. Use @productifyfw/core directly. See Known Issues.
The intended design: a @productifyfw/react package providing a context provider and hooks over the core client.
App Setup
// App.tsx
import { ProductifyProvider } from "@productifyfw/react";
import Dashboard from "./Dashboard";
function App() {
return (
<ProductifyProvider>
<Dashboard />
</ProductifyProvider>
);
}
export default App;Component Usage
// Dashboard.tsx
import {
useProductify,
useProductifyConfig,
useUser,
} from "@productifyfw/react";
function Dashboard() {
const pfy = useProductify();
const user = useUser();
const maxItems = useProductifyConfig<number>("maxItemsPerPage", {
defaultValue: 10,
});
const showAnalytics = pfy.isModuleEnabled("analytics");
return (
<div>
<h1>Welcome, {user?.name ?? "Guest"}</h1>
{showAnalytics && <AnalyticsDashboard />}
<ItemList maxItems={maxItems ?? 10} />
</div>
);
}
export default Dashboard;Other Frameworks
@productifyfw/core is framework-agnostic: instantiate ProductifyClient (or use the getProductifyClient() singleton) in any framework's dependency-injection mechanism. Dedicated bindings currently exist for Vue 3 and React only.
Module Loading
Productify supports dynamic module (pilet) loading. Enabled modules are listed in the frontend configuration and served by the Manager's pilet feed.
Module Configuration
Modules are configured in the Manager and included in window.__PRODUCTIFY__.config:
window.__PRODUCTIFY__.config.enabled_modules = [
"navigation@1.2.3",
"checkout@2.0.1",
"analytics@0.5.0",
];Loading Pilets
import { loadPilets, type PiletRegistry } from "@productifyfw/core";
const registry: PiletRegistry = { extensions: {}, pages: {} };
await loadPilets("/pilet-feed", registry, {
onLoad: (pilet) => console.log(`Loaded ${pilet.name}@${pilet.version}`),
onError: (pilet, error) => console.error(`Failed ${pilet.name}`, error),
});See the frontend integration library guide for details on the pilet API and integrity verification.
Security Considerations
Trusted Data
The data in window.__PRODUCTIFY__ comes from the Productify proxy after authentication and authorization. This data can be trusted for:
- Displaying user information
- Controlling UI visibility based on configuration
- Configuring application behavior
Client-Side Only
Important: Never rely solely on client-side checks for security. Always validate permissions on the backend.
// [OK] Good: Hide UI elements
if (!pfy.isConfigEnabled("adminUi")) {
// Don't show admin button
}
// [NO] Bad: Only client-side protection
if (pfy.isConfigEnabled("adminUi")) {
// Make admin API call
// Backend MUST also check permissions!
}XSS Protection
The library does not execute any configuration values. All data is treated as plain data. However:
- Never use
innerHTMLwith configuration values - Sanitize any user-controlled content before rendering
- Use framework's built-in XSS protection (Vue templates, React JSX, etc.)
Migration Guide
Migrating to the @productifyfw/core Library
If you were previously accessing window.__PRODUCTIFY__ directly, migrate to the type-safe client library:
// Old
const user = window.__PRODUCTIFY__?.user;
const config = window.__PRODUCTIFY__?.config;
// New
import { getProductifyClient } from "@productifyfw/core";
const pfy = getProductifyClient();
const user = pfy.getUser();
const config = pfy.getConfigObject();Benefits of Migration
- Type Safety: Full TypeScript support with intellisense
- Null Safety: Proper handling of missing configuration
- Consistent API: Same API across different frameworks
- Future-Proof: Easy to extend with new features
Best Practices
- Initialize Early: Create ProductifyClient instance at app startup
- Use Singleton: Share one instance across your application
- Provide Defaults: Always provide sensible defaults for configuration values
- Cache Results: Cache expensive checks in component state/computed properties
- Handle Missing Config: Gracefully handle cases where config might not be available
- Don't Mutate: Never modify
window.__PRODUCTIFY__directly
Troubleshooting
Configuration Not Found
If window.__PRODUCTIFY__ is undefined:
- Verify application is accessed through Productify proxy
- Check proxy configuration and middleware
- Ensure correct domain/URL is used
- Check browser console for injection errors
Type Errors
Ensure TypeScript configuration includes:
{
"compilerOptions": {
"lib": ["ES2020", "DOM"],
"moduleResolution": "bundler"
}
}Stale Configuration
If configuration doesn't update:
// Manually refresh configuration; onChange fires when content changed
const pfy = new ProductifyClient({
onChange: (config) => console.log("Configuration updated:", config),
});
pfy.refresh();Support
For issues, questions, or contributions:
- GitHub Issues: https://github.com/ProductifyFW/fe-integrations/issues