AQL Pages
Build dynamic pages using ArangoDB Query Language (AQL) that return component structures rendered automatically.
Write pages in pure AQL
Pre-built UI components
Query data and render in one file
description How It Works
AQL Pages are .aql files in the aqlpages/ directory. They return an array of component definitions that get automatically rendered.
File Structure
aqlpages/demo.aql → accessible at /demo
aqlpages/
├── demo.aql # → /demo
├── dashboard.aql # → /dashboard
└── reports.aql # → /reports
code Basic Example
An AQL page defines components as JSON objects and returns them as an array.
// Define a page component
LET page = {
component: 'page',
title: 'My Dashboard'
}
// Define an alert
LET alert = {
component: 'alert',
title: 'Welcome!',
description: 'This is your dashboard',
color: 'green',
width: [12, 12, 12],
icon: 'check_circle'
}
// Return components as array
RETURN [page, alert]
widgets Available Components
AQL Pages come with pre-built components for common UI patterns.
{
component: 'alert',
title: 'Warning',
description: 'Something needs attention',
color: 'yellow', // red, blue, green, yellow, purple, pink, gray
width: [6, 12, 12], // [lg, md, sm] grid columns
icon: 'warning' // Material Icons name
}
{
component: 'card',
title: 'Card Title',
description: 'Card content here',
top_image: 'https://example.com/image.jpg',
width: [4, 6, 12],
icon: 'info',
components: [...] // Nested components
}
{
component: 'bignumber',
title: 'Total Sales',
description: 'Last 12 months',
value: 10000,
extra: '€', // Currency or unit
color: 'green',
width: [4, 6, 12],
percentage: 90, // Progress indicator
icon: 'paid'
}
{
component: 'chart',
title: 'Monthly Sales',
type: 'line', // line, bar, radar, pie, etc.
labels: ["Jan", "Feb", "Mar", "Apr"],
series: [
{
name: 'Revenue',
data: [100, 200, 150, 300]
}
],
width: [6, 12, 12]
}
{
component: 'table',
title: 'Users',
width: [12, 12, 12],
caption: { title: 'User list', position: 'bottom' },
headers: [
{ title: 'Name', align: 'left' },
{ title: 'Email', align: 'left' },
{ title: 'Status', align: 'center' }
],
rows: [
[
{ title: 'John', align: 'left' },
{ title: 'john@example.com', align: 'left' },
{ title: 'Active', align: 'center', color: 'green' }
]
],
footer: [...]
}
{
component: 'map',
title: 'Locations',
width: [6, 12, 12],
markers: [
{
latitude: 51.505,
longitude: -0.09,
content: 'London Office'
}
]
}
{
component: 'breadcrumb',
items: [
{ label: 'Home', url: '/' },
{ label: 'Dashboard', url: '/dashboard' },
{ label: 'Reports' }
]
}
{
component: 'button',
title: 'Submit',
color: 'blue',
icon: 'check_circle',
width: [4, 6, 12],
type: 'button', // button, submit
disabled: false
}
{
component: 'object',
title: 'User Details',
description: 'User information',
width: [4, 6, 12],
icon: 'person',
color: 'blue',
data: {
name: 'John Doe',
email: 'john@example.com',
role: 'Admin'
},
keys: ['name', 'email', 'role'],
components: [...] // Nested components
}
autorenew Dynamic Data with AQL
The power of AQL Pages is combining database queries with component rendering.
// Generate cards from database query
LET cards = (
FOR product IN products
FILTER product.active == true
LIMIT 6
RETURN {
component: 'card',
title: product.name,
description: product.description,
top_image: product.image_url,
width: [4, 6, 12]
}
)
// Calculate KPIs from data
LET total_sales = (
FOR order IN orders
FILTER order.status == 'completed'
COLLECT AGGREGATE total = SUM(order.amount)
RETURN total
)[0]
LET sales_kpi = {
component: 'bignumber',
title: 'Total Sales',
value: total_sales,
extra: '€',
color: 'green',
width: [4, 6, 12],
icon: 'paid'
}
RETURN [sales_kpi, cards]
grid_view Responsive Grid
The width property controls responsive grid columns using a 12-column system.
// width: [lg, md, sm]
width: [4, 6, 12] // 3 per row on large, 2 on medium, 1 on small
width: [6, 6, 12] // 2 per row on large/medium, 1 on small
width: [12, 12, 12] // Full width on all screens
add_circle Creating Custom Components
You can create your own components by adding etlua template files to the app/views/partials/components/ directory.
Component File Structure
Each component is an etlua template
app/views/partials/components/
├── alert.html.etlua # Built-in
├── card.html.etlua # Built-in
├── chart.html.etlua # Built-in
├── ...
└── mycomponent.html.etlua # Your custom component!
Example: Custom Stats Component
app/views/partials/components/stats.html.etlua
<% component.width = component.width or {4, 6, 12} %>
<div class="lg:col-span-<%= component.width[1] %> md:col-span-<%= component.width[2] %> col-span-<%= component.width[3] %>">
<div class="p-6 rounded-xl bg-<%= component.color or 'blue' %>-500/10 border border-<%= component.color or 'blue' %>-500/20">
<% if component.icon then %>
<span class="material-icons-outlined text-<%= component.color or 'blue' %>-400 text-3xl">
<%= component.icon %>
</span>
<% end %>
<div class="mt-4">
<p class="text-sm text-slate-400"><%= component.label %></p>
<p class="text-3xl font-bold text-white">
<%= component.value %><%= component.suffix or '' %>
</p>
<% if component.change then %>
<p class="text-sm mt-2 <%= component.change > 0 and 'text-green-400' or 'text-red-400' %>">
<%= component.change > 0 and '↑' or '↓' %> <%= math.abs(component.change) %>%
</p>
<% end %>
</div>
</div>
</div>
Using Your Custom Component
In your AQL page
LET my_stats = {
component: 'stats', // Matches filename: stats.html.etlua
label: 'Monthly Revenue',
value: 45000,
suffix: '€',
icon: 'trending_up',
color: 'green',
change: 12.5, // +12.5% indicator
width: [4, 6, 12]
}
RETURN [my_stats]
info Component naming convention:
component value in AQL matches the filename (without .html.etlua)
component.property in the template
component.property or default for optional properties with defaults
Pro Tips
Nesting: Components like card and object support nested components arrays for complex layouts.
Icons: Use Material Icons Outlined names for the icon property.
Performance: AQL queries are executed server-side, so you can aggregate complex data without multiple API calls.