jekyll-openapi
A (mostly) spec-compliant OpenAPI 3.1 toolkit for Liquid-based static site generators (Jekyll in particular).
It deliberately does not render HTML. The gem provides:
- a navigable object model over a parsed OpenAPI document — dereferencing, tag and operation enumeration, parameter merging;
- schema-to-text renderers (TypeScript-like and simplified XML) exposed as opt-in Liquid filters;
…and your site provides the templates. See examples/ for complete, copy-paste-able templates.
The gem works as a plain Ruby library too — without Jekyll or Liquid. That usage, the object model, and the renderer API are documented in examples/ruby/README.md.
Jekyll integration
Add the gem to your Gemfile and list it as a plugin — nothing else to do,
the filters register themselves and warnings are routed to Jekyll's logger:
# Gemfile
group :plugins do
gem "jekyll-openapi"
end
# _config.yml
plugins:
- jekyll-openapi
Walkthrough: a minimal API page
Four steps take you from a spec file to a rendered reference page.
1. Enable the plugin as shown above (Gemfile + plugins: entry). No
_plugins/ file is needed.
2. Drop your spec into _data/ so Jekyll parses it into site.data:
_data/openapi.yml # YAML or JSON; becomes site.data.openapi
3. Write a page that walks the tags and operations with the filters:
---
title: API Reference
---
{% assign api = site.data.openapi %}
<h1>{{ api.info.title }}</h1>
{% assign tags = api | oapi_tags %}
{% for tag in tags %}
<h2>{{ tag.name }}</h2>
{{ tag.description | markdownify }}
{% assign ops = api | oapi_operations: tag.name %}
{% for op in ops %}
<h3>{{ op.method | upcase }} {{ op.path }}</h3>
{{ op.operation.description | markdownify }}
{% endfor %}
{% endfor %}
4. Build (bundle exec jekyll serve) and you have a page listing every
operation, grouped by tag. That's a complete, working integration.
oapi_operations already does the tedious parts for you: it skips
non-operation path-item keys, handles x- extension methods, filters by tag,
and merges path-level parameters into each operation.
Each op in the loop is a plain Hash you can traverse further:
| key | content |
|---|---|
"path" |
"/pets/{petId}" |
"method" |
normalized method, x- prefix stripped ("mkcol") |
"raw_method" |
the literal key ("x-mkcol") |
"operation" |
the Operation object, dereferenced |
"parameters" |
path-level + operation-level parameters, merged (operation wins on same name/location) |
"grouped_parameters" |
same, grouped: {"query" => [...], "path" => [...], "header" => [...], "cookie" => [...]} |
Going further
The walkthrough above is intentionally bare. For a production-quality page — tag index/navigation, parameter tables, request/response bodies with rendered schemas — start from the complete examples and restyle them:
- examples/jekyll/api-doc.html — a full API reference page: tag cloud, operations grouped by tag, path/query parameter tables, request and response schemas.
- examples/jekyll/_includes/oapi/schema.html — a reusable include that picks the XML or TypeScript renderer based on the content type.
Liquid filters
Registered automatically in Jekyll. (In a plain Liquid setup you register them yourself — see examples/ruby/README.md.)
| filter | input → output |
|---|---|
oapi_dereference |
document → document with local $refs resolved |
oapi_tags |
document → tag list |
oapi_operations |
document (+ optional tag argument) → operation list |
oapi_parameters |
parameter list → grouped by location |
oapi_merge_parameters |
path params, operation params → merged list |
oapi_schema |
JSON-Schema → TypeScript-like string |
oapi_xml_schema |
JSON-Schema → simplified XML string |
The two schema filters turn a JSON-Schema into readable text, e.g.
{{ content.schema | oapi_schema }} produces:
{
name: "string"
age?: integer
}
See examples/jekyll/_includes/oapi/schema.html for choosing the renderer by content type.
Error handling
The library never raises on malformed input: it warns through
JekyllOpenAPI.logger (in Jekyll, routed to Jekyll's own logger) and degrades
gracefully — unresolvable, external, and circular $refs are left in place;
invalid schemas render as "". Your build never breaks on a bad spec.
Compliance notes
Intentional simplifications, in the spirit of "readable docs over exhaustive fidelity":
- Only local (
#/...) references are resolved; external files/URLs are left as-is. - The TypeScript renderer supports
oneOf/anyOf(|),allOf(&), 3.1 type arrays (type: [string, "null"]), enums,additionalProperties, and infersobject/arraywhentypeis omitted — but does not mergeallOfmembers into a single object literal. - The XML renderer honors
xml.name,xml.prefix,xml.namespace,xml.attributeandxml.wrapped, and renders example values where provided.
Development
rake test # or: ruby -Ilib -Itest test/test_<name>.rb
Tests run against generic fixtures (test/fixtures/petstore.yml); when the gem
sources live inside the eCorpus_doc repository, an extra integration suite runs
the whole pipeline over the real eCorpus API definition.