# ⚠️ Error Handling and Security
---
## 🎯 Runtime error behaviour
Clera never throws errors that crash the app. All internal errors are caught, logged, and handled gracefully:
- **Missing action:** logs a warning, no-ops
- **Missing DOM element:** logs a warning, no-ops
- **Lifecycle error:** logs the error, continues
- **Plugin install error:** logs the error, skips the plugin
- **Hardware capability error:** rejects the Promise with a clear message
Your own action functions are wrapped in try/catch. An unhandled exception inside an action logs the error but does not crash the page:
```
[CLERA:ACTION_THROW] Action "addTask" threw (page "home").
```
---
## ⚡ Handling errors in async actions
Clera catches synchronous throws automatically. For async actions, unhandled Promise rejections are also caught and logged. But for user-facing errors, handle them explicitly:
```js
async function loadData(context) {
try {
const response = await fetch("/api/data");
if (!response.ok) throw new Error("Server error");
const data = await response.json();
context.render("#content", buildHtml(data));
} catch (error) {
context.render("#content", `
Failed to load data.
`);
}
}
```
---
## 📱 Hardware capability errors
Always handle hardware rejections. The user may deny permission, cancel the picker, or be on a device that does not support the feature:
```js
async function pickPhoto(context) {
try {
const result = await app.hardware.files.pick({ accept: "image/*" });
showPhoto(result.files[0].uri, context);
} catch (error) {
if (error.message !== "File picker cancelled.") {
context.render("#status", "Could not access files.");
}
}
}
```
---
## 📝 Security notes
### innerHTML and user content
`context.render()`, `append()`, and `clear()` all use `innerHTML`. If you render user-supplied content, sanitize it first to prevent XSS:
```js
function escapeHtml(text) {
return text
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
context.render("#messages", messages.map(msg =>
`
${escapeHtml(msg.text)}
`
).join(""));
```
Never render raw user input directly into innerHTML without escaping.
### localStorage
`localStorage` data is readable by any JavaScript on the same origin. Do not store sensitive data (tokens, passwords, private user data) in localStorage. Use `sessionStorage` or secure HttpOnly cookies for sensitive values.
### 🌐 CSRF with app.php()
For backend requests, use the built-in CSRF support:
```js
app.start({
php: {
baseUrl: "https://api.example.com",
csrf: {
header: "X-CSRF-Token",
token: () => document.querySelector('meta[name="csrf-token"]').content
}
}
});
```
---
## Next
[05 Deployment](./05-deployment.md)