tawa init my-plugin # full scaffold
tawa sample --api my-plugin # alternative
Infrastructure:
[ ] catalog-info.yaml with routes, gas, databases, internalDependencies
[ ] GET /health returning { status: 'ok', service: 'my-plugin' }
[ ] tawa preflight passes
Code:
[ ] Zod validation on all POST endpoints
[ ] Septor emit on all financial/compliance operations (fire-and-forget)
[ ] No console.log — use pino logger
[ ] No hardcoded secrets — use process.env
[ ] RelayClient.fromEnv() not new RelayClient({ jwtSecret })
[ ] verifyTokenJWKS() not verifyToken(token, secret)
Testing:
[ ] 80%+ test coverage
[ ] Tests verify Septor events emitted correctly
Documentation:
[ ] openapi.yaml for auto-generated UI tile
[ ] CHANGELOG.md (builder publishes on deploy automatically)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: my-plugin
description: What your plugin does
annotations:
insureco.io/framework: express
insureco.io/pod-tier: nano
insureco.io/openapi: openapi.yaml # enables auto-generated UI tile
insureco.io/catalog-version: "0.5.0"
spec:
type: service
lifecycle: production
owner: my-org
routes:
- path: /api/my-plugin/action
methods: [POST]
auth: required
gas: 5 # tokens charged per successful call
databases:
- type: mongodb
internalDependencies:
- service: septor
port: 3000
tests:
smoke:
- path: /health
expect: 200
app.get('/health', (_req, res) => {
res.json({ status: 'ok', service: 'my-plugin' })
})
import { z } from 'zod'
const RequestSchema = z.object({
entityName: z.string().min(1).max(200),
orgSlug: z.string().min(1),
})
app.post('/api/my-plugin/action', async (req, res) => {
const parsed = RequestSchema.safeParse(req.body)
if (!parsed.success) {
return res.status(400).json({ success: false, error: parsed.error.message })
}
const { entityName, orgSlug } = parsed.data
// ...
})
septor.emit('action.completed', {
entityId: orgSlug,
data: { entityName, result: result.status },
metadata: { who: req.user?.bioId, why: 'Plugin action requested' },
}).catch((err) => logger.error({ err }, 'Septor emit failed'))
Gas is charged by Janus for routes declared with gas. You write no gas code. The platform handles it.
gas: 0 = free route (good for status/health checks)auth: required always on financial/compliance routes/health = deploy health check fails immediatelyinternalDependencies = env vars like SEPTOR_URL not injectedCHANGELOG.md in your repo is published automatically on every deployLast updated: February 28, 2026