# Build a Multi-Author Blog URL: /docs/tutorials/sites/build-a-multi-author-blog/ Section: sites Tags: tutorial, intermediate, blog, authors -------------------------------------------------------------------------------- Build a Multi-Author Blog In this tutorial, you'll transform a single-author blog into a full multi-author publication with author profiles, individual author pages, and rich bylines. Tip Tip Default Theme Has This Built-In! Bengal's default theme includes complete multi-author support: Author pages with social links and post listings Author bylines in blog posts (enable content.author feature) Author index page listing all contributors If you're using the default theme, you may only need to add frontmatter โ€” no template work required! This tutorial shows both how to use the built-in features and how to customize them. Note Note Who is this for? This guide is for developers building team blogs, publication sites, or any content with multiple contributors. You should be familiar with Bengal basics from the Build a Blog tutorial. Goal By the end of this tutorial, you will have: Structured author data in frontmatter Rich author bylines with avatars and social links Dedicated author profile pages An author listing page Posts filtered by author Prerequisites A Bengal site (run bengal new site my-blog if starting fresh) Basic understanding of Kida templates Steps 1Define Author Metadata3 min Add structured author information to your blog posts. Bengal supports two ways to specify authors in frontmatter: Simple (name only): --- title: "Getting Started with Python" author: Jane Smith --- Structured (full profile): --- title: "Getting Started with Python" author: name: Jane Smith avatar: /images/authors/jane.jpg bio: Senior developer and tech writer twitter: janesmith github: janesmith linkedin: janesmith website: https://janesmith.dev --- Create a test post at content/blog/python-basics.md: --- title: "Python Basics for Beginners" date: 2024-01-15 draft: false author: name: Jane Smith avatar: /images/authors/jane.jpg bio: Senior developer specializing in Python and web technologies twitter: janesmith github: janesmith tags: ["python", "tutorial"] --- # Python Basics for Beginners Learn the fundamentals of Python programming... Tip Tip Avoid Repetition Instead of repeating full author data in every post, you can define authors in a data file and reference them. We'll cover this later. 2Create an Author Byline Component5 min Display author information on blog posts. Create a reusable author byline partial at templates/partials/author-byline.html: {# templates/partials/author-byline.html #} {% if page.author %} <div class="author-byline"> {% if page.author.avatar %} <img src="{{ page.author.avatar }}" alt="{{ page.author.name }}" class="author-avatar"> {% else %} <div class="author-avatar-placeholder"> {{ page.author.name[0] }} </div> {% end %} <div class="author-info"> <a href="/authors/{{ page.author.name | slugify }}/" class="author-name"> {{ page.author.name }} </a> {% if page.author.bio %} <p class="author-bio">{{ page.author.bio }}</p> {% end %} <div class="author-meta"> <time datetime="{{ page.date | date_iso }}"> {{ page.date | date('%B %d, %Y') }} </time> <span>ยท</span> <span>{{ page.reading_time }} min read</span> </div> <div class="author-social"> {% if page.author.twitter %} <a href="https://twitter.com/{{ page.author.twitter }}" aria-label="Twitter"> <svg class="icon"><use href="#icon-twitter"></use></svg> </a> {% end %} {% if page.author.github %} <a href="https://github.com/{{ page.author.github }}" aria-label="GitHub"> <svg class="icon"><use href="#icon-github"></use></svg> </a> {% end %} {% if page.author.linkedin %} <a href="https://linkedin.com/in/{{ page.author.linkedin }}" aria-label="LinkedIn"> <svg class="icon"><use href="#icon-linkedin"></use></svg> </a> {% end %} </div> </div> </div> {% end %} Add matching styles in assets/css/author.css: .author-byline { display: flex; gap: 1rem; padding: 1.5rem 0; border-bottom: 1px solid var(--border-color); margin-bottom: 2rem; } .author-avatar { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; } .author-avatar-placeholder { width: 64px; height: 64px; border-radius: 50%; background: var(--accent); color: white; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; font-weight: bold; } .author-name { font-weight: 600; font-size: 1.125rem; text-decoration: none; color: var(--text); } .author-name:hover { color: var(--accent); } .author-bio { font-size: 0.875rem; color: var(--text-muted); margin: 0.25rem 0; } .author-meta { font-size: 0.875rem; color: var(--text-muted); display: flex; gap: 0.5rem; } .author-social { display: flex; gap: 0.75rem; margin-top: 0.5rem; } .author-social a { color: var(--text-muted); } .author-social a:hover { color: var(--accent); } .author-social .icon { width: 18px; height: 18px; } Include the byline in your blog post template (templates/blog/single.html): {% extends "default/single.html" %} {% block article_header %} <header class="article-header"> <h1>{{ page.title }}</h1> {% include "partials/author-byline.html" %} </header> {% end %} 3Create Author Profile Pages5 min Build individual pages for each author. Create an author directory structure: mkdir -p content/authors Create a profile for Jane at content/authors/jane-smith.md: --- title: Jane Smith type: author avatar: /images/authors/jane.jpg bio: | Jane is a senior developer with 10 years of experience in Python and web technologies. She writes about best practices, tutorials, and the joy of clean code. location: San Francisco, CA twitter: janesmith github: janesmith linkedin: janesmith website: https://janesmith.dev --- ## About Jane Jane has been writing code since she was 12 years old. She's passionate about developer experience and making complex topics accessible. ## Interests - Python ecosystem - Web performance - Developer tools - Teaching and mentoring Create the author page template at templates/authors/single.html: {% extends "default/base.html" %} {% block content %} <article class="author-profile"> <header class="author-header"> {% if page.metadata.avatar %} <img src="{{ page.metadata.avatar }}" alt="{{ page.title }}" class="author-profile-avatar"> {% end %} <div class="author-header-info"> <h1>{{ page.title }}</h1> {% if page.metadata.location %} <p class="author-location">๐Ÿ“ {{ page.metadata.location }}</p> {% end %} {% if page.metadata.bio %} <p class="author-bio-long">{{ page.metadata.bio }}</p> {% end %} <div class="author-links"> {% if page.metadata.website %} <a href="{{ page.metadata.website }}">Website</a> {% end %} {% if page.metadata.twitter %} <a href="https://twitter.com/{{ page.metadata.twitter }}">Twitter</a> {% end %} {% if page.metadata.github %} <a href="https://github.com/{{ page.metadata.github }}">GitHub</a> {% end %} </div> </div> </header> <div class="author-content"> {{ page.content | safe }} </div> <section class="author-posts"> <h2>Posts by {{ page.title }}</h2> {# Use author index for O(1) lookup (faster than where filter) #} {% let author_posts_refs = site.indexes.author.get(page.title) | list %} {% let author_posts = author_posts_refs | resolve_pages | sort_by('date', reverse=true) %} {% if author_posts %} <ul class="post-list"> {% for post in author_posts %} <li> <a href="{{ post.href }}">{{ post.title }}</a> <time>{{ post.date | date('%B %d, %Y') }}</time> </li> {% end %} </ul> {% else %} <p>No posts yet.</p> {% end %} </section> </article> {% end %} Tip Tip Performance Tip: Use Author Index The site.indexes.author provides O(1) lookup for author posts, which is much faster than filtering all pages with where('author.name', ...). The author index is automatically built during site generation and maps author names to their post references. For even more advanced use cases, consider using the author_view filter (see the default theme implementation for examples). Add styles to assets/css/author.css: .author-profile { max-width: 800px; margin: 0 auto; } .author-header { display: flex; gap: 2rem; padding-bottom: 2rem; border-bottom: 1px solid var(--border-color); margin-bottom: 2rem; } .author-profile-avatar { width: 150px; height: 150px; border-radius: 50%; object-fit: cover; } .author-header-info h1 { margin: 0 0 0.5rem; } .author-location { color: var(--text-muted); margin: 0.5rem 0; } .author-bio-long { font-size: 1.125rem; line-height: 1.6; } .author-links { display: flex; gap: 1rem; margin-top: 1rem; } .author-links a { padding: 0.5rem 1rem; background: var(--bg-secondary); border-radius: 4px; text-decoration: none; } .author-posts { margin-top: 3rem; } .post-list { list-style: none; padding: 0; } .post-list li { display: flex; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid var(--border-color); } .post-list time { color: var(--text-muted); font-size: 0.875rem; } 4Create an Authors Listing Page3 min Display all authors on a team page. Create content/authors/_index.md: --- title: Our Authors description: Meet the writers behind our content type: authors --- # Our Authors Meet the talented people who contribute to this blog. Create the listing template at templates/authors/list.html: {% extends "default/base.html" %} {% block content %} <div class="authors-page"> <header> <h1>{{ page.title }}</h1> {% if page.description %} <p class="lead">{{ page.description }}</p> {% end %} </header> <div class="authors-grid"> {% for author in section.pages | sort_by('title') %} <a href="{{ author.href }}" class="author-card"> {% if author.metadata.avatar %} <img src="{{ author.metadata.avatar }}" alt="{{ author.title }}" class="author-card-avatar"> {% else %} <div class="author-card-avatar-placeholder"> {{ author.title[0] }} </div> {% end %} <h2>{{ author.title }}</h2> {% if author.metadata.bio %} <p>{{ author.metadata.bio | truncate(100) }}</p> {% end %} {# Use author index for O(1) lookup (faster than where filter) #} {% let post_count = site.indexes.author.get(author.title) | length %} <span class="author-post-count">{{ post_count }} posts</span> </a> {% end %} </div> </div> {% end %} Add grid styles: .authors-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 2rem; } .author-card { display: block; padding: 1.5rem; background: var(--bg-secondary); border-radius: 8px; text-decoration: none; color: var(--text); transition: transform 0.2s, box-shadow 0.2s; } .author-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .author-card-avatar { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; margin-bottom: 1rem; } .author-card h2 { margin: 0 0 0.5rem; font-size: 1.25rem; } .author-card p { font-size: 0.875rem; color: var(--text-muted); margin: 0 0 1rem; } .author-post-count { font-size: 0.75rem; color: var(--accent); font-weight: 600; } 5Support Multiple Authors per Post3 min Handle collaborative posts with multiple authors. For posts with multiple authors, use the authors field: --- title: "Collaborative Guide to Testing" date: 2024-01-20 authors: - name: Jane Smith avatar: /images/authors/jane.jpg - name: John Doe avatar: /images/authors/john.jpg --- Update your byline partial to handle multiple authors: {# templates/partials/author-byline.html #} {% if page.authors %} <div class="authors-byline"> <span class="byline-label">Written by</span> <div class="authors-list"> {% for author in page.authors %} <a href="/authors/{{ author.name | slugify }}/" class="author-chip"> {% if author.avatar %} <img src="{{ author.avatar }}" alt="{{ author.name }}"> {% end %} <span>{{ author.name }}</span> </a> {% end %} </div> <div class="post-meta"> <time>{{ page.date | date('%B %d, %Y') }}</time> <span>ยท</span> <span>{{ page.reading_time }} min read</span> </div> </div> {% elif page.author %} {# Single author - use existing byline #} <div class="author-byline"> {# ... existing single author code ... #} </div> {% end %} Add styles for author chips: .authors-byline { padding: 1rem 0; border-bottom: 1px solid var(--border-color); } .byline-label { font-size: 0.875rem; color: var(--text-muted); } .authors-list { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0.5rem 0; } .author-chip { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0.75rem 0.25rem 0.25rem; background: var(--bg-secondary); border-radius: 999px; text-decoration: none; color: var(--text); font-size: 0.875rem; } .author-chip img { width: 28px; height: 28px; border-radius: 50%; } .author-chip:hover { background: var(--bg-tertiary); } 6Add Author Data File (Optional)Optional 3 min Centralize author data to avoid repetition. For larger teams, define authors in a data file instead of repeating info in every post. Create data/authors.yaml: jane-smith: name: Jane Smith avatar: /images/authors/jane.jpg bio: Senior developer specializing in Python twitter: janesmith github: janesmith john-doe: name: John Doe avatar: /images/authors/john.jpg bio: Frontend architect and design systems expert twitter: johndoe github: johndoe Reference by key in frontmatter: --- title: "My Post" author: jane-smith # References data/authors.yaml --- You'll need a custom template function to resolve this: # In a build hook or custom extension def get_author(key): # site.data supports safe access - returns empty dict if missing return site.data.authors[key] if key in site.data.authors else None Note Note This advanced pattern requires a build hook. See Build Hooks for details. Summary You now have a fully functional multi-author blog with: โœ… Structured author data in frontmatter โœ… Rich bylines with avatars and social links โœ… Individual author profile pages โœ… Team listing page โœ… Support for collaborative posts Next Steps Author Byline Recipe: Quick reference for byline patterns Social Sharing: Let authors share their posts Content Freshness: Show post age indicators -------------------------------------------------------------------------------- Metadata: - Author: lbliii - Word Count: 1776 - Reading Time: 9 minutes