GEO for Shopify: Complete Implementation Guide
beginnerShopify implements GEO via theme.liquid for meta tags and JSON-LD schema. Use Liquid's built-in article.published_at and article.updated_at for recency signals. Add Product and Article schema via script tags in the layout. Shopify's CDN-delivered HTML is fully crawlable by AI bots.
GEO for Shopify: Complete Implementation Guide
Shopify implements GEO via theme.liquid for meta tags and JSON-LD schema. Use Liquid’s built-in article.published_at and article.updated_at for recency signals. Add Product and Article schema via script tags in the layout. Shopify’s CDN-delivered HTML is fully crawlable by AI bots.
Shopify generates server-rendered HTML for all pages — products, collections, blog posts, and static pages. This makes it natively compatible with AI crawlers. The main GEO work is adding structured data and ensuring meta tags are complete.
theme.liquid Head Section
Edit layout/theme.liquid to add complete meta tags:
{% comment %} layout/theme.liquid {% endcomment %}
<!DOCTYPE html>
<html lang="{{ shop.locale }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- TITLE AND DESCRIPTION -->
<title>
{%- if template == 'index' -%}
{{ shop.name }} — {{ shop.description }}
{%- elsif template == 'product' -%}
{{ product.title }} | {{ shop.name }}
{%- elsif template == 'article' -%}
{{ article.title }} | {{ blog.title }} | {{ shop.name }}
{%- elsif page_title -%}
{{ page_title }} | {{ shop.name }}
{%- else -%}
{{ shop.name }}
{%- endif -%}
</title>
{%- if page_description -%}
<meta name="description" content="{{ page_description | escape }}">
{%- else -%}
<meta name="description" content="{{ shop.description | escape }}">
{%- endif -%}
<meta name="author" content="{{ shop.name }}">
<link rel="canonical" href="{{ canonical_url }}">
<meta name="robots" content="index, follow">
<!-- OPEN GRAPH -->
{%- if template == 'article' -%}
<meta property="og:type" content="article">
<meta property="article:published_time" content="{{ article.published_at | date: '%Y-%m-%dT%H:%M:%SZ' }}">
<meta property="article:modified_time" content="{{ article.updated_at | date: '%Y-%m-%dT%H:%M:%SZ' }}">
<meta property="article:author" content="{{ article.author }}">
{%- for tag in article.tags -%}
<meta property="article:tag" content="{{ tag }}">
{%- endfor -%}
{%- elsif template == 'product' -%}
<meta property="og:type" content="product">
{%- else -%}
<meta property="og:type" content="website">
{%- endif -%}
<meta property="og:title" content="{{ page_title | escape }}">
<meta property="og:description" content="{{ page_description | default: shop.description | escape }}">
<meta property="og:url" content="{{ canonical_url }}">
<meta property="og:site_name" content="{{ shop.name }}">
<meta property="og:locale" content="{{ shop.locale | replace: '-', '_' }}">
{%- if template == 'product' and product.featured_image -%}
<meta property="og:image" content="{{ product.featured_image | img_url: '1200x630' }}">
{%- elsif template == 'article' and article.image -%}
<meta property="og:image" content="{{ article.image | img_url: '1200x630' }}">
{%- endif -%}
<!-- JSON-LD SCHEMA -->
{%- if template == 'article' -%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": {{ article.title | json }},
"description": {{ article.excerpt_or_content | strip_html | truncate: 160 | json }},
"author": {
"@type": "Person",
"name": {{ article.author | json }}
},
"publisher": {
"@type": "Organization",
"name": {{ shop.name | json }},
"logo": {
"@type": "ImageObject",
"url": {{ shop.url | append: '/assets/logo.png' | json }}
}
},
"datePublished": {{ article.published_at | date: '%Y-%m-%dT%H:%M:%SZ' | json }},
"dateModified": {{ article.updated_at | date: '%Y-%m-%dT%H:%M:%SZ' | json }},
"mainEntityOfPage": {
"@type": "WebPage",
"@id": {{ canonical_url | json }}
}
{%- if article.image -%}
, "image": {{ article.image | img_url: '1200x630' | json }}
{%- endif -%}
}
</script>
{%- endif -%}
{%- if template == 'product' -%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": {{ product.title | json }},
"description": {{ product.description | strip_html | truncate: 300 | json }},
"url": {{ canonical_url | json }},
"sku": {{ product.selected_or_first_available_variant.sku | json }},
"brand": {
"@type": "Brand",
"name": {{ product.vendor | json }}
},
"offers": {
"@type": "Offer",
"price": {{ product.selected_or_first_available_variant.price | money_without_currency }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}",
"url": {{ canonical_url | json }}
}
{%- if product.featured_image -%}
, "image": {{ product.featured_image | img_url: '1200x1200' | json }}
{%- endif -%}
}
</script>
{%- endif -%}
{{ content_for_header }}
</head>
robots.txt in Shopify
Shopify generates robots.txt automatically. To customize it, go to Online Store → Themes → Edit code and create templates/robots.txt.liquid:
{% comment %} templates/robots.txt.liquid {% endcomment %}
User-agent: GPTBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Claude-User
Allow: /
User-agent: Claude-SearchBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: BingBot
Allow: /
{% for group in robots.default_groups %}
User-agent: {{ group.user_agent }}
{% for rule in group.rules %}{{ rule }}
{% endfor %}
{% endfor %}
Sitemap: {{ routes.root_url }}sitemap.xml
Sitemap: {{ routes.root_url }}llms.txt
llms.txt via Shopify Page
Create a new page with handle llms-txt and configure a route:
In config/routes.json:
{
"/llms.txt": "page?handle=llms-txt"
}
Create a template templates/page.llms-txt.liquid:
{% layout none %}
HTTP/1.1 200 OK
Content-Type: text/plain
# {{ shop.name }}
> {{ shop.description }}
## Products
{% for product in collections.all.products limit: 20 %}
- [{{ product.title }}]({{ shop.url }}{{ product.url }}): {{ product.description | strip_html | truncate: 100 }}
{% endfor %}
## Blog
{% for article in blogs.news.articles limit: 20 %}
- [{{ article.title }}]({{ shop.url }}{{ article.url }}): {{ article.excerpt | strip_html | truncate: 100 }}
{% endfor %}
## Pages
- [About Us]({{ shop.url }}/pages/about): About our company
- [Contact]({{ shop.url }}/pages/contact): Contact information
FAQPage Schema for Blog Articles
For blog posts that answer questions, add FAQPage schema:
{% if article.metafields.custom.faq_questions %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{% for question in article.metafields.custom.faq_questions %}
{
"@type": "Question",
"name": {{ question.q | json }},
"acceptedAnswer": {
"@type": "Answer",
"text": {{ question.a | json }}
}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
</script>
{% endif %}
GEO Checklist for Shopify
- theme.liquid: complete meta tags with og:type, og:title, og:description, og:url, og:image
- Article pages: article:published_time and article:modified_time via article.published_at and article.updated_at
- Article pages: JSON-LD Article schema in head
- Product pages: JSON-LD Product schema with pricing and availability
- Canonical URL: canonical_url Liquid variable in head
- robots.txt.liquid: all 8 AI crawlers explicitly allowed
- llms.txt: created as Shopify page with custom template
- Blog post content: inverted pyramid (answer in first paragraph)
- Product descriptions: direct value statement in first sentence
- Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1