{"id":39980,"date":"2026-04-07T07:00:00","date_gmt":"2026-04-07T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=39980"},"modified":"2026-04-07T10:25:12","modified_gmt":"2026-04-07T17:25:12","slug":"product-color-swatches-sanity","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity","title":{"rendered":"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>A seasonal product launch usually starts with a simple request: \u201cShow the same item in summer\u2019s brand colors.\u201d Then the asks pile up. Add matching swatches. Update the hero image. Adjust the product card. Ensure the campaign page uses the exact same color story everywhere.<\/p>\n<p>For small teams, this is a nightmare of exported files and complicated design handoffs. Each new color means more manual work and more room for versioning mistakes between code and content.<\/p>\n<p>In this guide, you\u2019ll build a source of truth architecture so images update dynamically from a master asset.<\/p>\n<h2>The Modern Stack: Sanity + Cloudinary + TanStack Start<\/h2>\n<p>By combining these three tools, you\u2019ll create a product preview flow that\u2019s automated and scalable:<\/p>\n<ul>\n<li>\n<p><strong>Sanity (The Brain).<\/strong> As the control layer, it stores the launch name, active brand color, and swatch hex codes.<\/p>\n<\/li>\n<li>\n<p><strong>Cloudinary (The Image Engine).<\/strong> Takes one base product image and applies a color transformation on the fly. No more exporting 10 different JPEGs.<\/p>\n<\/li>\n<li>\n<p><strong>TanStack Start (The Storefront).<\/strong> Fetches the live theme and renders the updated preview with server-side speed.<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/sanity-cloudinary-theme-demo.vercel.app\"><strong>Click here to view the Live Demo<\/strong><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\"><strong>Source Code<\/strong><\/a><\/p>\n<\/li>\n<\/ul>\n<h2>Wiring the Storefront Shell<\/h2>\n<p>Begin with a fresh <strong>TanStack Start<\/strong> project. If you\u2019re wondering why, it\u2019s because Start handles the server-side data fetch from Sanity seamlessly, ensuring the theme feels instantaneous to the user without layout shift.<\/p>\n<p>Create your project following the TanStack Start docs, then install the core engine for this build:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-keyword\">@sanity<\/span>\/client @sanity\/image-url @cloudinary\/url-gen lucide-react\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<div class='c-callout  c-callout--inline-title c-callout--tip'><strong class='c-callout__title'>Tip:<\/strong> <p>Use <strong>shadcn\/ui<\/strong> for the interface elements to keep the UI clean while we focus on the data logic. If you want the exact look of the demo, follow the <a href=\"https:\/\/ui.shadcn.com\/docs\/installation\/tanstack\">shadcn\/ui installation guide<\/a>.<\/p><\/div>\n<h2>Setting Up the Image Engine (Cloudinary)<\/h2>\n<p>Cloudinary gives this demo its <a href=\"https:\/\/ai.cloudinary.com\/demos\/recolor\">chameleon<\/a> capabilities. Instead of exporting a new product image for every launch color, you\u2019ll upload one base asset Cloudinary will generate the variation when the page loads.<\/p>\n<p>Steps to set up:<\/p>\n<ol>\n<li>Create a (free!) <a href=\"https:\/\/cloudinary.com\/signup\">Cloudinary account<\/a>.<\/li>\n<li>Open the Media Library and upload the product image you want to use as your source.\n<ul>\n<li>\n<em>Tip:<\/em> A \u201cclean\u201d product image (white or neutral background) works best, as the recolor effect depends on having a solid source image to transform.<\/li>\n<\/ul>\n<\/li>\n<li>After uploading, keep these two values from your Cloudinary dashboard:\n<ul>\n<li>Cloud name: You\u2019ll use this to build your delivery URLs.<\/li>\n<li>Public ID: This tells Cloudinary exactly which image to transform.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>The cloud name identifies your account in the delivery URL, and the public ID acts as the address for your asset. Later, we\u2019ll use both with <a href=\"https:\/\/cloudinary.com\/documentation\/image_transformations\">Cloudinary image transformations<\/a> so the app can generate color-swapped previews from that single asset on the fly.<\/p>\n<h2>The Control Layer: Sanity Studio<\/h2>\n<p>You\u2019ll use Sanity to define which launch theme is active, store the specific brand hex codes, and link the Cloudinary asset that will be transformed.<\/p>\n<h3>Initializing the Studio<\/h3>\n<p>Set up a clean, TypeScript-based Studio. Sanity Studio v4 requires <strong>Node 20+<\/strong>. I\u2019d recommend separating your studio folder to keep the project organized.<\/p>\n<p>From your project root, run:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">mkdir<\/span> <span class=\"hljs-selector-tag\">studio<\/span>\n<span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">studio<\/span>\n<span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">create<\/span> <span class=\"hljs-selector-tag\">sanity<\/span><span class=\"hljs-keyword\">@latest<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>When prompted, follow these steps to keep the setup lean:<\/p>\n<ul>\n<li>Project: Select your existing Sanity project.<\/li>\n<li>Dataset: Choose <code>production<\/code>.<\/li>\n<li>Schema: Choose <strong>Clean project with no predefined schema types.<\/strong>\n<\/li>\n<li>Language\/Package Manager: Use <strong>TypeScript<\/strong> and <strong>npm<\/strong>.<\/li>\n<\/ul>\n<h3>Connecting the Assets<\/h3>\n<p>To bridge Sanity and Cloudinary, you\u2019ll use the official plugin. This allows you to browse and select your Cloudinary assets directly from the Sanity interface.<\/p>\n<p>Install <a href=\"https:\/\/www.sanity.io\/plugins\/sanity-plugin-cloudinary\">sanity-plugin-cloudinary<\/a>:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install sanity-plugin-cloudinary\n<\/code><\/span><\/pre>\n<p>Next, register the plugin in your <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/studio\/sanity.config.ts\"><code>sanity.config.ts<\/code><\/a>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ studio\/sanity.config.ts<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { cloudinarySchemaPlugin } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"sanity-plugin-cloudinary\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> defineConfig({\n  <span class=\"hljs-comment\">\/\/ ... other config<\/span>\n  <span class=\"hljs-attr\">plugins<\/span>: &#91;structureTool(), visionTool(), cloudinarySchemaPlugin()],\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Defining the Theme Schema<\/h3>\n<p>Let\u2019s take a look at the document type, <code>launchTheme<\/code>, you created earlier. The secret sauce here is how you\u2019ll store the hex code and the asset reference. Thanks to a regex validation for the hex code, the data going to Cloudinary will always be formatted correctly.<\/p>\n<p>In your schema file, <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/studio\/schemaTypes\/documents\/launchTheme.ts\"><code>studio\/schemaTypes\/documents\/launchTheme.ts<\/code><\/a>, define these key fields:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">defineField<\/span>({\n  <span class=\"hljs-attribute\">name<\/span>: <span class=\"hljs-string\">\"brandPrimaryColor\"<\/span>,\n  title: <span class=\"hljs-string\">\"Brand Primary Color\"<\/span>,\n  type: <span class=\"hljs-string\">\"string\"<\/span>,\n  description: <span class=\"hljs-string\">\"The hex code for the launch (e.g., #DD7A2E)\"<\/span>,\n  validation: (Rule) =&gt;\n    Rule.<span class=\"hljs-built_in\">required<\/span>().<span class=\"hljs-built_in\">regex<\/span>(\/^#(?:&#91;<span class=\"hljs-number\">0<\/span>-<span class=\"hljs-number\">9<\/span>A-Fa-f]{<span class=\"hljs-number\">3<\/span>}|&#91;<span class=\"hljs-number\">0<\/span>-<span class=\"hljs-number\">9<\/span>A-Fa-f]{<span class=\"hljs-number\">6<\/span>})$\/),\n}),\n  <span class=\"hljs-selector-tag\">defineField<\/span>({\n    <span class=\"hljs-attribute\">name<\/span>: <span class=\"hljs-string\">\"productImage\"<\/span>,\n    title: <span class=\"hljs-string\">\"Product Image\"<\/span>,\n    type: <span class=\"hljs-string\">\"cloudinary.asset\"<\/span>, \/\/ This uses the Cloudinary plugin we installed\n  });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Populating Your First Launch<\/h3>\n<p>Once your Studio is running (<code>npm run dev<\/code>), create a new <strong>Launch Theme<\/strong> document. This is where you\u2019ll set the vibe for the storefront:<\/p>\n<ul>\n<li>Theme Title: Autumn Orange<\/li>\n<li>Brand Primary Color: <code>#DD7A2E<\/code>\n<\/li>\n<li>Product Image: Click the Cloudinary selector, enter your API credentials, and select the base asset you uploaded earlier.<\/li>\n<\/ul>\n<h2>Modeling the Theme Data<\/h2>\n<p>With Sanity and Cloudinary in place, you\u2019ll need to give our app a clean, predictable shape for the data it will consume. This is where you\u2019ll define the theme object, validate the incoming colors, and establish a safe fallback so the UI stays stable even if a content editor makes a mistake.<\/p>\n<h3>Defining the Shape<\/h3>\n<p>You\u2019ll use a shared <code>BrandTheme<\/code> interface to keep the storefront logic simple. This ensures that whether the data is coming from a live Sanity fetch or a local mock, the components receive the exact same object.<\/p>\n<p>In <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/types\/theme.ts\"><code>src\/types\/theme.ts<\/code><\/a>, we define the contract:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">\/**\n * A color value stored as a hex string.\n * <span class=\"hljs-doctag\">@example<\/span> \"#DD7A2E\"\n *\/<\/span>\nexport type HexColor = `<span class=\"hljs-comment\">#${string}`<\/span>\n\n<span class=\"hljs-comment\">\/**\n * Brand theme data used by the demo app.\n *\/<\/span>\nexport <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">BrandTheme<\/span> <\/span>{\n  id: string\n  name: string\n  primaryColor: HexColor\n  productName: string\n  baseImageUrl: string\n  cloudinaryPublicId: string\n  cloudinarySecureUrl?: string\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Type Safety and Normalization<\/h3>\n<p>Since content is managed externally, it\u2019s best to check it using a small utility in <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/lib\/theme\/brand.ts\"><code>src\/lib\/theme\/brand.ts<\/code><\/a> to normalize the hex color before it reaches the UI or the Cloudinary URL generator.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/**\n * Checks if a string looks like a valid hex color.\n *\/<\/span>\n \n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">isHexColor<\/span>(<span class=\"hljs-params\">value: string<\/span>): <span class=\"hljs-title\">value<\/span> <span class=\"hljs-title\">is<\/span> <span class=\"hljs-title\">HexColor<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-regexp\">\/^#(?:&#91;0-9A-Fa-f]{3}|&#91;0-9A-Fa-f]{6})$\/<\/span>.test(value)\n}\n\n<span class=\"hljs-comment\">\/**\n * Returns a safe fallback hex color if the input is invalid.\n *\/<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">normalizeHexColor<\/span>(<span class=\"hljs-params\">\n  value: string,\n  fallback: HexColor = <span class=\"hljs-string\">'#DD7A2E'<\/span>,\n<\/span>): <span class=\"hljs-title\">HexColor<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> isHexColor(value) ? value : fallback\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The \u2018Development First\u2019 Mock<\/h3>\n<p>To speed up the UI build before the Sanity query was fully wired, keep a local mock theme. This acts as both a development tool and a production fallback.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/lib\/theme\/brand.ts<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getMockBrandTheme<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">BrandTheme<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-string\">\"autumn-orange\"<\/span>,\n    <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Autumn Orange\"<\/span>,\n    <span class=\"hljs-attr\">primaryColor<\/span>: <span class=\"hljs-string\">\"#DD7A2E\"<\/span>,\n    <span class=\"hljs-attr\">productName<\/span>: <span class=\"hljs-string\">\"Nimbus Bottle\"<\/span>,\n    <span class=\"hljs-attr\">baseImageUrl<\/span>: <span class=\"hljs-string\">\"\/images\/product-bottle.png\"<\/span>,\n    <span class=\"hljs-attr\">cloudinaryPublicId<\/span>: <span class=\"hljs-string\">\"product-bottle_zdwgem\"<\/span>,\n    <span class=\"hljs-attr\">cloudinarySecureUrl<\/span>:\n      <span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/demo-article-projects\/image\/upload\/v1773466265\/product-bottle_zdwgem.png\"<\/span>,\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This modeling layer is small but critical. By establishing this process early, the Sanity fetch layer knows exactly what to map to, and the Cloudinary logic can rely on a guaranteed hex value.<\/p>\n<h2>Fetching the \u2018Active\u2019 Theme<\/h2>\n<p>With your data model ready, you\u2019ll need to bridge the gap between Sanity and our app. The goal is simple: Fetch the currently active <code>launchTheme<\/code> document and map it into your <code>BrandTheme<\/code> shape.<\/p>\n<h3>The GROQ Query<\/h3>\n<p>In <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/lib\/sanity\/queries.ts\"><code>src\/lib\/sanity\/queries.ts<\/code><\/a>, you wrote a <a href=\"https:\/\/www.sanity.io\/docs\/content-lake\/how-queries-work\">GROQ query<\/a> that targets the specific seasonal product launch. Use the <code>isActive<\/code> flag to filter and sort by the latest update, ensuring the storefront always reflects the most recent marketing decision.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> groq <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"groq\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> activeLaunchThemeQuery = groq<span class=\"hljs-string\">`\n  *&#91;_type == \"launchTheme\" &amp;&amp; isActive == true] | order(_updatedAt desc)&#91;0]{\n    _id,\n    title,\n    slug,\n    brandPrimaryColor,\n    productName,\n    productImage\n  }\n`<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Server-Side Mapping<\/h3>\n<p>The data coming back from Sanity\u2019s API is raw, so you\u2019ll need to clean it up before it hits your components. In <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/server\/services\/theme.server.ts\"><code>src\/server\/services\/theme.server.ts<\/code><\/a>, you\u2019ll handle the transformation, including a critical fallback logic. If Sanity returns nothing (e.g., if no theme is marked \u201cactive\u201d), the app falls back to our local mock theme.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">mapSanityThemeToBrandTheme<\/span>(<span class=\"hljs-params\">\n  document: SanityLaunchThemeResult\n<\/span>): <span class=\"hljs-title\">BrandTheme<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-built_in\">document<\/span>.slug?.current || <span class=\"hljs-built_in\">document<\/span>._id,\n    <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-built_in\">document<\/span>.title,\n    <span class=\"hljs-attr\">primaryColor<\/span>: normalizeHexColor(<span class=\"hljs-built_in\">document<\/span>.brandPrimaryColor),\n    <span class=\"hljs-attr\">productName<\/span>: <span class=\"hljs-built_in\">document<\/span>.productName,\n    <span class=\"hljs-attr\">baseImageUrl<\/span>: <span class=\"hljs-string\">\"\/images\/product-bottle.png\"<\/span>, <span class=\"hljs-comment\">\/\/ Local fallback asset<\/span>\n    <span class=\"hljs-attr\">cloudinaryPublicId<\/span>: <span class=\"hljs-built_in\">document<\/span>.productImage?.public_id || <span class=\"hljs-string\">\"\"<\/span>,\n    <span class=\"hljs-attr\">cloudinarySecureUrl<\/span>: <span class=\"hljs-built_in\">document<\/span>.productImage?.secure_url,\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The TanStack Start Connection<\/h3>\n<p>To keep the UI fast and SEO-friendly, you\u2019ll expose this logic through a <strong>TanStack Start server function<\/strong> in <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/server\/functions\/theme.functions.ts\"><code>src\/server\/functions\/theme.functions.ts<\/code><\/a>. This allows the homepage loader to fetch the theme on the server and pass it directly into the UI components.<\/p>\n<p>By the end of this step, the app no longer relies on hardcoded strings. It reads the brand\u2019s vision from Sanity and prepares the Cloudinary identifiers for the next (and most visual) step.<\/p>\n<h2>Cloudinary Recolor Logic<\/h2>\n<p>With the active theme flowing from Sanity into your TanStack Start app, you\u2019ve reached the phase where you\u2019ll turn a single static image into a dynamic, brand-aware asset.<\/p>\n<h3>Stability is Key: The Two-URL Strategy<\/h3>\n<p>To create a smooth user experience, you\u2019ll generate two versions of the image: a <strong>Base URL<\/strong> (the original) and a <strong>Recolor URL<\/strong> (the themed version).<\/p>\n<p>The secret to a professional look and feel is keeping the \u201cframing rules\u201d identical. By using the same crop, width, and padding for both, the product stays still while the color swaps \u2014 no jumping or shifting.<\/p>\n<h3>1. The Base Image Builder<\/h3>\n<p>In <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/lib\/cloudinary\/url.ts\"><code>src\/lib\/cloudinary\/url.ts<\/code><\/a>, start by building a clean, normalized version of the original product. You\u2019ll use Cloudinary\u2019s transformation URL format to ensure the original is already centered and padded, matching the layout of your future recolored versions.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildCloudinaryImageUrl<\/span>(<span class=\"hljs-params\">\n  options: BuildCloudinaryImageUrlOptions\n<\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> cloudName = getPublicEnv(<span class=\"hljs-string\">\"VITE_CLOUDINARY_CLOUD_NAME\"<\/span>);\n  <span class=\"hljs-keyword\">if<\/span> (!cloudName) <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\"<\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> { publicId, width = <span class=\"hljs-number\">1200<\/span>, height = <span class=\"hljs-number\">1200<\/span> } = options;\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-string\">`https:\/\/res.cloudinary.com\/<span class=\"hljs-subst\">${cloudName}<\/span>\/image\/upload`<\/span>,\n    <span class=\"hljs-string\">`f_auto,q_auto,c_pad,w_<span class=\"hljs-subst\">${width}<\/span>,h_<span class=\"hljs-subst\">${height}<\/span>,b_white`<\/span>, <span class=\"hljs-comment\">\/\/ Normalized frame<\/span>\n    publicId,\n  ].join(<span class=\"hljs-string\">\"\/\"<\/span>);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>2. The Recolor Engine<\/h3>\n<p>The <code>buildRecolorImageUrl<\/code> function follows the same pattern but introduces the <code>e_gen_recolor<\/code> transformation. This tells Cloudinary: \u201cFind the [item] in this image and change its color to [hex code].\u201d<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildRecolorImageUrl<\/span>(<span class=\"hljs-params\">\n  options: BuildRecolorImageUrlOptions\n<\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> cloudName = getPublicEnv(<span class=\"hljs-string\">\"VITE_CLOUDINARY_CLOUD_NAME\"<\/span>);\n  <span class=\"hljs-keyword\">if<\/span> (!cloudName || !isCloudinaryPreviewEnabled()) <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"\"<\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> {\n    publicId,\n    color,\n    prompt = <span class=\"hljs-string\">\"bottle\"<\/span>,\n    width = <span class=\"hljs-number\">1200<\/span>,\n    height = <span class=\"hljs-number\">1200<\/span>,\n  } = options;\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-string\">`https:\/\/res.cloudinary.com\/<span class=\"hljs-subst\">${cloudName}<\/span>\/image\/upload`<\/span>,\n    <span class=\"hljs-string\">`f_auto,q_auto,c_pad,w_<span class=\"hljs-subst\">${width}<\/span>,h_<span class=\"hljs-subst\">${height}<\/span>,b_white`<\/span>,\n    <span class=\"hljs-string\">`e_gen_recolor:prompt_<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">encodeURIComponent<\/span>(\n      prompt\n    )}<\/span>;to-color_<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">encodeURIComponent<\/span>(color)}<\/span>`<\/span>,\n    publicId,\n  ].join(<span class=\"hljs-string\">\"\/\"<\/span>);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>3. Feature Flags for Control<\/h3>\n<p>Since AI-driven recoloring is powerful, add a small toggle in your environment variables. This lets you turn the recolor layer on or off during development without touching the core logic.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name\nVITE_ENABLE_CLOUDINARY_PREVIEW=<span class=\"hljs-literal\">true<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>At this stage, the \u201cengine\u201d is fully built. The app can now take any hex code from Sanity, pass it through this URL builder, and receive a high-fidelity, brand-accurate product preview in milliseconds.<\/p>\n<blockquote>\n<p>For the full implementation of the URL builders, check out: <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/lib\/cloudinary\/url.ts\"><code>src\/lib\/cloudinary\/url.ts<\/code><\/a><\/p>\n<\/blockquote>\n<h2>Bringing Your Project to Life: The Product Preview UI<\/h2>\n<p>Now that your app can generate both the original and recolored images, you\u2019ll need to give that logic a home. The UI is where the setup finally pays off, turning abstract data into a tangible, interactive experience.<\/p>\n<h3>One Job: Clean Interaction<\/h3>\n<p>You kept the interface focused on a single task: showing the base product first, then allowing the user to switch between brand swatches to see the image update in real time. By avoiding a heavy gallery or complex product logic, the performance stays snappy.<\/p>\n<h3>Managing the State<\/h3>\n<p>Use a simple signal (or state) to track which color the user has selected. By default, this is the <code>primaryColor<\/code> you fetched from Sanity.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">const &#91;selectedColor, setSelectedColor] = createSignal<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">HexColor<\/span>&gt;<\/span>(\n  theme.primaryColor,\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Computing the Dynamic Preview<\/h3>\n<p>The process happens in a memoized value. Whenever the <code>selectedColor<\/code> changes, the component automatically rebuilds the Cloudinary URL. If the Cloudinary ID is missing for any reason, it safely falls back to your base product image.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> previewImageUrl = createMemo(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">if<\/span> (!theme.cloudinaryPublicId) {\n    <span class=\"hljs-keyword\">return<\/span> theme.baseImageUrl;\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    buildRecolorImageUrl({\n      <span class=\"hljs-attr\">publicId<\/span>: theme.cloudinaryPublicId,\n      <span class=\"hljs-attr\">color<\/span>: selectedColor(),\n      <span class=\"hljs-attr\">prompt<\/span>: <span class=\"hljs-string\">\"bottle\"<\/span>,\n    }) || theme.baseImageUrl\n  );\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Render Loop<\/h3>\n<p>The image block listens to that <code>previewImageUrl<\/code>. Below it, a row of swatches allows the user to trigger the transformation. Each button is styled dynamically using the hex code provided by the theme data.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ The Image Display<\/span>\n\n&lt;img\n  src={previewImageUrl()}\n  alt={theme.productName}\n  className=<span class=\"hljs-string\">\"h-full w-full object-contain transition-opacity duration-300\"<\/span>\n\/&gt;;\n\n<span class=\"hljs-comment\">\/\/ The Swatch Controls<\/span>\n{\n  swatches.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">color<\/span>) =&gt;<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n      <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{color}<\/span>\n      <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> setSelectedColor(color)}\n      className=\"h-10 w-10 rounded-full border shadow-sm hover:scale-105 transition-transform\"\n      style={{ backgroundColor: color }}\n      aria-label={`Select ${color} swatch`}\n    \/&gt;<\/span>\n  ));\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Result<\/h3>\n<p>Click a swatch, rebuild the Cloudinary URL, and watch the preview update instantly. Because you normalized the image frames in the previous step, the product remains still while the color shifts, creating a high-end, seamless feel for the end-user.<\/p>\n<blockquote>\n<p>Explore the full UI implementation here:**<\/p>\n<\/blockquote>\n<ul>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/components\/product\/product-preview.tsx\">src\/components\/product\/product-preview.tsx<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/components\/product\/color-swatch-picker.tsx\"><code>src\/components\/product\/color-swatch-picker.tsx<\/code><\/a>\n<\/li>\n<\/ul>\n<h2>Wiring the Homepage: Closing the Loop<\/h2>\n<p>With the theme query and preview UI ready, the last step is to connect everything on the homepage. This is where the live Sanity theme, the Cloudinary image engine, and the product preview component finally converge into a working storefront.<\/p>\n<h3>The Server-Side Handshake<\/h3>\n<p>The page layout is minimal. Its primary job is to load the active theme on the server using a <strong>TanStack Start loader<\/strong>. This ensures that the moment a user hits the page, the brand identity is already baked in, so you don\u2019t have to worry about unstyled content or empty states.<\/p>\n<p>In <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/routes\/index.tsx\"><code>src\/routes\/index.tsx<\/code><\/a>, the loader does the heavy lifting:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createFileRoute(<span class=\"hljs-string\">\"\/\"<\/span>)({\n  <span class=\"hljs-attr\">loader<\/span>: <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-comment\">\/\/ Fetches from Sanity or falls back to our local mock<\/span>\n    <span class=\"hljs-keyword\">const<\/span> theme = <span class=\"hljs-keyword\">await<\/span> getActiveBrandTheme();\n    <span class=\"hljs-keyword\">return<\/span> { theme };\n  },\n  <span class=\"hljs-attr\">component<\/span>: HomePage,\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Clean Handoff<\/h3>\n<p>Inside the page component, you\u2019ll simply \u201cconsume\u201d that loader data. One of the biggest advantages of this architecture is <strong>decoupling<\/strong>: The page doesn\u2019t need to know how Sanity queries work or how Cloudinary builds transformation strings. It just hands off the <code>theme<\/code> object to the <code>ProductPreview<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">HomePage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { theme } = Route.useLoaderData();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-auto flex min-h-screen max-w-6xl items-center px-6 py-12\"<\/span>&gt;<\/span>\n      {\/* The UI takes it from here *\/}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ProductPreview<\/span> <span class=\"hljs-attr\">theme<\/span>=<span class=\"hljs-string\">{theme}<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Full Loop in Action<\/h3>\n<p>Once this route is wired, the entire workflow is automated:<\/p>\n<ol>\n<li>\n<strong>Marketing<\/strong> updates the brand color in Sanity Studio.<\/li>\n<li>\n<strong>TanStack Start<\/strong> fetches that update on the next page load.<\/li>\n<li>\n<strong>Cloudinary<\/strong> receives the new hex code and generates the recolored product image on the fly.<\/li>\n<\/ol>\n<p>This setup removes launch day struggles away from developers and designers, and happens directly in the content management flow instead.<\/p>\n<blockquote>\n<p>Check out the full route implementation here: <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\/blob\/main\/src\/routes\/index.tsx\"><code>src\/routes\/index.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h2>Wrap Up: Beyond the Asset Trap<\/h2>\n<p>We started with a common \u201cLaunch Day\u201d headache: one product, several brand colors, and a mountain of manual image exports every time the theme changed. The solution wasn\u2019t to work harder\u2014it was to stop treating every color variation as a separate asset.<\/p>\n<p>By using <strong>Sanity<\/strong> as the control layer and <strong>Cloudinary<\/strong> as the image engine, we\u2019ve built a storefront that doesn\u2019t just display content\u2014it generates it. <strong>TanStack Start<\/strong> acts as the glue, ensuring that this high-tech workflow feels instant and seamless to the end user.<\/p>\n<h3>The Automated Workflow<\/h3>\n<p>Here\u2019s how the system works once the pipes are connected:<\/p>\n<ol>\n<li>\n<strong>Set the vibe.<\/strong> You define the active launch theme and brand colors in Sanity.<\/li>\n<li>\n<strong>Single source.<\/strong> You store one high-quality, neutral base image in Cloudinary.<\/li>\n<li>\n<strong>Live fetch.<\/strong> The app pulls the latest theme data on every request.<\/li>\n<li>\n<strong>Dynamic render.<\/strong> Cloudinary generates brand-accurate, recolored previews the moment a user clicks a swatch.<\/li>\n<\/ol>\n<p>The result is a lighter content workflow, zero duplicate assets, and a storefront that can pivot to a new seasonal campaign in the time it takes to hit <strong>Publish<\/strong> in Sanity.<\/p>\n<p>Ready to implement this in your next project? <a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up<\/a> for a free Cloudinary account today, and check out the full implementation, including all the schemas and transformation logic, in the repository below:<\/p>\n<ul>\n<li>\n<strong>GitHub Repository:<\/strong> <a href=\"https:\/\/github.com\/musebe\/sanity-cloudinary-theme-demo\">sanity-cloudinary-theme-demo<\/a>\n<\/li>\n<li>\n<strong>Live Demo:<\/strong> <a href=\"https:\/\/sanity-cloudinary-theme-demo.vercel.app\/\">Check out the final result<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":39981,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[336,98],"class_list":["post-39980","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-ai","tag-e-commerce"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-04-07T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-07T17:25:12+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI\",\"datePublished\":\"2026-04-07T14:00:00+00:00\",\"dateModified\":\"2026-04-07T17:25:12+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity\"},\"wordCount\":12,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA\",\"keywords\":[\"AI\",\"E-commerce\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2026\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity\",\"url\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity\",\"name\":\"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA\",\"datePublished\":\"2026-04-07T14:00:00+00:00\",\"dateModified\":\"2026-04-07T17:25:12+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity","og_locale":"en_US","og_type":"article","og_title":"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI","og_url":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity","og_site_name":"Cloudinary Blog","article_published_time":"2026-04-07T14:00:00+00:00","article_modified_time":"2026-04-07T17:25:12+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI","datePublished":"2026-04-07T14:00:00+00:00","dateModified":"2026-04-07T17:25:12+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity"},"wordCount":12,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA","keywords":["AI","E-commerce"],"inLanguage":"en-US","copyrightYear":"2026","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity","url":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity","name":"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA","datePublished":"2026-04-07T14:00:00+00:00","dateModified":"2026-04-07T17:25:12+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/product-color-swatches-sanity#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Automating Product Color Swatches: Dynamic Brand Theming With Sanity and Cloudinary AI"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1774641808\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI\/Blog_Automating_Product_Color_Swatches__Dynamic_Brand_Theming_with_Sanity_and_Cloudinary_AI.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39980","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=39980"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39980\/revisions"}],"predecessor-version":[{"id":39982,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39980\/revisions\/39982"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/39981"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=39980"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=39980"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=39980"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}