artisan-build/forge-client

A Saloon-based client for Laravel Forge (2025+ Version)
1,488 1
Install
composer require artisan-build/forge-client
Latest Version:v1.1.0
PHP:^8.3
License:MIT
Last Updated:Sep 28, 2026
Links: GitHub  ·  Packagist
Maintainer: edgrosvenor

Laravel Forge Client

A comprehensive, production-ready PHP SDK for the Laravel Forge API. Built with Saloon, this package provides a clean, strongly-typed interface for managing your Laravel Forge infrastructure programmatically.

[!WARNING] This package is currently under active development. Once a 0.* version has been tagged, we strongly recommend locking your application to a specific working version because we might make breaking changes even in patch releases until we've tagged 1.0.

Features

  • 🚀 Complete API Coverage - All 132+ Laravel Forge API endpoints
  • 🎯 Strongly Typed - PHP 8.3+ enums for all API values (server types, PHP versions, database types, etc.)
  • 🛠️ Atomic Artisan Commands - Individual commands for each operation (create, list, destroy, etc.)
  • 🔒 Production Ready - Confirmation prompts for destructive operations with --dangerously-skip-confirmation flag
  • 📝 Comprehensive Logging - Configurable logging for all API operations and command executions
  • ✅ Laravel & Laravel Zero Compatible - Minimal dependencies for maximum portability
  • 🧪 Fully Tested - Comprehensive test suite with mocked API responses
  • 📚 Excellent Documentation - Detailed examples for every resource and command

Table of Contents

Installation

Install the package via Composer:

composer require artisan-build/forge-client

Publish Configuration (Optional)

The package works out of the box, but you can publish the configuration file to customize settings:

php artisan vendor:publish --tag="forge-client-config"

This creates config/forge-client.php where you can configure API settings, logging, retry behavior, and defaults.

Configuration

Environment Variables

Set your Forge API token in your .env file:

FORGE_API_TOKEN=your-forge-api-token-here

You can generate an API token from your Forge account settings.

Optional Configuration

# Default organization (slug or ID) - optional
FORGE_ORGANIZATION=my-organization

# Default server (name or ID) - optional
FORGE_SERVER=my-server

# Server Creation Defaults - optional
FORGE_PHP_VERSION=php84
FORGE_DATABASE=mysql8

# API Configuration
FORGE_API_URL=https://forge.laravel.com/api/v1
FORGE_TIMEOUT=30

# Retry Configuration
FORGE_RETRY_TIMES=3
FORGE_RETRY_SLEEP=1000

# Logging Configuration
FORGE_LOG_CHANNEL=stack
FORGE_LOG_LEVEL=info

Default Organization & Server

Setting default organization and server in your configuration simplifies commands:

// config/forge-client.php
return [
    'default_organization' => env('FORGE_ORGANIZATION', 'my-org'),
    'default_server' => env('FORGE_SERVER', 'production-server'),
];

With defaults configured, you can run commands without specifying organization/server:

# Without defaults
php artisan forge:list-sites --server=my-server --organization=my-org

# With defaults configured
php artisan forge:list-sites

Argument Ordering Principle

All commands follow a consistent argument ordering pattern: most specific → least specific.

This means resource-specific identifiers always come before broader context arguments:

# Correct: deployment → site → server → organization
php artisan forge:get-deployment {deployment} --site={site} --server={server} --organization={org}

# Correct: site → server → organization
php artisan forge:get-site {site} --server={server} --organization={org}

# Correct: database-user → server → organization
php artisan forge:get-database-user {user} --server={server} --organization={org}

# Correct: server → organization
php artisan forge:get-server {server} --organization={org}

This pattern makes commands intuitive and predictable across all resources. When both organization and server can have config defaults, the more specific resource (server) is still specified before the broader context (organization).

Quick Start

Using the SDK in Your Code

use ArtisanBuild\ForgeClient\ForgeClient;

// Initialize the SDK
$forge = new ForgeClient();

// Authenticate with your API token
$forge->authenticate(config('forge-client.api_token'));

// List all organizations
$response = $forge->organizations()->organizationsIndex(
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null
);

$organizations = $response->json('data');

foreach ($organizations as $org) {
    echo "Organization: {$org['name']} (ID: {$org['id']})\n";
}

Using Artisan Commands

# List all organizations
php artisan forge:list-organizations

# Get a specific server (by ID or name)
php artisan forge:get-server 12345
php artisan forge:get-server production-server

# Create a new server (requires confirmation)
php artisan forge:create-server \
  --organization=my-org \
  --name=staging-server \
  --provider=ocean2 \
  --credential=123 \
  --size=456 \
  --region=nyc3

# Deploy a site (requires confirmation)
php artisan forge:deploy-site my-production-app

# Automated workflows (skip confirmation)
php artisan forge:deploy-site my-production-app --dangerously-skip-confirmation

Basic Usage

Organizations

Organizations are the top-level entity in Forge. All servers, sites, and resources belong to an organization.

List Organizations
use ArtisanBuild\ForgeClient\ForgeClient;

$forge = new ForgeClient();
$forge->authenticate(config('forge-client.api_token'));

$response = $forge->organizations()->organizationsIndex(
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null
);

$organizations = $response->json('data');

Artisan Command:

php artisan forge:list-organizations
Get Organization Details
$response = $forge->organizations()->organizationsShow(
    organization: 'my-organization' // slug or ID
);

$organization = $response->json('data');

Artisan Command:

php artisan forge:get-organization my-organization

Servers

Servers are the core infrastructure managed by Forge.

List Servers
$response = $forge->servers()->organizationsServersIndex(
    organization: 'my-org',
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null,
    filteripAddress: null,
    filtername: null,
    filterregion: null,
    filtersize: null,
    filterprovider: null,
    filterubuntuVersion: null,
    filterphpVersion: null,
    filterdatabaseType: null
);

$servers = $response->json('data');

Artisan Command:

# List all servers in organization
php artisan forge:list-servers --organization=my-org

# With filters
php artisan forge:list-servers \
  --organization=my-org \
  --filter-provider=ocean2 \
  --filter-region=nyc3
Get Server Details
$response = $forge->servers()->organizationsServersShow(
    organization: 'my-org',
    server: 12345 // server ID
);

$server = $response->json('data');

Artisan Command:

# By ID
php artisan forge:get-server 12345 --organization=my-org

# By name (automatically resolved to ID)
php artisan forge:get-server production-server --organization=my-org
Create a Server
use ArtisanBuild\ForgeClient\Enums\CloudProvider;
use ArtisanBuild\ForgeClient\Enums\ServerType;
use ArtisanBuild\ForgeClient\Enums\DatabaseType;
use ArtisanBuild\ForgeClient\Enums\PhpVersion;
use ArtisanBuild\ForgeClient\Enums\UbuntuVersion;

$response = $forge->servers()->organizationsServersStore(
    organization: 'my-org'
);

// Note: Body parameters are set on the request object
// Check the generated request class for available parameters

Artisan Command:

php artisan forge:create-server \
  --organization=my-org \
  --name=new-server \
  --provider=ocean2 \
  --credential=123 \
  --size=456 \
  --region=nyc3 \
  --type=app \
  --php-version=php84 \
  --database=mysql8 \
  --ubuntu-version=24.04

Available Enums:

  • CloudProvider: aws, ocean2, hetzner, vultr, akamai, laravel, custom
  • ServerType: app, web, loadbalancer, database, cache, worker, meilisearch
  • DatabaseType: mysql8, mysql, mariadb, postgres, none
  • PhpVersion: php74, php80, php81, php82, php83, php84, php85
  • UbuntuVersion: 2204, 2404
Reboot a Server
$response = $forge->servers()->organizationsServersActionsStore(
    organization: 'my-org',
    server: 12345
);

Artisan Command:

# Requires confirmation
php artisan forge:reboot-server 12345 --organization=my-org

# Skip confirmation for automation
php artisan forge:reboot-server 12345 \
  --organization=my-org \
  --dangerously-skip-confirmation
Delete a Server
$response = $forge->servers()->organizationsServersDestroy(
    organization: 'my-org',
    server: 12345
);

Artisan Command:

# Requires confirmation (destructive operation)
php artisan forge:destroy-server 12345 --organization=my-org

# For automation
php artisan forge:destroy-server 12345 \
  --organization=my-org \
  --dangerously-skip-confirmation

Sites

Sites represent the web applications hosted on your servers.

List Sites
$response = $forge->sites()->organizationsServersSitesIndex(
    organization: 'my-org',
    server: 12345,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null,
    filteraliases: null,
    filtername: null
);

$sites = $response->json('data');

Artisan Command:

php artisan forge:list-sites \
  --organization=my-org \
  --server=12345
Get Site Details
$response = $forge->sites()->organizationsServersSitesShow(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

$site = $response->json('data');

Artisan Command:

php artisan forge:get-site 67890 \
  --organization=my-org \
  --server=12345
Create a Site
$response = $forge->sites()->organizationsServersSitesStore(
    organization: 'my-org',
    server: 12345
);

// Body parameters set on request object

Artisan Command:

php artisan forge:create-site \
  --organization=my-org \
  --server=12345 \
  --domain=example.com \
  --project-type=php \
  --directory=/public
Update Site
$response = $forge->sites()->organizationsServersSitesUpdate(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

Artisan Command:

php artisan forge:update-site 67890 \
  --organization=my-org \
  --server=12345 \
  --directory=/public
Deploy Site
$response = $forge->sites()->organizationsServersSitesActionsStore(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

Artisan Command:

# Triggers deployment (requires confirmation)
php artisan forge:deploy-site 67890 \
  --organization=my-org \
  --server=12345
Enable/Disable Quick Deploy
// Enable quick deploy
$response = $forge->sites()->organizationsServersSitesQuickDeployStore(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

// Disable quick deploy
$response = $forge->sites()->organizationsServersSitesQuickDeployDestroy(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

Artisan Commands:

# Enable quick deploy
php artisan forge:enable-quick-deploy 67890 \
  --organization=my-org \
  --server=12345

# Disable quick deploy
php artisan forge:disable-quick-deploy 67890 \
  --organization=my-org \
  --server=12345
Delete Site
$response = $forge->sites()->organizationsServersSitesDestroy(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

Artisan Command:

# Requires confirmation (destructive)
php artisan forge:destroy-site 67890 \
  --organization=my-org \
  --server=12345

Deployments

Monitor and manage site deployments.

List Deployments
$response = $forge->deployments()->organizationsServersSitesDeploymentsIndex(
    organization: 'my-org',
    server: 12345,
    site: 67890,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null
);

$deployments = $response->json('data');

Artisan Command:

php artisan forge:list-deployments 67890 \
  --organization=my-org \
  --server=12345
Get Deployment Details
$response = $forge->deployments()->organizationsServersSitesDeploymentsShow(
    organization: 'my-org',
    server: 12345,
    site: 67890,
    deployment: 111213
);

$deployment = $response->json('data');

Artisan Command:

php artisan forge:get-deployment 111213 \
  --site=67890 \
  --organization=my-org \
  --server=12345
Trigger Deployment
$response = $forge->deployments()->organizationsServersSitesDeploymentsStore(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

Artisan Command:

# Requires confirmation
php artisan forge:trigger-deployment 67890 \
  --organization=my-org \
  --server=12345
Update Deployment Script
$response = $forge->deployments()->organizationsServersSitesDeploymentsScriptsUpdate(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

// Body contains the deployment script content

Artisan Command:

php artisan forge:update-deployment-script 67890 \
  --organization=my-org \
  --server=12345 \
  --script="cd /home/forge/site && git pull && php artisan migrate --force"

Databases

Manage MySQL/PostgreSQL databases and users.

List Database Schemas
$response = $forge->databases()->organizationsServersDatabasesIndex(
    organization: 'my-org',
    server: 12345,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null,
    filtername: null
);

$databases = $response->json('data');

Artisan Command:

php artisan forge:list-databases \
  --organization=my-org \
  --server=12345
Get Database Details
$response = $forge->databases()->organizationsServersDatabasesShow(
    organization: 'my-org',
    server: 12345,
    database: 44556
);

$database = $response->json('data');

Artisan Command:

php artisan forge:get-database 44556 \
  --organization=my-org \
  --server=12345
Create Database
$response = $forge->databases()->organizationsServersDatabasesStore(
    organization: 'my-org',
    server: 12345
);

// Body parameters include database name

Artisan Command:

php artisan forge:create-database \
  --organization=my-org \
  --server=12345 \
  --name=my_database
Delete Database
$response = $forge->databases()->organizationsServersDatabasesDestroy(
    organization: 'my-org',
    server: 12345,
    database: 44556
);

Artisan Command:

# Requires confirmation (destructive)
php artisan forge:destroy-database 44556 \
  --organization=my-org \
  --server=12345
List Database Users
$response = $forge->databases()->organizationsServersDatabaseUsersIndex(
    organization: 'my-org',
    server: 12345,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null,
    filtername: null
);

$users = $response->json('data');

Artisan Command:

php artisan forge:list-database-users \
  --organization=my-org \
  --server=12345
Create Database User
$response = $forge->databases()->organizationsServersDatabaseUsersStore(
    organization: 'my-org',
    server: 12345
);

// Body parameters include username, password, and databases

Artisan Command:

php artisan forge:create-database-user \
  --organization=my-org \
  --server=12345 \
  --name=app_user \
  --password=secure_password \
  --databases=my_database
Update Database User
$response = $forge->databases()->organizationsServersDatabaseUsersUpdate(
    organization: 'my-org',
    server: 12345,
    databaseUser: 77889
);

Artisan Command:

php artisan forge:update-database-user 77889 \
  --organization=my-org \
  --server=12345 \
  --databases=my_database,another_database
Delete Database User
$response = $forge->databases()->organizationsServersDatabaseUsersDestroy(
    organization: 'my-org',
    server: 12345,
    databaseUser: 77889
);

Artisan Command:

# Requires confirmation (destructive)
php artisan forge:destroy-database-user 77889 \
  --organization=my-org \
  --server=12345

Background Processes

Manage background processes (daemons) running on your servers.

List Background Processes
$response = $forge->backgroundProcesses()->organizationsServersBackgroundProcessesIndex(
    organization: 'my-org',
    server: 12345,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null,
    filtercommand: null,
    filterstatus: null
);

$processes = $response->json('data');

Artisan Command:

php artisan forge:list-background-processes \
  --organization=my-org \
  --server=12345
Get Background Process Details
$response = $forge->backgroundProcesses()->organizationsServersBackgroundProcessesShow(
    organization: 'my-org',
    server: 12345,
    backgroundProcess: 99887
);

$process = $response->json('data');

Artisan Command:

php artisan forge:get-background-process 99887 \
  --organization=my-org \
  --server=12345
Create Background Process
$response = $forge->backgroundProcesses()->organizationsServersBackgroundProcessesStore(
    organization: 'my-org',
    server: 12345
);

// Body includes command, user, directory

Artisan Command:

php artisan forge:create-background-process \
  --organization=my-org \
  --server=12345 \
  --command="php artisan horizon" \
  --user=forge \
  --directory=/home/forge/app
Update Background Process
$response = $forge->backgroundProcesses()->organizationsServersBackgroundProcessesUpdate(
    organization: 'my-org',
    server: 12345,
    backgroundProcess: 99887
);

Artisan Command:

php artisan forge:update-background-process 99887 \
  --organization=my-org \
  --server=12345 \
  --command="php artisan horizon"
Restart Background Process
$response = $forge->backgroundProcesses()->organizationsServersBackgroundProcessesActionsStore(
    organization: 'my-org',
    server: 12345,
    backgroundProcess: 99887
);

Artisan Command:

php artisan forge:restart-background-process 99887 \
  --organization=my-org \
  --server=12345
Delete Background Process
$response = $forge->backgroundProcesses()->organizationsServersBackgroundProcessesDestroy(
    organization: 'my-org',
    server: 12345,
    backgroundProcess: 99887
);

Artisan Command:

# Requires confirmation
php artisan forge:destroy-background-process 99887 \
  --organization=my-org \
  --server=12345

Firewall Rules

Manage server firewall rules for security.

List Firewall Rules
$response = $forge->firewallRules()->organizationsServersFirewallRulesIndex(
    organization: 'my-org',
    server: 12345,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null,
    filtername: null,
    filterport: null,
    filtertype: null
);

$rules = $response->json('data');

Artisan Command:

php artisan forge:list-firewall-rules \
  --organization=my-org \
  --server=12345
Get Firewall Rule Details
$response = $forge->firewallRules()->organizationsServersFirewallRulesShow(
    organization: 'my-org',
    server: 12345,
    firewallRule: 55443
);

$rule = $response->json('data');

Artisan Command:

php artisan forge:get-firewall-rule 55443 \
  --organization=my-org \
  --server=12345
Create Firewall Rule
use ArtisanBuild\ForgeClient\Enums\FirewallRuleType;

$response = $forge->firewallRules()->organizationsServersFirewallRulesStore(
    organization: 'my-org',
    server: 12345
);

// Body includes name, port, type, ip_address

Artisan Command:

php artisan forge:create-firewall-rule \
  --organization=my-org \
  --server=12345 \
  --name="Allow Redis" \
  --port=6379 \
  --type=allow \
  --ip-address=192.168.1.100

Firewall Rule Types:

  • allow - Allow traffic
  • deny - Deny traffic
Delete Firewall Rule
$response = $forge->firewallRules()->organizationsServersFirewallRulesDestroy(
    organization: 'my-org',
    server: 12345,
    firewallRule: 55443
);

Artisan Command:

# Requires confirmation
php artisan forge:destroy-firewall-rule 55443 \
  --organization=my-org \
  --server=12345

SSL Certificates

Manage SSL certificates for your sites.

List SSL Certificates
$response = $forge->sites()->organizationsServersSitesCertificatesIndex(
    organization: 'my-org',
    server: 12345,
    site: 67890,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null,
    filterdomain: null
);

$certificates = $response->json('data');

Artisan Command:

php artisan forge:list-ssl-certificates 67890 \
  --organization=my-org \
  --server=12345
Get SSL Certificate Details
$response = $forge->sites()->organizationsServersSitesCertificatesShow(
    organization: 'my-org',
    server: 12345,
    site: 67890,
    certificate: 22334
);

$certificate = $response->json('data');

Artisan Command:

php artisan forge:get-ssl-certificate 22334 \
  --site=67890 \
  --organization=my-org \
  --server=12345
Create SSL Certificate
use ArtisanBuild\ForgeClient\Enums\CertificateType;

$response = $forge->sites()->organizationsServersSitesCertificatesStore(
    organization: 'my-org',
    server: 12345,
    site: 67890
);

// Body includes domain, type (letsencrypt, existing, clone)

Artisan Command:

# Let's Encrypt certificate
php artisan forge:create-ssl-certificate 67890 \
  --organization=my-org \
  --server=12345 \
  --domain=example.com \
  --type=letsencrypt

# Existing certificate
php artisan forge:create-ssl-certificate 67890 \
  --organization=my-org \
  --server=12345 \
  --type=existing \
  --certificate="$(cat certificate.crt)" \
  --key="$(cat private.key)"
Activate SSL Certificate
$response = $forge->sites()->organizationsServersSitesCertificatesActionsStore(
    organization: 'my-org',
    server: 12345,
    site: 67890,
    certificate: 22334
);

Artisan Command:

php artisan forge:activate-ssl-certificate 22334 \
  --site=67890 \
  --organization=my-org \
  --server=12345
Delete SSL Certificate
$response = $forge->sites()->organizationsServersSitesCertificatesDestroy(
    organization: 'my-org',
    server: 12345,
    site: 67890,
    certificate: 22334
);

Artisan Command:

# Requires confirmation
php artisan forge:destroy-ssl-certificate 22334 \
  --site=67890 \
  --organization=my-org \
  --server=12345

Commands

The Commands resource represents one-off commands executed on servers (not to be confused with Artisan commands).

// List server commands
$response = $forge->commands()->organizationsServersCommandsIndex(
    organization: 'my-org',
    server: 12345,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null
);

Scheduled Jobs

Manage cron jobs on your servers.

// List scheduled jobs
$response = $forge->scheduledJobs()->organizationsServersScheduledJobsIndex(
    organization: 'my-org',
    server: 12345,
    sort: '-created_at',
    pagesize: 25,
    pagecursor: null
);

Other Resources

The SDK provides full access to all Forge API resources:

  • Integrations - Source control providers (GitHub, GitLab, Bitbucket)
  • Logs - Server and application logs
  • Monitors - Server monitoring configuration
  • Nginx - Nginx configuration templates
  • Providers - Cloud provider credentials
  • Recipes - Server provisioning recipes
  • Redirect Rules - Site redirect/rewrite rules
  • Roles - Organization role management
  • SSH Keys - Server SSH key management
  • Security Rules - Additional security configurations
  • Server Credentials - Server access credentials
  • Teams - Organization team management
  • User - Current user information

All resources follow the same pattern as shown above. Check the resource classes in src/Resource/ for available methods.

Artisan Commands Reference

All commands support the --dangerously-skip-confirmation flag to bypass confirmation prompts for automation.

Organization Commands

List Organizations
php artisan forge:list-organizations

Options:

  • None
Get Organization
php artisan forge:get-organization {organization}

Arguments:

  • organization - Organization slug or ID

Example:

php artisan forge:get-organization my-organization

Server Credential Commands

List Server Credentials
php artisan forge:list-server-credentials {organization?}

Arguments:

  • organization - Organization slug or ID (optional if FORGE_ORGANIZATION is set)

Options:

  • --pagesize= - Number of results per page
  • --pagecursor= - Cursor for pagination

Example:

# Using organization argument
php artisan forge:list-server-credentials my-organization

# Or using environment variable
export FORGE_ORGANIZATION="my-organization"
php artisan forge:list-server-credentials

This command lists all server credentials (cloud provider API keys) configured for your organization. You'll need the credential ID when creating servers.

Provider Commands

List Providers
php artisan forge:list-providers

Options:

  • --pagesize= - Number of results per page
  • --pagecursor= - Cursor for pagination

Example:

php artisan forge:list-providers
List Provider Regions
php artisan forge:list-provider-regions {provider}

Arguments:

  • provider - Provider ID

Options:

  • --pagesize= - Number of results per page
  • --pagecursor= - Cursor for pagination

Example:

# First, get the provider ID
php artisan forge:list-providers

# Then list regions for that provider
php artisan forge:list-provider-regions 1
List Provider Sizes
php artisan forge:list-provider-sizes {provider}

Arguments:

  • provider - Provider ID

Options:

  • --pagesize= - Number of results per page
  • --pagecursor= - Cursor for pagination

Example:

# First, get the provider ID
php artisan forge:list-providers

# Then list sizes for that provider
php artisan forge:list-provider-sizes 1

Server Commands

List Servers
php artisan forge:list-servers

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --filter-name= - Filter by server name
  • --filter-provider= - Filter by cloud provider
  • --filter-region= - Filter by region
  • --filter-size= - Filter by server size
  • --filter-ip-address= - Filter by IP address
  • --filter-ubuntu-version= - Filter by Ubuntu version
  • --filter-php-version= - Filter by PHP version
  • --filter-database-type= - Filter by database type

Example:

php artisan forge:list-servers \
  --organization=my-org \
  --filter-provider=ocean2 \
  --filter-region=nyc3
Get Server
php artisan forge:get-server {server}

Arguments:

  • server - Server ID or name

Options:

  • --organization= - Organization slug or ID (optional if default configured)

Example:

# By ID
php artisan forge:get-server 12345 --organization=my-org

# By name
php artisan forge:get-server production-server --organization=my-org
Create Server
php artisan forge:create-server

Options:

  • --organization= - Organization slug or ID (required unless set in config via FORGE_ORGANIZATION)
  • --name= - Server name (required)
  • --provider= - Cloud provider: laravel, ocean2, hetzner, vultr, akamai, aws, custom (required)
  • --credential= - Server credential ID (required - use forge:list-server-credentials to find IDs)
  • --size= - Server size ID (required - use forge:list-provider-sizes to find IDs)
  • --region= - Region code (required - use forge:list-provider-regions to find codes)
  • --type= - Server type: app, web, database, cache, worker, meilisearch, scheduler, loadbalancer (default: app)
  • --ubuntu-version= - Ubuntu version: 22.04, 24.04 (default: 24.04)
  • --php-version= - PHP version: php81, php82, php83, php84 (optional - defaults to FORGE_PHP_VERSION if set)
  • --database= - Database type: mysql8, postgres, mariadb, none (optional - defaults to FORGE_DATABASE if set)
  • --dangerously-skip-confirmation - Skip confirmation prompt (for automation)

Prerequisites: Before creating a server, you need to:

  1. Get a credential ID: php artisan forge:list-server-credentials --organization=my-org
  2. Get a provider ID: php artisan forge:list-providers
  3. Get a region code: php artisan forge:list-provider-regions {provider-id}
  4. Get a size ID: php artisan forge:list-provider-sizes {provider-id}

Example:

# Create a DigitalOcean server with explicit options
php artisan forge:create-server \
  --organization=my-org \
  --name=staging-server \
  --provider=ocean2 \
  --credential=123 \
  --region=nyc3 \
  --size=456 \
  --type=app \
  --php-version=php84 \
  --database=mysql8 \
  --ubuntu-version=24.04

# Create a Laravel-managed server using config defaults
# (Assuming FORGE_PHP_VERSION=php84 and FORGE_DATABASE=mysql8 in .env)
php artisan forge:create-server \
  --organization=my-org \
  --name=laravel-server \
  --provider=laravel \
  --credential=789 \
  --region=us-east \
  --size=1
Reboot Server
php artisan forge:reboot-server {server}

Arguments:

  • server - Server ID or name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

Example:

# With confirmation
php artisan forge:reboot-server production-server

# Skip confirmation
php artisan forge:reboot-server production-server --dangerously-skip-confirmation
Destroy Server
php artisan forge:destroy-server {server}

Arguments:

  • server - Server ID or name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

Example:

php artisan forge:destroy-server old-server --dangerously-skip-confirmation

Site Commands

List Sites
php artisan forge:list-sites

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --filter-name= - Filter by site name
  • --filter-aliases= - Filter by site aliases
Get Site
php artisan forge:get-site {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Create Site
php artisan forge:create-site

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --domain= - Domain name (required)
  • --project-type= - Project type: laravel, symfony, statamic, wordpress, phpmyadmin, php, next.js, nuxt.js, static-html, other, custom (optional)
  • --directory= - Web directory, e.g., /public (optional)

Example:

php artisan forge:create-site \
  --organization=my-org \
  --server=production-server \
  --domain=example.com \
  --project-type=php \
  --directory=/public
Update Site
php artisan forge:update-site {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --directory= - Update web directory
Deploy Site
php artisan forge:deploy-site {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

Example:

php artisan forge:deploy-site example.com --dangerously-skip-confirmation
Enable Quick Deploy
php artisan forge:enable-quick-deploy {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Disable Quick Deploy
php artisan forge:disable-quick-deploy {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Destroy Site
php artisan forge:destroy-site {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

Deployment Commands

List Deployments
php artisan forge:list-deployments {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Get Deployment
php artisan forge:get-deployment {deployment}

Arguments:

  • deployment - Deployment ID

Options:

  • --site= - Site ID or domain name (required)
  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Trigger Deployment
php artisan forge:trigger-deployment {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt
Update Deployment Script
php artisan forge:update-deployment-script {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --script= - Deployment script content (required)

Example:

php artisan forge:update-deployment-script example.com \
  --script="cd /home/forge/example.com && git pull origin main && composer install --no-dev && php artisan migrate --force"

Database Commands

List Databases
php artisan forge:list-databases

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --filter-name= - Filter by database name
Get Database
php artisan forge:get-database {database}

Arguments:

  • database - Database ID or name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Create Database
php artisan forge:create-database

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --name= - Database name (required)

Example:

php artisan forge:create-database \
  --organization=my-org \
  --server=production-server \
  --name=my_application_db
Destroy Database
php artisan forge:destroy-database {database}

Arguments:

  • database - Database ID or name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt
List Database Users
php artisan forge:list-database-users

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --filter-name= - Filter by username
Get Database User
php artisan forge:get-database-user {user}

Arguments:

  • user - Database user ID or username

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Create Database User
php artisan forge:create-database-user

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --name= - Username (required)
  • --password= - Password (required)
  • --databases= - Comma-separated list of database names to grant access (required)

Example:

php artisan forge:create-database-user \
  --organization=my-org \
  --server=production-server \
  --name=app_user \
  --password=secure_password_here \
  --databases=my_app_db,my_app_cache
Update Database User
php artisan forge:update-database-user {user}

Arguments:

  • user - Database user ID or username

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --databases= - Comma-separated list of database names to grant access (required)
Destroy Database User
php artisan forge:destroy-database-user {user}

Arguments:

  • user - Database user ID or username

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

Background Process Commands

List Background Processes
php artisan forge:list-background-processes

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --filter-command= - Filter by command
  • --filter-status= - Filter by status
Get Background Process
php artisan forge:get-background-process {process}

Arguments:

  • process - Background process ID

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Create Background Process
php artisan forge:create-background-process

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --command= - Command to run (required)
  • --user= - User to run as (optional, defaults to forge)
  • --directory= - Working directory (optional)

Example:

php artisan forge:create-background-process \
  --organization=my-org \
  --server=production-server \
  --command="php artisan horizon" \
  --user=forge \
  --directory=/home/forge/example.com
Update Background Process
php artisan forge:update-background-process {process}

Arguments:

  • process - Background process ID

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --command= - Updated command (required)
Restart Background Process
php artisan forge:restart-background-process {process}

Arguments:

  • process - Background process ID

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt
Destroy Background Process
php artisan forge:destroy-background-process {process}

Arguments:

  • process - Background process ID

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

Firewall Commands

List Firewall Rules
php artisan forge:list-firewall-rules

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --filter-name= - Filter by rule name
  • --filter-port= - Filter by port
  • --filter-type= - Filter by type (allow/deny)
Get Firewall Rule
php artisan forge:get-firewall-rule {rule}

Arguments:

  • rule - Firewall rule ID

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Create Firewall Rule
php artisan forge:create-firewall-rule

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --name= - Rule name (optional)
  • --port= - Port number (required)
  • --type= - Rule type: allow, deny (required)
  • --ip-address= - IP address to allow/deny (optional, defaults to 0.0.0.0/0)

Example:

php artisan forge:create-firewall-rule \
  --organization=my-org \
  --server=production-server \
  --name="Allow Redis from app server" \
  --port=6379 \
  --type=allow \
  --ip-address=192.168.1.100
Destroy Firewall Rule
php artisan forge:destroy-firewall-rule {rule}

Arguments:

  • rule - Firewall rule ID

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

SSL Certificate Commands

List SSL Certificates
php artisan forge:list-ssl-certificates {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --filter-domain= - Filter by domain
Get SSL Certificate
php artisan forge:get-ssl-certificate {certificate}

Arguments:

  • certificate - SSL certificate ID

Options:

  • --site= - Site ID or domain name (required)
  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
Create SSL Certificate
php artisan forge:create-ssl-certificate {site}

Arguments:

  • site - Site ID or domain name

Options:

  • --organization= - Organization slug or ID (required)
  • --server= - Server ID or name (required)
  • --domain= - Domain name (required)
  • --type= - Certificate type: letsencrypt, existing, clone (required)
  • --certificate= - Certificate content (required if type=existing)
  • --key= - Private key content (required if type=existing)

Example:

# Let's Encrypt
php artisan forge:create-ssl-certificate example.com \
  --organization=my-org \
  --server=production-server \
  --domain=example.com \
  --type=letsencrypt

# Existing certificate
php artisan forge:create-ssl-certificate example.com \
  --organization=my-org \
  --server=production-server \
  --type=existing \
  --certificate="$(cat /path/to/certificate.crt)" \
  --key="$(cat /path/to/private.key)"
Activate SSL Certificate
php artisan forge:activate-ssl-certificate {certificate}

Arguments:

  • certificate - SSL certificate ID

Options:

  • --site= - Site ID or domain name (required)
  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt
Destroy SSL Certificate
php artisan forge:destroy-ssl-certificate {certificate}

Arguments:

  • certificate - SSL certificate ID

Options:

  • --site= - Site ID or domain name (required)
  • --organization= - Organization slug or ID (optional if default configured)
  • --server= - Server ID or name (optional if default configured)
  • --dangerously-skip-confirmation - Skip confirmation prompt

Enums

The SDK provides strongly-typed enums for all Forge API values, ensuring type safety and preventing invalid API requests.

Available Enums

CloudProvider
use ArtisanBuild\ForgeClient\Enums\CloudProvider;

CloudProvider::AWS;      // 'aws'
CloudProvider::OCEAN2;   // 'ocean2' (DigitalOcean)
CloudProvider::HETZNER;  // 'hetzner'
CloudProvider::VULTR;    // 'vultr'
CloudProvider::AKAMAI;   // 'akamai' (Linode/Akamai)
CloudProvider::LARAVEL;  // 'laravel' (Laravel Cloud)
CloudProvider::CUSTOM;   // 'custom'

Helper Methods:

CloudProvider::OCEAN2->label();       // "DigitalOcean"
CloudProvider::OCEAN2->description(); // "DigitalOcean cloud hosting"
CloudProvider::isValid('ocean2');     // true
CloudProvider::options();             // Array of all options
ServerType
use ArtisanBuild\ForgeClient\Enums\ServerType;

ServerType::APP;          // 'app'
ServerType::WEB;          // 'web'
ServerType::LOADBALANCER; // 'loadbalancer'
ServerType::DATABASE;     // 'database'
ServerType::CACHE;        // 'cache'
ServerType::WORKER;       // 'worker'
ServerType::MEILISEARCH;  // 'meilisearch'
DatabaseType
use ArtisanBuild\ForgeClient\Enums\DatabaseType;

DatabaseType::MYSQL8;   // 'mysql8'
DatabaseType::MYSQL;    // 'mysql'
DatabaseType::MARIADB;  // 'mariadb'
DatabaseType::POSTGRES; // 'postgres'
DatabaseType::NONE;     // 'none'

Helper Methods:

DatabaseType::MYSQL8->label();              // "MySQL 8"
DatabaseType::MYSQL8->description();        // "MySQL 8.0"
DatabaseType::MYSQL8->isMySQL();           // true
DatabaseType::POSTGRES->isPostgreSQL();    // true
PhpVersion
use ArtisanBuild\ForgeClient\Enums\PhpVersion;

PhpVersion::PHP74; // 'php74'
PhpVersion::PHP80; // 'php80'
PhpVersion::PHP81; // 'php81'
PhpVersion::PHP82; // 'php82'
PhpVersion::PHP83; // 'php83'
PhpVersion::PHP84; // 'php84'
PhpVersion::PHP85; // 'php85'

Helper Methods:

PhpVersion::PHP84->label();              // "PHP 8.4"
PhpVersion::PHP84->version();            // "8.4"
PhpVersion::PHP84->isSupported();        // true
PhpVersion::latest();                    // PhpVersion::PHP85
PhpVersion::supported();                 // Array of supported versions
UbuntuVersion
use ArtisanBuild\ForgeClient\Enums\UbuntuVersion;

UbuntuVersion::UBUNTU_22_04; // '2204'
UbuntuVersion::UBUNTU_24_04; // '2404'

Helper Methods:

UbuntuVersion::UBUNTU_24_04->label();       // "Ubuntu 24.04"
UbuntuVersion::UBUNTU_24_04->version();     // "24.04"
UbuntuVersion::UBUNTU_24_04->isLTS();       // true
UbuntuVersion::latest();                    // UbuntuVersion::UBUNTU_24_04
FirewallRuleType
use ArtisanBuild\ForgeClient\Enums\FirewallRuleType;

FirewallRuleType::ALLOW; // 'allow'
FirewallRuleType::DENY;  // 'deny'
CertificateType
use ArtisanBuild\ForgeClient\Enums\CertificateType;

CertificateType::LETSENCRYPT; // 'letsencrypt'
CertificateType::EXISTING;    // 'existing'
CertificateType::CLONE;       // 'clone'
SiteType
use ArtisanBuild\ForgeClient\Enums\SiteType;

SiteType::LARAVEL;     // 'laravel'
SiteType::SYMFONY;     // 'symfony'
SiteType::STATAMIC;    // 'statamic'
SiteType::WORDPRESS;   // 'wordpress'
SiteType::PHPMYADMIN;  // 'phpmyadmin'
SiteType::PHP;         // 'php'
SiteType::NEXTJS;      // 'next.js'
SiteType::NUXTJS;      // 'nuxt.js'
SiteType::STATIC_HTML; // 'static-html'
SiteType::OTHER;       // 'other'
SiteType::CUSTOM;      // 'custom'
IntegrationType
use ArtisanBuild\ForgeClient\Enums\IntegrationType;

IntegrationType::GITHUB;    // 'github'
IntegrationType::GITLAB;    // 'gitlab'
IntegrationType::BITBUCKET; // 'bitbucket'
JobFrequency
use ArtisanBuild\ForgeClient\Enums\JobFrequency;

JobFrequency::MINUTELY; // 'minutely'
JobFrequency::HOURLY;   // 'hourly'
JobFrequency::NIGHTLY;  // 'nightly'
JobFrequency::WEEKLY;   // 'weekly'
JobFrequency::MONTHLY;  // 'monthly'
JobFrequency::CUSTOM;   // 'custom'
LogType
use ArtisanBuild\ForgeClient\Enums\LogType;

LogType::APP;    // 'app'
LogType::NGINX;  // 'nginx'
LogType::PHP;    // 'php'
LogType::MYSQL;  // 'mysql'
MonitorType
use ArtisanBuild\ForgeClient\Enums\MonitorType;

MonitorType::CPU;        // 'cpu'
MonitorType::MEMORY;     // 'memory'
MonitorType::DISK;       // 'disk'
MonitorType::CUSTOM;     // 'custom'

Using Enums

Enums provide type safety and validation:

use ArtisanBuild\ForgeClient\Enums\CloudProvider;
use ArtisanBuild\ForgeClient\Enums\PhpVersion;
use ArtisanBuild\ForgeClient\Enums\DatabaseType;

// In your code
$provider = CloudProvider::OCEAN2;
$phpVersion = PhpVersion::PHP84;
$database = DatabaseType::MYSQL8;

// Validation
if (CloudProvider::isValid('ocean2')) {
    // Valid provider
}

// Get all options for dropdowns
$providers = CloudProvider::options();
// Returns: ['ocean2' => 'DigitalOcean', 'akamai' => 'Akamai', ...]

// Get latest/recommended versions
$latestPhp = PhpVersion::latest(); // PHP 8.5
$supportedPhp = PhpVersion::supported(); // All supported versions

Error Handling

The SDK provides comprehensive exception handling with detailed error messages.

Exception Types

ForgeException

Base exception for all Forge Client errors.

use ArtisanBuild\ForgeClient\Exceptions\ForgeException;

try {
    $response = $forge->servers()->organizationsServersShow('my-org', 12345);
} catch (ForgeException $e) {
    echo "Forge Error: {$e->getMessage()}";
    echo "Context: " . json_encode($e->getContext());
}

Methods:

  • getMessage() - Error message
  • getContext() - Additional error context (array)
  • getCode() - Error code
ValidationException

Thrown when request parameters fail validation before the API call.

use ArtisanBuild\ForgeClient\Exceptions\ValidationException;

try {
    // Invalid PHP version
    $response = $forge->servers()->organizationsServersStore('my-org');
} catch (ValidationException $e) {
    echo "Validation Error: {$e->getMessage()}";
    echo "Failed Field: {$e->getField()}";
    echo "Invalid Value: {$e->getValue()}";
}

Methods:

  • getField() - Field that failed validation
  • getValue() - Invalid value provided
  • getExpected() - Expected value format/type
ApiException

Thrown when the Forge API returns an error response.

use ArtisanBuild\ForgeClient\Exceptions\ApiException;

try {
    $response = $forge->servers()->organizationsServersShow('my-org', 99999);
} catch (ApiException $e) {
    echo "API Error: {$e->getMessage()}";
    echo "Status Code: {$e->getStatusCode()}";
    echo "Response: " . json_encode($e->getResponseData());
}

Methods:

  • getStatusCode() - HTTP status code (404, 500, etc.)
  • getResponseData() - Full API response body
  • getErrorCode() - Forge-specific error code
AuthenticationException

Thrown when API authentication fails.

use ArtisanBuild\ForgeClient\Exceptions\AuthenticationException;

try {
    $forge->authenticate('invalid-token');
    $response = $forge->organizations()->organizationsIndex();
} catch (AuthenticationException $e) {
    echo "Authentication Error: {$e->getMessage()}";
    echo "Troubleshooting:\n";
    echo "1. Check your API token in .env\n";
    echo "2. Verify token is active in Forge dashboard\n";
    echo "3. Ensure token has required permissions\n";
}
RateLimitException

Thrown when Forge API rate limit is exceeded (60 requests per minute).

use ArtisanBuild\ForgeClient\Exceptions\RateLimitException;

try {
    $response = $forge->servers()->organizationsServersIndex('my-org');
} catch (RateLimitException $e) {
    echo "Rate Limit Exceeded: {$e->getMessage()}";
    echo "Retry After: {$e->getRetryAfter()} seconds";

    // Wait and retry
    sleep($e->getRetryAfter());
    $response = $forge->servers()->organizationsServersIndex('my-org');
}

Methods:

  • getRetryAfter() - Seconds until rate limit resets
  • getLimit() - Rate limit maximum (60)
  • getRemaining() - Remaining requests

Error Handling Patterns

Comprehensive Try-Catch
use ArtisanBuild\ForgeClient\Exceptions\{
    ValidationException,
    AuthenticationException,
    RateLimitException,
    ApiException,
    ForgeException
};

try {
    $response = $forge->servers()->organizationsServersShow('my-org', $serverId);
    $server = $response->json('data');
} catch (ValidationException $e) {
    // Handle validation errors (before API call)
    Log::error('Invalid parameters', [
        'field' => $e->getField(),
        'value' => $e->getValue(),
    ]);
} catch (AuthenticationException $e) {
    // Handle authentication failures
    Log::error('Authentication failed', ['message' => $e->getMessage()]);
    throw $e; // Re-throw for application-level handling
} catch (RateLimitException $e) {
    // Handle rate limiting
    Log::warning('Rate limit hit', ['retry_after' => $e->getRetryAfter()]);
    sleep($e->getRetryAfter());
    // Retry logic here
} catch (ApiException $e) {
    // Handle API errors (404, 500, etc.)
    if ($e->getStatusCode() === 404) {
        Log::warning('Server not found', ['server_id' => $serverId]);
    } else {
        Log::error('API error', [
            'status' => $e->getStatusCode(),
            'response' => $e->getResponseData(),
        ]);
    }
} catch (ForgeException $e) {
    // Catch-all for any other SDK errors
    Log::error('Forge Client error', ['message' => $e->getMessage()]);
}
Laravel Exception Handler

Add to app/Exceptions/Handler.php:

use ArtisanBuild\ForgeClient\Exceptions\RateLimitException;
use ArtisanBuild\ForgeClient\Exceptions\AuthenticationException;

public function register(): void
{
    $this->reportable(function (RateLimitException $e) {
        // Log rate limit hits for monitoring
        Log::warning('Forge rate limit exceeded', [
            'retry_after' => $e->getRetryAfter(),
        ]);
    });

    $this->reportable(function (AuthenticationException $e) {
        // Alert team about auth failures
        Log::critical('Forge authentication failed', [
            'message' => $e->getMessage(),
        ]);
    });
}

Troubleshooting

Common Issues

Authentication Failures

Problem: AuthenticationException: Invalid API token

Solutions:

  1. Verify your API token in .env:
    FORGE_API_TOKEN=your-actual-token
    
  2. Generate a new token at https://forge.laravel.com/user-profile/api
  3. Ensure the token has required permissions (full access recommended)
  4. Check the token wasn't revoked in Forge dashboard
Rate Limiting

Problem: RateLimitException: Too many requests

Details: Forge API limits requests to 60 per minute per API token.

Solutions:

  1. Implement request throttling in your application:

    use Illuminate\Support\Facades\Cache;
    
    $key = 'forge-api-requests';
    $requests = Cache::increment($key);
    
    if ($requests === 1) {
        Cache::put($key, 1, now()->addMinute());
    }
    
    if ($requests > 55) {
        sleep(60); // Wait for rate limit reset
    }
    
  2. Batch operations when possible

  3. Cache Forge data locally to reduce API calls

  4. Use webhooks for real-time updates instead of polling

Server/Site Not Found

Problem: ApiException: Server not found (404)

Solutions:

  1. Verify the server ID is correct
  2. Check you're using the correct organization
  3. Ensure the server wasn't deleted
  4. If using server name, ensure it's unique:
    # This might fail if multiple servers named "staging"
    php artisan forge:get-server staging
    
    # Use ID instead
    php artisan forge:get-server 12345
    
Command Failures

Problem: Artisan commands hang or fail

Solutions:

  1. Ensure defaults are configured if not passing arguments:

    FORGE_ORGANIZATION=my-org
    FORGE_SERVER=my-server
    
  2. Check command syntax:

    # Incorrect
    php artisan forge:create-server my-server
    
    # Correct
    php artisan forge:create-server \
      --name=my-server \
      --organization=my-org \
      --provider=ocean2 \
      --credential=123 \
      --size=456 \
      --region=nyc3
    
  3. Use --help to see required options:

    php artisan forge:create-server --help
    
Logging Issues

Problem: No logs appearing for Forge operations

Solutions:

  1. Check logging configuration:

    FORGE_LOG_CHANNEL=stack
    FORGE_LOG_LEVEL=info
    
  2. Ensure log channel exists in config/logging.php

  3. Verify log permissions:

    chmod -R 775 storage/logs
    
SSL Certificate Errors

Problem: Let's Encrypt certificate creation fails

Solutions:

  1. Verify domain DNS points to server
  2. Ensure site is accessible via HTTP first
  3. Check firewall allows ports 80 and 443
  4. Domain must be publicly accessible (no local/private IPs)
Network/Timeout Issues

Problem: Requests timing out

Solutions:

  1. Increase timeout in config:

    FORGE_TIMEOUT=60
    
  2. Check network connectivity:

    curl -I https://forge.laravel.com/api
    
  3. Verify no firewall blocking outbound HTTPS

Debug Mode

Enable detailed logging for troubleshooting:

// In a service provider or bootstrap file
use Illuminate\Support\Facades\Log;

if (config('app.debug')) {
    // Log all Forge API calls
    Event::listen(\Saloon\Events\SendingRequest::class, function ($event) {
        Log::debug('Forge API Request', [
            'method' => $event->request->getMethod(),
            'uri' => $event->request->getUri(),
        ]);
    });

    Event::listen(\Saloon\Events\ResponseReceived::class, function ($event) {
        Log::debug('Forge API Response', [
            'status' => $event->response->status(),
            'body' => $event->response->body(),
        ]);
    });
}

Getting Help

Contributing

This package is part of our internal toolkit and is optimized for our own purposes. We do not accept issues or PRs in this repository.

However, if you find this package useful and want to build upon it for your own needs, you're welcome to fork it!

License

The MIT License (MIT). Please see License File for more information.

Related Packages

doctrine/dbal

Powerful PHP database abstraction layer (DBAL) with many features for database s...

637,827,751 9,703
laravel/framework

The Laravel Framework.

589,172,971 34,950
laravel/tinker

Powerful REPL for the Laravel framework.

498,216,183 7,442
laravel/serializable-closure

Laravel Serializable Closure provides an easy and secure way to serialize closur...

413,412,504 609
nunomaduro/collision

Cli error handling for console/command-line PHP applications.

394,964,158 4,660

Version History

Version Released PHP Laravel License
v1.1.0 ^8.3 ^11.36|^12.0|^13.0 MIT
v1.0.2 ^11.36|^12.0|^13.0 MIT
v1.0.1 ^11.36|^12.0|^13.0 MIT
1.0.0 ^11.36|^12.0|^13.0 MIT