Otto Guide
From zero to a published Otto site, then the AsciiDoc syntax you'll use day to day.
Contents
Quickstart
gem install ottogenmkdir mysite && cd mysiteotto initotto buildotto serve
Open
http://127.0.0.1:8778/.
You should see the welcome page wrapped in the
default layout.
Layouts
Layouts live in
_layouts/. The
default scaffold gives you
_layouts/default.html.erb:
<!DOCTYPE html><html><head><title><%= page.title %></title></head><body><%= content %></body></html>
Layouts are ERB templates with three locals:
-
content— the rendered AsciiDoc body of the page or post -
page— the page/post object (front matter, url, etc.) -
site— the site config and data (title, posts, collections, etc.)
Layouts can chain by declaring their own front matter:
---layout: default---<article><%= content %></article>
A page using
layout: post will
be wrapped in
_layouts/post.html.erb,
which is itself wrapped in
_layouts/default.html.erb.
Includes (partials)
Drop reusable HTML/ERB into
_includes/, then
call
<%= partial 'name.html' %>
from any layout or other partial:
_includes/header.htmlfooter.html
Partials see the same
site,
page, and
content as the
calling layout.
Data files
Anything in
_data/ is exposed
as
site.data.<filename>:
# _data/nav.yml- title: Homeurl: /- title: Abouturl: /about
<nav><% site.data.nav.each do |item| %><a href="<%= item['url'] %>"><%= item['title'] %></a><% end %></nav>
YAML and JSON are both supported.
Collections
Declare a collection in
config.yml and
create a matching
_<name>/
directory:
collections:recipes:output: true
Files in
_recipes/*.adoc
become
site.recipes (an
array of items). With
output: true they
render to
/recipes/<slug>.html.
With
output: false
they're available to layouts but not written to disk.
Permalinks
Customize URLs per-document:
---permalink: /custom/path.html---
Or set a global default for posts in
config.yml:
permalink: /:year/:month/:day/:slug/
Templates ending with
/ produce pretty
URLs by writing
<path>/index.html.
Available tokens:
:year,
:month,
:day,
:slug,
:title.
Drafts
Drafts live in
_drafts/<slug>.adoc
(no date prefix). They're excluded by default. Include
them with the
--drafts flag:
otto build --draftsotto watch --drafts
When included, drafts use today's date and sort to the
top of
site.posts.
Health checks
otto doctor
verifies the project layout and reports any missing
files.
AsciiDoc primer
A working subset of AsciiDoc to get you productive. Full reference: docs.asciidoctor.org.
Document structure
= Document title:author: Ada Lovelace:revdate: 2026-05-01== Section 1A paragraph.== Section 2Another paragraph.
= Title
is the document title.
==,
===,
etc. are subsections. Attributes
(:key: value)
below the title set document-wide variables.
Paragraphs and text formatting
A normal paragraph. *Bold*, _italic_, `monospace`, ~subscript~, ^superscript^.A second paragraph separated by a blank line.
Links
https://example.com[Example]link:about.html[About this site]
Lists
* unordered* bullet** nested. ordered. one. twoterm:: definitionanother term:: another definition
Code blocks
[source,ruby]----def helloputs "world"end----
Admonitions
NOTE: A short note in line.[WARNING]====A longer warning blockthat spans multiple lines.====
Available levels:
NOTE,
TIP,
IMPORTANT,
WARNING,
CAUTION.
Tables
|===| Column 1 | Column 2| cell A1 | cell A2| cell B1 | cell B2|===
Images
image::pictures/cat.jpg[A cat, 400, 300]
The first argument is alt text; numbers are width and height.
Includes
include::shared/disclaimer.adoc[]
AsciiDoc includes pull file content in at conversion time. Paths are relative to the including file.
Attributes in content
Reference site and page metadata anywhere in your body:
This is {site_title}, written by {site_author}.You're reading "{page_title}" — see all posts at {site_url}/blog.
Front matter keys are prefixed with
site_
(from
config.yml)
or
page_
(from the document's own front matter).
IDs and roles
[#main-section,role="lead"]== Hello
Adds
id="main-section"
and
class="lead"
to the rendered section.