How to Securely Fetch Unpublished CraftCMS Entries with a Custom API Header


CraftCMS provides a robust GraphQL API for querying your content, but sometimes you need to tailor the API for different user roles. For example, you might want admins to fetch all entries (including unpublished ones) while the public can only see published entries. In this blog post, we’ll show you how to use a custom API header to securely control access to your entries.
Why Use a Custom Header?
A custom API header allows you to:
Tailor API responses for different user roles without creating separate endpoints.
Securely differentiate access levels by validating the header’s value.
Keep your public-facing API clean and free of admin-specific logic.
For this tutorial, we’ll configure CraftCMS to:
Fetch only published (
live
) entries for public users.Fetch all entries (published and unpublished) for admins with a valid header.
Step 1: Define the Custom Header
We’ll use a header called X-Admin-Access
to signal admin-level requests. Requests with this header will return all entries, while requests without it will default to fetching only published entries.
Example Headers:
Public Request:
GET /graphql
Admin Request:
GET /graphql X-Admin-Access: your-secure-admin-key
Step 2: Create a Custom Module in CraftCMS
CraftCMS’s modular architecture lets you hook into its GraphQL system. We’ll use a custom module to intercept GraphQL queries and adjust them based on the presence of X-Admin-Access
.
Custom Module Code
Add this to your module’s init
method:
namespace modules\custommodule;
use Craft;
use craft\gql\base\Resolver;
use craft\gql\resolvers\elements\Entry;
use GraphQL\Type\Definition\ResolveInfo;
class CustomModule extends \yii\base\Module
{
public function init()
{
parent::init();
// Hook into GraphQL resolvers for entries
Entry::setCustomResolver([$this, 'customEntryResolver']);
}
public function customEntryResolver($source, array $arguments, $context, ResolveInfo $resolveInfo)
{
$request = Craft::$app->getRequest();
$headers = $request->getHeaders();
// Validate the X-Admin-Access header
$adminKey = Craft::parseEnv('$ADMIN_API_KEY'); // Secure key from .env
$isAdminAccess = $headers->get('X-Admin-Access') === $adminKey;
// Adjust query based on header
if ($isAdminAccess) {
$arguments['status'] = null; // Fetch all entries
} else {
$arguments['status'] = 'live'; // Fetch only published entries
}
// Use the default CraftCMS resolver
return Resolver::defaultResolver($source, $arguments, $context, $resolveInfo);
}
}
Step 3: Add the Admin API Key to .env
For security, store your admin key in the .env
file. Never hardcode sensitive data in your codebase.
Add the following to your .env
file:
ADMIN_API_KEY=your-secure-admin-key
Step 4: Using the Custom Header
Public Request
When a public user queries your GraphQL API, they will only see live (published) entries.
Example Public Query:
query {
entries(section: "blog") {
title
slug
}
}
Response:
[
{
"title": "Published Entry 1",
"slug": "published-entry-1"
},
{
"title": "Published Entry 2",
"slug": "published-entry-2"
}
]
Admin Request
When an admin queries the API with the X-Admin-Access
header, all entries (published and unpublished) are returned.
Example Admin Query:
query {
entries(section: "blog") {
title
slug
status
}
}
Request Header:
X-Admin-Access: your-secure-admin-key
Response:
[
{
"title": "Published Entry 1",
"slug": "published-entry-1",
"status": "live"
},
{
"title": "Unpublished Entry 2",
"slug": "unpublished-entry-2",
"status": "disabled"
}
]
Step 5: Secure Your API
1. Use HTTPS
Always serve your API over HTTPS to encrypt headers during transmission.
2. Validate the Admin Key
Ensure the X-Admin-Access
header matches the secure key stored in .env
. Use hash_equals
for secure string comparison to prevent timing attacks.
3. Rotate the Admin Key
Periodically change the admin key to mitigate the risk of compromise. Update the .env
file and notify authorized users accordingly.
Step 6: Test Your Implementation
Test both public and admin requests to ensure:
Public users only see published entries.
Admin users with a valid header see all entries.
Invalid or missing headers result in the default behavior (public view).
Conclusion
By leveraging CraftCMS’s extensibility and GraphQL capabilities, you can implement secure, role-based API access with a simple custom header. This approach keeps your public API lightweight while providing full access to admins when needed.
If you have additional security needs, consider combining this approach with rate limiting, IP whitelisting, or Craft’s user roles for even finer control.
Subscribe to my newsletter
Read articles from Vadim Kononov directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Vadim Kononov
Vadim Kononov
I am an accomplished Solution Architect, Full Stack Developer and DevOps Specialist with a passion for creative leadership and mentorship, business optimization and technical direction, and ingenious solutions to complex problems. I am especially interested in App & Web Development, Cyber Security, Cloud Computing, Data Science, Open Source Software, Statistical Analysis and Discrete Mathematics.