Appearance
Laravel "This cache store does not support tagging", handled
Laravel throws BadMethodCallException: This cache store does not support tagging. when code calls Cache::tags() on a store whose driver has no tags() method, which in practice means the file, database and dynamodb drivers. The fix in an app is to point the cache at Redis or Memcached. The fix in a package is harder, because you do not choose the host's cache driver, and a package that throws this on a fresh install is broken on the most common setup there is. This post shows how FilamentCraft's fragment cache handles it: probe for tag support once, cache when tags exist, render uncached when they do not, and say so in a diagnostic command instead of in a stack trace.
Why Laravel throws "This cache store does not support tagging"
The exception comes from one method on Illuminate\Cache\Repository. Before it builds a tagged cache, tags() asks the repository whether the underlying store can do it:
php
public function tags($names)
{
if (! $this->supportsTags()) {
throw new BadMethodCallException('This cache store does not support tagging.');
}
// ...
}
public function supportsTags()
{
return method_exists($this->store, 'tags');
}That is the whole check. Stores that extend TaggableStore (Redis, Memcached, APC, array) have a tags() method, the rest do not. The Laravel cache documentation states it plainly: "Cache tags are not supported when using the file, dynamodb, or database cache drivers."
The trap is the default. The same page notes that a new Laravel application is configured to use the database cache driver. So any package that tags its cache entries works on the author's Redis-backed machine and throws on a fresh laravel new project. The issue trackers of packages such as laravel-geoip and a spatie/laravel-honeypot discussion show what that looks like from the user's side.
Why a page builder wants tags anyway
FilamentCraft caches each rendered section of a published page as an HTML fragment. When someone publishes a template or a region, or saves site settings, every cached fragment for that site has to go. Without tags you would need to know every key you ever wrote for the site, which means storing a key index and keeping it consistent. With tags it is one call:
php
Cache::store($store)->tags(["site:{$siteId}"])->flush();Every fragment is written under the site:{id} tag, so publishing flushes the whole site in one call. It is coarse (there is no per-template granularity) but it is correct, and correctness is the part you cannot afford to get wrong when a customer has just pressed publish.

Probe once, fall back quietly
We did not want to make Redis a requirement, so tag support is the gate for fragment caching rather than a precondition for installing the package. The class that owns this is FragmentCacheStore, and its check is a probe, not a config lookup:
php
public function supportsTags(): bool
{
try {
$this->store()->tags(['probe']);
return true;
} catch (Throwable) {
return false;
}
}
public function enabled(): bool
{
if (! Config::boolean('filamentcraft.cache.enabled', true)) {
return false;
}
return $this->supportsTags();
}We call tags() and catch the failure instead of calling Repository::supportsTags() for a reason that came out of a bug. Resolving the store can fail too. If filamentcraft.cache.store names a store that is not defined in config/cache.php, Laravel's cache manager throws while building it. Before v1.20.0 that error surfaced on every cached section render. The CHANGELOG entry for v1.20.0 reads: "A misconfigured filamentcraft.cache.store name degrades to uncached rendering instead of erroring on every cached section render." Putting both failure modes inside one try means one answer to one question: can we cache here or not.
remember() then uses that answer and renders directly when the answer is no:
php
public function remember(string $key, array $tags, int $ttl, Closure $callback): string
{
if (! $this->enabled()) {
return $callback();
}
return $this->store()->tags($tags)->remember($key, $ttl, $callback);
}Flushing follows the same rule, with one more layer. flush() returns early on a store without tags, and wraps the real flush in its own try, because a publish must never fail because Redis blinked. A missed flush costs at most one TTL of stale HTML (the ttl default is 3600 seconds). A failed publish costs the customer's trust in the button.
Keys that change when the content changes
Tags handle invalidation on publish. The cache key handles everything else, so that most edits never need a flush at all. FragmentCacheKey::for() hashes the section's settings and blocks together with the render color scheme and render context, after a recursive ksort so that key order in the stored JSON cannot produce two keys for the same content:
{tag_prefix}:section:{siteId}:{sectionId}:{sha1(settings+blocks+scheme+context)}:{locale}:{themeVersion}A settings edit, a locale, a theme change (the theme version bumps) or a different color scheme each produce a new key. The old entry is never read again and expires on its own. Custom sections built in the no-code section builder add a version counter to the same key, so re-saving one invalidates its own fragments without a manual flush.
The tag_prefix config value prefixes these keys, not the tag. The invalidation tag is always the bare site:{id}, which matters if two apps share one Redis database: give them separate databases so one app's publish cannot flush the other's fragments.
Sections that should never be cached
Some output depends on the request, the session or the clock. Caching those would serve one visitor's cart to the next. Every section class answers cacheable(), and the default in the shared schema trait is true:
php
public static function cacheable(): bool
{
return false;
}The built-ins that return false are the countdown (its remaining time is computed at render time), the commerce sections (cart, checkout, product listing and product detail) and the header, but only while a custom localeUrlsUsing() resolver is registered. That last one is conditional because host locale URLs depend on the current path, and fragments are cached per locale, not per path:
php
public static function cacheable(): bool
{
return ! app(LocaleUrlResolver::class)->isCustomised();
}Draft renders in the editor skip the fragment cache entirely, so an author never sees stale output while editing.
Silent is only fine if something tells you
A silent fallback has an obvious cost: you can run a production site on the database store for months and never learn that nothing is cached. So the diagnosis lives in php artisan filamentcraft:doctor, not in the request path. Its cache check does three things:
- If
filamentcraft.cache.storenames a store that is missing fromconfig/cache.php, it fails with that store name. - If caching is disabled in config, it passes and says so.
- If the store has no tag support, it warns: "The active cache store does not support tags, so section fragment caching is silently OFF."
Keeping this out of the request path was deliberate. In v1.19.0 we shipped two runtime warnings that fired on every render, including for valid setups, and v1.19.1 moved them into doctor. A log line repeated on every request gets ignored, while a check you run on deploy (or in CI with --strict, which also fails on warnings) gets read.
What to do in your own package
If you ship a package that tags cache entries, the pattern is short:
- Wrap store resolution and
tags()in one probe and cache the answer per request if it is on a hot path. - Render or compute directly when the probe fails. Do not throw from a read path.
- Make flushes best effort, and choose TTLs so a missed flush is bounded.
- Tell the user once, in an install or doctor command, that caching is off and which store would turn it on.
For FilamentCraft itself, turning fragment caching on is one config line on a Redis-backed app:
php
// config/filamentcraft.php
'cache' => [
'enabled' => true,
'store' => 'redis',
'ttl' => 3600,
'tag_prefix' => 'fc',
],The cache reference covers the full key composition and the section failure log that shares the same store, and the commands reference lists every doctor flag. You can see cached published pages on the live demo.
