diff --git a/src/index.js b/src/index.js
index 52b75f9..491dbc5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -12,7 +12,7 @@
 // @ts-check
 
 import { errorResponse, errorWithResponse, makeContext } from './util.js';
-import getProductQueryCS from './queries/cs-product.js';
+import getProductQueryCS, { adapter } from './queries/cs-product.js';
 import getProductQueryCore from './queries/core-product.js';
 import HTML_TEMPLATE from './templates/html.js';
 import { resolveConfig } from './config.js';
@@ -40,10 +40,11 @@ async function fetchProductCS(sku, config) {
 
   const json = await resp.json();
   try {
-    const [product] = json.data.products;
-    if (!product) {
+    const [productData] = json.data.products;
+    if (!productData) {
       throw errorWithResponse(404, 'could not find product', json.errors);
     }
+    const product = adapter(productData);
     return product;
   } catch (e) {
     console.error('failed to parse product: ', e);
diff --git a/src/queries/cs-product.js b/src/queries/cs-product.js
index d0ae1d7..34619e3 100644
--- a/src/queries/cs-product.js
+++ b/src/queries/cs-product.js
@@ -12,11 +12,85 @@
 
 import { gql } from '../util.js';
 
+/**
+ * @param {any} productData
+ * @returns {Product}
+ */
+export const adapter = (productData) => {
+  const minPrice = productData.priceRange?.minimum ?? productData.price;
+  const maxPrice = productData.priceRange?.maximum ?? productData.price;
+
+  /** @type {Product} */
+  const product = {
+    sku: productData.sku,
+    name: productData.name,
+    metaTitle: productData.metaTitle,
+    metaDescription: productData.metaDescription,
+    metaKeyword: productData.metaKeyword,
+    description: productData.description,
+    url: productData.url,
+    urlKey: productData.urlKey,
+    shortDescription: productData.shortDescription,
+    addToCartAllowed: productData.addToCartAllowed,
+    inStock: productData.inStock,
+    externalId: productData.externalId,
+    images: productData.images ?? [],
+    attributes: productData.attributes ?? [],
+    options: (productData.options ?? []).map((option) => ({
+      id: option.id,
+      label: option.title,
+      // eslint-disable-next-line no-underscore-dangle
+      typename: option.values?.[0]?.__typename,
+      required: option.required,
+      multiple: option.multi,
+      items: (option.values ?? []).map((value) => ({
+        id: value.id,
+        label: value.title,
+        inStock: value.inStock,
+        type: value.type,
+        product: value.product
+          ? {
+            sku: value.product.sku,
+            name: value.product.name,
+            prices: value.product.price ? {
+              regular: value.product.price.regular,
+              final: value.product.price.final,
+              visible: value.product.price.roles?.includes('visible'),
+            } : undefined,
+          }
+          : undefined,
+        quantity: value.quantity,
+        isDefault: value.isDefault,
+      })),
+    })),
+    prices: {
+      regular: {
+        // TODO: determine whether to use min or max
+        amount: minPrice.regular.amount.value,
+        currency: minPrice.regular.amount.currency,
+        maximumAmount: maxPrice.regular.amount.value,
+        minimumAmount: minPrice.regular.amount.value,
+        // TODO: add variant?
+      },
+      final: {
+        // TODO: determine whether to use min or max
+        amount: minPrice.final.amount.value,
+        currency: minPrice.final.amount.currency,
+        maximumAmount: maxPrice.final.amount.value,
+        minimumAmount: minPrice.final.amount.value,
+        // TODO: add variant?
+      },
+      visible: minPrice.roles?.includes('visible'),
+    },
+  };
+
+  return product;
+};
+
 export default ({ sku }) => gql`{
     products(
       skus: ["${sku}"]
     ) {
-      __typename
       id
       sku
       name
@@ -30,18 +104,15 @@ export default ({ sku }) => gql`{
       url
       addToCartAllowed
       inStock
+      externalId
       images(roles: []) { 
         url
         label
-        roles
-        __typename
       }
       attributes(roles: []) {
         name
         label
         value
-        roles
-        __typename
       }
       ... on SimpleProductView {
         price {
@@ -49,47 +120,61 @@ export default ({ sku }) => gql`{
             amount {
               value
               currency
-              __typename
             }
-            __typename
           }
           regular {
             amount {
               value
               currency
-              __typename
             }
-            __typename
           }
           roles
-          __typename
         }
-        __typename
       }
       ... on ComplexProductView {
         options {
+          __typename
           id
           title
           required
+          multi
           values {
             id
             title
-            ... on ProductViewOptionValueProduct {
-              product {
-                sku
-                name
-                __typename
-              }
+            inStock
+            ...on ProductViewOptionValueConfiguration {
               __typename
             }
             ... on ProductViewOptionValueSwatch {
+              __typename
               type
               value
+            }
+            ... on ProductViewOptionValueProduct {
               __typename
+              quantity
+              isDefault
+              product {
+                sku
+                name
+                price {
+                  regular {
+                    amount {
+                      value
+                      currency
+                    }
+                  }
+                  final {
+                    amount {
+                      value
+                      currency
+                    }
+                  }
+                  roles
+                }
+              }
             }
-            __typename
           }
-          __typename
         }
         priceRange {
           maximum {
@@ -97,44 +182,32 @@ export default ({ sku }) => gql`{
               amount {
                 value
                 currency
-                __typename
               }
-              __typename
             }
             regular {
               amount {
                 value
                 currency
-                __typename
               }
-              __typename
             }
             roles
-            __typename
           }
           minimum {
             final {
               amount {
                 value
                 currency
-                __typename
               }
-              __typename
             }
             regular {
               amount {
                 value
                 currency
-                __typename
               }
-              __typename
             }
             roles
-            __typename
           }
-          __typename
         }
-        __typename
       }
     }
   }`;
diff --git a/src/templates/html.js b/src/templates/html.js
index a20c210..c1a81e5 100644
--- a/src/templates/html.js
+++ b/src/templates/html.js
@@ -13,30 +13,33 @@
 
 import JSON_LD_TEMPLATE from './json-ld.js';
 
+/**
+ * @param {string} name
+ * @param {string|boolean|number|undefined|null} [content]
+ * @returns {string}
+ */
+const metaContent = (name, content) => (content ? `<meta name="${name}" content="${content}">` : '');
+
+/**
+ * @param {Product} product
+ */
 export default (product) => {
   const {
     sku,
     name,
+    urlKey,
     metaTitle,
     metaDescription,
     description,
     images,
     attributes,
     options,
+    addToCartAllowed,
+    inStock,
+    metaKeyword,
+    externalId,
   } = product;
 
-  const jsonLd = JSON_LD_TEMPLATE({
-    sku,
-    description: description ?? metaDescription,
-    image: images[0].url,
-    name,
-    // TODO: add following...
-    url: '',
-    brandName: '',
-    reviewCount: 0,
-    ratingValue: 0,
-  });
-
   return /* html */`\
 <!DOCTYPE html>
   <html>
@@ -50,13 +53,18 @@ export default (product) => {
       <meta name="twitter:card" content="summary_large_image">
       <meta name="twitter:title" content="${metaTitle || name}">
       <meta name="twitter:image" content="${images[0].url}">
+      <meta name="keywords" content="${metaKeyword}">
       <meta name="sku" content="${sku}">
+      <meta name="urlKey" content="${urlKey}">
+      ${metaContent('externalId', externalId)}
+      ${metaContent('addToCartAllowed', addToCartAllowed)}
+      ${metaContent('inStock', inStock)}
       <meta name="viewport" content="width=device-width, initial-scale=1">
       <script src="/scripts/aem.js" type="module"></script>
       <script src="/scripts/scripts.js" type="module"></script>
       <link rel="stylesheet" href="/styles/styles.css">
       <script type="application/ld+json">
-        ${jsonLd}
+        ${JSON_LD_TEMPLATE(product)}
       </script>
     </head>
     <body>
@@ -64,7 +72,7 @@ export default (product) => {
       <main>
         <div>
           <h1>${name}</h1>
-          <div class="product-gallery">
+          <div class="product-images">
             <div>
               ${images.map((img) => `
               <div>
@@ -89,13 +97,20 @@ export default (product) => {
           ${options.map((opt) => `
           <div>
             <div>${opt.id}</div>
-            <div>${opt.title}</div>
+            <div>${opt.label}</div>
+            <div>${opt.typename}</div>
+            <div>${opt.type ?? ''}</div>
+            <div>${opt.multiple ? 'multiple' : ''}</div>
             <div>${opt.required === true ? 'required' : ''}</div>
           </div>
-          ${opt.values.map((val) => `
+          ${opt.items.map((item) => `
           <div>
-            <div>${val.id}</div>
-            <div>${val.title}</div>
+            <div>option</div>
+            <div>${item.id}</div>
+            <div>${item.label}</div>
+            <div>${item.value ?? ''}</div>
+            <div>${item.selected ? 'selected' : ''}</div>
+            <div>${item.inStock ? 'inStock' : ''}</div>
           </div>`).join('\n')}`).join('\n')}
           </div>
         </div>
diff --git a/src/templates/json-ld.js b/src/templates/json-ld.js
index d0fc5a8..e0f53e5 100644
--- a/src/templates/json-ld.js
+++ b/src/templates/json-ld.js
@@ -9,54 +9,72 @@
  * OF ANY KIND, either express or implied. See the License for the specific language
  * governing permissions and limitations under the License.
  */
-
 // @ts-check
 
+import { pruneUndefined } from '../util.js';
+
 /**
- * @param {{
-*  sku: string;
-*  url: string;
-*  description: string;
-*  image: string;
-*  name: string;
-*  brandName: string;
-*  reviewCount: number;
-*  ratingValue: number;
-* }} param0
-* @returns {string}
-*/
-export default ({
-  sku,
-  url,
-  name,
-  description,
-  image,
-  brandName,
-  reviewCount,
-  ratingValue,
-}) => JSON.stringify({
-  '@context': 'http://schema.org',
-  '@type': 'Product',
-  '@id': url,
-  name,
-  sku,
-  description,
-  image,
-  productID: sku,
-  brand: {
-    '@type': 'Brand',
-    name: brandName,
-  },
-  offers: [],
-  ...(typeof reviewCount === 'number'
+ * @param {Product} product
+ * @returns {string}
+ */
+export default (product) => {
+  const {
+    sku,
+    url,
+    name,
+    description,
+    images,
+    reviewCount,
+    ratingValue,
+    attributes,
+    inStock,
+    prices,
+  } = product;
+
+  const image = images?.[0].url;
+  const brandName = attributes.find((attr) => attr.name === 'brand')?.value;
+
+  return JSON.stringify(pruneUndefined({
+    '@context': 'http://schema.org',
+    '@type': 'Product',
+    '@id': url,
+    name,
+    sku,
+    description,
+    image,
+    productID: sku,
+    offers: [
+      /**
+       * TODO: add offers from variants, if `product.options[*].product.prices` exists
+       */
+      {
+        '@type': 'Offer',
+        sku,
+        url,
+        image,
+        availability: inStock ? 'InStock' : 'OutOfStock',
+        price: prices.final.amount,
+        priceCurrency: prices.final.currency,
+      },
+    ],
+    ...(brandName
+      ? {
+        brand: {
+          '@type': 'Brand',
+          name: brandName,
+        },
+      }
+      : {}),
+    ...(typeof reviewCount === 'number'
      && typeof ratingValue === 'number'
      && reviewCount > 0
-    ? {
-      aggregateRating: {
-        '@type': 'AggregateRating',
-        ratingValue,
-        reviewCount,
-      },
-    }
-    : {}),
-});
+      ? {
+        aggregateRating: {
+          '@type': 'AggregateRating',
+          ratingValue,
+          reviewCount,
+        },
+      }
+      : {}),
+  }));
+};
diff --git a/src/types.d.ts b/src/types.d.ts
index 2779aa5..444a0d8 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -11,11 +11,6 @@ declare global {
     params: Record<string, string>;
   }
 
-  export interface Product {
-    sku: string;
-    [key: string]: unknown;
-  }
-
   export interface Env {
     VERSION: string;
     ENVIRONMENT: string;
@@ -31,6 +26,83 @@ declare global {
       headers: Record<string, string>;
     }
   }
+
+  export interface Product {
+    name: string;
+    sku: string;
+    addToCartAllowed: boolean;
+    inStock: boolean | null;
+    shortDescription?: string;
+    metaDescription?: string;
+    metaKeyword?: string;
+    metaTitle?: string;
+    description?: string;
+    images: Image[];
+    prices: Prices;
+    attributes: Attribute[];
+    options: ProductOption[];
+    url?: string;
+    urlKey?: string;
+    externalId?: string;
+
+    // not handled currently:
+    externalParentId?: string;
+    variantSku?: string;
+    reviewCount?: number;
+    ratingValue?: number;
+    optionUIDs?: string[];
+  }
+
+  interface Image {
+    url: string;
+    label: string;
+  }
+
+  interface Price {
+    amount?: number;
+    currency?: string;
+    maximumAmount?: number;
+    minimumAmount?: number;
+    variant?: 'default' | 'strikethrough';
+  }
+
+  interface Prices {
+    regular: Price;
+    final: Price;
+    visible: boolean;
+  }
+
+  export interface ProductOption {
+    id: string;
+    type: 'text' | 'image' | 'color' | 'dropdown';
+    typename:
+    | 'ProductViewOptionValueProduct'
+    | 'ProductViewOptionValueSwatch'
+    | 'ProductViewOptionValueConfiguration';
+    label: string;
+    required: boolean;
+    multiple: boolean;
+    items: OptionValue[];
+  }
+
+  interface OptionValue {
+    id: string;
+    label: string;
+    inStock: boolean;
+    value: string;
+    selected: boolean;
+    product?: {
+      name: string;
+      sku: string;
+      prices?: Prices;
+    };
+  }
+
+  interface Attribute {
+    name: string;
+    label: string;
+    value: string;
+  }
 }
 
 export { };
\ No newline at end of file
diff --git a/src/util.js b/src/util.js
index 00ed35e..c7a268e 100644
--- a/src/util.js
+++ b/src/util.js
@@ -71,3 +71,7 @@ export function makeContext(pctx, req, env) {
   };
   return ctx;
 }
+
+export function pruneUndefined(obj) {
+  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
+}