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
blockandspanimage. Note that the image handling is currently very simple; we link to theasset.urlusingasset.altTextas the image alt text andasset.titleas 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 file name 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-inputimport {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.
