AQL Pages

Build dynamic pages using ArangoDB Query Language (AQL) that return component structures rendered automatically.

code
AQL Powered

Write pages in pure AQL

widgets
Components

Pre-built UI components

auto_awesome
Dynamic

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.

folder

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.

alert Notification messages
{
  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
}
card Content containers
{
  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
}
bignumber KPI / Metric display
{
  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'
}
chart Data visualization
{
  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]
}
table Data tables
{
  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: [...]
}
map Interactive maps
{
  component: 'map',
  title: 'Locations',
  width: [6, 12, 12],
  markers: [
    {
      latitude: 51.505,
      longitude: -0.09,
      content: 'London Office'
    }
  ]
}
breadcrumb Navigation path
{
  component: 'breadcrumb',
  items: [
    { label: 'Home', url: '/' },
    { label: 'Dashboard', url: '/dashboard' },
    { label: 'Reports' }
  ]
}
button Action buttons
{
  component: 'button',
  title: 'Submit',
  color: 'blue',
  icon: 'check_circle',
  width: [4, 6, 12],
  type: 'button',         // button, submit
  disabled: false
}
object Object detail view
{
  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
col-4
col-4
col-4
col-6
col-6
col-12

add_circle Creating Custom Components

You can create your own components by adding etlua template files to the app/views/partials/components/ directory.

create_new_folder

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!
code

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>
play_arrow

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:

check_circle The component value in AQL matches the filename (without .html.etlua)
check_circle All properties passed in AQL become available as component.property in the template
check_circle Use component.property or default for optional properties with defaults
lightbulb

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.