← Docs

Using JavaScript in Templates

Templates run in a real browser. You can use Alpine.js, animations, and any JS library via CDN.

ShareMagic templates are rendered in a headless Chrome instance before being screenshotted. This means JavaScript executes — your <script> tags run, DOM manipulation works, and external libraries load from CDNs.

Page data is available through Liquid variables (see the Template Authoring Guide), not through a JavaScript API. Use Liquid to write data into the HTML, then use JS to enhance the layout.

What Works

  • <script> tags (inline and external via CDN)
  • Alpine.js, Petite Vue, or any lightweight framework
  • CSS animations and transitions (captured at screenshot time)
  • Google Fonts and external stylesheets
  • Canvas and SVG manipulation

Alpine.js Example

A product card that conditionally shows a sale badge and formats the layout with Alpine:

<script src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js" defer></script>

<div
  x-data='{
    name: "{{ Product.name | default: title }}",
    brand: "{{ Product.brand.name }}",
    price: "{{ Product.offers.price }}",
    currency: "{{ Product.offers.priceCurrency | default: "USD" }}",
    available: "{{ Product.offers.availability }}" !== "",
    get onSale() { return this.price && parseFloat(this.price) < 50 },
    get formattedPrice() {
      if (!this.price) return null;
      return new Intl.NumberFormat("en-US", { style: "currency", currency: this.currency }).format(this.price);
    }
  }'
  style="width: 1200px; height: 630px; display: flex; align-items: center; justify-content: center; background: #18181b; font-family: system-ui; padding: 80px; color: white;"
>
  <div>
    <p x-show="brand" x-text="brand" style="font-size: 16px; text-transform: uppercase; letter-spacing: 2px; color: #a1a1aa;"></p>
    <h1 x-text="name" style="font-size: 52px; line-height: 1.1; margin-top: 8px;"></h1>
    <div style="display: flex; align-items: center; gap: 16px; margin-top: 24px;">
      <span x-show="formattedPrice" x-text="formattedPrice" style="font-size: 36px; font-weight: 700;"></span>
      <span x-show="onSale" style="background: #ef4444; padding: 4px 12px; border-radius: 4px; font-size: 14px; font-weight: 600;">SALE</span>
    </div>
    <p style="font-size: 18px; color: #71717a; margin-top: 32px;">{{ domain }}</p>
  </div>
</div>

This uses Liquid to inject the raw data, then Alpine to format the price with Intl.NumberFormat and conditionally show a sale badge — logic that would be harder to express in Liquid alone.

CSS Animations

Animations run in the browser but the screenshot captures a single frame. If you want an animation to be visible in the OG image, make sure the interesting state is the default state or use animation-fill-mode: forwards with a short duration so it completes before the screenshot.

<style>
  @keyframes fade-in {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
  }
  .animate {
    animation: fade-in 0.1s ease-out forwards;
  }
</style>

<div style="width: 1200px; height: 630px; background: #18181b; display: flex; align-items: center; justify-content: center;">
  <h1 class="animate" style="font-size: 52px; color: white; font-family: system-ui;">
    {{ title }}
  </h1>
</div>

Tips

  • Use defer on script tags. This ensures the HTML is parsed before JS runs.
  • Keep it fast. The browser waits for the page to settle before screenshotting, but heavy JS can slow down image generation.
  • Liquid first, JS second. Use Liquid for simple variable injection and conditionals. Reach for JS when you need formatting, math, or complex conditional logic.
  • Test in a real browser. Open the template HTML as a local file or paste into a CodePen to debug JS issues before uploading.