File structure
The anatomy of a Backfill integration and how the platform finds what each file does.
Every Backfill integration is a TypeScript project with a fixed layout. Hooks, jobs, API routes, custom entities, and fields are discovered by file path. UI pages are referenced from the manifest.
Required files
| File | Purpose |
|---|---|
backfill.config.ts | Manifest. defineExtension({...}) (or defineConnector({...})) as the default export. |
package.json | Declare @backfill-io/sdk as a devDependency plus any browser-compatible runtime packages your scripts import. |
tsconfig.json | TypeScript config — strict: true, noEmit: true. |
.backfillrc | Local dev credentials. Gitignored. |
src/ | All script source. |
What lives in src/
src/
├── hooks/<entity-kebab>/ Hooks for a given entity
│ ├── before-save.ts
│ ├── after-save.ts
│ └── recalculate.ts
├── jobs/ Scheduled jobs
│ └── <name>.ts
├── api/ HTTP routes
│ └── <path>.ts
├── entities/ Custom entity definitions
│ └── <Name>.ts
├── fields/ Field declarations for host entities
│ └── <EntityName>.ts
├── pages/ UI pages and action handlers
│ └── <page>.tsx
└── lib/ Shared helpers
└── <Name>.ts
Shared helpers usually live under src/lib. They are not registered on their
own, but any hook, job, route, page, entity, tab, panel, or action can import
them. The CLI bundles each executable entrypoint independently, so shared helper
code is duplicated per entrypoint in v1.
Where each thing goes
| To do this… | Put a file at… | What it exports |
|---|---|---|
| React to an entity create / update | src/hooks/<entity-kebab>/after-save.ts | export default async function (ctx) { ... } |
| Modify or reject an entity write | src/hooks/<entity-kebab>/before-save.ts | export default async function (ctx) { return { ... } } |
| Contribute to draft calculation | src/hooks/<entity-kebab>/recalculate.ts | See Hooks → Recalculate |
| Run on a schedule | src/jobs/<name>.ts | export const schedule = "...", export async function run() {...} |
| Expose an HTTP endpoint | src/api/<path>.ts | export const GET = async (ctx) => {...} (or POST, PUT, PATCH, DELETE) |
| Define a custom entity | src/entities/<PascalName>.ts | export default defineEntity({ name, fields }) |
| Define fields on a host entity | src/fields/<EntityName>.ts | export const stripeCustomerId = defineEntityField({ key: "stripe_customer_id", ... }) or defineLineField(...) |
| Add a dashboard page | src/pages/<name>.tsx + entry in pages[] | A default-export render function |
| Add a tab / panel / action | A render .tsx + entry in ui.{tabs,panels,actions} | Same as a page render |