← Back to Skills

Building a Tawa PlugIN

tawa init my-plugin # full scaffold tawa sample --api my-plugin # alternative ```

Ruleprovision

About

tawa init my-plugin # full scaffold tawa sample --api my-plugin # alternative ```

Skill Content

This is the raw markdown that gets installed as a Claude Code rule.

# Building a Tawa PlugIN

## Scaffold

```bash
tawa init my-plugin        # full scaffold
tawa sample --api my-plugin  # alternative
```

## Checklist

```
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)
```

## Minimal catalog-info.yaml

```yaml
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
```

## Required: Health Endpoint

```typescript
app.get('/health', (_req, res) => {
  res.json({ status: 'ok', service: 'my-plugin' })
})
```

## Required: Input Validation

```typescript
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
  // ...
})
```

## Required: Septor Audit

```typescript
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 Automatic

Gas is charged by Janus for routes declared with `gas`. You write no gas code. The platform handles it.

## Key Facts

- `gas: 0` = free route (good for status/health checks)
- `auth: required` always on financial/compliance routes
- Missing `/health` = deploy health check fails immediately
- Missing `internalDependencies` = env vars like `SEPTOR_URL` not injected
- `CHANGELOG.md` in your repo is published automatically on every deploy

Install

Copy the skill content and save it to:

~/.claude/rules/tawa-plugin-scaffold.md
Download .md

Coming soon via CLI:

tawa chaac install tawa-plugin-scaffold

Details

Format
Rule
Category
provision
Version
1.0.51515
Tokens
~738
Updated
2026-06-24
platformpluginscaffoldgasseptoropenapivalidation