0.0
The project is in a healthy, maintained state
A (mostly) spec-compliant OpenAPI 3.1 toolkit for Jekyll and other Liquid-based static site generators: a navigable document model (dereferencing, operations, tags, parameters) and schema-to-text renderers (TypeScript-like, XML), exposed as Liquid filters. Self-registers when listed in a Jekyll site's plugins; usable as a plain library elsewhere. HTML rendering is left to the consumer.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

>= 4.0
~> 5.0
~> 13.0
>= 0
 Project Readme

jekyll-openapi

A (mostly) spec-compliant OpenAPI 3.1 toolkit for Liquid-based static site generators (Jekyll in particular), with partial OpenAPI 3.2 support (see OpenAPI 3.2 support).

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 and OpenAPI 3.2 additionalOperations, 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, lowercase, x- prefix stripped ("mkcol")
"raw_method" the literal key ("x-mkcol", "MKCOL")
"operation" the Operation object, dereferenced
"parameters" path-level + operation-level parameters, merged (operation wins on same name/location)
"grouped_parameters" same, grouped: {"query" => [...], "querystring" => [...], "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:

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 made navigable (lazy, name-preserving). Optional — every filter below dereferences internally; call it only if you also hand-walk the raw hash. Idempotent.
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_examples parameter or media-type object → normalized {name, value, summary, description} list, unifying examples/example/schema-level examples
oapi_headers response object → normalized {name, description, required, deprecated, schema} list ($ref'd headers resolved)
oapi_schemas document → named component-schema map (for a Models section)
oapi_security_schemes document → named security-scheme map
oapi_servers document → server list
oapi_schema JSON-Schema → TypeScript-like string
oapi_xml_schema JSON-Schema → simplified XML string
oapi_html_schema JSON-Schema → semantic HTML; named component refs become links. Works on dereferenced or raw {"$ref": …} schemas (link mode needs only the pointer).
oapi_schema_anchor schema name → schema-<name> anchor id
oapi_operation_anchor operation entry (or operationId) → fragment id; falls back to method+path when operationId is absent
oapi_tag_anchor tag object (or name) → fragment id
oapi_representation media-type string → "xml"/"json"/"text"/"binary" (robust to text/xml, *+xml, ; charset=…)
oapi_is_xml media-type string → boolean (XML-family)

The text schema filters turn a JSON-Schema into readable text, e.g. {{ content.schema | oapi_schema }} produces:

{
  name: "string"
  age?: integer
}

oapi_html_schema instead emits semantic, class-annotated HTML (<dl>/<ul>, never tables) in which a $ref to a named component schema renders as a link to #schema-<name> rather than being inlined — so operation bodies stay compact, reused types are cross-linked, and recursive schemas terminate. Descriptions are rendered as markdown when run inside Jekyll (it reuses the site's configured converter) and as escaped plain text otherwise. Pair it with oapi_schemas + oapi_schema_anchor to emit a matching "Models" section; see examples/jekyll/_includes/oapi/models.html and schema.html (which picks a renderer by content type), styled by _sass/oapi-schema.scss.

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 and external $refs are left in place and warn only when followed; recursive schemas terminate (rendered as a link, or their name in the text renderers); invalid schemas render as "". Your build never breaks on a bad spec.

OpenAPI 3.2 support

Partial. Because the model hands templates plain hashes, most new 3.2 fields flow through untouched and are usable today; "supported" below means the library actively understands the feature, "passthrough" means the raw data reaches your template but no helper interprets it.

Supported:

  • additionalOperations — enumerated by oapi_operations like any other operation, with path-level parameters merged; method is the lowercased method name, raw_method the literal key ("PROPFIND"). Gives webDAV-style APIs first-class treatment (previously only reachable via x- methods).
  • The query HTTP method — recognized as a first-class path-item key.
  • Tag summary — passes through oapi_tags (tested); use it as a short display name where description is prose.
  • in: querystring parameters — grouped under their own "querystring" key by oapi_parameters / grouped_parameters (the parameter is content-based: render its media-type schema, not a name/value pair). An operation-level querystring parameter overrides a path-level one regardless of name, and mixing it with in: query parameters warns (the spec forbids it) but keeps both.

Passthrough (data reaches templates, no helper logic):

  • Tag parent / kind — the fields are readable, but oapi_operations and oapi_tags treat tags as a flat list: no hierarchy building, no filtering by kind.
  • Response summary, Server name — readable on the raw objects.
  • Security scheme additions (deviceAuthorization flow, oauth2MetadataUrl, deprecated) — readable via oapi_security_schemes.

Not supported (TODO):

  • Streaming / sequential media types — itemSchema, itemEncoding and prefixEncoding are ignored by the schema renderers and oapi_examples.
  • Example Object dataValue / serializedValueoapi_examples only reads value.
  • xml.nodeType — the XML renderer still keys off the 3.1 attribute / wrapped flags.
  • $self and multi-document descriptions — consistent with the existing local-$refs-only policy.
  • components.mediaTypes reuse — untested; generic local $ref resolution may already handle it, but no guarantees.

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 infers object/array when type is omitted — but does not merge allOf members into a single object literal.
  • The XML renderer honors xml.name, xml.prefix, xml.namespace, xml.attribute and xml.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, a pristine OpenAPI 3.1 document, and test/fixtures/filestore.yml, a WebDAV-flavored OpenAPI 3.2 one); when the gem sources live inside the eCorpus_doc repository, an extra integration suite runs the whole pipeline over the real eCorpus API definition.