xiaohongshu
xiaohongshu
fvtt-error-handling
This skill should be used when adding error handling to catch blocks, standardizing error handling across a codebase, or ensuring proper UX with user messages vs technical logs. Covers NotificationOptions, Hooks.onError, and preventing console noise.
Full skill instructions
Domain: Foundry VTT V12/V13 Error Handling Status: Production-Ready (Verified against V13 API) Last Updated: 2025-12-31
This skill provides production-ready error handling patterns for Foundry VTT modules, using the documented V13 APIs: NotificationOptions and Hooks.onError.
Always separate error funnel from user notification:
Hooks.onError): Logs stack traces, triggers ecosystem hooks, provides structured dataui.notifications.*): Clean, sanitized messages with full UX controlWhy separation matters:
Hooks.onError mutates error.message when msg is provided (see Foundry GitHub #6669)clean: true only works in NotificationOptions, not Hooks.onErrorconsole: false prevents double-logging)| Pattern | Error Funnel | User Notification | Use Case |
|---|---|---|---|
| User-facing | Hooks.onError (notify: null) | ui.notifications.error | Unexpected failures |
| Expected validation | (none) | ui.notifications.warn | User input errors |
| Developer-only | Hooks.onError (notify: null) | (none) | Diagnostic logging |
| High-frequency | Throttled Hooks.onError | Throttled ui.notifications.warn | Render loops, hooks |
Use for: Unexpected errors, operations that failed, critical failures
} catch (err) {
// Preserve original error as cause when wrapping non-Errors
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
// Error funnel: stack traces + ecosystem hooks (no UI)
Hooks.onError(`YourModule.${contextDescription}`, error, {
msg: "[YourModule]",
log: "error",
notify: null, // No UI from hook
data: { contextDescription, userFacingDescription } // Structured context
});
// Fully controlled user message (sanitized, no console - already logged)
ui.notifications.error(`[YourModule] ${userFacingDescription}`, {
clean: true,
console: false // Hooks.onError already logged
});
}
Key points:
userFacingDescription (no technical details leaked)Hooks.on("error", ...))data for debugging and hook subscribersconsole: false prevents double-loggingUse for: User input errors, missing data, expected validation failures
} catch (err) {
const message = `[YourModule] ${userFacingDescription}`;
ui.notifications.warn(message, {
clean: true,
console: false // Expected failures - no console noise
});
}
Key points:
warn severity (not error) for expected casesconsole: false avoids noise for common user-driven failuresconsole: game.settings.get("your-module", "enableProfiling")
Use for: Diagnostic logging, internal errors that don't need user notification
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
Hooks.onError(`YourModule.${contextDescription}`, error, {
msg: "[YourModule]",
log: "error",
notify: null, // Log only, no UI spam
data: { contextDescription } // Structured context for debugging
});
}
Key points:
console.error(...) for truly isolated cases (but lose ecosystem hooks)Use for: Render loops, hooks, event handlers that might error repeatedly
// Module-scoped throttle (outside class/function)
const errorThrottles = new Map();
// In catch block:
} catch (err) {
const throttleKey = contextDescription;
const lastError = errorThrottles.get(throttleKey) || 0;
// Throttle: 5 second window per context
if (Date.now() - lastError > 5000) {
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
// Error funnel (no UI)
Hooks.onError(`YourModule.${contextDescription}`, error, {
msg: "[YourModule]",
log: "warn",
notify: null, // No UI from hook
data: { contextDescription, userFacingDescription }
});
// Separate controlled notification (console: false prevents double-logging)
ui.notifications.warn(`[YourModule] ${userFacingDescription}`, {
clean: true,
console: false // Already logged via Hooks.onError
});
errorThrottles.set(throttleKey, Date.now());
}
}
Key points:
warn severity for non-critical repeated errorsconsole: false prevents double-loggingAsk these questions:
Is this unexpected? (system failure, unhandled case, critical error) → Pattern 1: User-facing failures
Is this expected validation? (user input error, missing required data) → Pattern 2: Expected validation
Is this diagnostic-only? (internal state, no user impact) → Pattern 3: Developer-only
Could this fire repeatedly? (render loop, hook, event handler) → Pattern 4: High-frequency throttled
Hooks.onError Mutates Error ObjectsCritical: Hooks.onError modifies error.message when msg is provided:
// Internal Foundry implementation (from GitHub #6669):
if (msg) err.message = `${msg}: ${err.message}`;
This is why we separate funnel from notification:
Always normalize to Error objects:
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
console Default Behavior Not DocumentedIssue: Foundry V13 docs don't explicitly state the default value for NotificationOptions.console.
Evidence: API examples show { console: false } to suppress logging, implying default is true.
Best practice: Be explicit to prevent double-logging and future-proof against default changes:
ui.notifications.error(message, {
clean: true,
console: false // Explicit is better than implicit
});
notify String Values Are UndocumentedIssue: Hooks.onError accepts notify: null | string, but valid string values aren't enumerated in V13 API docs.
Your assumption: notify accepts "error", "warn", "info" (like ui.notifications.* methods)
Reality: Likely correct but NOT explicitly documented.
Best practice: Use notify: null + separate ui.notifications.* for full control:
// Avoid relying on undocumented notify strings
Hooks.onError(..., { notify: null }); // Error funnel only
ui.notifications.error(...); // Separate notification
clean: true Only Works in NotificationOptionsIssue: { clean: true } sanitizes untrusted input, but ONLY in ui.notifications.*.
Does NOT work in Hooks.onError: The msg parameter is NOT sanitized by Hooks.onError.
Best practice:
msg in Hooks.onError generic (module prefix only): msg: "[YourModule]"ui.notifications.* call with { clean: true }// ❌ Bad - untrusted data in Hooks.onError msg
Hooks.onError(..., { msg: `[Module] ${userInput}` }); // Not sanitized!
// ✅ Good - untrusted data in ui.notifications with clean: true
Hooks.onError(..., { msg: "[Module]", notify: null });
ui.notifications.error(`[Module] ${userInput}`, { clean: true });
{ cause: err } Has Excellent SupportGood news: Error cause parameter (ES2022) is widely supported:
Usage:
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
Preserves original error context for debugging in modern browsers.
When adding error handling to your module:
grep -r "} catch" scripts/
console.log → console.error or Hooks.onError (minimum fix)notify: null) + ui.notifications.*ui.notifications.warn with console: false{ clean: true } on ui.notifications.* for user/document dataconsole: false (prevents noise + double-logging)msg (it's a prefix only)new Error(String(err), { cause: err })data to Hooks.onError for debuggingwarn severity used for expected validation errorsnotify: null)console: false)data appears in error hooks for debuggingVerified against Foundry V13 API (2025-12-31):
NotificationOptions.console: Optional boolean - "Whether to log the message to the console"NotificationOptions.clean: Optional boolean - "Whether to clean the provided message string as untrusted user input"Hooks.onError signature:
static onError(
location: string,
error: Error,
options?: {
data?: object;
log?: null | string;
msg?: string;
notify?: null | string;
}
): void
msg: String - "A message which should prefix the resulting error or notification"log: null | string - "The level at which to log the error to console (if at all)"notify: null | string - "The level at which to spawn a notification in the UI (if at all)"data: object - "Additional data to pass to the hook subscribers""info", "warn", "error" (all documented)notify: null: Explicitly documented as valid (suppresses UI notification){ cause: err }: Excellent browser support (ES2022, 4+ years available)console default value not explicitly stated (defensive console: false recommended)notify string values not enumerated (use notify: null + separate notifications)Hooks.onError mutates error.message (normalize to Error objects, separate funnel from notification){ cause: err } retains original error when wrapping non-Errorsdata parameter provides context to hook subscribers and debugging{ clean: true } on ui.notifications.* prevents XSS from error messagesconsole: false prevents duplicate entriesFor published modules, consider localizing error messages:
const message = game.i18n.format("YOUR_MODULE.Error.FailedToAddItem", {
error: err.message
});
ui.notifications.error(message, { clean: true, console: false });
Note: If using game.i18n.format, the format function returns a sanitized string, so clean: true may be redundant (but harmless).
foundry-vtt-performance-safe-updates - Multi-client update safety patternsfoundry-vtt-dialog-compat - DialogV2 Shadow DOM patternsfoundry-vtt-version-compat - API compatibility layer patterns// In blades-alternate-actor-sheet.js
import { queueUpdate } from "./lib/update-queue.js";
async _onItemCreate(event) {
event.preventDefault();
const itemType = event.currentTarget.dataset.itemType;
try {
// Attempt to create item
const itemData = {
name: `New ${itemType}`,
type: itemType,
system: {}
};
await queueUpdate(async () => {
await this.actor.createEmbeddedDocuments("Item", [itemData]);
});
ui.notifications.info(`[BitD-Alt] Created new ${itemType}`);
} catch (err) {
// Pattern 1: User-facing failure
const error = err instanceof Error ? err : new Error(String(err), { cause: err });
Hooks.onError("BitD-Alt.ItemCreate", error, {
msg: "[BitD-Alt]",
log: "error",
notify: null,
data: { itemType, actorId: this.actor.id }
});
ui.notifications.error(`[BitD-Alt] Failed to create ${itemType}`, {
clean: true,
console: false
});
}
}
Last Updated: 2025-12-31 Status: Production-Ready (Verified against Foundry V13 API) Maintainer: Claude Code (BitD Alternate Sheets)
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience