Skip to main content

Triggers

Execute backend functions automatically in response to database changes.

Why Use Triggers

You need to react when data changes: send a notification when a new user signs up, update a search index when a product is modified, or log an audit trail when records are deleted.

Without triggers, you'd need to scatter this logic across every place that writes data, or poll the database for changes. With triggers, you declare what collection to watch and Squid calls your function automatically:

// Backend: runs automatically when a document changes
@trigger('onNewOrder', 'orders')
async handleNewOrder(request: TriggerRequest<Order>): Promise<void> {
if (request.mutationType === 'insert') {
await this.sendOrderConfirmation(request.docAfter);
}
}

No polling. No duplicated logic. Your code runs exactly when the data changes.

Overview

Triggers are backend functions that execute automatically when documents in a database collection are inserted, updated, or deleted. They run after the mutation is committed, giving you a reliable way to build reactive workflows.

When to use triggers

Use CaseRecommendation
React to database changes automatically✅ Trigger
Call a function from the clientUse Executables
Run code on a scheduleUse Schedulers
Expose an HTTP endpoint to external servicesUse Webhooks

How it works

  1. You decorate a method with @trigger in a class that extends SquidService
  2. Squid registers the trigger at deploy time
  3. When a document in the specified collection is inserted, updated, or deleted, Squid calls your function
  4. Your function receives a TriggerRequest with the mutation type, the document before and after the change, and the document ID

Quick Start

Prerequisites

  • A Squid backend project initialized with squid init-backend
  • The @squidcloud/backend package installed

Step 1: Create a trigger function

Create a service class that extends SquidService and add your trigger:

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@trigger('userChange', 'users')
async handleUserChange(request: TriggerRequest): Promise<void> {
console.log(`User ${request.docId} was ${request.mutationType}d`);
console.log('Before:', request.docBefore);
console.log('After:', request.docAfter);
}
}

Step 2: Export the service

Ensure your service is exported from the service index file:

service/index.ts
export * from './example-service';

Step 3: Start or deploy the backend

For local development, run the backend locally using the Squid CLI:

squid start

To deploy to the cloud, see deploying your backend.

The trigger now fires automatically whenever a document in the users collection is inserted, updated, or deleted.

Core Concepts

The @trigger decorator

The @trigger decorator has two forms: positional parameters and an options object.

Positional parameters:

Backend code
@trigger('userChange', 'users')              // id, collection (uses built-in DB)
@trigger('userChange', 'users', 'myDatabase') // id, collection, connectorId
ParameterTypeDescription
idstringA unique identifier for this trigger
collectionNamestringThe name of the collection to watch
integrationId?stringThe connector ID. Defaults to the built-in database

Options object:

Backend code
@trigger({ collection: 'users', mutationTypes: ['insert', 'update'] })
PropertyTypeDescription
id?stringA unique identifier. If omitted, defaults to ClassName.FunctionName
collectionstringThe name of the collection to watch
integrationId?stringThe connector ID. Defaults to the built-in database
mutationTypes?MutationType[]Filter which mutation types trigger the function. If omitted, all mutations trigger

TriggerRequest

The TriggerRequest<T> object passed to your trigger function provides details about the change:

PropertyTypeDescription
docIdstring | Record<string, any>The document ID (string for single-field keys, object for composite keys)
collectionNamestringThe name of the affected collection
integrationIdstringThe connector ID for the affected database
mutationTypeMutationTypeThe type of mutation: 'insert', 'update', or 'delete'
docBefore?TThe document state before the mutation. Available for update and delete
docAfter?TThe document state after the mutation. Available for insert and update

The generic type parameter T lets you type the document data:

Backend code
interface User {
id: string;
name: string;
email: string;
}

@trigger('userChange', 'users')
async handleUserChange(request: TriggerRequest<User>): Promise<void> {
// request.docAfter is typed as User | undefined
const user = request.docAfter;
if (user) {
console.log(user.name); // type-safe access
}
}

Mutation types

Triggers respond to three types of mutations:

Mutation TypedocBeforedocAfterDescription
'insert'undefinedPresentA new document was created
'update'PresentPresentAn existing document was modified
'delete'PresentundefinedA document was removed

Filtering by mutation type

Use the options object form to trigger only on specific mutation types. This is useful when you need separate logic for inserts, updates, and deletes:

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

export class OrderService extends SquidService {
@trigger({ collection: 'orders', mutationTypes: ['insert'] })
async onNewOrder(request: TriggerRequest): Promise<void> {
// Only runs on insert
await this.sendOrderConfirmation(request.docAfter);
}

@trigger({ collection: 'orders', mutationTypes: ['update'] })
async onOrderUpdate(request: TriggerRequest): Promise<void> {
// Only runs on update
await this.notifyOrderStatusChange(request.docBefore, request.docAfter);
}

@trigger({ collection: 'orders', mutationTypes: ['delete'] })
async onOrderCancelled(request: TriggerRequest): Promise<void> {
// Only runs on delete
await this.processRefund(request.docBefore);
}
}

Using external database connectors

By default, triggers watch the built-in database. To watch a collection in an external database connector, specify the connector ID:

Backend code
// Positional form
@trigger('userSync', 'users', 'myPostgresDb')

// Options object form
@trigger({ collection: 'users', integrationId: 'myPostgresDb' })

Triggers work with any database connector that supports mutations, including PostgreSQL, MySQL, MongoDB, and others.

Using the Squid client

Access other Squid services from within a trigger using this.squid. This gives you access to the same Database operations available in the client SDK:

Backend code
@trigger('auditLog', 'orders')
async logOrderChange(request: TriggerRequest): Promise<void> {
const auditCollection = this.squid.collection('audit-log');
await auditCollection.doc().insert({
collection: request.collectionName,
docId: request.docId,
mutationType: request.mutationType,
timestamp: new Date(),
before: request.docBefore,
after: request.docAfter,
});
}

Error Handling

Triggers execute after the mutation is committed. If a trigger throws an error, the original mutation is not rolled back. Your trigger should handle errors gracefully to avoid losing information about the change.

Backend code
@trigger('processChange', 'payments')
async handlePaymentChange(request: TriggerRequest): Promise<void> {
try {
await this.processPaymentUpdate(request);
} catch (error) {
console.error(`Trigger failed for doc ${request.docId}:`, error);
// Log the failure for manual review
await this.squid.collection('failed-triggers').doc().insert({
docId: request.docId,
mutationType: request.mutationType,
error: String(error),
timestamp: new Date(),
});
}
}

Best Practices

  1. Keep triggers fast. Triggers run asynchronously after each mutation. Long-running operations delay processing of subsequent changes. For heavy work, have the trigger write to a queue collection and process it separately.

  2. Handle errors gracefully. Since trigger errors do not roll back the original mutation, log failures and consider a retry mechanism if the triggered action is critical.

  3. Use mutation type filtering. When your logic applies to only certain operations, use the mutationTypes option to avoid unnecessary trigger executions.

  4. Avoid circular triggers. If a trigger writes to the same collection it watches, it will trigger itself in a loop. Write to a different collection or use mutation type filtering to prevent cycles.

  5. Type your TriggerRequest. Use the generic parameter (TriggerRequest<MyType>) for type-safe access to document data.

Code Examples

Sending notifications on new records

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

interface User {
id: string;
name: string;
email: string;
}

export class NotificationService extends SquidService {
@trigger({ collection: 'users', mutationTypes: ['insert'] })
async welcomeNewUser(request: TriggerRequest<User>): Promise<void> {
const user = request.docAfter;
if (!user) return;

await this.squid
.collection('notifications')
.doc()
.insert({
userId: user.id,
message: `Welcome, ${user.name}!`,
createdAt: new Date(),
});
}
}

Maintaining a derived collection

Backend code
import { SquidService, trigger, TriggerRequest } from '@squidcloud/backend';

interface Product {
id: string;
name: string;
price: number;
category: string;
}

export class AnalyticsService extends SquidService {
@trigger('productSync', 'products')
async syncProductStats(request: TriggerRequest<Product>): Promise<void> {
const statsCollection = this.squid.collection('category-stats');

if (request.mutationType === 'insert') {
const product = request.docAfter!;
const statsDoc = statsCollection.doc(product.category);
const stats = await statsDoc.snapshot();
await statsDoc.insert({
category: product.category,
count: (stats?.count ?? 0) + 1,
});
}

if (request.mutationType === 'delete') {
const product = request.docBefore!;
const statsDoc = statsCollection.doc(product.category);
const stats = await statsDoc.snapshot();
if (stats) {
await statsDoc.update({ count: Math.max(0, stats.count - 1) });
}
}
}
}

See Also