Which Setup Should You Choose?
| Approach | Setup time | Tools needed | Best for |
|---|---|---|---|
| 1. Single HTML file (CDN) | ~2 min | Text editor + browser | Quick experiments, first lesson, zero-friction start |
| 2. Static server (ES modules) | ~10 min | Text editor + Node.js (optional) | Multi-file projects without a build step |
| 3. npm + Vite project | ~15 min | Node.js + npm + terminal | Real projects, hot reload, production builds, testing |
All examples in the early lessons of this course run with the single-file CDN setup. You do not need Node.js or npm installed to follow along. When you reach the projects section, the Approach 3 setup will be recommended.
Approach 1 – Single HTML File (CDN, No Tools Required)
This is the fastest way to run OWL JS. Download the OWL bundle from the GitHub releases page and include it with a <script> tag, or use jsDelivr CDN directly. No npm, no Node.js, no terminal commands.
File structure
hello_owl/
index.html
app.js
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello OWL JS</title>
<!-- Load OWL JS from CDN (IIFE build — exposes global 'owl') -->
<script src="https://cdn.jsdelivr.net/npm/@odoo/owl/dist/owl.iife.js"></script>
</head>
<body>
<!-- OWL will mount into this div -->
<div id="app"></div>
<!-- Load your app code AFTER OWL -->
<script src="app.js"></script>
</body>
</html>
app.js
// 'owl' is available as a global variable (loaded by the CDN script)
const { Component, mount, xml, useState } = owl;
class App extends Component {
state = useState({ count: 0 });
static template = xml`
<div style="font-family: sans-serif; padding: 2rem; text-align: center;">
<h1>Hello OWL JS! 🦉</h1>
<p style="font-size: 2rem; margin: 1rem 0;">
Count: <strong><t t-esc="state.count"/></strong>
</p>
<button t-on-click="() => state.count++"
style="margin: 0.5rem; padding: 0.5rem 1.5rem; font-size: 1rem;">
+ Increment
</button>
<button t-on-click="() => state.count--"
style="margin: 0.5rem; padding: 0.5rem 1.5rem; font-size: 1rem;">
- Decrement
</button>
<button t-on-click="() => state.count = 0"
style="margin: 0.5rem; padding: 0.5rem 1.5rem; font-size: 1rem;">
Reset
</button>
</div>
`;
}
// Mount the App component into the #app div
mount(App, document.getElementById("app"));
If you double-click index.html to open it (the URL starts with file:///), the CDN script loads fine because it is an external URL. However, once you split your code across multiple files and use ES modules (type="module"), browsers block module imports over file:// for security. Use a local web server (see Approach 2) instead.
Approach 2 – Static Server with ES Modules
This approach uses native JavaScript modules (import/export) to split your code across multiple files without a build step. You need a simple local HTTP server — browsers refuse to load ES modules over file://.
File structure
hello_owl/
index.html
main.js ← entry point
src/
App.js ← root component
Counter.js ← sub-component
Starting a local server
# Option A: Python (installed by default on macOS and most Linux)
python3 -m http.server 8080
# Visit http://localhost:8080
# Option B: Node.js (if you have npm)
npx serve .
# Visit the URL it prints
# Option C: VS Code Live Server extension
# Right-click index.html → "Open with Live Server"
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OWL JS App</title>
</head>
<body>
<div id="app"></div>
<!-- type="module" enables ES module imports -->
<script src="main.js" type="module"></script>
</body>
</html>
main.js (entry point)
// Import OWL from a local file or CDN as ES module
// Using CDN skypack which serves OWL as an ESM
import { mount } from "https://cdn.skypack.dev/@odoo/owl";
import { App } from "./src/App.js";
mount(App, document.getElementById("app"));
src/App.js
import { Component, xml, useState } from "https://cdn.skypack.dev/@odoo/owl";
import { Counter } from "./Counter.js";
export class App extends Component {
static components = { Counter }; // Register sub-component
static template = xml`
<div>
<h1>OWL JS App</h1>
<Counter/>
</div>
`;
}
src/Counter.js
import { Component, xml, useState } from "https://cdn.skypack.dev/@odoo/owl";
export class Counter extends Component {
state = useState({ count: 0 });
static template = xml`
<div>
<p>Count: <t t-esc="state.count"/></p>
<button t-on-click="() => state.count++">+</button>
</div>
`;
}
Approach 3 – npm + Vite (Recommended for Real Projects)
For real applications — Odoo module development, standalone OWL apps, projects with tests — you want a proper build tool. Vite is the recommended choice in 2026: it starts in under a second and rebuilds instantly on save.
Prerequisites
You need Node.js 18+ and npm installed. Download from nodejs.org. Verify installation:
node --version # should show v18.x or higher
npm --version # should show 9.x or higher
Create and configure the project
# 1. Create a new Vite project (choose "vanilla" + "JavaScript")
npm create vite@latest my-owl-app -- --template vanilla
cd my-owl-app
# 2. Install dependencies and add OWL
npm install
npm install @odoo/owl
# 3. Start the development server (with hot reload)
npm run dev
# Visit http://localhost:5173
Final file structure
my-owl-app/
index.html ← HTML entry point
src/
main.js ← JavaScript entry point
App.js ← Root component
components/ ← Your component files
package.json
vite.config.js
src/main.js
import { mount } from "@odoo/owl";
import { App } from "./App.js";
mount(App, document.getElementById("app"));
src/App.js
import { Component, xml, useState } from "@odoo/owl";
export class App extends Component {
state = useState({ message: "Hello OWL JS! 🦉" });
static template = xml`
<div>
<h1><t t-esc="state.message"/></h1>
<input
t-model="state.message"
placeholder="Type something..."
style="width: 300px; padding: 0.5rem; font-size: 1rem;"
/>
</div>
`;
}
Available npm scripts
npm run dev # Start dev server with hot reload at http://localhost:5173
npm run build # Build optimised production files into /dist
npm run preview # Preview the production build locally
Recommended Editor Setup
VS Code is strongly recommended for OWL development. Useful extensions:
| Extension | Purpose |
|---|---|
| OWL Vision (Odoo Inc.) | OWL template syntax highlighting, component navigation, autocompletion |
| ESLint | JavaScript linting — catch errors before running |
| Prettier | Auto-format JavaScript and HTML on save |
| Live Server | Quick static server for Approach 2 setup |
| GitLens | Git history and blame inline |
Odoo provides an OWL Devtools browser extension for Chrome and Firefox. Once installed, it adds an "OWL" panel to your browser's developer tools showing the component tree, props, and state of every mounted OWL component. Install it from the Chrome Web Store (search "OWL Devtools") — it is invaluable for debugging.
📋 Summary
- Approach 1 (CDN) — Fastest start. Single HTML + JS file. OWL loaded via CDN
<script>tag. No tools needed. Perfect for learning the core concepts. - Approach 2 (Static server) — Multi-file ES modules. Run a local HTTP server (
python3 -m http.serverornpx serve). No build step. - Approach 3 (npm + Vite) — Full dev setup.
npm install @odoo/owl, Vite for hot reload and production builds. Best for real projects. - Use VS Code with the OWL Vision extension for syntax highlighting and template autocompletion.
- Install the OWL Devtools browser extension for debugging component trees in the browser.
Frequently Asked Questions
No — Approach 1 (single HTML + CDN) requires only a browser and a text editor. Node.js is only required for Approach 3 (npm + Vite). For learning the core OWL concepts in this course, you do not need Node.js at all. You will want it when you start building multi-file projects or when you reach the Odoo integration lessons.
The jsDelivr URL https://cdn.jsdelivr.net/npm/@odoo/owl/dist/owl.iife.js serves the latest published version. To pin to a specific version (e.g. OWL 2.x), use https://cdn.jsdelivr.net/npm/@odoo/owl@2/dist/owl.iife.js. For production, always pin to a specific version to avoid unexpected breaking changes.
Yes — OWL is written in TypeScript and ships full type definitions. In the Vite setup, rename your files from .js to .ts and add a tsconfig.json. The OWL type definitions give you autocompletion for Component, useState, prop types, and lifecycle hooks. TypeScript is not required to learn OWL — this course uses JavaScript — but it is a valuable addition for larger projects.
Setting up a standalone OWL project (Approaches 1–3) is much simpler than setting up a full Odoo development environment. A real Odoo dev environment requires installing PostgreSQL, Python, Odoo itself, and configuring a database. The OWL-only setups here let you learn and practice OWL components without that overhead. The Odoo Integration section of this course covers the full Odoo setup when you are ready for it.
🏋️ Exercise
Set up Approach 1 (single HTML file) and get the counter example running in your browser:
- Create a folder named
owl-practice. - Inside it create
index.htmlandapp.jswith the code from this lesson. - Open
index.htmlin your browser and verify the counter works. - Add a fourth button: "Add 10" that adds 10 to the count with a single click.
- Add a
<p>that shows "Even" when the count is even and "Odd" when it is odd. Use thet-if/t-elsedirectives (covered in the Dynamic Templates section — try it now as a preview).