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

# Setup Guide

> Step-by-step guide to configure custom domains for your multi-tenancy application

## Prerequisites

Before setting up custom domains, ensure you have:

1. A **Laravel Forge** account with an active subscription
2. A **DigitalOcean** account with API access
3. A server configured in Laravel Forge
4. Queue workers running on your server
5. Laravel Scheduler configured and running

## Step 1: Configure Laravel Forge

### 1.1 Create a Forge API Token

1. Log in to [Laravel Forge](https://forge.laravel.com)
2. Navigate to **Account** → **API** → **Create New Token**
3. Give your token a name (e.g., "Custom Domains API")
4. Copy the generated token (you won't be able to see it again)

### 1.2 Get Your Server ID

1. In Laravel Forge, navigate to your server
2. Look at the URL in your browser: `https://forge.laravel.com/servers/{SERVER_ID}`
3. Copy the `SERVER_ID` from the URL

### 1.3 Get Your Nginx Template ID

1. In your Forge server, go to **Nginx Templates**
2. Create a new template or use an existing one suitable for your application
3. The template ID will be in the URL or template details
4. Common default template ID is `14570` (you can use this initially)

## Step 2: Configure DigitalOcean

### 2.1 Create a DigitalOcean API Token

1. Log in to [DigitalOcean](https://cloud.digitalocean.com)
2. Navigate to **API** → **Tokens/Keys** → **Generate New Token**
3. Give your token a name (e.g., "DNS Validation")
4. Set the scopes to **Read** and **Write**
5. Copy the generated token

<Warning>
  The DigitalOcean token is used for DNS validation during SSL certificate provisioning. Keep it secure.
</Warning>

## Step 3: Configure Environment Variables

Add the following variables to your `.env` file:

```bash .env theme={null}
# Laravel Forge Configuration
FORGE_API_KEY=your_forge_api_token_here
FORGE_SERVER_ID=your_forge_server_id
FORGE_NGINX_TEMPLATE=14570
FORGE_DIGITALOCEAN_TOKEN=your_digitalocean_api_token
```

<Tip>
  The `FORGE_NGINX_TEMPLATE` can be left as `14570` (default PHP template) or changed to your custom template ID.
</Tip>

## Step 4: Run Database Migration

The custom domains feature requires a new `domains` table. Run the migration:

```bash theme={null}
php artisan migrate
```

This creates the `domains` table with the following structure:

```php theme={null}
Schema::create('domains', function (Blueprint $table) {
    $table->id();
    $table->foreignId('team_id')->constrained('teams')->cascadeOnDelete();
    $table->string('domain');
    $table->boolean('dns_verified')->default(false);
    $table->string('forge_site_id')->nullable();
    $table->string('forge_certificate_id')->nullable();
    $table->boolean('ssl_installed')->default(false);
    $table->boolean('ssl_active')->default(false);
    $table->datetime('ssl_installed_at')->nullable();
    $table->integer('dns_verification_attempts')->default(0);
    $table->jsonb('domain_installation_history')->nullable();
    $table->timestamps();
});
```

## Step 5: Configure Queue Workers

Custom domains rely heavily on background jobs. Ensure your queue worker is running:

### For Development

```bash theme={null}
php artisan queue:work --tries=3
```

### For Production (with Supervisor)

Create a Supervisor configuration file at `/etc/supervisor/conf.d/laravel-worker.conf`:

```ini theme={null}
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/your/application/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=forge
numprocs=2
redirect_stderr=true
stdout_logfile=/path/to/your/application/storage/logs/worker.log
stopwaitsecs=3600
```

Then reload Supervisor:

```bash theme={null}
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
```

<Tip>
  Laravel Forge can automatically configure Supervisor for you. Go to your site's **Queue** section and enable the queue worker.
</Tip>

## Step 6: Configure Laravel Scheduler

The scheduler runs the `app:check-custom-domain-setup` command periodically to monitor and complete pending domain setups.

### For Development

Run the scheduler manually:

```bash theme={null}
php artisan schedule:work
```

### For Production

Add this cron entry (Forge does this automatically):

```bash theme={null}
* * * * * cd /path/to/your/application && php artisan schedule:run >> /dev/null 2>&1
```

### Schedule Configuration

Add to your `routes/console.php` or `app/Console/Kernel.php`:

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('app:check-custom-domain-setup')
    ->everyFiveMinutes()
    ->withoutOverlapping()
    ->runInBackground();
```

## Step 7: Test the Configuration

Create a test to verify your Forge connection:

```bash theme={null}
php artisan tinker
```

```php theme={null}
$forge = app(\App\Services\ForgeService::class);
$sites = $forge->sites();
dd($sites);
```

If you see a list of sites, your configuration is working correctly.

## Step 8: Configure Your Main Server

### 8.1 Wildcard SSL Certificate (Optional)

If you want to support subdomains of your main domain (e.g., `*.yourdomain.com`), configure a wildcard certificate:

1. In Laravel Forge, go to your main site
2. Navigate to **SSL** → **LetsEncrypt**
3. Request a wildcard certificate

### 8.2 Nginx Configuration

Ensure your main site's Nginx configuration can handle custom domains. The default configuration should work, but you may need to adjust based on your needs.

## Step 9: Enable Custom Domains in Team Settings

### 9.1 Add Domain Field to Team Resource

In your Filament Team resource (`app/Filament/Resources/TeamResource.php`), add a domain field:

```php theme={null}
use Filament\Forms;

public static function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\TextInput::make('name')
                ->required()
                ->maxLength(255),

            Forms\Components\Textarea::make('description')
                ->rows(3),

            Forms\Components\TextInput::make('domain')
                ->label('Custom Domain')
                ->placeholder('app.yourdomain.com')
                ->helperText('Enter your custom domain (e.g., portal.company.com)')
                ->maxLength(255)
                ->rule('regex:/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i'),

            Forms\Components\Placeholder::make('domain_status')
                ->label('Domain Status')
                ->content(fn ($record) => match(true) {
                    !$record || !$record->domain => 'No domain configured',
                    $record->domain_verified => '✅ Verified and Active',
                    default => '⏳ Pending verification'
                }),
        ]);
}
```

### 9.2 Add to Table Columns (Optional)

```php theme={null}
use Filament\Tables;

public static function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('name'),
            Tables\Columns\TextColumn::make('domain')
                ->label('Custom Domain'),
            Tables\Columns\IconColumn::make('domain_verified')
                ->label('Verified')
                ->boolean(),
        ]);
}
```

## Step 10: Create Team Observer

The team observer triggers domain verification when a domain is added or updated. It should already be in place at `app/Observers/TeamObserver.php`:

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

namespace App\Observers;

use App\Jobs\VerifyDomainDnsRecordsJob;
use App\Models\Team;

class TeamObserver
{
    public function updated(Team $team): void
    {
        if ($team->isDirty('domain') && $team->domain) {
            VerifyDomainDnsRecordsJob::dispatch($team, $team->domain);
        }
    }
}
```

Ensure the observer is registered on your Team model:

```php theme={null}
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use App\Observers\TeamObserver;

#[ObservedBy([TeamObserver::class])]
class Team extends Model
{
    // ...
}
```

## Verification

After completing the setup:

1. Log in to your Filament admin panel
2. Navigate to **Team Settings** (or Teams management)
3. Add a custom domain to a test team
4. Monitor the logs: `tail -f storage/logs/laravel.log`
5. Watch the queue worker process the jobs
6. Check the domain record in the database:

```bash theme={null}
php artisan tinker
```

```php theme={null}
\App\Models\Domain::latest()->first();
```

## Next Steps

* [DNS Configuration](/multi-tenancy/custom-domains/dns-configuration) - Configure DNS records for custom domains
* [SSL Certificates](/multi-tenancy/custom-domains/ssl-certificates) - Understand SSL certificate automation
* [Troubleshooting](/multi-tenancy/custom-domains/troubleshooting) - Common issues and solutions

## Common Issues

### Queue Jobs Not Running

**Problem**: Domain verification is stuck in pending.

**Solution**: Ensure your queue worker is running:

```bash theme={null}
php artisan queue:work
```

### Forge API Errors

**Problem**: Jobs fail with Forge API errors.

**Solution**: Verify your `FORGE_API_KEY` and `FORGE_SERVER_ID` are correct.

### DNS Verification Fails

**Problem**: DNS verification keeps failing.

**Solution**: Ensure DNS records are properly configured (see DNS Configuration guide).

### SSL Certificate Issues

**Problem**: SSL certificates fail to provision.

**Solution**: Verify your `FORGE_DIGITALOCEAN_TOKEN` is valid and has proper permissions.
