- How services work and the
useService()hook - The orm service: searchRead, create, write, unlink
- The notification service: success, warning, danger toasts
- The dialog service: confirmation dialogs and custom dialogs
- Writing and registering a custom service
How Services Work
A service is a plain JavaScript object registered in the services registry. It is instantiated once when the Odoo web client boots, and the same instance is injected into every component that requests it. Services can depend on other services.
In a component, inject a service in setup():
import { useService } from '@web/core/utils/hooks';
setup() {
this.orm = useService('orm');
this.notification = useService('notification');
this.dialog = useService('dialog');
this.user = useService('user');
this.router = useService('router');
this.action = useService('action');
}
orm Service
The orm service is the primary way to call Odoo model methods from JavaScript:
// search + read combined
const partners = await this.orm.searchRead(
'res.partner', // model
[['is_company', '=', true]], // domain
['name', 'email', 'phone'], // fields
{ limit: 20, order: 'name asc' }, // options
);
// search only (returns IDs)
const ids = await this.orm.search('res.partner', [['name', 'like', 'Al']]);
// read specific records
const records = await this.orm.read('res.partner', [1, 2, 3], ['name', 'email']);
// searchCount
const total = await this.orm.searchCount('res.partner', []);
// create
const newId = await this.orm.create('library.book', {
name: 'Clean Code', author: 'Robert C. Martin',
});
// write
await this.orm.write('library.book', [newId], { available: true });
// unlink
await this.orm.unlink('library.book', [newId]);
// call arbitrary model method
const result = await this.orm.call(
'library.book', 'check_availability', [[bookId]], {}
);
notification Service
Show non-blocking toast messages:
// Success (green)
this.notification.add('Record saved successfully!', { type: 'success' });
// Warning (yellow)
this.notification.add('Low stock warning.', { type: 'warning' });
// Danger (red)
this.notification.add('Operation failed.', { type: 'danger' });
// Info (blue, default)
this.notification.add('Processing your request…');
// With auto-dismiss duration (ms) and sticky option
this.notification.add('Changes saved.', {
type: 'success',
sticky: false, // auto-dismiss (default)
});
this.notification.add('Important notice.', {
type: 'warning',
sticky: true, // stays until user closes
});
dialog Service
Show confirmation dialogs or custom dialog components:
import { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog';
// Simple confirmation
this.dialog.add(ConfirmationDialog, {
title: 'Delete Book',
body: 'Are you sure you want to delete this book?',
confirm: async () => {
await this.orm.unlink('library.book', [this.bookId]);
this.notification.add('Book deleted.', { type: 'success' });
},
cancel: () => {/* optional cancel handler */},
});
// Custom dialog component
import { MyCustomDialog } from './my_custom_dialog';
this.dialog.add(MyCustomDialog, {
title: 'Custom Dialog',
bookId: this.bookId,
onSave: (result) => {
this.state.savedResult = result;
},
});
Writing a Custom Service
Encapsulate shared logic (e.g., a book availability cache) in a custom service:
/** @odoo-module **/
import { registry } from '@web/core/registry';
const bookService = {
dependencies: ['orm', 'notification'],
async start(env, { orm, notification }) {
const cache = new Map();
return {
async checkAvailability(bookId) {
if (cache.has(bookId)) {
return cache.get(bookId);
}
const [book] = await orm.read(
'library.book', [bookId], ['available_copies']
);
const available = book.available_copies > 0;
cache.set(bookId, available);
return available;
},
clearCache() {
cache.clear();
},
};
},
};
registry.category('services').add('book', bookService);
Inject it in any component with this.book = useService('book').
- Services are singletons — inject once in
setup()viauseService() ormservice covers all ORM operations: searchRead, create, write, unlink, callnotification.add(msg, {type})for toasts;dialog.add(Component, props)for dialogs- Custom services are registered in
registry.category('services')and can depend on other services
Frequently Asked Questions
What's the difference between orm.call and orm.searchRead?
orm.searchRead(model, domain, fields, opts) is a convenience wrapper for the common search+read pattern. orm.call(model, method, args, kwargs) calls any Python model method by name — use it for custom model methods, workflow actions, or any method not covered by the ORM convenience wrappers.
Can services hold reactive state?
Yes — use reactive() from @odoo/owl inside the service's start() method to create reactive data that components can observe. Components will re-render when the reactive object changes, even though the data lives in the service rather than the component's local state.
How do I call a Python @api.model method from JavaScript?
Use orm.call(modelName, methodName, positionalArgs, keywordArgs). For example: await this.orm.call('library.book', 'get_bestsellers', [], {limit: 5}). The method must be decorated with @api.model on the Python side. Record-based methods (decorated with @api.multi or @api.one) pass record IDs as the first positional argument.