# Handler Architecture in Auth0 Deploy CLI: validate, calcChanges, and processChanges Explained

> Understand the Auth0 Deploy CLI handler architecture. Learn how validate calcChanges and processChanges manage Auth0 resource deployments efficiently with this powerful tool.

- Repository: [Auth0/auth0-deploy-cli](https://github.com/auth0/auth0-deploy-cli)
- Tags: architecture
- Published: 2026-02-25

---

**Auth0 Deploy CLI implements a plugin-style handler architecture where each resource type extends a base `APIHandler` class that standardizes configuration deployment through four distinct phases: loading remote state, validating assets, calculating differential changes, and processing CRUD operations with exponential-backoff retry logic.**

The auth0-deploy-cli repository orchestrates tenant configuration management through a sophisticated handler architecture that treats each Auth0 resource as a pluggable component. This design pattern ensures consistent validation, change detection, and deployment semantics across diverse resource types while allowing resource-specific customization for identifiers and API endpoints.

## The Base Handler Lifecycle in APIHandler

Every handler extends the abstract `APIHandler` base class defined in [`src/tools/auth0/handlers/default.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/auth0/handlers/default.ts). This foundation enforces a standardized pipeline that converts desired state definitions into Management API operations.

The lifecycle flows through four method phases:

- **load()** - Retrieves existing tenant configuration via `getType()`
- **validate(assets)** - Enforces uniqueness constraints on identifiers
- **calcChanges(assets)** - Computes differential state changes
- **processChanges(assets, changes)** - Executes operations with retry logic

## Validation Phase: The validate() Method

The `validate()` method in [`src/tools/auth0/handlers/default.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/auth0/handlers/default.ts) (lines 49-74) prevents deployment errors by checking for duplicate identifiers before any API calls occur.

```typescript
async validate(assets: Assets): Promise<void> {
  const typeAssets = assets[this.type];
  if (!Array.isArray(typeAssets)) return;

  // duplicate names are forbidden
  const duplicateNames = duplicateItems(typeAssets, 'name');
  if (duplicateNames.length) { 
    // ... throws ValidationError 
  }

  // duplicate identifiers (default `id`) are forbidden
  const duplicateIDs = duplicateItems(typeAssets, this.id);
  if (duplicateIDs.length) { 
    // ... throws ValidationError 
  }
}

```

This validation catches configuration errors early, throwing `ValidationError` for duplicate `name` or `id` values detected by the `duplicateItems` helper in [`src/tools/utils.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/utils.ts).

## Change Calculation Phase: calcChanges() and calculateChanges()

The `calcChanges()` method serves as the diffing engine, comparing desired assets against existing tenant state. Located in [`src/tools/auth0/handlers/default.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/auth0/handlers/default.ts) (lines 23-38), it delegates to the centralized `calculateChanges()` function in [`src/tools/calculateChanges.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/calculateChanges.ts).

```typescript
async calcChanges(assets: Assets): Promise<CalculatedChanges> {
  const typeAssets = assets[this.type];
  if (!typeAssets) return { del: [], create: [], conflicts: [], update: [] };
  const existing = await this.getType();

  return calculateChanges({
    handler: this,
    assets: typeAssets,
    allowDelete: !!this.config('AUTH0_ALLOW_DELETE'),
    existing,
    identifiers: this.identifiers,
  });
}

```

The calculation produces four buckets:

- **create**: Assets present in desired state but missing from tenant
- **update**: Assets matching by identifier but differing in properties  
- **del**: Assets present in tenant but missing from desired state (when `AUTH0_ALLOW_DELETE` is enabled)
- **conflicts**: Assets requiring rename handling to avoid name collisions

Matching logic iterates through `identifiers` (default `['id', 'name']`) to link existing and desired resources. In [`src/tools/calculateChanges.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/calculateChanges.ts) (lines 63-70), the matching loop processes each identifier strategy:

```typescript
for (const id of identifiers) {
  processAssets(id, [...create]);
}

```

## Processing Phase: processChanges() with Exponential Backoff

The `processChanges()` method executes the calculated operations in a specific order to satisfy dependencies and avoid name collisions. Found in [`src/tools/auth0/handlers/default.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/auth0/handlers/default.ts) (lines 105-141), it implements a retry-aware execution pipeline.

Execution order:

1. **Delete** operations (with `AUTH0_ALLOW_DELETE` guard)
2. **Conflict resolution** (temporary renames)
3. **Create** operations  
4. **Update** operations

Each operation wraps Management API calls with `retryWithExponentialBackoff` (defined in lines 44-98 of [`default.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/default.ts)), handling rate limits via the `Retry-After` header and implementing jitter to prevent thundering herd issues.

```typescript
await this.client.pool
  .addEachTask({
    data: del,
    generator: (delItem) =>
      retryWithExponentialBackoff(() => {
        const delFn = this.getClientFN(this.functions.delete);
        return delFn(delItem[this.id]);
      }, retryConfig)
      .then(() => { 
        this.didDelete(delItem); 
        this.deleted++; 
      })
  })
  .promise();

```

## Concrete Handler Implementation

Resource-specific handlers like `ClientHandler` in [`src/tools/auth0/handlers/clients.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/auth0/handlers/clients.ts) extend `DefaultAPIHandler` and customize only resource-specific parameters:

```typescript
export default class ClientHandler extends DefaultAPIHandler {
  constructor(opts) {
    super({
      ...opts,
      type: 'clients',
      identifiers: ['client_id', 'name'],
      objectFields: ['refresh_token', 'session_transfer'],
      stripCreateFields: ['client_id'],
      // ...
    });
  }

  async getType() {
    const data = await paginate(this.client.clients.getAll);
    return shouldExcludeThirdPartyClients(data) 
      ? data.filter(...) 
      : data;
  }
}

```

Key customization points include:

- **type**: Maps to the Management API client property (e.g., `clients`, `rules`)
- **identifiers**: Unique lookup keys (e.g., `['client_id', 'name']`)
- **objectFields**: Nested objects requiring special null-handling for deletions
- **stripCreateFields/UpdateFields**: Read-only fields to exclude from API payloads

## Summary

- **Standardized Pipeline**: All handlers inherit `validate()`, `calcChanges()`, and `processChanges()` from `APIHandler`, ensuring consistent deployment semantics across all resource types.
- **Four-Bucket Diffing**: The `calculateChanges()` function categorizes operations into `create`, `update`, `del`, and `conflicts` based on identifier matching against existing tenant state.
- **Resilient Execution**: `processChanges()` implements exponential backoff retry logic for all Management API calls, respecting rate limits via `Retry-After` headers.
- **Resource Customization**: Concrete handlers only specify `type`, `identifiers`, and `getType()` implementation, while reusing the core validation and processing infrastructure.
- **Safety First**: Validation occurs before API calls, and deletions require explicit `AUTH0_ALLOW_DELETE` configuration to prevent accidental data loss.

## Frequently Asked Questions

### How does Auth0 Deploy CLI determine which resources to create versus update?

The `calcChanges()` method compares desired assets against existing tenant state using configurable `identifiers` (typically `id` and `name`). If a desired asset matches an existing resource by identifier, it enters the `update` bucket; otherwise, it enters `create`. Assets existing in the tenant but absent from the desired configuration populate the `del` bucket when deletion is enabled.

### What happens when AUTH0_ALLOW_DELETE is disabled?

When `AUTH0_ALLOW_DELETE` is false or undefined, the `del` bucket returned by `calculateChanges()` remains empty regardless of tenant state. The `processChanges()` method skips deletion logic entirely, preserving orphaned resources in the Auth0 tenant even when they are removed from the local configuration files.

### Can handlers customize the retry behavior for API calls?

While the exponential backoff parameters are standardized in [`src/tools/auth0/handlers/default.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/auth0/handlers/default.ts), individual handlers inherit the `processChanges()` method which applies `retryWithExponentialBackoff` uniformly to all `create`, `update`, and `delete` operations. The retry logic respects the `Retry-After` header for 429 responses and implements jitter to distribute load across retry attempts.

### Where is the handler registry defined and how are handlers instantiated?

All handlers are exported from [`src/tools/auth0/handlers/index.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/auth0/handlers/index.ts) and instantiated by the Deploy orchestrator in [`src/tools/deploy.ts`](https://github.com/auth0/auth0-deploy-cli/blob/main/src/tools/deploy.ts). The orchestrator iterates through the registry, calling `load()`, `validate()`, `calcChanges()`, and `processChanges()` sequentially for each resource type defined in the tenant configuration.