Files
ODOO4projects.com/odoo-community-upgrade.html
T
webmaster 0e832d8fd1 perf(site-wide): render-blocking /style.css on 17 pages; <main> landmark; BF footer form action
Same defect the bootcamp carried (2aa9b19), fixed site-wide in one mechanical pass.

P0 (17 pages): /style.css was deferred via media="print" onload=... -> unstyled
first paint -> a BODY layout shift. Now a normal render-blocking
<link rel="stylesheet">; the now-redundant no-JS <noscript> for style.css is
dropped. Web fonts + Font Awesome keep their legitimate deferral.
Pages: index.html, odoo-black-friday/, odoo-hosting-offers/, de/index.html,
es/index.html, odoo-hosting-2027.html (+de/+es), odoo-community-upgrade.html
(+de/+es), odoo-staging-environment/, odoo-community-ai/,
odoo-n8n-masterclass/, australia.html, workshops/, what-can-i-use-odoo-for/.

P1 (1 page): /odoo-black-friday/ footer form action mailto: -> CRM webhook
(Lighthouse is-on-https). Changed only because the page already loads a handler
for #footerContactForm (static/black-friday.js). The other 13 mailto-form pages
load no handler and are left alone on purpose (no drive-by handler copy-in).
Added the no-JS <noscript> fallback so a JS-off visitor keeps a path.

P2 (35 pages): add a single <main> landmark wrapping the primary content; top
bar and footer stay outside. Additive only, no redesign.

Guards held: bootcamp EN/DE/ES and every JS/static file untouched; Buy forms
product_id 4/17 intact; one [data-bc-seats]; ads-consent gate intact.
verify-html.py PROBLEMS 0; competitor QC 74 pre-existing, 0 added.
2026-11-02 18:18:46 +00:00

1039 lines
50 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Odoo Community Upgrade — Managed for You | ODOO4projects</title>
<meta
name="description"
content="An Odoo Community upgrade, explained: what changes, what breaks, the real commands, and how we migrate your database onto our servers."
/>
<link rel="canonical" href="https://odoo4projects.com/odoo-community-upgrade.html" />
<link rel="alternate" hreflang="en-US" href="https://odoo4projects.com/odoo-community-upgrade.html" />
<link rel="alternate" hreflang="de" href="https://odoo4projects.com/de/odoo-community-upgrade.html" />
<link rel="alternate" hreflang="es" href="https://odoo4projects.com/es/odoo-community-upgrade.html" />
<link rel="alternate" hreflang="x-default" href="https://odoo4projects.com/odoo-community-upgrade.html" />
<meta property="og:title" content="Odoo Community Upgrade — Managed for You" />
<meta
property="og:description"
content="What changes in an Odoo Community upgrade, the real commands for the developer, and how we move your database onto our servers."
/>
<meta property="og:type" content="article" />
<meta property="og:url" content="https://odoo4projects.com/odoo-community-upgrade.html" />
<meta property="og:image" content="https://odoo4projects.com/images/hero/amsterdam.webp" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Odoo Community Upgrade — Managed for You" />
<meta
name="twitter:description"
content="What changes in an Odoo Community upgrade, the real commands for the developer, and how we move your database onto our servers."
/>
<meta name="twitter:image" content="https://odoo4projects.com/images/hero/amsterdam.webp" />
<link rel="icon" href="images/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Marcellus&display=swap"
media="print"
onload="this.media='all'"
/>
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Marcellus&display=swap" /></noscript>
<link rel="stylesheet" href="style.css" />
<style>
/* ---- Odoo Community Upgrade page (page-scoped; additive) ---- */
.oc-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Top bar (page-scoped: the site has no global header) */
.cmp-topbar {
background: var(--midnight-2);
border-bottom: 1px solid var(--line-brass);
padding: 0.85rem 0;
}
.cmp-topbar .container {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
.cmp-brand {
font-family: Marcellus, Georgia, serif;
font-size: 1.15rem;
letter-spacing: 0.04em;
color: #fff;
}
.cmp-brand span {
color: var(--brass);
}
.cmp-topbar-links {
display: flex;
gap: 1.25rem;
font-size: 0.82rem;
margin-left: auto;
}
.cmp-topbar-links a {
color: rgba(255, 255, 255, 0.7);
}
.cmp-topbar-links a:hover {
color: var(--brass);
}
.cmp-topbar .lang-switch-slot {
display: flex;
align-items: center;
padding-left: 1rem;
border-left: 1px solid rgba(201, 162, 39, 0.45);
}
.cmp-topbar .lang-switch {
color: rgba(255, 255, 255, 0.7);
}
.cmp-topbar .lang-switch a:hover,
.cmp-topbar .lang-switch a:focus-visible {
color: var(--brass-bright);
}
.cmp-topbar .lang-switch a[aria-current="true"] {
color: var(--brass);
text-decoration: underline;
text-underline-offset: 3px;
}
/* Hero — the same midnight lounge as the rest of the site, with our own
Amsterdam photo behind the copy. */
.oc-hero {
min-height: 0;
padding: 3.5rem 0 3rem;
}
.oc-hero .container {
position: relative;
z-index: 1;
}
.oc-hero h1 span {
color: var(--brass);
}
.oc-hero .hero-sub {
max-width: 700px;
}
.oc-hero-img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
opacity: 0.25;
filter: brightness(1.15) saturate(1.1);
}
.oc-body {
max-width: 760px;
margin: 0 auto;
color: var(--ink-soft);
line-height: 1.75;
}
.section-dark .oc-body {
color: rgba(255, 255, 255, 0.78);
}
.oc-body p:last-child {
margin-bottom: 0;
}
.oc-body strong {
color: var(--ink);
}
.section-dark .oc-body strong {
color: #fff;
}
.oc-pull {
max-width: 760px;
margin: 2rem auto 0;
padding-top: 1.25rem;
border-top: 1px solid var(--line-brass);
font-family: Marcellus, Georgia, serif;
font-size: 1.05rem;
color: var(--ink);
text-align: center;
}
.section-dark .oc-pull {
color: var(--brass-bright);
}
/* Numbered process list */
.oc-steps {
max-width: 820px;
margin: 0 auto;
padding: 0;
list-style: none;
counter-reset: oc-step;
}
.oc-steps li {
position: relative;
counter-increment: oc-step;
padding: 0 0 1.4rem 3.2rem;
color: rgba(255, 255, 255, 0.78);
line-height: 1.7;
border-left: 1px solid rgba(201, 162, 39, 0.25);
margin-left: 1rem;
}
.oc-steps li::before {
content: counter(oc-step);
position: absolute;
left: -1.05rem;
top: -0.15rem;
width: 2.1rem;
height: 2.1rem;
display: flex;
align-items: center;
justify-content: center;
font-family: Marcellus, Georgia, serif;
font-size: 1rem;
color: var(--midnight);
background: var(--brass);
border-radius: 50%;
}
.oc-steps li strong {
color: #fff;
font-weight: 600;
}
.oc-steps li:last-child {
padding-bottom: 0;
}
/* Technical section: code cards + substitution notes */
.oc-code {
background: var(--midnight);
border: 1px solid var(--line-brass);
border-radius: var(--radius-lg);
padding: 1rem 1.15rem;
margin: 1.1rem 0 0;
overflow-x: auto;
box-shadow: var(--shadow-rest);
}
.section-dark .oc-code {
background: var(--midnight-2);
}
/* R1 moved the process list onto a light band and the technical section onto a
dark one, so their text colours have to follow the band, not their old parent. */
.section-light .oc-steps li {
color: var(--ink-soft);
}
.section-light .oc-steps li strong {
color: var(--ink);
}
.section-dark .oc-sub h3 {
color: var(--cream);
}
.section-dark .oc-sub > p,
.section-dark .oc-note {
color: rgba(255, 255, 255, 0.78);
}
.section-dark .oc-note strong {
color: #fff;
}
/* R3 puts the "See the plans" link inside the dark offer band. */
.section-dark .oc-body a {
color: var(--brass-bright);
}
.oc-code code {
display: block;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
font-size: 0.8rem;
line-height: 1.7;
color: #ece6da;
white-space: pre;
tab-size: 2;
}
.oc-note {
margin: 0.9rem 0 0;
padding: 0.15rem 0 0.15rem 1rem;
border-left: 2px solid var(--brass);
font-size: 0.9rem;
color: var(--ink-soft);
line-height: 1.7;
}
.oc-note p:last-child {
margin-bottom: 0;
}
.oc-sub {
margin-top: 2.25rem;
}
.oc-sub h3 {
color: var(--ink);
margin-bottom: 0.35rem;
}
.oc-sub > p {
color: var(--ink-soft);
line-height: 1.75;
}
/* FAQ */
.cmp-faq-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
max-width: 1000px;
margin: 0 auto;
}
.cmp-faq {
background: var(--ivory);
border: 1px solid var(--line);
border-radius: var(--radius-lg);
padding: 0.4rem 1.25rem;
box-shadow: var(--shadow-rest);
transition: box-shadow 0.25s;
}
.cmp-faq[open],
.cmp-faq:hover {
box-shadow: var(--shadow-hover);
}
.cmp-faq summary {
cursor: pointer;
list-style: none;
padding: 0.85rem 0;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
font-family: Marcellus, Georgia, serif;
font-size: 0.98rem;
color: var(--midnight);
}
.cmp-faq summary::-webkit-details-marker {
display: none;
}
.cmp-faq summary::after {
content: "+";
font-family: Inter, sans-serif;
color: var(--brass);
font-size: 1.15rem;
line-height: 1;
flex-shrink: 0;
transition: transform 0.25s;
}
.cmp-faq[open] summary::after {
content: "\2212";
}
.cmp-faq summary:focus-visible {
outline: 2px solid var(--brass);
outline-offset: 3px;
border-radius: var(--radius);
}
.cmp-faq .cmp-a {
font-size: 0.87rem;
color: var(--ink-soft);
line-height: 1.7;
padding-bottom: 1rem;
}
.cmp-faq .cmp-a p:last-child {
margin-bottom: 0;
}
@media (max-width: 768px) {
.cmp-faq-grid {
grid-template-columns: 1fr;
}
}
/* Final CTA band */
.cmp-cta {
text-align: center;
}
.cmp-cta .oc-lead {
font-size: 1.1rem;
color: rgba(255, 255, 255, 0.72);
max-width: 700px;
margin: 0 auto 1.5rem;
line-height: 1.7;
}
.cmp-cta-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin-top: 0.5rem;
}
.cmp-cta-actions .btn {
align-self: center;
}
/* Focus states on every CTA */
.btn:focus-visible,
.cmp-topbar-links a:focus-visible {
outline: 3px solid var(--brass-bright);
outline-offset: 2px;
border-radius: var(--radius);
}
.btn-brass:focus-visible {
outline-color: #fff;
}
@media (max-width: 768px) {
.oc-hero {
padding: 3rem 0 2.5rem;
}
.oc-steps li {
padding-left: 2.6rem;
}
}
</style>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
"@id": "https://odoo4projects.com/odoo-community-upgrade.html#webpage",
"url": "https://odoo4projects.com/odoo-community-upgrade.html",
"name": "Odoo Community Upgrade — Managed for You",
"description": "An Odoo Community upgrade, explained: what changes, what breaks, the real commands, and how we migrate your database onto our servers.",
"inLanguage": "en-US",
"isPartOf": { "@id": "https://odoo4projects.com/#organization" }
},
{
"@type": "Service",
"name": "Odoo Community database migration onto ODOO4projects hosting",
"provider": { "@id": "https://odoo4projects.com/#organization" },
"url": "https://odoo4projects.com/odoo-community-upgrade.html"
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is an upgrade the same as an update?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. An update gives you the newest revision of the software you already run. An upgrade moves your database to a new major release — 18 to 19, or 19 to 20 — and transforms the data itself. It is one-way: there is no supported path back down, and it is not a licence change, an edition change or a hosting change."
}
},
{
"@type": "Question",
"name": "Can we upgrade straight from Odoo 14 to Odoo 20?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. It is one major version at a time: 14 → 15 → 16 → 17 → 18 → 19 → 20. Skipping a version is not a supported path. Our own migration service documents support for databases on Odoo 17, 18 and 19, main releases only — no patch releases — and older databases need a compatibility review first, which starts with a support ticket."
}
},
{
"@type": "Question",
"name": "What happens to my history — the invoices, the payments, the attachments?",
"acceptedAnswer": {
"@type": "Answer",
"text": "That is the right question to ask, and the honest answer is that it depends on the route. The OpenUpgrade route — a version migration run on your own server — carries the database across and keeps the history and the links between records. The OpenUpgrade documentation warns that the other route, moving documents into a fresh system, recovers only some of your data and loses the history and the links between records — the invoices for each customer, for example. Our service builds you a new database and moves your data into it, so ours is the route to ask about: before you approve anything, ask us to list exactly which records will be in the new database, and which will not. We would rather you ask it than assume it."
}
},
{
"@type": "Question",
"name": "Will my custom modules still work?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Not by themselves. A module written for your business has to have its code brought up to the target version, and any renames that would otherwise lose data need their own migration scripts. Odoo's documentation is direct about it: an upgrade is blocked until a version of your custom modules exists for the release you are moving to. Modules that ship with Odoo and modules from the community association carry their own migration scripts; yours do not, and someone has to write them."
}
},
{
"@type": "Question",
"name": "What does it cost, and how long does it take?",
"acceptedAnswer": {
"@type": "Answer",
"text": "We publish no price and no duration for the upgrade service, and we are not going to invent one here. What we do publish is how it starts: you open a support ticket, we review your database for compatibility, and we come back to you. If you are weighing it against doing it yourself, the honest comparison is not the server rental — it is your own hours, which never appear on that invoice."
}
},
{
"@type": "Question",
"name": "Do I need a developer?",
"acceptedAnswer": {
"@type": "Answer",
"text": "To run the commands in the technical section, yes — or someone comfortable in a terminal with PostgreSQL. To have us do it, no: that is the point of the service. You send the database, we build the new system on our infrastructure, and you get credentials."
}
},
{
"@type": "Question",
"name": "Can I look at the upgraded system before going live?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes, and you should. Whoever does the work migrates a copy first — that is the first rule of the whole process, in Odoo's documentation and in the OpenUpgrade project's — and the migrated copy is what you inspect before anything happens to the system your business is running on. Our service follows the same principle: we build the new system on our own servers and you compare it with the old one."
}
},
{
"@type": "Question",
"name": "We are hosted somewhere else. Can you still upgrade us?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes, that is exactly what our migration service is for. You open a support ticket, we send you a secure form to upload your database, and we spin up a new server for you on our infrastructure and provide the credentials. Databases on Odoo 17, 18 and 19, main releases only; older ones after a compatibility review."
}
}
]
}
]
}
</script>
<!-- Privacy-friendly analytics by Plausible -->
<script async src="https://plausible.office.odoo4projects.com/js/pa-NhA7Rf-79VopXFQXhSS4e.js"></script>
<script>
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
plausible.init()
</script>
<script defer src="/static/lang.js"></script>
</head>
<body>
<!-- Page-scoped top bar: the site has no global header, so the page carries its own way home. -->
<div class="cmp-topbar">
<div class="container">
<a class="cmp-brand" href="index.html">ODOO4<span>projects</span></a>
<nav class="cmp-topbar-links" aria-label="Page links">
<a href="index.html#pricing">Plans &amp; pricing</a>
<a href="support.html">Support</a>
</nav>
<div data-lang-switcher class="lang-switch-slot"></div>
</div>
</div>
<!-- Hero -->
<main>
<section class="hero oc-hero">
<div class="hero-bg">
<img
class="oc-hero-img"
src="images/hero/amsterdam.webp"
alt="Amsterdam at dusk — one of the six regions our servers run in"
width="1920"
height="768"
fetchpriority="high"
decoding="async"
/>
</div>
<div class="container">
<p class="hero-kicker">ODOO COMMUNITY UPGRADE</p>
<h1><span>Odoo Community Upgrade</span>: What Changes, and How We Do It For You</h1>
<p class="hero-sub">
Moving up a major Odoo version is not an update: it is a one-way change to your database, and it
is not something you want to get wrong. Here is how we do it for you — a new system on our own
servers, your current one left untouched while we work — and, underneath, the exact commands a
developer would run.
</p>
<div class="hero-actions">
<a href="support.html#contact" class="btn btn-brass btn-lg">Open a ticket — tell us which version you run</a>
<a href="index.html#pricing" class="btn btn-outline btn-lg">See the plans</a>
</div>
</div>
</section>
<!-- What an upgrade is -->
<section class="section-light">
<div class="container">
<p class="section-kicker">The difference that matters</p>
<h2 class="section-title">What an Odoo Community upgrade actually is</h2>
<div class="oc-body">
<p>
Odoo uses two words that sound the same and are not. An <strong>update</strong> gets you the newest
revision of the software you already run. An <strong>upgrade</strong> moves your database to a new
major release — 18 to 19, or 19 to 20 — and it transforms the data itself. It is one-way: there is no
supported way back down, and it is not a licence change, not a move to a different edition and not a
switch of hosting provider.
</p>
<p>Three things about it are worth knowing before anyone touches your system.</p>
<p>
<strong>It happens one major version at a time.</strong> A jump from 17 to 20 is three migrations, not
one: 17 → 18, 18 → 19, 19 → 20. Skipping a version is not a supported path anywhere in the Odoo
ecosystem.
</p>
<p>
<strong>It is done on a copy, never on the live database.</strong> Everyone who has written about
this — Odoo's own documentation, the OCA's OpenUpgrade project, and every practitioner we have
watched — starts by taking a backup and working on a duplicate. You find out what broke on the copy,
not in front of your customers.
</p>
<p>
<strong>Your own customisations are the part that needs work.</strong> A module built for your
business does not move by itself: its code has to be brought up to the target version, and the renames
that would otherwise lose data need migration scripts written for it. Odoo's documentation says it
plainly — an upgrade is blocked until a version of your custom modules exists for the release you are
moving to.
</p>
<p>
Then the route itself. Odoo sells an upgrade path for Enterprise, and for Odoo Community the
open-source answer is the OCA's OpenUpgrade project. The OpenUpgrade documentation states that Odoo
Community Edition does not support migrations between major releases, and records that OpenUpgrade is
the solution Odoo's own founder recommends for it. It is a real project, maintained by the community
association, and it is the route a developer will use on a Community database.
</p>
</div>
</div>
</section>
<!-- Our way -->
<section class="section-dark" id="our-way">
<div class="container">
<p class="section-kicker">The ODOO4projects way</p>
<h2 class="section-title">The ODOO4projects way: a new database, and we do the work</h2>
<div class="oc-body">
<p>
You do not have to do any of this yourself. If your business runs on Odoo Community and you want
the version moved without hiring someone to work through the sequence on this page, this is what we
do instead.
</p>
<p>
You open a support ticket and tell us what you have. We look at your database first — its version,
its customisations, whether it is something we can move — and we tell you what we find. Then we send
you a link to a secure form where you upload your database. When we have it, we spin up a new server
for you on our own infrastructure and hand you the credentials. Your existing system keeps running
the whole time, because we are not touching it: we work on the database you sent us and the new
server we built.
</p>
<p>
If you would rather see a number first: which plan you need depends on the modules you run, and the
plans are here — <a href="index.html#pricing">See the plans</a>. The upgrade itself is not priced on
that page; we come back to you after we have looked at your database.
</p>
<p>
That last point is the whole reason to do it this way. On our service you are not migrating your
live database in the evening after the shop closes. You are looking at a second system, in a new
version, with your data in it, and you can compare the two before you decide anything.
</p>
<p>
Our migration service documents support for databases on Odoo 17, 18 and 19, main releases only —
patch releases such as 18.1 or 17.2 are not supported anywhere in our service. If your database is
older than 17, we can still help you get it to a supported release, but only after reviewing it to
check compatibility. New instances on our servers can be created on Odoo 19 or Odoo 20, so if you
are moving to 20 specifically, say so in the ticket: the version you are going to matters more than
the version you are on.
</p>
<p>
On the AI side, here is a case we have actually published. Copying invoices from one Odoo instance
to another by hand is slow and easy to get wrong, so we did it with a workflow instead: records
written from an Odoo 19 system into a new Odoo 20 system, driven by an automation workflow and an
MCP server pointed at the new system. Three rules made it safe — the invoice number and date never
change, the contact is looked up rather than guessed, the product is looked up rather than guessed —
and one hard constraint: if a contact or a product is missing, the run fails loudly rather than
creating a near-duplicate. Before releasing it on the rest, we checked one invoice end to end:
number, date, contact, product lines.
</p>
<p>
One thing we will not blur, because it is the difference between us and a salesman. The MCP server
we ship inside our own management console does hosting operations. It restarts a server, uploads a
module, adds or checks a domain, manages backups and reads a log file — that is the published list,
and it is a real convenience if you use an AI assistant. It is not a data-migration engine and we do
not describe it as one. The import work above was done with an automation workflow and a third-party
MCP; the AI was doing the tedious part and being checked by a human, exactly as it should be.
</p>
<p>
And the honest limit, in the words of the project that knows this best. The OpenUpgrade
documentation names the export-and-import alternative and prices it plainly: you recover only some
of your data, and in particular you do not get the history and the links between records — the
invoices for each customer, for example. That is the risk of any route that moves documents into a
fresh system, and it is why, if you take that route with us, you ask us one question before you
approve anything: which records will be in the new database, and which will not. We would rather you
ask it than assume it.
</p>
</div>
</div>
</section>
<!-- Process -->
<section class="section-light">
<div class="container">
<p class="section-kicker">The published path</p>
<h2 class="section-title">The upgrade process, step by step</h2>
<div class="oc-body">
<p>
This is the sequence that Odoo's documentation, the OpenUpgrade project and the recorded workshops
all agree on. Nothing here is ours and nothing here is secret — it is the published path, in the
order it is done.
</p>
</div>
<ol class="oc-steps">
<li>
<strong>Back up the database, and know where the filestore is.</strong> Before anything else. The
documentation of the OpenUpgrade project says you absolutely must take a backup of the live
database, and every practitioner does this first.
</li>
<li>
<strong>Work on a copy.</strong> Restore the backup into a test database and migrate that. Your live
system stays untouched until the copy works.
</li>
<li>
<strong>Bring the target version up first.</strong> Install the newer Odoo, and put the OpenUpgrade
code for the target version on its add-ons path, alongside the modules you already use.
</li>
<li>
<strong>Port your custom modules.</strong> Bring their code to the new version before the migration
runs.
</li>
<li>
<strong>Run the migration on the copy.</strong> One module at a time, in dependency order, each
running its own pre-script, then loading, then its post-script.
</li>
<li>
<strong>Expect to run it more than once.</strong> This is iterative. In the recorded workshop, the
environment — repositories, branches, the module list — is the part that takes the time; the
migration run itself is quick once the environment exists, and you reset and run again until the log
is clean.
</li>
<li>
<strong>Finish with a plain update.</strong> Running the update once more without the migration
parameters is what clears the "incompatible" state and leaves the database usable.
</li>
<li>
<strong>Test by data, not by the version number.</strong> Open the migrated copy and check the
things that matter: a customer, a product, an order, an invoice, a payment. That is how the
practitioners verify it on screen.
</li>
<li>
<strong>Clean up afterwards.</strong> Obsolete tables, columns and views left behind by a migration
cost performance, so they are removed once the system has been running for a while and nobody has
reported a problem.
</li>
<li>
<strong>Then move to production.</strong> Restore the migrated database where it belongs, or have
the upgrade run on the production database at a time you choose.
</li>
</ol>
<p class="oc-pull">
That is the honest shape of it: preparation is the long part, the migration itself is the short part,
and the copy is what you are allowed to break.
</p>
</div>
</section>
<!-- Technical section -->
<section class="section-dark" id="commands">
<div class="container">
<p class="section-kicker">For your developer</p>
<h2 class="section-title">For your developer: the actual commands</h2>
<div class="oc-body">
<p>
If you are running Odoo Community yourself and you would rather do this in-house than have us do
it, these are the commands.
They are quoted from the documentation they come from, and each one is labelled with what you must
substitute and what that source warns about. Nothing below has been run by us against your database,
and no command published by us runs on our own servers — our service starts with a support ticket,
not a terminal.
</p>
</div>
<div class="oc-sub">
<h3>Odoo's upgrade platform</h3>
<p>
Odoo documents an upgrade platform that works for on-premise installations from the command line.
Odoo's documentation states the migration scripts it runs are not open source and come with
Enterprise — but the platform itself will upgrade a self-hosted database.
</p>
<pre class="oc-code"><code># upgraded TEST database
python &lt;(curl -s https://upgrade.odoo.com/upgrade) test -d &lt;your db name&gt; -t &lt;target version&gt;
# production upgrade
python &lt;(curl -s https://upgrade.odoo.com/upgrade) production -d &lt;your db name&gt; -t &lt;target version&gt;
# general help
python &lt;(curl -s https://upgrade.odoo.com/upgrade) --help</code></pre>
<div class="oc-note">
<p>
<strong>Substitute:</strong> <code>&lt;your db name&gt;</code> is the database on the machine you
run the command from, and <code>&lt;target version&gt;</code> is the release you are moving to —
17.0, 18.0 or 19.0, matching the pricing selector on our own plans page.
</p>
<p>
What Odoo's documentation requires next to those three lines, because it changes whether they
work: the script needs the usual Linux commands present on the machine; the system user running it
needs access to your PostgreSQL database (the standard PostgreSQL client environment, such as
<code>.pgpass</code>); and the machine must reach the upgrade platform on TCP 443 and on a port in
the range 32768-60999, so a firewall may need an exception. The copy you upload is submitted
without a filestore, so the upgraded database has to have the production filestore merged into it
before you test it. The download contains <code>dump.sql</code> and a filestore folder, and only
the person who requested the upgrade can download the result.
</p>
</div>
</div>
<div class="oc-sub">
<h3>The Community route: OCA OpenUpgrade</h3>
<pre class="oc-code"><code># 1. OpenUpgrade for the TARGET version (the branch is the target major version)
git clone --depth 1 --branch &lt;TARGET&gt;.0 https://github.com/OCA/OpenUpgrade.git
# 2. helper library — the line the OpenUpgrade docs use
pip install "git+https://github.com/OCA/openupgradelib.git@master#egg=openupgradelib"
# 3. put the clone on the add-ons path of the &lt;TARGET&gt; Odoo config
# addons_path = &lt;your own addons&gt;,&lt;/abs/path&gt;/OpenUpgrade
# 4. migrate the copy of the database
&lt;odoo-bin&gt; -c /etc/odoo/&lt;TARGET&gt;.conf \
-d &lt;DB&gt; \
--upgrade-path=/abs/path/OpenUpgrade/openupgrade_scripts/scripts \
-u all --stop-after-init \
--load=base,web,openupgrade_framework
# 5. finish: a plain update, no migration parameters
&lt;odoo-bin&gt; -c /etc/odoo/&lt;TARGET&gt;.conf -d &lt;DB&gt; -u all --stop-after-init</code></pre>
<div class="oc-note">
<p>
<strong>Substitute four things:</strong> <code>&lt;TARGET&gt;</code> is the target major version
(17, 18 or 19 — not the version you are coming from); <code>&lt;DB&gt;</code> is the copy you are
migrating, never the live database; <code>&lt;odoo-bin&gt;</code> is the binary for the target
version, and what it is called depends on how you installed Odoo (<code>odoo-bin</code>,
<code>odoo</code>, or the Docker equivalent); and both absolute paths are your own — the add-ons
path and the location of the OpenUpgrade clone.
</p>
<p>
Two details worth carrying from the project's own documentation: the
<code>openupgrade_framework</code> part of that command is a set of patches that, among other
things, stops Odoo dropping the columns and tables of fields that no longer exist — which is one
of the ways a migration loses data. And the environment variable
<code>OPENUPGRADE_USE_DEMO=yes</code> keeps demo data in the migrated database when you want it.
</p>
</div>
</div>
<div class="oc-sub">
<h3>Your custom modules</h3>
<pre class="oc-code"><code># Odoo's own code-rewrite command (it runs the scripts in odoo/upgrade_code)
&lt;odoo-bin&gt; upgrade_code --from &lt;FROM&gt; --to &lt;TO&gt; --dry-run</code></pre>
<div class="oc-note">
<p>
<strong>Substitute</strong> <code>&lt;FROM&gt;</code> and <code>&lt;TO&gt;</code> with the two
major versions (18 and 19, for example). Run it with <code>--dry-run</code> first. Odoo's own
command reference calls this command a "best-effort" tool and warns that it is "not silver
bullets" — it rewrites what it can and leaves you a list of what a human still has to change. The
OpenUpgrade community's equivalent for module code is <code>odoo-module-migrator</code>, which
logs INFO for automatic changes, WARNING for things to check and ERROR for things that will not
work at all. Both tools report; neither one thinks.
</p>
</div>
</div>
<div class="oc-sub">
<h3>Backups and the filestore</h3>
<pre class="oc-code"><code># stop the service while you work (packaged install; the Odoo docs show this pattern)
sudo service odoo&lt;TARGET&gt; stop
# database dump (PostgreSQL's own tool)
sudo -u postgres pg_dump -Fc -d &lt;DB&gt; -f /path/backup/&lt;DB&gt;.dump
# restore a dump into a fresh database
sudo -u postgres createdb -O &lt;DB_OWNER&gt; &lt;DB_NEW&gt;
sudo -u postgres pg_restore -d &lt;DB_NEW&gt; /path/backup/&lt;DB&gt;.dump
# the filestore lives under Odoo's data directory:
# default on Unix: $XDG_DATA_HOME or ~/.local/share/Odoo or /var/lib/Odoo
# filestore path: &lt;data_dir&gt;/filestore/&lt;DB&gt;</code></pre>
<div class="oc-note">
<p>
<strong>Substitute</strong> the database names, the owner, the backup path and the filestore data
directory with your own; the filestore is what holds the attachments and documents your records
point at, and a database restored without it is a database whose files are missing. These are
PostgreSQL's and Odoo's documented procedures, not ours — we do not publish a command that runs on
our own servers, because our service starts with a ticket.
</p>
</div>
</div>
</div>
</section>
<!-- Self-hosting cost -->
<section class="section-light">
<div class="container">
<p class="section-kicker">Sweat equity</p>
<h2 class="section-title">What doing it yourself really costs</h2>
<div class="oc-body">
<p>
A rented server is cheap. That is the smallest number in this decision. <a href="blog/posts/community-vs-enterprise-cost.html">Odoo Community itself is
free</a> — you will not find a per-user licence anywhere in this route, because Community does not have
one.
</p>
<p>
What is not in that number is the person. An upgrade on your own server means someone installs the
new version, gets the migration code onto the right path, ports the modules your business actually
uses, runs the migration until the log stops complaining, checks that the customers, orders and
invoices are still there, cleans up what the migration left behind, and then does it again on the
production database at a time that suits the business. Odoo's own documentation says the update is
most appropriately done by the person who deployed Odoo in the first place. On a rented server, that
person is you or someone you pay by the hour — and neither of those lines appears on the invoice.
That is the half of the bill nobody prints, and it is the half that lands in your evenings.
</p>
<p>
We are not going to put a number on your hours, because we have not measured them and a marketing
figure would be worse than none. What we will say is the part that is not really about money: while
you are doing that work, you are not doing the work you get paid for, and if the migration is wrong
you find out from a customer.
</p>
</div>
</div>
</section>
<!-- FAQ -->
<section class="section-dark" id="faq">
<div class="container">
<p class="section-kicker">Straight answers</p>
<h2 class="section-title">Questions about upgrading</h2>
<div class="cmp-faq-grid">
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">Is an upgrade the same as an update?</summary>
<div class="cmp-a">
<p>
No. An update gives you the newest revision of the software you already run. An upgrade moves
your database to a new major release — 18 to 19, or 19 to 20 — and transforms the data itself.
It is one-way: there is no supported path back down, and it is not a licence change, an edition
change or a hosting change.
</p>
</div>
</details>
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">Can we upgrade straight from Odoo 14 to Odoo 20?</summary>
<div class="cmp-a">
<p>
No. It is one major version at a time: 14 → 15 → 16 → 17 → 18 → 19 → 20. Skipping a version is
not a supported path. Our own migration service documents support for databases on Odoo 17, 18
and 19, main releases only — no patch releases — and older databases need a compatibility review
first, which starts with a support ticket.
</p>
</div>
</details>
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">What happens to my history — the invoices, the payments, the attachments?</summary>
<div class="cmp-a">
<p>
That is the right question to ask, and the honest answer is that it depends on the route. The
OpenUpgrade route — a version migration run on your own server — carries the database across and
keeps the history and the links between records. The OpenUpgrade documentation warns that the
other route, moving documents into a fresh system, recovers only some of your data and loses the
history and the links between records — the invoices for each customer, for example. Our service
builds you a new database and moves your data into it, so ours is the route to ask about: before
you approve anything, ask us to list exactly which records will be in the new database, and
which will not. We would rather you ask it than assume it.
</p>
</div>
</details>
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">Will my custom modules still work?</summary>
<div class="cmp-a">
<p>
Not by themselves. A module written for your business has to have its code brought up to the
target version, and any renames that would otherwise lose data need their own migration scripts.
Odoo's documentation is direct about it: an upgrade is blocked until a version of your custom
modules exists for the release you are moving to. Modules that ship with Odoo and modules from
the community association carry their own migration scripts; yours do not, and someone has to
write them.
</p>
</div>
</details>
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">What does it cost, and how long does it take?</summary>
<div class="cmp-a">
<p>
We publish no price and no duration for the upgrade service, and we are not going to invent one
here. What we do publish is how it starts: you open a support ticket, we review your database
for compatibility, and we come back to you. If you are weighing it against doing it yourself,
the honest comparison is not the server rental — it is your own hours, which never appear on
that invoice.
</p>
</div>
</details>
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">Do I need a developer?</summary>
<div class="cmp-a">
<p>
To run the commands in the technical section, yes — or someone comfortable in a terminal with
PostgreSQL. To have us do it, no: that is the point of the service. You send the database, we
build the new system on our infrastructure, and you get credentials.
</p>
</div>
</details>
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">Can I look at the upgraded system before going live?</summary>
<div class="cmp-a">
<p>
Yes, and you should. Whoever does the work migrates a copy first — that is the first rule of the
whole process, in Odoo's documentation and in the OpenUpgrade project's — and the migrated copy
is what you inspect before anything happens to the system your business is running on. Our
service follows the same principle: we build the new system on our own servers and you compare
it with the old one.
</p>
</div>
</details>
<details class="cmp-faq">
<summary class="plausible-event-name=FAQ">We are hosted somewhere else. Can you still upgrade us?</summary>
<div class="cmp-a">
<p>
Yes, that is exactly what our migration service is for. You open a support ticket, we send you a
secure form to upload your database, and we spin up a new server for you on our infrastructure
and provide the credentials. Databases on Odoo 17, 18 and 19, main releases only; older ones
after a compatibility review.
</p>
</div>
</details>
</div>
</div>
</section>
<!-- Final CTA band -->
<section class="section-dark cmp-cta" id="start">
<div class="container">
<p class="oc-lead">
Open a support ticket, we review your database for compatibility, and we come back to you.
</p>
<div class="cmp-cta-actions">
<a href="support.html#contact" class="btn btn-brass btn-lg">Open a ticket — tell us which version you run</a>
<a href="index.html#pricing" class="btn btn-outline btn-lg">See the plans</a>
</div>
</div>
</section>
<!-- Footer (copied from index.html; icon-font glyphs replaced with inline SVG so the page
carries no external icon dependency) -->
</main>
<footer class="footer">
<div class="container">
<div class="fgrid">
<div>
<h4>Useful Links</h4>
<ul>
<li><a href="/odoo-erp-automation-bootcamp/">ODOO Bootcamp</a></li>
<li><a href="odoo-hosting-2027.html">ODOO Hosting in 2027</a></li>
<li><a href="support.html">Support</a></li>
<li><a href="legal.html">Legal</a></li>
</ul>
</div>
<div>
<h4>Partners</h4>
<div class="partner-list">
<a href="https://oc-guy.com" target="_blank" rel="noopener" class="partner-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#c9a227" stroke-width="2" aria-hidden="true"><path d="M20 7h-9M14 17H5M20 7l-4-4M20 7l-4 4M4 17l4 4M4 17l4-4" /></svg> OC Guy</a>
<a href="https://oca-py.org" target="_blank" rel="noopener" class="partner-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#c9a227" stroke-width="2" aria-hidden="true"><path d="M20 7h-9M14 17H5M20 7l-4-4M20 7l-4 4M4 17l4 4M4 17l4-4" /></svg> OCA Paraguay</a>
<a href="https://OD8N.com" target="_blank" rel="noopener" class="partner-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#c9a227" stroke-width="2" aria-hidden="true"><path d="M20 7h-9M14 17H5M20 7l-4-4M20 7l-4 4M4 17l4 4M4 17l4-4" /></svg> OD8N</a>
</div>
</div>
<div class="fcol-desc">
<h4>About us</h4>
<p>We build smart, scalable business solutions by combining the <strong>stability</strong> of Odoo ERP with the <strong>power</strong> of AI Agents and N8N automations.</p>
<p>From streamlined sales to global rollouts, our <strong>lean, eco-friendly</strong> approach helps businesses cut costs, boost efficiency, and grow without limits.</p>
</div>
<div>
<h4>Follow us on</h4>
<div class="social">
<a href="https://www.linkedin.com/company/odoo4projects/" target="_blank" aria-label="LinkedIn"><svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5zM3 9h4v12H3zM9 9h3.8v1.7h.05c.53-.95 1.83-1.95 3.76-1.95 4.02 0 4.76 2.5 4.76 5.75V21h-4v-5.5c0-1.31-.02-3-1.9-3-1.9 0-2.19 1.42-2.19 2.9V21H9z" /></svg></a>
<a href="https://www.youtube.com/@ODOO4projects" target="_blank" aria-label="YouTube"><svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23 12s0-3.2-.41-4.73a2.5 2.5 0 0 0-1.76-1.77C19.3 5.1 12 5.1 12 5.1s-7.3 0-8.83.4A2.5 2.5 0 0 0 1.4 7.27C1 8.8 1 12 1 12s0 3.2.41 4.73a2.5 2.5 0 0 0 1.76 1.77c1.53.4 8.83.4 8.83.4s7.3 0 8.83-.4a2.5 2.5 0 0 0 1.76-1.77C23 15.2 23 12 23 12zM9.75 15.02V8.98L15.5 12z" /></svg></a>
</div>
<form class="footer-contact-form" id="footerContactForm" action="mailto:hello@odoo4projects.com" method="post" enctype="text/plain">
<label class="oc-sr-only" for="footerContactEmail">Your email address</label>
<input type="email" id="footerContactEmail" name="email" placeholder="Your email address" required />
<div class="footer-textarea-wrap">
<label class="oc-sr-only" for="footerContactText">Your message</label>
<textarea id="footerContactText" name="message" placeholder="Your message..." required></textarea>
<button type="submit" class="footer-send-btn" aria-label="Send message"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M2 21l21-9L2 3v7l15 2-15 2z" /></svg></button>
</div>
<div class="footer-form-status" id="footerFormStatus" role="status"></div>
</form>
</div>
</div>
<div class="fcopy">Copyright &copy; ODOO4projects LLC</div>
</div>
</footer>
</body>
</html>