transform.PortableText
Syntax
Returns
Portable Text is a JSON structure that represent rich text content in the Sanity CMS. In Hugo, this function is typically used in a Content Adapter that creates pages from Sanity data.
Types supported
block
andspan
image
. Note that the image handling is currently very simple; we link to theasset.url
usingasset.altText
as the image alt text andasset.title
as the title. For more fine grained control you may want to process the images in a image render hook.code
(see the code-input plugin). Code will be rendered as fenced code blocks with any filename provided passed on as a markdown attribute.
Since the Portable Text gets converted to Markdown before it gets passed to Hugo, rendering of links, headings, images and code blocks can be controlled with Render Hooks.
Example
Content Adapter
{{ $projectID := "mysanityprojectid" }}
{{ $useCached := true }}
{{ $api := "api" }}
{{ if $useCached }}
{{/* See https://www.sanity.io/docs/api-cdn */}}
{{ $api = "apicdn" }}
{{ end }}
{{ $url := printf "https://%s.%s.sanity.io/v2021-06-07/data/query/production" $projectID $api }}
{{/* prettier-ignore-start */ -}}
{{ $q := `*[_type == 'post']{
title, publishedAt, summary, slug, body[]{
...,
_type == "image" => {
...,
asset->{
_id,
path,
url,
altText,
title,
description,
metadata {
dimensions {
aspectRatio,
width,
height
}
}
}
}
},
}`
}}
{{/* prettier-ignore-end */ -}}
{{ $body := dict "query" $q | jsonify }}
{{ $opts := dict "method" "post" "body" $body }}
{{ $r := resources.GetRemote $url $opts }}
{{ $m := $r | transform.Unmarshal }}
{{ $result := $m.result }}
{{ range $result }}
{{ if not .slug }}
{{ continue }}
{{ end }}
{{ $markdown := transform.PortableText .body }}
{{ $content := dict
"mediaType" "text/markdown"
"value" $markdown
}}
{{ $params := dict
"portabletext" (.body | jsonify (dict "indent" " "))
}}
{{ $page := dict
"content" $content
"kind" "page"
"path" .slug.current
"title" .title
"date" (.publishedAt | time )
"summary" .summary
"params" $params
}}
{{ $.AddPage $page }}
{{ end }}
Sanity setup
Below outlines a suitable Sanity studio setup for the above example.
import {defineConfig} from 'sanity'
import {structureTool} from 'sanity/structure'
import {visionTool} from '@sanity/vision'
import {schemaTypes} from './schemaTypes'
import {media} from 'sanity-plugin-media'
import {codeInput} from '@sanity/code-input'
export default defineConfig({
name: 'default',
title: 'my-sanity-project',
projectId: 'mysanityprojectid',
dataset: 'production',
plugins: [structureTool(), visionTool(), media(),codeInput()],
schema: {
types: schemaTypes,
},
})
Type/schema definition:
import {defineField, defineType} from 'sanity'
export const postType = defineType({
name: 'post',
title: 'Post',
type: 'document',
fields: [
defineField({
name: 'title',
type: 'string',
validation: (rule) => rule.required(),
}),
defineField({
name: 'summary',
type: 'string',
validation: (rule) => rule.required(),
}),
defineField({
name: 'slug',
type: 'slug',
options: {source: 'title'},
validation: (rule) => rule.required(),
}),
defineField({
name: 'publishedAt',
type: 'datetime',
initialValue: () => new Date().toISOString(),
validation: (rule) => rule.required(),
}),
defineField({
name: 'body',
type: 'array',
of: [
{
type: 'block',
},
{
type: 'image'
},
{
type: 'code',
options: {
language: 'css',
languageAlternatives: [
{title: 'HTML', value: 'html'},
{title: 'CSS', value: 'css'},
],
withFilename: true,
},
},
],
}),
],
})
Note that the above requires some additional plugins to be installed:
npm i sanity-plugin-media @sanity/code-input
import {postType} from './postType'
export const schemaTypes = [postType]
Server setup
Unfortunately, Sanity’s API does not support
RFC 7234 and their output changes even if the data has not. A recommended setup is therefore to use their cached apicdn
endpoint (see above) and then set up a reasonable polling and file cache strategy in your Hugo configuration, e.g:
HTTPCache:
polls:
- disable: false
for:
includes:
- https://*.*.sanity.io/**
high: 3m
low: 30s
caches:
getresource:
dir: :cacheDir/:project
maxAge: 5m
[HTTPCache]
[[HTTPCache.polls]]
disable = false
high = '3m'
low = '30s'
[HTTPCache.polls.for]
includes = ['https://*.*.sanity.io/**']
[caches]
[caches.getresource]
dir = ':cacheDir/:project'
maxAge = '5m'
{
"HTTPCache": {
"polls": [
{
"disable": false,
"for": {
"includes": [
"https://*.*.sanity.io/**"
]
},
"high": "3m",
"low": "30s"
}
]
},
"caches": {
"getresource": {
"dir": ":cacheDir/:project",
"maxAge": "5m"
}
}
}
The polling above will be used when running the server/watch mode and rebuild when you push new content in Sanity.
See Caching in resources.GetRemote for more fine grained control.