JSON2HTML for Edge Delivery Services
Delivering well-structured markup is essential for any AEM site, but when working with a legacy headless CMS, you're often limited to raw JSON responses - far from the clean HTML you're aiming for.
That's where JSON2HTML comes in. This generic worker bridges the gap by transforming JSON data into fully formed HTML pages tailored for Edge Delivery Services.
Best of all, it requires minimal setup - no need to deploy your own service. Here’s how it works…
Usage
Once you have your JSON data, endpoint, and a mustache template to transform JSON into BYOM friendly HTML - then follow these steps to stitch everything together.
- Step 1: Prepare your JSON data
- Step 2: Use a Mustache template to generate Edge Delivery Friendly HTML using BYOM (Bring your own markup)
- Step 3: Add an overlay to your content source as follows:
"overlay": {
"url": "https://json2html.adobeaem.workers.dev/<ORG>/<SITE>/<BRANCH>",
"type": "markup"
},
- Step 4: Update the configuration using a POST call to the
/config/
endpoint:
POST https://json2html.adobeaem.workers.dev/config/<ORG>/<SITE>/<BRANCH>
Authorization: token <your-admin-token>
Content-Type: application/json
[
{
"path": "/path1/abc/def/",
"endpoint": "https://<some-endpoint/path>/event-{{id}}.json",
"regex": "/[^/]+$/",
"template": "/templates/category/template.html",
"headers": {
"X-API-Key": "your-api-key-here",
"Accept": "application/json"
},
"forwardHeaders": [
"Authorization",
],
"relativeURLPrefix": "https://some-domain.com"
},
{
"path": "/path2/xyz/",
"endpoint": "https://<another-endpoint/path>/event-{{id}}.json",
"regex": "/[^/]+$/",
"template": "/templates/category/template.html"
}
]
- Step 5: Preview the dynamic path that would use this overlay and it will generate the HTML based on the configuration above and you can then visit the .aem.page of that path and see the result. Once previewed, everything in Edge Delivery Services will continue to work as expected.
Configuration Management
The configuration is managed through a POST request to the /config/:org/:site/:branch
endpoint.
Authentication
- Authorization Header: Include your AEM Admin API token in the
Authorization
header
Request Format
- Method: POST
- URL:
https://json2html.adobeaem.workers.dev/config/<ORG>/<SITE>/<BRANCH>
- Headers:
Authorization: token <your-admin-token>
Content-Type: application/json
- Body: JSON configuration object
Configuration Structure
The request body should contain an array with one or more configuration objects:
[
{
"path": "/path1/abc/def/",
"endpoint": "https://<some-endpoint/path>/event-{{id}}.json",
"regex": "/[^/]+$/",
"template": "/templates/category/template.html",
"headers": {
"X-API-Key": "your-api-key-here",
"Accept": "application/json"
},
"forwardHeaders": [
"Authorization",
],
"relativeURLPrefix": "https://some-domain.com"
},
{
"path": "/path2/xyz/",
"endpoint": "https://<another-endpoint/path>/event-{{id}}.json",
"regex": "/[^/]+$/",
"template": "/templates/category/template.html"
}
]
Configuration Parameters
Each configuration object in the array requires the following keys:
path
: The URL path pattern to match for this overlay configuration. The worker will use this to determine which config to apply.- Example:
/path1/abc/def/
- Required: Yes
- Example:
endpoint
: The API endpoint URL that contains the JSON data. Use{{id}}
as a placeholder that will be replaced with the ID extracted from the request URL.- Example:
https://api.example.com/event-{{id}}.json
- Required: Yes
- Example:
regex
: A regular expression pattern to extract the ID from the request URL. Should be provided as a string with forward slashes.- Example:
/[^/]+$/
(matches the last segment of the URL path) - Required: Yes
- Example:
template
: Relative URL to a Mustache template file located under the same org/site/branch that will be used to render the JSON data. If not provided, a default semantic HTML structure will be generated.- Example:
/templates/event.html
- Required: No (but recommended to have a template)
- Default: Basic semantic HTML with nested divs
- Example:
headers
: Custom HTTP headers to send when fetching data from the endpoint. If not provided, it defaults to{ 'Content-Type': 'application/json' }
.- Example:
{ 'X-API-Key': 'your-api-key-here', 'Accept': 'application/json' }
- Required: No
- Default:
{ 'Content-Type': 'application/json' }
- Example:
forwardHeaders
: An array of header names to forward from the incoming request to the endpoint API call. This is useful for passing authentication tokens from Helix Admin to AEM content source for example.- Example:
["Authorization"]
- Required: No
- Default: No incoming headers are forwarded
- Example:
relativeURLPrefix
: allows you to prefix relative URLs in the generated HTML content with a domain. Applies only to src and href attributes and only for these file types that are supported by BYOM: .mp4, .pdf, .svg, .jpg, .jpeg, .png- Example:
https://some-domain.com
- Required: No
- Default: No prefix will be added to relative paths
- Example:
The worker will:
- Match the request URL against the
path
patterns - Use the
regex
to extract an ID from the URL - Replace
{{id}}
in theendpoint
URL with the extracted ID - Fetch JSON data from the endpoint
- Render using either the specified
template
or default HTML structure
Example Configuration Updates
Using curl:
curl -X POST \
https://json2html.adobeaem.workers.dev/config/myorg/mysite/main \
-H "Authorization: token your-admin-token-here" \
-H "Content-Type: application/json" \
-d '[
{
"path": "/events/",
"endpoint": "https://api.example.com/events/{{id}}.json",
"regex": "/[^/]+$/",
"template": "/templates/event.html",
"headers": {
"X-API-Key": "your-api-key-here"
},
"forwardHeaders": [
"Authorization",
],
"relativeURLPrefix": "https://some-domain.com"
},
{
"path": "/path2/xyz/",
"endpoint": "https://<another-endpoint/path>/event-{{id}}.json",
"regex": "/[^/]+$/",
"template": "/templates/another-mustache-template.html"
}
]'
Example Regex and Endpoint Combinations
- Match numeric ID at end of path:
"regex": "/\\d+$/"
"endpoint": "https://api.example.com/items/{{id}}.json"
Matches: /products/123
, /categories/789
- Match alphanumeric slug:
"regex": "/[a-zA-Z0-9-]+$/"
"endpoint": "https://api.example.com/articles/{{id}}"
Matches: /blog/my-article-123
, /news/breaking-news
- Match date and ID pattern:
"regex": "/\\d{4}/\\d{2}/\\d{2}/[\\w-]+$/"
"endpoint": "https://api.example.com/archive/{{id}}"
Matches: /posts/2023/12/25/christmas-special
- Match multiple path segments:
"regex": "/([^/]+)/([^/]+)$/"
"endpoint": "https://api.example.com/{{id}}/details.json"
Matches: /category/subcategory
, /region/city
- Match specific prefix with ID:
"regex": "/event-([\\w-]+)$/"
"endpoint": "https://api.example.com/events/{{id}}/full"
Matches: /calendar/event-summer-2023
, /schedule/event-conf-2024
- Match UUID format:
"regex": "/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/"
"endpoint": "https://api.example.com/records/{{id}}"
Matches: /data/550e8400-e29b-41d4-a716-446655440000
Understanding the relativeURLPrefix Option
The relativeURLPrefix
is a configuration option that allows you to rewrite relative URLs in the generated HTML content. This is particularly useful for serving static assets from a CDN or different domain while keeping content URLs relative.
What it does: Automatically adds a custom domain prefix to relative URLs in your HTML content.
Before: <img src="/images/photo.jpg">
After: <img src="https://cdn.example.com/images/photo.jpg">
How to use it: Add “relativeURLPrefix”: “https://your-cdn.com”
to your config.
What gets changed: Only URLs that start with /
and end with supported file types
What doesn't change: URLs that are already full web addresses or don't match the file type criteria.
Supported File Types: The system only rewrites URLs for these file extensions:
.mp4
- Video files.pdf
- PDF documents.svg
- SVG graphics.jpg
/.jpeg
- JPEG images.png
- PNG images
Important Notes
- Only affects URLs starting with forward slash (root-relative URLs)
- Does not modify URLs that are already absolute or protocol-relative
Example Template
The example template below demonstrates how to handle complex JSON structures and conditional rendering using mustache.js syntax.
- Variable substitution with
{{variable}}
- Section blocks with
{{#section}}...{{/section}}
- Conditional rendering with
{{#condition}}...{{/condition}}
- Array iteration
- Nested object access
- HTML escaping with
{{{triple-mustache}}}
- Boolean flags for conditional content
This template showcases various Mustache patterns:
<!-- Basic variable substitution -->
<title>{{title}}</title>
<!-- Conditional sections -->
{{#hasValues}}
<!-- Content only rendered if hasValues is true -->
{{/hasValues}}
<!-- Array iteration -->
{{#values}}
<!-- Content repeated for each item in values array -->
{{/values}}
<!-- Nested object access -->
{{values.0.valueName}}
<!-- HTML content with triple mustache -->
{{{highlights}}}
<!-- Boolean flags for conditional rendering -->
{{#hasConsensus}}
<!-- Content only rendered if hasConsensus is true -->
{{/hasConsensus}}
- The template uses standard Mustache syntax
- HTML content in JSON should be properly escaped
- Boolean flags control the visibility of optional sections
- The template demonstrates handling of nested data structures
- All variable substitutions use proper Mustache escaping
Odds and ends
Some meta tags are handled differently in Edge Delivery Services while previewing.
For example, if you want to add a bunch of article:tags, you can do the following in the mustache template:
<meta name="tags" content="{{#tags}}{{.}},{{/tags}}">
and they will be blown up into individual article:tag meta elements in the resulting HTML
FAQ
Why use mustache and not handlebars or more complex templating solutions?
As with everything else in Edge Delivery Services, we strive to make complex interactions as simple and as fast as possible. We use a dependency-free version of Mustache which is a logic-less template syntax that keeps things as simple as possible and results in extremely fast processing times.
What if I want to do some pre/post-processing of the source JSON / resulting HTML for my pages?
- For source JSON - you can either update the source JSON into the format you need or handle it at a different edge worker that the json2html worker will use as an endpoint.
- For resulting HTML - the HTML generated by this service will be previewed and published like any other page. You can control the resulting HTML on the page using block/site JS/CSS as you please like with any other page in Edge Delivery Services. You can also do whatever is in the confines of the mustache template which is pretty powerful in itself.
What if I have multiple JSONs that I would like to combine into one page in Edge Delivery Services?
- You can have an edge worker that combines them together and then use that as the endpoint for this service.
- If you have multiple JSON endpoints and are using AEM Headless, then you can use the Content Fragment Delivery with OpenAPI or create a Persisted GraphQL Query that combines the JSON from multiple fragments and caches them for better performance.
How do I develop and test for this without disturbing my production configuration?
Like with everything else in Edge Delivery, this service is also branch-aware. That means you can push a config to a separate branch and use that branch for testing anything you would want to. Once you are satisfied with the testing, then you can push it up the main branch and update the config in the main branch as necessary.