Recipe schema is the bits that are not visible by you or the user but are by Search Engines. Type any recipe in google and where you will see featured image, cooking times etc. this is taken from schema.
See Understand How Structured Data Works
For validity, you use Rich Result Test.
Apart of Schema, important is OpenGraph and Breadcrumbs. Have look on mine from this project:
Schema (Posts, Recipes and Breadcrumb)
<script type="application/ld+json">
{{ if eq .Title .Site.Title }}
// homepage
{
"@context": "https://schema.org/",
"@type": "WebPage",
"name": "{{ .Title }}",
"url": "{{.Site.BaseURL}}",
"inLanguage": {{ .Site.LanguageCode }},
"isFamilyFriendly": "true",
"copyrightYear": "{{ .PublishDate.Format "2006" }}",
"copyrightHolder": {{ .Site.Params.author }},
"accountablePerson": {
"@type": "Person",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}"
},
"author": {
"@type": "Person",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}"
},
"creator": {
"@type": "Person",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}"
},
"publisher": {
"@type": "Organization",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}",
"logo": {
"@type": "ImageObject",
"url": {{ .Site.Params.logo_sq_png | absURL }}
}
},
}
{{ else if .Params.recipe }}
// schema for recipes
{
"@context": "https://schema.org/",
"@type": "Recipe",
"mainEntityOfPage": { "@type": "WebPage" },
"name": {{ .Title }},
{{ if .Params.featuredImage }}"image": {{ .Params.featuredImage | absURL }},{{ end }}
"author": {
"@type": "Person",
"name": {{ .Site.Params.author }}
},
"datePublished": {{ .PublishDate }},
"dateModified": {{ .Lastmod }},
"description": {{ .Description }},
"recipeCuisine": {{ .Params.recipeCuisine }},
"prepTime": {{ .Params.prepTime }},
"cookTime": {{ .Params.cookTime }},
"totalTime": {{ .Params.totalTime }},
"keywords": {{ .Params.tags }},
"recipeYield": [
{{ .Params.recipeYield }}
],
"recipeCategory": {{ range last 1 .Params.categories }}{{ . }}{{ end }},
"nutrition": {
"@type": "NutritionInformation",
"calories": {{ .Params.calories }}
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "5",
"ratingCount": "25"
},
"recipeIngredient": [
{{ .Params.recipeIngredient }}
],
"recipeInstructions": [
{
"@type": "HowToStep",
"name": "Method",
{{ if .Params.featuredImage }}"image": {{ .Params.featuredImage | absURL }},{{ end }}
"text": "{{ .Params.recipeInstructions }}",
"url": "{{ .Permalink }}#method"
}
]
}
{{ else }}
// schema for posts
{
"@context":"http://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": { "@type": "WebPage" },
"headline": {{ .Title | truncate 110 }},
{{ if .Params.featuredImage }}"image": {{ .Params.featuredImage | absURL }},{{ end }}
"image": {{ .Params.logo_sq_png | absURL }},
"url": {{ .Permalink }},
"datePublished": {{ .PublishDate }},
"dateModified": {{ .Lastmod }},
"inLanguage": {{ .Site.LanguageCode }},
"isFamilyFriendly": "true",
"copyrightYear": "{{ .PublishDate.Format "2006" }}",
"copyrightHolder": {{ .Site.Params.author }},
"accountablePerson": {
"@type": "Person",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}"
},
"author": {
"@type": "Person",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}"
},
"creator": {
"@type": "Person",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}"
},
"publisher": {
"@type": "Organization",
"name": {{ .Site.Params.author }},
"url": "{{.Site.BaseURL}}",
"logo": {
"@type": "ImageObject",
"url": {{ .Site.Params.logo_sq_png | absURL }}
}
},
"articleBody": {{ .Content | safeJS | htmlUnescape | plainify }},
"description": {{ with .Description }}{{ . | plainify }}{{ else }}{{if .IsPage}}{{ .Summary | plainify | safeHTML }}{{ else }}{{ with .Site.Params.description }}{{ . | plainify }}{{ end }}{{ end }}{{ end }},
"keywords": [{{ range $i, $e := .Params.tags }}{{ if $i }}, {{ end }}{{ $e }}{{ end }}]
}
{{ end }}
</script>
{{ if .IsHome }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"url": "{{.Site.BaseURL}}",
"logo": "{{ .Site.Params.logo_sq_png | absURL }}"
}
</script>
{{ end }}
{{ if ne .Title .Site.Title }}
<script type="application/ld+json">
//breadcrumb schema
{{ if .Params.categories }}
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{.Site.BaseURL}}"
},
{{ range $i, $e := .Params.categories }}
{
"@type": "ListItem",
"position": 2,
"name": {{ $e }},
"item": "https://yummyrecipes.uk/{{ $e | urlize }}"
},
{{ end }}
{
"@type": "ListItem",
"position": 3,
"name": {{.Title}}
}]
}
{{ else }}
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{.Site.BaseURL}}"
},{
"@type": "ListItem",
"position": 2,
"name": {{.Title}}
}]
}
{{ end }}
</script>
{{ end }}
OpenGraph
<meta property="og:title" content="{{ .Title }}">
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:url" content="{{ .Permalink }}">
<meta property="og:locale" content="{{ .Site.LanguageCode }}">
{{ if .Params.featuredImage }}
<meta property="og:image" content="{{ .Params.featuredImage | absURL }}">
{{ else }}
<meta property="og:image" content="{{.Site.BaseURL}}{{ .Site.Params.og_image }}"> <!-- use 1.91:1 minimum 1200x630 -->
{{ end }}
{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}}
{{- if .IsPage }}
{{- if not .PublishDate.IsZero }}<meta property="article:published_time" {{ .PublishDate.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ else if not .Date.IsZero }}<meta property="article:published_time" {{ .Date.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ end }}
{{- if not .Lastmod.IsZero }}<meta property="article:modified_time" {{ .Lastmod.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>{{ end }}
{{- else }}
{{- if not .Date.IsZero }}<meta property="og:updated_time" {{ .Lastmod.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{- end }}
{{- end }}{{/* .IsPage */}}
{{- with .Params.audio }}<meta property="og:audio" content="{{ . }}">{{ end }}
{{- with .Site.Params.title }}<meta property="og:site_name" content="{{ . }}">{{ end }}
{{- with .Params.videos }}
{{- range . }}
<meta property="og:video" content="{{ . | absURL }}">
{{ end }}{{ end }}
{{- /* If it is part of a series, link to related articles */}}
{{- $permalink := .Permalink }}
{{- $siteSeries := .Site.Taxonomies.series }}{{ with .Params.series }}
{{- range $name := . }}
{{- $series := index $siteSeries ($name | urlize) }}
{{- range $page := first 6 $series.Pages }}
{{- if ne $page.Permalink $permalink }}<meta property="og:see_also" content="{{ $page.Permalink }}">{{ end }}
{{- end }}
{{ end }}{{ end }}
{{- if .IsPage }}
{{ with .Site.Author.facebook }}<meta property="article:author" content="https://www.facebook.com/{{ . }}">{{ end }}
{{ with .Site.Social.facebook }}<meta property="article:publisher" content="https://www.facebook.com/{{ . }}">{{ end }}
<meta property="article:section" content="{{ .Section }}">
{{- with .Params.tags }}{{ range first 6 . }}<meta property="article:tag" content="{{ . }}">{{ end }}{{ end }}
{{ end }}
{{- /* Facebook Page Admin ID for Domain Insights */}}
{{- with .Site.Social.facebook_admin }}<meta property="fb:admins" content="{{ . }}">{{ end }}
ps. I personally prefer to use JSON type of schema instead Microdata like @pitifi9191 shown in his example.