Skip to content

Guards - Field Modification Rules

Control which fields can be modified in CREATE vs UPDATE operations.

Configuration Options

typescript
guards: {
  // Fields allowed on CREATE
  createable?: string[];

  // Fields allowed on UPDATE/PATCH
  updatable?: string[];

  // Fields only modifiable via specific actions
  protected?: Record<string, string[]>;

  // Fields set on CREATE, never modified after
  immutable?: string[];
}

How It Works

ListWhat it controls
createableFields allowed in create (POST) request body
updatableFields allowed in update (PATCH) request body
protectedFields blocked from CRUD, only modifiable via named actions
immutableFields allowed on create, blocked on all updates

Combining lists:

  • createable + updatable - Most fields go in both (can set on create AND modify later)
  • createable only - Field is set once, cannot be changed via update
  • protected - Don't also list in createable or updatable (they're mutually exclusive)
  • immutable - Don't also list in updatable (contradiction)
typescript
guards: {
  createable: ["name", "description", "category"],
  updatable: ["name", "description"],
  // "category" is only in createable = set once, can't change via update
  protected: {
    status: ["approve", "reject"],  // NOT in createable/updatable
  },
  immutable: ["invoiceNumber"],     // NOT in updatable
}

If a field is not listed anywhere, it cannot be set by the client.

System-Managed Fields (Always Protected)

These are automatically protected - you cannot override:

  • createdAt, createdBy
  • modifiedAt, modifiedBy
  • deletedAt, deletedBy

Example

typescript
guards: {
  createable: ["name", "description", "amount"],
  updatable: ["name", "description"],
  protected: {
    status: ["approve", "reject"],     // Only via these actions
    amount: ["reviseAmount"],
  },
  immutable: ["invoiceNumber"],
}

Disabling Guards

typescript
guards: false  // Only system fields protected

PUT/Upsert with External IDs

When you disable guards AND use client-provided IDs, you unlock PUT (upsert) operations. This is designed for syncing data from external systems.

Requirements for PUT

  1. generateId: false in database config (client provides IDs)
  2. guards: false in resource definition

How PUT Works

PUT /resource/:id
├── Record exists? → UPDATE (replace all fields)
└── Record missing? → CREATE with provided ID

Database Config

typescript
// quickback.config.ts
export default {
  database: {
    generateId: false,  // Client provides IDs (enables PUT)
    // Other options: 'uuid' | 'cuid' | 'nanoid' | 'serial'
  }
};

Resource Definition

typescript
defineResource(externalAccounts, {
  firewall: {
    organization: {},  // Still isolated by org
  },
  guards: false,       // Disables field restrictions
  crud: {
    put: {
      access: { roles: ['admin', 'sync-service'] }
    }
  }
});

What's Still Protected with guards: false

Even with guards: false, system-managed fields are ALWAYS protected:

  • createdAt, createdBy - Set on INSERT only
  • modifiedAt, modifiedBy - Auto-updated
  • deletedAt, deletedBy - Set on soft delete

Ownership Auto-Population

When PUT creates a new record, ownership fields are auto-set from context:

typescript
// Client sends: PUT /accounts/ext-123 { name: "Acme" }
// Server creates:
{
  id: "ext-123",           // Client-provided
  name: "Acme",            // Client-provided
  organizationId: ctx.activeOrgId,  // Auto-set from firewall
  createdAt: now,          // Auto-set
  createdBy: ctx.userId,   // Auto-set
  modifiedAt: now,         // Auto-set
  modifiedBy: ctx.userId,  // Auto-set
}

Use Cases for PUT/External IDs

Use CaseWhy PUT?
External API syncExternal system controls the ID
Webhook handlersEvents come with their own IDs
Data migrationPreserve IDs from source system
Idempotent updatesSafe to retry (no duplicate creates)
Bulk upsertCreate or update in one operation

ID Generation Options

generateIdPUT Available?Notes
'uuid'NoServer generates UUID
'cuid'NoServer generates CUID
'nanoid'NoServer generates nanoid
'serial'NoDatabase auto-increments
falseYes (if guards: false)Client provides ID

Backend security, simplified.