growthatlas / laravel-connector
Official GrowthAtlas connector package for Laravel applications. Exposes the GrowthAtlas Connector API v1 endpoints to receive and publish AI-generated SEO content.
Requires
- php: ^8.1
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
The official Laravel package for connecting any Laravel application to GrowthAtlas.
Install it on your Laravel site and GrowthAtlas can:
- Push AI-generated SEO content directly into your
Post,Article, or any Eloquent model. - Pull your existing pages for SEO gap analysis and internal linking.
- Import your entities (products, categories, locations) for topical authority clustering.
- Close the performance loop using Google Search Console data from the GrowthAtlas dashboard.
The package exposes five endpoints under /api/growthatlas/v1/ that implement the Connector API v1 specification. All endpoints are secured with a Bearer token and optionally an HMAC-SHA256 signature. You configure which Eloquent models back the responses — no forking required.
What it does
GrowthAtlas dashboard
│
│ POST /api/growthatlas/v1/content-drafts ← AI article payload
│ GET /api/growthatlas/v1/pages ← your published pages
│ GET /api/growthatlas/v1/entities ← products, categories…
│ GET /api/growthatlas/v1/site-profile ← site metadata
│ GET /api/growthatlas/v1/health ← connection test
▼
Your Laravel app
│
▼
App\Models\Post (or any model you configure)
Autopilot publishing flow:
- GrowthAtlas generates an SEO article based on your keyword strategy.
- It
POSTs the article to/api/growthatlas/v1/content-draftson your site. - The connector validates the Bearer token and writes the article into your configured Eloquent model.
- The record appears in your database as a draft, ready for your review or auto-published.
- GrowthAtlas marks the content as published and tracks GSC performance.
Requirements
| Requirement | Version |
|---|---|
| PHP | ^8.1 |
| Laravel | ^10.0, ^11.0, ^12.0, or ^13.0 |
| Filament (opt.) | ^4.0 |
Install
1. Require the package
composer require growthatlas/laravel-connector
The package registers itself automatically via Laravel's package auto-discovery. No manual provider registration needed.
2. Publish the config file
php artisan vendor:publish --tag=growthatlas-connector-config
This creates config/growthatlas-connector.php in your application.
3. Add your API key to .env
Go to your GrowthAtlas dashboard → Integrations → New Integration → select Laravel. Copy the generated API key:
GROWTHATLAS_API_KEY=ga_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Quick start (5 minutes)
Step 1 — Tell the connector which model receives drafts
Open config/growthatlas-connector.php. Point publishing.model at your model and map the columns:
'publishing' => [ 'model' => App\Models\Post::class, 'fields' => [ 'title' => 'title', 'slug' => 'slug', 'body' => 'body', // or 'content' — whatever your column is 'meta_description' => 'meta_description', ], 'status_column' => 'status', 'growthatlas_id_column' => 'growthatlas_draft_id', // add this column (see below) 'status_map' => [ 'draft' => 'draft', 'published' => 'published', ], ],
Step 2 — Add the idempotency column
The connector uses growthatlas_draft_id to detect duplicate pushes and skip re-creating them.
php artisan make:migration add_growthatlas_draft_id_to_posts_table
// In the migration up() method: $table->unsignedBigInteger('growthatlas_draft_id')->nullable()->unique();
php artisan migrate
Step 3 — Clear caches
php artisan config:clear php artisan route:clear
Step 4 — Verify the connection
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://yoursite.com/api/growthatlas/v1/health
Expected response:
{
"success": true,
"data": {
"status": "ok",
"connector": "laravel",
"connector_version": "1.0.0",
"platform": "laravel",
"growthatlas_api_version": "v1"
}
}
Then click Test Connection in the GrowthAtlas dashboard. A green tick means you're live.
Configuration reference
config/growthatlas-connector.php — published by vendor:publish --tag=growthatlas-connector-config.
| Key | Default | Purpose |
|---|---|---|
api_key |
env('GROWTHATLAS_API_KEY') |
Bearer token GrowthAtlas sends on every request. Required. |
signing_secret |
env('GROWTHATLAS_SIGNING_SECRET') |
Optional shared secret for HMAC-SHA256 signature verification. |
route_prefix |
"api/growthatlas/v1" |
Full URL prefix for all connector routes. |
route_middleware |
["api"] |
Laravel middleware group applied to connector routes. |
publishing.model |
App\Models\Post |
Eloquent model that receives content drafts. |
publishing.fields |
see config | Column map: GrowthAtlas field → your column name. |
publishing.status_column |
"status" |
Column that stores the publish status. |
publishing.status_map |
{draft: draft, published: published} |
Maps GrowthAtlas status values to your model's status values. |
publishing.growthatlas_id_column |
"growthatlas_draft_id" |
Idempotency column — must be unique indexed. |
publishing.published_at_column |
"published_at" |
Timestamp column set to now() when pushing as published. Set to null to disable. |
pages.source |
"eloquent" |
How pages are fetched: "eloquent" or "sitemap". |
pages.model |
App\Models\Post |
Eloquent model used when source = "eloquent". |
pages.url_column |
"slug" |
Column containing the page URL or slug. |
entities |
[] |
Map of type => ModelClass for the /entities endpoint. |
log_inbound |
false |
Log each inbound request to growthatlas_inbound_requests table. Powers the Filament page. |
Environment variables
# Required GROWTHATLAS_API_KEY=ga_live_xxxxxxxxxxxxxxxx # Optional — HMAC signature verification (recommended for production) GROWTHATLAS_SIGNING_SECRET= # Optional — change the default publish model GROWTHATLAS_PUBLISH_MODEL=App\Models\Post # Optional — Filament admin page + request logging GROWTHATLAS_LOG_INBOUND=false
Wiring your own model
Case 1 — Standard blog (App\Models\Post, columns: title, slug, body, status)
'publishing' => [ 'model' => App\Models\Post::class, 'fields' => [ 'title' => 'title', 'slug' => 'slug', 'body' => 'body', 'meta_description' => 'meta_description', // Featured image — value is an absolute CDN URL. // Do NOT pass through Storage::url() as it will corrupt absolute URLs. // 'featured_image_url' => 'featured_image_path', // 'featured_image_alt' => 'featured_image_alt', ], 'status_column' => 'status', 'published_at_column' => 'published_at', // auto-set to now() when publishing 'growthatlas_id_column' => 'growthatlas_draft_id', 'status_map' => ['draft' => 'draft', 'published' => 'published'], ], 'pages' => [ 'source' => 'eloquent', 'model' => App\Models\Post::class, 'url_column' => 'slug', ],
Case 2 — Filament app with App\Models\Article (body column is content)
'publishing' => [ 'model' => App\Models\Article::class, 'fields' => [ 'title' => 'title', 'slug' => 'slug', 'body' => 'content', // different column name 'meta_description' => 'seo_description', ], 'status_column' => 'status', 'growthatlas_id_column' => 'growthatlas_draft_id', 'status_map' => ['draft' => 0, 'published' => 1], // boolean status ],
Case 3 — Expose products and categories via /entities
'entities' => [ 'product' => App\Models\Product::class, 'category' => App\Models\Category::class, ],
Each model should have name, slug, description, and url (or slug) columns — the connector reads them automatically. For different column names, override the response in a custom controller action (see Custom Behaviour below).
Security
Bearer token authentication
Every request from GrowthAtlas includes:
Authorization: Bearer <your-api-key>
The connector verifies this using hash_equals() (constant-time comparison) to prevent timing attacks.
HMAC-SHA256 signatures (optional, recommended for production)
When signing_secret is set, the connector also verifies the X-GrowthAtlas-Signature header:
GROWTHATLAS_SIGNING_SECRET=your-64-char-random-secret
Copy this same value into the Signing Secret field in your GrowthAtlas integration settings. GrowthAtlas will then send:
X-GrowthAtlas-Signature: sha256=<hex-digest>
where the digest is HMAC-SHA256(secret, raw_request_body). The connector verifies this automatically. When the signing secret is set, requests without a matching signature are rejected with 401.
Security checklist
- Use HTTPS on your domain — GrowthAtlas never sends to plain HTTP in production.
- Set a strong, unique
GROWTHATLAS_API_KEY(min 32 chars, generated randomly). - Set
GROWTHATLAS_SIGNING_SECRETto a 64-char random string for HMAC verification. - Add a
uniqueindex ongrowthatlas_draft_idin your model migration. - Run
php artisan config:clearafter changing.envvalues.
Routes registered
After installation, run php artisan route:list | grep growthatlas to confirm:
| Method | URI | Description |
|---|---|---|
GET |
api/growthatlas/v1/health |
Connection test — no auth required |
GET |
api/growthatlas/v1/site-profile |
Site metadata |
GET |
api/growthatlas/v1/pages |
Paginated page list |
GET |
api/growthatlas/v1/entities |
Paginated entity list |
POST |
api/growthatlas/v1/content-drafts |
Receive content draft |
The prefix is configurable via route_prefix in the config.
Filament admin page (optional)
Enable the built-in GrowthAtlas Connector Filament page to monitor your integration without leaving the admin panel. Requires Filament 4.
1. Register the plugin in your panel provider
use GrowthAtlas\Connector\Filament\GrowthAtlasConnectorPlugin; public function panel(Panel $panel): Panel { return $panel ->plugin(GrowthAtlasConnectorPlugin::make()) // ... }
2. Enable inbound-request logging (optional but recommended)
GROWTHATLAS_LOG_INBOUND=true
Then publish and run the audit-log migration:
php artisan vendor:publish --tag=growthatlas-connector-migrations php artisan migrate
This creates the growthatlas_inbound_requests table.
3. Clear config cache
php artisan config:clear
The GrowthAtlas page now appears in your Filament sidebar under the Integrations group. It shows:
- API key status (masked) and signing secret status
- Last inbound request timestamp
- Recent 20 inbound requests — endpoint, HTTP status, signature validity, IP, time
- Rotate signing secret action — writes a new secret to
.envand displays it for copying to the GrowthAtlas dashboard - Open /health button
Troubleshooting
401 Unauthorized on all endpoints
- Confirm
GROWTHATLAS_API_KEYin.envexactly matches the key in the GrowthAtlas dashboard (no trailing spaces). - Run
php artisan config:clearafter any.envchange.
404 Not Found on /api/growthatlas/v1/health
- Run
php artisan route:list | grep growthatlas— if no routes appear, the service provider wasn't registered. Check that your app has package auto-discovery enabled (most do by default). - If you use route caching:
php artisan route:clear && php artisan route:cache. - Confirm
route_prefixin config matches how you're calling the URL.
Published posts return 404
The connector sets the status column but some models also require a non-null published_at
to show posts publicly. Make sure publishing.published_at_column is set to your timestamp
column (default "published_at"). The connector then writes now() automatically when
pushing as "published". Set it to null if your model handles this via an observer.
500 on POST /content-drafts
- The
growthatlas_draft_idcolumn may be missing. Run the migration. publishing.modelclass doesn't exist or isn't auto-loadable. Check the FQCN.
Draft created twice (no idempotency)
- The
growthatlas_draft_idcolumn isn't in the model's$fillablearray. Add it. - The column exists but doesn't have a
uniqueindex. Add one in a migration.
Signature verification fails (401 after setting signing_secret)
- The secret in
.envmust match the Signing Secret field in GrowthAtlas exactly. - Run
php artisan config:clear. - Ensure no middleware is modifying the raw request body before the connector middleware runs.
Testing the connection locally
Via curl
# Health check curl -H "Authorization: Bearer YOUR_API_KEY" \ http://localhost/api/growthatlas/v1/health # Simulate a draft push curl -X POST http://localhost/api/growthatlas/v1/content-drafts \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "growthatlas_draft_id": 9999, "title": "Test Article", "slug": "test-article", "body": "## Hello\n\nThis is a test.", "meta_description": "A test draft.", "publish_status": "draft", "source": "growthatlas" }'
Expected response for first call (created: true):
{"success":true,"data":{"external_id":"1","url":"http://localhost/test-article","status":"draft","created":true}}
Run again with the same growthatlas_draft_id to verify idempotency — created will be false.
Integration Recipe (for AI assistants)
This section is a machine-readable step-by-step guide. If you are an AI assistant (ChatGPT, Claude, Gemini, Copilot, Cursor, etc.) helping a developer integrate this package into a Laravel application, follow these steps in order. Each step is independently verifiable.
Prerequisites
- PHP 8.1+
- Laravel 10, 11, 12, or 13
- Composer installed
- A GrowthAtlas account at https://growthatlas.io with a Laravel integration created
Step 1 — Install
composer require growthatlas/laravel-connector
Verify: composer show growthatlas/laravel-connector prints the version. Package auto-discovers GrowthAtlas\Connector\ConnectorServiceProvider.
Step 2 — Publish config
php artisan vendor:publish --tag=growthatlas-connector-config
Verify: config/growthatlas-connector.php exists.
Step 3 — Set environment variable
GROWTHATLAS_API_KEY=<copy from GrowthAtlas dashboard → Integrations → your integration → API Key>
Verify: php artisan tinker --execute="echo config('growthatlas-connector.api_key');" prints the key.
Step 4 — Identify the target model
Ask the developer which Eloquent model stores blog posts/articles and what the column names are for:
- title
- slug (URL identifier)
- body content
- meta description
- publish status
Step 5 — Add idempotency column
php artisan make:migration add_growthatlas_draft_id_to_posts_table
In up():
$table->unsignedBigInteger('growthatlas_draft_id')->nullable()->unique();
Also add 'growthatlas_draft_id' to the model's $fillable array.
php artisan migrate
Verify: php artisan tinker --execute="Schema::hasColumn('posts', 'growthatlas_draft_id');" returns true.
Step 6 — Configure config/growthatlas-connector.php
Set publishing.model to the target model FQCN. Map publishing.fields keys to actual column names. Example for App\Models\Post:
'publishing' => [ 'model' => App\Models\Post::class, 'fields' => [ 'title' => 'title', 'slug' => 'slug', 'body' => 'body', // adjust if column is 'content' 'meta_description' => 'meta_description', ], 'status_column' => 'status', 'growthatlas_id_column' => 'growthatlas_draft_id', 'status_map' => ['draft' => 'draft', 'published' => 'published'], ], 'pages' => [ 'source' => 'eloquent', 'model' => App\Models\Post::class, 'url_column' => 'slug', // or 'url' if the model stores full URLs ],
Step 7 — Clear caches
php artisan config:clear php artisan route:clear
Step 8 — Verify health endpoint
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://yoursite.com/api/growthatlas/v1/health
Expected:
{"success":true,"data":{"status":"ok","connector":"laravel","growthatlas_api_version":"v1"}}
If 401: recheck GROWTHATLAS_API_KEY and run php artisan config:clear.
If 404: run php artisan route:list | grep growthatlas to confirm routes loaded.
Step 9 — Test draft ingestion
curl -X POST https://yoursite.com/api/growthatlas/v1/content-drafts \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"growthatlas_draft_id":1,"title":"Hello GrowthAtlas","slug":"hello-growthatlas","body":"Test.","publish_status":"draft","source":"growthatlas"}'
Expected: {"success":true,"data":{"external_id":"1","status":"draft","created":true}}
Confirm a row exists in the database with growthatlas_draft_id = 1.
Step 10 — Test in GrowthAtlas dashboard
Go to Integrations, find the integration, click Test Connection. Green tick = connected.
Troubleshooting quick-reference for AI assistants
| Symptom | Most likely cause | Fix |
|---|---|---|
401 everywhere |
GROWTHATLAS_API_KEY mismatch |
Check .env, php artisan config:clear |
404 on all routes |
Service provider not loaded | Check auto-discovery; php artisan route:list | grep growthatlas |
500 on /content-drafts |
Model not found or column missing | Check FQCN in config; run migration |
| Draft created twice | growthatlas_draft_id not in $fillable |
Add to $fillable on the model |
| Signature 401 | Secret mismatch | Match secret in .env and GrowthAtlas dashboard |
| Published post returns 404 | published_at never set |
Set published_at_column in config (default "published_at") |
| Featured image missing | Field not in field map | Uncomment featured_image_url in publishing.fields |
Versioning
Follows Semantic Versioning. The package targets Connector API v1. When GrowthAtlas releases API v2, a new major version will be released.
Full changelog: CHANGELOG.md
Contributing
- Fork and branch:
git checkout -b feat/my-feature - Make changes and commit.
- Open a pull request with a clear description.
License
MIT. See LICENSE.