├── .editorconfig
├── .eleventy.js
├── .eleventyignore
├── .env.example
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .npmignore
├── README.md
├── _includes
└── base.njk
├── changelog.md
├── config
├── index.js
└── queries.js
├── demo
├── .eleventy.js
├── package.json
└── src
│ ├── _includes
│ └── base.njk
│ ├── index.njk
│ └── page.njk
├── package.json
└── src
├── articles.js
├── collections.js
├── pages.js
├── products.js
└── shop.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 2
10 | end_of_line = lf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 | max_line_length = 80
15 |
--------------------------------------------------------------------------------
/.eleventy.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | const getShopInfo = require('./src/shop');
4 | const getAllProducts = require('./src/products');
5 | const getAllCollections = require('./src/collections');
6 | const getAllPages = require('./src/pages');
7 | const getAllArticles = require('./src/articles');
8 |
9 | const defaultConfig = require("./config");
10 |
11 | const getShopifyContent = async (config) => {
12 | // defaultConfig is a standard config file with the default queries assigned
13 | // anything that the user changes overwrites the default config
14 | // the result from this overwriting is the below shopifyConfig variable
15 | const shopifyConfig = Object.assign(defaultConfig, config);
16 |
17 | console.log(chalk.yellow.bold(`SHOPIFY:GETTING SHOP INFO`))
18 | console.log(chalk.yellow.bold(`SHOPIFY:GETTING PRODUCTS`))
19 | console.log(chalk.yellow.bold(`SHOPIFY:GETTING COLLECTIONS`))
20 | console.log(chalk.yellow.bold(`SHOPIFY:GETTING PAGES`))
21 | console.log(chalk.yellow.bold(`SHOPIFY:GETTING ARTICLES`))
22 |
23 | const shop = await getShopInfo(shopifyConfig.shopQuery);
24 | const products = await getAllProducts(shopifyConfig.productsQuery);
25 | const collections = await getAllCollections(shopifyConfig.collectionsQuery);
26 | const pages = await getAllPages(shopifyConfig.pagesQuery);
27 | const articles = await getAllArticles(shopifyConfig.articlesQuery);
28 |
29 | console.log(chalk.greenBright.bold(`SHOPIFY:SUCCESSFULLY RETRIEVED ${shop.name.toUpperCase()} INFO`))
30 | console.log(chalk.greenBright.bold(`SHOPIFY:SUCCESSFULLY RETRIEVED ${products.length} PRODUCT${products.length > 1 || products.length == 0 ? 'S' : ''}`))
31 | console.log(chalk.greenBright.bold(`SHOPIFY:SUCCESSFULLY RETRIEVED ${collections.length} COLLECTIONS`))
32 | console.log(chalk.greenBright.bold(`SHOPIFY:SUCCESSFULLY RETRIEVED ${pages.length} PAGE${pages.length > 1 || pages.length == 0 ? 'S' : ''}`))
33 | console.log(chalk.greenBright.bold(`SHOPIFY:SUCCESSFULLY RETRIEVED ${articles.length} ARTICLE${articles.length > 1 || articles.length == 0 ? 'S' : ''}`))
34 |
35 | console.log(chalk.yellow.bold(`SHOPIFY:MAPPING PRODUCTS TO COLLECTIONS`))
36 |
37 | collections.map(collection => {
38 | if (collection.products.length > 0) {
39 | return collection.products.map(collectionProduct => {
40 | const foundProduct = products.find(product => {
41 | return product.id === collectionProduct.id
42 | })
43 | return foundProduct
44 | })
45 | } else {
46 | return collection
47 | }
48 | })
49 |
50 | console.log(chalk.greenBright.bold(`SHOPIFY:SUCCESSFULLY MAPPED PRODUCTS TO COLLECTIONS`))
51 |
52 | return {
53 | shop: shop,
54 | products: products,
55 | collections: collections,
56 | pages: pages,
57 | articles: articles,
58 | };
59 | };
60 |
61 | module.exports = (eleventyConfig, pluginConfig) => {
62 | eleventyConfig.addGlobalData(
63 | "shopify",
64 | async () => await getShopifyContent(pluginConfig || {})
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/.eleventyignore:
--------------------------------------------------------------------------------
1 | README.md
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | SHOPIFY_STORE_URL=*.myshopify.com
2 | SHOPIFY_ACCESS_TOKEN=
3 | SHOPIFY_API_VERSION=2021-07
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | _site
3 | package-lock.json
4 | .env
5 | .DS_Store
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | index.md
2 | _includes
3 | _site
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eleventy-plugin-shopify
2 |
3 | [](https://badge.fury.io/js/eleventy-plugin-shopify)
4 |
5 | Import your [Shopify](https://www.shopify.com/?ref=permalight-nyc) products, pages, and collections into [Eleventy](https://11ty.dev/) as global data.
6 |
7 | [View the demo site](https://eleventy-plugin-shopify-demo.netlify.app/)
8 |
9 | ## Installation
10 |
11 | 1. Install plugin using npm:
12 |
13 | ```
14 | npm install eleventy-plugin-shopify
15 | ```
16 |
17 | 2. Add plugin to your `.eleventy.js` config, ensuring to add your Shopify url and a Storefront API key. Check out the Shopify docs for [how to create a Storefront API key](https://shopify.dev/api/storefront/getting-started):
18 |
19 | You may also pass through your own graphql queries for products, collections, pages, and articles. Check out the [graphiql storefront explorer](https://shopify.dev/custom-storefronts/tools/graphiql-storefront-api) to test queries. You may have to adjust the queries based on cost and size of the store.
20 |
21 | ```js
22 | const pluginShopify = require("eleventy-plugin-shopify");
23 |
24 | require("dotenv").config();
25 |
26 | const { SHOPIFY_STORE_URL, SHOPIFY_ACCESS_TOKEN, SHOPIFY_API_VERSION } =
27 | process.env;
28 |
29 | module.exports = (eleventyConfig) => {
30 | eleventyConfig.addPlugin(pluginShopify, {
31 | url: SHOPIFY_STORE_URL,
32 | key: SHOPIFY_ACCESS_TOKEN,
33 | version: SHOPIFY_API_VERSION,
34 | // optional: shopQuery, productsQuery, collectionsQuery, pagesQuery, articlesQuery
35 | });
36 | };
37 | ```
38 |
39 | The example above is using `dotenv` with a `.env` file to ensure credentials are **not** stored in the source code. Here's an example of the `.env` file:
40 |
41 | ```text
42 | SHOPIFY_STORE_URL=*.myshopify.com
43 | SHOPIFY_ACCESS_TOKEN=
44 | SHOPIFY_API_VERSION=2021-10
45 | ```
46 |
47 | ## Usage
48 |
49 | ## API
50 |
51 | - `shopify.shop`: A tool for accessing the shop name and URL
52 | - `shopify.products`: An array of all products in Shopify
53 | - `shopify.articles`: An array of all articles in Shopify
54 | - `shopify.pages`: An array of all pages in Shopify
55 | - `shopify.collections`: An array of all collections in Shopify
56 |
57 | ## Development
58 |
59 | 1. Create a `.env` file inside of `demo` with the following credentials:
60 |
61 | ```text
62 | SHOPIFY_STORE_URL=*.myshopify.com
63 | SHOPIFY_ACCESS_TOKEN=
64 | SHOPIFY_API_VERSION=2021-07
65 | ```
66 |
67 | 2. Amend the `.eleventy.js` file within `demo` so it points to the source code in the parent directory:
68 |
69 | #### When developing locally
70 |
71 | ```js
72 | const pluginShopify = require("../");
73 | // const pluginShopify = require("eleventy-plugin-shopify");
74 | ```
75 |
76 | #### When using npm file
77 |
78 | ```js
79 | // const pluginShopify = require("../");
80 | const pluginShopify = require("eleventy-plugin-shopify");
81 | ```
82 |
83 | 3. Install development dependencies (in root):
84 |
85 | ```text
86 | npm install
87 | ```
88 |
89 | 4. Install the demo dependencies (in ./demo):
90 |
91 | ```text
92 | cd demo
93 | npm install
94 | ```
95 |
96 | 5. Run the demo locally:
97 | ```text
98 | npm run dev
99 | ```
100 |
101 | # Gotchas
102 |
103 | Beware of the `page` keyword when adding a layout for your shopify pages. In the demo we've called this `spage`
104 |
--------------------------------------------------------------------------------
/_includes/base.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Eleventy Shopify Plugin Test Page
8 |
9 |
10 | {{ content | safe }}
11 |
12 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # 0.0.5
2 |
3 | - Refactor pagination for large catalogs
4 | - Gets products and then maps those products to collections by ID
5 |
6 | # 0.0.4
7 |
8 | - Proof of concept
9 | - Added queries and config for different stores
10 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | const productsQuery = require("./queries").productsQuery;
2 | const collectionsQuery = require("./queries").collectionsQuery;
3 | const pagesQuery = require("./queries").pagesQuery;
4 | const articlesQuery = require("./queries").articlesQuery;
5 | const shopQuery = require("./queries").shopQuery;
6 |
7 | require("dotenv").config();
8 |
9 | const { SHOPIFY_STORE_URL, SHOPIFY_ACCESS_TOKEN, SHOPIFY_API_VERSION } = process.env;
10 |
11 | const config = {
12 | url: SHOPIFY_STORE_URL,
13 | key: SHOPIFY_ACCESS_TOKEN,
14 | version: SHOPIFY_API_VERSION,
15 | endpoint: `https://${SHOPIFY_STORE_URL}/api/${SHOPIFY_API_VERSION}/graphql.json`,
16 | headers: {
17 | 'X-Shopify-Storefront-Access-Token': SHOPIFY_ACCESS_TOKEN,
18 | 'Content-Type': 'application/graphql'
19 | },
20 | shopQuery,
21 | productsQuery,
22 | collectionsQuery,
23 | pagesQuery,
24 | articlesQuery,
25 | };
26 |
27 | module.exports = config;
28 |
--------------------------------------------------------------------------------
/config/queries.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | productsQuery: (cursor = null) => `
3 | query {
4 | products(first: 50, sortKey: CREATED_AT${cursor ? `, after: "${cursor}"` : ``}) {
5 | edges {
6 | cursor
7 | node {
8 | id
9 | title
10 | handle
11 | }
12 | }
13 | }
14 | }`,
15 | collectionsQuery: (cursor = null) => `
16 | query {
17 | collections(first:20${cursor ? `, after: "${cursor}"` : ``}) {
18 | edges {
19 | cursor
20 | node {
21 | title
22 | handle
23 | descriptionHtml
24 | products(first: 250) {
25 | edges {
26 | node {
27 | id
28 | }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }`,
35 | pagesQuery: (cursor = null) => `
36 | query {
37 | pages(first:50, sortKey: TITLE${cursor ? `, after: "${cursor}"` : ``}) {
38 | edges {
39 | cursor
40 | node{
41 | id
42 | title
43 | updatedAt
44 | handle
45 | body
46 | seo {
47 | title
48 | description
49 | }
50 | }
51 | }
52 | }
53 | }`,
54 | articlesQuery: (cursor = null) => `
55 | query {
56 | articles(first:50${cursor ? `, after: "${cursor}"` : ``}) {
57 | edges {
58 | cursor
59 | node {
60 | id
61 | handle
62 | title
63 | publishedAt
64 | contentHtml
65 | tags
66 | image {
67 | originalSrc
68 | altText
69 | }
70 | publishedAt
71 | seo {
72 | title
73 | description
74 | }
75 | }
76 | }
77 | }
78 | }`,
79 | shopQuery: () => `
80 | query {
81 | shop {
82 | name
83 | description
84 | moneyFormat
85 | privacyPolicy {
86 | title
87 | body
88 | handle
89 | }
90 | refundPolicy {
91 | title
92 | body
93 | handle
94 | }
95 | shippingPolicy {
96 | title
97 | body
98 | handle
99 | }
100 | termsOfService {
101 | title
102 | body
103 | handle
104 | }
105 | paymentSettings{
106 | currencyCode
107 | }
108 | primaryDomain {
109 | url
110 | }
111 | }
112 | }`,
113 | }
114 |
--------------------------------------------------------------------------------
/demo/.eleventy.js:
--------------------------------------------------------------------------------
1 | // const pluginShopify = require("../"); // For local development
2 |
3 | const pluginShopify = require("eleventy-plugin-shopify");
4 |
5 | require("dotenv").config();
6 |
7 | const { SHOPIFY_STORE_URL, SHOPIFY_ACCESS_TOKEN, SHOPIFY_API_VERSION } = process.env;
8 |
9 | module.exports = (eleventyConfig) => {
10 | eleventyConfig.addPlugin(pluginShopify, {
11 | url: SHOPIFY_STORE_URL,
12 | key: SHOPIFY_ACCESS_TOKEN,
13 | version: SHOPIFY_API_VERSION,
14 | });
15 | return {
16 | htmlTemplateEngine: 'njk',
17 | dir: {
18 | input: 'src'
19 | },
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "eleventy --serve",
4 | "build": "eleventy"
5 | },
6 | "dependencies": {
7 | "@11ty/eleventy": "^2.0.0",
8 | "dotenv": "^10.0.0",
9 | "eleventy-plugin-shopify": "^0.0.5"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/demo/src/_includes/base.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ title + " | " }}Eleventy Shopify Demo
9 |
15 |
16 |
17 | {{shopify.shop.name}}
18 | {{ content | safe }}
19 | {# The Eleventy Shopify plugin was made by Dan Leatherman in Brooklyn, NY. Demo hosted on Netlify. View the code on Github.
#}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/demo/src/index.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base.njk
3 | title: Eleventy Shopify
4 | ---
5 |
6 | {% if shopify.products.length > 0 %}
7 | Products
8 |
13 | {% endif %}
14 |
15 | {% if shopify.collections.length > 0 %}
16 | Collections
17 |
22 | {% endif %}
23 |
24 | Pages
25 |
26 | {% for p in shopify.pages %}
27 | - {{ p.title }}
28 | {% endfor %}
29 |
30 |
31 | Articles
32 |
37 |
--------------------------------------------------------------------------------
/demo/src/page.njk:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base.njk
3 | title: Eleventy Shopify
4 | ---
5 |
6 | {% if shopify.products.length > 0 %}
7 | Products
8 |
13 | {% endif %}
14 |
15 | {% if shopify.collections.length > 0 %}
16 | Collections
17 |
22 | {% endif %}
23 |
24 | Pages
25 |
26 | {% for p in shopify.pages %}
27 | - {{ p.title }}
28 | {% endfor %}
29 |
30 |
31 | Articles
32 |
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eleventy-plugin-shopify",
3 | "version": "0.1.0",
4 | "description": "An eleventy plugin to pull in Shopify Data",
5 | "main": ".eleventy.js",
6 | "scripts": {
7 | "start": "eleventy --serve",
8 | "build": "eleventy",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/dleatherman/eleventy-plugin-shopify.git"
14 | },
15 | "keywords": [
16 | "11ty",
17 | "11ty-plugin",
18 | "eleventy",
19 | "eleventy-plugin"
20 | ],
21 | "author": "Dan Leatherman",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/dleatherman/eleventy-plugin-shopify/issues"
25 | },
26 | "homepage": "https://github.com/dleatherman/eleventy-plugin-shopify#readme",
27 | "dependencies": {
28 | "chalk": "^4.1.2",
29 | "node-fetch-cache": "^3.0.3"
30 | },
31 | "devDependencies": {
32 | "@11ty/eleventy": "^2.0.0",
33 | "dotenv": "^10.0.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/articles.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch-cache')
2 |
3 | const config = require('../config')
4 | const { articlesQuery } = require('../config/queries')
5 |
6 | let allArticles = [];
7 |
8 | async function getArticles(query = articlesQuery, cursor = null, previousArticles = []) {
9 | if (previousArticles.length > 0) {
10 | allArticles = [...previousArticles];
11 | }
12 | const response = await fetch(config.endpoint, {
13 | method: 'post',
14 | body: query(cursor),
15 | headers: config.headers
16 | })
17 | const res = await response.json()
18 | const articles = res.data.articles.edges
19 | try {
20 | await getArticles(query, articles[articles.length - 1].cursor, [...articles])
21 | } catch (error) {
22 | return allArticles
23 | }
24 | }
25 |
26 | async function getAllArticles(query) {
27 | const articles = await getArticles(query);
28 | return allArticles.map((article) => {
29 | return article.node;
30 | });
31 | }
32 |
33 | module.exports = getAllArticles
34 |
--------------------------------------------------------------------------------
/src/collections.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch-cache')
2 |
3 | const config = require('../config')
4 | const { collectionsQuery } = require('../config/queries')
5 |
6 | let allCollections = [];
7 |
8 | async function getCollections(query = collectionsQuery, cursor = null, previousCollections = []) {
9 | if (previousCollections.length > 0) {
10 | allCollections = [...previousCollections];
11 | }
12 | const response = await fetch(config.endpoint, {
13 | method: 'post',
14 | body: query(cursor),
15 | headers: config.headers
16 | })
17 | const res = await response.json()
18 | const collections = res.data.collections.edges
19 | try {
20 | await getCollections(query, collections[collections.length - 1].cursor, [...collections])
21 | } catch (error) {
22 | return allCollections
23 | }
24 | }
25 |
26 | async function getAllCollections(query) {
27 | let collections = await getCollections(query);
28 | return allCollections.map((collection) => {
29 | // collection.node.products = collection.node.products.edges.map(product => {
30 | // return product.node
31 | // })
32 | return collection.node
33 | })
34 | }
35 |
36 | module.exports = getAllCollections
37 |
--------------------------------------------------------------------------------
/src/pages.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch-cache')
2 |
3 | const config = require('../config')
4 | const { pagesQuery } = require('../config/queries')
5 |
6 | let allPages = [];
7 |
8 | async function getPages(query = pagesQuery, cursor = null, previousPages = []) {
9 | if (previousPages.length > 0) {
10 | allPages = [...previousPages];
11 | }
12 | const response = await fetch(config.endpoint, {
13 | method: 'post',
14 | body: query(cursor),
15 | headers: config.headers
16 | })
17 | const res = await response.json()
18 | const pages = res.data.pages.edges
19 | try {
20 | await getPages(query, pages[pages.length - 1].cursor, [...pages])
21 | } catch (error) {
22 | return allPages
23 | }
24 | }
25 |
26 | async function getAllPages(query) {
27 | let pages = await getPages(query);
28 | return allPages.map((page) => {
29 | return page.node;
30 | });
31 | }
32 |
33 | module.exports = getAllPages
34 |
--------------------------------------------------------------------------------
/src/products.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch-cache')
2 |
3 | const config = require('../config')
4 | const { productsQuery } = require('../config/queries')
5 |
6 | let allProducts = [];
7 |
8 | async function getProducts(query = productsQuery, cursor = null, previousProducts = []) {
9 | if (previousProducts.length > 0) {
10 | allProducts = [...previousProducts];
11 | }
12 | const response = await fetch(config.endpoint, {
13 | method: 'post',
14 | body: query(cursor),
15 | headers: config.headers
16 | })
17 | const res = await response.json()
18 | const products = res.data.products.edges
19 | try {
20 | await getProducts(query, products[products.length - 1].cursor, [...products])
21 | } catch (error) {
22 | return allProducts
23 | }
24 | }
25 |
26 | async function getAllProducts(query) {
27 | const products = await getProducts(query);
28 | return allProducts.map((product) => {
29 | return product.node;
30 | });
31 | }
32 |
33 | module.exports = getAllProducts
34 |
--------------------------------------------------------------------------------
/src/shop.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch-cache');
2 |
3 | const config = require('../config')
4 | const { shopQuery } = require('../config/queries')
5 |
6 | async function getShopInfo(query = shopQuery) {
7 | const response = await fetch(config.endpoint, {
8 | method: 'post',
9 | body: query(),
10 | headers: config.headers
11 | })
12 | const res = await response.json()
13 | const shop = res.data.shop
14 | return shop
15 | }
16 |
17 | module.exports = getShopInfo
18 |
--------------------------------------------------------------------------------