End-to-end — scaffold, iterate locally, publish to a tenant.
Extension Authoring
A FastYoke extension is a React bundle the admin shell loads at
runtime. The fy CLI handles every mechanical step; this recipe is
a full walkthrough from empty directory to installed extension.
1. Scaffold
npm install -g @fastyoke/cli
fy init shift-heatmap
cd shift-heatmap
pnpm add -g @fastyoke/cli
fy init shift-heatmap
cd shift-heatmap
fy init drops you a minimal TypeScript project:
shift-heatmap/
├── manifest.json
├── package.json
├── tsconfig.json
├── README.md
├── src/
│ └── index.tsx
└── .gitignore
The bundler is fy build / fy dev (esbuild under the hood) — no
separate config file is generated.
Install deps and you're ready:
npm install
2. Manifest shape
The scaffold's manifest.json is the truth the host reads on
install. Minimum required keys:
{
"id": "acme.shift-heatmap",
"version": "0.1.0",
"required_scopes": ["entities:read", "jobs:read"],
"components": [],
"pages": [
{ "name": "Shift Heatmap", "path": "/shift-heatmap" }
]
}
3. Write a minimal component
Replace src/index.tsx:
import React from 'react';
import { useFastYoke } from '@fastyoke/sdk';
export default function ShiftHeatmap() {
const { tenantId, jobs } = useFastYoke();
const [count, setCount] = React.useState<number | null>(null);
React.useEffect(() => {
void jobs.list({}).then((rows) => setCount(rows.length));
}, [jobs]);
return (
<section style={{ padding: '2rem' }}>
<header>
<p style={{ color: '#6b7280', fontSize: '0.8rem' }}>
Extension · acme.shift-heatmap
</p>
<h1>Shift heatmap</h1>
<p style={{ color: '#6b7280' }}>Tenant {tenantId}</p>
</header>
<p>
Active jobs in this tenant: {count ?? <em>loading…</em>}
</p>
</section>
);
}
Two things are worth noting:
useFastYoke()comes from@fastyoke/sdkand gives you the activetenantId,projectId, and the typed clients (jobs,entities,schemas,pages,files,extensions). The host provides it via an import map, so your extension shares the same React context and SDK clients with the shell.jobs.list()resolves to aJobResponse[]array directly, not a paginated{ items }object — read.lengthstraight off the result.- We're not importing from
react-dom/ mounting our own root. The host does that — your component is a child ofFastYokeProvider.
4. Local iteration
Spin up watch mode:
fy dev
esbuild rebuilds dist/bundle.mjs on every save. In another
terminal, keep the admin shell running (npm run build && npm run preview from the main repo) — every fy dev rebuild reloads
seamlessly once the extension is uploaded.
5. Mint a tenant admin JWT
fy publish authenticates as a tenant admin. Grab a token from
the admin shell:
# Option A — browser: DevTools → Application → Local Storage →
# fastyoke-auth → copy the "token" value.
# Option B — minted via the /auth/login endpoint.
export FASTYOKE_TOKEN="eyJhbGciOi..."
See Authentication for the full shape.
6. Build + publish
fy build
fy publish \
--tenant https://fastyoke.example \
--token "$FASTYOKE_TOKEN" \
--manifest ./manifest.json \
--bundle ./dist/bundle.mjs
The upload runs through the three-layer scanner (same policy as
manual file uploads). On success, you get back the
tenant_extensions row id — the extension is installed and
active.
7. Find it in the admin shell
Navigate to Extensions in the admin nav. Your extension shows
up with its manifest metadata and the sha256 of the bundle. The
page at /shift-heatmap (from your manifest's pages[].path)
now serves your component.
Bumping a new version
Two rules:
- Bump
manifest.json→version. Same-version re-upload returns409to protect you from overwriting history. - Re-run
fy build && fy publish.
The old version row stays in tenant_extensions but is flipped to
is_active = 0. The host serves only the new one.
Troubleshooting
| Symptom | Fix |
|---|---|
| "bundle rejected by security scan" | Inspect what the scanner flagged — usually a third-party dep with heuristic-positive code. Swap it or vendor a trimmed copy. |
| Component renders blank | Check the browser console. Host vs extension React instance mismatch? Run the host via preview, not serve. |
| Import map not resolving | Extensions must mark react, react-dom, and @fastyoke/sdk as external in the bundler — fy build / fy dev configure esbuild this way out of the box; don't bypass them by running esbuild directly without the same externals. |
Related
- SDK Quickstart — installation + the host-provider pattern explained at a higher level.
- SDK Reference — full hook + component surface.