Spoosh
Guides

Tags & Invalidation

Understand how queries are tagged and when they refresh

Spoosh uses tags to determine which queries should refresh after a mutation. Understanding this system helps you control cache behavior effectively.

This guide covers the Invalidation Plugin. See the plugin page for installation and configuration.

The Mental Model

Every query is automatically tagged based on its resolved API path. When a mutation succeeds, Spoosh uses wildcard pattern matching to find and refetch matching queries.

// This query gets tagged with: "users/123/posts"
useRead((api) => api("users/:id/posts").GET({ params: { id: 123 } }));

// When this mutation succeeds...
const { trigger } = useWrite((api) => api("users/:id").PATCH());
await trigger({ params: { id: 123 }, body: updated });
// → Default invalidation: ["users", "users/*"]
// → The query above refetches because "users/123/posts" matches "users/*"

Pattern Matching

Invalidation uses wildcard patterns to match query tags:

PatternMatchesDoes NOT Match
"posts""posts" (exact)"posts/1"
"posts/*""posts/1", "posts/1/comments""posts"
["posts", "posts/*"]"posts" AND all children-

Custom Tags

Override the auto-generated tag with custom tags:

// Single custom tag
useRead((api) => api("dashboard/stats").GET(), {
  tags: "dashboard",
});

// Multiple custom tags
useRead((api) => api("users/:id/posts").GET({ params: { id: "5" } }), {
  tags: ["user-posts", "dashboard"],
});

Invalidation Patterns

After a mutation, control what gets invalidated:

PatternEffect
Default (autoInvalidate: true)[firstSegment, firstSegment/*]
"posts"Exact match only
"posts/*"All children of posts
["posts", "posts/*"]posts AND all children
falseSkip invalidation
"*"Global refetch — every active query refetches
const { trigger } = useWrite((api) => api("posts").POST());

// Default behavior — invalidates ["posts", "posts/*"]
await trigger({ body: newPost });

// Exact match only
await trigger({ body: newPost, invalidate: "posts" });

// Children only
await trigger({ body: newPost, invalidate: "posts/*" });

// Parent and children
await trigger({ body: newPost, invalidate: ["posts", "posts/*"] });

// Cross-domain — refresh unrelated queries too
await trigger({ body: newPost, invalidate: ["posts", "posts/*", "dashboard"] });

// Disable invalidation
await trigger({ body: newPost, invalidate: false });

Manual Invalidation

For events outside mutations (WebSocket, timers, external state), trigger invalidation manually:

import { invalidate } from "./spoosh";

// Invalidate specific patterns
socket.on("posts-updated", () => {
  invalidate(["posts", "posts/*"]);
});

// Force everything to refresh
socket.on("full-sync", () => invalidate("*"));

Choosing the Right Pattern

ScenarioPattern
Standard CRUDDefault (auto-invalidate)
Specific resource update"posts/123"
All children of a resource"posts/*"
Resource and all children["posts", "posts/*"]
No related queries to refreshinvalidate: false
User logout / account switchinvalidate: "*" + clearCache: true
Cross-domain updates["posts", "dashboard"]
External events (WebSocket)invalidate(patterns) manually

For complete API reference, configuration options, and advanced examples, see the Invalidation Plugin.

On this page