Development Book V17:Managing Sitemaps for the Odoo Website

A sitemap plays a crucial role in enhancing the functionality of a website. It serves as a comprehensive file housing essential information about the website's pages and associated files. Notably, search engines rely on sitemaps to efficiently index and navigate through the diverse pages of your website, ensuring optimal visibility and accessibility for users.

This blog provides insights into the process of altering an existing sitemap specifically tailored for the Odoo website. The approach involves utilizing the sitemap_xml_index method to make modifications to the pre-existing sitemap structure.

@http.route('/sitemap.xml', type='http', auth="public", website=True, multilang=False, sitemap=False)
def sitemap_xml_index(self, **kwargs):
  current_website =
  Attachment = request.env['ir.attachment'].sudo()
  View = request.env['ir.ui.view'].sudo()
  mimetype = 'application/xml;charset=utf-8'
  content = None
  def create_sitemap(url, content):
      return Attachment.create({
          'raw': content.encode(),
          'mimetype': mimetype,
          'type': 'binary',
          'name': url,
          'url': url,
  dom = [('url', '=', '/sitemap-%d.xml' %, ('type', '=', 'binary')]
  sitemap =, limit=1)
  if sitemap:
      # Check if stored version is still valid
      create_date = fields.Datetime.from_string(sitemap.create_date)
      delta = - create_date
      if delta < SITEMAP_CACHE_TIME:
          content = base64.b64decode(sitemap.datas)
  if not content:
      # Remove all sitemaps in ir.attachments as we're going to regenerated them
      dom = [('type', '=', 'binary'), '|', ('url', '=like', '/sitemap-%d-%%.xml' %,
             ('url', '=', '/sitemap-%d.xml' %]
      sitemaps =
      pages = 0
      locs =
      while True:
          values = {
              'locs': islice(locs, 0, LOC_PER_SITEMAP),
              'url_root': request.httprequest.url_root[:-1],
          urls = View._render_template('website.sitemap_locs', values)
          if urls.strip():
              content = View._render_template('website.sitemap_xml', {'content': urls})
              pages += 1
              last_sitemap = create_sitemap('/sitemap-%d-%d.xml' % (, pages), content)
      if not pages:
          return request.not_found()
      elif pages == 1:
          # rename the -id-page.xml => -id.xml
              'url': "/sitemap-%d.xml" %,
              'name': "/sitemap-%d.xml" %,
          # TODO: in master/saas-15, move current_website_id in template directly
          pages_with_website = ["%d-%d" % (, p) for p in range(1, pages + 1)]
          # Sitemaps must be split in several smaller files with a sitemap index
          content = View._render_template('website.sitemap_index_xml', {
              'pages': pages_with_website,
              'url_root': request.httprequest.url_root,
          create_sitemap('/sitemap-%d.xml' %, content)
  return request.make_response(content, [('Content-Type', mimetype)])


The inherent function is outlined in the controller module of the website. It is invoked every time a sitemap file is generated.

locs =

This piece of code is used to produce the site URL for the sitemap. The definition involves leveraging the _enumerate_pages function within the provided sample website.

def _enumerate_pages(self, query_string=None, force=False):
  """ Available pages in the website/CMS. This is mostly used for links
      generation and can be overridden by modules setting up new HTML
      controllers for dynamic pages (e.g. blog).
      By default, returns template views marked as pages.
      :param str query_string: a (user-provided) string, fetches pages
                               matching the string
      :returns: a list of mappings with two keys: ``name`` is the displayable
                name of the resource (page), ``url`` is the absolute URL
                of the same.
      :rtype: list({name: str, url: str})
  router = self.env['ir.http'].routing_map()
  url_set = set()
  sitemap_endpoint_done = set()
  for rule in router.iter_rules():
      if 'sitemap' in rule.endpoint.routing and rule.endpoint.routing['sitemap'] is not True:
          if rule.endpoint in sitemap_endpoint_done:
          func = rule.endpoint.routing['sitemap']
          if func is False:
          for loc in func(self.env, rule, query_string):
              yield loc
      if not self.rule_is_enumerable(rule):
      if 'sitemap' not in rule.endpoint.routing:
          logger.warning('No Sitemap value provided for controller %s (%s)' %
                         (rule.endpoint.method, ','.join(rule.endpoint.routing['routes'])))
      converters = rule._converters or {}
      if query_string and not converters and (query_string not in{}, append_unknown=False)[1]):
      values = [{}]
      # converters with a domain are processed after the other ones
      convitems = sorted(
          key=lambda x: (hasattr(x[1], 'domain') and (x[1].domain != '[]'), rule._trace.index((True, x[0]))))
      for (i, (name, converter)) in enumerate(convitems):
          if 'website_id' in self.env[converter.model]._fields and (not converter.domain or converter.domain == '[]'):
converter.domain = "[('website_id', 'in', (False, current_website_id))]"
          newval = []
          for val in values:
              query = i == len(convitems) - 1 and query_string
              if query:
                  r = "".join([x[1] for x in rule._trace[1:] if not x[0]])  # remove model converter from route
                  query = sitemap_qs2dom(query, r, self.env[converter.model]._rec_name)
                  if query == FALSE_DOMAIN:
              for rec in converter.generate(self.env, args=val, dom=query):
                  newval[-1].update({name: rec})
          values = newval
      for value in values:
          domain_part, url =, append_unknown=False)
          if not query_string or query_string.lower() in url.lower():
              page = {'loc': url}
              if url in url_set:
              yield page
  # '/' already has a http.route & is in the routing_map so it will already have an entry in the xml
  domain = [('url', '!=', '/')]
  if not force:
      domain += [('website_indexed', '=', True), ('visibility', '=', False)]
      # is_visible
      domain += [
          ('website_published', '=', True), ('visibility', '=', False),
          '|', ('date_publish', '=', False), ('date_publish', '<=',
  if query_string:
      domain += [('url', 'like', query_string)]
  pages = self._get_website_pages(domain)
  for page in pages:
      record = {'loc': page['url'], 'id': page['id'], 'name': page['name']}
      if page.view_id and page.view_id.priority != 16:
          record['priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
      if page['write_date']:
          record['lastmod'] = page['write_date'].date()
      yield record


This function facilitates the seamless transition between various pages on your website within the sitemap. Its utility extends to the addition of new pages or URLs to enhance the comprehensiveness of your sitemap.

Adding a Records Page to Your Sitemap

For adding a records page to sitemap first import the following method.

Import slug from odoo.addons.http_routing.models.ir_http
Import from sitemap_qs2dom

The slug serves the purpose of forming user-friendly URLs, and its creation is grounded in the sitemap_qs2dom, a function predominantly utilized for generating domains based on routes and query strings.

Now, create a new method.

class Main(http.Controller):
   def sitemap_records(env, rule, qs):
       records = env[your.model]
       dom = sitemap_qs2dom(qs, '/url', records._rec_name)
       for r in
           loc = '/url/%s' % slug(r)
           if not qs or qs.lower() in loc:
               yield {'loc': loc}


The 'sitemap_records' is a Python generator function that gets invoked whenever a sitemap is generated. Within this function, a domain is created using 'sitemap_qs2dom'. Subsequently, this generated domain is utilized for searching records. The location is then determined by employing the 'slug()' method, which contributes to obtaining a user-friendly URL.

Subsequently, incorporate the reference to the 'sitemap_records' function into the record detail root.

   @http.route('/url/', type='http', auth="user", website=True, sitemap=sitemap_records)
   def records_detail(self, record):

In this context, we provided a reference to the 'sitemap_records()' function to the root by utilizing the 'sitemap' keyword.

This functionality enables the addition of record pages to 'sitemap.xml'. In cases where there's no requirement for record filtering and you simply want to list all records in the sitemap, you can substitute a function reference with True.

The sitemap undergoes an update every 12 hours. To observe the changes, navigate to attachments, delete the current 'sitemap.xml', and open '/sitemap.xml' in a browser to see the changes.



Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635



Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.



Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message