create-fedi-app
APIs

window.fediInternal

Fedi-specific browser API for mini app discovery, installation, and version detection — not available in standard browsers.

window.fediInternal is a Fedi-only API absent from standard browsers and other Lightning wallets. Unlike WebLN and Nostr, it is optional — older Fedi builds may not inject it, and your app must degrade gracefully.

Version detection

The API is versioned. Check fediInternal.version before calling methods:

type FediInternal =
  | { version: 0 }
  | { version: 1 }
  | { version: 2; getInstalledMiniApps(): Promise<Array<{ url: string }>>; installMiniApp(miniApp: TMiniAppInstall): Promise<void> };

interface TMiniAppInstall {
  id: string;
  title: string;
  url: string;
  imageUrl?: string | null;
  description?: string;
}
VersionMethodsNotes
0NoneAPI present but no callable methods
1NonePlaceholder for future expansion
2getInstalledMiniApps, installMiniAppCurrent full API

getInstalledMiniApps()

Lists mini apps installed in the user's Fedi wallet.

// fediInternal.version === 2 only
const apps = await window.fediInternal!.getInstalledMiniApps();
// [{ url: "https://example.com/my-app" }, ...]

| Returns | Promise<Array<{ url: string }>> | | Errors | Network failure, permission denied (manageInstalledMiniApps) |

Use this to deep-link between mini apps or show a "related apps" panel. Call only on user gesture — do not invoke on page load.

Permissions

Both v2 methods require Fedi's manageInstalledMiniApps permission. On first call, Fedi shows an Allow/Deny dialog. If the user denies (especially with "Remember my choice"), subsequent calls reject immediately.

Use isFediPermissionError() from lib/fedi.ts to detect denials:

import { isFediPermissionError } from '../lib/fedi';

try {
  await window.fediInternal!.getInstalledMiniApps();
} catch (err) {
  if (isFediPermissionError(err)) {
    // Show permission guidance + manual retry button
  }
}

See Fedi permission docs.

installMiniApp(miniApp)

Prompts the user to install a mini app in their Fedi catalog.

ParameterTypeRequiredDescription
idstringyesUnique app identifier
titlestringyesDisplay name
urlstringyesHTTPS URL of the mini app
imageUrlstring | nullnoIcon URL
descriptionstringnoShort description

| Returns | Promise<void> | | Errors | User cancellation, invalid URL, permission denied (manageInstalledMiniApps) |

await window.fediInternal!.installMiniApp({
  id: 'my-game',
  title: 'Sat Stack',
  url: 'https://satstack.example.com',
  imageUrl: 'https://satstack.example.com/icon.png',
  description: 'Stack sats in a tower',
});

useFediInternal hook

Base template hook at hooks/useFediInternal.ts:

function useFediInternal(): {
  isAvailable: boolean;
  version: 0 | 1 | 2 | null;
  getInstalledMiniApps: (() => Promise<Array<{ url: string }>>) | null;
  installMiniApp: ((miniApp: TMiniAppInstall) => Promise<void>) | null;
}

Methods are null when unavailable or version < 2.

const { isAvailable, version, getInstalledMiniApps } = useFediInternal();

if (!isAvailable) return <p>Open in Fedi to see installed apps</p>;
if (version! < 2) return <p>Update Fedi for mini app discovery</p>;

useFediBalance hook

Ecash module hook at hooks/useFediBalance.ts:

type TMiniAppsLoadState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'loaded'; miniApps: Array<{ url: string }> }
  | { status: 'permissionDenied' }
  | { status: 'error' };

type TFediBalanceState =
  | { status: 'loading' }
  | { status: 'unavailable' }
  | {
      status: 'ready';
      version: 0 | 1 | 2;
      miniAppsLoad: TMiniAppsLoadState;
    };

function useFediBalance(): {
  state: TFediBalanceState;
  loadMiniApps: () => Promise<void>;
};

Detects fediInternal version on mount. On v2, call loadMiniApps() from a button click so Fedi can prompt for manageInstalledMiniApps. Surfaces permissionDenied when the user rejects the permission.

Degradation pattern

function MiniAppList() {
  const { state, loadMiniApps } = useFediBalance();

  if (state.status !== 'ready' || state.version < 2) {
    return null;
  }

  if (state.miniAppsLoad.status === 'idle') {
    return <button onClick={() => void loadMiniApps()}>Load installed apps</button>;
  }

  // loaded | permissionDenied | error ...
}

Never assume fediInternal exists. Feature-detect exactly like WebLN and Nostr.

Demo

Visit /demo/ecash in a generated project for FediVersionBadge, BalanceDisplay, and InstallMiniAppButton components.

On this page