> ## Documentation Index
> Fetch the complete documentation index at: https://chorus.pixelsprout.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Chorus Actions

> Master Chorus Actions - the powerful RPC-style system for handling multi-table write operations with validation, offline support, and optimistic updates.

# Chorus Actions

**Chorus Actions** are the core of Laravel Chorus's write operations system. They provide a clean, powerful way to handle complex multi-table operations with built-in validation, offline support, and real-time synchronization.

## Core Concepts

### What is a Chorus Action?

A Chorus Action is a server-side class that:

* **Handles multiple operations** atomically across different tables
* **Validates data** using operation-specific validation rules
* **Supports offline writes** automatically with queue and sync
* **Enables optimistic updates** for instant UI feedback
* **Generates TypeScript functions** for type-safe client usage

### Key Features

* **Multi-table operations** - Create, update, delete across multiple tables in one action
* **Automatic offline support** - No configuration needed
* **Operation validation** - Table.operation-specific validation rules
* **Type generation** - Auto-generated TypeScript functions
* **Single operation shorthand** - Simplified API for simple actions
* **Optimistic updates** - Instant UI feedback

## Creating Chorus Actions

### Generate with Artisan

```bash theme={null}
php artisan chorus:make-action CreateMessageWithActivityAction
```

This creates a new action in `app/Actions/ChorusActions/`.

### Basic Structure

```php theme={null}
<?php

namespace App\Actions\ChorusActions;

use App\Models\Message;
use App\Models\User;
use Illuminate\Http\Request;
use Pixelsprout\LaravelChorus\Support\ChorusAction;

final class CreateMessageWithActivityAction extends ChorusAction
{
    public function rules(): array
    {
        return [
            'messages.create' => [
                'body' => 'required|string|max:1000',
                'platform_id' => 'required|uuid|exists:platforms,id',
            ],
            'users.update' => [
                'id' => 'required|uuid|exists:users,id',
                'last_activity_at' => 'required|date',
            ],
        ];
    }

    public function handle(Request $request): void
    {
        $user = auth()->user();

        if (!$user) {
            throw new \Exception('User must be authenticated');
        }

        // Get create operations for messages
        $messageCreateOperations = $this->getOperations('messages', 'create');
        
        foreach ($messageCreateOperations as $messageData) {
            Message::create([
                'id' => $messageData['id'] ?? Str::uuid(),
                'body' => $messageData['body'],
                'platform_id' => $messageData['platform_id'],
                'user_id' => $user->id,
                'tenant_id' => $user->tenant_id,
            ]);
        }

        // Update user activity
        User::where('id', $user->id)->update([
            'last_activity_at' => now(),
        ]);
    }
}
```

## Validation System

### Operation-Specific Rules

The `rules()` method uses a `table.operation` format for precise validation:

```php theme={null}
public function rules(): array
{
    return [
        'messages.create' => [
            'body' => 'required|string|max:1000',
            'platform_id' => 'required|uuid|exists:platforms,id',
        ],
        'messages.update' => [
            'id' => 'required|uuid|exists:messages,id',
            'body' => 'required|string|max:1000',
        ],
        'users.update' => [
            'id' => 'required|uuid|exists:users,id',
            'last_activity_at' => 'required|date',
        ],
    ];
}
```

### Available Operations

* **`create`** - Insert new records
* **`update`** - Modify existing records
* **`delete`** - Remove records

## Automatic UUID Generation

Chorus automatically generates UUIDs for create operations when the `id` field is missing:

```typescript theme={null}
// UUID automatically generated
create('messages', {
    body: "Hello world",
    user_id: 123,
});

// You can still provide your own ID if needed
create('messages', {
    id: "custom-id-123",
    body: "Hello world", 
    user_id: 123,
});
```

## Handling Operation Data

Chorus Actions use the `getOperations()` method to retrieve operation data from the request:

```php theme={null}
public function handle(Request $request): void
{
    $user = auth()->user();
    
    // Get create operations for messages
    $messageCreateOperations = $this->getOperations('messages', 'create');
    
    foreach ($messageCreateOperations as $messageData) {
        Message::create([
            'id' => $messageData['id'] ?? Str::uuid(),
            'body' => $messageData['body'],
            'user_id' => $user->id,
        ]);
    }

    // Get update operations for users
    $userUpdateOperations = $this->getOperations('users', 'update');
    
    foreach ($userUpdateOperations as $userData) {
        User::where('id', $userData['id'])->update($userData);
    }

    // Get delete operations for comments
    $commentDeleteOperations = $this->getOperations('comments', 'delete');
    
    foreach ($commentDeleteOperations as $commentData) {
        Comment::where('id', $commentData['id'])->delete();
    }
}
```

### The getOperations() Method

The `getOperations(string $table, string $operation)` method returns an array of operation data:

```php theme={null}
// Returns array of data for creating messages
$creates = $this->getOperations('messages', 'create');

// Returns array of data for updating users  
$updates = $this->getOperations('users', 'update');

// Returns array of data for deleting posts
$deletes = $this->getOperations('posts', 'delete');
```

### Handling Empty Operations

Always check if operations exist before processing:

```php theme={null}
public function handle(Request $request): void
{
    $messageCreateOperations = $this->getOperations('messages', 'create');
    
    if (empty($messageCreateOperations)) {
        throw new \Exception('No message data found in request');
    }
    
    foreach ($messageCreateOperations as $messageData) {
        // Process each message...
    }
}
```

### Authorization and Validation

Validate data and check permissions within your operations:

```php theme={null}
public function handle(Request $request): void
{
    $user = auth()->user();
    
    $messageUpdateOperations = $this->getOperations('messages', 'update');
    
    foreach ($messageUpdateOperations as $messageData) {
        // Verify the message belongs to the user's tenant
        $message = Message::where('id', $messageData['id'])
            ->where('tenant_id', $user->tenant_id)
            ->first();

        if (!$message) {
            throw new \Exception('Message not found or unauthorized');
        }

        // Update the message
        $message->update([
            'body' => $messageData['body'],
        ]);
    }
}
```

## Single Operation Shorthand

For actions with only **one operation rule**, clients can use simplified data format:

### Server-Side (Single Operation)

```php theme={null}
final class SimpleCreateMessageAction extends ChorusAction
{
    public function rules(): array
    {
        return [
            'messages.create' => [
                'body' => 'required|string|max:1000',
                'platform_id' => 'required|uuid|exists:platforms,id',
            ],
        ];
    }

    public function handle(Request $request): void
    {
        // Get the message create operations
        $messageCreateOperations = $this->getOperations('messages', 'create');
        
        foreach ($messageCreateOperations as $messageData) {
            Message::create([
                'id' => $messageData['id'] ?? Str::uuid(),
                'body' => $messageData['body'],
                'platform_id' => $messageData['platform_id'],
                'user_id' => auth()->id(),
            ]);
        }
    }
}
```

### Client-Side Usage

```typescript theme={null}
// Full format (always works) - UUID auto-generated
await createMessageAction(({ create }) => {
    create('messages', {
        body: "Hello world",
        platform_id: "abc-123"
    });
});

// Shorthand format (single operation only) - UUID auto-generated
await createMessageAction({
    body: "Hello world",
    platform_id: "abc-123"
});
```

## Route Registration

**Important:** Chorus Actions must be registered as routes to be accessible from the client-side. Add your action routes to `routes/web.php` with proper authentication:

### Basic Route Registration

```php theme={null}
// routes/web.php
use App\Actions\ChorusActions\CreateMessageAction;
use App\Actions\ChorusActions\UpdateMessageAction;
use App\Actions\ChorusActions\DeleteMessageAction;

// Chorus Actions - must be authenticated
Route::middleware(['auth'])->group(function () {
    Route::post('/api/actions/create-message', CreateMessageAction::class)->name('chorus.create-message');
    Route::post('/api/actions/update-message', UpdateMessageAction::class)->name('chorus.update-message');
    Route::post('/api/actions/delete-message', DeleteMessageAction::class)->name('chorus.delete-message');
});
```

### Route Naming Convention

The route path should match the action name used in the generated TypeScript functions:

* **Action Class**: `CreateMessageAction`
* **Route Path**: `/api/actions/create-message`
* **Generated Function**: `createMessageAction()`
* **Route Name**: `chorus.create-message` (optional but recommended)

### Authentication & Authorization

Always protect Chorus Action routes with appropriate middleware:

```php theme={null}
// Basic authentication
Route::middleware(['auth'])->group(function () {
    Route::post('/api/actions/create-user', CreateUserAction::class);
});

// Additional middleware for admin actions
Route::middleware(['auth', 'admin'])->group(function () {
    Route::post('/api/actions/delete-user', DeleteUserAction::class);
});

// API-specific middleware
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
    Route::post('/api/actions/bulk-import', BulkImportAction::class);
});
```

### Multi-Tenancy Support

For multi-tenant applications, you can add tenant-specific middleware:

```php theme={null}
Route::middleware(['auth', 'tenant'])->group(function () {
    Route::post('/api/actions/create-message', CreateMessageAction::class);
    Route::post('/api/actions/update-message', UpdateMessageAction::class);
});
```

## Client-Side Integration

### Generated Functions

Chorus automatically generates TypeScript functions for each action:

```typescript theme={null}
// Auto-generated in resources/js/_generated/chorus-actions.ts
export async function createMessageWithActivityAction(
  callback: ({ create, update, remove }: ChorusActionMethods) => void
): Promise<ChorusActionResponse>;
```

### React Example

```tsx theme={null}
import { createMessageWithActivityAction } from '@/_generated/chorus-actions';

export default function CreateMessageForm() {
    const handleSubmit = async (formData) => {
        const result = await createMessageWithActivityAction(({ create, update }) => {
            // UUID automatically generated for create operations
            create('messages', {
                body: formData.message,
                platform_id: formData.platformId,
                user_id: user.id,
                tenant_id: user.tenant_id,
            });
            
            update('users', {
                id: user.id,
                last_activity_at: new Date().toISOString(),
            });
        });
        
        if (result.success) {
            console.log('Message created successfully!');
        }
    };
}
```

### Vue Example

```vue theme={null}
<script setup>
import { createMessageWithActivityAction } from '@/_generated/chorus-actions';

const handleSubmit = async (formData) => {
    const result = await createMessageWithActivityAction(({ create, update }) => {
        // UUID automatically generated for create operations
        create('messages', {
            body: formData.message,
            platform_id: formData.platformId,
            user_id: user.id,
        });
        
        update('users', {
            id: user.id,
            last_activity_at: new Date().toISOString(),
        });
    });
};
</script>
```

## Advanced Patterns

### Authorization

```php theme={null}
public function handle(Request $request): void
{
    // Check permissions before operations
    if (!auth()->user()->can('create', Message::class)) {
        throw new UnauthorizedException('Cannot create messages');
    }
    
    if (!$this->canUpdateUser(auth()->id())) {
        throw new UnauthorizedException('Cannot update user data');
    }
    
    // Proceed with operations...
    $messageCreateOperations = $this->getOperations('messages', 'create');
    
    foreach ($messageCreateOperations as $messageData) {
        Message::create($messageData);
    }
}
```

### Database Transactions

```php theme={null}
public function handle(Request $request): void
{
    DB::transaction(function () use ($request) {
        // All operations in a single transaction
        $messageCreateOperations = $this->getOperations('messages', 'create');
        foreach ($messageCreateOperations as $messageData) {
            Message::create($messageData);
        }
        
        $userUpdateOperations = $this->getOperations('users', 'update');
        foreach ($userUpdateOperations as $userData) {
            User::where('id', $userData['id'])->update($userData);
        }
        
        $platformUpdateOperations = $this->getOperations('platforms', 'update');
        foreach ($platformUpdateOperations as $platformData) {
            Platform::where('id', $platformData['id'])->update($platformData);
        }
    });
}
```

### Complex Business Logic

```php theme={null}
public function handle(Request $request): void
{
    $user = auth()->user();
    
    // Get message create operations
    $messageCreateOperations = $this->getOperations('messages', 'create');
    
    foreach ($messageCreateOperations as $messageData) {
        // Create the message
        $message = Message::create([
            'id' => $messageData['id'] ?? Str::uuid(),
            'body' => $messageData['body'],
            'user_id' => $user->id,
            'platform_id' => $messageData['platform_id'],
        ]);
        
        // Update user stats based on message content
        $wordCount = str_word_count($messageData['body']);
        User::where('id', $user->id)->update([
            'total_words' => DB::raw("total_words + {$wordCount}"),
            'message_count' => DB::raw('message_count + 1'),
            'last_activity_at' => now(),
        ]);
        
        // Award achievements
        $updatedUser = User::find($user->id);
        if ($updatedUser->message_count >= 100) {
            UserAchievement::create([
                'user_id' => $user->id,
                'achievement_id' => 'prolific_writer',
                'earned_at' => now(),
            ]);
        }
    }
}
```

## Best Practices

### 1. Name Actions Descriptively

```php theme={null}
// Good
CreateMessageWithActivityAction
UpdatePostAndNotifyFollowersAction
DeleteUserAndCleanupDataAction

// Avoid
MessageAction
PostAction
UserAction
```

### 2. Keep Actions Focused

Each action should represent a single business operation, even if it touches multiple tables.

### 3. Use Transactions for Consistency

Wrap related operations in database transactions to ensure data consistency.

### 4. Validate Everything

Use comprehensive validation rules for each operation to prevent invalid data.

### 5. Handle Authorization

Always check permissions before performing operations.

## Next Steps

<CardGroup cols={2}>
  <Card title="Write Path" href="/concepts/write-path" icon="pen">
    Understand the complete write path flow
  </Card>

  <Card title="React Integration" href="/integrations/react" icon="react">
    Use Chorus Actions in React components
  </Card>

  <Card title="Vue Integration" href="/integrations/vue" icon="vuejs">
    Use Chorus Actions in Vue components
  </Card>

  <Card title="Commands" href="/commands" icon="terminal">
    Generate and manage Chorus Actions with Artisan
  </Card>
</CardGroup>

***

Chorus Actions provide a powerful, flexible system for handling complex write operations while maintaining real-time synchronization and offline support. The combination of multi-table operations, automatic validation, and type-safe client integration makes building sophisticated real-time applications straightforward and maintainable.
