Laravel: Auto-Brand New Client Workspaces with AI-Extracted Visual Assets
Getting Started with Brand Kit Extraction
In the fast-paced world of web development and digital branding, efficiently onboarding new clients is paramount. Imagine a scenario where, upon signing a new client, you could instantly populate their new workspace with their established brand identity – logo, primary colors, and fonts. This isn't a distant future; it's achievable today with the help of AI-powered services and a robust framework like Laravel. This tutorial will guide you through integrating a powerful Brand Kit Extractor API into your Laravel application to automate this crucial step, saving valuable time and ensuring brand consistency from day one.
Accessing the Brand Kit Extractor API
Before we dive into the code, let's get you set up with the Brand Kit Extractor API. This service analyzes a given website URL and deterministically extracts brand assets like logos, color palettes, and font families.
To begin, you'll need an account.
Register or sign in at the provided portal. Once logged in, navigate to the API services section and locate the Brand Kit Extractor. You'll have several plan options: Free, Plus, and Pro. Choose the one that best suits your needs and complete the activation process.
After activating your chosen plan, head over to the official documentation:
Brand Kit Extractor Documentation. Within the documentation, find the "Service token" panel. Here, you will find your unique, service-scoped token. It's crucial to understand that regenerating this token will invalidate any previously issued tokens. Therefore, treat it with care and store it securely.
For this integration, we'll store this token in our Laravel application's environment configuration. The API supports authentication via a Bearer token, an X-API-Token header, or a token query parameter. We will opt for the X-API-Token header for this implementation.
Understanding the API Contract
The Brand Kit Extractor API operates on a simple, yet effective, contract. The core functionality is exposed via a POST request to the following endpoint:
https://ai.mihajlo.mk/api/brand-kit-extractor/v1/extract-brand-kit
The request body must be a JSON object containing a single key, url, which is the public URL of the website you want to analyze. For example:
{
"url": "https://example.com"
}
The API response is also a JSON object containing a wealth of brand information, including:
brand_name: The identified name of the brand.logos: A list of extracted logo URLs.colors: An array of dominant color hex codes.fonts: An array of identified font families.imagery: URLs of prominent imagery found on the site.social_profiles: Links to social media profiles detected.css_variables: CSS variables related to the brand's styling.
It is essential to validate each of these fields upon receiving the response to ensure data integrity before storing it in your application.
Laravel Integration Architecture
For this Laravel integration, we will adopt a clean architecture that separates concerns and promotes testability. The core components will include:
- Environment Configuration: Storing API credentials securely.
- Service Class: Encapsulating the API interaction logic. This class will handle making the HTTP request, setting headers, and managing timeouts.
- Controller/Job: Orchestrating the process. For this tutorial, we'll use a dedicated Artisan command to trigger the extraction, making it easily runnable on demand or via a scheduled task.
- Data Mapping: Transforming the API's raw JSON response into a structured domain object that your application can easily work with.
- Automated Tests: Ensuring the integration works as expected, even when the external API is unavailable or returns unexpected data.
We will leverage Laravel's built-in HTTP client for making external requests, which simplifies request management and response handling. For testing, Http::fake() will be invaluable.
Project Setup and Environment Configuration
First, ensure you have a Laravel project set up. If not, you can create one using Composer:
composer create-project laravel/laravel ai-brand-extractor
cd ai-brand-extractor
Next, we need to store our API token. Open your .env file and add the following line:
BRAND_KIT_API_TOKEN=YOUR_SERVICE_TOKEN_HERE
Replace YOUR_SERVICE_TOKEN_HERE with the actual token you copied from the documentation. It's good practice to also define the API base URL:
BRAND_KIT_API_URL=https://ai.mihajlo.mk/api/brand-kit-extractor/v1
After updating your .env file, clear your configuration cache to ensure the new variables are loaded:
php artisan config:clear
Creating the Service Class
Let's create a dedicated service class to handle communication with the Brand Kit Extractor API. This promotes reusability and keeps our controllers or commands cleaner.
Create a new file at app/Services/BrandKitExtractorService.php:
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Log;
class BrandKitExtractorService
{
protected string $apiUrl;
protected string $apiKey;
public function __construct()
{
$this->apiUrl = config('services.brand_kit.url');
$this->apiKey = config('services.brand_kit.token');
}
/**
* Extracts brand kit information from a given URL.
*
* @param string $websiteUrl The URL of the website to analyze.
* @return array|null The extracted brand kit data or null on failure.
*/
public function extract(string $websiteUrl): ?array
{
try {
$response = Http::withHeaders([
'X-API-Token' => $this->apiKey,
'Accept' => 'application/json',
])->timeout(30)->post("{$this->apiUrl}/extract-brand-kit", [
'url' => $websiteUrl,
]);
// Basic check for HTTP errors
$response->throw();
$data = $response->json();
// Validate essential fields before returning
if ($this->isValidBrandKitResponse($data)) {
return $data;
} else {
Log::warning('Brand Kit API returned invalid data structure.', ['response' => $data]);
return null;
}
} catch (RequestException $e) {
Log::error('Brand Kit API request failed.', [
'url' => $websiteUrl,
'error_message' => $e->getMessage(),
'response_status' => $e->response()->status() ?? 'N/A',
'response_body' => $e->response()->json() ?? $e->response()->body() ?? 'N/A',
]);
return null;
} catch (\Throwable $e) {
Log::error('An unexpected error occurred during Brand Kit extraction.', [
'url' => $websiteUrl,
'error_message' => $e->getMessage(),
]);
return null;
}
}
/**
* Basic validation of the Brand Kit API response structure.
*
* @param array $data The API response data.
* @return bool True if the response structure is considered valid.
*/
protected function isValidBrandKitResponse(array $data): bool
{
// This is a simplified validation. In a production app, you might want more robust checks.
return isset($data['brand_name']) &&
isset($data['logos']) && is_array($data['logos']) &&
isset($data['colors']) && is_array($data['colors']) &&
isset($data['fonts']) && is_array($data['fonts']);
}
}
We also need to configure the service in config/services.php. Add the following to the array:
'brand_kit' => [
'url' => env('BRAND_KIT_API_URL'),
'token' => env('BRAND_KIT_API_TOKEN'),
],
Remember to clear the config cache again if you haven't already:
php artisan config:clear
Creating an Artisan Command
To trigger the brand kit extraction, we'll create an Artisan command. This command will accept a client's website URL as an argument and then use our service to fetch and process the brand data.
Generate the command:
php artisan make:command ExtractClientBrandKit
Now, open app/Console/Commands/ExtractClientBrandKit.php and implement the following logic:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\BrandKitExtractorService;
use App\Models\Client; // Assuming you have a Client model
use Illuminate\Support\Str;
class ExtractClientBrandKit extends Command
{
protected $signature = 'client:extract-brand-kit {websiteUrl}';
protected $description = 'Extracts brand kit information for a given website URL and creates/updates a client.';
protected BrandKitExtractorService $brandKitService;
public function __construct(BrandKitExtractorService $brandKitService)
{
parent::__construct();
$this->brandKitService = $brandKitService;
}
public function handle()
{
$websiteUrl = $this->argument('websiteUrl');
if (!filter_var($websiteUrl, FILTER_VALIDATE_URL)) {
$this->error('Invalid website URL provided.');
return Command::FAILURE;
}
$this->info("Attempting to extract brand kit for: {$websiteUrl}");
$brandData = $this->brandKitService->extract($websiteUrl);
if ($brandData) {
$this->info('Brand kit extracted successfully!');
$this->displayBrandData($brandData);
// --- Integration with your application's client model ---
// This is a simplified example. In a real application, you'd likely
// fetch an existing client or create a new one based on context.
$client = $this->findOrCreateClient($websiteUrl, $brandData);
if ($client) {
$this->updateClientWorkspace($client, $brandData);
$this->info("Client workspace updated/created for {$client->name}.");
return Command::SUCCESS;
} else {
$this->error('Failed to find or create client record.');
return Command::FAILURE;
}
// --- End of integration example ---
} else {
$this->error('Failed to extract brand kit data.');
return Command::FAILURE;
}
}
/**
* Displays the extracted brand data in a readable format.
*
* @param array $brandData The extracted brand data.
*/
protected function displayBrandData(array $brandData): void
{
$this->comment("Brand Name: " . ($brandData['brand_name'] ?? 'N/A'));
$this->comment("Logos: " . implode(', ', $brandData['logos'] ?? ['N/A']));
$this->comment("Colors: " . implode(', ', $brandData['colors'] ?? ['N/A']));
$this->comment("Fonts: " . implode(', ', $brandData['fonts'] ?? ['N/A']));
// You can display other fields like imagery and social_profiles as needed.
}
/**
* Placeholder for finding or creating a client record.
* In a real app, this would interact with your database.
*
* @param string $websiteUrl
* @param array $brandData
* @return \App\Models\Client|null
*/
protected function findOrCreateClient(string $websiteUrl, array $brandData): ?Client
{
// Replace with your actual client creation/finding logic
// For demonstration, let's assume a Client model with 'name', 'website_url', 'logo_url', 'primary_color', 'font_family' fields.
$clientName = $brandData['brand_name'] ?? 'Untitled Client - ' . Str::random(4);
$logoUrl = $brandData['logos'][0] ?? null;
$primaryColor = $brandData['colors'][0] ?? null;
$fontFamily = $brandData['fonts'][0] ?? null;
// Example: Find existing client or create a new one
$client = Client::updateOrCreate(
['website_url' => $websiteUrl],
[
'name' => $clientName,
'logo_url' => $logoUrl,
'primary_color' => $primaryColor,
'font_family' => $fontFamily,
// Other fields as needed
]
);
return $client;
}
/**
* Placeholder for updating the client's workspace with extracted assets.
* This would involve updating UI elements, storing assets, etc.
*
* @param Client $client
* @param array $brandData
*/
protected function updateClientWorkspace(Client $client, array $brandData): void
{
// In a real application, you might:
// 1. Download and store the logo file.
// 2. Update the client's profile settings in your database.
// 3. Trigger other frontend or backend processes that rely on these brand assets.
// Example: Log an action indicating workspace update
$this->info("Simulating workspace update for client: {$client->name}");
// For instance, if you have a dedicated 'BrandSettings' model or similar.
// $client->brandSettings()->updateOrCreate(
// [],
// [
// 'primary_color' => $brandData['colors'][0] ?? null,
// 'font_family' => $brandData['fonts'][0] ?? null,
// // ...
// ]
// );
}
}
Note: The Client model and its associated database table are assumed. You'll need to create these if they don't exist in your project. For example, using Eloquent:
php artisan make:model Client -m
Then, define the columns in the generated migration file (e.g., database/migrations/xxxx_xx_xx_xxxxxx_create_clients_table.php):
Schema::create('clients', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('website_url')->unique();
$table->string('logo_url')->nullable();
$table->string('primary_color')->nullable();
$table->string('font_family')->nullable();
$table->timestamps();
});
And run the migration:
php artisan migrate