Skip to main content

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 success flag
  • 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.