AdonisJS Open Graph Tags: Add OG to Your Edge.js Templates

Add og:title, og:image, og:description and Twitter card tags to AdonisJS apps using Edge.js view templates — with practical copy-paste examples.

AdonisJS + Edge.js: server-rendered OG tags

AdonisJS uses Edge.js as its template engine. Since Edge templates are rendered on the server, social crawlers receive fully-rendered HTML with all OG meta tags — no JavaScript execution required. This makes AdonisJS a great choice for pages that need correct social previews.

Layout template with OG tags

Create a base layout that includes OG tag slots with sensible defaults:

{{-- resources/views/layouts/main.edge --}}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>{{ ogTitle ?? 'My AdonisJS App' }}</title>

  {{-- Open Graph --}}
  <meta property="og:title"       content="{{ ogTitle ?? 'My AdonisJS App' }}" />
  <meta property="og:description" content="{{ ogDescription ?? 'Default description.' }}" />
  <meta property="og:image"       content="{{ ogImage ?? 'https://yourdomain.com/og/default.png' }}" />
  <meta property="og:url"         content="{{ ogUrl ?? request.completeUrl() }}" />
  <meta property="og:type"        content="{{ ogType ?? 'website' }}" />

  {{-- Twitter Card --}}
  <meta name="twitter:card"        content="summary_large_image" />
  <meta name="twitter:title"       content="{{ ogTitle ?? 'My AdonisJS App' }}" />
  <meta name="twitter:description" content="{{ ogDescription ?? 'Default description.' }}" />
  <meta name="twitter:image"       content="{{ ogImage ?? 'https://yourdomain.com/og/default.png' }}" />
</head>
<body>
  @!section('content')
</body>
</html>

Controller: passing OG data to views

// app/controllers/posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'

export default class PostsController {
  async show({ params, view }: HttpContext) {
    const post = await Post.findByOrFail('slug', params.slug)

    return view.render('posts/show', {
      post,
      ogTitle: post.title,
      ogDescription: post.excerpt,
      ogImage: `https://yourdomain.com/og/${post.slug}.png`,
      ogUrl: `https://yourdomain.com/blog/${post.slug}`,
      ogType: 'article',
    })
  }

  async index({ view }: HttpContext) {
    const posts = await Post.query().orderBy('created_at', 'desc')

    return view.render('posts/index', {
      posts,
      ogTitle: 'Blog — My AdonisJS App',
      ogDescription: 'Read our latest articles.',
      ogImage: 'https://yourdomain.com/og/blog.png',
      ogUrl: 'https://yourdomain.com/blog',
    })
  }
}

Page template using the layout

{{-- resources/views/posts/show.edge --}}
@layout('layouts/main')

@section('content')
  <article>
    <h1>{{ post.title }}</h1>
    <p>{{ post.excerpt }}</p>
    <div>{{{ post.html }}}</div>
  </article>
@end

Edge.js component for reusable OG tags

Extract OG tags into a reusable Edge component:

{{-- resources/views/components/og-tags.edge --}}
@component()
@prop('title')
@prop('description')
@prop('image')
@prop('url')
@prop('type', 'website')

<meta property="og:title"       content="{{ title }}" />
<meta property="og:description" content="{{ description }}" />
<meta property="og:image"       content="{{ image }}" />
<meta property="og:url"         content="{{ url }}" />
<meta property="og:type"        content="{{ type }}" />
<meta name="twitter:card"       content="summary_large_image" />
<meta name="twitter:title"      content="{{ title }}" />
<meta name="twitter:description" content="{{ description }}" />
<meta name="twitter:image"      content="{{ image }}" />
@end

{{-- Usage in layout: --}}
<head>
  <title>{{ ogTitle }}</title>
  @!component('components/og-tags', {
    title: ogTitle,
    description: ogDescription,
    image: ogImage,
    url: ogUrl,
    type: ogType
  })
</head>

AdonisJS middleware for default OG

Set global OG defaults via middleware so every route has a fallback:

// app/middleware/og_defaults_middleware.ts
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'

export default class OgDefaultsMiddleware {
  async handle({ request, view }: HttpContext, next: NextFn) {
    view.share({
      ogTitle: 'My AdonisJS App',
      ogDescription: 'Default site description.',
      ogImage: 'https://yourdomain.com/og/default.png',
      ogUrl: request.completeUrl(),
      ogType: 'website',
    })

    return next()
  }
}

// start/kernel.ts
router.use([
  () => import('#middleware/og_defaults_middleware'),
])

// Now any controller can override specific values:
// return view.render('page', { ogTitle: 'Custom Title' })

Verify your AdonisJS OG tags

After deploying, paste your URL into OGFixer to preview how Twitter, Slack, Discord, and LinkedIn will render your links.

Related guides