Starter Kit - Ships with your template. You own it - modify freely.
Overview
Conductor includes four beautifully designed error pages out of the box. Each page features:
- Modern, responsive design with gradient backgrounds
- Clear error messaging with status codes
- Actionable buttons (Home, Back, Sign In, Try Again)
- SEO-friendly meta tags (noindex, nofollow)
- Support for custom messages and dynamic content
- Consistent styling with smooth animations
All error pages are built using the html operation with Liquid templates and embedded CSS, making them easy to customize without external dependencies.
Available Error Pages
401 - Unauthorized
Route: /errors/401
Displayed when authentication is required. Features a lock icon and amber gradient.
Custom Parameters:
message - Override default message
Example Usage:
flow:
- name: check-auth
operation: code
handler: ./check-auth.ts
- name: unauthorized
condition: ${!check-auth.output.authenticated}
operation: http
config:
url: /errors/401?message=${encodeURIComponent('Please log in to continue')}
method: GET
Actions:
- Sign In button (redirects to
/login)
- Go Home button (redirects to
/)
403 - Forbidden
Route: /errors/403
Displayed when access is denied due to insufficient permissions. Features red gradient.
Custom Parameters:
message - Override default message
contactEmail - Display support contact information
Example Usage:
flow:
- name: check-permission
operation: code
handler: ./check-permission.ts
- name: forbidden
condition: ${!check-permission.output.authorized}
operation: http
config:
url: /errors/403?message=Admin%20access%20required&[email protected]
method: GET
Actions:
- Go Home button
- Log In button (redirects to
/login)
- Optional support email link
404 - Not Found
Route: /errors/404
Displayed when a page or resource doesn’t exist. Features purple gradient.
Custom Parameters:
message - Override default message
searchEnabled - Show search form (boolean)
helpfulLinks - Array of helpful navigation links
Default Helpful Links:
helpfulLinks:
- title: Home
url: /
- title: Documentation
url: /docs
- title: Contact Support
url: /support
Example Usage:
output:
- when: ${not-found}
redirect:
url: /errors/404?searchEnabled=true
status: 302
Actions:
- Go Home button
- Go Back button (browser history)
- Optional search form (when
searchEnabled=true)
- Customizable helpful links list
500 - Internal Server Error
Route: /errors/500
Displayed when server errors occur. Features pink-to-blue gradient.
Custom Parameters:
message - Override default message
errorId - Unique error identifier for support tracking
errorStack - Stack trace (only shown when dev=true)
dev - Enable development mode to show error details
supportEmail - Display support contact
Example Usage:
flow:
- name: process-data
operation: code
handler: ./process.ts
retry:
maxAttempts: 3
backoff: exponential
- name: server-error
condition: ${process-data.failed}
operation: http
config:
url: /errors/500?errorId=${generateId()}&[email protected]
method: GET
Actions:
- Go Home button
- Try Again button (reloads page)
- Error ID display (for support reference)
- Optional error details panel (dev mode only)
- Optional support email link
Customization
Changing Styles
Error pages use inline CSS for zero dependencies. To customize the look:
- Locate the error page YAML:
ensembles/system/errors/404.yaml
- Edit the
styles section in the agent config:
agents:
- name: render-404
operation: html
config:
styles: |
body {
background: linear-gradient(135deg, #your-color 0%, #your-color-2 100%);
}
.error-code {
font-size: 8rem; /* Make code bigger */
}
- Deploy changes:
Adding Search
Enable search on the 404 page by passing searchEnabled=true:
output:
- when: ${page-not-found}
redirect:
url: /errors/404?searchEnabled=true
status: 302
Customize search endpoint:
# In ensembles/system/errors/404.yaml
template: |
<form action="/your-search-endpoint" method="get">
<input type="text" name="q" placeholder="Search..." />
<button type="submit">Search</button>
</form>
Customizing Helpful Links
Pass custom links to the 404 page:
input:
helpfulLinks:
- title: API Documentation
url: /api/docs
- title: Status Page
url: https://status.example.com
- title: Community Forum
url: https://forum.example.com
Adding Brand Logo
Add your logo to any error page:
template: |
<div class="error-page">
<div class="error-content">
<img src="/logo.png" alt="Logo" class="error-logo" />
<div class="error-code">404</div>
<!-- rest of template -->
Add corresponding CSS:
styles: |
.error-logo {
width: 120px;
margin-bottom: 2rem;
}
Integration
How Errors Are Triggered
Error pages are automatically displayed in several scenarios:
1. Ensemble Output Redirects
Redirect to an error page based on conditions:
output:
# Not found
- when: ${!lookup.output.found}
redirect:
url: /errors/404
status: 302
# Unauthorized
- when: ${!auth.output.authenticated}
redirect:
url: /errors/401
status: 302
# Forbidden
- when: ${!auth.output.authorized}
redirect:
url: /errors/403
status: 302
2. Conditional HTTP Responses
Return error page content directly:
flow:
- name: fetch-error-page
condition: ${validation.failed}
operation: http
config:
url: /errors/400?message=Invalid%20input
method: GET
output:
status: ${validation.failed ? 400 : 200}
body: ${validation.failed ? fetch-error-page.output : data.output}
3. Agent Failure Handling
Handle agent failures gracefully:
flow:
- name: call-external-api
operation: http
config:
url: https://api.example.com/data
method: GET
retry:
maxAttempts: 3
backoff: exponential
- name: handle-failure
condition: ${call-external-api.failed}
operation: http
config:
url: /errors/500?errorId=${generateId()}
method: GET
output:
- when: ${call-external-api.success}
status: 200
body: ${call-external-api.output}
- when: ${call-external-api.failed}
status: 500
body: ${handle-failure.output}
4. Custom Error Handling in Code Operations
Trigger errors from TypeScript handlers:
// agents/user/my-handler/handler.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function handler(ctx: AgentExecutionContext) {
const { userId } = ctx.input
// Not found
if (!userId) {
return ctx.redirect('/errors/404?message=User%20not%20found')
}
// Check permissions
const hasPermission = await checkPermission(userId)
if (!hasPermission) {
return ctx.redirect('/errors/403?message=Insufficient%20permissions')
}
try {
const data = await fetchData(userId)
return { data }
} catch (error) {
// Server error
const errorId = generateErrorId()
await logError(errorId, error)
return ctx.redirect(`/errors/500?errorId=${errorId}`)
}
}
Error Tracking
Log errors before redirecting for monitoring:
flow:
- name: process-request
operation: code
handler: ./process.ts
- name: log-error
condition: ${process-request.failed}
operation: data
config:
backend: d1
binding: DB
query: |
INSERT INTO error_logs (error_id, timestamp, message, stack)
VALUES (?, ?, ?, ?)
params:
- ${generateId()}
- ${Date.now()}
- ${process-request.error.message}
- ${process-request.error.stack}
- name: show-error
condition: ${process-request.failed}
operation: http
config:
url: /errors/500?errorId=${log-error.output.insertId}
method: GET
Full 404 Page Reference
Here’s the complete YAML for the 404 error page:
name: error-404
description: 404 Not Found error page
trigger:
- type: http
path: /errors/404
methods: [GET]
public: true
responses:
html:
enabled: true
json:
enabled: true
agents:
- name: render-404
operation: html
config:
templateEngine: liquid
template: |
<div class="error-page">
<div class="error-content">
<div class="error-code">404</div>
<h1>Page Not Found</h1>
<p class="error-message">
{% if message %}
{{message}}
{% else %}
The page you're looking for doesn't exist or has been moved.
{% endif %}
</p>
<div class="error-actions">
<a href="/" class="btn-primary">Go Home</a>
<a href="javascript:history.back()" class="btn-secondary">Go Back</a>
</div>
{% if searchEnabled %}
<div class="error-search">
<form action="/search" method="get">
<input type="text" name="q" placeholder="Search for what you need..." />
<button type="submit">Search</button>
</form>
</div>
{% endif %}
{% if helpfulLinks %}
<div class="helpful-links">
<h2>Helpful Links</h2>
<ul>
{% for link in helpfulLinks %}
<li><a href="{{link.url}}">{{link.title}}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
</div>
styles: |
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
}
.error-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.error-content {
background: white;
border-radius: 16px;
padding: 3rem;
max-width: 600px;
width: 100%;
text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.error-code {
font-size: 6rem;
font-weight: 900;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 1rem;
line-height: 1;
}
h1 {
font-size: 2rem;
margin: 0 0 1rem 0;
color: #2d3748;
}
.error-message {
font-size: 1.125rem;
color: #718096;
margin-bottom: 2rem;
line-height: 1.6;
}
.error-actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.btn-primary, .btn-secondary {
padding: 0.875rem 2rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s;
display: inline-block;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #f7fafc;
color: #667eea;
border: 2px solid #667eea;
}
.btn-secondary:hover {
background: #edf2f7;
}
.error-search {
margin: 2rem 0;
padding: 2rem 0;
border-top: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
}
.error-search form {
display: flex;
gap: 0.5rem;
max-width: 400px;
margin: 0 auto;
}
.error-search input {
flex: 1;
padding: 0.75rem;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 1rem;
}
.error-search button {
padding: 0.75rem 1.5rem;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
}
.helpful-links {
text-align: left;
margin-top: 2rem;
}
.helpful-links h2 {
font-size: 1.25rem;
margin-bottom: 1rem;
color: #2d3748;
}
.helpful-links ul {
list-style: none;
padding: 0;
margin: 0;
}
.helpful-links li {
margin-bottom: 0.75rem;
}
.helpful-links a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.helpful-links a:hover {
text-decoration: underline;
}
seo:
title: "404 - Page Not Found"
description: "The page you're looking for could not be found"
robots: noindex, nofollow
meta:
- name: viewport
content: width=device-width, initial-scale=1
- http-equiv: status
content: "404"
input:
message: $input.message
searchEnabled: $input.searchEnabled
helpfulLinks: $input.helpfulLinks
input:
message: null
searchEnabled: false
helpfulLinks:
- title: Home
url: /
- title: Documentation
url: /docs
- title: Contact Support
url: /support
output:
html: ${render-404.output}
Related Pages