Feed service

The feed service answers one question: given this request, which pilets should load? It is the operational control plane for your micro frontend system.

Pilet catalog checkout · catalog admin beta-promo Feed Service applies rules: role · tenant · cohort Admin checkout · catalog · admin Beta user checkout · catalog · beta-promo Anonymous checkout · catalog
One catalog, one feed, three different responses — each user only ever receives (and downloads) the pilets they're allowed to see.

The protocol

The app shell makes one GET request at startup:

GET /api/v1/pilets
Authorization: Bearer <user-token>

The feed responds with a JSON array:

{
  "items": [
    {
      "name": "pilet-checkout",
      "version": "3.1.0",
      "link": "https://cdn.example.com/checkout@3.1.0/index.js",
      "spec": "v2",
      "integrity": "sha384-oqVuAfXRKap...",
      "dependencies": {},
      "config": {
        "checkoutApiUrl": "https://api.example.com/v2/checkout"
      }
    },
    {
      "name": "pilet-catalog",
      "version": "8.0.1",
      "link": "https://cdn.example.com/catalog@8.0.1/index.js",
      "spec": "v2",
      "integrity": "sha384-...",
      "dependencies": {}
    }
  ]
}

Feed item fields

FieldRequiredNotes
namenpm package name
versionsemver string
linkURL to the JS bundle
specPilet format: v0, v1, v2, or mf
integrityRecommendedSRI hash — shell verifies before evaluating
dependenciesExtra dependency URLs (usually {})
configOptionalPassed to the pilet as api.meta.config
customOptionalArbitrary metadata

Runtime configuration via config

The config field is underused. It lets you parameterise pilet behaviour from the feed — changing it without redeploying the pilet:

// In the pilet
export function setup(api: PiletApi) {
  const { checkoutApiUrl, featureFlags } = api.meta.config;

  api.registerPage('/checkout', () => (
    <CheckoutPage apiUrl={checkoutApiUrl} flags={featureFlags} />
  ));
}

Different environments, tenants, or experiment groups can receive different configs. One pilet build, multiple runtime behaviours.

Hosting options

Piral Cloud — the official, managed feed service from the Piral team, and the recommended starting point. It adds a dashboard for managing pilets, canary rollouts, instant rollback, and per-user targeting.

  • Learn about it at piral.cloud.
  • To get started, the online community edition at feed.piral.cloud is free of charge — ideal for evaluation and small projects.
  • For production, request a demo and a license via partner.piral.cloud. That gives you the ability to run the feed service on-premises for production workloads, and to get professional support backing your project.

Static JSON — The simplest possible feed: a .json file hosted on a static CDN. Useful for demos, integration tests, and simple internal tools. Zero infrastructure, no targeting, no rollback.

{
  "items": [
    {
      "name": "my-pilet",
      "version": "1.0.0",
      "link": "https://cdn.example.com/my-pilet@1.0.0/index.js",
      "spec": "v2",
      "integrity": "",
      "dependencies": {}
    }
  ]
}

Self-hosted — Implement the protocol yourself. The sample-pilet-service provides a Node.js reference implementation. See Custom feed services for a production guide.

Per-user pilet targeting

Because the feed has the user's auth token, it can return different pilet sets for different users:

app.get('/api/v1/pilets', async (req, res) => {
  const user = await verifyToken(req.headers.authorization);
  const all = await db.getActivePilets();

  const visible = all.filter(p => {
    if (p.requiredRole && !user.roles.includes(p.requiredRole)) return false;
    if (p.betaOnly && !user.isBetaTester) return false;
    if (p.tenantId && p.tenantId !== user.tenantId) return false;
    return true;
  });

  res.json({ items: toMetadata(visible) });
});

Users who shouldn't see an admin pilet never receive it — and never download its code. This is stronger than route-level guards inside a pilet.

Rollback

To roll back a misbehaving pilet:

  1. Update the feed to return the previous version (version + link fields)
  2. Users who reload get the old version
  3. The app shell does not need to change or redeploy

With Piral Cloud, this is a single click. With a self-hosted feed, it's a database update. Either way: no CI pipeline, no git revert, no on-call emergency.

Cache the feed response

Cache GET /api/v1/pilets at your CDN with a short TTL:

Cache-Control: public, max-age=30, stale-while-revalidate=60

Most requests are served from cache (fast), but the cache refreshes every 30 seconds. A rollback propagates to all users within 30–60 seconds of the database update.

Publishing a pilet

pilet publish POSTs to the feed's publish endpoint. When you have a browser available, prefer the interactive login — you don't have to handle or paste any secret:

npx pilet publish --url https://feed.example.com/api/v1/pilets --interactive

For CI/CD and other automation, use an API key instead (typically from a secret):

npx pilet publish --url https://feed.example.com/api/v1/pilets --api-key $FEED_API_KEY

The feed unpacks the .tgz, extracts the bundle, uploads it to your CDN, and records the new version. The next feed response will include it.