Portal Community

FormActionContext Interface

Your custom handler receives this context object:

interface FormActionContext {
  // Current form state
  formId: string;
  tenantId: string;
  formValues: Record<string, any>;
  formEngine: FormEngine;

  // Application services
  apiClient: FormDefinitionApiClient;

  // Navigation
  navigate: (url: string) => void;

  // Completion signals
  complete: () => void;           // Signal success — hides loading state
  fail: (reason?: string) => void; // Signal failure — shows error, hides loading

  // Action config from schema
  config?: Record<string, any>;

  // Validation
  validate: () => Promise<boolean>;
}

Registering a Custom Action

import { registerFormAction } from '@atlas-forms/control-registry-js';

registerFormAction('export-to-pdf', async (context: FormActionContext) => {
  try {
    const { formValues, formId, tenantId } = context;

    // Validate first if needed
    const isValid = await context.validate();
    if (!isValid) {
      context.fail('Please fix validation errors before exporting.');
      return;
    }

    // Call your custom service
    const pdfBlob = await pdfService.generatePDF(formId, formValues, tenantId);
    downloadBlob(pdfBlob, `form-${formId}.pdf`);

    context.complete(); // Signal success
  } catch (err) {
    context.fail('PDF export failed. Please try again.');
  }
});

Using in the Form Schema

{
  "type": "custom",
  "label": "Export PDF",
  "variant": "secondary",
  "icon": "file-pdf",
  "config": {
    "handlerType": "export-to-pdf",
    "handlerConfig": {
      "template": "detailed",
      "includeSignature": true
    }
  }
}

The handlerConfig is available in your handler as context.config, allowing the same handler to behave differently based on the form that uses it.

Handler Lifecycle

When the user clicks a custom action button:

  1. Button enters loading state (spinner visible)
  2. Your async handler is called with FormActionContext
  3. If you call context.complete() — button exits loading, success toast shown
  4. If you call context.fail(reason) — button exits loading, error toast shown
  5. If your handler throws — treated as fail() with generic error message
Always Call complete() or fail() If your handler exits without calling either, the button stays in loading state indefinitely. Always call one of them in every code path, including error handlers.

Accessing Other Services

Custom action handlers can import any service from your application. The context.apiClient gives you the Atlas Forms API client. For other services (your own APIs, EdgeStream, workflow engine), import them directly in the handler file.