dgtlss / atlas
Expose selected Laravel routes as agent-readable Markdown and JSON without changing their HTML behavior.
Requires
- php: ^8.1
- illuminate/cache: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- league/html-to-markdown: ^5.1
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|11.x-dev@dev
- phpunit/phpunit: ^10.5|^11.0|^12.0
README
Expose selected Laravel routes as agent-readable Markdown and JSON without rewriting the controller or Blade view that already powers the browser experience.
Install
composer require dgtlss/atlas
Publish the config if you want to customize negotiation, headers, metadata, or cache behavior:
php artisan vendor:publish --tag=atlas-config
Compatibility
| Atlas | PHP | Laravel |
|---|---|---|
| 1.0.x | 8.1+ | 10.x, 11.x, 12.x |
| 1.0.x | 8.4+ | 13.x provisional |
Laravel 13 stays declared in package metadata, but it should be treated as provisional until the stable framework release can be validated in CI without the experimental flag.
Quick Start
use Illuminate\Support\Facades\Route; Route::get('/docs/getting-started', fn () => view('docs.getting-started')) ->name('docs.show') ->atlas();
HTML stays the default response for browsers. Atlas only transforms the response when the route is explicitly opted in and the request asks for a supported machine-readable format:
curl -H "Accept: text/markdown" https://example.com/docs/getting-started curl -H "Accept: application/json" https://example.com/docs/getting-started curl "https://example.com/docs/getting-started?atlas=markdown"
Stable v1 Surface
- Route macro:
->atlas(array $options = []) - Middleware alias:
atlas - Contracts:
Dgtlss\Atlas\Contracts\MarkdownTransformerDgtlss\Atlas\Contracts\JsonTransformerDgtlss\Atlas\Contracts\AtlasPresenter
Route Options
Route::get('/knowledge-base/{article}', ShowArticleController::class)->atlas([ 'formats' => ['markdown', 'json'], 'presenter' => App\Atlas\ArticlePresenter::class, 'cache' => ['ttl' => 900, 'vary' => ['locale']], 'metadata' => ['section' => 'knowledge-base'], 'query_parameter' => 'format', 'markdown_transformer' => App\Atlas\CustomMarkdownTransformer::class, 'json_transformer' => App\Atlas\CustomJsonTransformer::class, ]);
Supported route options in v1:
| Option | Type | Default | Purpose |
|---|---|---|---|
formats |
array<string> |
['markdown', 'json'] |
Restrict which transformed formats a route will serve. |
presenter |
`class-string | null` | null |
cache |
`bool | int | array` |
metadata |
array |
[] |
Merge route-specific metadata into transformed responses. |
query_parameter |
`string | false` | config value |
markdown_transformer |
class-string<MarkdownTransformer> |
config value | Override Markdown generation for a route. |
json_transformer |
class-string<JsonTransformer> |
config value | Override JSON generation for a route. |
Negotiation Rules
Atlas only transforms routes that explicitly opt in with ->atlas().
Negotiation precedence in v1 is:
- Query parameter override, if enabled globally and for the route.
Acceptheader negotiation for the formats allowed on that route.- Fall back to the original HTML response.
Examples:
curl "https://example.com/docs/getting-started?atlas=json" curl "https://example.com/docs/getting-started?format=markdown" curl -H "Accept: application/json" https://example.com/docs/getting-started
Presenter Contract
Presenters take precedence over the generic HTML transformers when you need precise structured output.
namespace App\Atlas; use Dgtlss\Atlas\Contracts\AtlasPresenter; use Dgtlss\Atlas\Support\AtlasContext; class ArticlePresenter implements AtlasPresenter { public function toMarkdown(AtlasContext $context): string { return "# {$context->title()}\n\nCustom article body"; } public function toJson(AtlasContext $context): array { return [ 'title' => $context->title(), 'url' => $context->url(), 'content' => 'Custom article body', 'summary' => 'Custom summary', 'headings' => [], 'metadata' => $context->metadata(), ]; } }
Response Shape
Markdown responses can include frontmatter-style metadata, followed by the transformed page body:
--- title: "Atlas Docs" url: "https://example.com/docs/getting-started" metadata: {"section":"docs"} --- # Atlas Docs
JSON responses use these stable top-level keys:
{
"title": "Page title",
"url": "https://example.com/docs/getting-started",
"content": "Markdown-friendly readable content",
"summary": "Short summary",
"headings": [
{
"level": 1,
"text": "Getting started",
"id": "getting-started"
}
],
"metadata": {
"section": "docs"
}
}
Config Defaults
Published config defaults:
return [ 'enabled' => true, 'allowed_environments' => null, 'default_formats' => ['markdown', 'json'], 'negotiation' => [ 'accept_header' => true, 'allow_query_parameter' => true, 'query_parameter' => 'atlas', ], 'metadata' => [ 'include_frontmatter' => true, 'defaults' => [], ], 'headers' => [ 'canonical' => true, 'source_url' => true, ], 'cache' => [ 'enabled' => false, 'store' => null, 'ttl' => 600, 'vary' => ['locale'], ], ];
Important config behaviors:
enabled: disables Atlas globally while leaving your original routes untouched.allowed_environments: restricts transformation to specific app environments.cache.vary: cache keys vary by route, format, and the configured dimensions such aslocaleoruser.metadata.include_frontmatter: controls whether Markdown responses prepend frontmatter-style metadata.headers.canonicalandheaders.source_url: add canonical/source attribution headers to transformed responses.
Fallback Behavior
- Atlas only transforms routes that explicitly call
->atlas(). - Existing auth and authorization still run before transformation.
- Redirects, downloads, binary responses, and streamed responses fall back to their original response in v1.
- Successful non-HTML responses also fall back to their original response unless a presenter is explicitly configured.
- Unsupported or disallowed requested formats fall back to the original route response.
Headers and Caching
Transformed responses preserve the original successful status code and copy through existing headers and cookies where possible. Atlas adds:
X-Atlas-FormatX-Atlas-Source-URLwhen enabledLink: <...>; rel="canonical"when enabled
If caching is enabled, Atlas stores transformed payloads per route and format, then further varies them by the configured cache dimensions.
v1 Guarantees and Non-Goals
Atlas 1.0 guarantees:
- explicit route opt-in
- original route/controller execution before transformation
- Markdown and JSON outputs only
- presenter precedence over generic HTML transformation
- fallback to the original response for unsupported response types
Atlas 1.0 does not aim to provide:
- perfect conversion of highly interactive layouts
- deep client-rendered SPA extraction
- additional machine-readable formats beyond Markdown and JSON
Development
See CONTRIBUTING.md for local testing and CI-lane commands.