zoxta/nova-cloudflare-card
A Laravel Nova card that brings Cloudflare analytics into your admin panel, showing zone traffic, cache hit rates, and threat metrics at a glance. Configurable credentials and a clean dashboard widget make it easy to monitor Cloudflare without leaving Nova.
Installation
composer require zoxta/nova-cloudflare-card
Nova cards auto-register their assets via the service provider, but you must explicitly tell Nova where to render the card.
Where to look first
composer.json — confirm the package supports your Nova version (laravel/nova is a private dependency so the constraint usually reads something like ^4.0|^5.0). If it pins an older Nova, you will hit a conflict during install.config/cloudflare.php (after publishing) — this is where API credentials and the target zone live.src/Cards/CloudflareCard.php — read the component() method and the __construct signature; that tells you exactly what the card accepts.resources/js/components/Card.vue — see what payload shape the Vue component expects from $this->withMeta() or the card's data() method.Publish the config/assets
php artisan vendor:publish --tag=cloudflare-card-config
php artisan vendor:publish --tag=cloudflare-card-assets
Configure credentials
Add to .env:
[email protected]
CLOUDFLARE_API_KEY=your-global-api-key
CLOUDFLARE_ZONE_ID=your_zone_id
(Some versions expect an API token with Zone:Analytics:Read scope instead of the email/key pair — check the config file's keys before assuming.)
Register the card
In app/Nova/NovaServiceProvider.php:
public function cards()
{
return [
new \Zoxta\NovaCloudflareCard\CloudflareCard(),
];
}
Or attach it to a specific dashboard via dashboards() if you want it scoped.
First use case
The most common pattern is a global dashboard tile showing the last 24–30 days of requests, bandwidth, and threat/cache-hit stats for a single Cloudflare zone — a quick "is the site healthy?" glance for ops teams who don't want to open the Cloudflare dashboard.
Scoping the card to a specific zone
Since most installs manage one production zone, pass the zone ID through the card constructor or the config file rather than editing the package:
new CloudflareCard(config('services.cloudflare.zone_id'))
If the constructor doesn't accept arguments, override the value in config/cloudflare.php and cache it per environment.
Per-dashboard placement
Cards can be registered on individual Nova dashboards rather than globally. This is useful if you run a "Marketing" dashboard versus an "Infrastructure" dashboard and only want traffic stats on the latter:
public function dashboards()
{
return [
(new Main)->cards([
new CloudflareCard(),
]),
];
}
Wrapping the API call for reuse
If you need the same Cloudflare data elsewhere (a custom metric card, a scheduled summary email), extract the GraphQL/REST call into your own service rather than duplicating it. The package almost certainly wraps Cloudflare's GraphQL analytics endpoint (https://api.cloudflare.com/client/v4/graphql); you can mirror the query in an action class and inject it both into your card's data() method and any artisan command.
class CloudflareMetrics
{
public function forZone(string $zoneId, string $since = '-1440'): array
{
// mirror the package's query so card + CLI share a contract
}
}
Extending the card
Nova cards are just classes with a component() and render()/data() method. If the package's card doesn't expose the fields you need, subclass it and override data():
class ExtendedCloudflareCard extends CloudflareCard
{
public function component()
{
return 'cloudflare-card'; // reuse the Vue component
}
public function data()
{
return array_merge(parent::data(), [
'custom' => $this->customMetric(),
]);
}
}
Then reference the new class in NovaServiceProvider.
Multi-zone layouts
For agencies managing several zones, instantiate the card once per zone with a distinct name():
(new CloudflareCard())->name('Cloudflare — Client A'),
(new CloudflareCard('zone_b_id'))->name('Cloudflare — Client B'),
Nova respects name() for the card title; make sure you also cache per-zone so polling doesn't multiply latency.
Caching
Nova cards re-render on every dashboard load. Wrap the package's API call in your own Cache::remember('cf-stats-'.$zoneId, now()->addMinutes(15), ...) if the card itself doesn't cache — otherwise a 6-second Cloudflare GraphQL round-trip makes your dashboard feel broken whenever an ops user refreshes.
Polling
If the Vue component supports refreshIntervalSeconds (many Nova cards do), start at 60 seconds minimum. Cloudflare's free-tier rate limits will bite you at sub-minute polling, and the analytics data doesn't update faster than ~5 minutes anyway.
Credentials confusion: Key vs Token
Cloudflare has two auth flavours — the legacy global API key (email + key) and scoped API tokens. The package's README or config will hint at one, but it's easy to mix them. Symptom: 403 authentication error or empty graphs even though the zone renders. Verify with:
curl -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID
If that succeeds, the token is fine and the problem is your package config pointing at CLOUDFLARE_API_KEY instead.
Zone ID, not account ID
The GraphQL analytics query filters on zoneTag, which is the zone ID (32-char hex found at the bottom of the Cloudflare zone Overview page). Passing an account ID returns empty datasets without a clear error — you'll just see zeros. Double-check before filing an issue.
GraphQL response shape is deeply nested
Cloudflare returns data.viewer.zones[0].httpRequests1dGroups[0].sum.requests style paths. If the card renders blank but your API call returns 200, log the raw response and diff against the package's parser — a single nullable change in Cloudflare's schema can silently break a hardcoded path.
Asset rebuild after customization
If you edit resources/js (e.g., to adjust the chart), you must run npm run prod inside the package and re-publish assets, or Nova will keep serving the compiled bundle. There's no watch mode tied to your app's Vite server.
Nova 5 vs Nova 4
Nova 5 moved cards to a slightly different lifecycle (component() caching, asset registration via Nova::script). If the package predates Nova 5, cards may render an empty div with a console warning like "Failed to mount component: template or render function not defined." Check the nova peer dependency in composer.json before installing.
Cache poisoning across users
Nova cards render in the authenticated user's session but Cache::remember is global. If you cache the raw API result, fine — if you cache the rendered meta (including user-scoped filters), two admins will see each other's data. Cache the API payload only.
Debugging tip: bypass the card
Drop this in tinker to confirm the failure is in the package versus your config:
app(\Zoxta\NovaCloudflareCard\CloudflareCard::class)->data();
If it throws, you'll get the real exception; Nova swallows errors into a generic "Unable to load card" message, which wastes hours.
Extension points
The card's Vue component is registered globally under the name returned by component(). You can register your own component with the same name before Nova boots to fully replace the UI, or use Nova::script('cloudflare-card-custom', __DIR__.'/dist/custom.js') to layer additional behavior without forking the package.
Maintenance risk
At 10 stars and a single maintainer, treat this as a small utility you may need to patch yourself. Pin the version in composer.json ("zoxta/nova-cloudflare-card": "1.0.*") and vendor-patch rather than relying on upstream fixes. The underlying Cloudflare GraphQL API is stable, so the primary rot risk is Nova version bumps rather than the data layer.
How can I help you explore Laravel packages today?