jgrossi/corcel
Eloquent-based models to query a WordPress database directly from Laravel or any Composer PHP app. Use WordPress as the CMS/admin backend while your app consumes posts, pages, taxonomies, options, menus, users, ACF fields, attachments, and more.
Installation (Laravel 12+):
composer require jgrossi/corcel
Add the service provider to config/app.php (Laravel 12+ auto-discovers providers, but explicit declaration remains valid):
'providers' => [
// ...
Jgrossi\Corcel\CorcelServiceProvider::class,
],
Configuration: Publish the config file:
php artisan vendor:publish --provider="Jgrossi\Corcel\CorcelServiceProvider"
Update .env with your WordPress site URL and API credentials (prefer Application Passwords):
CORCEL_WORDPRESS_URL=https://your-wordpress-site.com
CORCEL_APP_PASSWORD=your_application_password # Recommended over CORCEL_PASSWORD
CORCEL_APP_NAME=YourAppName
First Use Case (Laravel 12+): Fetch a post by ID with improved type safety:
use Jgrossi\Corcel\Models\Post;
$post = Post::find(1);
echo $post->title->render(); // Uses Laravel 12's enhanced stringable output
CRUD Operations (Optimized for Laravel 12):
// Create (Laravel 12's mass assignment protection applies)
$post = Post::create([
'title' => 'New Post',
'content' => 'Hello, WordPress!',
'status' => 'draft' // Explicit status handling
]);
// Update (uses Laravel 12's model events)
$post->update(['title' => 'Updated Title']);
// Delete (soft deletes supported)
$post->delete(); // Returns deleted model
Relationships (Enhanced with Laravel 12's relationship improvements):
// Define relationship (Laravel 12's relationship macros work)
public function author()
{
return $this->belongsTo(User::class, 'author_id')
->withDefault(); // Handle missing relationships gracefully
}
// Eager loading (Laravel 12's query builder optimizations)
$posts = Post::with('author', 'categories')->get();
Media Handling (Streamlined for Laravel 12's filesystem):
use Illuminate\Support\Facades\Storage;
$media = Media::create([
'file' => Storage::disk('s3')->putFile('uploads', 'image.jpg'),
'alt_text' => 'Image Alt Text',
]);
Taxonomies (Laravel 12's collection methods):
$post->categories()->sync([1, 2, 3]);
// Get taxonomy terms as collections
$terms = $post->tags()->get();
$terms->each(fn($term) => $term->name);
Laravel 12 Events: Leverage new event system:
use Jgrossi\Corcel\Events\PostSaved;
PostSaved::dispatch($post)
->then(fn() => Log::info('Post saved via Corcel'));
Middleware: Use Laravel 12's enhanced middleware stack:
Corcel::middleware(function ($request) {
// Custom middleware logic
return $request->header('X-Custom-Header') === 'allowed';
});
Custom Endpoints: Extend with Laravel 12's HTTP client:
Corcel::extend('custom', function () {
return new class {
public function get($id)
{
return Corcel::http()->get("/wp-json/custom/v1/items/{$id}");
}
};
});
Authentication:
CORCEL_PASSWORD is deprecated. Use CORCEL_APP_PASSWORD for security.Caching:
php artisan cache:clear
php artisan corcel:clear-cache
$post = Post::find(1, ['tags' => ['posts']]);
Model Binding:
route('posts.show', function (Post $post) { // Type-hinted model
return $post->title;
});
REST API Limits:
429 errors with Laravel 12's retry mechanism:
try {
$post = Post::find(1);
} catch (RateLimitExceeded $e) {
$e->retryAfter(function () {
return now()->addSeconds(5);
});
}
Laravel 12 Debugging Tools:
config/corcel.php:
'debug' => env('CORCEL_DEBUG', false),
php artisan tinker
>>> \Jgrossi\Corcel\Facades\Corcel::debug();
Logging:
Corcel::enableLogging('single'); // Logs to 'single' channel
Custom Models (Laravel 12's model enhancements):
namespace App\Models;
use Jgrossi\Corcel\Models\Post;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
class Post extends Post
{
use HasUuids; // Laravel 12's UUID support
protected $casts = [
'date' => 'datetime:Y-m-d',
];
}
API Extensions (Laravel 12's HTTP client):
Corcel::extend('wp-graphql', function () {
return new class {
public function query($query)
{
return Corcel::http()->post('/wp-graphql', [
'json' => ['query' => $query],
]);
}
};
});
Hooks (Laravel 12's event system):
use Jgrossi\Corcel\Facades\Corcel;
Corcel::hook('wp_loaded', function () {
// Your logic
});
// Or use Laravel events
event(new \Jgrossi\Corcel\Events\WordPressLoaded);
Laravel 12 Features:
Post::observe(PostObserver::class);
namespace App\Http\Resources;
use Jgrossi\Corcel\Models\Post;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request)
{
return [
'title' => $this->title->render(),
'url' => route('posts.show', $this),
];
}
}
How can I help you explore Laravel packages today?