Created
January 5, 2025 21:34
-
-
Save Tommydemian/fe5896c66b5cba6b5b4a5139362758df to your computer and use it in GitHub Desktop.
Gist Description Verify a Shopify Theme App Embed in Remix (Server-Side) This code demonstrates how to: Fetch all themes and identify the main theme (role: MAIN). Retrieve settings_data.json from that theme, removing the multiline comment block that can break JSON.parse. Check whether a specific app embed block is set to disabled: false. It’s de…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { json } from "@remix-run/node"; | |
import type { LoaderFunction } from "@remix-run/node"; | |
import { getMainThemeId, getSettingsData, isAppEmbedDisabled } from "~/lib/shopify.server"; | |
export const loader: LoaderFunction = async ({ request }) => { | |
// Set up or retrieve your 'admin' Shopify client somehow | |
// e.g. const { admin } = await authenticate.admin(request); | |
const themeId = await getMainThemeId(admin); | |
let embedDisabled = true; // default to "off" | |
if (themeId) { | |
const settingsData = await getSettingsData(admin, themeId); | |
embedDisabled = isAppEmbedDisabled(settingsData, "dev-storelock"); | |
} | |
return json({ embedDisabled }); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************** | |
* shopify.server.ts | |
* A minimal Shopify + Remix helper for verifying | |
* theme app embed status via settings_data.json | |
****************************************************/ | |
// If you have a specific type/interface for admin context, import it here. | |
// Example: | |
// import type { AdminApiContextWithoutRest } from "wherever/your-shopify-context-lives"; | |
// 1. Query: Fetch themes | |
const GET_THEMES = ` | |
query { | |
themes(first: 20) { | |
edges { | |
node { | |
id | |
role | |
name | |
} | |
} | |
} | |
} | |
`; | |
// 2. Query: Fetch settings_data.json from a given theme | |
const GET_THEME_FILE = ` | |
query getThemeFile($id: ID!) { | |
theme(id: $id) { | |
files(filenames: ["config/settings_data.json"], first: 1) { | |
nodes { | |
filename | |
body { | |
... on OnlineStoreThemeFileBodyText { | |
content | |
} | |
} | |
} | |
} | |
} | |
} | |
`; | |
/** | |
* getMainThemeId | |
* Calls the admin.graphql(...) method to list themes, returning the ID of the main theme. | |
*/ | |
export async function getMainThemeId( | |
admin: any // or AdminApiContextWithoutRest if you have that type | |
): Promise<string | undefined> { | |
const response = await admin.graphql(GET_THEMES); | |
const responseJson = await response.json(); | |
// Transform data into an array of theme nodes | |
const themes = responseJson?.data?.themes?.edges ?? []; | |
// Find the one with role: MAIN | |
const mainTheme = themes.find((t: any) => t.node.role === "MAIN"); | |
return mainTheme ? mainTheme.node.id : undefined; | |
} | |
/** | |
* getSettingsData | |
* Given the main theme ID, fetch config/settings_data.json | |
* from the Shopify Admin API, remove the multiline comment, then parse as JSON. | |
*/ | |
export async function getSettingsData(admin: any, themeId: string) { | |
const response = await admin.graphql(GET_THEME_FILE, { variables: { id: themeId } }); | |
const result = await response.json(); | |
// Grab the raw content from the file | |
const rawContent = result?.data?.theme?.files?.nodes?.[0]?.body?.content ?? ""; | |
// Remove the multi-line comment (/* ... */) if present | |
const cleaned = rawContent.replace(/\/\*[\s\S]*?\*\//, ""); | |
// Parse the cleaned JSON | |
return JSON.parse(cleaned); | |
} | |
/** | |
* isAppEmbedDisabled | |
* Inspect the parsed settings_data.json to see if your app's block is disabled. | |
* @param parsedData - The result from getSettingsData(...) | |
* @param appHandle - Unique handle/identifier for your app (e.g. "dev-storelock") | |
* @returns boolean - true if the block is disabled or missing, false if it's enabled | |
*/ | |
export function isAppEmbedDisabled(parsedData: any, appHandle: string): boolean { | |
const blocks = parsedData?.current?.blocks; | |
if (!blocks) { | |
// No blocks at all -> treat as disabled | |
return true; | |
} | |
// Loop over block IDs | |
for (const blockId of Object.keys(blocks)) { | |
const block = blocks[blockId]; | |
// Check if the block type includes our app handle | |
if (block?.type?.includes(appHandle)) { | |
// If block.disabled === true, then yes, it's disabled | |
return block.disabled === true; | |
} | |
} | |
// If we never found a block matching our app handle, consider it disabled | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment