Custom entities
Declare your own entity types with defineEntity.
When you need to track something the platform doesn’t provide out-of-the-box, you can create a custom entity. Each custom entity gets its own importable entity class, JSONB-backed storage with deploy-time validation, and the same Entity API as native entities in Backfill.
For the distinction between entities, records, resources, documents, and lines, see the SDK glossary.
File location
Create one file per entity, at src/entities/<PascalName>.ts. It’s auto-registered on deploy. The filename must match the entity name (MyEntity → MyEntity.ts), and the file’s default export is defineEntity({ ... }):
// src/entities/NexusAddress.ts
import { defineEntity } from "@backfill-io/sdk";
export default defineEntity({
name: "NexusAddress",
fields: {
nexusName: { type: "string", required: true },
street: { type: "string", required: true },
city: { type: "string", required: true },
state: { type: "string", required: true },
postalCode: { type: "string", required: true },
country: { type: "string", default: "US" },
active: { type: "boolean", default: true },
startsOn: { type: "date" },
status: { type: "enum", values: ["draft", "active", "archived"], default: "draft" },
customerId: { type: "reference", entity: "Customer", index: true },
rate: { type: "decimal" },
},
});
Field types
We currently support the field types below:
| Type | Use for |
|---|---|
string | IDs, names, dates as ISO strings, free text. |
number | Amounts, counts, percentages. |
boolean | Flags. |
json | Nested objects, arrays, anything you want as opaque structured data. |
date | ISO YYYY-MM-DD date strings. |
datetime | ISO datetimes or browser datetime-local strings. |
enum | One string from a declared values list. |
reference | A string id pointing at a standard entity or another custom entity in the same extension. |
decimal | Exact decimal strings or numbers for rates, hours, quantities, and money-like values. |
Each field can declare:
required?: boolean— fail to create if missing.default?: any— applied at create when the field isn’t supplied.index?: boolean— addindex: truewhen you expect to filter by equality on this field often. Backfill can use the index instead of scanning custom entity data.referencefields are indexed for equality automatically.
enum fields must declare values: string[]. reference fields must declare
entity: string; this can be a Backfill standard entity such as Invoice or a
custom entity declared by the same extension.
Naming rules
- Entity name must be PascalCase (e.g.,
RevenueSchedule,NexusAddress). - Cannot collide with a standard entity name (you can’t define one called
Invoice). - Filename must match the name:
MyEntitylives atsrc/entities/MyEntity.ts.
Errors look like:
src/entities/foo.ts: entity name 'foo' must be PascalCase (e.g. RevenueSchedule)
src/entities/Invoice.ts: entity name 'Invoice' conflicts with a standard entity
Permission grants
Custom entities go through the same permission system. Grant access in the manifest:
permissions: {
entities: { NexusAddress: ["read", "write"] },
}
Without explicit permissions, your own scripts can’t access the entity you just defined.
displayOn — surface on parent entities
displayOn adds a panel to a parent entity’s detail page in the dashboard.
defineEntity({
name: "StripeReconciliation",
fields: {
chargeId: { type: "string", required: true },
invoiceId: { type: "reference", entity: "Invoice", index: true },
matchedAt: { type: "datetime" },
confidence: { type: "number" },
},
displayOn: [
{ entity: "Invoice", foreignKey: "invoiceId" },
],
});
The parent entity must be a standard entity and the foreignKey must be either
a string field or a reference field that points at that parent entity.
Hooks against custom entities
Hooks work the same as for standard entities. Drop a file at src/hooks/<custom-entity-kebab>/<event>.ts. The entity name converts to kebab using the standard rule (so NexusAddress → nexus-address).
src/hooks/nexus-address/before-save.ts
src/hooks/stripe-reconciliation/after-save.ts
Reading and writing
Review entities to see all Entity methods available for custom entities. Example below:
import { NexusAddress } from "@backfill-io/sdk";
const records = NexusAddress.query({ where: { active: true } });
NexusAddress.create({
nexusName: "California",
street: "...",
city: "...",
state: "CA",
postalCode: "94000",
});