Public Access
Two user decisions (2026-11-03). The page is live.
1. The anonymous-contact modal is removed in full, not just its gate: its markup and
comment, the .contact-modal and .dev-anon-btn CSS, the JS (modalEls, openContactModal,
closeContactModal, initContactModal, enhanceContact), the two init-time calls and the
per-card render call. ODOO_EXPERTS_QUOTE_WEBHOOK is deleted — the only readers were the
two gates (initContactModal, enhanceContact) and the postTo inside the modal's own
submit handler; nothing else read it. The modal's lede ("We pass your request on ...")
goes with it, so no relay copy survives. Kept on purpose: the invite relay form and
ODOO_EXPERTS_INVITE_WEBHOOK, the OCA filter + badge, ODOO_EXPERTS_WEBHOOK, and the
card's direct "Email this freelancer" chip.
2. hello@odoo4projects.com is retired site-wide: 24 occurrences (mailto + visible text)
become support@odoo4projects.com, and the 12 support@ODOO4projects.com case variants
are normalised to lower case. oliver@odoo4projects.com is a freelancer's own published
contact address on their card and is left untouched.
Tests run: verify-html.py PROBLEMS 0; check-inline-js.py OK; competitor QC exit 3 with 74
pre-existing hits and 0 added; impeccable detect 1 -> 1 (identical, 0 new).
1039 lines
50 KiB
HTML
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 & 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 <(curl -s https://upgrade.odoo.com/upgrade) test -d <your db name> -t <target version>
|
|
|
|
# production upgrade
|
|
python <(curl -s https://upgrade.odoo.com/upgrade) production -d <your db name> -t <target version>
|
|
|
|
# general help
|
|
python <(curl -s https://upgrade.odoo.com/upgrade) --help</code></pre>
|
|
<div class="oc-note">
|
|
<p>
|
|
<strong>Substitute:</strong> <code><your db name></code> is the database on the machine you
|
|
run the command from, and <code><target version></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 <TARGET>.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 <TARGET> Odoo config
|
|
# addons_path = <your own addons>,</abs/path>/OpenUpgrade
|
|
|
|
# 4. migrate the copy of the database
|
|
<odoo-bin> -c /etc/odoo/<TARGET>.conf \
|
|
-d <DB> \
|
|
--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
|
|
<odoo-bin> -c /etc/odoo/<TARGET>.conf -d <DB> -u all --stop-after-init</code></pre>
|
|
<div class="oc-note">
|
|
<p>
|
|
<strong>Substitute four things:</strong> <code><TARGET></code> is the target major version
|
|
(17, 18 or 19 — not the version you are coming from); <code><DB></code> is the copy you are
|
|
migrating, never the live database; <code><odoo-bin></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)
|
|
<odoo-bin> upgrade_code --from <FROM> --to <TO> --dry-run</code></pre>
|
|
<div class="oc-note">
|
|
<p>
|
|
<strong>Substitute</strong> <code><FROM></code> and <code><TO></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<TARGET> stop
|
|
|
|
# database dump (PostgreSQL's own tool)
|
|
sudo -u postgres pg_dump -Fc -d <DB> -f /path/backup/<DB>.dump
|
|
|
|
# restore a dump into a fresh database
|
|
sudo -u postgres createdb -O <DB_OWNER> <DB_NEW>
|
|
sudo -u postgres pg_restore -d <DB_NEW> /path/backup/<DB>.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: <data_dir>/filestore/<DB></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:support@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 © ODOO4projects LLC</div>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|