Pilet API
The Manager component provides a comprehensive Pilar pilet API for managing microfrontend modules (pilets) in the Productify Framework.
Overview
Pilets are packaged microfrontend modules that can be dynamically loaded by frontend applications. The pilet API provides:
- Package Storage: Binary storage of pilet packages (typically gzipped tarballs)
- Version Management: Support for multiple versions of the same pilet
- Access Control: Project-scoped pilets with authentication
- Integrity Verification: SHA256 hash validation
- GraphQL & REST: Dual API interfaces for flexibility
Key Concepts
Project-based Storage
Pilets are stored at the project level, not the application level. This allows:
- Sharing pilets across multiple applications within a project
- Centralized version management
- Simplified deployment workflows
FrontendConfig Integration
The FrontendConfig.EnabledModules array contains pilet names that should be loaded by the frontend:
{
"module_config": {
"enabled_modules": ["@myapp/dashboard", "@myapp/analytics"],
"available_modules": [
"@myapp/dashboard@1.0.0",
"@myapp/analytics@2.1.0",
"@myapp/settings@1.0.0"
]
}
}Frontend applications read this configuration and dynamically load the specified pilets via the pilet API.
Automatic AvailableModules Updates
When you upload or update a pilet, the system automatically:
- Adds the pilet (with version) to
AvailableModulesin all frontend settings connected to the project - Updates across project, tenant, and application level settings
- Removes the pilet from both
AvailableModulesandEnabledModuleswhen deleted
This ensures all settings stay synchronized without manual updates.
REST API Endpoints
All endpoints require authentication (PAT or session-based) and are available under /pilets.
Piral Feed Service
Get enabled pilets in Piral feed format for dynamic loading:
GET /pilet-feed/:applicationIdNote: This endpoint does not require authentication - it's designed to be accessed by frontend applications through the proxy.
The endpoint automatically resolves the project ID from the application ID, so frontends don't need to know the project structure.
Response:
{
"items": [
{
"name": "@myapp/dashboard",
"version": "1.0.0",
"link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/dashboard/1.0.0",
"hash": "abc123...",
"spec": "v2"
},
{
"name": "@myapp/analytics",
"version": "2.1.0",
"link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/analytics/2.1.0",
"hash": "def456...",
"spec": "v2"
}
]
}Notes:
- Returns only pilets that are:
- Enabled in the database (enabled=true)
- Listed in the application's EnabledModules (from merged frontend config)
- No authentication required - accessible through proxy
- Compatible with Piral v2 specification
- Each item includes a relative download link (
/pilets/{project_id}/{name}/{version}) - Hash provided for integrity verification
- Frontend can use this endpoint to discover and load available pilets
- Settings are merged from project → tenant → application (application has highest priority)
Upload Package
Upload or update a pilet package:
POST /pilets/:projectId/:name/:version?description=...
Content-Type: application/gzip
[binary package content]Response:
{
"id": "uuid",
"name": "@myapp/dashboard",
"version": "1.0.0",
"size": 12345,
"hash": "abc123...",
"message": "Package uploaded successfully"
}Automatic Settings Sync:
When a pilet is uploaded or updated, the system automatically:
- Adds
{name}@{version}toAvailableModulesin all frontend settings for the project - Updates existing entries if the same module name with a different version exists
- Syncs across project-level, tenant-level, and application-level settings
- This happens in the background and doesn't block the upload
Limits:
- Maximum package size: 50MB
- Name format: Alphanumeric, dashes, underscores, dots, and scoped packages (
@scope/name)
Download Package
Download a specific pilet package version:
GET /pilets/:projectId/:name/:versionResponse: Binary package with headers:
Content-Type: Package MIME typeContent-Disposition: Suggested filenameContent-Length: Package sizeX-Pilet-Hash: SHA256 hash for verification
List Packages
List all pilets for a project:
GET /pilets/:projectId?enabled=trueResponse:
{
"packages": [
{
"id": "uuid",
"name": "@myapp/dashboard",
"version": "1.0.0",
"description": "Dashboard module",
"size": 12345,
"hash": "abc123...",
"enabled": true,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-02T00:00:00Z"
}
],
"count": 1
}Delete Package
Soft-delete a pilet package (disables it):
DELETE /pilets/:projectId/:name/:versionResponse:
{
"message": "Package disabled successfully"
}Automatic Settings Sync:
When a pilet is deleted, the system automatically:
- Removes the module from
AvailableModulesin all frontend settings - Removes the module from
EnabledModulesin all frontend settings - Applies to project-level, tenant-level, and application-level settings
- Ensures no references to the deleted pilet remain in the configuration
GraphQL API
Queries
List Pilet Packages
query ListPilets($projectId: ID!, $enabled: Boolean) {
piletPackages(projectId: $projectId, enabled: $enabled) {
packages {
id
name
version
description
size
hash
enabled
createdAt
updatedAt
}
count
}
}Get Single Pilet
query GetPilet($projectId: ID!, $name: String!, $version: String!) {
piletPackage(projectId: $projectId, name: $name, version: $version) {
id
name
version
description
size
hash
enabled
createdAt
updatedAt
}
}Mutations
Update Package Metadata
mutation UpdatePilet(
$projectId: ID!
$name: String!
$version: String!
$description: String
$enabled: Boolean
) {
updatePiletPackage(
projectId: $projectId
name: $name
version: $version
description: $description
enabled: $enabled
) {
id
name
version
description
enabled
}
}Enable Package
mutation EnablePilet($projectId: ID!, $name: String!, $version: String!) {
enablePiletPackage(projectId: $projectId, name: $name, version: $version) {
id
enabled
}
}Disable Package
mutation DisablePilet($projectId: ID!, $name: String!, $version: String!) {
disablePiletPackage(projectId: $projectId, name: $name, version: $version) {
id
enabled
}
}Frontend Integration
Using Piral Feed Service (Recommended)
The simplest way to load pilets is using the Piral-compatible feed service:
import { createInstance } from "piral";
const instance = createInstance({
requestPilets() {
// No need for projectId - just use /pilet-feed
// Proxy automatically adds applicationId
return fetch("/pilet-feed")
.then((response) => response.json())
.then((data) => data.items);
},
});The feed service automatically:
- Returns only pilets that are both enabled in database and listed in EnabledModules
- Provides download links for each pilet
- Includes hash for integrity verification
- Follows Piral v2 specification format
- Respects the same module enablement logic as the frontend config
Alternative: Manual Loading
If you need more control, you can manually load pilets:
Fetch Frontend Configuration:
javascriptconst config = await fetch(`/api/frontend-config/${applicationId}`).then( (r) => r.json() ); const enabledModules = config.module_config.enabled_modules;Download Enabled Pilets:
javascriptfor (const moduleName of enabledModules) { const [name, version] = parseModuleName(moduleName); // e.g., "@myapp/dashboard@1.0.0" const pilet = await fetch(`/pilets/${projectId}/${name}/${version}`, { headers: { Authorization: `Bearer ${token}` }, }).then((r) => r.blob()); // Load pilet into shell await loadPilet(pilet); }Register Pilets:
javascript// Using Piral or similar microfrontend framework import { createInstance } from "piral"; const instance = createInstance({ // ... configuration }); // Pilets are loaded dynamically based on EnabledModules
Version Specification
Modules in EnabledModules can specify versions:
- Latest version:
"@myapp/dashboard"(application resolves latest) - Specific version:
"@myapp/dashboard@1.0.0" - Version range:
"@myapp/dashboard@^1.0.0"(application resolves)
Security
Authentication
All pilet endpoints require authentication:
- Personal Access Tokens (PAT): Recommended for CI/CD and automation
- Session-based: For web UI interactions
Authorization
- Users must have access to the project to manage pilets
- Creator and editor tracking for audit purposes
Validation
- Size limits: 50MB maximum package size
- Name validation: Prevents injection attacks
- Hash verification: SHA256 for integrity checking
- Content-type validation: Ensures proper MIME types
Soft Deletes
Pilets are disabled rather than deleted to:
- Maintain audit trails
- Allow rollback if needed
- Preserve historical data
Development Workflow
1. Build Pilet
# Using npm/pnpm
cd pilets/my-dashboard
pnpm build
pnpm pack2. Upload to Manager
curl -X POST \
-H "Authorization: Bearer $PAT_TOKEN" \
-H "Content-Type: application/gzip" \
--data-binary @myapp-dashboard-1.0.0.tgz \
"https://manager.example.com/pilets/$PROJECT_ID/@myapp/dashboard/1.0.0?description=Dashboard+module"3. Enable in FrontendConfig
mutation {
updateFrontendSettings(
id: "SETTINGS_ID"
moduleConfig: {
EnabledModules: ["@myapp/dashboard@1.0.0", "@myapp/analytics@2.1.0"]
}
) {
moduleConfig {
EnabledModules
}
}
}4. Frontend Loads Pilet
The frontend application automatically fetches and loads pilets based on the configuration.
Database Schema
PiletPackage Entity
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
name | String | Package name (supports scoped packages) |
version | String | Semantic version |
description | String (optional) | Package description |
content | Bytes | Binary package data |
content_type | String | MIME type (default: application/gzip) |
size | Integer | Package size in bytes |
hash | String | SHA256 hash |
metadata | JSON (optional) | Additional metadata |
enabled | Boolean | Enable/disable flag |
created_at | DateTime | Creation timestamp |
updated_at | DateTime (optional) | Last update timestamp |
Relationships
- Project: Each pilet belongs to one project
- Creator: User who uploaded the pilet
- Editor: User who last modified the pilet
Examples
Fetch Pilets via Feed Service
curl "$APP_URL/pilet-feed" | jq '.items[] | {name, version, link}'Output:
{
"name": "@myapp/dashboard",
"version": "1.0.0",
"link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/dashboard/1.0.0"
}
{
"name": "@myapp/analytics",
"version": "2.1.0",
"link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/analytics/2.1.0"
}Piral Shell Integration
// app-shell.tsx
import { createInstance, Piral } from "piral";
const instance = createInstance({
requestPilets() {
// Simple! No project_id needed
return fetch("/pilet-feed")
.then((response) => response.json())
.then((data) => data.items);
},
});
export default () => <Piral instance={instance}>{/* Your app layout */}</Piral>;Complete Upload/Enable Workflow
#!/bin/bash
PROJECT_ID="550e8400-e29b-41d4-a716-446655440000"
PAT_TOKEN="your-personal-access-token"
MANAGER_URL="https://manager.example.com"
# Upload pilet
echo "Uploading pilet..."
curl -X POST \
-H "Authorization: Bearer $PAT_TOKEN" \
-H "Content-Type: application/gzip" \
--data-binary @dashboard-1.0.0.tgz \
"$MANAGER_URL/pilets/$PROJECT_ID/@myapp/dashboard/1.0.0?description=Dashboard+v1.0.0"
# Verify upload
echo "Verifying upload..."
curl -H "Authorization: Bearer $PAT_TOKEN" \
"$MANAGER_URL/pilets/$PROJECT_ID/@myapp/dashboard/1.0.0" \
-o /tmp/verify.tgz
# Compare hashes
sha256sum dashboard-1.0.0.tgz /tmp/verify.tgz
echo "Pilet uploaded and verified successfully!"List All Enabled Pilets
curl -H "Authorization: Bearer $PAT_TOKEN" \
"$MANAGER_URL/pilets/$PROJECT_ID?enabled=true" | jq '.packages[] | {name, version, size}'See Also
- Pilet Management Guide - User interface documentation
- Frontend Settings - Configure enabled modules
- Configuration Layers - Understanding inheritance
- Authentication - Access control details
- GraphQL API Reference - General API documentation