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 Case | Recommendation |
|---|---|
| React to database changes automatically | ✅ Trigger |
| Call a function from the client | Use Executables |
| Run code on a schedule | Use Schedulers |
| Expose an HTTP endpoint to external services | Use Webhooks |
How it works
- You decorate a method with
@triggerin a class that extendsSquidService - Squid registers the trigger at deploy time
- When a document in the specified collection is inserted, updated, or deleted, Squid calls your function
- Your function receives a
TriggerRequestwith 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/backendpackage installed
Step 1: Create a trigger function
Create a service class that extends SquidService and add your trigger:
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:
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:
@trigger('userChange', 'users') // id, collection (uses built-in DB)
@trigger('userChange', 'users', 'myDatabase') // id, collection, connectorId
| Parameter | Type | Description |
|---|---|---|
id | string | A unique identifier for this trigger |
collectionName | string | The name of the collection to watch |
integrationId? | string | The connector ID. Defaults to the built-in database |
Options object:
@trigger({ collection: 'users', mutationTypes: ['insert', 'update'] })
| Property | Type | Description |
|---|---|---|
id? | string | A unique identifier. If omitted, defaults to ClassName.FunctionName |
collection | string | The name of the collection to watch |
integrationId? | string | The 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:
| Property | Type | Description |
|---|---|---|
docId | string | Record<string, any> | The document ID (string for single-field keys, object for composite keys) |
collectionName | string | The name of the affected collection |
integrationId | string | The connector ID for the affected database |
mutationType | MutationType | The type of mutation: 'insert', 'update', or 'delete' |
docBefore? | T | The document state before the mutation. Available for update and delete |
docAfter? | T | The document state after the mutation. Available for insert and update |
The generic type parameter T lets you type the document data:
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 Type | docBefore | docAfter | Description |
|---|---|---|---|
'insert' | undefined | Present | A new document was created |
'update' | Present | Present | An existing document was modified |
'delete' | Present | undefined | A 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:
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:
// 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:
@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.
@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
-
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.
-
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.
-
Use mutation type filtering. When your logic applies to only certain operations, use the
mutationTypesoption to avoid unnecessary trigger executions. -
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.
-
Type your TriggerRequest. Use the generic parameter (
TriggerRequest<MyType>) for type-safe access to document data.
Code Examples
Sending notifications on new records
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
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
- Executables - Call backend functions from the client
- Schedulers - Run code on a schedule
- Webhooks - Expose HTTP endpoints
- Rate and quota limiting - Protect your backend functions
- API reference - Full API documentation for the trigger decorator