Every OpenCart fix we catalog

SEOLZ catalogs 68 fixes for OpenCart across 4 areas — SEO, answer-engine readiness, accessibility, security and site-health. Each lists the exact steps for OpenCart, with a link to the official docs.

SEO · 31 fixes

Duplicate meta descriptionModerate effort

Write a unique, page-specific meta description for every page on your store so Google can display a compelling, accurate snippet for each one in search results.

On OpenCart

  1. For a product: go to Catalog → Products, edit the product, open the 'SEO' tab, and fill in the 'Meta Tag Description' field.
  2. For a category: go to Catalog → Categories, edit the category, open the 'SEO' tab, and fill in 'Meta Tag Description'.
  3. For information/CMS pages: go to Design → Information, edit the page, and fill in 'Meta Tag Description'.
  4. Install an SEO extension from the OpenCart Marketplace (e.g. 'SEO Pack' or 'Ultimate SEO') for bulk editing capabilities.
  5. After saving, clear the OpenCart cache via Dashboard → Settings (wrench icon) → Refresh or by deleting the /system/storage/cache/ folder via FTP.
  6. Verify the new description appears in page source, and submit to Google Search Console.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <meta name="description" content="Shop women's running shoes at Acme Store — lightweight, cushioned designs for road and trail. Free shipping on orders over $50.">
  <title>Women's Running Shoes | Acme Store</title>
</head>

Place inside the <head> element of every page. Replace the content value with a unique 120–160 character description for that specific page.

Duplicate titleModerate effort

Give every page a unique, descriptive title tag that accurately reflects that page's specific content.

On OpenCart

  1. For products: go to Catalog → Products → edit a product → 'SEO' tab → enter a value in 'Meta Tag Title'.
  2. For categories: Catalog → Categories → edit a category → 'SEO' tab → 'Meta Tag Title'.
  3. For information pages: Catalog → Information → edit a page → 'Meta Tag Title'.
  4. For the default title on the homepage and layout: System → Settings → your store → 'Meta Tag Title'.
  5. Install an SEO extension from the OpenCart marketplace (e.g. SEO Pack Pro) to manage bulk title updates and set dynamic title templates across your catalog.
Code · Generic HTML (html)
<head>
  <title>Merino Wool Running Socks – Free UK Shipping | BrandName</title>
</head>

Place inside <head>. Make the title unique per page — include the specific item name and brand.

Faceted url indexableModerate effort

Point the rel=canonical on every faceted/filter URL to its clean parent category URL (or add noindex), so Google treats filtered pages as a single authoritative page instead of thousands of duplicates.

On OpenCart

  1. OpenCart category pages with filters append query strings like ?filter_color=10&filter_size=5 to category URLs.
  2. Install an SEO extension from the OpenCart marketplace that supports canonical tag management (e.g., 'SEO Pack Pro', 'D_SEO Module') — these allow you to define that filtered category URLs should output a canonical pointing to the base category URL.
  3. Without an extension: edit the catalog/view/theme/[your-theme]/template/product/category.tpl (or .twig in newer OpenCart) file. Find or add the canonical tag in the <head> section and output the base category URL without filter params: <link rel='canonical' href='<?php echo $category_url; ?>'> — where $category_url is the clean category URL passed from the controller.
  4. In catalog/controller/product/category.php, ensure the $data['canonical'] variable is set to the clean category URL (without filter params) and pass it to the template.
  5. Verify with Google Search Console URL Inspection.
Code · Generic HTML (html)
<head>
  <!-- Canonical points to the CLEAN category URL, not the filtered variant -->
  <link rel="canonical" href="https://www.example.com/womens-pants">
</head>

Place this in the <head> of every filtered/faceted page. The href must be the clean parent category URL — never the filtered URL itself.

Images missing altModerate effort

Add descriptive alt text to every product and content image so search engines and screen readers can understand what the image shows.

On OpenCart

  1. FOR PRODUCT IMAGES: Admin > Catalog > Products > open a product > Image tab. The main image alt text is pulled from the Product Name by default; for gallery images, there is no native alt field — install the 'Image Alt Tag' extension from the OpenCart marketplace to add per-image alt fields.
  2. FOR CMS/BANNER IMAGES: Admin > Design > Banners > edit banner > fill in the 'Title' field, which renders as the alt attribute.
Code · Generic HTML (html)
<!-- BEFORE (missing alt — bad) -->
<img src="/images/red-leather-bag.jpg">

<!-- AFTER (descriptive alt — correct) -->
<img src="/images/red-leather-bag.jpg" alt="Red leather crossbody bag with gold buckle clasp">

<!-- Decorative image (intentionally empty alt — correct) -->
<img src="/images/divider-line.png" alt="">

Place this wherever an <img> tag appears in your HTML. Replace the placeholder text with your real description.

Images missing alt bulkModerate effort

Add descriptive alt text to every product and content image at the template level so search engines and screen readers can understand all images on the page.

On OpenCart

  1. Go to Catalog → Products → edit a product. On the 'Image' tab, the main image does not have a native alt field by default — the theme typically uses the product name. Confirm your theme's `catalog/view/theme/YOUR-THEME/template/product/product.twig` (or `.tpl`) sets `alt="{{ heading_title }}"` on the main image.
  2. For the product listing grid, check `catalog/view/theme/YOUR-THEME/template/product/category.twig` and ensure `<img ... alt="{{ product.name }}">` is present.
  3. If your theme is missing alt attributes, edit the `.twig` template files to add them, or install an SEO extension from the OpenCart Marketplace that handles image alt text.
Code · Generic HTML (html)
<!-- Informative product image — describe what the image shows -->
<img src="/images/red-running-shoes-side-view.jpg"
     alt="Red canvas running shoes, side view, white rubber sole"
     width="800" height="800">

<!-- Decorative image — empty alt tells screen readers to skip it -->
<img src="/images/divider-wave.svg"
     alt=""
     role="presentation"
     width="1200" height="40">

Correct alt text pattern for informative images vs. decorative images.

Meta description too longQuick win

Shorten your meta description to 150–160 characters so Google displays it in full instead of cutting it off with "…" in search results.

On OpenCart

  1. Go to Catalog → Products → click Edit on the product → select the 'SEO' tab (or 'Data' → 'Meta Tag Description' depending on version).
  2. Edit the 'Meta Tag Description' field, keeping it under 160 characters.
  3. For category pages: Catalog → Categories → Edit → 'SEO' tab → 'Meta Tag Description'.
  4. Save.
Code · Generic HTML (html)
<meta name="description" content="Award-winning Bang & Olufsen headphones, speakers and TVs. Visit our Malaysia showroom at The Gardens Mall, Level 2. Free shipping on orders over RM500.">

Place this inside the <head> element of every page. Replace the content value with your page-specific description (max 160 characters).

Meta description too shortModerate effort

Expand your meta description to 120–160 characters so Google displays a compelling, complete snippet in search results that drives more clicks to your store.

On OpenCart

  1. Admin → Catalog → Products → select the product → 'SEO' tab → update the 'Meta Tag Description' field → Save.
  2. For categories: Admin → Catalog → Categories → select the category → 'SEO' tab → update 'Meta Tag Description' → Save.
Code · Generic HTML (html)
<meta name="description" content="Shop the official Nike national team jerseys — advanced Dri-FIT heat tech meets bold heritage design. Free shipping on orders over $75. Explore the full collection now.">

Place this inside the <head> element of your HTML page. Replace the content value with your 120–160 character description.

Missing canonicalModerate effort

Add a self-referencing canonical tag to every page so Google knows which URL is the definitive version of that content.

On OpenCart

  1. OpenCart does not output canonical tags by default. Install the 'SEO Pack' or 'SEO Canonical URL' extension from the OpenCart Marketplace or a trusted third-party developer.
  2. After installation, configure the extension from Extensions → Extensions → SEO, enabling canonical tags for product, category, and information pages.
  3. Alternatively, edit your theme's `header.twig` (or `header.tpl` for older versions) to add `<link rel='canonical' href='{{ canonical }}'>` and pass the canonical URL variable from the relevant controllers.
  4. Clear the OpenCart cache under System → Maintenance → Cache and verify via page source.
Code · Generic HTML (html)
<link rel="canonical" href="https://www.yourstore.com/your-page/" />

Paste this inside the <head> element of every page. Replace the href value with the full, absolute preferred URL of that specific page.

Missing h1Quick win

Add a single, descriptive H1 tag to every page that currently lacks one, clearly stating the page's main topic.

On OpenCart

  1. For product pages: the Product Name is output as H1 by default in catalog/view/theme/<theme>/template/product/product.twig (or product.tpl in older versions). Ensure the product name is filled in Catalog → Products.
  2. For category pages: Catalog → Categories → ensure the Category Name is filled in; it renders as H1 in the category template.
  3. If the template uses a non-H1 tag, edit product.twig and change the wrapping element to <h1 itemprop="name">{{ heading_title }}</h1>.
  4. Save and verify in browser DevTools.
Code · Generic HTML (html)
<h1>Women's Running Shoes</h1>

Place exactly one of these per page, as the first heading in the main content area.

Missing meta descriptionModerate effort

Write and add a unique meta description (120–160 characters) to every page that is missing one, so Google can display an accurate, compelling snippet in search results.

On OpenCart

  1. For a product: Admin → Catalog → Products → click the product → 'SEO' tab → 'Meta Tag Description'.
  2. For a category: Admin → Catalog → Categories → click the category → 'SEO' tab → 'Meta Tag Description'.
  3. For information pages: Admin → Catalog → Information → click the page → 'Meta Tag Description'.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <meta name="description" content="Shop our full range of handmade leather wallets. Free shipping on orders over $50. Slim, durable & personalised — order yours today.">
  <title>Handmade Leather Wallets | YourStoreName</title>
</head>

Place this tag inside the <head> element of every HTML page. Keep the content between 120–160 characters.

Missing og descriptionModerate effort

Add an og:description meta tag to every page so that social media platforms display a compelling, accurate preview when your pages are shared.

On OpenCart

  1. Admin → Catalog → Products → select a product → click the 'SEO' tab (or 'Data' tab depending on version) → fill in 'Meta Tag Description'.
  2. OpenCart's default Storefront themes render og:description from this value. Check catalog/view/theme/[your-theme]/template/common/head.twig (or head.tpl in older versions) for the og:description meta tag.
  3. If missing from the template, add: {% if description %}<meta property="og:description" content="{{ description }}">{% endif %} inside the <head> block.
  4. For categories: Catalog → Categories → select category → fill in 'Meta Tag Description'.
  5. Install an SEO extension from the OpenCart marketplace (search 'Open Graph') for a no-code solution.
  6. Validate with Facebook Sharing Debugger after saving.
Code · Generic HTML (html)
<head>
  <!-- Open Graph / Social Sharing -->
  <meta property="og:type"        content="website" />
  <meta property="og:url"         content="https://www.example.com/page-url/" />
  <meta property="og:title"       content="Page Title – Brand Name" />
  <meta property="og:description" content="A concise, compelling summary of this page in 100–160 characters that encourages clicks." />
  <meta property="og:image"       content="https://www.example.com/images/social-preview.jpg" />
</head>

Place all Open Graph tags inside the <head> element of every page.

Missing titleQuick win

Add a unique, descriptive title tag (30–60 characters) to every page that is missing one.

On OpenCart

  1. For a product: go to Catalog → Products → select the product → click the 'SEO' tab → fill in the 'Meta Tag Title' field.
  2. For a category: Catalog → Categories → select the category → 'SEO' tab → 'Meta Tag Title'.
  3. Click Save.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Slim Bifold Leather Wallet | BrandName</title>
</head>
<body>
  <!-- page content -->
</body>
</html>

Place this inside the <head> element of every HTML page. Keep the text between 30–60 characters.

Noindex detectedQuick win

Remove or replace the noindex directive on any page you want to appear in Google search results.

On OpenCart

  1. OpenCart does not natively output noindex tags — check any installed SEO extensions (e.g., SEO Extension Pro, OpenCart SEO) in Admin → Extensions → Extensions for a per-page or site-wide noindex toggle.
  2. Check your theme's `header.twig` (or `header.tpl` for older versions) in the `/catalog/view/theme/your-theme/template/common/` directory for any hard-coded robots meta tags and remove them.
Code · Generic HTML (html)
<meta name="robots" content="index,follow">

Place inside <head>. This is the CORRECT tag that allows indexing and link-following.

Page speed warningModerate effort

Reduce your page load time to under 1.5 seconds by optimizing images, eliminating render-blocking resources, and enabling caching and compression.

On OpenCart

  1. In the OpenCart admin go to System > Settings > [your store] > Server tab: set 'Compression Level' to a value between 1–9 (use 6 as a balanced default) to enable Gzip output compression.
  2. Enable the built-in OpenCart cache: go to System > Settings > Server tab and set 'Cache' to the file-based cache driver and enter a cache expiry time (3600 seconds is a good starting point).
  3. Images: Before uploading, compress product images using a tool like Squoosh (squoosh.app) and save in WebP format. Install an image optimisation extension from the OpenCart Marketplace to automate this on future uploads.
  4. Extensions: Go to Extensions > Extensions > Modules and disable any module not in active use — each enabled module may add queries and assets to every page load.
  5. For a larger performance gain, configure your web server (Apache/Nginx) to serve Gzip-compressed files and add browser cache headers — this requires access to your hosting control panel (e.g. cPanel > .htaccess) or your server's Nginx config.
Seo category description missingModerate effort

Write a 100–200 word description for every category page explaining what products it contains and who they are for, so Google can rank it for category-level keywords.

On OpenCart

  1. In the OpenCart admin, go to Catalog → Categories.
  2. Click Edit next to the category.
  3. In the 'Description' field on the General tab, use the rich-text editor to write your 100–200 word category description.
  4. Click Save.
  5. The description is rendered in your theme's category.twig (OpenCart 3+) or category.tpl (OpenCart 2) template. Most default themes display it at the top of the category page.
Code · Generic HTML (html)
<section class="category-description">
  <h1>Women's Waterproof Hiking Boots</h1>
  <p>
    Our women's waterproof hiking boots are built for trail runners, weekend
    trekkers, and serious mountaineers who refuse to let wet weather slow them
    down. Every pair in this collection features a fully sealed waterproof
    membrane, aggressive rubber outsoles for grip on loose or muddy terrain,
    and reinforced ankle support to protect against sprains on uneven ground.
    Whether you're day-hiking a local forest trail or taking on a multi-day
    alpine route, you'll find boots here rated for every conditions and
    experience level. Lightweight options keep pack weight low on fast-and-light
    adventures, while insulated models keep feet warm below freezing. Browse the
    full range below to find your perfect fit.
  </p>
</section>

Place this block near the top of your category page, above the product grid.

Seo category description thinModerate effort

Expand your category page description to at least 100 words of genuinely helpful, keyword-rich content that explains what shoppers will find in that category.

On OpenCart

  1. In OpenCart admin, go to Catalog > Categories and edit the desired category.
  2. On the 'General' tab, locate the 'Description' HTML editor and write your 100+ word description.
  3. Switch to the 'SEO' tab and fill in the Meta Description field as well.
  4. Click 'Save'. If the description does not display on the front end, check your theme's category.twig (or category.tpl in older versions) for {{ description }} output.
Code · Generic HTML (html)
<section class="category-description">
  <h2>Shop Our Women's Running Shoes</h2>
  <p>
    Discover our curated collection of women's running shoes, designed for every
    type of runner — from casual joggers to marathon enthusiasts. Whether you
    need extra cushioning for long-distance road running, a lightweight racer
    for speed training, or a grippy trail shoe for off-road adventures, you'll
    find the perfect fit here.
  </p>
  <p>
    We stock top brands including Brooks, ASICS, New Balance, and Hoka, all
    rigorously tested for comfort, durability, and performance. Each pair is
    available in a wide range of widths and sizes, so you never have to
    compromise on fit. Browse by surface type, cushion level, or pronation
    support to narrow down your ideal shoe in seconds.
  </p>
  <p>
    Not sure where to start? Check out our
    <a href="/guides/running-shoe-guide">Running Shoe Buying Guide</a> or
    explore our <a href="/collections/running-accessories">Running Accessories</a>
    collection to complete your kit.
  </p>
</section>

Paste this pattern into any category page template. Replace placeholder text with your real content.

Seo category emptyQuick win

Either populate the empty category with products, or add a noindex tag and remove it from your navigation so Google does not waste crawl budget on a page with no content.

On OpenCart

  1. In the OpenCart admin, go to Catalog → Categories.
  2. Edit the empty category.
  3. Option A — Add products: Go to Catalog → Products, edit each product, and in the 'Links' tab assign it to this category.
  4. Option B — Noindex: OpenCart has no built-in noindex field. Go to the category's edit page → SEO tab → this tab handles meta titles/descriptions but not robots. Edit your theme's catalog/view/theme/[your-theme]/template/product/category.tpl and add <meta name='robots' content='noindex, follow'> inside the <head> block when the product count is zero, or install the 'SEO Pack Pro' extension.
  5. Option C — Delete & redirect: Delete the category. Go to System → Tools → URL Redirects if your version supports it, or install the '301 Redirect' extension and add a redirect from the old category URL to the parent.
  6. Remove the category from navigation by editing Catalog → Categories and setting 'Status' to 'Disabled', or remove it from the top navigation module under Extensions → Modules.
Code · Generic HTML (html)
<meta name="robots" content="noindex, follow">

Place inside the <head> element of an empty category page to prevent Google from indexing it.

Seo facet url not canonicalizedModerate effort

Add a canonical tag to every filtered, sorted, and paginated collection/category URL pointing back to the clean (unfiltered) category URL, so Google consolidates duplicate pages and focuses crawl budget on your real content.

On OpenCart

  1. OpenCart does not output canonical tags by default. Install an SEO extension such as 'SEO Mega Pack' or 'Journal3 SEO' from the OpenCart Marketplace that adds canonical tag support.
  2. Once installed, configure the extension to set the canonical URL for category pages to the base category URL, stripping filter/sort query parameters.
  3. Alternatively, edit catalog/view/theme/[your-theme]/template/common/header.twig to manually add: <link rel="canonical" href="{{ canonical_url }}"> and set $data['canonical_url'] in the Header controller to the clean category URL.
  4. Clear the OpenCart cache via Dashboard → Settings and verify with page source inspection.
Code · Generic HTML (html)
<head>
  <!-- All other head tags -->
  <link rel="canonical" href="https://www.example.com/collections/hoops">
</head>

Place this inside the <head> of every filtered/sorted/paginated category page. The href must be the full, absolute clean URL (no query parameters).

Seo product description minimalModerate effort

Expand your product descriptions to at least 300 words by adding specifications, use cases, and benefits so search engines have enough content to rank and shoppers have enough information to buy.

On OpenCart

  1. Go to OpenCart Admin → Catalog → Products.
  2. Click 'Edit' on the product.
  3. Select the 'General' tab.
  4. Expand the 'Description' rich-text editor field and write a 300+ word description with specs, use cases, and benefits.
  5. Click 'Save'. Navigate to the live product page and use Google Search Console to request re-indexing.
Code · Generic HTML (html)
<section class="product-description">
  <h2>About the [Product Name]</h2>
  <p>
    The [Product Name] is a [category] designed for [target audience]. Whether you need
    [use case 1] or [use case 2], this [product] delivers [core benefit] without
    [common pain point].
  </p>

  <h3>Key Features &amp; Specifications</h3>
  <ul>
    <li><strong>Material:</strong> [e.g. 100% organic cotton, BPA-free polypropylene]</li>
    <li><strong>Dimensions:</strong> [W × H × D] / Weight: [X kg / X lbs]</li>
    <li><strong>Colour options:</strong> [Colour 1], [Colour 2], [Colour 3]</li>
    <li><strong>Compatibility:</strong> [e.g. Works with iOS 14+ and Android 10+]</li>
    <li><strong>Warranty:</strong> [X-year manufacturer warranty]</li>
  </ul>

  <h3>Why Shoppers Choose [Product Name]</h3>
  <p>
    Unlike [generic alternatives], the [Product Name] [specific differentiator]. This means
    [tangible benefit for the shopper], saving you [time / money / effort].
  </p>
  <p>
    Ideal for [persona / scenario], the [Product Name] has been tested to [standard or
    certification] and ships ready to use — no assembly required.
  </p>

  <h3>How to Use</h3>
  <p>
    [Step-by-step or scenario-based usage instructions in 2–4 sentences. Include any
    important setup, safety, or care notes here.]
  </p>

  <h3>In the Box</h3>
  <ul>
    <li>1 × [Product Name]</li>
    <li>1 × [Accessory or manual]</li>
    <li>1 × [Charging cable / carry bag / etc.]</li>
  </ul>
</section>

Structural template for a product description section — paste inside your product page's main content area.

Seo product image alt filenameModerate effort

Replace auto-generated filename-based alt text on product images with short, descriptive phrases that accurately describe what the image shows.

On OpenCart

  1. Go to Admin → Catalog → Products and open a product.
  2. Click the 'Image' tab.
  3. OpenCart does not have a native alt text field per product image by default; install the 'Image Alt Tag' or 'SEO Image Alt' extension from the OpenCart marketplace.
  4. After installing, the extension adds an alt text input next to each image in the product editor — fill it in and save.
Code · Generic HTML (html)
<!-- BAD: filename-based alt text -->
<img src="/images/IMG_4823_blue_v2.webp" alt="IMG_4823_blue_v2.webp">

<!-- BAD: empty alt text -->
<img src="/images/blue-sweater.webp" alt="">

<!-- GOOD: descriptive alt text -->
<img src="/images/blue-sweater.webp" alt="Blue cotton crew-neck sweater, front view">

The correct pattern for a product image alt attribute — descriptive, specific, keyword-natural.

Seo product missing brandModerate effort

Add a `brand` property to your Product schema markup so search engines can match your listings to brand-specific queries.

On OpenCart

  1. Open your active theme's product template, typically `catalog/view/theme/<your-theme>/template/product/product.twig` (OpenCart 3+).
  2. Find or add a `<script type='application/ld+json'>` block and insert the brand property using the manufacturer variable: `"brand": {"@type": "Brand", "name": "{{ manufacturer }}"}` — the manufacturer variable is passed from the product controller.
  3. Make sure every product has a Manufacturer assigned under Catalog → Products → edit product → Links tab → Manufacturer.
  4. Install an SEO extension from OpenCart's marketplace if you prefer a no-code approach (search 'structured data' or 'schema markup').
  5. Test with Google's Rich Results Test after saving and refreshing the storefront cache.
Seo product missing gtinModerate effort

Add a GTIN (and/or MPN) field to your Product structured data so Google can match your listings to its product knowledge graph for rich results and Google Shopping.

On OpenCart

  1. Go to Admin → Catalog → Products → [select a product] → 'Data' tab.
  2. Enter the product's UPC/EAN in the 'UPC', 'EAN', or 'ISBN' field (OpenCart has multiple barcode fields natively).
  3. Install a 'Rich Snippets' or 'Structured Data' extension from the OpenCart Extension Store (e.g. 'Product Rich Snippets Schema') that reads these fields and outputs valid JSON-LD including the 'gtin' property.
  4. Validate with Google's Rich Results Test after installation.
Seo product missing stock statusModerate effort

Add a visible stock availability label and an `availability` property to your Product structured data so Google can display rich results and shoppers know immediately whether a product is in stock.

On OpenCart

  1. OpenCart does not include Product JSON-LD by default. Install an extension such as *JSON-LD Structured Data* from the OpenCart Marketplace to auto-generate schema including `availability` from your inventory.
  2. After installing, configure the extension to map 'In Stock' (quantity > 0) → `https://schema.org/InStock` and 'Out of Stock' → `https://schema.org/OutOfStock`.
  3. For a manual implementation, edit `catalog/view/theme/<your-theme>/template/product/product.twig` and add the JSON-LD script block (see Generic HTML snippet), using Twig variables to pull stock status.
  4. Validate in the Rich Results Test.
Code · Generic HTML (html)
<!-- Product structured data with availability -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Your Product Name",
  "description": "A short description of your product.",
  "sku": "YOUR-SKU-123",
  "image": "https://www.example.com/images/product.jpg",
  "brand": {
    "@type": "Brand",
    "name": "Your Brand Name"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/products/your-product",
    "priceCurrency": "USD",
    "price": "29.99",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>

<!-- Visible stock label near Add-to-Cart button -->
<p class="stock-status" style="font-weight:bold; color:#2a7a2a;">&#10003; In Stock</p>

Paste the complete block into the <head> section of your product page HTML. Change availability value to OutOfStock or PreOrder as needed.

Seo product no imagesModerate effort

Add at least one high-quality image to every product page so shoppers can see what they're buying and Google can surface your products in Image Search.

On OpenCart

  1. Log in to your OpenCart admin and navigate to Catalog → Products.
  2. Click 'Edit' on the product that needs images.
  3. Click the 'Image' tab at the top of the product editor.
  4. For the main image, click 'Edit' next to 'Image' and select or upload a file from the file manager.
  5. Under 'Additional Images', click 'Add Image' to upload additional product photos.
  6. Click 'Save' and then view the product page on the storefront to confirm images are displaying.
Code · Generic HTML (html)
<figure class="product-gallery">
  <img
    src="/images/products/black-leather-wallet-front.jpg"
    alt="Black leather wallet with RFID blocking, front view"
    width="800"
    height="800"
    loading="eager"
  />
  <img
    src="/images/products/black-leather-wallet-open.jpg"
    alt="Black leather wallet open showing card slots and cash pocket"
    width="800"
    height="800"
    loading="lazy"
  />
</figure>

Basic responsive product image with descriptive alt text — place inside your product page HTML where the gallery/main image appears.

Seo product no spec tableModerate effort

Add a structured specifications table or list to every product page so shoppers, search engines, and AI tools can instantly find and use key product details.

On OpenCart

  1. In the OpenCart admin go to Catalog → Products → (select product) → 'Attribute' tab.
  2. Search for and add relevant attributes (created under Catalog → Attributes) and enter the value for each one on this product.
  3. OpenCart renders product attributes in a 'Specification' tab on the product page by default in most themes.
  4. To restyle the tab, edit 'catalog/view/theme/<your-theme>/template/product/product.twig' (or .tpl in older versions).
  5. Save and check the product page front-end.
Seo product price not in htmlModerate effort

Render your product price in the server-generated HTML so Google and other crawlers can read it directly — without executing JavaScript.

On OpenCart

  1. OpenCart renders prices via PHP template files. Open catalog/view/theme/<your-theme>/template/product/product.twig (OpenCart 3+) and confirm the price is output via {{ price }} — a server-side PHP variable.
  2. If a 3rd-party extension replaced the price with a JS-rendered element, revert to the core template or configure the extension to output the price server-side.
  3. For JSON-LD, install an SEO extension from the OpenCart Marketplace that adds Product structured data (e.g. OpenCart SEO Extension by OpenCart.com) and outputs price in the 'offers' property.
  4. Verify by viewing page source.
Seo product single imageModerate effort

Add at least 4 product images (multiple angles, detail shots, and lifestyle/in-use photos) to every product page to improve click-through rates, conversions, and Google Images visibility.

On OpenCart

  1. Go to Admin → Catalog → Products → [select your product] → Image tab.
  2. The 'Image' field at the top sets the main product image; use the image picker to change it.
  3. In the 'Additional Images' section below, click the '+' button to add as many extra images as needed.
  4. OpenCart's core does not have a per-image alt text field by default — install the free 'SEO Images' extension from the OpenCart marketplace to enable per-image alt text.
  5. Click 'Save' when done.
Code · Generic HTML (html)
<figure class="product-gallery">
  <!-- Main hero image -->
  <img
    src="/images/red-leather-wallet-front.webp"
    alt="Red leather bifold wallet — front view"
    width="2000"
    height="2000"
    loading="eager"
    fetchpriority="high"
  />
</figure>

<!-- Thumbnail strip -->
<ul class="product-thumbnails" role="list">
  <li>
    <img
      src="/images/red-leather-wallet-front-thumb.webp"
      alt="Red leather bifold wallet — front view"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
  <li>
    <img
      src="/images/red-leather-wallet-back-thumb.webp"
      alt="Red leather bifold wallet — back view"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
  <li>
    <img
      src="/images/red-leather-wallet-interior-thumb.webp"
      alt="Red leather bifold wallet — interior card slots"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
  <li>
    <img
      src="/images/red-leather-wallet-lifestyle-thumb.webp"
      alt="Red leather bifold wallet shown in a shirt pocket"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
</ul>

Basic accessible product image gallery markup. Place inside your product detail page template.

Seo product title genericModerate effort

Rewrite every product page title tag to include the specific product name plus at least one differentiating attribute (variant, material, brand, or model) so Google and shoppers can instantly tell what the page is about.

On OpenCart

  1. Log in to the OpenCart admin and go to Catalog → Products.
  2. Click Edit on the target product.
  3. Go to the 'SEO' tab (or 'Data' tab depending on version).
  4. Update the 'Meta Tag Title' field with your descriptive product title.
  5. Click Save.
  6. For bulk updates, use an SEO extension from the OpenCart marketplace or export/import via the built-in import tool, editing the 'meta_title' column.
Code · Generic HTML (html)
<head>
  <title>Magnetic Card Holder Phone Mount – MagSafe Compatible – BrandName</title>
  <meta name="description" content="Attach your cards securely to any MagSafe-compatible iPhone with BrandName's slim magnetic card holder mount. Holds 1–3 cards. Available in black and white.">
</head>

Place inside the <head> section of every product page. Follow the pattern: Product Name – Key Attribute – Brand.

Server errorModerate effort

Identify and fix every page returning an HTTP 5xx server error so search engines can crawl and index those URLs again.

On OpenCart

  1. Enable OpenCart's error log via Admin > System > Settings > [Store] > Server tab, set Error Log to Yes.
  2. Download and review the error log from system/logs/error.txt — it will show the PHP error and file causing the 500.
  3. Disable recently installed extensions via Admin > Extensions > Extensions, select the type, and Uninstall/Disable.
  4. Check file permissions on the server; OpenCart requires specific permissions (e.g. 755 on directories, 644 on files) — wrong permissions can cause 500 errors.
  5. After fixing, submit the URL in Google Search Console > URL Inspection > Request Indexing.
Code · Generic HTML (html)
# Run this in your terminal to check the HTTP status code of any URL
curl -o /dev/null -s -w "HTTP Status: %{http_code}\n" https://www.example.com/your-page

# A healthy page returns:
# HTTP Status: 200

# A broken page returns (example):
# HTTP Status: 500

This is the correct HTTP 200 response header your server should return for a healthy page. Use curl on the command line to verify any URL's status code before and after your fix.

Title too longQuick win

Shorten your page title tag to 60 characters or fewer so it displays fully — without truncation — in Google search results.

On OpenCart

  1. For a product: go to Catalog → Products, edit the product, open the 'SEO' tab (or 'Data' tab in older versions), and edit the 'Meta Tag Title' to ≤60 characters.
  2. For a category: go to Catalog → Categories, edit the category, open the 'SEO' tab, and edit 'Meta Tag Title'.
  3. Save changes and clear the store cache.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <title>Running Shoes for Men | YourBrand</title>
</head>

Place inside the <head> element of every HTML page. Keep the total character count at or under 60.

Title too shortQuick win

Expand your page title to between 30 and 60 characters so Google displays it fully in search results and shoppers can immediately understand what the page is about.

On OpenCart

  1. For a product: go to Catalog → Products → select the product → click the 'SEO' tab → update the 'Meta Tag Title' field.
  2. For a category: go to Catalog → Categories → select the category → 'SEO' tab → update 'Meta Tag Title'.
  3. For information pages: go to Catalog → Information → select the page → update 'Meta Tag Title'.
  4. Save and then clear the OpenCart cache (if using a caching extension) so the new title tag is served immediately.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <title>Men's Merino Wool Beanies – BrandName</title>
</head>

Place inside the <head> element of every page. Replace the example with your actual descriptive title (30–60 chars).

Accessibility (WCAG) · 12 fixes

Aria required childrenModerate effort

Add the required child ARIA roles (e.g. `role="option"` inside `role="listbox"`, or `role="group"`/`role="option"` inside `role="combobox"`) so every composite ARIA widget contains the child roles the ARIA specification demands.

On OpenCart

  1. Connect via FTP/SFTP to your server. Template files are in catalog/view/theme/[your-theme]/template/.
  2. Identify the relevant .twig or .tpl file (e.g. common/header.twig, product/category.twig).
  3. Edit the file to add the correct child role attributes to children of ARIA parent roles, or remove tabindex from img/a elements that are illegal children.
  4. Save the file and hard-refresh your browser. Test with axe DevTools.
Button nameModerate effort

Add a descriptive, screen-reader-accessible name to every button on your site so assistive technology users know what each button does.

On OpenCart

  1. Navigate to your theme folder: catalog/view/theme/<your-theme>/template/. Open the relevant .twig or .tpl file (e.g. common/header.twig, product/product.twig).
  2. Locate the <button> element and add aria-label="Descriptive name" to the tag.
  3. Save, refresh the store cache if applicable, and verify with axe DevTools.
Code · Generic HTML (html)
<!-- PATTERN 1: Icon-only button — add aria-label directly on the button -->
<button type="button" aria-label="Open search">
  <svg aria-hidden="true" focusable="false" width="24" height="24" viewBox="0 0 24 24">
    <path d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"
          stroke="currentColor" stroke-width="2" fill="none"/>
  </svg>
</button>

<!-- PATTERN 2: Button with an <img> icon — give the img a descriptive alt -->
<button type="button">
  <img src="/icons/cart.svg" alt="View shopping cart" width="24" height="24">
</button>

<!-- PATTERN 3: Button labelled by visible text elsewhere on the page -->
<h2 id="newsletter-heading">Subscribe to our newsletter</h2>
<!-- ... form fields ... -->
<button type="submit" aria-labelledby="newsletter-heading">Subscribe</button>

<!-- PATTERN 4: Visible text button — just ensure the text is real HTML text -->
<button type="button">Add to Cart</button>

<!-- PATTERN 5: Quantity stepper buttons -->
<button type="button" aria-label="Decrease quantity">−</button>
<input type="number" value="1" aria-label="Product quantity">
<button type="button" aria-label="Increase quantity">+</button>

Use one of these patterns depending on your button's content. Apply the correct pattern to every icon-only or ambiguous button on your site.

Color contrastQuick win

Increase the contrast ratio between your text color and its background color to meet the WCAG 4.5:1 minimum, so all users — including those with low vision — can read your content clearly.

On OpenCart

  1. Navigate to catalog/view/theme/<your-theme>/stylesheet/stylesheet.css (via FTP or the admin file editor under Extensions → File Manager if available).
  2. Search for the failing color hex and replace it with your accessible value.
  3. Clear the site cache and verify the change.
Code · Generic HTML (css)
/* BEFORE (fails WCAG 1.4.3 — ratio 4.47:1 on white) */
.secondary-text {
  color: #777777;
}

/* AFTER (passes WCAG 1.4.3 — ratio ~7.0:1 on white) */
.secondary-text {
  color: #595959;
}

Replace the failing color with one that achieves 4.5:1+ contrast on white (#ffffff). #595959 achieves ~7.0:1 on white.

Empty table headerQuick win

Add visible or screen-reader-accessible text to every empty `<th>` table header cell so assistive technology users can understand what each column or row represents.

On OpenCart

  1. Via FTP or your hosting file manager, navigate to catalog/view/theme/<your-theme>/template/ and find .twig or .tpl files containing table markup.
  2. Add text or `aria-label` to empty `<th>` elements in those template files.
  3. In the OpenCart Admin, go to System → Maintenance → Clear Cache to flush the template cache.
  4. Verify with axe DevTools.
Code · Generic HTML (html)
<!-- OPTION 1: Add visible text (best for most cases) -->
<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>Basic Plan</th>
      <th>Pro Plan</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Storage</td>
      <td>5 GB</td>
      <td>50 GB</td>
    </tr>
  </tbody>
</table>

<!-- OPTION 2: Visually hidden text (for top-left corner cells) -->
<style>
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
</style>
<table>
  <thead>
    <tr>
      <th><span class="sr-only">Feature name</span></th>
      <th>Basic Plan</th>
      <th>Pro Plan</th>
    </tr>
  </thead>
</table>

<!-- OPTION 3: aria-label directly on the th element -->
<table>
  <thead>
    <tr>
      <th aria-label="Feature name"></th>
      <th>Basic Plan</th>
      <th>Pro Plan</th>
    </tr>
  </thead>
</table>

Option 1 — visible text in the header cell (preferred). Option 2 — visually hidden text via a span. Option 3 — aria-label on the th element.

Html has langQuick win

Add a valid `lang` attribute to the `<html>` element so browsers, screen readers, and search engines know what language your page is written in.

On OpenCart

  1. In your OpenCart admin, go to System → Settings → Store and ensure the Language is correctly set.
  2. Open your active theme's main template file, typically catalog/view/theme/[your-theme]/template/common/header.twig.
  3. Find the `<html>` tag and update it to: `<html lang="{{ lang }}">` (OpenCart passes a lang variable to templates).
  4. Save and verify via View Source.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Your Page Title</title>
</head>
<body>
  <!-- page content -->
</body>
</html>

Add the lang attribute to the opening <html> tag of every HTML page. Use a valid BCP 47 language tag.

Landmark no duplicate bannerModerate effort

Ensure your page has only one banner landmark (one `<header>` or `role="banner"` element that is not nested inside another landmark), so screen reader users can navigate your site reliably.

On OpenCart

  1. In the OpenCart admin, go to Design → Theme Editor, or access the files directly via FTP at catalog/view/theme/<your-theme>/template/common/header.tpl (or header.twig for OpenCart 3+).
  2. Open header.twig/header.tpl and search for '<header' and 'role="banner"'. There should be only one.
  3. Identify any module or extension that is injecting a second <header> (check catalog/view/theme/<theme>/template/ for module templates).
  4. Change duplicate <header> elements to <div> and remove role="banner" from any that are not the primary site header.
  5. Refresh the OpenCart cache under Admin → Dashboard → (gear icon) → Refresh, then verify with axe DevTools.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Store Page</title>
</head>
<body>

  <!-- CORRECT: Single top-level <header> = one banner landmark -->
  <header>
    <a href="/"><img src="logo.svg" alt="Acme Store"></a>
    <nav aria-label="Main navigation">
      <ul>
        <li><a href="/shop">Shop</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <article>
      <!-- CORRECT: <header> nested inside <article> is NOT a banner landmark -->
      <header>
        <h1>Product Name</h1>
        <p>Posted: <time datetime="2024-01-01">January 1, 2024</time></p>
      </header>
      <p>Product description goes here.</p>
    </article>

    <!-- CORRECT: promotional strip uses <div>, not <header> -->
    <div class="promo-bar" role="region" aria-label="Promotional offer">
      <p>Free shipping on orders over $50!</p>
    </div>
  </main>

  <footer>
    <p>&copy; 2024 Acme Store</p>
  </footer>

</body>
</html>

Correct page structure: one top-level <header> as the banner, inner content sections use <div> or are wrapped in a sectioning element.

Landmark no duplicate mainQuick win

Find and remove every duplicate `<main>` element or `role="main"` attribute so that exactly one main landmark exists on each page.

On OpenCart

  1. Open your active theme's template files under catalog/view/theme/<your-theme>/template/common/. Check header.twig/header.tpl and footer.twig/footer.tpl for '<main' or 'role="main"'.
  2. The primary content wrapper is typically in catalog/view/theme/<your-theme>/template/common/content*.twig. Keep exactly one '<main>' there and convert any duplicates to '<div>'.
  3. Refresh the OpenCart cache and verify with the axe extension.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Page Title</title>
</head>
<body>

  <header>
    <nav aria-label="Primary navigation">
      <!-- site-wide navigation -->
    </nav>
  </header>

  <!-- ✅ Only ONE <main> element per page -->
  <main id="main-content">
    <h1>Page Heading</h1>
    <!-- primary page content goes here -->
  </main>

  <footer>
    <!-- footer content -->
  </footer>

</body>
</html>

Correct page structure: exactly one <main> element wrapping the primary content, with header and footer outside it.

Landmark one mainQuick win

Add a single `<main>` landmark element (or `role="main"`) to every page so assistive technologies can identify and navigate directly to the primary content.

On OpenCart

  1. Navigate to your OpenCart theme folder: `catalog/view/theme/<your-theme>/template/common/`.
  2. Open `header.twig` and `footer.twig` to find where the main content `<div>` opens and closes.
  3. In your layout file (often embedded in `index.twig` or passed from the controller), wrap the `{{ body }}` output in `<main id="main-content">…</main>`.
  4. Clear the OpenCart cache via Admin → Dashboard → the gear icon → Refresh, and verify with axe DevTools.
Code · Generic HTML (html)
<!-- Skip link at the very top of <body> -->
<a href="#main-content" class="skip-link">Skip to main content</a>

<header><!-- site header / nav here --></header>

<main id="main-content">
  <!-- ALL primary page content goes here:
       product details, blog post, category grid, etc. -->
</main>

<footer><!-- site footer here --></footer>

Place this landmark element around all unique page content, after the <header> and <nav> and before the <footer>. Use exactly one per page.

Meta viewportQuick win

Remove `user-scalable=no` (and any `maximum-scale` below 5) from your site's `<meta name="viewport">` tag so visitors can pinch-to-zoom on mobile devices.

On OpenCart

  1. Via FTP, open catalog/view/theme/<your-theme>/template/common/header.twig (or header.tpl for older versions).
  2. Find the <meta name="viewport"> tag and remove `user-scalable=no`.
  3. Save and upload the file; clear OpenCart's cache via Admin → Dashboard → gear icon → Refresh.
Code · Generic HTML (html)
<meta name="viewport" content="width=device-width, initial-scale=1">

Place this in the <head> of every HTML page. This is the correct, accessibility-compliant viewport tag.

Meta viewport largeQuick win

Remove or raise the `maximum-scale` value in your meta viewport tag so users can pinch-zoom on mobile devices.

On OpenCart

  1. Via FTP/SFTP, navigate to your theme folder: catalog/view/theme/<your-theme>/template/common/.
  2. Open 'header.twig' (OpenCart 3+) or 'header.tpl' (OpenCart 2.x).
  3. Find the <meta name="viewport"> tag and remove zoom-blocking attributes.
  4. Save and refresh your store. Clear any server-side cache if applicable.
Code · Generic HTML (html)
<meta name="viewport" content="width=device-width, initial-scale=1">

Replace any existing <meta name="viewport"> tag in your <head> with this. Omitting maximum-scale and user-scalable entirely is the safest, most accessible choice.

Select nameQuick win

Add a visible or programmatic label to every `<select>` dropdown so assistive technologies can announce its purpose to users.

On OpenCart

  1. Navigate to your OpenCart store's theme files. The default theme path is 'catalog/view/theme/YOUR-THEME/template/'.
  2. Find the template with the offending <select> (e.g. 'product/product.tpl' for variant selectors, 'checkout/checkout.tpl' for address dropdowns).
  3. Add a <label for="MATCHING-ID">Descriptive text</label> before each bare <select>, or add aria-label="Descriptive text" directly on the element.
  4. Save, clear caches from Admin → Dashboard → the blue cog icon → Refresh, then verify with axe DevTools.
Code · Generic HTML (html)
<!-- Method 1: Visible label (preferred) -->
<label for="sort-order">Sort by</label>
<select id="sort-order" name="sort">
  <option value="price-asc">Price: Low to High</option>
  <option value="price-desc">Price: High to Low</option>
  <option value="newest">Newest</option>
</select>

<!-- Method 2: Visually hidden label (when visible label breaks design) -->
<style>
  .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
</style>
<label for="sort-order" class="visually-hidden">Sort by</label>
<select id="sort-order" name="sort">
  <option value="price-asc">Price: Low to High</option>
  <option value="price-desc">Price: High to Low</option>
  <option value="newest">Newest</option>
</select>

<!-- Method 3: aria-label (last resort, no visible label possible) -->
<select aria-label="Sort by" name="sort">
  <option value="price-asc">Price: Low to High</option>
  <option value="price-desc">Price: High to Low</option>
  <option value="newest">Newest</option>
</select>

<!-- Method 4: Wrapping label (implicit association — no for/id needed) -->
<label>
  Sort by
  <select name="sort">
    <option value="price-asc">Price: Low to High</option>
    <option value="price-desc">Price: High to Low</option>
    <option value="newest">Newest</option>
  </select>
</label>

The gold-standard approach: a visible <label> linked to the <select> via matching for/id attributes.

Skip linkQuick win

Add a valid, focusable target element (with a matching `id`) for every "Skip to main content" link so keyboard and assistive-technology users can bypass repeated navigation.

On OpenCart

  1. Via FTP, open your active theme's main layout file at `catalog/view/theme/<your-theme>/template/common/header.tpl` (or `header.twig` for OpenCart 3.x+).
  2. Add the skip link immediately after the opening `<body>` tag (which may be in `index.tpl` — check your theme structure).
  3. Add `id="main-content" tabindex="-1"` to the `<div>` or `<main>` element that wraps the page's primary content in your layout.
  4. Add the CSS for `.skip-link` to your theme's stylesheet.
  5. Refresh OPcache/file cache in the OpenCart admin and test with keyboard Tab.

Security (OWASP) · 16 fixes

Hsts disabledQuick win

Set a valid HTTP Strict-Transport-Security (HSTS) header with a max-age of at least 31536000 (one year) and includeSubDomains to force browsers to always use HTTPS for your store.

On OpenCart

  1. Set SSL URLs: Admin → Settings → Store → Server tab → set Use SSL to Yes, and enter your https:// URL in the SSL URL field.
  2. Set the HSTS header at the Apache or Nginx level — OpenCart itself does not provide a security headers UI.
  3. Apache (.htaccess in OpenCart root): add Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" inside <IfModule mod_headers.c>.
  4. Nginx server block: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; then reload nginx.
  5. Verify with DevTools or curl.
Code · Generic HTML (html)
Strict-Transport-Security: max-age=31536000; includeSubDomains

This is an HTTP response header — it cannot be set in HTML meta tags. It must be set by your server, CDN, or platform. Shown here for reference only.

Hsts max age too shortQuick win

Increase your HSTS max-age directive to at least 31536000 seconds (1 year) so browsers enforce HTTPS connections for a meaningful period.

On OpenCart

  1. OpenCart does not control HTTP headers natively — configure at the web server level.
  2. For Apache, edit your .htaccess in the OpenCart root: <IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" </IfModule>
  3. For Nginx, add in your server {} block for HTTPS: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  4. Reload the web server and verify the header appears in DevTools or with curl -I https://yourdomain.com.
Code · Generic HTML (http)
Strict-Transport-Security: max-age=31536000; includeSubDomains

This is an HTTP response header — it must be sent by your server, NOT as an HTML meta tag. The meta tag is not supported for HSTS.

Https not availableQuick win

Enable HTTPS by installing a valid SSL/TLS certificate and redirecting all HTTP traffic to the secure HTTPS version of your store.

On OpenCart

  1. Install an SSL certificate on your server via your hosting panel or Let's Encrypt.
  2. Open your OpenCart config.php (root directory) and config.php inside the admin/ directory.
  3. Change the HTTP_SERVER and HTTPS_SERVER constants so both begin with https://.
  4. In the OpenCart Admin, go to System → Settings → Edit your store → Server tab and set 'Use SSL' to 'Yes'.
  5. Add a 301 redirect in .htaccess: RewriteEngine On / RewriteCond %{HTTPS} off / RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Info disclosure serverQuick win

Remove or suppress the HTTP `Server` response header so your web server no longer advertises its software name and version number to the public internet.

On OpenCart

  1. OpenCart is self-hosted — the `Server` header is controlled by the web server, not OpenCart itself.
  2. For Apache hosting: edit `.htaccess` or `httpd.conf` — add `ServerTokens Prod` and `ServerSignature Off`, then restart Apache.
  3. For nginx: add `server_tokens off;` to your nginx config, then reload.
  4. Verify with browser DevTools or `curl -I https://yourstore.com`.
Code · Generic HTML (html)
<!-- The Server header cannot be controlled from HTML.
     Apply the fix at your web server, CDN, or hosting control panel
     using one of the platform-specific configurations shown below. -->

There is no HTML tag to set or suppress the Server header — it is purely a server/CDN-level configuration. Use the server-specific snippets below.

Info disclosure x powered byQuick win

Remove or suppress the X-Powered-By HTTP response header so your server stops advertising which technology stack, hosting platform, or software version powers your store.

On OpenCart

  1. OpenCart runs on PHP; the X-Powered-By header typically comes from PHP itself.
  2. Add header_remove('X-Powered-By'); at the very top of your index.php (OpenCart root) and admin/index.php.
  3. Set expose_php = Off in php.ini (usually at /etc/php/[version]/apache2/php.ini or equivalent).
  4. For Apache: add 'Header unset X-Powered-By' to your .htaccess.
  5. Restart Apache/PHP-FPM and verify with DevTools.
Code · Generic HTML (html)
<!-- X-Powered-By is an HTTP response header set by the server, not in HTML markup.
     Use the server-side code snippets below for your specific platform. -->

There is no HTML tag for this — it is a server-side HTTP response header. Use the server/framework snippets below.

Insecure cookieModerate effort

Set the HttpOnly, Secure, and SameSite flags on all session and authentication cookies to prevent them from being stolen or misused by attackers.

On OpenCart

  1. Open system/library/session/native.php (or the relevant session handler for your version).
  2. Find the session_set_cookie_params() call and update it to include httponly: true, secure: true, samesite: 'Strict' (see PHP snippet).
  3. Alternatively edit index.php in the root and add session_set_cookie_params() before session_start().
  4. Save the file and test in browser DevTools → Application → Cookies.
Code · OpenCart (php)
<?php
// In system/library/session/native.php, before session_start():
session_set_cookie_params([
    'lifetime' => 0,
    'path'     => '/',
    'domain'   => '',
    'secure'   => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);

// Then proceed with:
session_start();

Edit system/library/session/native.php — update or add session_set_cookie_params before session_start.

Missing content security policyModerate effort

Add a Content-Security-Policy (CSP) HTTP response header to every page to block unauthorized scripts, styles, and resources from loading in your visitors' browsers.

On OpenCart

  1. Edit your .htaccess file (Apache) and add: Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';"
  2. For Nginx, add the add_header directive in your OpenCart server block.
  3. Alternatively, modify system/startup.php or a custom controller to call header() before any output is sent.
  4. Use Report-Only mode initially, since OpenCart admin and extensions commonly use inline scripts.
Missing permissions policyQuick win

Add a Permissions-Policy HTTP response header to your store to explicitly restrict which browser features (camera, microphone, geolocation, etc.) pages and embedded third-party scripts are allowed to access.

On OpenCart

  1. Apache: Edit the .htaccess file in the OpenCart web root or the Apache virtual host config. Add the Header set line inside <IfModule mod_headers.c>.
  2. Nginx: Add add_header to the server block in your Nginx site config.
  3. Verify in browser DevTools → Network → Response Headers.
Code · Generic HTML (meta tag — limited support) (html)
<!-- NOT recommended — use a real HTTP header instead. Shown for reference only. -->
<meta http-equiv="Permissions-Policy" content="camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()">

Meta tag delivery is not recommended — use HTTP headers instead. Included for reference only; browser support is partial.

Missing referrer policyQuick win

Add a Referrer-Policy HTTP response header set to strict-origin-when-cross-origin to control what URL information browsers send when users navigate away from your store.

On OpenCart

  1. Edit the .htaccess file in your OpenCart root (Apache) and add: Header always set Referrer-Policy "strict-origin-when-cross-origin"
  2. For Nginx, add the header directive to your server block configuration and reload Nginx.
  3. Verify in DevTools after saving.
Code · Generic HTML (html)
<meta name="referrer" content="strict-origin-when-cross-origin">

Add inside the <head> element of every HTML page as a fallback when HTTP header control is unavailable.

Missing security txtQuick win

Create and publish a /.well-known/security.txt file with at least a Contact: line so security researchers know how to report vulnerabilities responsibly.

On OpenCart

  1. Connect via SFTP to your OpenCart root directory.
  2. Create .well-known/ at the root, add security.txt inside it.
  3. Ensure your .htaccess does not deny access to .well-known.
  4. Visit https://yourdomain.com/.well-known/security.txt to confirm it loads.
Code · Generic HTML (text)
Contact: mailto:security@yourdomain.com
Expires: 2026-12-31T23:59:00.000Z
Preferred-Languages: en
Policy: https://yourdomain.com/security-policy
Acknowledgments: https://yourdomain.com/security-acknowledgments

Save this as security.txt and place it at /.well-known/security.txt on your web server. Update all placeholder values before publishing.

Missing strict transport securityQuick win

Add the Strict-Transport-Security (HSTS) response header to every HTTPS page so browsers always use an encrypted connection to your store.

On OpenCart

  1. OpenCart does not provide a built-in setting for HSTS headers — the header must be set at the server level.
  2. For Apache: Edit your .htaccess file in the OpenCart root or your VirtualHost config for the HTTPS site. Add the HSTS Header directive from the Apache snippet below.
  3. For Nginx: Add the add_header directive from the Nginx snippet below to your HTTPS server block, then reload Nginx.
  4. Confirm in your OpenCart Admin → Settings → Server that 'Use SSL' is set to Yes for the entire store, not just checkout.
  5. Verify with browser DevTools.
Code · Generic HTML (html)
<!-- HSTS cannot be set via an HTML meta tag. It MUST be sent as an HTTP response header. Use the server-level examples below. -->
<!-- Correct header value -->
<!-- Strict-Transport-Security: max-age=31536000; includeSubDomains -->

Meta tags cannot set HSTS — it must be a real HTTP response header. This is a reminder of the correct header value to use at the server level.

Missing x content type optionsQuick win

Add the `X-Content-Type-Options: nosniff` HTTP response header to every page and asset your store serves.

On OpenCart

  1. For Apache: add 'Header always set X-Content-Type-Options "nosniff"' to the .htaccess file in your OpenCart root directory.
  2. For Nginx: add 'add_header X-Content-Type-Options nosniff always;' to your server {} block.
  3. Alternatively, edit OpenCart's index.php or a system startup file to add the PHP header() call: header('X-Content-Type-Options: nosniff'); — place it before any output.
  4. Verify in browser dev-tools after deploying.
Code · Generic HTML (http)
X-Content-Type-Options: nosniff

This is an HTTP response header — it is sent by the server, not placed in HTML markup. The examples below show where and how to set it.

Missing x frame optionsQuick win

Add the X-Frame-Options HTTP response header set to DENY or SAMEORIGIN to prevent your store's pages from being embedded in iframes on other sites.

On OpenCart

  1. For Apache-hosted OpenCart, edit the .htaccess file in the OpenCart root and add: <IfModule mod_headers.c> Header always set X-Frame-Options 'SAMEORIGIN' </IfModule>
  2. For Nginx-hosted OpenCart, add to your server block: add_header X-Frame-Options 'SAMEORIGIN' always; and reload Nginx.
  3. Alternatively, edit OpenCart's index.php or a system startup file to add: header('X-Frame-Options: SAMEORIGIN'); before any output.
  4. Verify with browser DevTools.
Code · Generic HTML (http)
X-Frame-Options: SAMEORIGIN

This is an HTTP response header — it must be sent by the server, not a meta tag. It should appear in the HTTP response headers for every page.

Ssl cert expiring soonQuick win

Renew your SSL/TLS certificate before it expires to keep your store secure, trusted, and visible in search results.

On OpenCart

  1. SSL is managed at the hosting/server level. Renew through your hosting control panel or Let's Encrypt.
  2. After renewal, update config.php and admin/config.php in the OpenCart root to ensure HTTP_SERVER and HTTPS_SERVER constants use your https:// URL.
  3. In OpenCart Admin go to System → Settings → your store → Server tab and confirm 'Use SSL' is set to Yes.
Code · Generic HTML (html)
<!-- Place this in your maintenance/holding page only if HTTPS is temporarily unavailable -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

Meta-refresh redirect for maintenance: not applicable to SSL renewal. Instead, use this HTML to display a temporary notice if your cert has lapsed and you need to redirect HTTP traffic while fixing HTTPS.

Ssl not accessibleModerate effort

Enable HTTPS on your store by opening port 443 and installing a valid SSL/TLS certificate so every page is served over a secure connection.

On OpenCart

  1. Ensure port 443 is open on your server and a valid SSL certificate is installed.
  2. Edit your OpenCart config.php and admin/config.php files: change the HTTP_SERVER and HTTPS_SERVER constants to use https://.
  3. In Admin → Settings → Your Store → Server tab, enable 'Use SSL' and set the SSL URL to your https:// address.
  4. Optionally add an HTTP→HTTPS redirect in your .htaccess file.
Weak spfQuick win

Add a hard-fail (-all) or soft-fail (~all) terminator to your SPF DNS record so that mail servers reject or flag email sent from servers not explicitly authorized by your domain.

On OpenCart

  1. OpenCart does not manage DNS. Edit SPF at your registrar or hosting DNS panel (e.g. cPanel Zone Editor).
  2. Identify what mail service OpenCart is configured to use: Admin → System → Settings → Mail tab. Include that service's SPF include value in your record.
  3. Append -all at the end and save.
Code · Generic HTML (dns)
v=spf1 include:_spf.google.com include:sendgrid.net -all

DNS TXT record value for your root domain (@). Replace the include/redirect mechanisms with the ones your email service provider specifies, and always end with -all or ~all.

Site Lifecycle · 9 fixes

Cms versionModerate effort

Update your CMS and ecommerce platform to the latest stable version immediately to close known security vulnerabilities and maintain vendor support.

On OpenCart

  1. In the OpenCart admin go to Dashboard — the version number is displayed in the footer.
  2. Download the latest release from opencart.com/index.php?route=cms/download.
  3. Back up your database and all files via your hosting control panel (cPanel, Plesk) or FTP.
  4. Use the OpenCart upgrade package (not a fresh install) — copy the new files over the existing installation, then run the install/upgrade.php script via your browser and follow the prompts.
  5. After upgrading, delete the install/ directory, clear the cache under Admin > Dashboard > blue refresh buttons, and test the storefront.
Code · Generic HTML (html)
<!-- REMOVE or suppress this tag from your <head> — it advertises your CMS version to attackers -->
<!-- <meta name="generator" content="WordPress 5.8.1" /> -->

<!-- In WordPress, add this to your theme's functions.php to remove it: -->
<!-- remove_action('wp_head', 'wp_generator'); -->

If your site injects a generator meta tag exposing your CMS version publicly, remove it to reduce information exposure to attackers. This does not replace upgrading — it is a secondary hardening step.

Domain expiryQuick win

Log in to your domain registrar, confirm your domain's expiry date, and enable auto-renew so your store never goes offline due to an expired domain.

On OpenCart

  1. OpenCart does not manage domain registration. Log in to your registrar or hosting control panel and enable auto-renew on the domain.
Code · Generic HTML (html)
<!-- No HTML change required. Domain renewal is managed at your domain registrar's dashboard (e.g. GoDaddy, Namecheap, Cloudflare Registrar). Log in, find the domain, enable auto-renew, and confirm a valid payment method is on file. -->

No code change is needed on your website — this fix is completed entirely in your registrar's dashboard. There is no markup or configuration file involved.

Html langQuick win

Add a lang attribute to your site's <html> tag that matches the primary language of your store's content.

On OpenCart

  1. In OpenCart, the HTML wrapper is in the theme's header template file, typically catalog/view/theme/<your-theme>/template/common/header.twig (or header.tpl for older versions).
  2. Open that file via Admin › Design › Theme Editor, or via FTP.
  3. Find the <html opening tag and add the lang attribute. OpenCart passes language data to templates; use the language code variable, e.g.: <html lang="{{ language_code }}"> or hard-code your language: <html lang="en">.
  4. Save the file and clear the OpenCart cache via Admin › Dashboard › cog icon › Refresh, then verify via View Page Source.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Your Store</title>
</head>
<body>
  <!-- page content -->
</body>
</html>

Place this as the very first tag in your HTML document, before <head>.

Lifecycle oos schema not updatedModerate effort

Update the `offers.availability` value in your Product structured data from `InStock` to `OutOfStock` (or `PreOrder`) whenever a product sells out, so Google's records always match your actual inventory.

On OpenCart

  1. OpenCart does not output Product schema by default; it is typically added via an extension (e.g. SEO extensions from the OpenCart marketplace).
  2. Identify which extension or template file is generating your JSON-LD. Check `catalog/view/theme/<your-theme>/template/product/product.twig` (OpenCart 3) for any `application/ld+json` blocks.
  3. Update the availability value to use a Twig conditional based on OpenCart's stock status variable: `{% if product.stock_status == 'In Stock' %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}` — adjust the stock_status label to match your store's configured stock status name.
  4. Clear OpenCart's cache under Admin → Dashboard → (gear icon) → Refresh, then validate with Google's Rich Results Test.
Code · Generic HTML (json)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Your Product Name",
  "image": "https://example.com/images/product.jpg",
  "description": "Short product description.",
  "sku": "YOUR-SKU-123",
  "brand": {
    "@type": "Brand",
    "name": "Your Brand"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/product-page",
    "priceCurrency": "USD",
    "price": "29.99",
    "availability": "https://schema.org/OutOfStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>

Place this inside a <script type='application/ld+json'> tag in the <head> or <body> of your product page. Replace placeholder values with real data. Use OutOfStock when sold out.

Lifecycle orphaned productsModerate effort

Add internal links pointing to every orphaned product page from relevant category pages, collection pages, or other on-site content so search crawlers and shoppers can discover them.

On OpenCart

  1. Go to Admin → Catalog → Products, open each orphaned product, and click the 'Links' tab.
  2. Under 'Categories,' add all relevant categories. Save.
  3. Go to Catalog → Categories to create any missing categories, then re-assign products.
  4. Use System → Design → Layout or a menu module to add the category to your site navigation.
Code · Generic HTML (html)
<!-- Add this inside a relevant category page, blog post, or 'Featured Products' section -->
<a href="/products/your-orphaned-product-slug">Product Display Name</a>

Add a simple text link from any existing page (category, homepage, blog post) pointing directly to the orphaned product URL.

Mixed contentModerate effort

Ensure every resource (images, scripts, stylesheets, fonts, iframes) on your store is loaded over HTTPS, never HTTP, so browsers never block or warn about mixed content.

On OpenCart

  1. Go to Admin → System → Settings → Edit your store → Server tab. Confirm 'Use SSL' is set to Yes.
  2. In config.php and admin/config.php (root of your server), ensure HTTP_SERVER and HTTP_CATALOG constants use 'https://'.
  3. Search theme template files (catalog/view/theme/yourtheme/) for hardcoded 'http://' URLs and update them.
  4. Clear the cache: Admin → Dashboard → the gear icon → Refresh.
  5. Re-test with browser DevTools Console.
Code · Generic HTML (html)
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

Add this meta tag inside <head> to instruct browsers to automatically upgrade HTTP sub-requests to HTTPS. This is a safety net — fix the root URLs too.

Mobile viewportQuick win

Add a responsive viewport meta tag to every page so mobile browsers render your site at the correct width and scale.

On OpenCart

  1. Via FTP/SFTP, open `catalog/view/theme/<your-theme>/template/common/header.twig` (OpenCart 3+) or `header.tpl` (OpenCart 2).
  2. Search for `meta name="viewport"` and add it if missing: `<meta name="viewport" content="width=device-width, initial-scale=1">`.
  3. Save the file, then clear the OpenCart cache in Admin → Dashboard → the blue cog icon → Refresh.
  4. Verify the change on a mobile browser.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Page Title</title>
</head>
<body>
  <!-- page content -->
</body>
</html>

Place the viewport meta tag as early as possible in <head>, before any CSS or other meta tags.

Server versionQuick win

Remove or suppress HTTP response headers that advertise your server software name and version to the public internet.

On OpenCart

  1. Set 'expose_php = Off' in 'php.ini'.
  2. In Apache, add 'Header unset X-Powered-By' and 'ServerTokens Prod' to '.htaccess' or your VirtualHost.
  3. In Nginx, set 'server_tokens off;' and 'fastcgi_hide_header X-Powered-By;' in the server block.
  4. Verify with DevTools that no software version appears in response headers.
Code · Generic HTML (html)
<!-- HTTP response headers cannot be set via HTML meta tags.
     Use the server-side or CDN configuration snippets below. -->

Meta tags cannot suppress HTTP response headers — this must be done at the server/CDN layer. The snippets below cover the most common server/framework approaches.

Ssl expiryQuick win

Renew your TLS/SSL certificate before it expires and enable automatic renewal so your store never goes offline or shows security warnings.

On OpenCart

  1. SSL is managed by your hosting provider — log into cPanel/Plesk and confirm auto-renewal is on for your certificate.
  2. After renewal, verify OpenCart Admin → System → Settings → your store → Server tab → 'Use SSL' is set to 'Yes'.
  3. Update your config.php and admin/config.php files to use https:// in the HTTP_SERVER and HTTPS_SERVER constants if they were not already.
Code · Generic HTML (html)
<!-- SERVER-SIDE redirect is strongly preferred. Example nginx config snippet below. -->
<!-- nginx: force HTTPS -->
<!-- server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
} -->

Verify your HTTPS redirect is in place at the server/app level. This meta-refresh is a last-resort fallback only — implement a proper server-side 301 redirect instead.