How to Add Custom JavaScript to Your Shopify Theme
Want to add custom functionality to your Shopify store? JavaScript powers features like interactive product experiences, custom cart behaviour, and animations. This guide, NextSky covers the best ways to add JavaScript to Shopify themes in 2026 while maintaining performance, scalability, and compatibility with future theme updates.
Why JavaScript Matters in Shopify Themes
JavaScript brings your store to life beyond static Liquid templates. Common use cases include:
- Custom interactive elements (tabs, accordions, modals, quizzes)
- Real-time updates (inventory checks, dynamic pricing, variant-based content)
- Enhanced UX (sticky add-to-cart, quantity selectors with validation, exit-intent offers)
- Third-party integrations that need initialization on specific pages
Done correctly, JavaScript improves conversion rates. Done poorly, it creates render-blocking issues, conflicts with other apps, or breaks during theme updates.
Shopify’s Recommended Approach: Section {% javascript %} Blocks
For most section-specific functionality, the best method is using the {% javascript %} Liquid tag inside section files.
Why this approach wins:
- Shopify automatically bundles all section JavaScript into optimized files.
- Scripts load with the defer attribute (non-render-blocking).
- Code only loads on pages where the section actually appears.
- Each section’s JavaScript is wrapped in an IIFE (self-executing function), preventing variable conflicts.
- Works seamlessly with the theme editor’s live preview.
How to use it:
- Go to Online Store → Themes → Actions → Edit code.
- Open or create a file in the sections/ folder (e.g., sections/custom-countdown.liquid).
- Add your HTML markup first, then include the {% javascript %} block after it.
Example – Custom countdown timer section:
liquid
<div class="countdown-section" id="countdown-{{ section.id }}"
data-end-date="{{ section.settings.end_date }}">
<div class="countdown-timer"></div>
</div>
{% javascript %}
document.addEventListener('shopify:section:load', function(event) {
const section = event.target ;
if (!section.classList.contains('countdown-section')) return;
const container = section.querySelector('.countdown-section');
const endDate = container.dataset.endDate;
const timerEl = container.querySelector('.countdown-timer');
// Initialize countdown logic here
function updateTimer() {
// Your countdown code...
console.log('Countdown running for section:', container.id );
}
updateTimer();
const interval = setInterval(updateTimer, 1000);
// Clean up when section is removed in editor
document.addEventListener('shopify:section:unload', function(unloadEvent) {
if ( unloadEvent.target === section) {
clearInterval(interval);
}
});
});
{% endjavascript %}
Important rules:
- You can only have one {% javascript %} tag per section file.
- No Liquid variables ({{ }} or {% %}) inside the {% javascript %} block.
- Use data-* attributes on your HTML elements to pass values from Liquid into JavaScript.
Adding Global JavaScript in theme.liquid
Use this method only for scripts that truly need to run on every page (e.g., global analytics setup, utility functions, or certain third-party pixels).
Best practice placement:
Add external scripts just before the closing </body> tag with the defer attribute:
liquid
<script src="{{ 'custom-global.js' | asset_url }}" defer></script>
For very small, critical inline scripts, you can place them in the <head>, but keep them tiny to avoid delaying rendering.
When to avoid theme.liquid:
- Section-specific behavior (use {% javascript %} instead)
- Large scripts that don’t need to run everywhere
Quick Additions Without Editing Theme Code
Merchants who prefer not to touch the code editor can use:
- Custom Liquid block in the theme editor (add section → Custom Liquid)
- Custom HTML block (if available in your theme)
Simply paste a <script> tag inside. This works for simple snippets and third-party embeds but is less performant for complex logic because scripts are inline and not automatically deferred or bundled.
Using External JavaScript Files in the Assets Folder
For larger, reusable code:
- Create a new file in assets/ (e.g., custom-product.js).
- Reference it in theme.liquid or inside a section:
liquid
<script src="{{ 'custom-product.js' | asset_url }}" defer></script>
This approach works well for shared utilities but is generally less efficient than {% javascript %} blocks for section-specific features.
Best practices for javascript in Shopify Themes
- Prioritize the section method for anything tied to a specific section or block.
- Always use defer (or let Shopify handle it via {% javascript %}).
- Use vanilla JavaScript or lightweight modern approaches. Many current themes (including Dawn-based ones) do not include jQuery by default.
- Namespace your code or use IIFEs to avoid polluting the global scope.
- Listen for shopify:section:load and shopify:section:unload events when working inside sections for proper theme editor support.
- Test thoroughly in the theme editor preview, on mobile, and after publishing.
- Keep scripts focused, avoid monolithic files that load everywhere.
- Comment your code, especially if working with a team or agency.
Common mistakes to avoid
- Placing large scripts in the <head> without defer or async.
- Assuming jQuery is available globally.
- Putting Liquid syntax inside {% javascript %} tags.
- Adding event listeners without cleanup logic (causes duplicate listeners in the theme editor).
- Ignoring performance impact on Core Web Vitals and Lighthouse scores.
- Adding arbitrary scripts to checkout pages (this is heavily restricted or no longer supported in the current architecture).