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.
<script> tags (inline and external via CDN) 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.
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>
defer on script tags. This ensures the HTML is parsed before JS runs.