Multilingual associations in Joomla
You built your website in two languages. A visitor reads your English "About" page, clicks the language switcher to read it in Dutch, and lands on the Dutch home page instead of the Dutch "About" page. That small, frustrating jump is exactly the problem Multilingual Associations solve.
An association is a link that tells Joomla: this English article and that Dutch article are the same page in two languages. Once Joomla knows that, the language switcher keeps the reader on the same content, and search engines learn which pages are translations of each other.
Associations are the glue that turns several single-language items into one piece of content with many faces.
This article starts with the basics for owners and editors, moves on to the setup work for administrators, and ends with the database, the PHP API, and the SEO details for developers. The goal is simple: help you understand associations well enough to build a multilingual Joomla site that behaves correctly.
1. The Basics
1.1 What an Association Is
An association is a record that groups together the equivalent versions of one item across your installed languages. If you have an English article, a Dutch article, and a German article that all say the same thing, an association ties the three together as one logical page.
The key idea: associations do not translate anything. You still write each translation yourself, as a separate article in its own language. The association simply records "these belong together" so the rest of Joomla can act on that knowledge.
An association is not a translation. It is a statement that two or more items already are translations of one another.
1.2 The Problem They Solve
On a multilingual site, the language switcher has to answer one question every time a visitor clicks it: "Where is this same page in the other language?" Without associations, Joomla cannot answer, so it does the only safe thing and sends the visitor to that language's home page.
Visitor on /en/about-us
clicks "Nederlands"
|
v
association exists?
+----------------+------------------+
yes no
| |
go to /nl/over-ons go to /nl/ (the Dutch home page)
(same page, other language) (visitor is lost)
1.3 What You Can Associate
Out of the box, Joomla supports associations for the core content types. Each is linked to its equivalents through the same mechanism:
| Item type | Component |
|---|---|
| Articles | com_content |
| Categories | com_categories (used by content, contacts, news feeds, banners) |
| Contacts | com_contact |
| News feeds | com_newsfeeds |
| Menu items | com_menus |
| Site modules | com_modules |
Each of these types registers itself with the multilingual framework, so they all link translations the same way. Tags and custom fields are not associated: they can carry a language, but Joomla does not group them into translation sets. Modules are a special case covered in section 4: they are mostly controlled by language assignment, yet site modules can also be associated.
1.4 Three Words People Mix Up
Multilingual Joomla has three terms that sound alike. Keeping them apart saves a lot of confusion:
| Term | Meaning |
|---|---|
| Language pack | The interface translation (buttons, labels). Files on disk. |
| Content language | A language your content can be written in (a row in #__languages). |
| Association | A link saying two content items are the same page in different languages. |
This article is about the third one. It assumes the first two already exist.
Back to top2. Prerequisites: A Multilingual Site
Associations only appear and only work when the site is set up for multiple languages. Four things must be in place first.
2.1 Install Content Languages
Under System → Install → Languages, install each language pack you need. Then, under System → Manage → Content Languages, create one Content Language per language and publish it. The content language carries the URL code (the sef, such as en or nl), the language tag (en-GB, nl-NL), and the flag image.
System → Manage → Content Languages
├─ English (en-GB) sef: en published
└─ Nederlands (nl-NL) sef: nl published
2.2 Enable the Language Filter Plugin
The system plugin plg_system_languagefilter is the engine of the whole multilingual system. Enable it under System → Manage → Plugins. When it is off, there is no language switcher, no language URL prefix, and no Associations tab anywhere.
2.3 Assign Content to a Language
Every article, category, menu item, and module has a Language field. To take part in an association, an item must be set to one specific language, not the special value All.
| Language setting | Behaviour |
|---|---|
All |
Shown in every language. Cannot be associated, and does not need to be. |
English (en-GB) |
Shown only in English. Can be associated with a Dutch and a German item. |
Use All for things that are identical in every language (a logo module, a contact email). Use a specific language for anything you actually translate.
2.4 Turn On "Item Associations"
Open the Language Filter plugin and check the parameter Item Associations. Set it to Yes. This single switch decides whether the switcher and the hreflang tags use associations at all:
Language Filter plugin
├─ Item Associations: Yes → switcher follows associations, hreflang tags added
└─ Item Associations: No → switcher always jumps to the home page
Back to top3. Creating and Managing Associations
There are two places to build associations: inside an item's edit screen, and inside the dedicated Multilingual Associations component. Both write to the same place.
3.1 The Associations Tab
Open any associable item (for example an article) and look for the Associations tab. It shows one row per other content language. For each language you can either pick an existing item or create a new translation:
Edit Article "About Us" (English)
└─ Associations tab
Nederlands (nl-NL): [ Select or create the Dutch version ]
Deutsch (de-DE): [ Select or create the German version ]
If the Associations tab is missing, one of the four prerequisites in section 2 is not met, almost always the Language Filter plugin or the item's language being set to All.
3.2 The Multilingual Associations Component
For real translation work, the editor tab is slow. The dedicated tool at System → Manage → Multilingual Associations (the component com_associations) gives you a side-by-side editor:
Multilingual Associations
├─ Item Type: Articles
├─ Source language: English (en-GB)
└─ Target language: Nederlands (nl-NL)
+-------------------------+ +-------------------------+
| English article | | Dutch article |
| (read-only reference) | | (edit / create here) |
+-------------------------+ +-------------------------+
You pick an item type and two languages, click a source item on the left, and edit or create its counterpart on the right. This is the fastest way to translate a whole site section without losing your place.
3.3 One Item Per Language, One Group
Two rules govern every association group:
- An association group holds at most one item per language. You cannot link two English articles together.
- An item belongs to one association group per type. Joining a new group removes it from the old one.
4. Menus, Home Pages, and Modules
Articles are the easy part. The navigation around them needs more care, and this is where most multilingual sites break.
4.1 One Menu Per Language
The clean pattern is one menu per language: a "Main Menu (English)" and a "Hoofdmenu (Nederlands)". Each menu's items are set to that language. You then associate equivalent menu items across the menus, exactly like articles, on the menu item's Associations tab.
4.2 A Home Page Per Language (Required)
Every content language must have its own default menu item (its home page), set to that language. This is not optional. If the default language has no home menu item, the front-end shows a hard error:
Error: there is no home menu item for the default language.
So a two-language site has two "Home" menu items, each marked Default, each in its own language, and the two associated together.
4.3 Modules: Assigned by Language First
For modules, the Language value does the heavy lifting. Joomla publishes only the modules that match the active language, so day to day you control modules by assigning each one a language rather than by associating them. A common setup:
| Module | Language | Result |
|---|---|---|
| Logo / banner image | All |
One module, shown everywhere |
| Welcome text (English) | en-GB |
Shown only on English pages |
| Welkomstekst (Dutch) | nl-NL |
Shown only on Dutch pages |
That said, Joomla 6 does support module associations for site modules. When the Language Filter is active, a site module's editor shows an Associations toolbar button, and the type "Modules" appears in the Multilingual Associations component. Use it when you want the tooling to track which modules are translations of each other; administrator modules cannot be associated.
4.4 The Language Switcher Module
Publish the Language Switcher module (mod_languages) so visitors can change language. Set its own language to All so it shows on every page. It is this module, combined with associations, that delivers the experience from section 1.2.
5. The Language Switcher and Fallback Behaviour
It helps to know exactly what the switcher does when a visitor clicks another language, because the behaviour changes with your settings.
5.1 The Decision the Switcher Makes
Visitor clicks another language
|
v
Item Associations = Yes ?
+-----------------+-----------------+
no yes
| |
go to that language's association for THIS item exists ?
home page +---------------+--------------+
yes no
| |
go to the associated go to that language's
item (same page) home page
5.2 Why a Switch Lands on the Home Page
If a click always lands on the home page even though associations are on, the current page simply has no association for the target language yet. The fix is to create the missing translation and associate it. The switcher is only as complete as your associations are.
5.3 Items Set to "All"
An item whose language is All appears in every language, so there is nothing to switch to. The switcher stays on the same URL. That is correct behaviour, not a bug.
6. Associations and the Router
The language switcher decides which item to go to. The router decides what the URL looks like. These are two different jobs, and seeing the split makes routing problems much easier to debug.
6.1 Two Jobs, Two Subsystems
| Question | Answered by |
|---|---|
| Where is this page in the other language? | Associations (the shared key) |
| What is the clean URL for that page? | The SEF router + Language Filter plugin |
6.2 The Flow When a Language Switches
Visitor on /en/about-us (article id 5)
|
v
association lookup → nl-NL item is article id 8
|
v
router builds the URL for id 8 in nl-NL
|
v
/nl/over-ons (sef prefix "nl" comes from the content language)
The URL prefix /nl/ is not stored in the association. It comes from the content language sef code (section 2.1), which the Language Filter plugin adds to every front-end URL. The association only supplies the target id; the router turns that id into the path.
6.3 Why Menu Associations Drive Routing
The router prefers to build a URL from a menu item, because the menu item defines the route segment. When an article has no associated menu item in the target language, the router has to fall back to a generic route, which is often uglier or lands on the wrong page. This is why associating menu items (section 4) matters as much as associating articles: it gives the router a clean anchor in every language.
Back to top7. Under the Hood: the #__associations Table
All associations, for every content type, live in a single, deceptively small table: #__associations. Understanding its three columns explains the whole feature.
| Column | Holds |
|---|---|
id |
The id of the associated item (for an article, the #__content.id). |
context |
What kind of item it is, such as com_content.item (see section 8). |
key |
A 32-character hash that is the same for every item in one group. |
7.1 The key Is the Glue
There is no "pairs" table and no left/right columns. Items belong together because they share the same key hash. Joomla computes that hash when you save an association, and every member of the group stores it:
id context key
--- ---------------- --------------------------------
5 com_content.item 1a79a4d60de6718e8e5b326e338ae533 <- English article (id 5)
8 com_content.item 1a79a4d60de6718e8e5b326e338ae533 <- Dutch article (id 8)
12 com_content.item 1a79a4d60de6718e8e5b326e338ae533 <- German article (id 12)
To find the translations of article 5, Joomla reads its key, then selects every other row with that same key and context. Three articles, three rows, one shared hash.
7.2 The Primary Key
The primary key is the pair (context, id), with an index on key. That composite key enforces rule 3.3: a given item appears once per context, so it can never sit in two association groups at the same time.
SELECT id
FROM #__associations
WHERE context = 'com_content.item'
AND key = (
SELECT key FROM #__associations
WHERE context = 'com_content.item' AND id = 5
);
-- returns 5, 8, 12 : the whole translation set
7.3 What Is NOT Here
The table stores only ids, contexts, and hashes. The actual article language lives in #__content.language; the title lives in #__content.title. The associations table is a pure index of relationships, nothing more.
8. Association Contexts
The context column is how one table serves every content type at once. Each associable type registers a context string, and Joomla only ever compares rows that share the same context.
| Context | Item type |
|---|---|
com_content.item |
Articles |
com_categories.item |
Categories (all components that use categories) |
com_contact.item |
Contacts |
com_newsfeeds.item |
News feeds |
com_menus.item |
Menu items |
com_modules.item |
Site modules |
Because comparison is scoped to a context, an article (id 5, com_content.item) and a menu item (id 5, com_menus.item) never collide, even though they share the numeric id. The context keeps every type in its own namespace inside one table.
9. The PHP API
Developers rarely touch #__associations directly. Joomla provides a small helper class plus per-component helpers that return ready-to-use data.
9.1 Is the Feature Even On?
Before reading associations, check that the site is configured for them. The check folds together "is the Language Filter plugin enabled" and "is Item Associations set to Yes":
use Joomla\CMS\Language\Associations;
if (Associations::isEnabled()) {
// associations are active on this site
}
9.2 Reading a Group
The core helper returns the translations of one item, keyed by language code:
use Joomla\CMS\Language\Associations;
$associations = Associations::getAssociations(
'com_content', // the extension
'#__content', // the item's table
'com_content.item', // the association context
$articleId // the id of the current item
);
// $associations is keyed by language tag:
// [ 'nl-NL' => (object) ['id' => 8, ...],
// 'de-DE' => (object) ['id' => 12, ...] ]
9.3 The Front-end Helper (URLs Ready to Print)
For building a switcher or a list of "read this in another language" links, each component ships a helper that returns SEF URLs, not just ids:
use Joomla\Component\Content\Site\Helper\AssociationHelper;
$links = AssociationHelper::getAssociations($articleId, 'article');
// [ 'nl-NL' => 'index.php?option=com_content&view=article&id=8&lang=nl-NL',
// 'de-DE' => 'index.php?option=com_content&view=article&id=12&lang=de-DE' ]
This is exactly what the Language Switcher module calls under the hood to turn each flag into a working link.
Back to top10. Adding Associations to Your Own Component
If you build a component with translatable items, you can plug into the same system. The Multilingual Associations component discovers support automatically by looking for one helper class.
10.1 Ship an AssociationsHelper Class
Provide a class named AssociationsHelper in your component's administrator Helper namespace. It extends the abstract base AssociationExtensionHelper, which already implements AssociationExtensionInterface for you. Joomla finds it by convention:
namespace Joomla\Component\Example\Administrator\Helper;
use Joomla\CMS\Association\AssociationExtensionHelper;
class AssociationsHelper extends AssociationExtensionHelper
{
protected $extension = 'com_example';
protected $itemTypes = ['item'];
protected $associationsSupport = true;
public function getType($typeName = '')
{
// describe the 'item' type: its tables, fields, and views
}
public function getAssociationsForItem($id = 0, $view = null)
{
// return the association group for one item, keyed by language
}
}
The interface itself only requires two methods: hasAssociationsSupport() (the base class answers it from $associationsSupport) and getAssociationsForItem(). The getType() method describes your item's tables and fields so the side-by-side editor can render them.
10.2 Store and Save the Links
When your edit form includes an associations field, the framework writes the #__associations rows for you on save, using your component name as the context base (for example com_example.item). You do not compute the hash yourself; the table class does it.
10.3 Use the Behaviour, Not Raw SQL
Read associations through Associations::getAssociations() rather than querying the table directly. The helper handles the context, the published state, the access level, and the fallback to the default language, all of which are easy to get wrong by hand.
11. Performance on Large Sites
A worry on big multilingual sites is that all this lookup work slows pages down. In practice associations are one of the cheaper parts of the system, because of how the table is built.
11.1 The Lookup Is a Single Indexed Read
Finding the translations of an item is two indexed queries, not a scan. The primary key (context, id) finds the item's key instantly, and the idx_key index finds every sibling in the group:
#__associations indexes
├─ PRIMARY (context, id) → find THIS item's key
└─ idx_key (key) → find the rest of the group
Even with hundreds of thousands of rows, each lookup touches only the few rows that share one key. The cost grows with the size of one translation group (a handful of languages), not with the size of the whole site.
11.2 What Actually Costs Time
- Rendering each language URL: the router work in section 6 is heavier than the association read. Joomla caches built routes to soften this.
- Per-language menus: keeping one menu per language keeps each menu small, which keeps menu and route caches efficient.
- Many languages: ten languages means up to ten rows per group. This multiplies row count but not lookup cost, because the index still jumps straight to one group.
11.3 Practical Advice
Leave the standard indexes in place, enable Joomla's caching, and do not query #__associations in a loop. If you need many items' associations at once, gather the ids first and read them in as few calls as possible rather than one call per item.
12. Web Services API and Headless
This is a common question for headless and decoupled setups, and the honest answer is that associations are mostly an editorial and runtime concern.
- There is no dedicated public
com_associationsREST endpoint in core Joomla. The Multilingual Associations screen is an admin-side editing tool, not a data API. - You read translated content through the normal component endpoints, one language at a time, for example:
curl -H "X-Joomla-Token: <token>" \
"https://example.test/api/index.php/v1/content/articles/8"
To rebuild the relationships in a headless front-end, the most reliable source is the #__associations table itself: select the rows that share a key within a context, then fetch each item by id through its component endpoint. Treat the table as the source of truth and join on the key.
13. SEO and Metadata
Associations are not only a navigation convenience. They are the single most important multilingual SEO signal Joomla produces.
13.1 hreflang Alternate Links
When Item Associations are on and a page has associations, Joomla adds hreflang alternate links to the page head. These tell Google which URLs are translations of each other, so the right language version appears in the right country's results:
<link rel="alternate" hreflang="en-GB" href="https://example.test/en/about-us">
<link rel="alternate" hreflang="nl-NL" href="https://example.test/nl/over-ons">
<link rel="alternate" hreflang="x-default" href="https://example.test/en/about-us">
The x-default entry points at your default language and is the version shown to visitors whose language you do not target. Joomla adds it automatically.
13.2 No Associations Means No hreflang
If a page has no associations, it gets no alternate links. Search engines then treat your English and Dutch pages as unrelated, and may even read near-duplicate translations as duplicate content. Complete associations are how you avoid that.
13.3 Clean Per-language URLs
The Language Filter plugin gives each language its own URL prefix (/en/, /nl/) from the content language sef code. Associations make sure the switcher links between those clean URLs rather than dumping users at the root, which keeps both visitors and crawlers on coherent paths.
14. Migration Considerations
When you move a site to Joomla 6 from an older version, associations usually survive the database upgrade untouched. Most "lost translation" reports after a migration are configuration problems, not data loss. Walk this checklist before assuming the data is broken.
14.1 What to Check After a Migration
| Check | Why it breaks switching |
|---|---|
| Language Filter plugin enabled | If off, no switcher and no Associations tab appear at all. |
| Item Associations = Yes | If reset to No, every switch jumps to the home page. |
| Item language assignments | Items reset to All drop out of their groups silently. |
| One Default home per language | A missing default home throws a fatal front-end error. |
| Menu associations | Missing menu links push the router onto fallback routes. |
| Third-party association helpers | Extensions must extend AssociationExtensionHelper for the modern API. |
14.2 The Data Is Probably Fine
The #__associations table carries over as-is, so the key groups from your old site still exist. A quick sanity check confirms it:
SELECT context, COUNT(*) AS rows, COUNT(DISTINCT key) AS groups
FROM #__associations
GROUP BY context;
-- healthy: each context shows several rows per group, not one row per group
If a context shows almost as many groups as rows, many groups have only one member, which means associations were created but their partners lost their language assignment during the move. Fix the language fields, and the existing keys link up again.
Back to top15. Common Mistakes and Pitfalls
15.1 No Associations Tab
Symptom: an article's edit screen has no Associations tab.
Fix: enable the Language Filter plugin, make sure you have at least two published content languages, and set the item's Language to a specific language instead of All.
15.2 Switcher Always Goes Home
Symptom: clicking another language always lands on its home page.
Fix: set Item Associations to Yes in the Language Filter plugin, then create the missing translation and associate it with the current page.
15.3 "No Home Menu Item for the Default Language"
Symptom: the front-end shows a fatal error about a missing default home.
Fix: create one Default home menu item per content language, each set to its own language, and associate them together.
15.4 Content Set to "All"
Symptom: an item cannot be associated, or the same text appears in every language.
Fix: use All only for genuinely shared items. Anything you translate must be set to a specific language so it can join an association group.
15.5 Associating Unpublished or Restricted Items
Symptom: the switcher link exists but leads to a 404 or an access-denied page.
Fix: make sure every item in an association group is published and shares a public access level. The helper hides associations the visitor cannot view.
15.6 Editing the Table by Hand
Symptom: hand-inserted rows with a guessed key do not link items.
Fix: let Joomla compute the key through the Associations tab or the component. A mismatched hash silently breaks the group.
16. Best Practices
If you remember only a few things from this article, remember these:
- Set up the foundation first: content languages, the Language Filter plugin, and Item Associations = Yes, before you create content.
- One menu and one Default home per language, all associated. This prevents the most common multilingual error.
- Associate as you translate, using the Multilingual Associations component. An untranslated page is a dead end for the switcher.
- Reserve "All" for truly shared items like a logo or a global module. Everything you translate gets a specific language.
- Trust the tools: never hand-edit
#__associations. Let Joomla compute thekey. - Check the hreflang output on a few pages. If the alternate links are missing, your associations are incomplete, and your multilingual SEO is leaking.
17. Quick Reference
PREREQUISITES Language Filter plugin ON + 2 published content languages
TURN ON Language Filter plugin -> Item Associations: Yes
ASSOCIATE open item -> Associations tab -> pick/create per language
BULK EDIT System -> Manage -> Multilingual Associations (com_associations)
HOME PAGES one Default menu item per language, associated
MODULES assigned by Language; site modules can also be associated
SWITCHER publish mod_languages (Language: All)
ROUTER association finds the id ; router builds the /xx/ URL
DATABASE #__associations (id, context, key) ; same key = one group
CONTEXTS com_content.item, com_menus.item, com_categories.item, ...
PHP CHECK Associations::isEnabled()
PHP READ Associations::getAssociations('com_content','#__content',
'com_content.item', $id)
FRONT-END URLS AssociationHelper::getAssociations($id, 'article')
SEO associations -> hreflang alternate + x-default links
RULE one item per language per group ; one group per item
Back to top18. Summary
Multilingual Associations are the layer that turns a pile of single-language items into a coherent multilingual website. They do not translate your content; they record that your translations belong together, and then Joomla uses that knowledge everywhere:
- For visitors: the language switcher keeps them on the same page instead of dropping them at the home page.
- For editors: the Associations tab and the Multilingual Associations component make linking translations quick.
- For administrators: the Language Filter plugin, per-language menus, and one Default home per language hold the system together.
- For developers: a single
#__associationstable, scoped by context and grouped by a sharedkey, plus theAssociationshelper class, expose everything cleanly. - For SEO: complete associations produce the
hreflangalternate links that tell search engines which pages are translations of one another.
Once you understand the prerequisites, the Associations tab, the shared key in the database, and the role of the Language Filter plugin, you can build and debug a multilingual Joomla site with confidence.
If your language switcher keeps sending visitors to the home page, or your translated pages are missing their hreflang tags, the cause almost always lives in this layer. It pays to get associations right early, because they quietly decide whether your multilingual site feels like one website or several disconnected ones.


Joomla specialist and Linux admin for fast, secure and scalable websites.


