> For the complete documentation index, see [llms.txt](https://docs.chamilo.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.chamilo.org/administration-guide/admin-guide/installation/upgrading.md).

# Upgrading

Note: On this page, we use 3.0.0 as a strict version number and 3.x to identify all versions that start with the number 3 (3.0.0, 3.0.1, 3.1.0, etc). The same convention applies to 2.x.

The upgrade process from 1.11.x is also described in your `public/documentation/installation_guide.html` file, inside your Chamilo code. The information here is largely redundant. You can see it online at `https://campus.chamilo.net/documentation/installation_guide.html`.

**Upgrade to 3.0, not to 2.x.** Version 3.0 is the current release, and some 1.11.x settings had no equivalent yet in 2.0.0. A 1.11.x system therefore goes straight to 3.0. We have tested similar migrations extensively, but every platform carries its own history: try it on a test environment first, and consider being professionally accompanied by [official Chamilo providers](https://chamilo.org/providers) in this endeavour.

## Upgrading from 1.11.x to 3.0

Upgrading from Chamilo 1.11.x to 3.0 is a **major migration**, not a simple update. Chamilo 2.0 was rebuilt on the Symfony framework with a restructured database schema, new API, and different file organization, and 3.0 continues that line. Plan this migration carefully and try it out on a test environment before rolling out in production.

### Before You Begin

1. **Read the release notes** for Chamilo 3.x to understand what has changed, what is new, and what features from 1.11.x may not yet be available.
2. **Back up everything**:
   * Full database dump (`mysqldump` or equivalent).
   * All files in the Chamilo 1.11.x installation directory, especially `app/upload/`, `app/courses/`, and `main/`.
   * Your `configuration.php` file.
3. **Test on a staging server first.** Never run the migration directly on your production server.
4. **Verify server requirements.** Chamilo 3.x has different requirements than 1.11.x (notably, PHP 8.3 or later — the installer refuses anything older). See [Server Requirements](/administration-guide/admin-guide/installation/server-requirements.md).
5. **Decide what happens to the `version` table of the 1.11.x database.** Chamilo 2.x and later store the Doctrine migration history in a table of that name, with other columns, so the two cannot coexist. The web wizard handles this for you: it renames the 1.11.x table to `version_1_11` before it runs the first migration. The command line does not, so **delete or rename that table yourself before you run `doctrine:migrations:migrate`**. The table is not necessary for Chamilo 1.11.x to work.
6. **Unpack the new code in a new directory.** The 1.11.x files stay where they are. The installer reads them as the source of your courses and uploads, and writes the result into the new tree.
7. **If you used sublanguages, nothing extra is required.** The upgrade converts them and writes their terms into `var/translations/`, which your web server already needs to write. The interface reads them from there, with no rebuild.

### Running the Upgrade

You can run the upgrade through the web wizard or through the command line.

#### Web wizard

1. Point the `DocumentRoot` of your virtual host to the `public/` subdirectory of the new tree.
2. Create an empty `UPGRADE_ENABLED` file in the project root, next to `.env` and `composer.json` — see below.
3. Open your URL. The wizard starts, because the new tree has no `.env` file yet.
4. On step 2, select the upgrade option and give the root path of your 1.11.x installation.
5. Follow the wizard to the end.

The wizard has no login of its own, so an upgrade needs an authorisation on the server: that `UPGRADE_ENABLED` file. **Only you create it.** The wizard never writes it for itself, because the file's whole value is that its presence proves a person decided on the server.

Your new tree has no `.env` yet, so the wizard does open without the file. It asks for it on the requirements step, when you choose the upgrade and before it asks for your database. Create the file then and continue; you do not restart the wizard.

The installer deletes the file when the upgrade finishes. If your project root is read-only, it says so instead, and you delete the file by hand. While it is there, the installer stays open, and the administration page reports it under the platform health checks.

#### Command line

Set `UPDATE_PATH` to the root of your 1.11.x installation, then run the migrations:

```bash
UPDATE_PATH=/path/to/chamilo-1.11 php bin/console doctrine:migrations:migrate --no-interaction
```

Raise `memory_limit` and `max_execution_time` first. The migration reads every course file, so it needs far more than the defaults.

#### How long it takes

The duration follows the size of your database and of your course files. As one reference point, a 1.11.28 platform with 238 tables, 11 courses, 63 users and 1489 course files took **6 minutes** and 1.7 GB of memory, and ran 393 migrations. A large production platform takes hours. Plan a maintenance window, and read the [Chamilo forum](https://chamilo.org) or contact an [official provider](https://chamilo.org/providers) before you run it on production.

### What May Require Manual Attention

| Area                              | Notes                                                                                                                                                                                                                                                                                                                                    |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Custom plugins**                | 1.11.x plugins do not work in 2.x or 3.x. They must be rewritten or replaced. The official ones have been ported progressively since 2.0 — check the plugin list of your version to see which are available.                                                                                                                             |
| **Custom themes**                 | 1.11.x themes do not work in 2.x or 3.x. Recreate your branding using the 3.x theming system.                                                                                                                                                                                                                                            |
| **Custom database modifications** | Any direct database modifications outside of Chamilo may not be migrated.                                                                                                                                                                                                                                                                |
| **SCORM packages**                | SCORM content should migrate, but test packages individually to verify playback.                                                                                                                                                                                                                                                         |
| **External integrations**         | Any integrations using the 1.11.x API or web services need to be updated to use the 2.x REST-only API using [API Platform](https://github.com/api-platform/api-platform).                                                                                                                                                                |
| **Profile settings**              | After a legacy upgrade, verify that `changeable_options` and `visible_options` still reflect the expected profile editability and visibility rules. The upgrade code preserves their values from legacy settings instead of treating them as `configuration.php` entries ([PR #9161](https://github.com/chamilo/chamilo-lms/pull/9161)). |

### Auditing quiz-question relations after a legacy migration

If migrated exercises appear to have missing questions, use the console command introduced in [PR #9157](https://github.com/chamilo/chamilo-lms/pull/9157) to **audit before attempting a repair**. Run it from the Chamilo application root against a staging copy of the migrated database.

A first pass examines the target database only and **does not change data**:

```bash
php bin/console chamilo:migration:repair-quiz-relations-from-legacy
```

Empty quizzes alone do not prove that relations were lost. To compare against the old Chamilo data, make the legacy database available on the **same database server**, with suitable read permissions, and provide its database name:

```bash
php bin/console chamilo:migration:repair-quiz-relations-from-legacy --source-database=legacy_chamilo_db
```

You can narrow either audit to particular *target quiz iids* with a repeatable `--quiz=123` option. Without `--apply`, both forms remain read-only. The comparison reports missing relations, affected quizzes and possible conflicts or unresolved references.

**Do not apply a repair based solely on an empty quiz count.** Preserve verified database backups and review the compared source and target data. A repair requires `--source-database`, `--apply`, and both `--expected-missing-relations` and `--expected-affected-quizzes` set to the **exact counts from the preceding read-only comparison**. The command refuses to write if these counts changed or if affected quizzes have unsafe conflicts. Apply only after validating the results on a staging copy; repeat the read-only audit afterwards to confirm that no verified missing relations remain.

## Upgrading from 2.x to 3.0

This upgrade keeps your existing directory and your existing database. You copy the new code over the old tree, then run the migrations, either through the web wizard or through the command line.

### Seed the migration history first

Chamilo installs the database schema directly from the entity definitions, so an installation created by the installer holds the final schema but an **empty migration history**. Installations created before Chamilo 3.0 were never given that history. Two things depend on it:

* `doctrine:migrations:migrate` decides what to run from it. With an empty history it tries to replay every migration from the beginning over a schema that is already current.
* The web installer decides from it whether an upgrade is pending. With an empty history it refuses the request, because nothing proves that an upgrade is due.

So seed it once, and observe the order below.

> **Warning: seed the history before you copy the new code.** The commands mark every migration that the **deployed** code carries as already executed. If you run them after you copy the 3.0 code, they also mark the 3.0 migrations, and your upgrade never runs.

With your current version still in place, run:

```bash
php bin/console doctrine:migrations:sync-metadata-storage --no-interaction
php bin/console doctrine:migrations:version --add --all --no-interaction
```

The first command creates the history table. The second marks the migrations of your current version. `doctrine:migrations:version` fails on its own if the table does not exist yet, so do not skip the first one.

**No shell on your hosting?** The administration page does the same thing: the **No migration history: click to fix** item of the platform health checks. It does not need the order above, because it reads your schema instead of the code on disk: it records only the migration namespaces your database proves it already carries, and leaves the newer ones pending. So you may copy the 3.0 code first and use the link afterwards — see *If you already copied the 3.0 code* below.

Check the result:

```bash
php bin/console doctrine:migrations:status
```

`Executed` must equal `Available`, and `New` must be 0. Now copy the 3.0 code.

### If you already copied the 3.0 code

Two symptoms identify this case. The web wizard answers **"Chamilo is already installed"** although your platform is a 2.x one, and `doctrine:migrations:status` reports `Executed` 0.

Your history is still empty, and the code on disk is no longer the code that created your database. So do **not** seed it with `--add --all` from here: that marks every migration the deployed code carries, which records the migrations your upgrade still needs as already done. They then never run, and nothing warns you afterwards — your database simply lacks the columns they create.

There are two ways out, and the first one needs no shell.

**Way one, from the administration page.** Open the platform health checks and use the **No migration history: click to fix** item. It reads your schema, records only the namespaces your database proves it already carries, and leaves the newer migrations pending. The notification reports both counts: for a 2.0.x database under the 3.0 code, 347 recorded and 46 still pending. Then run the upgrade as described below.

**Way two, from a shell.** Seed everything, then remove from the history the namespaces your previous version did not carry.

Every 2.0.x release, from 2.0.0 to 2.0.3, carries the same **344** migrations, all of them in the `V200` namespace. Chamilo 3.0.0 carries **393**: the `V210` and `V300` namespaces, plus three `V200` migrations. Those three are dated copies of migrations that also live in `V210` and `V300`, added so the 1.11.x path creates their columns early, and each one checks the schema before it acts. So recording the whole `V200` namespace loses nothing — their twins stay pending and do the work.

```bash
php bin/console doctrine:migrations:sync-metadata-storage --no-interaction
php bin/console doctrine:migrations:version --add --all --no-interaction
```

Then, in your database, remove the migrations that must still run:

```sql
DELETE FROM version WHERE version LIKE '%V210%' OR version LIKE '%V300%';
```

Check the result:

```bash
php bin/console doctrine:migrations:status
```

For a 2.0.x source and Chamilo 3.0.0, `Executed` must be 347, `Available` 393 and `New` 46. Now continue with the upgrade below.

### Authorise the upgrade, if you use the web wizard

Your platform is already installed, so the wizard refuses it until you authorise the upgrade on the server. The authorisation is an empty file named `UPGRADE_ENABLED`, in the project root, next to `.env` and `composer.json`:

```bash
cd /var/www/chamilo
touch UPGRADE_ENABLED
```

It goes in the project root, not in `public/`: from there nobody can ask over HTTP whether your platform is currently open for upgrading. Without it the wizard answers `No UPGRADE_ENABLED found in the project root`. The installer deletes the file when the upgrade finishes; on a read-only project root it says so instead, and you delete it by hand. The command line needs no such file.

### Run the upgrade

Copy the new code, then either open your URL and follow the wizard, or run the migrations from the command line:

```bash
php bin/console doctrine:migrations:migrate --no-interaction
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod
```

The web wizard opens only while migrations are pending. Once the upgrade finishes, it answers `409 Conflict` again, which is what protects it: the wizard has no login of its own.

## Updating Chamilo 3.0.x

Minor updates within the 3.0 branch are more straightforward.

### Update Process

#### Using a package

1. **Back up** the database and files.
2. **Download the latest 3.0.x version** from [chamilo.org](https://chamilo.org/download):
3. **Expand locally**

For example (adapt to the downloaded version)

```bash
unzip chamilo-3.0.1.zip
```

4. **Copy the files over your existing Chamilo installation**

   ```bash
   cp -r chamilo/* [your-chamilo-installation-path]/
   cp -r chamilo/.* [your-chamilo-installation-path]/
   ```
5. **Run database migrations:**

   ```bash
   php bin/console doctrine:migrations:migrate --no-interaction
   ```
6. **Clear the cache:**

   ```bash
   php bin/console cache:clear --env=prod
   php bin/console cache:warmup --env=prod
   ```
7. **Change permissions**

Adapt to your web server user:

```bash
sudo chown -R www-data: [your-chamilo-installation-path]/var
```

8. **Verify** that the platform loads correctly and spot-check key functionality.

#### Using Git

If you installed Chamilo using Git, you can follow these instructions instead.

1. **Back up** the database and files.
2. **Pull the latest code** (or download the new release):

   ```bash
   git pull origin 3.0
   ```
3. **Update PHP dependencies:**

   ```bash
   composer install --no-dev --optimize-autoloader
   ```
4. **Update JavaScript dependencies and rebuild assets:**

   ```bash
   yarn install && yarn build
   ```
5. **Run database migrations:**

   ```bash
   php bin/console doctrine:migrations:migrate --no-interaction
   ```
6. **Clear the cache:**

   ```bash
   php bin/console cache:clear --env=prod
   php bin/console cache:warmup --env=prod
   ```
7. **Change permissions**

Adapt to your web server user:

```bash
sudo chown -R www-data: [your-chamilo-installation-path]/var
```

8. **Verify** that the platform loads correctly and spot-check key functionality.

### Automating Updates

For organizations that manage multiple Chamilo instances, consider scripting the update process:

```bash
#!/bin/bash
set -e

# Pull code
git pull origin 3.0

# Dependencies
composer install --no-dev --optimize-autoloader
yarn install && yarn build

# Database
php bin/console doctrine:migrations:migrate --no-interaction

# Cache
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod

echo "Update complete."
```

## Chamilo 3.1: Course language migration

In [PR #9176](https://github.com/chamilo/chamilo-lms/pull/9176), course languages were normalized from the textual `course.course_language` column to `course.language_id`, which references `language.id`. The API and supported callers continue to expose the course language as an ISO code. This is a database relationship change, not a new language selector for teachers.

Before executing the relevant V310 migration (`Version20261005120000`), check that each course language code matches **exactly one** `language.isocode` value. This read-only query identifies missing or ambiguous matches while the legacy column still exists:

```sql
SELECT c.course_language, COUNT(DISTINCT l.id) AS language_matches
FROM course c
LEFT JOIN language l ON l.isocode = c.course_language
GROUP BY c.course_language
HAVING COUNT(DISTINCT l.id) <> 1;
```

A successful preflight returns no rows. If it returns any, review and correct the source data carefully before running the migration. The migration refuses unmatched/ambiguous codes and also rejects a conflicting partial schema (`course_language` and `language_id` both present). Back up the database and test on staging; do not remove the legacy column or disable these checks manually.

## Chamilo 3.1: AI Toolbox registration on upgraded courses

When AI Toolbox is missing from courses that existed before an upgrade, check whether the relevant **V310 migrations** ran successfully. The repair migration `Version20261008113000` ensures the Toolbox tool and `toolbox_items` resource type exist, and creates missing Toolbox course-tool entries **as drafts**; it does not publish them automatically. Subsequent migration improvements address upgrade reliability and performance. This is an upgrade repair, not a replacement for the platform's normal tool visibility and permissions.

If the tool is still absent, check migration status and the Toolbox configuration before changing course records. Do **not** manually insert tool or resource-link rows or repeatedly execute migrations without reviewing the actual schema and migration history. See [PR #9195](https://github.com/chamilo/chamilo-lms/pull/9195) and its follow-up [PR #9229](https://github.com/chamilo/chamilo-lms/pull/9229).

## Chamilo 3.1: Legacy database schema normalization

The October 2026 schema cleanup changes some database **column names, types and relations**, while retaining fields that are still actively used. In particular, `course_rel_class.course_code` is replaced by `course_rel_class.c_id` (referencing `course.id`), and `course_request.category_code` is replaced by `course_request.category_id` (referencing `course_category.id`). Selected `title`, `url` and long `VARCHAR` columns are converted to `LONGTEXT`, while other Chamilo-owned `VARCHAR` fields are normalized to length 255 where applicable. External package-owned columns are deliberately handled separately.

For installations with custom SQL reports, plugins or integrations, **review their queries and schema assumptions** before deploying these migrations. The class/course and course-request/category conversions refuse unresolved references rather than silently discarding them. The cleanup also removes obsolete FOS group tables after safely preserving group-derived user roles; incompatible or invalid legacy group data causes the migration to stop for review. Do not drop those tables or rewrite the associations manually in advance.

These changes are documented in [PR #9212](https://github.com/chamilo/chamilo-lms/pull/9212) (issue #7445). Back up the database and verify the upgrade on a staging copy, particularly if custom code reads these legacy tables or expects fixed text lengths.

## Security notes

### October 2026 security maintenance on the 3.x development branch

Several security fixes authored by **christianbeeznest** were merged into Chamilo's `master` branch between **16 and 18 October 2026**. These are code-level protections, **not new settings**. A merged pull request does not guarantee that the change is included in a particular tagged release or in an installation that has not been updated. Check the release notes or deployed Git revision before marking a deployment as patched.

The changes include:

* **HTML filtering:** more robust removal of unsafe event attributes from user-supplied HTML ([PR #9247](https://github.com/chamilo/chamilo-lms/pull/9247)).
* **Learning path progress and exercise annotations:** strengthened server-side handling of learning-path progress requests and access to a learner's annotated attempt answers ([PR #9251](https://github.com/chamilo/chamilo-lms/pull/9251), [PR #9259](https://github.com/chamilo/chamilo-lms/pull/9259)).
* **Social profile links and link checking:** tighter URL validation and protection of the course-link checker against unsafe changes in DNS resolution ([PR #9263](https://github.com/chamilo/chamilo-lms/pull/9263), [PR #9271](https://github.com/chamilo/chamilo-lms/pull/9271)).
* **CSV exports:** mitigation of spreadsheet formula interpretation in exported data, across multiple legacy and modern reporting/export paths ([PR #9267](https://github.com/chamilo/chamilo-lms/pull/9267)).

**After deploying code containing these fixes**, run a staging regression check: create and display formatted HTML content; save and resume a learning path; verify that learners can read only their own authorized exercise annotations; check permitted social and course links; and open representative CSV exports in a spreadsheet tool to confirm that user-controlled text is not interpreted as a formula. Include teacher, learner and administrator roles where relevant. Keep existing authentication, access controls and outbound-network restrictions in place. These checks help catch regressions but do not substitute for a security review.

### Predictable passwords of SSO and SCIM users

Before Chamilo 3.0.2, some accounts provisioned or updated by an external identity system had a fixed, publicly guessable password. Anyone who knew the username could sign in with the regular login form if the account kept the `platform` authentication source.

Chamilo 3.0.2 fixes the code. It does **not** change passwords that already exist, so review these accounts after you update:

* **SCIM:** users created through `POST /scim/v2/Users` have the password `scim`.
* **Azure Entra ID, Facebook, Keycloak:** users who signed in through these providers had their password replaced by `azure`, `facebook` or `keycloak`. Only users who also have the `platform` authentication source can use it to sign in.
* **Generic OAuth2:** users created by the provider have their username as password.

To close the exposure:

1. Update to Chamilo 3.0.2 or later.
2. In **Administration > Users**, edit each affected user and set a new password. Users who sign in only through the identity provider do not need to know it.
3. If you use SCIM, set `auth_source` in `authentication.yaml` to a value other than `platform`, then clear the cache. See [SCIM](/administration-guide/admin-guide/authentication/scim.md). Existing users keep the authentication sources they already have.
4. Ask users who sign in with a password to change it.

New users created after the update receive a random password, and an SSO sign-in no longer overwrites the password of an existing user. Users who need a password for the regular login form can request one with **Forgot your password?** on the login page.

## Tips

* **Always back up before upgrading.** Database migrations are not reversible through the Chamilo interface.
* **Test on staging first** -- especially for the 1.11.x to 3.0 migration, which involves significant data transformation.
* **Schedule upgrades during maintenance windows** when users are not actively using the platform.
* **Subscribe to GitHub releases** on [Github](https://github.com/chamilo/chamilo-lms/releases) using the bell icon to be notified of new versions and security patches.
* **If the wizard answers `Chamilo is already installed`**, it found no pending migration. Run `php bin/console doctrine:migrations:status` to check. If `Executed` is 0 on a platform that works, your migration history was never seeded — see [Seed the migration history first](#seed-the-migration-history-first).
* **Automatic download of new versions** is not yet provided in Chamilo 3.0, but this is an ongoing project we hope to be releasing soon. The upgrade itself already runs from the web wizard.
