Actions
Actions allow you to define custom operations on tables and individual records. They're perfect for implementing business operations like bulk exports, sending notifications, generating reports, or any custom logic that doesn't fit into standard CRUD operations.
Types of Actions
Table Actions
Operate on multiple records at once. Perfect for bulk operations like exporting data, sending batch emails, or generating reports.
Detail/Record Actions
Operate on a single record. Perfect for operations like sending individual emails, generating PDFs, or triggering workflows.
Registering Table Actions
Using registerTableAction()
connector.customize(() => {
const c = new ModelCustomizer("users");
c.registerTableAction(
"export-csv", // Action ID
"Export to CSV", // Display label
async (request, records, db) => {
// Action logic here
const csv = convertToCSV(records);
return {
success: true,
data: csv,
filename: 'users-export.csv'
};
}
);
return c;
});Using addAction()
connector.customize(() => {
const c = new ModelCustomizer("users");
c.addAction({
type: "table",
id: "export-csv",
label: "Export to CSV",
callback: async (request, records, db) => {
const csv = convertToCSV(records);
return { success: true, data: csv };
}
});
return c;
});Registering Detail Actions
Using registerDetailAction()
connector.customize(() => {
const c = new ModelCustomizer("users");
c.registerDetailAction(
"send-welcome-email", // Action ID
"Send Welcome Email", // Display label
async (request, record, db) => {
await sendEmail({
to: record.email,
subject: "Welcome!",
body: `Hello ${record.name}, welcome to our platform!`
});
return {
success: true,
message: "Email sent successfully"
};
}
);
return c;
});Using addAction()
connector.customize(() => {
const c = new ModelCustomizer("orders");
c.addAction({
type: "detail",
id: "generate-invoice",
label: "Generate Invoice",
callback: async (request, record, db) => {
const invoice = await generateInvoicePDF(record);
return {
success: true,
url: invoice.url,
message: "Invoice generated"
};
}
});
return c;
});Action Parameters
request
The HTTP request object containing headers, query params, and body:
callback: async (request, records, db) => {
// Access headers
const userId = request.headers['user-id'];
// Access query parameters
const format = request.query.format;
// Access body
const options = request.body;
// ...
}records / record
The record(s) the action is being performed on:
// Table action - array of records
callback: async (request, records, db) => {
console.log(`Processing ${records.length} records`);
records.forEach(r => console.log(r.id));
}
// Detail action - single record
callback: async (request, record, db) => {
console.log(`Processing record ${record.id}`);
console.log(record.email);
}db
Database handler for performing database operations:
callback: async (request, records, db) => {
// Fetch related data
const relatedData = await db.get({
table: 'orders',
where: { userId: records[0].id }
});
// Insert audit log
await db.insert({
table: 'audit_log',
data: {
action: 'export',
userId: records[0].id,
timestamp: new Date().toISOString()
}
});
// ...
}Practical Examples
Export to CSV
c.registerTableAction(
"export-csv",
"Export to CSV",
async (request, records, db) => {
const headers = Object.keys(records[0]);
const csv = [
headers.join(','),
...records.map(r => headers.map(h => r[h]).join(','))
].join('\n');
return {
success: true,
data: csv,
contentType: 'text/csv',
filename: `export-${Date.now()}.csv`
};
}
);Send Bulk Email
c.registerTableAction(
"send-newsletter",
"Send Newsletter",
async (request, records, db) => {
const template = request.body.template;
const sent = [];
const failed = [];
for (const user of records) {
try {
await sendEmail({
to: user.email,
subject: "Monthly Newsletter",
template: template,
data: { name: user.name }
});
sent.push(user.email);
} catch (error) {
failed.push({ email: user.email, error: error.message });
}
}
return {
success: true,
sent: sent.length,
failed: failed.length,
details: { sent, failed }
};
}
);Generate Report
c.registerTableAction(
"generate-report",
"Generate Report",
async (request, records, db) => {
const report = {
totalRecords: records.length,
activeUsers: records.filter(r => r.isActive).length,
totalRevenue: records.reduce((sum, r) => sum + (r.revenue || 0), 0),
averageAge: records.reduce((sum, r) => sum + r.age, 0) / records.length
};
// Save report to database
const saved = await db.insert({
table: 'reports',
data: {
type: 'user-report',
data: JSON.stringify(report),
createdAt: new Date().toISOString()
}
});
return {
success: true,
report,
reportId: saved.id
};
}
);Send Individual Email
c.registerDetailAction(
"send-reset-email",
"Send Password Reset",
async (request, record, db) => {
const resetToken = generateResetToken();
// Save token to database
await db.update({
table: 'users',
data: { resetToken, resetTokenExpiry: Date.now() + 3600000 },
where: { id: record.id }
});
// Send email
await sendEmail({
to: record.email,
subject: "Password Reset",
body: `Reset link: https://app.com/reset?token=${resetToken}`
});
return {
success: true,
message: "Password reset email sent"
};
}
);Upload to S3
c.registerDetailAction(
"upload-to-s3",
"Upload to S3",
async (request, record, db) => {
const s3 = connector.using<S3Integration>('s3');
const result = await s3.prepareUpload({
key: `users/${record.id}/avatar.jpg`,
fileSize: record.avatarSize,
contentType: 'image/jpeg'
});
// Update record with S3 URL
await db.update({
table: 'users',
data: { avatarUrl: result.url },
where: { id: record.id }
});
return {
success: true,
uploadUrl: result.uploadUrl,
message: "Upload prepared"
};
}
);Generate Invoice PDF
c.registerDetailAction(
"generate-invoice",
"Generate Invoice",
async (request, record, db) => {
// Fetch related order items
const items = await db.get({
table: 'order_items',
where: { orderId: record.id }
});
// Generate PDF
const pdf = await generatePDF({
template: 'invoice',
data: {
order: record,
items: items.rows,
customer: await db.getSingle({
table: 'users',
where: { id: record.userId }
})
}
});
// Save to S3
const s3 = connector.using<S3Integration>('s3');
const uploadResult = await s3.upload({
key: `invoices/${record.id}.pdf`,
body: pdf,
contentType: 'application/pdf'
});
return {
success: true,
url: uploadResult.url,
message: "Invoice generated successfully"
};
}
);Calling Actions from API
Table Action Endpoint
POST /dbmanager/{tableName}/table-action
Headers:
auth: your-secret-key
Body:
{
"actionId": "export-csv",
"records": [
{ "id": 1, "email": "user1@example.com" },
{ "id": 2, "email": "user2@example.com" }
]
}Record Action Endpoint
POST /dbmanager/{tableName}/record-action
Headers:
auth: your-secret-key
Body:
{
"actionId": "send-welcome-email",
"record": {
"id": 1,
"email": "user@example.com",
"name": "John Doe"
}
}Querying Registered Actions
const customizer = new ModelCustomizer("users");
customizer.registerTableAction("export", "Export", () => {});
customizer.registerDetailAction("email", "Send Email", () => {});
// Get action IDs
const tableActions = customizer.getTableActionIds();
// ["export"]
const detailActions = customizer.getDetailActionIds();
// ["email"]
// Get action function
const exportAction = customizer.getTableAction("export");
if (exportAction) {
await exportAction(request, records, db);
}Best Practices
- Use descriptive action IDs (kebab-case recommended)
- Provide clear, user-friendly labels
- Return consistent response objects with
successflag - Include error handling and return meaningful error messages
- For long-running operations, consider using background jobs
- Log action executions for audit purposes
- Validate request data before processing
- Use the database handler for all database operations
- Leverage integrations for external services (S3, email, etc.)
Security: Actions have full database access. Always validate inputs and implement proper authorization checks.
Next Steps: Learn about Field Writers to transform field values during create/update operations.