Elgg master Documentation

Elgg (pronunciation) is a rapid development framework with built-in social features. It is a great fit for building any app where users log in and share information.

It has been used to build all kinds of social apps:

  • open networks (similar to Facebook)
  • topical (like the Elgg Community)
  • private intranets
  • dating
  • educational
  • company blog

This is the canonical documentation for the Elgg project.

Getting Started

Discover if Elgg is right for your community.


Showcases: https://elgg.org/showcase

For developers

  • Permissive license
  • Theme framework
  • Internationalization
  • Templating engine
  • Widgets framework
  • Plugin APIs
  • Social graph
  • Web services API
  • jQuery-based JS framework
  • Session management
  • Custom URL routing

For admins

  • User profiles and avatars
  • Fine-grained access control lists
  • Friends and friends lists (ala G+ circles)
  • Responsive, mobile-friendly design
  • RSS support
  • Activity stream
  • Plugins for common content types like blogs, bookmarks, files, microblogging, private messages, documents, message boards, discussion
  • User authentication and administration

If you need more functionality than what Elgg offers out-of-the-box there are a couple of options:

  • Add more by installing plugins - for example, blogs, forums, social bookmarks
  • Develop your own features via plugins
  • Hire someone to do the above for you

Bundled plugins

Elgg comes with a set of plugins. These provide the basic functionality for your social network.


A weblog, or blog, is arguably one of the fundamental DNA pieces of most types of social networking site. The simplest form of personal publishing, it allows for text-based notes to be published in reverse-chronological order. Commenting is also an important part of blogging, turning an individual act of publishing into a conversation.

Elgg’s blog expands this model by providing per-entry access controls and cross-blog tagging. You can control exactly who can see each individual entry, as well as find other entries that people have written on similar topics. You can also see entries written by your friends (that you have access to).


A typical Elgg dashboard

A typical Elgg dashboard

The dashboard is bundled with both the full and core-only Elgg packages. This is a users portal to activity that is important to them both from within the site and from external sources. Using Elgg’s powerful widget API, it is possible to build widgets that pull out relevant content from within an Elgg powered site as well as grab information from third party sources such as Twitter or Flickr (providing those widgets exist). A users dashboard is not the same as their profile, whereas the profile is for consumption by others, the dashboard is a space for users to use for their own needs.


For the technically savvy user, system diagnostics enables you to quickly evaluate the server environment, Elgg code, and plugins of an Elgg install. Diagnostics is a core system plugin that comes turned on by default with Elgg. To download the diagnostics file, follow the steps below. The file is a dump of all sorts of useful information.

To use:

  • Log in as Administrator
  • Go to Administration -> Administer -> Utilities ->System Diagnostics
  • Click ‘Download’

System diagnostics dump file contents:

  • List of all Elgg files along with a hash for each file
  • List of all the plugins
  • PHP superglobals
  • PHP settings
  • Apache settings
  • Elgg CONFIG values
    • language strings
    • site settings
    • database settings
    • plugin hooks
    • actions
    • views
    • page handlers
    • much more

File repository

A file in an Elgg file repository

A file in an Elgg file repository

The file repository allows users to upload any kind of file. As with everything in an Elgg system, you can filter uploaded files by tag and restrict access so that they’re only visible by the people you want them to be. Each file may also have comments attached to it.

There are a number of different uses for this functionality


An Elgg file repository RSS feed automatically doubles as an RSS feed, so you can subscribe to new audio content using programs like iTunes.

Special content

It is possible for other plugins to add to the players available for different content types. It’s possible for a plugin author to embed a viewer for Word documents, for example.

Note for developers

To add a special content type player, create a plugin with views of the form file/specialcontent/mime/type. For example, to create a special viewer for Word documents, you would create a view called file/specialcontent/application/msword, because application/msword is the MIME-type for Word documents. Within this view, the ElggEntity version of the file will be referenced as $vars['entity']. Therefore, the URL of the downloadable file is:

<?php echo $vars['url']; ?>action/file/download?file_guid=<?php echo $vars['entity']->getGUID(); ?>

Using this, it should be possible to develop most types of embeddable viewers.


A typical group profile

A typical group profile

Once you have found others with similar interests - or perhaps you are part of a research groups or a course/class - you may want to have a more structured setting to share content and discuss ideas. This is where Elgg’s powerful group building can be used. You can create and moderate as many groups as you like

  • You can keep all group activity private to the group or you can use the ‘make public’ option to disseminate work to the wider public.
  • Each group produces granular RSS feeds, so it is easy to follow group developments
  • Each group has its own URL and profile
  • Each group comes with a File repository, forum, pages and messageboard


A sample messageboard placed on the profile

A sample messageboard placed on the profile

The messageboard - similar to ‘The Wall’ in Facebook or a comment wall in other networks is a plugin that lets users put a messageboard widget on their profile. Other users can then post messages that will appear on the messageboard. You can then reply directly to any message and view the history between yourself and the person posting the message.


Message notification

Message notification

Private messaging can be sent to users by clicking on their avatar or profile link, providing you have permission. Then, using the built in WYSIWYG editor, it is possible to format the message. Each user has their own inbox and sentbox. It is possible to be notified via email of new messages.

When users first login, they will be notified about any new message by the messages notification mechanism in their top toolbar.


An Elgg Page

An Elgg Page

The pages plugin allows you to save and store hierarchically-organized pages of text, and restrict both reading and writing privileges to them. This means that you can collaboratively create a set of documents with a loose collection of people, participate in a writing process with a formal group, or simply use the functionality to write a document that only you can see, and only choose to share it once it’s done. The easy navigation menu allows you to see the whole document structure from any page. You can create as many of these structures as you like; each individual page has its own access controls, so you can reveal portions of the structure while keeping others hidden. In keeping with all other elements in Elgg, you can add comments on a page, or search for pages by tag.


Pages really come into their own in two areas, firstly as a way for users to build up things such as a resume, reflective documentation and so on. The second thing is in the area of collaboration, especially when in the context of groups. With the powerful access controls on both read and write, this plugin is ideal for collaborative document creation.


Developers should note that there are actually 2 types of pages:

  1. Top-level pages (with subtype page_top)
  2. Normal pages (with subtype page)


An Elgg profile

An Elgg profile

The profile plugin is bundled with both the full and core-only Elgg packages. The intention is that it can be disabled and replaced with another profile plugin if you wish. It provides a number of pieces of functionality which many consider fundamental to the concept of a social networking site, and is unique within the plugins because the profile icon it defines is referenced as standard from all over the system.

User details

This provides information about a user, which is configurable from within the plugin’s start.php file. You can change the available profile fields form the admin panel. Each profile field has its own access restriction, so users can choose exactly who can see each individual element. Some of the fields contain tags (for example skills) limiting access to a field will also limit who can find you by that tag.

User avatar
The Elgg context menu

The Elgg context menu

The user avatar represents a user (or a group) throughout the site. By default, this includes a context-sensitive menu that allows you to perform actions on the user it belongs to wherever you see their avatar. For example, you can add them as a friend, send an internal message, and more. Each plugin can add to this context menu, so its full contents will vary depending on the functionality active in the current Elgg site.

Notes for developers
Using a different profile icon
To replace the profile icon, or provide more content, extend the icon/user/default view.
Adding to the context menu

The context menu can be expanded by registering a plugin hook for ‘register’ ‘menu:user_hover’, the following sections have special meaning:

  • default for non-active links (eg to read a blog)
  • admin for links accessible by administrators only

In each case, the user in question will be passed as $params['entity'].

The Wire

Elgg wire plugin “The Wire” is Twitter-style microblogging plugin that allows users to post notes to the wire.

The following plugins are also bundled with Elgg, but are not (yet) documented

  • activity
  • bookmarks
  • ckeditor
  • custom_index
  • developers
  • discussions
  • embed
  • externalpages
  • friends
  • friends_collections
  • garbagecollector
  • invitefriends
  • legacy_urls
  • likes
  • logbrowser
  • logrotate
  • members
  • notifications
  • reportedcontent
  • search
  • site_notifications
  • tagcloud
  • twitter_api
  • uservalidationbyemail
  • web_services


MIT or GPLv2

A full Elgg package that includes the framework and a core set of plugins is available under version 2 of the GNU General Public License (GPLv2). We also make the framework (without the plugins) available under the MIT license.


The following answers are provided as a convenience to you; they are not legal counsel. Consult with a lawyer to be sure about the answers to these questions. The Elgg Foundation cannot be held responsible for decisions you make based on what you read on this page.

For questions not answered here, please refer to the official FAQ for the GPLv2.

How much does Elgg cost?

Elgg is free to download, install, and use. If you’d like to donate, we do appreciate our financial supporters!

Can I modify the source code?

Yes, but in general we recommend you make your modifications as plugins so that when a new version of Elgg is released, the upgrade process is as painless as possible.

Can I charge my users membership fees?


If I modify Elgg, do I have to make the changes available?

No, if you are using Elgg to provide a service, you do not have to make the source available. If you distribute a modified version of Elgg, then you must include the source code for the changes.

If I use Elgg to host a network, does The Elgg Foundation have any rights over my network?


What’s the difference between the MIT and GPL versions?

Plugins are not included with the MIT version.

You can distribute a commercial product based on Elgg using the MIT version without making your modifications available.

With the GPL licensed version, you have to include make your modifications of the framework public if you redistribute the framework.

Why are plugins missing from the MIT version?

The plugins were developed under the GPL license, so they cannot be released under an MIT license. Also, some plugins include external dependencies that are not compatible with the MIT license.

May I distribute a plugin for Elgg under a commercial license?

We believe you can, since plugins typically depend only the core framework and the framework is available under the MIT license. That said, we really recommend you consult with a lawyer on this particular issue to be absolutely sure.

Note that plugins released via the community site repository must be licensed under a GPLv2-compatible license. They do not necessarily have to be GPLv2, just compatible (like MIT).

Can we build our own tool that uses Elgg and sell that tool to our clients?

Yes, but then your clients will be free to redistribute that tool under the terms of the GPLv2.


Get your own instance of Elgg running in no time.


  • MySQL 5.5.3+ (5.0.0+ if upgrading an existing installation)

  • PHP 5.6+ with the following extensions:

    • GD (for graphics processing)
    • Multibyte String support (for i18n)
    • Proper configuration and ability to send email through an MTA
  • Web server with support for URL rewriting

Official support is provided for the following configurations:

  • Apache server
    • Apache with the rewrite module enabled
    • PHP running as an Apache module
  • Nginx server
    • Nginx with PHP-FPM using FastCGI

By “official support”, we mean that:

  • Most development and testing is performed with these configurations
  • Much of the installation documentation is written assuming Apache or Nginx is used
  • Priority on bug reports is given to Apache and Nginx users if the bug is web server specific (but those are rare).
Browser support policy

Feature branches support the latest 2 versions of all major browsers as were available at the time of the first stable release on that branch.

Bugfix release will not alter browser support, even if a new version of the browser has since been released.

Major browsers here means all of the following, plus their mobile counterparts:

  • Android Browser
  • Chrome
  • Firefox
  • IE
  • Safari

“Support” may mean that we take advantage of newer, unimplemented technologies but provide a JavaScript polyfill for the browsers that need it.

You may find that Elgg happens to work on unsupported browsers, but compatibility may break at any time, even during a bugfix release.


Upload Elgg

With Composer (recommended if comfortable with CLI):

cd /path/to/wwwroot/
composer self-update
composer global require "fxp/composer-asset-plugin:~1.1.4"
composer create-project elgg/starter-project:dev-master .
composer install
composer install # 2nd call is currently required

From pre-packaged zip (recommended if not comfortable with CLI):

  • Download the latest version of Elgg
  • Upload the ZIP file with an FTP client to your server
  • Unzip the files in your domain’s document root.
Create a data folder

Elgg needs a special folder to store uploaded files including profile icons and photos. You will need to create this directory.


For security reasons, this folder MUST be stored outside of your document root. If you created it under /www/ or /public_html/, you’re doing it wrong.

Once this folder has been created, you’ll need to make sure the web server Elgg is running on has permission to write to and create directories in it. This shouldn’t be a problem on Windows-based servers, but if your server runs Linux, Mac OS X or a UNIX variant, you’ll need to set the permissions on the directory.

If you are using a graphical FTP client to upload files, you can usually set permissions by right clicking on the folder and selecting ‘properties’ or ‘Get Info’.


Directories must be executable to be read and written to. The suggested permissions depend upon the exact server and user configuration. If the data directory is owned by the web server user, the recommended permissions are 770.

Setting your data directory to 777 will work, but it is insecure and is not recommended. If you are unsure how to correctly set permissions, contact your host for more information.

Create a MySQL database

Using your database administration tool of choice (if you’re unsure about this, ask your system administrator), create a new MySQL database for Elgg. You can create a MySQL database with any of the following tools:

Make sure you add a user to the database with all privileges and record the database name, username and password. You will need this information when installing Elgg.

Set up Cron

Elgg uses timed requests to your site to perform background tasks like sending notifications or performing database cleanup jobs. You need to configure the cron to be able to use those kind of features.

Visit your Elgg site

Once you’ve performed these steps, visit your Elgg site in your web browser. Elgg will take you through the rest of the installation process from there. The first account that you create at the end of the installation process will be an administrator account.

A note on settings.php and .htaccess

The Elgg installer will try to create two files for you:

  • elgg-config/settings.php, which contains local environment configuration for your installation
  • .htaccess, which allows Elgg to generate dynamic URLs

If these files can’t be automatically generated, for example because the web server doesn’t have write permissions in the directories, Elgg will tell you how to create them. You could also temporarily change the permissions on the root directory and the engine directory. Set the permissions on those two directories so that the web server can write those two files, complete the install process, and them change the permissions back to their original settings. If, for some reason, this won’t work, you will need to:

  • In elgg-config/, copy settings.example.php to settings.php, open it up in a text editor and fill in your database details
  • On Apache server, copy install/config/htaccess.dist to .htaccess
  • On Nginx server copy install/config/nginx.dist to /etc/nginx/sites-enabled and adjust it’s contents


Help! I’m having trouble installing Elgg


  • Recheck that your server meets the technical requirements for Elgg.
  • Follow the environment-specific instructions if need be
  • Have you verified that mod_rewrite is being loaded?
  • Is the mysql apache being loaded?

Keep notes on steps that you take to fix the install. Sometimes changing some setting or file to try to fix a problem may cause some other problem later on. If you need to start over, just delete all the files, drop your database, and begin again.

I can’t save my settings on installation (I get a 404 error when saving settings)

Elgg relies on the mod_rewrite Apache extension in order to simulate certain URLs. For example, whenever you perform an action in Elgg, or when you visit a user’s profile, the URL is translated by the server into something Elgg understands internally. This is done using rules defined in an .htaccess file, which is Apache’s standard way of defining extra configuration for a site.

This error suggests that the mod_rewrite rules aren’t being picked up correctly. This may be for several reasons. If you’re not comfortable implementing the solutions provided below, we strongly recommend that you contact your system administrator or technical support and forward this page to them.

The .htaccess, if not generated automatically (that happens when you have problem with mod_rewrite), you can create it by renaming install/config/htaccess.dist file you find with elgg package to .htaccess. Also if you find a .htaccess file inside the installation path, but you are still getting 404 error, make sure the contents of .htaccess are same as that of install/config/htaccess.dist.

``mod_rewrite`` isn’t installed.

Check your httpd.conf to make sure that this module is being loaded by Apache. You may have to restart Apache to get it to pick up any changes in configuration. You can also use PHP info to check to see if the module is being loaded.

The rules in ``.htaccess`` aren’t being obeyed.

In your virtual host configuration settings (which may be contained within httpd.conf), change the AllowOverride setting so that it reads:

AllowOverride all

This will tell Apache to pick up the mod_rewrite rules from .htaccess.

Elgg is not installed in the root of your web directory (ex: http://example.org/elgg/ instead of http://example.org/)

The install script redirects me to “action” when it should be “actions”

This is a problem with your mod_rewrite setup. DO NOT, REPEAT, DO NOT change any directory names!

I installed in a subdirectory and my install action isn’t working!

If you installed Elgg so that it is reached with an address like http://example.org/mysite/ rather than http://example.org/, there is a small chance that the rewrite rules in .htaccess will not be processed correctly. This is usually due to using an alias with Apache. You may need to give mod_rewrite a pointer to where your Elgg installation is.

  • Open up .htaccess in a text editor
  • Where prompted, add a line like RewriteBase /path/to/your/elgg/installation/ (Don’t forget the trailing slash)
  • Save the file and refresh your browser.

Please note that the path you are using is the web path, minus the host.

For example, if you reach your elgg install at http://example.org/elgg/, you would set the base like this:

RewriteBase /elgg/

Please note that installing in a subdirectory does not require using RewriteBase. There are only some rare circumstances when it is needed due to the set up of the server.

I did everything! mod_rewrite is working fine, but still the 404 error

Maybe there is a problem with the file .htaccess. Sometimes the elgg install routine is unable to create one and unable to tell you that. If you are on this point and tried everything that is written above:

  • check if it is really the elgg-created .htaccess (not only a dummy provided from the server provider)
  • if it is not the elgg provided htaccess file, use the htaccess_dist (rename it to .htaccess)
I get an error message that the rewrite test failed after the requirements check page

I get the following messages after the requirements check step (step 2) of the install:

We think your server is running the Apache web server.

The rewrite test failed and the most likely cause is that AllowOverride is not set to All for Elgg’s directory. This prevents Apache from processing the .htaccess file which contains the rewrite rules.

A less likely cause is Apache is configured with an alias for your Elgg directory and you need to set the RewriteBase in your .htaccess. There are further instructions in the .htaccess file in your Elgg directory.

After this error, everinteraction with the web interface results in a error 500 (Internal Server Error)

This is likely caused by not loading the “filter module by un-commenting the

#LoadModule filter_module modules/mod_filter.so

line in the “httpd.conf” file.

the Apache “error.log” file will contain an entry similar to:

... .htaccess: Invalid command ‘AddOutputFilterByType’, perhaps misspelled or defined by a module not included in the server configuration
There is a white page after I submit my database settings

Check that the Apache mysql module is installed and is being loaded.

I’m getting a 404 error with a really long url

If you see a 404 error during the install or on the creation of the first user with a url like: http://example.com/homepages/26/d147515119/htdocs/elgg/action/register that means your site url is incorrect in your sites_entity table in your database. This was set by you on the second page of the install. Elgg tries to guess the correct value but has difficulty with shared hosting sites. Use phpMyAdmin to edit this value to the correct base url.

I am having trouble setting my data path

This is highly server specific so it is difficult to give specific advice. If you have created a directory for uploading data, make sure your http server can access it. The easiest (but least secure) way to do this is give it permissions 777. It is better to give the web server ownership of the directory and limit the permissions.

The top cause of this issue is PHP configured to prevent access to most directories using open_basedir. You may want to check with your hosting provider on this.

Make sure the path is correct and ends with a /. You can check the path in your database in the config table.

If you only have ftp access to your server and created a directory but do not know the path of it, you might be able to figure it out from the www file path set in your config database table. Asking for help from your hosting help team is recommended at this stage.

I can’t validate my admin account because I don’t have an email server!

While it’s true that normal accounts (aside from those created from the admin panel) require their email address to be authenticated before they can log in, the admin account does not.

Once you have registered your first account you will be able to log in using the credentials you have provided!

I have tried all of these suggestions and I still cannot install Elgg

It is possible that during the process of debugging your install you have broken something else. Try doing a clean install:

  • drop your elgg database
  • delete your data directory
  • delete the Elgg source files
  • start over

If that fails, seek the help of the Elgg community. Be sure to mention what version of Elgg you are installing, details of your server platform, and any error messages that you may have received including ones in the error log of your server.

Developer Overview

This is a quick developer introduction to Elgg. It covers the basic approach to working with Elgg as a framework, and mentions some of the terms and technologies used.

See the Developer Guides for tutorials or the Design Docs for in-depth discussion on design.

Database and Persistence

Elgg uses MySQL 5.5 or higher for data persistence, and maps database values into Entities (a representation of an atomic unit of information) and Extenders (additional information and descriptions about Entities). Elgg supports additional information such as relationships between Entities, activity streams, and various types of settings.


Plugins change the behavior or appearance of Elgg by overriding views, or by handling events and plugin hooks. All changes to an Elgg site should be implemented through plugins to ensure upgrading core is easy.


Actions are the primary way users interact with an Elgg site. Actions are registered by plugins.

Events and Plugin Hooks

Events and Plugin Hooks are used in Elgg Plugins to interact with the Elgg engine under certain circumstances. Events and hooks are triggered at strategic times throughout Elgg’s boot and execution process, and allows plugins to modify or cancel the default behavior.


Views are the primary presentation layer for Elgg. Views can be overridden or extended by Plugins. Views are categories into a Viewtype, which hints at what sort of output should be expected by the view.


Elgg uses an AMD-compatible JavaScript system provided by require.js. Bundled with Elgg are jQuery 1.11.0, jQuery UI 1.10.4, jQuery Form v20140304, jQuery jeditable, and jQuery UI Autocomplete.

Plugins can load their own JS libs.


Elgg’s interface supports multiple languages, and uses Transifex for translation.


Elgg uses two caches to improve performance: a system cache and SimpleCache.

Administrator Guides

Best practices for effectively managing an Elgg-based site.

Getting Started

You have installed Elgg and worked through any potential initial issues. What now? Here are some suggestions on how to to familiarize yourself with Elgg.

Focus first on core functionality

When you’re new to Elgg, it’s best to explore the stock features in core and its bundled plugins before installing any third party plugins. It’s tempting install every interesting plugin from the community site, but exploring the core features builds a familiarity with Elgg’s expected behavior, and prevents introducing any confusing bugs from third party plugin into your new Elgg network.

Elgg installs with a basic set of social network plugins activated: blogs, social bookmarking, files, groups, likes, message boards, wiki-like pages, user profiles, and microblogging. To change the plugins that are activated, log in as an admin user, then use the topbar to browse to Administration, then to Plugins on the right sidebar.


The user you create during installation is an admin user.

Create test users

Users can be created two ways in stock Elgg:

  1. Complete the signup process using a different email address and username. (Logout first or use a different browser!)
  2. Add a user through the Admin section by browsing to Administration -> Users -> Add New User.


Users that self-register must validate their account through email before they can log in. Users that an admin creates are already validated.

Explore user functionality

Use your test users to create blogs, add widgets to your profile or dashboard, post to the Wire (microblogging), and create pages (wiki-like page creation). Investigate the Settings on the topbar. This is where a user sets notification settings and configures tools (which will be blank because none of the default plugins add controls here).

Explore admin functionality

All of the admin controls are found by clicking Administration in the topbar. The has a dashboard with a widget that explains the various sections. Change options in the Configure menu to change how Elgg looks and acts.

Extending Elgg

After exploring what Elgg can do out of the box, install some themes and plugins. You can find many plugins and themes at the community site that have been developed by third parties. These plugins do everything from changing language strings, to adding chat, to completely redesigning Elgg’s interface. Because these plugins are not official, be certain to check the comments to make sure you only install well-written plugins by high quality developers.

Upgrading Elgg

Switch a live site to a new version of Elgg.

If you’ve written custom plugins, you should also read the developer guides for information on upgrading plugin code for the latest version of Elgg.


  • Back up your database and code
  • Mind any version-specific comments below
  • Upgrade only one minor version at a time (1.6 => 1.7, then 1.7 => 1.8)
  • Try out the new version on a test site before doing an upgrade
  • Report any problems in plugins to the plugin authors
  • If you are a plugin author you can report any backwards-compatibility issues to GitHub

Basic instructions

  1. Back up your database, data directory, and code
  2. Download the new version of Elgg from http://elgg.org
  3. Update the files
    • If doing a patch upgrade (1.9.x), overwrite your existing files with the new version of Elgg
    • If doing a minor upgrade (1.x), replace the existing core files completely
  4. Merge any new changes to the rewrite rules
    • For Apache from install/config/htaccess.dist into .htaccess
    • For Nginx from install/config/nginx.dist into your server configuration (usually inside /etc/nginx/sites-enabled)
  5. Merge any new changes from settings.example.php into settings.php
  6. Visit http://your-elgg-site.com/upgrade.php


Any modifications should have been written within plugins, so that they are not lost on overwriting. If this is not the case, take care to maintain your modifications.

From 2.3 to 3.0

Update settings.php

On your working 2.3 installation:

  1. Open your settings.php file.
  2. In the browser, open the site’s Advanced Settings page in the admin area
  3. Copy the data directory path into your settings file as $CONFIG->dataroot.
  4. Copy the site URL into your settings file as $CONFIG->wwwroot.


Elgg 3.0 will not operate at all without $CONFIG->dataroot set in settings.php.

Update .htaccess

Find the line:

RewriteRule ^(.*)$ index.php?__elgg_uri=$1 [QSA,L]

And replace it with:

RewriteRule ^(.*)$ index.php [QSA,L]
Removed / changed language keys
  • The language keys related to comment notifications have changed. Check the generic_comment:notification:owner: language keys
New MySQL schema features are not applied

New 3.0 installations require MySQL 5.5.3 and use the utf8mb4 character set and LONGTEXT content columns (notably allowing storing longer content and extended characters like emoji).

The upgrade does not make these changes. We will make available instructions to manually upgrade the database and a small change that needs to be made in the settings.php file.

From 2.2 to 2.3

PHP Version

PHP 5.5 has reached end of life in July 2016. To ensure that Elgg sites are secure, we now require PHP 5.6 for new installations.

Existing installations can continue using PHP 5.5 until Elgg 3.0.

In order to upgrade Elgg to 2.3 using composer while using PHP 5.5, you may need to use --ignore-platform-reqs flag.

  • PHPUnit bootstrap is deprecated by composer autoloader: Tests should no longer bootstrap themselves using /engine/tests/phpunit/bootstrap.php. Instead, tests should extend \Elgg\TestCase.
  • Some core files now sniff if PHPUNIT_ELGG_TESTING_APPLICATION constant is set to determine whether Elgg is being bootstrapped for PHPUnit tests. phpunit.xml configuration needs to updated to include this constant definition.
  • PHPUnit bootstrap no longer sets global $CONFIG. Tests should use _elgg_services()->config instead.
  • Core and tests no longer use private global values in $_ELGG->view_path and $_ELGG->allowed_ajax_views
  • The database GUID columns need to be aligned. In the admin section an upgrade is available to handle this. Please make sure you have a backup available

From 2.3 to 3.0

Data removal

Be aware the 3.0 upgrade process will remove any remaining “legacy” password hashes. This will affect users who have never logged in under an Elgg 1.10 or later system. These users will be politely asked to reset their password.

Deprecations in 2.x


User avatars are now served via serve-file handler. Plugins should start using elgg_get_inline_url() and note that:

  • /avatar/view page handler and resource view have been deprecated
  • /mod/profile/icondirect.php file has been deprecated
  • profile_set_icon_url() is no longer registered as a callback for "entity:icon:url","user" plugin hook

Group avatars are now served via serve-file handler. Plugins should start using elgg_get_inline_url() and note that:

  • groupicon page handler (groups_icon_handler()) has been deprecated
  • /mod/groups/icon.php file has been deprecated

File entity thumbs and downloads are now served via serve-file handler. Plugins should start using elgg_get_inline_url() and elgg_get_download_url() and note that:

  • file/download page handler and resource view have been deprecated
  • mod/file/thumbnail.php file has been deprecated
  • Several views have been updated to use new download URLs, including:
    • mod/file/views/default/file/specialcontent/audio/default.php
    • mod/file/views/default/file/specialcontent/image/default.php
    • mod/file/views/default/resources/file/view.php
    • mod/file/views/rss/file/enclosure.php

From 1.x to 2.0

Removed plugins

The following plugins are no longer bundled with Elgg core:

IE-specific workarounds have been dropped

Several views (css/ie, css/ie7, css/ie8, etc.) as well as conditional comments have been discarded now that IE10+ browsers are more standards-compliant. If you need browser support farther back than that, you will need to find or build a plugin that introduces its own compatibility layer or polyfills.

Update your webserver config

URL paths like cache/* and rewrite.php now use the main front controller script. You must remove these rewrite rules from your webserver config (e.g. .htaccess).

Also remove the rules for paths like export/*; these endpoints have been removed.

Settings location

After upgrading, move your settings.php file from engine/ to elgg-config/.

From 1.10 to 1.11

Breaking changes

In versions 1.9 and 1.10, names and values for metadata and annotations were not correctly trimmed for whitespace. Elgg 1.11 correctly trims these strings and updates the database to correct existing strings. If your plugin uses metadata or annotations with leading or trailing whitespace, you will need to update the plugin to trim the names and values. This is especially important if you are using custom SQL clauses or have hard-coded metastring IDs, since the update might change metastring IDs.

From 1.8 to 1.9

Elgg 1.9 is a much lighter upgrade than 1.8 was.

Breaking changes

Plugins and themes written for 1.8 are expected to be compatible with 1.9 except as it pertains to comments, discussion replies, and notifications. Please report any backwards compatibility issues besides those just listed.

Upgrade steps

There are several data migrations involved, so it is especially important that you back up your database and data directory before performing the upgrade.

Download the new version and copy these files from the existing 1.8 site:

  • .htaccess
  • engine/settings.php
  • any 3rd-party plugin folders in the mod directory

Then replace the old installation directory with the new one. This way you are guaranteed to get rid of obsolete files which might cause problems if left behind.

Follow the basic instructions listed above.

After you’ve visited upgrade.php, go to the admin area of your site. You should see a notification that you have pending upgrades. Click the link in the notification bar to view and run the upgrades.

The new notifications system delivers messages via a minutely cron handler. If you haven’t done so yet, you will need to install and configure crontab on your server. If cron jobs are already configured, note that the scope of available cron periods may have changed and you may need to update your current crontab to reflect these changes.

Time commitment

Running all of the listed upgrades took about 1 hour and 15 minutes on the Elgg community site which at the time had to migrate:

  • ~75,000 discussion replies
  • ~75,000 data directories

You should take this only as a ballpark estimate for your own upgrade. How long it takes will depend on how large your site is and how powerful your servers are.

From 1.7 to 1.8

Elgg 1.8 is the biggest leap forward in the development of Elgg since version 1.0. As such, there is more work to update core and plugins than with previous upgrades.

Updating core

Delete the following core directories (same level as _graphics and engine):

  • _css
  • account
  • admin
  • dashboard
  • entities
  • friends
  • search
  • settings
  • simplecache
  • views


If you do not delete these directories before an upgrade, you will have problems!


Plugins can modify the behavior of and add new features to Elgg.

Where to get plugins

Plugins can be obtained from:

If no existing plugins meet your needs, you can hire a developer or create your own.

The Elgg Community

Finding Plugins
Look for particular plugin authors

The quality of plugins varies substantially. If you find a plugin that works well on your site, you can check what else that plugin author has developed by clicking on their name when viewing a plugin.

Evaluating Plugins
Look at the comments and ratings

Before downloading and using a plugin, it is always a good idea to read through the comments that others have left. If you see people complaining that the plugin does not work or makes their site unstable, you probably want to stay away from that plugin. The caveat to that is that sometimes users ignore installation instructions or incorrectly install a plugin and then leave negative feedback. Further, some plugin authors have chosen to not allow comments.

Install on a test site

If you are trying out a plugin for the first time, it is a bad idea to install it on your production site. You should maintain a separate test site for evaluating plugins. It is a good idea to slowly roll out new plugins to your production site even after they pass your evaluation on your test site. This enables you to isolate problems introduced by a new plugin.

Types of plugins


Themes are plugins that modify the look-and-feel of your site. They generally include stylesheets, client-side scripts and views that alter the default presentation and behavior of Elgg.

Language Packs

Language packs are plugins that provide support for other languages.

Language packs can extend and include translations for language strings found in the core, core plugins and/or third-party plugins.

Some of the language packs are already included in the core, and can be found in languages directory off Elgg’s root directory. Individual plugins tend to include their translations under the languages directory within the plugin’s root.

This structure makes it easy to create new language packs that supercede existing language strings or add support for new languages.


All plugins reside in the mod directory of your Elgg installation.

To install a new plugin:
  • extract (unzip) contents of the plugin distribution package
  • copy/FTP the extracted folder into the mod directory of your Elgg installation, making sure that manifest.xml is directly under the plugin directory (e.g. if you were to install a plugin called my_elgg_plugin, plugin’s manifest would need to be found at mod/my_elgg_plugin/manifest.xml)
  • activate the plugin from your admin panel
To activate a plugin:
  • Log in to your Elgg site with your administrator account
  • Go to Administration -> Configure -> Plugins
  • Find your plugin in the list of installed plugins and click on the ‘enable’ button.

Plugin order

Plugins are loaded according to the order they are listed on the Plugins page. The initial ordering after an install is more or less random. As more plugins are added by an administrator, they are placed at the bottom of the list.

Some general rules for ordering plugins:

  • A theme plugin should be last or at least near the bottom
  • A plugin that modifies the behavior of another plugin should be lower in the plugin list

Pre-1.8 notes

In Elgg 1.7 and below, the interface for managing installed plugins is located at Administration -> Tool Administration.


Make your site run as smoothly and responsively as possible.

Can Elgg scale to X million users?

People often ask whether Elgg can scale to large installations.

First, we might stop and ask, “where are you planning to get all those users?” Seriously, though, this is a really interesting problem. Making Elgg scale is, if anything, an issue of technical engineering. It’s interesting but more or less a solved problem. Computer science doesn’t work differently for Elgg than for Google, for example. Getting millions of users? That’s like the Holy Grail of the entire tech industry.

Second, as with most things in life, the answer is “it depends”:

  • How active are your users?
  • What hardware is Elgg running on?
  • Are your plugins behaving well?

Improving the efficiency of the Elgg engine is an ongoing project, although there are limits to the amount that any script can do.

If you are serious about scalability you will probably want to look at a number of things yourself.

Measure first

There is no point in throwing resources at a problem if you don’t know:

  • what the problem is
  • what resources the problem needs
  • where those resources are needed

Invest in some kind of profiling to tell you where your bottleneck is, especially if you’re considering throwing significant money at a problem.

Tune MySQL

Elgg makes extensive use of the back end database, making many trips on each pageload. This is perfectly normal and a well configured database server will be able to cope with thousands of requests per second.

Here are some configuration tips that might help:

  • Make sure that MySQL is configured to use an appropriate my.cnf for the size of your website.
  • Increase the amount of memory available to PHP and MySQL (you will have to increase the amount of memory available to the php process in any case)

Enable caching

Generally, if a program is slow, that is because it is repeatedly performing an expensive computation or operation. Caching allows the system to avoid doing that work over and over again by using memory to store the results so that you can skip all the work on subsequent requests. Below we discuss several generally-available caching solutions relevant to Elgg.


By default, views are cached in the Elgg data directory for a given period of time. This removes the need for a view to be regenerated on every page load.

This can be disabled by setting $CONFIG->simplecache_enabled = false; For best performance, make sure this value is set to true.

This does lead to artifacts during development if you are editing themes in your plugin as the cached version will be used in preference to the one provided by your plugin.

The simple cache can be disabled via the administration menu. It is recommended that you do this on your development platform if you are writing Elgg plugins.

This cache is automatically flushed when a plugin is enabled, disabled or reordered, or when upgrade.php is executed.

For best performance, you can also create a symlink from /cache/ in your www root dir to the /views_simplecache/ directory in the data directory you configured when you installed Elgg:

cd /path/to/wwwroot/
ln -s /path/to/dataroot/views_simplecache/ cache

If your webserver supports following symlinks, this will serve files straight off disk without booting up PHP each time.

For security reasons, some webservers (e.g. Apache in version 2.4) might follow the symlinks by default only if the owner of the symlink source and target match. If the cache symlink fails to work on your server, you can change the owner of the cache symlink itself (and not the /views_simplecache/ directory) with

cd /path/to/wwwroot/
chown -h wwwrun:www cache

In this example it’s assumed that the /views_simplecache/ directory in the data directory is owned by the wwwrun account that belongs to the www group. If this is not the case on your server, you have to modify the chown command accordingly.

System cache

The location of views are cached so that they do not have to be discovered (profiling indicated that page load took a non-linear amount of time the more plugins were enabled due to view discovery). Elgg also caches information like the language mapping and class map.

This can be disabled by setting $CONFIG->system_cache_enabled = false; For best performance, make sure this value is set to true.

This is currently stored in files in your dataroot (although later versions of Elgg may use memcache). As with the simple cache it is flushed when a plugin is enabled, disabled or reordered, or when upgrade.php is executed.

The system cache can be disabled via the administration menu, and it is recommended that you do this on your development platform if you are writing Elgg plugins.

Boot cache (experimental)

Elgg has the ability to cache numerous resources created and fetched during the boot process. To enable this cache you must set a TTL in your settings.php file: $CONFIG->boot_cache_ttl = 10;

A small TTL is recommended because it brings all the benefits of caching under load while reducing the harm if Elgg’s cache invalidation strategy should miss something.

Database query cache

For the lifetime of a given page’s execution, a cache of all SELECT queries is kept. This means that for a given page load a given select query will only ever go out to the database once, even if it is executed multiple times. Any write to the database will flush this cache, so it is advised that on complicated pages you postpone database writes until the end of the page or use the execute_delayed_* functionality. This cache will be automatically cleared at the end of a page load.

You may experience memory problems if you use the Elgg framework as a library in a PHP CLI script. This can be disabled by setting $CONFIG->db_disable_query_cache = true;

Etags and Expires headers

These technologies tell your users’ browsers to cache static assets (CSS, JS, images) locally. Having these enabled greatly reduces server load and improves user-perceived performance.

Use the Firefox yslow plugin or Chrome DevTools Audits to confirm which technologies are currently running on your site.

If the static assets aren’t being cached:
  • Verify that you have these extensions installed and enabled on your host
  • Update your .htaccess file, if you are upgrading from a previous version of Elgg
  • Enable Simplecache, which turns select views into browser-cacheable assets

Memcache is a generic caching technology developed by Brad Fitzpatrick for LiveJournal.



Installation requirements:

  • php5-memcache
  • memcached


Uncomment and populate the following sections in settings.php

$CONFIG->memcache = true;

$CONFIG->memcache_servers = array (
    array('server1', 11211),
    array('server2', 11211)

Optionaly if you run multiple Elgg installations but use ony one Memcache server, you may want to add a namespace prefix. In order to do this, uncomment the following line

$CONFIG->memcache_namespace_prefix = '';

We have had good results by using Squid to cache images for us.

Bytecode caching

There are numerous PHP code caches available on the market. These speed up your site by caching the compiled byte code from your script meaning that your server doesn’t have to compile the PHP code each time it is executed.

Direct file serving

If your server can be configured to support the X-Sendfile or X-Accel headers, you can configure it to be used in settings.php. This allows your web server to directly stream files to the client instead of using PHP’s readfile().


Don’t expect to run a site catering for millions of users on a cheap shared host. You will need to have your own host hardware and access over the configuration, as well as lots of bandwidth and memory available.

Memory, CPU and bandwidth

Due to the nature of caching, all caching solutions will require memory. It is a fairly cheap return to throw memory and CPU at the problem.

On advanced hardware it is likely that bandwidth is going to be your bottleneck before the server itself. Ensure that your host can support the load you are suggesting.


Lastly, take a look at your configuration as there are a few gotchas that can catch people.

For example, out of the box, Apache can handle quite a high load. However, most distros of Linux come with mysql configured for small sites. This can result in Apache processes getting stalled waiting to talk to one very overloaded MySQL process.

Check for poorly-behaved plugins

Plugins can be programmed in a very naive way and this can cause your whole site to feel slow.

Try disabling some plugins to see if that noticeably improves performance. Once you’ve found a likely offender, go to the original plugin author and report your findings.

Use client-rendered HTML

We’ve found that at a certain point, much of the time spent on the server is simply building the HTML of the page with Elgg’s views system.

It’s very difficult to cache the output of templates since they can generally take arbitrary inputs. Instead of trying to cache the HTML output of certain pages or views, the suggestion is to switch to an HTML-based templating system so that the user’s browser can cache the templates themselves. Then have the user’s computer do the work of generating the output by applying JSON data to those templates.

This can be very effective, but has the downside of being significant extra development cost. The Elgg team is looking to integrate this strategy into Elgg directly, since it is so effective especially on pages with repeated or hidden content.


Cron is a program available on Unix-based operating systems that enables users to run commands and scripts at set intervals or at specific times.

Elgg’s cron handler allows administrators and plugin developers to setup jobs that need to be executed at set intervals.

Most common examples of cron jobs in Elgg include:

  • sending out queued notifications
  • rotating the system log in the database
  • collecting garbage in the database (compacting the database by removing entries that are no longer required)

Currently, Elgg supports the following hooks:

  • minute - Run every minute
  • fiveminute - Run every 5 minutes
  • fifteenmin - Run every 15 minutes
  • halfhour - Run every 30 minutes
  • hourly - Run every hour
  • daily - Run every day
  • weekly - Run every week
  • monthly - Run every month
  • yearly - Run every year

How does it work?

Elgg activates its cron handler when particular cron pages are loaded. As an example, loading http://example.com/cron/hourly/ in a web browser activates the hourly hook. To automate this, cron jobs are setup to hit those pages at certain times. This is done by setting up a crontab which is a configuration file that determines what cron jobs do and at what interval.


The crontab needs to specify a script or command that will hit the Elgg cron pages. Two commonly available programs for this are GET and wget. You will need to determine the location of one of these on your server. Your crontab also needs to specify the location of your website.

# Crontab example.
# This file is an example of triggering Elgg cron events. It hits a URL to
# trigger the events. For testing, you can simulate the cronjob by loading the
# URL in a browser.
# See http://learn.elgg.org/en/stable/admin/cron.html for more information

# Location of your site (don't forget the trailing slash!)

# Location of lwp-request

# Make GET request and discard content
GET="$LWPR -m GET -d"

# The crontab
# Don't edit below this line unless you know what you are doing
* * * * * $GET ${ELGG}cron/minute/
*/5 * * * * $GET ${ELGG}cron/fiveminute/
15,30,45,59 * * * * $GET ${ELGG}cron/fifteenmin/
30,59 * * * * $GET ${ELGG}cron/halfhour/
@hourly $GET ${ELGG}cron/hourly/
@daily $GET ${ELGG}cron/daily/
@weekly $GET ${ELGG}cron/weekly/
@monthly $GET ${ELGG}cron/monthly/
@yearly $GET ${ELGG}cron/yearly/

In the above example, change the ELGG and GET variables to match you server setup. If you have SSH access to your Linux servers, type crontab -e and add your crontab configuration. If you already have a crontab configured, you will have to merge Elgg information into it. If you don’t have SSH access, you will have to use a web-based configuration tool. This will vary depending on hosting provider.

If you choose the wget utility, you might want to consider these flags:

  • --output-document or -O to specify the location of the concatenated output file. For example, under Debian: /usr/bin/wget --output-document=/dev/null. If you don’t do that, a new file will be created for each cron page load in the home directory of the cron user.
  • --spider to prevent the cron page from being downloaded.

On Windows servers, there is a number of cron emulators available.

For information on setting up cron jobs using cPanel see cPanel Docs.

In the command field, enter the appropriate link of the cron page. For example, for a weekly cron job, enter the command as http://www.example.com/cron/weekly/.

To see if your cron jobs are running, visit Statistics > Cron in your Elgg admin panel.

Backup and Restore



Shared hosting providers typically don’t provide an automated way to backup your Elgg installation. This article will address a method of accomplishing this task.

In IT there are often many ways to accomplish the same thing. Keep that in mind. This article will explain one method to backup and restore your Elgg installation on a shared hosting provider that uses the CPanel application. However, the ideas presented here can be tailored to other applications as well. The following are typical situations that might require a procedure such as this:

  • Disaster Recovery
  • Moving your Elgg site to a new host
  • Duplicating an installation

Topics covered:

  • Full backups of the Elgg directories and MySQL databases are performed daily (automated)
  • The backups are sent to an off-site location via FTP (automated)
  • The local backups are deleted after successful transfer to the off-site location (automatic)
  • Five days of backups will be maintained (automated)
  • Restoration of data to the new host (manual)

This process was composed with assistance from previous articles in the Elgg documentation wiki.


The following assumptions have been made:

  • The Elgg program directory is /home/userx/public_html
  • The Elgg data directory is /home/userx/elggdata
  • You’ve created a local directory for your backups at /home/userx/sitebackups
  • You have an off-site FTP server to send the backup files to
  • The directory that you will be saving the off-site backups to is /home/usery/sitebackups/
  • You will be restoring the site to a second shared hosting provider in the /home/usery/public_html directory


Be sure to replace userx, usery, http://mynewdomain.com and all passwords with values that reflect your actual installation!

Creating a usable backup - automatically

Customize the backup script

The script that you will use can be found here .

Just copy the script to a text file and name the file with a .pl extension. You can use any text editor to update the file.

Change the following to reflect your directory structure:

$directory_to_backup = '/home/userx/public_html';
$directory_to_backup2 = '/home/userx/elggdata';
$backup_dest_dir = '/home/userx/sitebackups';

Change the following to reflect your database parameters:

$dbhost = 'localhost';
$dbuser = 'userx_elgg';
$dbpwd = 'dbpassword';
$database_names_elgg = 'userx_elgg';

Change the following to reflect your off-site FTP server parameters:

$ftp_host = "FTP HOSTNAME/IP";
$ftp_user = "ftpuser";
$ftp_pwd = "ftppassword";
$ftp_dir = "/";

Save the file with the .pl extension (for the purposes of this article we will name the file: elgg-ftp-backup-script.pl) and upload it to the following directory /home/userx/sitebackups

Be aware that you can turn off FTP and flip a bit in the script so that it does not delete the local backup file in the event that you don’t want to use off-site storage for your backups.

Configure the backup Cron job

Login to your CPanel application and click on the “Cron Jobs” link. In the Common Settings dropdown choose “Once a day” and type the following in the command field /usr/bin/perl /home/userx/sitebackups/elgg-ftp-backup-script.pl

Click on the “Add New Cron Job” button. Daily full backups are now scheduled and will be transferred off-site.

Configure the cleanup Cron job

If you are sending your backups, via FTP, to another shared hosting provider that uses the CPanel application or you’ve turned off FTP altogether you can configure your data retention as follows.

Login to your CPanel application for your FTP site, or locally if you’re not using FTP, and click on the “Cron Jobs” link. In the Common Settings dropdown choose “Once a day” and type the following in the command field find /home/usery/sitebackups/full_* -mtime +4 -exec rm {} \;

The -mtime X parameter will set the number of days to retain backups. All files older than x number of days will be deleted. Click on the “Add New Cron Job” button. You have now configured your backup retention time.

Restoring from backup

Prepare your backup files

The assumption is that you’re restoring your site to another shared hosting provider with CPanel.

When the script backed the files up the original directory structure was maintained in the zip file. We need to do a little cleanup. Perform the following:

  • Download the backup file that you wish to restore from
  • Extract the contents of the backup file
  • Drill down and you will find your site backup and SQL backup. Extract both of these. You will then have:
    • a MySQL dump file with a .sql extension
    • another directory structure with the contents of:
      • /home/userx/public_html
      • /home/userx/elggdata
  • Repackage the contents of the /home/userx/public_html directory as a zip file so that the files are in the root of the zip file
    • The reason for doing this is simple. It’s much more efficient to upload one zip file than it is to ftp the contents of the /home/userx/public_html directory to your new host.
  • Repackage the contents of the /home/userx/elggdata directory as a zip file so that the files are in the root of the zip file

You should now have the following files:

  • the .sql file
  • the zip file with the contents of /home/userx/public_html in the root
  • the zip file with the contents of /home/userx/elggdata in the root
Restore the files

This is written with the assumption that you’re restoring to a different host but maintaining the original directory structure. Perform the following:

  • Login to the CPanel application on the host that you wish to restore the site to and open the File Manager.
  • Navigate to /home/usery/public_html
    • Upload the zip file that contains the /home/userx/public_html files
    • Extract the zip file
      You should now see all of the files in /home/usery/public_html
    • Delete the zip file
  • Navigate to /home/usery/elggdata
    • Upload the zip file that contains the /home/userx/elggdata files
    • Extract the zip file
      You should now see all of the files in /home/usery/elggdata
    • Delete the zip file

Program and data file restoration is complete

Restore the MySQL Database


Again, the assumption here is that you’re restoring your Elgg installation to a second shared hosting provider. Each shared hosting provider prepends the account holder’s name to the databases associated with that account. For example, the username for our primary host is userx so the host will prepend userx_ to give us a database name of userx_elgg. When we restore to our second shared hosting provider we’re doing so with a username of usery so our database name will be usery_elgg. The hosting providers don’t allow you to modify this behavior. So the process here isn’t as simple as just restoring the database from backup to the usery account. However, having said that, it’s not terribly difficult either.

Edit the MySQL backup

Open the .sql file that you extracted from your backup in your favorite text editor. Comment out the following lines with a hash mark:

#CREATE DATABASE /*!32312 IF NOT EXISTS*/ `userx_elgg` /*!40100 DEFAULT CHARACTER SET latin1 */;
#USE `userx_elgg`;

Save the file.

Create the new database

Perform the following:

  • Login to the CPanel application on the new host and click on the “MySQL Databases” icon
    • Fill in the database name and click the “create” button. For our example we are going to stick with elgg which will give us a database name of usery_elgg
    • You can associate an existing user with the new database, but to create a new user you will need to:
      • Go to the “Add New User” section of the “MySQL Databases” page
      • Enter the username and password. For our example we’re going to keep it simple and use elgg once again. This will give us a username of usery_elgg
    • Associate the new user with the new database
      • Go to the “Add User To Database” section of the “MySQL Databases” page. Add the usery_elgg user to the usery_elgg database
      • Select “All Privileges” and click the “Make Changes” button
Restore the production database

Now it’s time to restore the MySQL backup file by importing it into our new database named “usery_elgg”.

  • Login to the CPanel application on the new host and click on the “phpMyAdmin icon
    • Choose the usery_elgg database in the left hand column
    • Click on the “import” tab at the top of the page
    • Browse to the .sql backup on your local computer and select it
    • Click the “Go” button on the bottom right side of the page

You should now see a message stating that the operation was successful

Bringing it all together

The restored elgg installation knows nothing about the new database name, database username, directory structure, etc. That’s what we’re going to address here.

Edit /public_html/elgg-config/settings.php on the new hosting provider to reflect the database information for the database that you just created.

// Database username
$CONFIG->dbuser = 'usery_elgg';

// Database password
$CONFIG->dbpass = 'dbpassword';

// Database name
$CONFIG->dbname = 'usery_elgg';

// Database server
// (For most configurations, you can leave this as 'localhost')
$CONFIG->dbhost = 'localhost';

Upload the settings.php file back to the new host - overwriting the existing file.

Open the phpMyAdmin tool on the new host from the CPanel. Select the usery_elgg database on the left and click the SQL tab on the top of the page. Run the following SQL queries against the usery_elgg database:

Change the installation path

UPDATE `elgg_config` SET `value` = REPLACE(`value`, "/home/userx/public_html/grid/", "/home/usery/public_html/grid/") WHERE `name` = "path";

Change the data directory

UPDATE `elgg_config` SET `value` = REPLACE(`value`, "/home/userx/elggdata/", "/home/usery/elggdata/") WHERE `name` = "dataroot";

Change the site URL (if this has changed)

UPDATE `elgg_sites_entity` SET `url` = "http://mynewdomain.com";

Change the filestore data directory

UPDATE elgg_metadata set value = '/home/usery/elggdata/' WHERE name = 'filestore::dir_root';
Finalizing the new installation

Run the upgrade script by visiting the following URL: http://mynewdomain.com/upgrade.php . Do this step twice - back to back.

Update your DNS records so that your host name resolves to the new host’s IP address if this is a permanent move.


If you followed the steps outlined here you should now have a fully functional copy of your primary Elgg installation.

Getting Help

Having a problem with Elgg? The best way to get help is to ask at the Community Site. This site is community supported by a large group of volunteers. Here are a few tips to help you get the help you need.

Getting help

Don’t be a Help Vampire

We were all newbies at one time, but we can all learn. Not showing that you are making attempts to learn on your own or do your own research is off putting for those helping. Also, very generic questions like “How do I build a forum?” are almost impossible to answer.

Search first

Be sure to search the documentation (this site), the Community Site, and Google before asking a question. New users to Elgg frequently have the same questions, so please search. People are less inclined to reply to a post that has been answered many other times or that can be answered easily by Googling.

Ask once

Posting the same questions in multiple places makes it hard to answer you. Ask your question in one place only. Duplicate questions may be moderated.

Include Elgg Version

Different versions of Elgg have different features (and different bugs). Including the version of Elgg that you are using will help those helping you.

Have a reasonable profile

Profiles that look like spam or have silly names will often be ignored. Joviality is fine, but people are more likely to help Michael than 1337elggHax0r.

Post in the appropriate forum

Check to make sure you’re posting in the right forum. If you have a question about creating a plugin, don’t post to the Elgg Feedback forum. If you need help installing Elgg, post to Technical Support instead of the Theming group.

Use a descriptive topic title

Good topic titles concisely describe your problem or question. Bad topic titles are vague, contain all capital letters, and excessive punctuation.

Good title: “White screen after upgrading to 1.7.4.”

Bad title: “URGENT!!!!! site broke ;-( losing money help!!!!!!!!!!!”

Be detailed

Include as many details about your problem as possible. If you have a live site, include a link. Be forthcoming if community members might ask for more information. We can’t help you if you won’t give any details!

Keep it public

This is a public forum for the good of the Elgg project. Keep posts public. There’s no reason for anyone to ask you to send a private message or email. Likewise, there’s no reason to ask anyone to send a private email to you. Post in the public.


In addition to the site-wide Terms and Policies, following these guidelines keeps our community site useful and safe for everyone.


All content must be safe for work: PG in the US and UK. If your Elgg site has adult content and you have been asked to post a link, please mark it NSFW (Not Safe For Work) so people know.

Excessive swearing in any language will not be tolerated.


Working with technical problems can be frustrating. Please keep the community site free of frustration. If you’re feeling anxious, take a step away and do something else. Threatening or attacking community members, core developers, or plugin developers will not help solve your problem and will likely get you banned.


Advertising is not allowed. Posts with any sort of advertising will be moderated.

Asking for money / Offering to pay

Don’t ask for money on the community site. Likewise, don’t offer to pay for answers. If you are looking for custom development, post to the Professional Services group. Posts asking for money or recommending a commercial plugin may be moderated.


There’s a reason Elgg doesn’t have an option for signatures: they cause clutter and distract from the conversation. Users are discouraged from using signatures on the community site, and signatures with links or advertising will be removed.

Bumping, +1, me too

Don’t do it. If your question hasn’t been answered, see the top of this document for tips. These types of post add nothing to the conversation and may be moderated.

Posting Code

Long bits of code are confusing to read through in a forums context. Please use http://elgg.pastebin.com to post long bits of code and provide the Paste Bin link instead of directly posting the code.

Good Ideas

Not policies, but good ideas.

Say thanks

Did someone help you? Be sure to thank them! The community site is run by volunteers. No one has to help you with your problem. Be sure to show your appreciation!

Give back

Have a tip for Elgg? See someone with a similar problem you had? You’ve been there and can help them out, so give them a hand!


As of Elgg 3.0 several hardening settings have been added to Elgg. You can enable/disable these settings as you like.

Upgrade protection

The URL of http://your-elgg-site.com/upgrade.php can be protected by a unique token. This will prevent random users from being able to run this file. The token is not needed for logged in site administrators.

Cron protection

The URLs of the cron can be protected by a unique token. This will prevent random users from being able to run the cron. The token is not needed when running the cron from the commandline of the server.

Disable password autocomplete

Data entered in these fields will be cached by the browser. An attacker who can access the victim’s browser could steal this information. This is especially important if the application is commonly used in shared computers such as cyber cafes or airport terminals. If you disable this, password management tools can no longer autofill these fields. The support for the autocomplete attribute can be browser specific.

Email address change requires password

When a user wishes to change their email address associated with their account, they need to also supply their current password.

Notification to site administrators

When a new site administrator is added or when a site administrator is removed all the site administrators get a notification about this action.

Notifications to user

Site administrator

When the site administrator role is added to or removed from the account, send a notification to the user whos account this is affecting.


When the account of a user gets banned or unbanned, let the affected user know about this action.

Developer Guides

Customize Elgg’s behavior with plugins.

Don’t Modify Core


In general, you shouldn’t modify non-config files that come with third-party software like Elgg.

The best way to customize the behavior of Elgg is to install Elgg as a composer dependency and use the root directory to store modifications specific to your application, and alter behavior through the rich Elgg plugin API.

If you’d like to share customizations between sites or even publish your changes as a reusable package for the community, create a plugin using the same plugin APIs and file structure.

It makes it hard to get help

When you don’t share the same codebase as everyone else, it’s impossible for others to know what is going on in your system and whether your changes are to blame. This can frustrate those who offer help because it can add considerable noise to the support process.

It makes upgrading tricky and potentially disastrous

You will certainly want or need to upgrade Elgg to take advantage of

  • security patches
  • new features
  • new plugin APIs
  • new stability improvements
  • performance improvements

If you’ve modified core files, then you must be very careful when upgrading that your changes are not overwritten and that they are compatible with the new Elgg code. If your changes are lost or incompatible, then the upgrade may remove features you’ve added and even completely break your site.

This can also be a slippery slope. Lots of modifications can lead you to an upgrade process so complex that it’s practically impossible. There are lots of sites stuck running old versions software due to taking this path.

It may break plugins

You may not realize until much later that your “quick fix” broke seemingly unrelated functionality that plugins depended on.


  • Resist the temptation
    Editing existing files is quick and easy, but doing so heavily risks the maintainability, security, and stability of your site.
  • When receiving advice, consider if the person telling you to modify core will be around to rescue you if you run into trouble later!
  • Apply these principle to software in general.
    If you can avoid it, don’t modify third party plugins either, for the same reasons: Plugin authors release new versions, too, and you will want those updates.


Plugins must provide a manifest.xml file in the plugin root in order to be recognized by Elgg.


The start.php file bootstraps plugin by registering event listeners and plugin hooks.


This optional file is read by Elgg to configure various services, and must return an array if present. It should not be included by plugins and is not guaranteed to run at any particular time. Besides magic constants like __DIR__, its return value should not change. The currently supported sections are:

  • views
  • actions
  • settings
  • user_settings
  • widgets

Here’s a trivial example configuring view locations via the views key:


return [
        'views' => [
                'default' => [
                        'file/icon/' => __DIR__ . '/graphics/icons',

activate.php, deactivate.php

The activate.php and deactivate.php files contain procedural code that will run upon plugin activation and deactivation. Use these files to perform one-time events such as registering a persistent admin notice, registering subtypes, or performing garbage collection when deactivated.


Elgg plugins are required to have a manifest.xml file in the root of a plugin.

The manifest.xml file includes information about the plugin itself, requirements to run the plugin, and optional information including where to display the plugin in the admin area and what APIs the plugin provides.


The manifest file is a standard XML file in UTF-8. Everything is a child of the <plugin_manifest> element.

<?xml version="1.0" encoding="UTF-8" ?>
<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">

The manifest syntax is as follows:


Many elements can contain children attributes:

Required Elements

All plugins are required to define the following elements in their manifest files:

  • id - This has the name as the directory that the plugin uses.
  • name - The display name of the plugin.
  • author - The name of the author who wrote the plugin.
  • version - The version of the plugin.
  • description - A description of the what the plugin provides, its features, and other relevant information
  • requires - Each plugin must specify the release of Elgg it was developed for. See the plugin Dependencies page for more information.
Available Elements

In addition to the require elements above, the follow elements are available to use:

  • blurb - A short description of the plugin.
  • category - The category of the plugin. It is recommended to follow the [[Plugin_Guidelines|plugin guidelines]] and use one of the defined categories. There can be multiple entries.
  • conflicts - Specifies that the plugin conflicts with a certain system configuration.
  • copyright - The plugin’s copyright information.
  • license - The plugin’s license information.
  • provides - Specifies that this plugin provides the same functionality as another Elgg plugin or a PHP extension.
  • screenshot - Screenshots of the plugin. There can be multiple entries. See the advanced example for syntax.
  • suggests - Parallels the requires system, but doesn’t affect if the plugin can be enabled. Used to suggest other plugins that interact or build on the plugin.
  • website - A link to the website for the plugin.
Simple Example

This manifest file is the bare minimum a plugin must have.

<?xml version="1.0" encoding="UTF-8"?>
<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">
        <name>Example Manifest</name>
        <description>This is a simple example of a manifest file. In this example, there are not screenshots, dependencies, or additional information about the plugin.</description>

Advanced example

This example uses all of the available elements:

<?xml version="1.0" encoding="UTF-8"?>
<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">
        <name>Example Manifest</name>
        <author>Brett Profitt</author>
        <blurb>This is an example manifest file.</blurb>
        <description>This is a simple example of a manifest file. In this example, there are many options used, including screenshots, dependencies, and additional information about the plugin.</description>
        <copyright>(C) Brett Profitt 2014</copyright>
        <license>GNU Public License version 2</license>



        <!-- The path is relative to the plugin's root. -->
                <description>Elgg profile.</description>



Plugin coding guidelines

In addition to the Elgg Coding Standards, these are guidelines for creating plugins. Core plugins are being updated to this format and all plugin authors should follow these guidelines in their own plugins.

See also

Be sure to follow the Plugin skeleton for your plugin’s layout.

Use standardized routing with page handlers

  • Example: Bookmarks plugin

  • Page handlers should accept the following standard URLs:
    Purpose URL
    All page_handler/all
    User page_handler/owner/<username>
    User friends’ page_handler/friends/<username>
    Single entity page_handler/view/<guid>/<title>
    Add page_handler/add/<container_guid>
    Edit page_handler/edit/<guid>
    Group list page_handler/group/<guid>/owner
  • Include page handler scripts from the page handler. Almost every page handler should have a page handler script. (Example: bookmarks/all => mod/bookmarks/views/default/resources/bookmarks/all.php)

  • Pass arguments like entity guids to the resource view via $vars in elgg_view_resource().

  • Call elgg_gatekeeper() and elgg_admin_gatekeeper() in the page handler function if required.

  • The group URL should use views like resources/groups/*.php to render pages.

  • Page handlers should not contain HTML.

  • If upgrading a 1.7 plugin, update the URLs throughout the plugin. (Don’t forget to remove /pg/!)

Use standardized page handlers and scripts

  • Example: Bookmarks plugin
  • Store page functionality in mod/<plugin>/views/default/resources/<page_handler>/<page_name>.php
  • Use elgg_view_resource('<page_handler>/<page_name>') to render that.
  • Use the content page layout in page handler scripts: $content = elgg_view_layout('content', $options);
  • Page handler scripts should not contain HTML
  • Call elgg_push_breadcrumb() in the page handler scripts.
  • No need to worry about setting the page owner if the URLs are in the standardized format
  • For group content, check the container_guid by using elgg_get_page_owner_entity()

The object/<subtype> view

  • Example: Bookmarks plugin

  • Make sure there are views for $vars['full_view'] == true and $vars['full_view'] == false

  • Check for the object in $vars['entity'] . Use elgg_instance_of() to make sure it’s the type entity you want. Return true to short circuit the view if the entity is missing or wrong.

  • Use the new list body and list metadata views to help format. You should use almost no markup in these views.

  • Update action structure - Example: Bookmarks plugin.

  • Namespace action files and action names (example: mod/blog/actions/blog/save.php => action/blog/save)

  • Use the following action URLs:
    Purpose URL
    Add action/plugin/save
    Edit action/plugin/save
    Delete action/plugin/delete
  • Make the delete action accept action/<handler>/delete?guid=<guid> so the metadata entity menu has the correct URL by default

  • If updating a 1.7 plugin, replace calls to functions deprecated in 1.7 because these will produce visible errors on every load in 1.8


Actions are transient states to perform an action such as updating the database or sending a notification to a user. Used correctly, actions provide a level of access control and prevent against CSRF attacks.

Actions require action (CSRF) tokens to be submitted via GET/POST, but these are added automatically by elgg_view_form() and by using the is_action argument of the output/url view.

Action best practices

Action files are included within Elgg’s action system; like views, they are not regular scripts executable by users. Do not boot the Elgg core in your file and direct users to load it directly.

Because actions are time-sensitive they are not suitable for links in emails or other delayed notifications. An example of this would be invitations to join a group. The clean way to create an invitation link is to create a page handler for invitations and email that link to the user. It is then the page handler’s responsibility to create the action links for a user to join or ignore the invitation request.

Consider that actions may be submitted via XHR requests, not just links or form submissions.

Directly calling a file

This is an easy one: Don’t do it. With the exception of 3rd party application integration, there is not a reason to directly call a file in mods directory.


This page aims to list and document accessibility rules and best practices, to help core and plugins developpers to make Elgg the most accessible social engine framework that everyone dreams of.


This is an ongoing work, please contribute on Github if you have some skills in this field!

Tips for implementing accessibility

  • All accessibility-related tickets reported to trac should be tagged with “a11y”, short for “accessibility”
  • Use core views such as output/*, and input/* to generate markup, since we can bake a11y concerns into these views
  • All images should have a descriptive alt attribute. Spacer or purely decorative graphics should have blank alt attributes
  • All <a> tags should have text or an accessible image inside. Otherwise screen readers will have to read the URL, which is a poor experience <a> tags should contain descriptive text, if possible, as opposed to generic text like “Click here”
  • Markup should be valid
  • Themes should not reset “outline” to nothing. :focus deserves a special visual treatment so that handicapped users can know where they are

Tips for testing accessibility

  • Use the tools linked to from the resources section. Example report for community.elgg.org on June 16, 2012
  • Try different font-size/zoom settings in your browser and make sure the theme remains usable
  • Turn off css to make sure the sequential order of the page makes sense

Documentation objectives and principles

  • Main accessibility rules
  • collect and document best practices
  • Provide code examples
  • Keep the document simple and usable
  • Make it usable for both beginner developpers and experts (from most common and easiest changes to elaborate techniques)


The elgg/Ajax AMD module (introduced in Elgg 2.1) provides a set of methods for communicating with the server in a concise and uniform way, which allows plugins to collaborate on the request data, the server response, and the returned client-side data.

Client and server code written for the legacy API should not need modification.


All the ajax methods perform the following:

  1. Client-side, the data option (if given as an object) is filtered by the hook ajax_request_data.
  2. The request is made to the server, either rendering a view or a form, calling an action, or loading a path.
  3. The method returns a jqXHR object, which can be used as a Promise.
  4. Server-echoed content is turned into a response object (Elgg\Services\AjaxResponse) containing a string (or a JSON-parsed value).
  5. The response object is filtered by the hook ajax_response.
  6. The response object is used to create the HTTP response.
  7. Client-side, the response data is filtered by the hook ajax_response_data.
  8. The jqXHR promise is resolved and any success callbacks are called.

More notes:

  • All hooks have a type depending on the method and first argument. See below.
  • By default the elgg/spinner module is automatically used during requests.
  • User messages generated by system_message() and register_error() are collected and displayed on the client.
  • Elgg gives you a default error handler that shows a generic message if output fails.
  • PHP exceptions or denied resource return HTTP error codes, resulting in use of the client-side error handler.
  • The default HTTP method is POST for actions, otherwise GET. You can set it via options.method.
  • If a non-empty options.data is given, the default method is always POST.
  • For client caching, set options.method to "GET" and options.data.elgg_response_ttl to the max-age you want in seconds.
  • To save system messages for the next page load, set options.data.elgg_fetch_messages = 0. You may want to do this if you intent to redirect the user based on the response.
  • To stop client-side API from requiring AMD modules required server-side with elgg_require_js(), set options.data.elgg_fetch_deps = 0.
  • All methods accept a query string in the first argument. This is passed on to the fetch URL, but does not appear in the hook types.
Performing actions

Consider this action:

// in myplugin/actions/do_math.php


$arg1 = (int)get_input('arg1');
$arg2 = (int)get_input('arg2');

// will be rendered client-side
system_message('We did it!');

echo json_encode([
        'sum' => $arg1 + $arg2,
        'product' => $arg1 * $arg2,

To execute it, use ajax.action('<action_name>', options):

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.action('do_math', {
        data: {
                arg1: 1,
                arg2: 2
}).done(function (output, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {

Notes for actions:

  • All hooks have type action:<action_name>. So in this case, three hooks will be triggered:
    • client-side "ajax_request_data", "action:do_math" to filter the request data (before it’s sent)
    • server-side "ajax_response", "action:do_math" to filter the response (after the action runs)
    • client-side "ajax_response_data", "action:do_math" to filter the response data (before the calling code receives it)
  • CSRF tokens are added to the request data.
  • The default method is POST.
  • An absolute action URL can be given in place of the action name.
  • Using forward() in an action simply sends the response. The URL given in not returned to the client.


When setting data, use ajax.objectify($form) instead of $form.serialize(). Doing so allows the ajax_request_data plugin hook to fire and other plugins to alter/piggyback on the request.

Fetching data

Consider this PHP script that runs at http://example.org/myplugin_time.

// in myplugin/start.php
elgg_register_page_handler('myplugin_time', 'myplugin_get_time');

function myplugin_get_time() {

        echo json_encode([
                'rfc2822' => date(DATE_RFC2822),
                'day' => date('l'),

        return true;

To fetch its output, use ajax.path('<url_path>', options).

var Ajax = require('elgg/Ajax');
    var ajax = new Ajax();

    ajax.path('myplugin_time').done(function (output, statusText, jqXHR) {
        if (jqXHR.AjaxData.status == -1) {

Notes for paths:

  • The 3 hooks (see Actions above) will have type path:<url_path>. In this case, “path:myplugin_time”.
  • If the page handler echoes a regular web page, output will be a string containing the HTML.
  • An absolute URL can be given in place of the path name.
Fetching views

Consider this view:

// in myplugin/views/default/myplugin/get_link.php

if (empty($vars['entity']) || !$vars['entity'] instanceof ElggObject) {

$object = $vars['entity'];
/* @var ElggObject $object */

echo elgg_view('output/url', [
        'text' => $object->getDisplayName(),
        'href' => $object->getUrl(),
        'is_trusted' => true,

Since it’s a PHP file, we must register it for Ajax first:

// in myplugin_init()

To fetch the view, use ajax.view('<view_name>', options):

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.view('myplugin/get_link', {
        data: {
                guid: 123 // querystring
}).done(function (output, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {

Notes for views:

  • The 3 hooks (see Actions above) will have type view:<view_name>. In this case, “view:myplugin/get_link”.
  • output will be a string with the rendered view.
  • The request data are injected into $vars in the view.
  • If the request data contains guid, the system sets $vars['entity'] to the corresponding entity or false if it can’t be loaded.


In ajax views and forms, note that $vars can be populated by client input. The data is filtered like get_input(), but may not be the type you’re expecting or may have unexpected keys.

Fetching forms

Consider we have a form view. We register it for Ajax:

// in myplugin_init()

To fetch this using ajax.form('<action_name>', options).

var Ajax = require('elgg/Ajax');
var ajax = new Ajax();

ajax.form('myplugin/add').done(function (output, statusText, jqXHR) {
    if (jqXHR.AjaxData.status == -1) {

Notes for forms:

  • The 3 hooks (see Actions above) will have type form:<action_name>. In this case, “form:myplugin/add”.
  • output will be a string with the rendered view.
  • The request data are injected into $vars in your form view.
  • If the request data contains guid, the system sets $vars['entity'] to the corresponding entity or false if it can’t be loaded.


Only the request data are passed to the requested form view (i.e. as a third parameter accepted by elgg_view_form()). If you need to pass attributes or parameters of the form element rendered by the input/form view (i.e. normally passed as a second parameter to elgg_view_form()), use the server-side hook view_vars, input/form.


In ajax views and forms, note that $vars can be populated by client input. The data is filtered like get_input(), but may not be the type you’re expecting or may have unexpected keys.

Piggybacking on an Ajax request

The client-side ajax_request_data hook can be used to append or filter data being sent by an elgg/Ajax request.

Let’s say when the view foo is fetched, we want to also send the server some data:

// in your boot module
var Ajax = require('elgg/Ajax');
var elgg = require('elgg');

    var ajax = new Ajax();

elgg.register_hook_handler(Ajax.REQUEST_DATA_HOOK, 'view:foo', function (name, type, params, data) {
    // send some data back
    data.bar = 1;
    return data;

This data can be read server-side via get_input('bar');.


If data was given as a string (e.g. $form.serialize()), the request hooks are not triggered.

Piggybacking on an Ajax response

The server-side ajax_response hook can be used to append or filter response data (or metadata).

Let’s say when the view foo is fetched, we want to also send the client some additional data:

use Elgg\Services\AjaxResponse;

function myplugin_append_ajax($hook, $type, AjaxResponse $response, $params) {

    // alter the value being returned
    $response->getData()->value .= " hello";

    // send some metadata back. Only client-side "ajax_response" hooks can see this!
    $response->getData()->myplugin_alert = 'Listen to me!';

    return $response;

    // in myplugin_init()
    elgg_register_plugin_hook_handler(AjaxResponse::RESPONSE_HOOK, 'view:foo', 'myplugin_append_ajax');

To capture the metadata send back to the client, we use the client-side ajax_response hook:

// in your boot module
var Ajax = require('elgg/Ajax');
var elgg = require('elgg');

elgg.register_hook_handler(Ajax.RESPONSE_DATA_HOOK, 'view:foo', function (name, type, params, data) {

    // the return value is data.value

    // the rest is metadata


    return data;


Only data.value is returned to the success function or available via the Deferred interface.


Elgg uses these same hooks to deliver system messages over elgg/Ajax responses.

Handling errors

Responses basically fall into three categories:

  1. HTTP success (200) with status 0. No register_error() calls were made on the server.
  2. HTTP success (200) with status -1. register_error() was called.
  3. HTTP error (4xx/5xx). E.g. calling an action with stale tokens, or a server exception. In this case the done and success callbacks are not called.

You may need only worry about the 2nd case. We can do this by looking at jqXHR.AjaxData.status:

ajax.action('entity/delete?guid=123').done(function (value, statusText, jqXHR) {
if (jqXHR.AjaxData.status == -1) {
    // a server error was already displayed

        // remove element from the page
Requiring AMD modules

Each response from an Ajax service will contain a list of AMD modules required server side with elgg_require_js(). When response data is unwrapped, these modules will be loaded asynchronously - plugins should not expect these modules to be loaded in their $.done() and $.then() handlers and must use require() for any modules they depend on. Additionally AMD modules should not expect the DOM to have been altered by an Ajax request when they are loaded - DOM events should be delegated and manipulations on DOM elements should be delayed until all Ajax requests have been resolved.

Legacy elgg.ajax APIs

Elgg 1.8 introduced elgg.action, elgg.get, elgg.getJSON, and other methods which behave less consistently both client-side and server-side.

Legacy elgg.action


  • you must manually pull the output from the returned wrapper
  • the success handler will fire even if the action is prevented
  • the success handler will receive a wrapper object. You must look for wrapper.output
  • no ajax hooks
elgg.action('do_math', {
  data: {
    arg1: 1,
    arg2: 2
  success: function (wrapper) {
    if (wrapper.output) {
    } else {
      // the system prevented the action from running, but we really don't
      // know why
elgg.action notes
  • It’s best to echo a non-empty string, as this is easy to validate in the success function. If the action was not allowed to run for some reason, wrapper.output will be an empty string.
  • You may want to use the elgg/spinner module.
  • Elgg does not use wrapper.status for anything, but a call to register_error() causes it to be set to -1.
  • If the action echoes a non-JSON string, wrapper.output will contain that string.
  • elgg.action is based on jQuery.ajax and returns a jqXHR object (like a Promise), if you should want to use it.
  • After the PHP action completes, other plugins can alter the wrapper via the plugin hook 'output', 'ajax', which filters the wrapper as an array (not a JSON string).
  • A forward() call forces the action to be processed and output immediately, with the wrapper.forward_url value set to the normalized location given.
  • To make sure Ajax actions can only be executed via XHR, use elgg_ajax_gatekeeper().
elgg.action JSON response wrapper
  current_url: {String} "http://example.org/action/example/math", // not very useful
  forward_url: {String} "http://example.org/foo", ...if forward('foo') was called
  output: {String|Object} from echo in action
  status: {Number} 0 = success. -1 = an error was registered.
  system_messages: {Object}


It’s probably best to rely only on the output key, and validate it in case the PHP action could not run for some reason, e.g. the user was logged out or a CSRF attack did not provide tokens.


If forward() is used in response to a legacy ajax request (e.g. elgg.ajax), Elgg will always respond with this wrapper, even if not in an action.

Legacy view fetching

A plugin can use a view script to handle XHR GET requests. Here’s a simple example of a view that returns a link to an object given by its GUID:

// in myplugin_init()
// in myplugin/views/default/myplugin/get_link.php

if (empty($vars['entity']) || !$vars['entity'] instanceof ElggObject) {

$object = $vars['entity'];
/* @var ElggObject $object */

echo elgg_view('output/url', [
    'text' => $object->getDisplayName(),
    'href' => $object->getUrl(),
    'is_trusted' => true,
elgg.get('ajax/view/myplugin/get_link', {
  data: {
    guid: 123 // querystring
  success: function (output) {

The Ajax view system works significantly differently than the action system.

  • There are no access controls based on session status.
  • Non-XHR requests are automatically rejected.
  • GET vars are injected into $vars in the view.
  • If the request contains $_GET['guid'], the system sets $vars['entity'] to the corresponding entity or false if it can’t be loaded.
  • There’s no “wrapper” object placed around the view output.
  • System messages/errors shouldn’t be used, as they don’t display until the user loads another page.
  • Depending on the view’s suffix (.js, .html, .css, etc.), a corresponding Content-Type header is added.


In ajax views and forms, note that $vars can be populated by client input. The data is filtered like
get_input(), but may not be the type you’re expecting or may have unexpected keys.
Returning JSON from a view

If the view outputs encoded JSON, you must use elgg.getJSON to fetch it (or use some other method to set jQuery’s ajax option dataType to json). Your success function will be passed the decoded Object.

Here’s an example of fetching a view that returns a JSON-encoded array of times:

elgg.getJSON('ajax/view/myplugin/get_times', {
  success: function (data) {
    alert('The time is ' + data.friendly_time);
Legacy form fetching

If you register a form view (name starting with forms/), you can fetch it pre-rendered with elgg_view_form(). Simply use ajax/form/<action> (instead of ajax/view/<view_name>):

// in myplugin_init()
elgg.get('ajax/form/myplugin/add', {
  success: function (output) {

Only the request data are passed to the requested form view (i.e. as a third parameter accepted by elgg_view_form()). If you need to pass attributes or parameters of the form element rendered by the input/form view (i.e. normally passed as a second parameter to elgg_view_form()), use the server-side hook view_vars, input/form.


In ajax views and forms, note that $vars can be populated by client input. The data is filtered like
get_input(), but may not be the type you’re expecting or may have unexpected keys.
Legacy helper functions

These functions extend jQuery’s native Ajax features.

elgg.get() is a wrapper for jQuery’s $.ajax(), but forces GET and does URL normalization.

// normalizes the url to the current <site_url>/activity
elgg.get('/activity', {
   success: function(resultText, success, xhr) {

elgg.post() is a wrapper for jQuery’s $.ajax(), but forces POST and does URL normalization.


Elgg provides everything needed to authenticate users via username/email and password out of the box, including:

  • remember-me cookies for persistent login
  • password reset logic
  • secure storage of passwords
  • logout
  • UIs for accomplishing all of the above

All that’s left for you to do as a developer is to use the built-in authentication functions to secure your pages and actions.

Working with the logged in user

Check whether the current user is logged in with elgg_is_logged_in():

if (elgg_is_logged_in()) {
  // do something just for logged-in users

Check if the current user is an admin with elgg_is_admin_logged_in():

if (elgg_is_admin_logged_in()) {
  // do something just for admins

Get the currently logged in user with elgg_get_logged_in_user_entity():

$user = elgg_get_logged_in_user_entity();

The returned object is an ElggUser so you can use all the methods and properties of that class to access information about the user. If the user is not logged in, this will return null, so be sure to check for that first.


Gatekeeper functions allow you to manage how code gets executed by applying access control rules.

Forward a user to the front page if they are not logged in with elgg_gatekeeper():


echo "Information for logged-in users only";


In Elgg 1.8 and below this function was called gatekeeper()

Forward a user to the front page unless they are an admin with elgg_admin_gatekeeper():


echo "Information for admins only";


In Elgg 1.8 and below this function was called admin_gatekeeper()

Prevent CSRF attacks with action_gatekeeper().


// Mutate some state in the database on behalf of the logged in user...

This function should be used in Forms + Actions prior to Elgg 1.8.


As of Elgg version 1.8 this function is called for all registered actions. There is no longer a need to call this function in your own actions. If you wish to protect other pages with action tokens then you can call this function.

Pluggable Authentication Modules

Elgg has support for pluggable authentication modules (PAM), which enables you to write your own authentication handlers. Whenever a request needs to get authenticated the system will call elgg_authenticate() which probes the registered PAM handlers until one returns success.

The preferred approach is to create a separate Elgg plugin which will have one simple task: to process an authentication request. This involves setting up an authentication handler in the plugin’s start.php file, and to register it with the PAM module so it will get processed whenever the system needs to authenticate a request.

The authentication handler is a function and takes a single parameter. Registering the handler is being done by register_pam_handler() which takes the name of the authentication handler, the importance and the policy as parameters. It is advised to register the handler in the plugin’s init function, for example:

function your_plugin_init() {
   // Register the authentication handler

function your_plugin_auth_handler($credentials) {
   // do things ...

// Add the plugin's init function to the system's init event
elgg_register_elgg_event_handler('init', 'system', 'your_plugin_init');


By default an authentication module is registered with an importance of sufficient.

In a list of authentication modules; if any one marked sufficient returns true, pam_authenticate() will also return true. The exception to this is when an authentication module is registered with an importance of required. All required modules must return true for pam_authenticate() to return true, regardless of whether all sufficient modules return true.

Passed credentials

The format of the credentials passed to the handler can vary, depending on the originating request. For example, a regular login via the login form will create a named array, with the keys username and password. If a request was made for example via XML-RPC then the credentials will be set in the HTTP header, so in this case nothing will get passed to the authentication handler and the handler will need to perform steps on its own to authenticate the request.

Return value

The authentication handle should return a boolean, indicating if the request could be authenticated or not. One caveat is that in case of a regular user login where credentials are available as username and password the user will get logged in. In case of the XML-RPC example the authentication handler will need to perform this step itself since the rest of the system will not have any idea of either possible formats of credentials passed nor its contents. Logging in a user is quite simple and is being done by login(), which expects an ElggUser object.


Within the Elgg framework, context can be used to by your plugin’s functions to determine if they should run or not. You will be registering callbacks to be executed when particular events are triggered. Sometimes the events are generic and you only want to run your callback when your plugin caused the event to be triggered. In that case, you can use the page’s context.

You can explicitly set the context with set_context(). The context is a string and typically you set it to the name of your plugin. You can retrieve the context with the function get_context(). It’s however better to use elgg_push_context($string) to add a context to the stack. You can check if the context you want in in the current stack by calling elgg_in_context($context). Don’t forget to pop (with elgg_pop_context()) the context after you push one and don’t need it anymore.

If you don’t set it, Elgg tries to guess the context. If the page was called through the page handler, the context is set to the name of the handler which was set in elgg_register_page_handler(). If the page wasn’t called through the page handler, it uses the name of your plugin directory. If it cannot determine that, it returns main as the default context.

Sometimes a view will return different HTML depending on the context. A plugin can take advantage of that by setting the context before calling elgg_view() on the view and then setting the context back. This is frequently done with the search context.


If you setup cron correctly as described in Cron special hooks will be triggered so you can register for these hooks from your own code.

The example below registers a function for the daily cron.

function my_plugin_init() {
    elgg_register_plugin_hook_handler('cron', 'daily', 'my_plugin_cron_handler');

If timing is important in your cron hook be advised that the functions are executed in order of registration. This could mean that your function may start (a lot) later then you may have expected. However the parameters provided in the hook contain the original starting time of the cron, so you can always use that information.

function my_plugin_cron_handler($hook, $period, $return, $params) {
    $start_time = elgg_extract('time', $params);

See also

Events and Plugin Hooks has more information about hooks


Persist user-generated content and settings with Elgg’s generic storage API.


Creating an object

To create an object in your code, you need to instantiate an ElggObject. Setting data is simply a matter of adding instance variables or properties. The built-in properties are:

  • ``guid`` The entity’s GUID; set automatically
  • ``owner_guid`` The owning user’s GUID
  • ``subtype`` A single-word arbitrary string that defines what kind of object it is, for example blog
  • ``access_id`` An integer representing the access level of the object
  • ``title`` The title of the object
  • ``description`` The description of the object

The object subtype is a special property. This is an arbitrary string that describes what the object is. For example, if you were writing a blog plugin, your subtype string might be blog. It’s a good idea to make this unique, so that other plugins don’t accidentally try and use the same subtype. For the purposes of this document, let’s assume we’re building a simple forum. Therefore, the subtype will be forum:

$object = new ElggObject();
$object->subtype = "forum";
$object->access_id = 2;

access_id is another important property. If you don’t set this, your object will be private, and only the creator user will be able to see it. Elgg defines constants for the special values of access_id:

  • ACCESS_PRIVATE Only the owner can see it
  • ACCESS_FRIENDS Only the owner and his/her friends can see it
  • ACCESS_LOGGED_IN Any logged in user can see it
  • ACCESS_PUBLIC Even visitors not logged in can see it

Saving the object will automatically populate the $object->guid property if successful. If you change any more base properties, you can call $object->save() again, and it will update the database for you.

You can set metadata on an object just like a standard property. Let’s say we want to set the SKU of a product:

$object->SKU = 62784;

If you assign an array, all the values will be set for that metadata. This is how, for example, you set tags.

Metadata cannot be persisted to the database until the entity has been saved, but for convenience, ElggEntity can cache it internally and save it when saving the entity.

Loading an object
$entity = get_entity($guid);
if (!$entity) {
    // The entity does not exist or you're not allowed to access it.

But what if you don’t know the GUID? There are several options.

By user, subtype or site

If you know the user ID you want to get objects for, or the subtype, or the site, you have several options. The easiest is probably to call the procedural function elgg_get_entities:

$entities = elgg_get_entities(array(
    'type' => $entity_type,
    'subtype' => $subtype,
    'owner_guid' => $owner_guid,

This will return an array of ElggEntity objects that you can iterate through. elgg_get_entities paginates by default, with a limit of 10; and offset 0.

You can leave out owner_guid to get all objects and leave out subtype or type to get objects of all types/subtypes.

If you already have an ElggUser – e.g. elgg_get_logged_in_user_entity, which always has the current user’s object when you’re logged in – you can simply use:

$objects = $user->getObjects($subtype, $limit, $offset)

But what about getting objects with a particular piece of metadata?

By metadata

The function elgg_get_entities_from_metadata allows fetching entities with metadata in a variety of ways.

By annotation

The function elgg_get_entities_from_annotations allows fetching entities with metadata in a variety of ways.


As of Elgg 1.10 the default behaviour of elgg_get_entities_from_annotations was brought inline with the rest of the elgg_get_entities* functions.

Pre Elgg 1.10 the sorting of the entities was based on the latest addition of an annotation (in $options your could add $options[‘order_by’] = ‘maxtime ASC’ or $options[‘order_by’] = ‘maxtime DESC’. As of Elgg 1.10 this was changed to the creation time of the entity, just like the rest of the elgg_get_entities* functions. To get the old behaviour back add the following to your $options:

$options['selects'] = array('MAX(n_table.time_created) AS maxtime');
$options['group_by'] = 'n_table.entity_guid';
$options['order_by'] = 'maxtime ASC'


$options['order_by'] = 'maxtime DESC'
Displaying entities

In order for entities to be displayed in listing functions you need to provide a view for the entity in the views system.

To display an entity, create a view EntityType/subtype where EntityType is one of the following:

object: for entities derived from ElggObject user: for entities derived from ElggUser site: for entities derived from ElggSite group: for entities derived from ElggGroup

A default view for all entities has already been created, this is called EntityType/default.

Entity Icons

Entity icons can be saved from uploaded files, existing local files, or existing ElggFile objects. These methods save all sizes of icons defined in the system.

$object = new ElggObject();
$object->title = 'Example entity';
$object->description = 'An example object with an icon.';

// from an uploaded file

// from a local file

// from a saved ElggFile object
$file = get_entity(123);
if ($file instanceof ElggFile) {

The following sizes exist by default:
  • master - 550px at longer edge (not upscaled)
  • large - 200px at longer edge (not upscaled)
  • medium - 100px square
  • small - 40px square
  • tiny - 25px square
  • topbar - 16px square

Use elgg_get_icon_sizes() to get all possible icon sizes for a specific entity type and subtype. The function triggers the entity:icon:sizes hook.

To check if an icon is set, use $object->hasIcon($size).

You can retrieve the URL of the generated icon with``ElggEntity::getIconURL($params)`` method. This method accepts a $params argument as an array that specifies the size, type, and provide additional context for the hook to determine the icon to serve. The method triggers the entity:icon:url hook.

Use elgg_view_entity_icon($entity, $size, $vars) to render an icon. This will scan the following locations for a view and include the first match to .

  1. views/$viewtype/icon/$type/$subtype.php
  2. views/$viewtype/icon/$type/default.php
  3. views/$viewtype/icon/default.php


Type of view, e.g. 'default' or 'json'.
Type of entity, e.g. 'group' or 'user'.
Entity subtype, e.g. 'blog' or 'page'.

You do not have to return a fallback icon from the hook handler. If no uploaded icon is found, the view system will scan the views (in this specific order):

  1. views/$viewtype/$icon_type/$entity_type/$entity_subtype.svg
  2. views/$viewtype/$icon_type/$entity_type/$entity_subtype/$size.gif
  3. views/$viewtype/$icon_type/$entity_type/$entity_subtype/$size.png
  4. views/$viewtype/$icon_type/$entity_type/$entity_subtype/$size.jpg


Type of view, e.g. 'default' or 'json'.
Icon type, e.g. 'icon' or 'cover_image'.
Type of entity, e.g. 'group' or 'user'.
Entity subtype, e.g. 'blog' or 'page' (or 'default' if entity has not subtype).
Icon size (note that we do not use the size with svg icons)

Icon methods support passing an icon type if an entity has more than one icon. For example, a user might have an avatar and a cover photo icon. You would pass 'cover_photo' as the icon type:

$object->setIconFromUploadedFile('uploaded_photo', 'cover_photo');

        'size' => 'medium',
        'type' => 'cover_photo'

Note that custom icon types (e.g. cover photos) do not have preset sizes and coordinates. Use entity:<icon_type>:url hook to configure them.

By default icons will be stored in /icons/<icon_type>/<size>.jpg relative to entity’s directory on filestore. To provide an alternative location, use the entity:<icon_type>:file hook.

Adding, reading and deleting annotations

Annotations could be used, for example, to track ratings. To annotate an entity you can use the object’s annotate() method. For example, to give a blog post a rating of 5, you could use:

$blog_post->annotate('rating', 5);

To retrieve the ratings on the blog post, use $blogpost->getAnnotations('rating') and if you want to delete an annotation, you can operate on the ElggAnnotation class, eg $annotation->delete().

Retrieving a single annotation can be done with get_annotation() if you have the annotation’s ID. If you delete an ElggEntity of any kind, all its metadata, annotations, and relationships will be automatically deleted as well.

Extending ElggEntity

If you derive from one of the Elgg core classes, you’ll need to tell Elgg how to properly instantiate the new type of object so that get_entity() et al. will return the appropriate PHP class. For example, if I customize ElggGroup in a class called “Committee”, I need to make Elgg aware of the new mapping. Following is an example class extension:

// Class source
class Committee extends ElggGroup {

    protected function initializeAttributes() {
        $this->attributes['subtype'] = 'committee';

    // more customizations here

In your plugins elgg-plugin.php file add the entities section.

<?php // mod/example/elgg-plugin.php
return [
    // entities registration
    'entities' => [
                            'type' => 'group',
                            'subtype' => 'committee',
                            'class' => 'Committee',
                            'searchable' => true,

The entities will be registered upon activation of the plugin.

Now if you invoke get_entity() with the GUID of a committee object, you’ll get back an object of type Committee.


If you ever change the name of the class, use update_subtype() to change it as part of an upgrade

Advanced features
Entity URLs

Entity urls are provided by the getURL() interface and provide the Elgg framework with a common way of directing users to the appropriate display handler for any given object.

For example, a profile page in the case of users.

The url is set using the elgg\_register\_entity\_url\_handler() function. The function you register must return the appropriate url for the given type - this itself can be an address set up by a page handler.

The default handler is to use the default export interface.

Entity loading performance

elgg_get_entities has a couple options that can sometimes be useful to improve performance.

  • preload_owners: If the entities fetched will be displayed in a list with the owner information, you can set this option to true to efficiently load the owner users of the fetched entities.
  • preload_containers: If the entities fetched will be displayed in a list using info from their containers, you can set this option to true to efficiently load them.
  • distinct: When Elgg fetches entities using an SQL query, Elgg must be sure that each entity row appears only once in the result set. By default it includes a DISTINCT modifier on the GUID column to enforce this, but some queries naturally return unique entities. Setting the distinct option to false will remove this modifier, and rely on the query to enforce its own uniqueness.

The internals of Elgg entity queries is a complex subject and it’s recommended to seek help on the Elgg Community site before using the distinct option.

Pre-1.8 Notes

update_subtype(): This function is new in 1.8. In prior versions, you would need to edit the database by hand if you updated the class name associated with a given subtype.

elgg_register_entity_url_handler(): This function is new in 1.8. It deprecates register_entity_url_handler(), which you should use if developing for a pre-1.8 version of Elgg.

elgg_get_entities_from_metadata(): This function is new in 1.8. It deprecates get_entities_from_metadata(), which you should use if developing for a pre-1.8 version of Elgg.

Custom database functionality

It is strongly recommended to use entities wherever possible. However, Elgg supports custom SQL queries using the database API.

Example: Run SQL script on plugin activation

This example shows how you can populate your database on plugin activation.


if (!elgg_get_plugin_setting('database_version', 'my_plugin') {
    run_sql_script(__DIR__ . '/sql/activate.sql');
    elgg_set_plugin_setting('database_version', 1, 'my_plugin');


-- Create some table
CREATE TABLE prefix_custom_table(
    name VARCHAR(32),
    description VARCHAR(32),
    PRIMARY KEY (id)

-- Insert initial values for table
INSERT INTO prefix_custom_table (name, description)
VALUES ('Peter', 'Some guy'), ('Lisa', 'Some girl');

Note that Elgg execute statements through PHPs built-in functions and have limited support for comments. I.e. only single line comments are supported and must be prefixed by “– ” or “# ”. A comment must start at the very beginning of a line.



This section need some attention and will contain outdated information

The default Elgg system log is a simple way of recording what happens within an Elgg system. It’s viewable and searchable directly from the administration panel.

System log storage

A system log row is stored whenever an event concerning an object whose class implements the Loggable interface is triggered. ElggEntity and ElggExtender implement Loggable, so a system log row is created whenever an event is performed on all objects, users, groups, sites, metadata and annotations.

Common events include:

  • create
  • update
  • delete
  • login
Creating your own system log

There are some reasons why you might want to create your own system log. For example, you might need to store a full copy of entities when they are updated or deleted, for auditing purposes. You might also need to notify an administrator when certain types of events occur.

To do this, you can create a function that listens to all events for all types of object:


Your function can then be defined as:

function your_function_name($object, $event) {
   if ($object instanceof Loggable) {

You can then use the extra methods defined by Loggable to extract the information you need.

File System



Elgg’s filestore is located in the site’s dataroot that is configured during installation, and can be modified via site settings in Admin interface.

Directory Structure

The structure of the filestore is tied to file ownership by Elgg entities. Whenever the first file owned by an entity is written to the filestore, a directory corresponding to the entity GUID will be created within a parent bucket directory (buckets are bound to 5000 guids). E.g. files owned by user with guid 7777 will be located in 5000/7777/.

When files are created, filenames can contain subdirectory names (often referred to as $prefix throughout the code). For instance, avatars of the above user, can be found under 5000/7777/profile/.

File Objects

Writing Files

To write a file to the filestore, you would use an instance of ElggFile. Even though ElggFile extends ElggObject and can be stored as an actual Elgg entity, that is not always necessary (e.g. when writing thumbs of an image).

$file = new ElggFile();
$file->owner_guid = 7777;
$file->write('Contents of the file');

// to uprade this file to an entity
$file->subtype = 'file';
Reading Files

You can read file contents using instanceof of ElggFile.

// from an Elgg entity
$file = get_entity($file_guid);
// arbitrary file on the filestore
$file = new ElggFile();
$file->owner_guid = 7777;

// option 1
$contents = $file->grabFile();

// option 2
$contents = file_get_contents($file->getFilenameOnFilestore());
Serving Files

You can serve files from filestore using elgg_get_inline_url() and elgg_get_download_url(). Both functions accept 3 arguments:

  • ``file`` An instance of ElggFile to be served
  • ``use_cookie`` If set to true, validity of the URL will be limited to current session
  • ``expires`` Expiration time of the URL

You can use use_cookie and expires arguments as means of access control. For example, users avatars in most cases have a long expiration time and do not need to be restricted by current session - this will allows browsers to cache the images and file service will send appropriate Not Modified headers on consecutive requests.

For entities that are under Elgg’s access control, you may want to use cookies to ensure that access settings are respected and users do not share download URLs with somebody else.

You can also invalidated all previously generated URLs by updating file’s modified time, e.g. by using touch().

Embedding Files

Please note that due to their nature inline and download URLs are not suitable for embedding. Embed URLs must be permanent, whereas inline and download URLs are volatile (bound to user session and file modification time).

To embed an entity icon, use elgg_get_embed_url().

Handling File Uploads

In order to implement an action that saves a single file uploaded by a user, you can use the following approach:

// in your form
echo elgg_view('input/file', [
        'name' => 'upload',
        'label' => 'Select an image to upload',
        'help' => 'Only jpeg, gif and png images are supported',
// in your action
$uploaded_files = elgg_get_uploaded_files('upload');
if (!$uploaded_files) {
        register_error("No file was uploaded");

$uploaded_file = array_shift($uploaded_files);
if (!$uploaded_file->isValid()) {
        $error = elgg_get_friendly_upload_error($uploaded_file->getError());

$supported_mimes = [

$mime_type = ElggFile::detectMimeType($uploaded_file->getPathname(), $uploaded_file->getClientMimeType());
if (!in_array($mime_type, $supported_mimes)) {
        register_error("$mime_type is not supported");

$file = new ElggFile();
$file->owner_guid = elgg_get_logged_in_user_guid();
if ($file->acceptUploadedFile($uploaded_file)) {

If your file input supports multiple files, you can iterate through them in your action:

// in your form
echo elgg_view('input/file', [
        'name' => 'upload[]',
        'multiple' => true,
        'label' => 'Select images to upload',
// in your action
foreach (elgg_get_uploaded_files('upload') as $upload) {
        $file = new ElggFile();
        $file->owner_guid = elgg_get_logged_in_user_guid();
        if ($file->acceptUploadedFile($upload)) {

Forms + Actions

Create, update, or delete content.

Elgg forms submit to actions. Actions define the behavior for form submission.

This guide assumes basic familiarity with:

Registering actions

Actions must be registered before use. Use elgg_register_action for this:

elgg_register_action("example", __DIR__ . "/actions/example.php");

The mod/example/actions/example.php script will now be run whenever a form is submitted to http://localhost/elgg/action/example.


A stumbling point for many new developers is the URL for actions. The URL always uses /action/ (singular) and never /actions/ (plural). However, action script files are usually saved under the directory /actions/ (plural) and always have an extension.

Registering actions using plugin config file

You can also register actions via the elgg-plugin config file. To do this you need to provide an action section in the config file. The location of the action files are assumed to be in the plugin folder /actions.


return [
        'actions' => [
            'blog/save' => [], // all defaults
            'blog/delete' => [ // all custom
                  'access' => 'admin',
                  'filename' => __DIR__ . 'actions/blog/remove.php',

By default, actions are only available to logged in users.

To make an action available to logged out users, pass "public" as the third parameter:

elgg_register_action("example", $filepath, "public");

To restrict an action to only administrators, pass "admin" for the last parameter:

elgg_register_action("example", $filepath, "admin");
Writing action files

Use the get_input function to get access to request parameters:

$field = get_input('input_field_name', 'default_value');

You can then use the Database api to load entities and perform actions on them accordingly.

To indicate a successful action, use elgg_ok_response(). This function accepts data that you want to make available to the client for XHR calls (this data will be ignored for non-XHR calls)

$user = get_entity($guid);
// do something

$action_data = [
   'entity' => $user,
   'stats' => [
       'friends' => $user->getFriends(['count' => true]);

return elgg_ok_response($action_data, 'Action was successful', 'url/to/forward/to');

To indicate an error, use elgg_error_response()

$user = elgg_get_logged_in_user_entity();
if (!$user) {
   // show an error and forward the user to the referring page
   // send 404 error code on AJAX calls
   return elgg_error_response('User not found', REFERRER, ELGG_HTTP_NOT_FOUND);

if (!$user->canEdit()) {
   // show an error and forward to user's profile
   // send 403 error code on AJAX calls
   return elgg_error_response('You are not allowed to perform this action', $user->getURL(), ELGG_HTTP_FORBIDDEN);
Customizing actions

Before executing any action, Elgg triggers a hook:

$result = elgg_trigger_plugin_hook('action', $action, null, true);

Where $action is the action being called. If the hook returns false then the action will not be executed.

Example: Captcha

The captcha module uses this to intercept the register and user/requestnewpassword actions and redirect them to a function which checks the captcha code. This check returns true if valid or false if not (which prevents the associated action from executing).

This is done as follows:

elgg_register_plugin_hook_handler("action", "register", "captcha_verify_action_hook");
elgg_register_plugin_hook_handler("action", "user/requestnewpassword", "captcha_verify_action_hook");


function captcha_verify_action_hook($hook, $entity_type, $returnvalue, $params) {
  $token = get_input('captcha_token');
  $input = get_input('captcha_input');

  if (($token) && (captcha_verify_captcha($input, $token))) {
    return true;


  return false;

This lets a plugin extend an existing action without the need to replace the whole action. In the case of the captcha plugin it allows the plugin to provide captcha support in a very loosely coupled way.

Actions available in core


If your plugin does not implement any custom logic when deleting an entity, you can use bundled delete action

$guid = 123;
// You can provide optional forward path as a URL query parameter
$forward_url = 'path/to/forward/to';
echo elgg_view('output/url', array(
   'text' => elgg_echo('delete'),
   'href' => "action/entity/delete?guid=$guid&forward_url=$forward_url",
   'confirm' => true,

You can customize the success message keys for your entity type and subtype, using "entity:delete:$type:$subtype:success" and "entity:delete:$type:success" keys.

// to add a custom message when a blog post or file is deleted
// add the translations keys in your language files
return array(
   'entity:delete:object:blog:success' => 'Blog post has been deleted,
   'entity:delete:object:file:success' => 'File titled %s has been deleted',


To output a form, use the elgg_view_form function like so:

echo elgg_view_form('example');

Doing this generates something like the following markup:

<form action="http://localhost/elgg/action/example">
    <input type="hidden" name="__elgg_ts" value="1234567890" />
    <input type="hidden" name="__elgg_token" value="3874acfc283d90e34" />

Elgg does some things automatically for you when you generate forms this way:

  1. It sets the action to the appropriate URL based on the name of the action you pass to it
  2. It adds some anti-csrf tokens (__elgg_ts and __elgg_token) to help keep your actions secure
  3. It automatically looks for the body of the form in the forms/example view.

Put the content of your form in your plugin’s forms/example view:

// /mod/example/views/default/forms/example.php
echo elgg_view('input/text', array('name' => 'example'));

// defer form footer rendering
// this will allow other plugins to extend forms/example view

Now when you call elgg_view_form('example'), Elgg will produce:

<form action="http://localhost/elgg/action/example">
    <input type="hidden" name="__elgg_ts" value="...">
    <input type="hidden" name="__elgg_token" value="...">

    <input type="text" class="elgg-input-text" name="example">
    <div class="elgg-foot elgg-form-footer">
        <input type="submit" class="elgg-button elgg-button-submit" value="Submit">

To render a form input, use one of the bundled input views, which cover all standard HTML input elements. See individual view files for a list of accepted parameters.

echo elgg_view('input/select', array(
   'required' => true,
   'name' => 'status',
   'options_values' => array(
      'draft' => elgg_echo('status:draft'),
      'published' => elgg_echo('status:published'),
   // most input views will render additional parameters passed to the view
   // as tag attributes
   'data-rel' => 'blog',

The above example will render a dropdown select input:

<select required="required" name="status" data-rel="blog" class="elgg-input-dropdown">
   <option value="draft">Draft</option>
   <option value="published">Published</option>

To ensure consistency in field markup, use elgg_view_field(), which accepts all the parameters of the input being rendered, as well as #label and #help parameters (both of which are optional and accept HTML or text).

echo elgg_view_field(array(
   '#type' => 'select',
   '#label' => elgg_echo('blog:status:label'),
   '#help' => elgg_view_icon('help') . elgg_echo('blog:status:help'),
   'required' => true,
   'name' => 'status',
   'options_values' => array(
      'draft' => elgg_echo('status:draft'),
      'published' => elgg_echo('status:published'),
   'data-rel' => 'blog',

The above will generate the following markup:

<div class="elgg-field elgg-field-required">
   <label for="elgg-field-1" class="elgg-field-label">Blog status<span title="Required" class="elgg-required-indicator">*</span></label>
   <div class="elgg-field-input">
      <select required="required" name="status" data-rel="blog" id="elgg-field-1" class="elgg-input-dropdown">
         <option value="draft">Draft</option>
         <option value="published">Published</option>
   <div class="elgg-field-help elgg-text-help">
      <span class="elgg-icon-help elgg-icon"></span>This indicates whether or not the blog is visible in the feed
Input types

A list of bundled input types/views:

  • input/text - renders a text input <input type="text">
  • input/plaintext - renders a textarea <textarea></textarea>
  • input/longtext - renders a WYSIWYG text input
  • input/url - renders a url input <input type="url">
  • input/email - renders an email input <input type="email">
  • input/checkbox - renders a single checkbox <input type="checkbox">
  • input/checkboxes - renders a set of checkboxes with the same name
  • input/radio - renders one or more radio buttons <input type="radio">
  • input/submit - renders a submit button <input type="submit">
  • input/button - renders a button <button></button>
  • input/file - renders a file input <input type="file">
  • input/select - renders a select input <select></select>
  • input/hidden - renders a hidden input <input type="hidden">
  • input/password - renders a password input <input type="password">
  • input/number - renders a number input <input type="number">
  • input/date - renders a jQuery datepicker
  • input/access - renders an Elgg access level select
  • input/tags - renders an Elgg tags input
  • input/autocomplete - renders an Elgg entity autocomplete
  • input/captcha - placeholder view for plugins to extend
  • input/friendspicker - renders an Elgg friend autocomplete
  • input/userpicker - renders an Elgg user autocomplete
  • input/location renders an Elgg location input

Files and images

Use the input/file view in your form’s content view.

// /mod/example/views/default/forms/example.php
echo elgg_view(‘input/file’, array(‘name’ => ‘icon’));

Set the enctype of the form to multipart/form-data:

echo elgg_view_form(‘example’, array(
  ‘enctype’ => ‘multipart/form-data’

In your action file, use the $_FILES global to access the uploaded file:

$icon = $_FILES[‘icon’]

Sticky forms

Sticky forms are forms that retain user input if saving fails. They are “sticky” because the user’s data “sticks” in the form after submitting, though it was never saved to the database. This greatly improves the user experience by minimizing data loss. Elgg 1.8 includes helper functions so you can make any form sticky.

Helper functions

Sticky forms are implemented in Elgg 1.8 by the following functions:

elgg_make_sticky_form($name) Tells the engine to make all input on a form sticky.

elgg_clear_sticky_form($name) Tells the engine to discard all sticky input on a form.

elgg_is_sticky_form($name) Checks if $name is a valid sticky form.

elgg_get_sticky_values($name) Returns all sticky values saved for $name by elgg_make_sticky_form().


The basic flow of using sticky forms is: Call elgg_make_sticky_form($name) at the top of actions for forms you want to be sticky. Use elgg_is_sticky_form($name) and elgg_get_sticky_values($name) to get sticky values when rendering a form view. Call elgg_clear_sticky_form($name) after the action has completed successfully or after data has been loaded by elgg_get_sticky_values($name).

Example: User registration

Simple sticky forms require little logic to determine the input values for the form. This logic is placed at the top of the form body view itself.

The registration form view first sets default values for inputs, then checks if there are sticky values. If so, it loads the sticky values before clearing the sticky form:

// views/default/forms/register.php
$password = $password2 = '';
$username = get_input('u');
$email = get_input('e');
$name = get_input('n');

if (elgg_is_sticky_form('register')) {

The registration action sets creates the sticky form and clears it once the action is completed:

// actions/register.php


$guid = register_user($username, $password, $name, $email, false, $friend_guid, $invitecode);

if ($guid) {
Example: Bookmarks

The bundled plugin Bookmarks’ save form and action is an example of a complex sticky form.

The form view for the save bookmark action uses elgg_extract() to pull values from the $vars array:

// mod/bookmarks/views/default/forms/bookmarks/save.php
$title = elgg_extract('title', $vars, '');
$desc = elgg_extract('description', $vars, '');
$address = elgg_extract('address', $vars, '');
$tags = elgg_extract('tags', $vars, '');
$access_id = elgg_extract('access_id', $vars, ACCESS_DEFAULT);
$container_guid = elgg_extract('container_guid', $vars);
$guid = elgg_extract('guid', $vars, null);
$shares = elgg_extract('shares', $vars, array());

The page handler scripts prepares the form variables and calls elgg_view_form() passing the correct values:

// mod/bookmarks/pages/add.php
$vars = bookmarks_prepare_form_vars();
$content = elgg_view_form('bookmarks/save', array(), $vars);

Similarly, mod/bookmarks/pages/edit.php uses the same function, but passes the entity that is being edited as an argument:

$bookmark_guid = get_input('guid');
$bookmark = get_entity($bookmark_guid);


$vars = bookmarks_prepare_form_vars($bookmark);
$content = elgg_view_form('bookmarks/save', array(), $vars);

The library file defines bookmarks_prepare_form_vars(). This function accepts an ElggEntity as an argument and does 3 things:

  1. Defines the input names and default values for form inputs.
  2. Extracts the values from a bookmark object if it’s passed.
  3. Extracts the values from a sticky form if it exists.

TODO: Include directly from lib/bookmarks.php

// mod/bookmarks/lib/bookmarks.php
function bookmarks_prepare_form_vars($bookmark = null) {
     // input names => defaults
  $values = array(
    'title' => get_input('title', ''), // bookmarklet support
    'address' => get_input('address', ''),
    'description' => '',
    'access_id' => ACCESS_DEFAULT,
    'tags' => '',
    'shares' => array(),
    'container_guid' => elgg_get_page_owner_guid(),
    'guid' => null,
    'entity' => $bookmark,

  if ($bookmark) {
       foreach (array_keys($values) as $field) {
       if (isset($bookmark->$field)) {
         $values[$field] = $bookmark->$field;

  if (elgg_is_sticky_form('bookmarks')) {
       $sticky_values = elgg_get_sticky_values('bookmarks');
       foreach ($sticky_values as $key => $value) {
      $values[$key] = $value;


  return $values;

The save action checks the input, then clears the sticky form upon success:

// mod/bookmarks/actions/bookmarks/save.php

if ($bookmark->save()) {


See the Ajax guide for instructions on calling actions from JavaScript.


For enhanced security, all actions require an CSRF token. Calls to action URLs that do not include security tokens will be ignored and a warning will be generated.

A few views and functions automatically generate security tokens:

elgg_view('output/url', array('is_action' => TRUE));
$url = elgg_add_action_tokens_to_url("http://localhost/elgg/action/example");

In rare cases, you may need to generate tokens manually:

$__elgg_ts = time();
$__elgg_token = generate_action_token($__elgg_ts);

You can also access the tokens from javascript:


These are refreshed periodically so should always be up-to-date.

Security Tokens

On occasion we need to pass data through an untrusted party or generate an “unguessable token” based on some data. The industry-standard HMAC algorithm is the right tool for this. It allows us to verify that received data were generated by our site, and were not tampered with. Note that even strong hash functions like SHA-2 should not be used without HMAC for these tasks.

Elgg provides elgg_build_hmac() to generate and validate HMAC message authentication codes that are unguessable without the site’s private key.

// generate a querystring such that $a and $b can't be altered
$a = 1234;
$b = "hello";
$query = http_build_query([
    'a' => $a,
    'b' => $b,
    'mac' => elgg_build_hmac([$a, $b])->getToken(),
$url = "action/foo?$query";

// validate the querystring
$a = (int) get_input('a', '', false);
$b = (string) get_input('b', '', false);
$mac = get_input('mac', '', false);

if (elgg_build_hmac([$a, $b])->matchesToken($mac)) {
    // $a and $b have not been altered

Note: If you use a non-string as HMAC data, you must use types consistently. Consider the following:

$mac = elgg_build_hmac([123, 456])->getToken();

// type of first array element differs
elgg_build_hmac(["123", 456])->matchesToken($mac); // false

// types identical to original
elgg_build_hmac([123, 456])->matchesToken($mac); // true

Signed URLs

Signed URLs offer a limited level of security for situations where action tokens are not suitable, for example when sending a confirmation link via email. URL signatures verify that the URL has been generated by your Elgg installation (using site secret) and that the URL query elements were not tampered with.

URLs a signed with an unguessable SHA-256 HMAC key. See Security Tokens for more details.

$url = elgg_http_add_url_query_element(elgg_normalize_url('confirm'), [
   'user_guid' => $user_guid,

$url = elgg_http_get_signed_url($url);

notify_user($user_guid, $site->guid, 'Confirm', "Please confirm by clicking this link: $url");


Signed URLs do not offer CSRF protection and should not be used instead of action tokens.

Helper functions

Input and output

  • get_input($name) Grabs information from a form field (or any variable passed using GET or POST). Also sanitises input, stripping Javascript etc.
  • set_input($name, $value) Forces a value to a particular variable for subsequent retrieval by get_input()

Entity methods

  • $entity->getURL() Returns the URL of any entity in the system
  • $entity->getGUID() Returns the GUID of any entity in the system
  • $entity->canEdit() Returns whether or not the current user can edit the entity
  • $entity->getOwnerEntity() Returns the ElggUser owner of a particular entity

Entity and context retrieval

  • elgg_get_logged_in_user_entity() Returns the ElggUser for the current user
  • elgg_get_logged_in_user_guid() Returns the GUID of the current user
  • elgg_is_logged_in() Is the viewer logged in
  • elgg_is_admin_logged_in() Is the view an admin and logged in
  • elgg_gatekeeper() Shorthand for checking if a user is logged in. Forwards user to front page if not
  • elgg_admin_gatekeeper() Shorthand for checking the user is logged in and is an admin. Forwards user to front page if not
  • get_user($user_guid) Given a GUID, returns a full ElggUser entity
  • elgg_get_page_owner_guid() Returns the GUID of the current page owner, if there is one
  • elgg_get_page_owner_entity() Like elgg_get_page_owner_guid() but returns the full entity
  • elgg_get_context() Returns the current page’s context - eg “blog” for the blog plugin, “thewire” for the wire, etc. Returns “main” as default
  • elgg_set_context($context) Forces the context to be a particular value
  • elgg_push_context($context) Adds a context to the stack
  • elgg_pop_context() Removes the top context from the stack
  • elgg_in_context($context) Checks if you’re in a context (this checks the complete stack, eg. ‘widget’ in ‘groups’)


  • elgg_is_active_plugin($plugin_id) Check if a plugin is installed and enabled

Interface and annotations

  • elgg_view_image_block($icon, $info) Return the result in a formatted list
  • elgg_view_comments($entity) Returns any comments associated with the given entity
  • elgg_get_friendly_time($unix_timestamp) Returns a date formatted in a friendlier way - “18 minutes ago”, “2 days ago”, etc.
  • You can pass 'use_hover' => false to the user icon view if you don’t want the avatar drop down menu to appear e.g.
elgg_view_entity_icon($user, 'small', array('use_hover' => false));


Make your UI translatable into many different languages.

If you’d like to contribute translations to Elgg, see the contributors’ guide.

The default language is en for English. Currently Elgg will always fall back to an English translation, even if the site’s language is not English; this is a known bug.


Translations are stored in PHP files in the /languages directory of your plugin. Each file corresponds to a language. The format is /languages/{language-code}.php where {language-code} is the ISO 639-1 short code for the language. For example:

<?php // mod/example/languages/en.php

return [
        'example:text' => 'Some example text',

To override an existing translation, include it in your plugin’s language file, and make sure your plugin is ordered later on the Admin > Plugins page:

<?php // mod/better_example/languages/en.php

return [
        'example:text' => 'Some better text!',


Unless you are overriding core’s or another plugin’s language strings, it is good practice for the language keys to start with your plugin name. For example: yourplugin:success, yourplugin:title, etc. This helps avoid conflicts with other language keys.

Server-side API

elgg_echo($key, $args, $language)

Output the translation of the key in the current language.


echo elgg_echo('example:text');

It also supports variable replacement using sprintf syntax:

// 'welcome' => 'Welcome to %s, %s!'
echo elgg_echo('welcome', [

To force which language should be used for translation, set the third parameter:

echo elgg_echo('welcome', [], $user->language);

To first test whether elgg_echo() can find a translation:

$key = 'key:that:might:not:exist';
if (!elgg_language_key_exists($key)) {
        $key = 'fallback:key';

echo elgg_echo($key);


Some APIs allow creating translations for new keys. Translators should always include an English translation as a fallback. This makes elgg_language_key_exists($key) a reliable way to predict whether elgg_echo($key) will succeed.

Javascript API

elgg.echo(key, args)

This function is like elgg_echo in PHP.

Client-side translations are loaded asynchronously. Ensure translations are available by requiring the “elgg” AMD module:

define(function(require) {
        var elgg = require("elgg");


Translations are also available after the init, system JavaScript event.



Developers should use the AMD (Asynchronous Module Definition) standard for writing JavaScript code in Elgg.

Here we’ll describe making and executing AMD modules. The RequireJS documentation for defining modules may also be of use.

Executing a module in the current page

Telling Elgg to load an existing module in the current page is easy:


On the client-side, this will asynchronously load the module, load any dependencies, and execute the module’s definition function, if it has one.

Defining the Module

Here we define a basic module that alters the page, by passing a “definition function” to define():

// in views/default/myplugin/say_hello.js

define(function(require) {
    var elgg = require("elgg");
    var $ = require("jquery");


The module’s name is determined by the view name, which here is myplugin/say_hello.js. We strip the .js extension, leaving myplugin/say_hello.


The definition function must have one argument named require.

Making modules dependent on other modules

Below we refactor a bit so that the module depends on a new myplugin/hello module to provide the greeting:

// in views/default/myplugin/hello.js

define(function(require) {
    var elgg = require("elgg");

    return elgg.echo('hello_world');
// in views/default/myplugin/say_hello.js

define(function(require) {
    var $ = require("jquery");
    var hello = require("myplugin/hello");

Passing settings to modules
The elgg.data plugin hooks

The elgg module provides an object elgg.data which is populated from two server side hooks:

  • elgg.data, site: This filters an associative array of site-specific data passed to the client and cached.
  • elgg.data, page: This filters an associative array of uncached, page-specific data passed to the client.

Let’s pass some data to a module:


function myplugin_config_site($hook, $type, $value, $params) {
    // this will be cached client-side
    $value['myplugin']['api'] = elgg_get_site_url() . 'myplugin-api';
    $value['myplugin']['key'] = 'none';
    return $value;

function myplugin_config_page($hook, $type, $value, $params) {
    $user = elgg_get_logged_in_user_entity();
    if ($user) {
        $value['myplugin']['key'] = $user->myplugin_api_key;
        return $value;

elgg_register_plugin_hook_handler('elgg.data', 'site', 'myplugin_config_site');
elgg_register_plugin_hook_handler('elgg.data', 'page', 'myplugin_config_page');
define(function(require) {
    var elgg = require("elgg");

    var api = elgg.data.myplugin.api;
    var key = elgg.data.myplugin.key; // "none" or a user's key

    // ...


In elgg.data, page data overrides site data. Also note json_encode() is used to copy data client-side, so the data must be JSON-encodable.

Making a config module

You can use a PHP-based module to pass values from the server. To make the module myplugin/settings, create the view file views/default/myplugin/settings.js.php (note the double extension .js.php).


// this will be cached client-side
$settings = [
    'api' => elgg_get_site_url() . 'myplugin-api',
    'key' => null,
define(<?php echo json_encode($settings); ?>);

You must also manually register the view as an external resource:

// note the view name does not include ".php"


The PHP view is cached, so you should treat the output as static (the same for all users) and avoid session-specific logic.

Setting the URL of a module

You may have an AMD script outside your views you wish to make available as a module.

The best way to accomplish this is by configuring the path to the file using the views.php file in the root of your plugin:

<?php // views.php
return [
    'default' => [
        'underscore.js' => 'vendor/bower-asset/underscore/underscore.min.js',

If you’ve copied the script directly into your plugin instead of managing it with Composer, you can use something like this instead:

<?php // views.php
return [
    'default' => [
        'underscore.js' => __DIR__ . '/bower_components/underscore/underscore.min.js',

That’s it! Elgg will now load this file whenever the “underscore” module is requested.

Using traditional JS libraries as modules

It’s possible to support JavaScript libraries that do not declare themselves as AMD modules (i.e. they declare global variables instead) if you shim them by setting exports and deps in elgg_define_js:

// set the path, define its dependencies, and what value it returns
elgg_define_js('jquery.form', [
    'deps' => ['jquery'],
    'exports' => 'jQuery.fn.ajaxForm',

When this is requested client-side:

  1. The jQuery module is loaded, as it’s marked as a dependency.
  2. https://elgg.example.org/cache/125235034/views/default/jquery.form.js is loaded and executed.
  3. The value of window.jQuery.fn.ajaxForm is returned by the module.


Calls to elgg_define_js() must be in an init, system event handler.

Some things to note
  1. Do not use elgg.provide() anymore nor other means to attach code to elgg or other global objects. Use modules.
  2. Return the value of the module instead of adding to a global variable.
  3. Static (.js,.css,etc.) files are automatically minified and cached by Elgg’s simplecache system.
  4. The configuration is also cached in simplecache, and should not rely on user-specific values like get_language().

Booting your plugin

To add functionality to each page, or make sure your hook handlers are registered early enough, you may create a boot module for your plugin, with the name boot/<plugin_id>.

// in views/default/boot/example.js

define(function(require) {
    var elgg = require("elgg");
    var Plugin = require("elgg/Plugin");

    // plugin logic
    function my_init() { ... }

    return new Plugin({
        // executed in order of plugin priority
        init: function () {
            elgg.register_hook_handler("init", "system", my_init, 400);

When your plugin is active, this module will automatically be loaded on each page. Other modules can depend on elgg/init to make sure all boot modules are loaded.

Each boot module must return an instance of elgg/Plugin. The constructor must receive an object with a function in the init key. The init function will be called in the order of the plugin in Elgg’s admin area.


Though not strictly necessary, you may want to use the init, system event to control when your initialization code runs with respect to other modules.


A boot module cannot depend on the modules elgg/init or elgg/ready.

Modules provided with Elgg

Modules jquery and jquery-ui

You must depend on these modules to use $ or $.ui methods. In the future Elgg may stop loading these by default.

Module elgg


Translate interface text

elgg.echo('example:text', ['arg1']);


Display a status message to the user.



Display an error message to the user.



Normalize a URL relative to the elgg root:

// "http://localhost/elgg/blog"


Redirect to a new page.


This function automatically normalizes the URL.


Parse a URL into its component parts:

// returns {
//   fragment: "fragment",
//   host: "community.elgg.org",
//   path: "/file.php",
//   query: "arg=val"
// }


Get the GUID of the current page’s owner.


Register a hook handler with the event system. For best results, do this in a plugin boot module.

// boot module: /views/default/boot/example.js
define(function (require) {
    var elgg = require('elgg');
    var Plugin = require('elgg/Plugin');

    elgg.register_hook_handler('foo', 'bar', function () { ... });

    return new Plugin();


Emit a hook event in the event system. For best results depend on the elgg/init module.

// old
value = elgg.trigger_hook('my_plugin:filter', 'value', {}, value);

define(function (require) {
    var elgg = require('elgg');

    value = elgg.trigger_hook('my_plugin:filter', 'value', {}, value);


Force a refresh of all XSRF tokens on the page.

This is automatically called every 5 minutes by default.

This requires a valid security token in 1.8, but not in 1.9.

The user will be warned if their session has expired.


Add a security token to an object, URL, or query string:

// returns {
//   __elgg_token: "1468dc44c5b437f34423e2d55acfdd87",
//   __elgg_ts: 1328143779,
//   other: "data"
// }
elgg.security.addToken({'other': 'data'});

// returns: "action/add?__elgg_ts=1328144079&__elgg_token=55fd9c2d7f5075d11e722358afd5fde2"

// returns "?arg=val&__elgg_ts=1328144079&__elgg_token=55fd9c2d7f5075d11e722358afd5fde2"


Returns the logged in user as an JS ElggUser object.


Returns the logged in user’s guid.


True if the user is logged in.


True if the user is logged in and is an admin.


Get the current page’s language.

There are a number of configuration values set in the elgg object:

// The root of the website.
// The default site language.
// The current page's viewtype
// The Elgg version (YYYYMMDDXX).
// The Elgg release (X.Y.Z).
Module elgg/Ajax

See the Ajax page for details.

Module elgg/init

elgg/init loads and initializes all boot modules in priority order and triggers the [init, system] hook.

Require this module to make sure all plugins are ready.

Module elgg/Plugin

Used to create a boot module.

Module elgg/ready

elgg/ready loads and initializes all plugin boot modules in priority order.

Require this module to make sure all plugins are ready.

Module elgg/spinner

The elgg/spinner module can be used to create an Ajax loading indicator fixed to the top of the window.

define(function (require) {
   var spinner = require('elgg/spinner');

   elgg.action('friend/add', {
       beforeSend: spinner.start,
       complete: spinner.stop,
       success: function (json) {
           // ...


The elgg/Ajax module uses the spinner by default.

Module elgg/popup

The elgg/popup module can be used to display an overlay positioned relatively to its anchor (trigger).

The elgg/popup module is loaded by default, and binding a popup module to an anchor is as simple as adding rel="popup" attribute and defining target module with a href (or data-href) attribute. Popup module positioning can be defined with data-position attribute of the trigger element.

echo elgg_format_element('div', [
   'class' => 'elgg-module-popup hidden',
   'id' => 'popup-module',
], 'Popup module content');

// Simple anchor
echo elgg_view('output/url', [
   'href' => '#popup-module',
   'text' => 'Show popup',
   'rel' => 'popup',

// Button with custom positioning of the popup
echo elgg_format_element('button', [
   'rel' => 'popup',
   'class' => 'elgg-button elgg-button-submit',
   'text' => 'Show popup',
   'data-href' => '#popup-module',
   'data-position' => json_encode([
      'my' => 'center bottom',
      'at' => 'center top',

The elgg/popup module allows you to build out more complex UI/UX elements. You can open and close popup modules programmatically:

define(function(require) {
   var $ = require('jquery');
   $(document).on('click', '.elgg-button-popup', function(e) {


      var $trigger = $(this);
      var $target = $('#my-target');
              var $close = $target.find('.close');

      require(['elgg/popup'], function(popup) {
                popup.open($trigger, $target, {
                       'collision': 'fit none'

        $close.on('click', popup.close);

You can use getOptions, ui.popup plugin hook to manipulate the position of the popup before it has been opened. You can use jQuery open and close events to manipulate popup module after it has been opened or closed.

define(function(require) {

   var elgg = require('elgg');
   var $ = require('jquery');

   $('#my-target').on('open', function() {
      var $module = $(this);
      var $trigger = $module.data('trigger');

      elgg.ajax('ajax/view/my_module', {
         beforeSend: function() {
         success: function(output) {
   }).on('close', function() {
      var $trigger = $(this).data('trigger');

Open popup modules will always contain the following data that can be accessed via $.data():

  • trigger - jQuery element used to trigger the popup module to open
  • position - An object defining popup module position that was passed to $.position()

By default, target element will be appended to $('body') thus altering DOM hierarchy. If you need to preserve the DOM position of the popup module, you can add .elgg-popup-inline class to your trigger.

Module elgg/widgets

Plugins that load a widget layout via Ajax should initialize via this module:

require(['elgg/widgets'], function (widgets) {
Module elgg/lightbox

Elgg is distributed with the Colorbox jQuery library. Please go to http://www.jacklmoore.com/colorbox for more information on the options of this lightbox.

Use the following classes to bind your anchor elements to a lightbox:

  • elgg-lightbox - loads an HTML resource
  • elgg-lightbox-photo - loads an image resource (should be used to avoid displaying raw image bytes instead of an img tag)
  • elgg-lightbox-inline - displays an inline HTML element in a lightbox
  • elgg-lightbox-iframe - loads a resource in an iframe

You may apply colorbox options to an individual elgg-lightbox element by setting the attribute data-colorbox-opts to a JSON settings object.

echo elgg_view('output/url', [
   'text' => 'Open lightbox',
   'href' => 'ajax/view/my_view',
   'class' => 'elgg-lightbox',
   'data-colorbox-opts' => json_encode([
      'width' => '300px',

Use "getOptions", "ui.lightbox" plugin hook to filter options passed to $.colorbox() whenever a lightbox is opened. Note that the hook handler should depend on elgg/init AMD module.

elgg/lightbox AMD module should be used to open and close the lightbox programmatically:

define(function(require) {
   var lightbox = require('elgg/lightbox');
   var spinner = require('elgg/spinner');

      html: '<p>Hello world!</p>',
      onClosed: function() {
            onLoad: spinner.start,
            onComplete: spinner.stop,
            photo: true,
            href: 'https://elgg.org/cache/1457904417/default/community_theme/graphics/logo.png',

To support gallery sets (via rel attribute), you need to bind colorbox directly to a specific selector (note that this will ignore data-colorbox-opts on all elements in a set):

require(['elgg/lightbox'], function(lightbox) {
   var options = {
      photo: true,
      width: 500
   lightbox.bind('a[rel="my-gallery"]', options, false); // 3rd attribute ensures binding is done without proxies

You can also resize the lightbox programmatically if needed:

define(function(require) {
   var lightbox = require('elgg/lightbox');

      width: '300px'
Module elgg/ckeditor

This module can be used to add WYSIWYG editor to a textarea (requires ckeditor plugin to be enabled). Note that WYSIWYG will be automatically attached to all instances of .elgg-input-longtext.

require(['elgg/ckeditor'], function (elggCKEditor) {

   // Toggle CKEditor

   // Focus on CKEditor input
   // or

   // Reset CKEditor input
   // or

Inline tabs component

Inline tabs component fires an open event whenever a tabs is open and, in case of ajax tabs, finished loading:

// Add custom animation to tab content
    require(['jquery', 'elgg/ready'], function($) {
            $(document).on('open', '.theme-sandbox-tab-callback', function() {
                    $(this).data('target').hide().show('slide', {
                            duration: 2000,
                            direction: 'right',
                            complete: function() {
                                    alert('Thank you for clicking. We hope you enjoyed the show!');
                                    $(this).css('display', ''); // .show() adds display property

Traditional scripts

Although we highly recommend using AMD modules, you can register scripts with elgg_register_js:

elgg_register_js('jquery', $cdnjs_url);

This will override any URLs previously registered under this name.

Load a library on the current page with elgg_load_js:


This will load the library in the page footer. You must use the require() function to depend on modules like elgg and jquery.


Using inline scripts is NOT SUPPORTED because:
  • They are not testable (maintainability)
  • They are not cacheable (performance)
  • They prevent use of Content-Security-Policy (security)
  • They prevent scripts from being loaded with defer or async (performance)

Inline scripts in core or bundled plugins are considered legacy bugs.


The JS engine has a hooks system similar to the PHP engine’s plugin hooks: hooks are triggered and plugins can register functions to react or alter information. There is no concept of Elgg events in the JS engine; everything in the JS engine is implemented as a hook.

Registering hook handlers

Handler functions are registered using elgg.register_hook_handler(). Multiple handlers can be registered for the same hook.

The following example registers the handleFoo function for the foo, bar hook.

define(function (require) {
    var elgg = require('elgg');
    var Plugin = require('elgg/Plugin');

    function handleFoo(hook, type, params, value) {
        // do something

    elgg.register_hook_handler('foo', 'bar', handleFoo);

    return new Plugin();
The handler function

The handler will receive 4 arguments:

  • hook - The hook name
  • type - The hook type
  • params - An object or set of parameters specific to the hook
  • value - The current value

The value will be passed through each hook. Depending on the hook, callbacks can simply react or alter data.

Triggering custom hooks

Plugins can trigger their own hooks:

define(function(require) {
    var elgg = require('elgg');

    elgg.trigger_hook('name', 'type', {params}, "value");


Be aware of timing. If you don’t depend on elgg/init, other plugins may not have had a chance to register their handlers.

Available hooks
init, system
Plugins should register their init functions for this hook. It is fired after Elgg’s JS is loaded and all plugin boot modules have been initialized. Depend on the elgg/init module to be sure this has completed.
ready, system
This hook is fired when the system has fully booted (after init). Depend on the elgg/ready module to be sure this has completed.
getOptions, ui.popup
This hook is fired for pop up displays ("rel"="popup") and allows for customized placement options.
getOptions, ui.lightbox
This hook can be used to filter options passed to $.colorbox()
config, ckeditor
This filters the CKEditor config object. Register for this hook in a plugin boot module. The defaults can be seen in the module elgg/ckeditor/config.
prepare, ckeditor
This hook can be used to decorate CKEDITOR global. You can use this hook to register new CKEditor plugins and add event bindings.
ajax_request_data, *
This filters request data sent by the elgg/Ajax module. See Ajax for details.
ajax_response_data, *
This filters the response data returned to users of the elgg/Ajax module. See Ajax for details.
insert, editor
This hook is triggered by the embed plugin and can be used to filter content before it is inserted into the textarea. This hook can also be used by WYSIWYG editors to insert content using their own API (in this case the handler should return false). See ckeditor plugin for an example.

Third-party assets

We recommend managing third-party scripts and styles with Composer. Elgg core uses fxp/composer-asset-plugin for this purpose. This plugin allows you to pull dependencies from the Bower or Yarn package repositories, but using the Composer command-line tool.

For example, to include jQuery, you could run the following Composer commands:

composer global require fxp/composer-asset-plugin:~1.1.4
composer require bower-asset/jquery:~2.0


fxp/composer-asset-plugin must be installed globally! See https://github.com/francoispluchino/composer-asset-plugin for more info.


There are two ways to send notifications in Elgg:
  • Instant notifications
  • Event-based notifications send using a notifications queue

Instant notifications

The generic method to send a notification to a user is via the function notify_user(). It is normally used when we want to notify only a single user. Notification like this might for example inform that someone has liked or commented the user’s post.

The function usually gets called in an action file.


In this example a user ($user) is triggering an action to rate a post created by another user ($owner). After saving the rating (ElggAnnotation $rating) to database, we could use the following code to send a notification about the new rating to the owner.

// Subject of the notification
$subject = elgg_echo('ratings:notification:subject', array(), $owner->language);

// Summary of the notification
$summary = elgg_echo('ratings:notification:summary', array($user->name), $owner->language);

// Body of the notification message
$body = elgg_echo('ratings:notification:body', array(
        $rating->getValue() // A value between 1-5
), $owner->language);

$params = array(
        'object' => $rating,
        'action' => 'create',
        'summary' => $summary

// Send the notification
notify_user($owner->guid, $user->guid, $subject, $body, $params);


The language used by the recipient isn’t necessarily the same as the language of the person who triggers the notification. Therefore you must always remember to pass the recipient’s language as the third parameter to elgg_echo().


The 'summary' parameter is meant for notification plugins that only want to display a short message instead of both the subject and the body. Therefore the summary should be terse but still contain all necessary information.

Enqueued notifications

On large sites there may be many users who have subscribed to receive notifications about a particular event. Sending notifications immediately when a user triggers such an event might remarkably slow down page loading speed. This is why sending of such notifications shoud be left for Elgg’s notification queue.

New notification events can be registered with the elgg_register_notification_event() function. Notifications about registered events will be sent automatically to all subscribed users.

This is the workflow of the notifications system:

  1. Someone does an action that triggers an event within Elgg
    • The action can be create, update or delete
    • The target of the action can be any instance of the ElggEntity class (e.g. a Blog post)
  2. The notifications system saves this event into a notifications queue in the database
  3. When the pluging hook handler for the one-minute interval gets triggered, the event is taken from the queue and it gets processed
  4. Subscriptions are fetched for the user who triggered the event
    • By default this includes all the users who have enabled any notification method for the user at www.site.com/notifications/personal/<username>
  5. Plugins are allowed to alter the subscriptions using the [get, subscriptions] hook
  6. Plugins are allowed to terminate notifications queue processing with the [send:before, notifications] hook
  7. Plugins are allowed to alter the notification parameters with the [prepare, notification] hook
  8. Plugins are allowed to alter the notification subject/message/summary with the [prepare, notification:<action>:<type>:<subtype>] hook
  9. Plugins are allowed to format notification subject/message/summary for individual delivery methods with [format, notification:<method>] hook
  10. Notifications are sent to each subscriber using the methods they have chosen
    • Plugins can take over or prevent sending of each individual notification with the [send, notification:<method>] hook
  11. The [send:after, notifications] hook is triggered for the event after all notifications have been sent

Tell Elgg to send notifications when a new object of subtype “photo” is created:

 * Initialize the photos plugin
function photos_init() {
        elgg_register_notification_event('object', 'photo', array('create'));


In order to send the event-based notifications you must have the one-minute CRON interval configured.

Contents of the notification message can be defined with the 'prepare', 'notification:[action]:[type]:[subtype]' hook.


Tell Elgg to use the function photos_prepare_notification() to format the contents of the notification when a new objects of subtype ‘photo’ is created:

 * Initialize the photos plugin
function photos_init() {
    elgg_register_notification_event('object', 'photo', array('create'));
    elgg_register_plugin_hook_handler('prepare', 'notification:create:object:photo', 'photos_prepare_notification');

 * Prepare a notification message about a new photo
 * @param string                          $hook         Hook name
 * @param string                          $type         Hook type
 * @param Elgg_Notifications_Notification $notification The notification to prepare
 * @param array                           $params       Hook parameters
 * @return Elgg_Notifications_Notification
function photos_prepare_notification($hook, $type, $notification, $params) {
    $entity = $params['event']->getObject();
    $owner = $params['event']->getActor();
    $recipient = $params['recipient'];
    $language = $params['language'];
    $method = $params['method'];

    // Title for the notification
    $notification->subject = elgg_echo('photos:notify:subject', array($entity->title), $language);

    // Message body for the notification
    $notification->body = elgg_echo('photos:notify:body', array(
    ), $language);

    // Short summary about the notification
    $notification->summary = elgg_echo('photos:notify:summary', array($entity->title), $language);

    return $notification;


Make sure the notification will be in the correct language by passing the reciepient’s language into the elgg_echo() function.

Registering a new notification method

By default Elgg has two notification methods: email and the bundled site_notifications plugin. You can register a new notification method with the elgg_register_notification_method() function.


Register a handler that will send the notifications via SMS.

 * Initialize the plugin
function sms_notifications_init () {

After registering the new method, it will appear to the notification settings page at www.example.com/notifications/personal/[username].

Sending the notifications using your own method

Besides registering the notification method, you also need to register a handler that takes care of actually sending the SMS notifications. This happens with the 'send', 'notification:[method]' hook.

 * Initialize the plugin
function sms_notifications_init () {
        elgg_register_plugin_hook_handler('send', 'notification:sms', 'sms_notifications_send');

 * Send an SMS notification
 * @param string $hook   Hook name
 * @param string $type   Hook type
 * @param bool   $result Has anyone sent a message yet?
 * @param array  $params Hook parameters
 * @return bool
 * @access private
function sms_notifications_send($hook, $type, $result, $params) {
        /* @var Elgg_Notifications_Notification $message */
        $message = $params['notification'];

        $recipient = $message->getRecipient();

        if (!$recipient || !$recipient->mobile) {
                return false;

        // (A pseudo SMS API class)
        $sms = new SmsApi();

        return $sms->send($recipient->mobile, $message->body);


In most cases Elgg core takes care of handling the subscriptions, so notification plugins don’t usually have to alter them.

Subscriptions can however be:

It’s possible to modify the recipients of a notification dynamically with the 'get', 'subscriptions' hook.

 * Initialize the plugin
function discussion_init() {
        elgg_register_plugin_hook_handler('get', 'subscriptions', 'discussion_get_subscriptions');

 * Get subscriptions for group notifications
 * @param string $hook          'get'
 * @param string $type          'subscriptions'
 * @param array  $subscriptions Array containing subscriptions in the form
 *                       <user guid> => array('email', 'site', etc.)
 * @param array  $params        Hook parameters
 * @return array
function discussion_get_subscriptions($hook, $type, $subscriptions, $params) {
        $reply = $params['event']->getObject();

        if (!elgg_instanceof($reply, 'object', 'discussion_reply')) {
                return $subscriptions;

        $group_guid = $reply->getContainerEntity()->container_guid;
        $group_subscribers = elgg_get_subscriptions_for_container($group_guid);

        return ($subscriptions + $group_subscribers);

Page handler

Elgg offers a facility to manage your plugin pages via a page handler, enabling custom urls like http://yoursite/your_plugin/section. To add a page handler to a plugin, a handler function needs to be registered in the plugin’s start.php file with elgg_register_page_handler():

elgg_register_page_handler('your_plugin', 'your_plugin_page_handler');

The plugin’s page handler is passed two parameters:

  • an array containing the sections of the URL exploded by ‘/’. With this information the handler will be able to apply any logic necessary, for example loading the appropriate view and returning its contents.
  • the handler, this is the handler that is currently used (in our example your_plugin). If you don’t register multiple page handlers to the same function you’ll never need this.

Code flow

Pages in plugins should be rendered via page handlers (not by using Elgg\Application). Generally the rendering is done by views with names starting with resources/. The program flow is something like this:

  1. A user requests /plugin_name/section/entity
  2. Elgg checks if plugin_name is registered to a page handler and calls that function, passing array('section', 'entity') as the first argument
  3. The page handler function determines which resource view will display the page.
  4. The handler uses elgg_view_resource() to render the page, also passing in any relevant info to the view via the $vars argument.
  5. The resource view combines many separate views, calls formatting functions like elgg_view_layout() and elgg_view_page(), and then echos the final output
  6. The user sees a fully rendered page

There is no syntax enforced on the URLs, but Elgg’s coding standards suggests a certain format.


Elgg has two mechanisms to respond to HTTP requests that don’t already go through the Actions and Simplecache systems.

URL Identifier and Segments

After removing the site URL, Elgg splits the URL path by / into an array. The first element, the identifier, is shifted off, and the remaining elements are called the segments. For example, if the site URL is http://example.com/elgg/, the URL http://example.com/elgg/blog/owner/jane?foo=123 produces:

Identifier: 'blog'. Segments: ['owner', 'jane']. (the query string parameters are available via get_input())

The site URL (home page) is a special case that produces an empty string identifier and an empty segments array.


URL identifier/segments should be considered potentially dangerous user input. Elgg uses htmlspecialchars to escapes HTML entities in them.

Page Handler

To handle all URLs that begin with a particular identifier, you can register a function to act as a Page handler. When the handler is called, the segments array is passed in as the first argument.

The following code registers a page handler for “blog” URLs and shows how one might route the request to a resource view.

elgg_register_page_handler('blog', 'blog_page_handler');

function blog_page_handler(array $segments) {
     // if the URL is http://example.com/elgg/blog/view/123/my-blog-post
     // $segments contains: ['view', '123', 'my-blog-post']

     $subpage = elgg_extract(0, $segments);
     if ($subpage === 'view') {

         // use a view for the page logic to allow other plugins to easily change it
         $resource = elgg_view_resource('blog/view', [
             'guid' => (int)elgg_extract(1, $segments);

         return elgg_ok_response($resource);

     // redirect to a different location
     if ($subpage === '') {
         return elgg_redirect_response('blog/all');

     // send an error page
     if ($subpage === 'owner' && !elgg_entity_exists($segments[1])) {
         return elgg_error_response('User not found', 'blog/all', ELGG_HTTP_NOT_FOUND);

     // ... handle other subpages

The route Plugin Hook

The route plugin hook is triggered before page handlers are called. The URL identifier is given as the type of the hook. This hook can be used to add some logic before the request is handled elsewhere, or take over page rendering completely.

Generally devs should instead use a page handler unless they need to affect a single page or a wider variety of URLs.

The following code results in /blog/all requests being completely handled by the plugin hook handler. For these requests the blog page handler is never called.

function myplugin_blog_all_handler($hook, $type, $returnvalue, $params) {
    $segments = elgg_extract('segments', $returnvalue, array());

    if (isset($segments[0]) && $segments[0] === 'all') {
        $title = "We're taking over!";
        $content = elgg_view_layout('one_column', array(
            'title' => $title,
            'content' => "We can take over page rendering completely"
        echo elgg_view_page($title, $content);

        // in the route hook, return false says, "stop rendering, we've handled this request"
        return false;

elgg_register_plugin_hook_handler('route', 'blog', 'myplugin_blog_all_handler');


As of 2.1, route modification should be done in the route:rewrite hook.

The route:rewrite Plugin Hook

For URL rewriting, the route:rewrite hook (with similar arguments as route) is triggered very early, and allows modifying the request URL path (relative to the Elgg site).

Here we rewrite requests for news/* to blog/*:

function myplugin_rewrite_handler($hook, $type, $value, $params) {
    $value['identifier'] = 'blog';
    return $value;

elgg_register_plugin_hook_handler('route:rewrite', 'news', 'myplugin_rewrite_handler');


The hook must be registered directly in your plugin start.php (the [init, system] event is too late).

Routing overview

For regular pages, Elgg’s program flow is something like this:

  1. A user requests http://example.com/news/owner/jane.
  2. Plugins are initialized.
  3. Elgg parses the URL to identifier news and segments ['owner', 'jane'].
  4. Elgg triggers the plugin hook route:rewrite, news (see above).
  5. Elgg triggers the plugin hook route, blog (was rewritten in the rewrite hook).
  6. Elgg finds a registered page handler (see above) for blog, and calls the function, passing in the segments.
  7. The page handler function determines it needs to render a single user’s blog. It calls elgg_view_resource('blog/owner', $vars) where $vars contains the username.
  8. The resources/blog/owner view gets the username via $vars['username'], and uses many other views and formatting functions like elgg_view_layout() and elgg_view_page() to create the entire HTML page.
  9. The page handler echos the view HTML and returns true to indicate it handled the request.
  10. PHP invokes Elgg’s shutdown sequence.
  11. The user receives a fully rendered page.

Elgg’s coding standards suggest a particular URL layout, but there is no syntax enforced.


Elgg uses the Elgg\Application class to load and bootstrap Elgg. In future releases this class will offer a set of service objects for plugins to use.


If you have a useful idea, you can add a new service!

Page ownership

One recurring task of any plugin will be to determine the page ownership in order to decide which actions are allowed or not. Elgg has a number of functions related to page ownership and also offers plugin developers flexibility by letting the plugin handle page ownership requests as well. Determining the owner of a page can be determined with elgg_get_page_owner_guid(), which will return the GUID of the owner. Alternatively, elgg_get_page_owner_entity() will retrieve the whole page owner entity. If the page already knows who the page owner is, but the system doesn’t, the page can set the page owner by passing the GUID to elgg_set_page_owner_guid($guid).


The page owner entity can be any ElggEntity. If you wish to only apply some setting in case of a user or a group make sure you check that you have the correct entity.

Custom page owner handlers

Plugin developers can create page owner handlers, which could be necessary in certain cases, for example when integrating third party functionality. The handler will be a function which will need to get registered with elgg_register_plugin_hook_handler('page_owner', 'system', 'your_page_owner_function_name'); . The handler will only need to return a value (an integer GUID) when it knows for certain who the page owner is.

By default, the system uses default_page_owner_handler() to determine the page_owner from the following elements:

  • The username URL parameter
  • The owner_guid URL parameter
  • The URL path

It then passes off to any page owner handlers defined using the plugin hook. If no page owner can be determined, the page owner is set to 0, which is the same as the logged out user.

Permissions Check


As stated in the page, this method works only for granting write access to entities. You cannot use this method to retrieve or view entities for which the user does not have read access.

Elgg provides a mechanism of overriding write permissions check through the permissions_check plugin hook . This is useful for allowing plugin write to all accessible entities regardless of access settings. Entities that are hidden, however, will still be unavailable to the plugin.

Hooking permissions_check

In your plugin, you must register the plugin hook for permissions_check.

elgg_register_plugin_hook_handler('permissions_check', 'all', 'myplugin_permissions_check');

The override function

Now create the function that will be called by the permissions check hook. In this function we determine if the entity (in parameters) has write access. Since it is important to keep Elgg secure, write access should be given only after checking a variety of situations including page context, logged in user, etc. Note that this function can return 3 values: true if the entity has write access, false if the entity does not, and null if this plugin doesn’t care and the security system should consult other plugins.

function myplugin_permissions_check($hook_name, $entity_type, $return_value, $parameters) {
   $has_access = determine_access_somehow();

   if ($has_access === true) {
      return true;
   } else if ($has_access === false) {
      return false;

   return null;

Full Example

This is a full example using the context to determine if the entity has write access.


function myaccess_init() {
   // Register cron hook
   if (!elgg_get_plugin_setting('period', 'myaccess')) {
      elgg_set_plugin_setting('period', 'fiveminute', 'myaccess');

   // override permissions for the myaccess context
   elgg_register_plugin_hook_handler('permissions_check', 'all', 'myaccess_permissions_check');

   elgg_register_plugin_hook_handler('cron', elgg_get_plugin_setting('period', 'myaccess'), 'myaccess_cron');

 * Hook for cron event.
function myaccess_cron($event, $object_type, $object) {


   // returns all entities regardless of access permissions.
   // will NOT return hidden entities.
   $entities = get_entities();


 * Overrides default permissions for the myaccess context
function myaccess_permissions_check($hook_name, $entity_type, $return_value, $parameters) {
   if (elgg_in_context('myaccess_cron')) {
      return true;

   return null;

// Initialise plugin
register_elgg_event_handler('init', 'system', 'myaccess_init');

Plugin settings

You need to perform some extra steps if your plugin needs settings to be saved and controlled via the administration panel:

  • Create a file in your plugin’s default view folder called plugins/your_plugin/settings.php, where your_plugin is the name of your plugin’s directory in the mod hierarchy
  • Fill this file with the form elements you want to display together with internationalised text labels
  • Set the name attribute in your form components to params[`varname`] where varname is the name of the variable. These will be saved as private settings attached to a plugin entity. So, if your variable is called params[myparameter] your plugin (which is also passed to this view as $vars['entity']) will be called $vars['entity']->myparameter

An example settings.php would look like:

   <?php echo elgg_echo('myplugin:settings:limit'); ?>

   <select name="params[limit]">
      <option value="5" <?php if ($vars['entity']->limit == 5) echo " selected=\"yes\" "; ?>>5</option>
      <option value="8" <?php if ((!$vars['entity']->limit) || ($vars['entity']->limit == 8)) echo " selected=\"yes\" "; ?>>8</option>
      <option value="12" <?php if ($vars['entity']->limit == 12) echo " selected=\"yes\" "; ?>>12</option>
      <option value="15" <?php if ($vars['entity']->limit == 15) echo " selected=\"yes\" "; ?>>15</option>


You don’t need to add a save button or the form, this will be handled by the framework.


You cannot use form components that send no value when “off.” These include radio inputs and check boxes.

User settings

Your plugin might need to store per user settings too, and you would like to have your plugin’s options to appear in the user’s settings page. This is also easy to do and follows the same pattern as setting up the global plugin configuration explained earlier. The only difference is that instead of using a settings file you will use usersettings. So, the path to the user edit view for your plugin would be plugins/your_plugin/usersettings.php.


The title of the usersettings form will default to the plugin name. If you want to change this, add a translation for plugin_id:usersettings:title.

Retrieving settings in your code

To retrieve settings from your code use:

$setting = elgg_get_plugin_setting($name, $plugin_id);

or for user settings

$user_setting = elgg_get_plugin_user_setting($name, $user_guid, $plugin_id);


  • $name Is the value you want to retrieve
  • $user_guid Is the user you want to retrieve these for (defaults to the currently logged in user)
  • $plugin_name Is the name of the plugin (detected if run from within a plugin)

Setting values while in code

Values may also be set from within your plugin code, to do this use one of the following functions:

elgg_set_plugin_setting($name, $value, $plugin_id);


elgg_set_plugin_user_setting($name, $value, $user_guid, $plugin_id);


The $plugin_id needs to be provided when setting plugin (user)settings.

Default plugin (user) settings

If a plugin or a user not have a setting stored in the database, you sometimes have the need for a certain default value. You can pass this when using the getter functions.

$user_setting = elgg_get_plugin_user_setting($name, $user_guid, $plugin_id, $default);

$plugin_setting = elgg_get_plugin_setting($name, $plugin_id, $default);

Alternatively you can also provide default plugin and user settings in the elgg-plugin.php file.


return [
        'settings' => [
            'key' => 'value',
        'user_settings' => [
            'key' => 'value',


Elgg natively supports the “river”, an activity stream containing descriptions of activities performed by site members. This page gives an overview of adding events to the river in an Elgg plugin.

Pushing river items

Items are pushed to the activity river through a function call, which you must include in your plugins for the items to appear.

Here we add a river item telling that a user has created a new blog post:


        'view' => 'river/object/blog/create',
        'action_type' => 'create',
        'subject_guid' => $blog->owner_guid,
        'object_guid' => $blog->getGUID(),

All available parameters:

  • view => STR The view that will handle the river item (must exist)
  • action_type => STR An arbitrary string to define the action (e.g. ‘create’, ‘update’, ‘vote’, ‘review’, etc)
  • subject_guid => INT The GUID of the entity doing the action
  • object_guid => INT The GUID of the entity being acted upon
  • target_guid => INT The GUID of the the object entity’s container (optional)
  • access_id => INT The access ID of the river item (default: same as the object)
  • posted => INT The UNIX epoch timestamp of the river item (default: now)
  • annotation_id => INT The annotation ID associated with this river entry (optional)

When an item is deleted or changed, the river item will be updated automatically.

River views

In order for events to appear in the river you need to provide a corresponding view with the name specified in the function above.

We recommend /river/{type}/{subtype}/{action}, where:

  • {type} is the entity type of the content we’re interested in (object for objects, user for users, etc)
  • {subtype} is the entity subtype of the content we’re interested in (blog for blogs, photo_album for albums, etc)
  • {action} is the action that took place (‘’create’‘, ‘’update’‘, etc)

River item information will be passed in an object called $vars['item'], which contains the following important parameters:

  • $vars['item']->subject_guid The GUID of the user performing the action
  • $vars['item']->object_guid The GUID of the entity being acted upon

Timestamps etc will be generated for you.

For example, the blog plugin uses the following code for its river view:


$object = $vars['item']->getObjectEntity();

$excerpt = $object->excerpt ? $object->excerpt : $object->description;
$excerpt = strip_tags($excerpt);
$excerpt = elgg_get_excerpt($excerpt);

echo elgg_view('river/elements/layout', array(
        'item' => $vars['item'],
        'message' => $excerpt,


Customize the look and feel of Elgg.

A theme is a type of plugin that overrides display aspects of Elgg.

This guide assumes you are familiar with:

Create your plugin

Create your plugin as described in the developer guide.

  • Create a new directory under mod/
  • Create a new start.php
  • Create a manifest.xml file describing your theme.

Customize the CSS

As of Elgg 1.8, the css is split into several files based on what aspects of the site you’re theming. This allows you to tackle them one at a time, giving you a chance to make real progress without getting overwhelmed.

Here is a list of the existing CSS views:

  • elements/buttons.css: Provides a way to style all the different kinds of buttons your site will use. There are 5 kinds of buttons that plugins will expect to be available: action, cancel, delete, submit, and special.
  • elements/chrome.css: This file has some miscellaneous look-and-feel classes.
  • elements/components.css: This file contains many “css objects” that are used all over the site: media block, list, gallery, table, owner block, system messages, river, tags, photo, and comments.
  • elements/forms.css: This file determines what your forms and input elements will look like.
  • elements/icons.css: Contains styles for the icons and avatars used on your site.
  • elements/layout.css: Determines what your page layout will look like: sidebars, page wrapper, main body, header, footer, etc.
  • elements/modules.css: Lots of content in Elgg is displayed in boxes with a title and a content body. We called these modules. There are a few kinds: info, aside, featured, dropdown, popup, widget. Widget styles are included in this file too, since they are a subset of modules.
  • elements/navigation.css: This file determines what all your menus will look like.
  • elements/typography.css: This file determines what the content and headings of your site will look like.
  • rtl.css: Custom rules for users viewing your site in a right-to-left language.
  • admin.css: A completely separate theme for the admin area (usually not overridden).
  • elgg.css: Compiles all the core elements/* files into one file (DO NOT OVERRIDE).
  • elements/core.css: Contains base styles for the more complicated “css objects”. If you find yourself wanting to override this, you probably need to report a bug to Elgg core instead (DO NOT OVERRIDE).
  • elements/reset.css: Contains a reset stylesheet that forces elements to have the same default
View extension

There are two ways you can modify views:

The first way is to add extra stuff to an existing view via the extend view function from within your start.php’s initialization function.

For example, the following start.php will add mytheme/css to Elgg’s core css file:


    function mytheme_init() {
        elgg_extend_view('elgg.css', 'mytheme/css');

    elgg_register_event_handler('init', 'system', 'mytheme_init');
View overloading

Plugins can have a view hierarchy, any file that exists here will replace any files in the existing core view hierarchy... so for example, if my plugin has a file:


it will replace:


But only when the plugin is active.

This gives you total control over the way Elgg looks and behaves. It gives you the option to either slightly modify or totally replace existing views.


As of Elgg 2.0 the default Elgg icons come from the FontAwesome library. You can use any of these icons by calling:


icon-name can be any of the FontAwesome icons without the fa--prefix.


Starting in Elgg 1.8, we’ve provided you with some development tools to help you with theming: Turn on the “Developers” plugin and go to the “Theme Preview” page to start tracking your theme’s progress.

Customizing the front page

The main Elgg index page runs a plugin hook called ‘index,system’. If this returns true, it assumes that another front page has been drawn and doesn’t display the default page.

Therefore, you can override it by registering a function to the ‘index,system’ plugin hook and then returning true from that function.

Here’s a quick overview:

  • Create your new plugin
  • In the start.php you will need something like the following:

function pluginname_init() {
    // Replace the default index page
    elgg_register_plugin_hook_handler('index', 'system', 'new_index');

function new_index() {
    if (!include_once(dirname(dirname(__FILE__)) . "/pluginname/pages/index.php"))
        return false;

    return true;

// register for the init, system event when our plugin start.php is loaded
elgg_register_event_handler('init', 'system', 'pluginname_init');
  • Then, create an index page (/pluginname/pages/index.php) and use that to put the content you would like on the front page of your Elgg site.



Views are responsible for creating output. They handle everything from:

  • the layout of pages
  • chunks of presentation output (like a footer or a toolbar)
  • individual links and form inputs.
  • the images, js, and css needed by your web page

Using views

At their most basic level, the default views are just PHP files with snippets of html:

<h1>Hello, World!</h1>

Assuming this view is located at /views/default/hello.php, we could output it like so:

echo elgg_view('hello');

For your convenience, Elgg comes with quite a lot of views by default. In order to keep things manageable, they are organized into subdirectories. Elgg handles this situation quite nicely. For example, our simple view might live in /views/default/hello/world.php, in which case it would be called like so:

echo elgg_view('hello/world');

The name of the view simply reflects the location of the view in the views directory.

Views as templates

You can pass arbitrary data to a view via the $vars array. Our hello/world view might be modified to accept a variable like so:

<h1>Hello, <?= $vars['name']; ?>!</h1>

In this case, we can pass an arbitrary name parameter to the view like so:

echo elgg_view('hello/world', ['name' => 'World']);

which would produce the following output:

<h1>Hello, World!</h1>


Views don’t do any kind of automatic output sanitization by default. You are responsible for doing the correct sanitization yourself to prevent XSS attacks and the like.

Views as cacheable assets

As mentioned before, views can contain JS, CSS, or even images.

Asset views must meet certain requirements:

  • They must not take any $vars parameters
  • They must not change their output based on global state like
    • who is logged in
    • the time of day
  • They must contain a valid file extension
    • Bad: my/cool/template
    • Good: my/cool/template.html

For example, suppose you wanted to load some CSS on a page. You could define a view mystyles.css, which would look like so:

/* /views/default/mystyles.css */
.mystyles-foo {
  background: red;


Leave off the trailing ”.php” from the filename and Elgg will automatically recognize the view as cacheable.

To get a URL to this file, you would use elgg_get_simplecache_url:

// Returns "https://mysite.com/.../289124335/default/mystyles.css

Elgg automatically adds the magic numbers you see there for cache-busting and sets long-term expires headers on the returned file.


Elgg may decide to change the location or structure of the returned URL in a future release for whatever reason, and the cache-busting numbers change every time you flush Elgg’s caches, so the exact URL is not stable by design.

With that in mind, here’s a couple anti-patterns to avoid:

  • Don’t rely on the exact structure/location of this URL
  • Don’t try to generate the URLs yourself
  • Don’t store the returned URLs in a database

In your plugin’s init function, register the css file:

elgg_register_css('mystyles', elgg_get_simplecache_url('mystyles.css'));

Then on the page you want to load the css, call:


Views and third-party assets

The best way to serve third-party assets is through views. However, instead of manually copy/pasting the assets into the right location in /views/*, you can map the assets into the views system via the "views" key in your plugin’s elgg-plugin.php config file.

The views value must be a 2 dimensional array. The first level maps a viewtype to a list of view mappings. The secondary lists map view names to file paths, either absolute or relative to the Elgg root directory.

If you check your assets into source control, point to them like this:

<?php // mod/example/elgg-plugin.php
return [
    // view mappings
    'views' => [
        // viewtype
        'default' => [
            // view => /path/from/filesystem/root
            'js/jquery-ui.js' => __DIR__ . '/bower_components/jquery-ui/jquery-ui.min.js',

To point to assets installed with fxp/composer-asset-plugin, use install-root-relative paths by leaving off the leading slash:

<?php // mod/example/elgg-plugin.php
return [
    'views' => [
        'default' => [
            // view => path/from/install/root
            'js/jquery-ui.js' => 'vendor/bower-asset/jquery-ui/jquery-ui.min.js',

Elgg core uses this feature extensively, though the value is returned directly from /engine/views.php.


You don’t have to use Bower, Composer Asset Plugin, or any other script for managing your plugin’s assets, but we highly recommend using a package manager of some kind because it makes upgrading so much easier.

Specifying additional views directories

In elgg-plugin.php you can also specify directories to be scanned for views. Just provide a view name prefix ending with / and a directory path (like above).

<?php // mod/file/elgg-plugin.php
return [
    'views' => [
        'default' => [
            'file/icon/' => __DIR__ . '/graphics/icons',

With the above, files found within the icons folder will be interpreted as views. E.g. the view file/icon/general.gif will be created and mapped to mod/file/graphics/icons/general.gif.


This is a fully recursive scan. All files found will be brought into the views system.

Multiple paths can share the same prefix, just give an array of paths:

<?php // mod/file/elgg-plugin.php
return [
    'views' => [
        'default' => [
            'file/icon/' => [
                __DIR__ . '/graphics/icons',
                __DIR__ . '/more_icons', // processed 2nd (may override)


You might be wondering: “Why /views/default/hello/world.php instead of just /views/hello/world.php?”.

The subdirectory under /views determines the viewtype of the views below it. A viewtype generally corresponds to the output format of the views.

The default viewtype is assumed to be HTML and other static assets necessary to render a responsive web page in a desktop or mobile browser, but it could also be:

  • RSS
  • ATOM
  • JSON
  • Mobile-optimized HTML
  • TV-optimized HTML
  • Any number of other data formats

You can force Elgg to use a particular viewtype to render the page by setting the view input variable like so: https://mysite.com/?view=rss.

You could also write a plugin to set this automatically using the elgg_set_viewtype() function. For example, your plugin might detect that the page was accessed with an iPhone’s browser string, and set the viewtype to iphone by calling:


The plugin would presumably also supply a set of views optimized for those devices.

Altering views via plugins

Without modifying Elgg’s core, Elgg provides several ways to customize almost all output:

Overriding views

Views in plugin directories always override views in the core directory; however, when plugins override the views of other plugins, later plugins take precedent.

For example, if we wanted to customize the hello/world view to use an h2 instead of an h1, we could create a file at /mod/example/views/default/hello/world.php like this:

<h2>Hello, <?= $vars['name']; ?></h2>


When considering long-term maintenance, overriding views in the core and bundled plugins has a cost: Upgrades may bring changes in views, and if you have overridden them, you will not get those changes.

You may instead want to alter the input or the output of the view via plugin hooks.


Elgg caches view locations. This means that you should disable the system cache while developing with views. When you install the changes to a production environment you must flush the caches.

Extending views

There may be other situations in which you don’t want to override the whole view, you just want to prepend or append some more content to it. In Elgg this is called extending a view.

For example, instead of overriding the hello/world view, we could extend it like so:

elgg_extend_view('hello/world', 'hello/greeting');

If the contents of /views/default/hello/greeting.php is:

<h2>How are you today?</h2>

Then every time we call elgg_view('hello/world');, we’ll get:

<h1>Hello, World!</h1>
<h2>How are you today?</h2>

You can prepend views by passing a value to the 3rd parameter that is less than 500:

// appends 'hello/greeting' to every occurrence of 'hello/world'
elgg_extend_view('hello/world', 'hello/greeting');

// prepends 'hello/greeting' to every occurrence of 'hello/world'
elgg_extend_view('hello/world', 'hello/greeting', 450);

All view extensions should be registered in your plugin’s init,system event handler in start.php.

Altering view input

It may be useful to alter a view’s $vars array before the view is rendered.

Since 1.11, before each view rendering the $vars array is filtered by the plugin hook ["view_vars", $view_name]. Each registered handler function is passed these arguments:

  • $hook - the string "view_vars"
  • $view_name - the view name being rendered (the first argument passed to elgg_view())
  • $returnvalue - the modified $vars array
  • $params - an array containing:
    • vars - the original $vars array, unaltered
    • view - the view name
    • viewtype - The viewtype being rendered
Altering view input example

Here we’ll alter the default pagination limit for the comments view:

elgg_register_plugin_hook_handler('view_vars', 'page/elements/comments', 'myplugin_alter_comments_limit');

function myplugin_alter_comments_limit($hook, $type, $vars, $params) {
    // only 10 comments per page
    $vars['limit'] = elgg_extract('limit', $vars, 10);
    return $vars;
Altering view output

Sometimes it is preferable to alter the output of a view instead of overriding it.

The output of each view is run through the plugin hook ["view", $view_name] before being returned by elgg_view(). Each registered handler function is passed these arguments:

  • $hook - the string "view"
  • $view_name - the view name being rendered (the first argument passed to elgg_view())
  • $result - the modified output of the view
  • $params - an array containing:
    • viewtype - The viewtype being rendered

To alter the view output, the handler just needs to alter $returnvalue and return a new string.

Altering view output example

Here we’ll eliminate breadcrumbs that don’t have at least one link.

elgg_register_plugin_hook_handler('view', 'navigation/breadcrumbs', 'myplugin_alter_breadcrumb');

function myplugin_alter_breadcrumb($hook, $type, $returnvalue, $params) {
    // we only want to alter when viewtype is "default"
    if ($params['viewtype'] !== 'default') {
        return $returnvalue;

    // output nothing if the content doesn't have a single link
    if (false === strpos($returnvalue, '<a ')) {
        return '';

    // returning nothing means "don't alter the returnvalue"
Replacing view output completely

You can pre-set the view output by setting $vars['__view_output']. The value will be returned as a string. View extensions will not be used and the view hook will not be triggered.

elgg_register_plugin_hook_handler('view_vars', 'navigation/breadcrumbs', 'myplugin_no_page_breadcrumbs');

function myplugin_no_page_breadcrumbs($hook, $type, $vars, $params) {
    if (elgg_in_context('pages')) {
        return ['__view_output' => ""];

Displaying entities

If you don’t know what an entity is, check this page out first.

The following code will automatically display the entity in $entity:

echo elgg_view_entity($entity);

As you’ll know from the data model introduction, all entities have a type (object, site, user or group), and optionally a subtype (which could be anything - ‘blog’, ‘forumpost’, ‘banana’).

elgg_view_entity will automatically look for a view called type/subtype; if there’s no subtype, it will look for type/type. Failing that, before it gives up completely it tries type/default.

RSS feeds in Elgg generally work by outputting the object/default view in the ‘rss’ viewtype.

For example, the view to display a blog post might be object/blog. The view to display a user is user/default.

Full and partial entity views

elgg_view_entity actually has a number of parameters, although only the very first one is required. The first three are:

  • $entity - The entity to display
  • $viewtype - The viewtype to display in (defaults to the one we’re currently in, but it can be forced - eg to display a snippet of RSS within an HTML page)
  • $full_view - Whether to display a full version of the entity. (Defaults to false.)

This last parameter is passed to the view as $vars['full_view']. It’s up to you what you do with it; the usual behaviour is to only display comments and similar information if this is set to true.

Listing entities

This is then used in the provided listing functions. To automatically display a list of blog posts (see the full tutorial), you can call:

echo elgg_list_entities([
    'type' => 'object',
    'subtype' => 'blog',

This function checks to see if there are any entities; if there are, it first displays the navigation/pagination view in order to display a way to move from page to page. It then repeatedly calls elgg_view_entity on each entity before returning the result.

Note that elgg_list_entities allows the URL to set its limit and offset options, so set those explicitly if you need particular values (e.g. if you’re not using it for pagination).

Elgg knows that it can automatically supply an RSS feed on pages that use elgg_list_entities. It initializes the ["head","page"] plugin hook (which is used by the header) in order to provide RSS autodiscovery, which is why you can see the orange RSS icon on those pages in some browsers.

If your entity list will display the entity owners, you can improve performance a bit by preloading all owner entities:

echo elgg_list_entities([
    'type' => 'object',
    'subtype' => 'blog',

    // enable owner preloading
    'preload_owners' => true,

See also this background information on Elgg’s database.

If you want to show a message when the list does not contain items to list, you can pass a no_results message. If you want even more controle over the no_results message you can also pass a Closure (an anonymous function).

echo elgg_list_entities([
    'type' => 'object',
    'subtype' => 'blog',

    'no_results' => elgg_echo('notfound'),
Rendering a list with an alternate view

Since 1.11, you can define an alternative view to render list items using 'item_view' parameter.

In some cases, default entity views may be unsuitable for your needs. Using item_view allows you to customize the look, while preserving pagination, list’s HTML markup etc.

Consider these two examples:

echo elgg_list_entities_from_relationship([
    'type' => 'group',
    'relationship' => 'member',
    'relationship_guid' => elgg_get_logged_in_user_guid(),
    'inverse_relationship' => false,
    'full_view' => false,
echo elgg_list_entities_from_relationship([
    'type' => 'group',
    'relationship' => 'invited',
    'relationship_guid' => (int) $user_guid,
    'inverse_relationship' => true,
    'item_view' => 'group/format/invitationrequest',

In the first example, we are displaying a list of groups a user is a member of using the default group view. In the second example, we want to display a list of groups the user was invited to.

Since invitations are not entities, they do not have their own views and can not be listed using elgg_list_*. We are providing an alternative item view, that will use the group entity to display an invitation that contains a group name and buttons to access or reject the invitation.

Rendering a list as a table

Since 2.3 you can render lists as tables. Set $options['list_type'] = 'table' and provide an array of TableColumn objects as $options['columns']. The service elgg()->table_columns provides several methods to create column objects based around existing views (like page/components/column/*), properties, or methods.

In this example, we list the latest my_plugin objects in a table of 3 columns: entity icon, the display name, and a friendly format of the time.

echo elgg_list_entities([
    'type' => 'object',
    'subtype' => 'my_plugin',

    'list_type' => 'table',
    'columns' => [
        elgg()->table_columns->time_created(null, [
            'format' => 'friendly',

See the Elgg\Views\TableColumn\ColumnFactory class for more details on how columns are specified and rendered. You can add or override methods of elgg()->table_columns in a variety of ways, based on views, properties/methods on the items, or given functions.


Widgets are content areas that users can drag around their page to customize the layout. They can typically be customized by their owner to show more/less content and determine who sees the widget. By default Elgg provides plugins for customizing the profile page and dashboard via widgets.


To create a widget, create two views:

  • widgets/widget/edit
  • widgets/widget/content

content.php is responsible for all the content that will output within the widget. The edit.php file contains any extra edit functions you wish to present to the user. You do not need to add access level as this comes as part of the widget framework.


Using HTML checkboxes to set widget flags is problematic because if unchecked, the checkbox input is omitted from form submission. The effect is that you can only set and not clear flags. The “input/checkboxes” view will not work properly in a widget’s edit panel.

Register the widget

Once you have created your edit and view pages, you need to initialize the plugin widget.

The easiest way to do this is to add the widgets section to your elgg-plugin.php config file.

return [
        'widgets' => [
                'filerepo' => [
                        'context' => ['profile'],

Alternatively you can also use an function to add a widget. This is done within the plugins init() function.

// Add generic new file widget
    'id' => 'filerepo',
    'name' => elgg_echo('widgets:filerepo:name'),
    'description' => elgg_echo('widgets:filerepo:description'),
    'context' => ['profile'],


The only required attribute is the id.

Multiple widgets

It is possible to add multiple widgets for a plugin. You just initialize as many widget directories as you need.

 // Add generic new file widget
     'id' => 'filerepo',
     'name' => elgg_echo('widgets:filerepo:name'),
     'description' => elgg_echo('widgets:filerepo:description'),
     'context' => ['profile'],

 // Add a second file widget
     'id' => 'filerepo2',
     'name' => elgg_echo('widgets:filerepo2:name'),
     'description' => elgg_echo('widgets:filerepo2:description'),
     'context' => ['dashboard'],

 // Add a third file widget
     'id' => 'filerepo3',
     'name' => elgg_echo('widgets:filerepo3:name'),
     'description' => elgg_echo('widgets:filerepo3:description'),
     'context' => ['profile', 'dashboard'],

Make sure you have the corresponding directories within your plugin views structure:

Magic widget name and description

When registering a widget you can omit providing a name and a description. If a translation in the following format is provided, they will be used. For the name: widgets:<widget_id>:name and for the description widgets:<widget_id>:description. If you make sure these translation are available in a translation file, you have very little work registering the widget.

elgg_register_widget_type(['id' => 'filerepo']);
How to restrict where widgets can be used

The widget can specify the context that it can be used in (just profile, just dashboard, etc.).

    'id' => 'filerepo',
    'context' => ['profile', 'dashboard', 'other_context'],
Allow multiple widgets on the same page

By default you can only add one widget of the same type on the page. If you want more of the same widget on the page, you can specify this when registering the widget:

    'id' => 'filerepo',
    'multiple' => true,
Register widgets in a hook

If, for example, you wish to conditionally register widgets you can also use a hook to register widgets.

function my_plugin_init() {
    elgg_register_plugin_hook_handler('handlers', 'widgets', 'my_plugin_conditional_widgets_hook');

function my_plugin_conditional_widgets_hook($hook, $type, $return, $params) {
    if (!elgg_is_active_plugin('file')) {

    $return[] = \Elgg\WidgetDefinition::factory([
        'id' => 'filerepo',

    return $return;
Modify widget properties of existing widget registration

If, for example, you wish to change the allowed contexts of an already registered widget you can do so by re-registering the widget with elgg_register_widget_type as it will override an already existing widget definition. If you want even more control you can also use the handlers, widgets hook to change the widget definition.

function my_plugin_init() {
    elgg_register_plugin_hook_handler('handlers', 'widgets', 'my_plugin_change_widget_definition_hook');

function my_plugin_change_widget_definition_hook($hook, $type, $return, $params) {
    foreach ($return as $key => $widget) {
        if ($widget->id === 'filerepo') {
            $return[$key]->multiple = false;

    return $return;

Default widgets

If your plugin uses the widget canvas, you can register default widget support with Elgg core, which will handle everything else.

To announce default widget support in your plugin, register for the get_list, default_widgets plugin hook:

elgg_register_plugin_hook_handler('get_list', 'default_widgets', 'my_plugin_default_widgets_hook');

In the plugin hook handler, push an array into the return value defining your default widget support and when to create default widgets. Arrays require the following keys to be defined:

  • name - The name of the widgets page. This is displayed on the tab in the admin interface.
  • widget_context - The context the widgets page is called from. (If not explicitly set, this is your plugin’s id.)
  • widget_columns - How many columns the widgets page will use.
  • event - The Elgg event to create new widgets for. This is usually create.
  • entity_type - The entity type to create new widgets for.
  • entity_subtype - The entity subtype to create new widgets for. The can be ELGG_ENTITIES_ANY_VALUE to create for all entity types.

When an object triggers an event that matches the event, entity_type, and entity_subtype parameters passed, Elgg core will look for default widgets that match the widget_context and will copy them to that object’s owner_guid and container_guid. All widget settings will also be copied.

function my_plugin_default_widgets_hook($hook, $type, $return, $params) {
    $return[] = array(
        'name' => elgg_echo('my_plugin'),
        'widget_context' => 'my_plugin',
        'widget_columns' => 3,

        'event' => 'create',
        'entity_type' => 'user',
        'entity_subtype' => ELGG_ENTITIES_ANY_VALUE,

    return $return;

Walled Garden

Elgg supports a “Walled Garden” mode. In this mode, almost all pages are restricted to logged in users. This is useful for sites that don’t allow public registration.

Activating Walled Garden mode

To activate Walled Garden mode in Elgg 1.8, go to the Administration section. On the right sidebar menu, under the “Configure” section, expand “Settings,” then click on “Advanced.”

From the Advanced Settings page, find the option labelled “Restrict pages to logged-in users.” Enable this option, then click “Save” to switch your site into Walled Garden mode.

Exposing pages through Walled Gardens

Many plugins extend Elgg by adding pages. Walled Garden mode will prevent these pages from being viewed by logged out users. Elgg uses plugin hook to manage which pages are visible through the Walled Garden.

Plugin authors must register pages as public if they should be viewable through Walled Gardens by responding to the public_pages, walled_garden plugin hook.

The returned value is an array of regexp expressions for public pages.

The following code shows how to expose http://example.org/my_plugin/public_page through a Walled Garden. This assumes the plugin has registered a Page handler for my_plugin.

elgg_register_plugin_hook_handler('public_pages', 'walled_garden', 'my_plugin_walled_garden_public_pages');

function my_plugin_walled_garden_public_pages($hook, $type, $pages) {
   $pages[] = 'my_plugin/public_page';
   return $pages;

Web services

Build an HTTP API for your site.

Elgg provides a powerful framework for building web services. This allows developers to expose functionality to other web sites and desktop applications along with doing integrations with third-party web applications. While we call the API RESTful, it is actually a REST/RPC hybrid similar to the APIs provided by sites like Flickr and Twitter.

To create an API for your Elgg site, you need to do 4 things:

  • enable the web services plugin
  • expose methods
  • setup API authentication
  • setup user authentication

Additionally, you may want to control what types of authentication are available on your site. This will also be covered.


It is crucial that the web services are consumed via secure protocols. Do not enable web services if your site is not served via HTTPs. This is especially important if you allow API key only authentication.

If you are using third-party tools that expose API methods, make sure to carry out a thorough security audit. You may want to make sure that API authentication is required for ALL methods, even if they require user authentication. Methods that do not require API authentication can be easily abused to spam your site.

Ensure that the validity of API keys is limited and provide mechanisms for your API clients to renew their keys.

Exposing methods

The function to use to expose a method is elgg_ws_expose_function(). As an example, let’s assume you want to expose a function that echos text back to the calling application. The function could look like this

function my_echo($string) {
    return $string;

Since we are providing this function to allow developers to test their API clients, we will require neither API authentication nor user authentication. This call registers the function with the web services API framework:

                "string" => [
                        'type' => 'string',
        'A testing method which echos back a string',

If you add this code to a plugin and then go to http://yoursite.com/services/api/rest/json/?method=system.api.list, you should now see your test.echo method listed as an API call. Further, to test the exposed method from a web browser, you could hit the url: http://yoursite.com/services/api/rest/json/?method=test.echo&string=testing and you should see JSON data like this:


Plugins can filter the output of individual API methods by registering a handler for 'rest:output',$method plugin hook.

Response formats

JSON is the default format, however XML and serialized PHP can be fetched by enabling the data_views plugin and substituting xml or php in place of json in the above URLs.

You can also add additional response formats by defining new viewtypes.


Parameters expected by each method should be listed as an associative array, where the key represents the parameter name, and the value contains an array with type, default and required fields.

Values submitted with the API request for each parameter should match the declared type. API will throw on exception if validation fails.

Recognized parameter types are:

  • integer (or int)
  • boolean (or bool)
  • string
  • float
  • array

Unrecognized types will throw an API exception.

You can use additional fields to describe your parameter, e.g. description.

                'name' => [
                        'type' => 'string',
                        'required' => true,
                        'description' => 'Name of the person to be greeted by the API',
                'greeting' => [
                        'type' => 'string',
                        'required' => false,
                        'default' => 'Hello',
                        'description' => 'Greeting to be used, e.g. "Good day" or "Hi"',
        'A testing method which greets the user with a custom greeting',


If a missing parameter has no default value, the argument will be null. Before 2.1, a bug caused later arguments to be shifted left in this case.

Receive parameters as associative array

If you have a large number of method parameters, you can force the execution script to invoke the callback function with a single argument that contains an associative array of parameter => input pairs (instead of each parameter being a separate argument). To do that, set $assoc to true in elgg_ws_expose_function().

function greet_me($values) {
        $name = elgg_extract('name', $values);
        $greeting = elgg_extract('greeting', $values, 'Hello');
        return "$greeting, $name";

                "name" => [
                        'type' => 'string',
                "greeting" => [
                        'type' => 'string',
                        'default' => 'Hello',
                        'required' => false,
        'A testing method which echos a greeting',
        true // $assoc makes the callback receive an associative array


If a missing parameter has no default value, null will be used.

API authentication

You may want to control access to some of the functions that you expose. Perhaps you are exposing functions in order to integrate Elgg with another open source platform on the same server. In that case, you only want to allow that other application access to these methods. Another possibility is that you want to limit what external developers have access to your API. Or maybe you want to limit how many calls a developer can make against your API in a single day.

In all of these cases, you can use Elgg’s API authentication functions to control access. Elgg provides two built-in methods to perform API authentication: key based and HMAC signature based. You can also add your own authentication methods. The key based approach is very similar to what Google, Flickr, or Twitter. Developers can request a key (a random string) and pass that key with all calls that require API authentication. The keys are stored in the database and if an API call is made without a key or a bad key, the call is denied and an error message is returned.

Key-based authentication

As an example, let’s write a function that returns the number of users that have viewed the site in the last x minutes.

function count_active_users($minutes=10) {
    $seconds = 60 * $minutes;
    $count = count(find_active_users($seconds, 9999));
    return $count;

Now, let’s expose it and make the number of minutes an optional parameter:

                "minutes" => [
                        'type' => 'int',
                        'required' => false,
        'Number of users who have used the site in the past x minutes',

This function is now available and if you check system.api.list, you will see that it requires API authentication. If you hit the method with a web browser, it will return an error message about failing the API authentication. To test this method, you need an API key. Fortunately, there is a plugin called apiadmin that creates keys for you. It is available in the Elgg plugin repository. It will return a public and private key and you will use the public key for this kind of API authentication. Grab a key and then do a GET request with your browser on this API method passing in the key string as the parameter api_key. It might look something like this: http://yoursite.com/services/api/rest/xml/?method=users.active&api_key=1140321cb56c71710c38feefdf72bc462938f59f.

Signature-based authentication

The HMAC Authentication is similar to what is used with OAuth or Amazon’s S3 service. This involves both the public and private key. If you want to be very sure that the API calls are coming from the developer you think they are coming from and you want to make sure the data is not being tampered with during transmission, you would use this authentication method. Be aware that it is much more involved and could turn off developers when there are other sites out there with key-based authentication.


With the addition of the OAuth plugin, Elgg also fully supports the OAuth 1.0a authorization standard. Clients can then use standard OAuth libraries to make any API calls to the site.

User authentication

So far you have been allowing developers to pull data out of your Elgg site. Now we’ll move on to pushing data into Elgg. In this case, it is going to be done by a user. Maybe you have created a desktop application that allows your Users to post to the wire without going to the site. You need to expose a method for posting to the wire and you need to make sure that a user cannot post using someone else’s account. Elgg provides a token-based approach for user authentication. It allows a user to submit their username and password in exchange for a token using the method auth.gettoken. This token can then be used for some amount of time to authenticate all calls to the API before it expires by passing it as the parameter auth_token. If you do not want to have your users trusting their passwords to 3rd-party applications, you can also extend the current capability to use an approach like OAuth.

Let’s write our wire posting function:

function my_post_to_wire($text) {

    $text = substr($text, 0, 140);

    $access = ACCESS_PUBLIC;

    // returns guid of wire post
    return thewire_save_post($text, $access, "api");

Exposing this function is the same as the previous except we require user authentication and we’re going to make this use POST rather than GET HTTP requests.

                "text" => [
                        'type' => 'string',
        'Post to the wire. 140 characters or less',

Please note that you will not be able to test this using a web browser as you did with the other methods. You need to write some client code to do this. There is some example client code in /engine/lib/api.php. Take a look at send_api_post_call(). You can also do a search for clients that have been written for the APIs of Flickr or Twitter or any other similar API. You will find a wide variety written in almost any language you can think of.

Building out your API

As soon as you feel comfortable with Elgg’s web services API framework, you will want to step back and design your API. What sort of data are you trying to expose? Who or what will be API users? How do you want them to get access to authentication keys? How are you going to document your API? Be sure to take a look at the APIs created by popular Web 2.0 sites for inspiration. If you are looking for 3rd party developers to build applications using your API, you will probably want to provide one or more language-specific clients.

Determining the authentication available

Elgg’s web services API uses a type of pluggable authentication module (PAM) architecture to manage how users and developers are authenticated. This provides you the flexibility to add and remove authentication modules. Do you want to not use the default user authentication PAM but would prefer using OAuth? You can do this.

The first step is registering a callback function for the rest, init plugin hook:

register_plugin_hook('rest', 'init', 'rest_plugin_setup_pams');

Then in the callback function, you register the PAMs that you want to use:

function rest_plugin_setup_pams() {
    // user token can also be used for user authentication

    // simple API key check
    register_pam_handler('api_auth_key', "sufficient", "api");

    // override the default pams
    return true;

When testing, you may find it useful to register the pam_auth_session PAM so that you can easily test your methods from the browser. Be careful not to use this PAM on a production site because it could open up your users to a CSRF attack.

Right now, the only other PAMs publicly available besides those provided by the Elgg core are the OAuth PAMs. See Justin Richer’s OAuth plugin for more detail.

Upgrading Plugins

Prepare your plugin for the next version of Elgg.

See the administrator guides for how to upgrade a live site.


From 2.x to 3.0

Removed views
  • forms/admin/site/advanced/system
  • resources/file/download
  • output/checkboxes: use output/tags if you want the same behaviour
  • input/write_access: mod/pages now uses the access:collections:write plugin hook.
  • invitefriends/form
  • page/layouts/content: use page/layouts/default
  • page/layouts/one_column: use page/layouts/default
  • page/layouts/one_sidebar: use page/layouts/default
  • page/layouts/two_sidebar: use page/layouts/default
  • page/layouts/walled_garden: use page/layouts/default
  • page/layouts/walled_garden/cancel_button
  • page/layouts/two_column_left_sidebar
  • page/layouts/widgets/add_panel
  • page/elements/topbar_wrapper: update your use of page/elements/topbar to include a check for a logged in user
  • groups/group_sort_menu: use register, filter:menu:groups/all plugin hook
  • subscriptions/form/additions: extend notifications/settings/other instead
  • likes/count: modifications can now be done to the likes_count menu item
  • likes/css: likes now uses elgg/likes.css
  • messageboard/css
  • notifications/subscriptions/personal
  • notifications/subscriptions/collections
  • notifications/subscriptions/form
  • notifications/subscriptions/jsfuncs
  • notifications/subscriptions/forminternals
  • notifications/css
  • river/item: use elgg_view_river_item() to render river items
  • admin.js
  • aalborg_theme/homepage.png
  • aalborg_theme/css
  • resources/avatar/view: Use entity icon API
  • ajax_loader.gif
  • button_background.gif
  • button_graduation.png
  • elgg_toolbar_logo.gif
  • header_shadow.png
  • powered_by_elgg_badge_drk_bckgnd.gif
  • powered_by_elgg_badge_light_bckgnd.gif
  • sidebar_background.gif
  • spacer.gif
  • toptoolbar_background.gif
  • two_sidebar_background.gif
  • ajax_loader_bw.gif: use graphics/ajax_loader_bw.gif
  • elgg_logo.png: use graphics/elgg_logo.png
  • favicon-128.png: use graphics/favicon-128.png
  • favicon-16.png: use graphics/favicon-16.png
  • favicon-32.png: use graphics/favicon-32.png
  • favicon-64.png: use graphics/favicon-64.png
  • favicon.ico: use graphics/favicon.ico
  • favicon.svg: use graphics/favicon.svg
  • friendspicker.png: use graphics/friendspicker.png
  • walled_garden.jpg: use graphics/walled_garden.jpg
  • core/friends/collection
  • core/friends/collections
  • core/friends/collectiontabs
  • core/friends/tablelist
  • core/friends/talbelistcountupdate
  • lightbox/elgg-colorbox-theme/colorbox-images/*`
  • navigation/menu/page: now uses navigation/menu/default and a prepare hook
  • page/elements/by_line: Use object/elements/imprint
Removed functions/methods

All the functions in engine/lib/deprecated-1.9.php were removed. See https://github.com/Elgg/Elgg/blob/2.0/engine/lib/deprecated-1.9.php for these functions. Each @deprecated declaration includes instructions on what to use instead. All the functions in engine/lib/deprecated-1.10.php were removed. See https://github.com/Elgg/Elgg/blob/2.0/engine/lib/deprecated-1.10.php for these functions. Each @deprecated declaration includes instructions on what to use instead.

  • _elgg_manage_pagesetup
  • datalist_get
  • datalist_set
  • developers_setup_menu
  • elgg_get_metastring_id
  • elgg_get_metastring_map
  • get_default_filestore
  • garbagecollector_orphaned_metastrings
  • groups_setup_sidebar_menus
  • set_default_filestore
  • generate_user_password: Use ElggUser::setPassword
  • row_to_elggrelationship
  • run_function_once: Use Elgg\Upgrade\Batch interface
  • system_messages
  • notifications_plugin_pagesetup
  • elgg_format_url: Use elgg_format_element() or the “output/text” view for HTML escaping.
  • get_site_by_url
  • ElggEntity::addToSite
  • ElggEntity::getSites
  • ElggEntity::removeFromSite
  • ElggEntity::isFullyLoaded
  • ElggFile::setFilestore: ElggFile objects can no longer use custom filestores.
  • ElggFile::size: Use getSize
  • ElggDiskFilestore::makeFileMatrix: Use Elgg\EntityDirLocator
  • ElggData::get: Usually can be replaced by property read
  • ElggData::getClassName: Use get_class()
  • ElggData::set: Usually can be replaced by property write
  • ElggEntity::setURL: See getURL for details on the plugin hook
  • ElggFileCache::sanitise_filename: Use sanitizeFilename
  • ElggMenuBuilder::compareByWeight: Use compareByPriority
  • ElggMenuItem::getWeight: Use getPriority
  • ElggMenuItem::getContent: Use elgg_view_menu_item()
  • ElggMenuItem::setWeight: Use setPriority
  • ElggRiverItem::getPostedTime: Use getTimePosted
  • ElggSession has removed all deprecated methods
  • ElggSite::addEntity
  • ElggSite::addObject
  • ElggSite::addUser
  • ElggSite::getEntities: Use elgg_get_entities_from_relationship()
  • ElggSite::getExportableValues: Use toObject
  • ElggSite::getMembers: Use elgg_get_entities_from_relationship()
  • ElggSite::getObjects: Use elgg_get_entities_from_relationship()
  • ElggSite::listMembers: Use elgg_list_entities_from_relationship()
  • ElggSite::removeEntity
  • ElggSite::removeObject
  • ElggSite::removeUser
  • ElggSite::isPublicPage: Logic moved to the router and should not be accessed directly
  • ElggSite::checkWalledGarden: Logic moved to the router and should not be accessed directly
  • ElggUser::countObjects: Use elgg_get_entities()
  • Logger::getClassName: Use get_class()
  • Elgg\Application\Database::getTablePrefix: Read the prefix property
  • elgg_view_access_collections()
  • ElggSession::get_ignore_access: Use getIgnoreAccess
  • ElggSession::set_ignore_access: Use setIgnoreAccess
  • profile_pagesetup
  • groups_setup_sidebar_menus
  • groups_set_icon_url
Removed global vars
  • $SESSION: Use the API provided by elgg_get_session()
  • $CONFIG->site_id: Use 1
  • $CONFIG->search_info
  • $CONFIG->input: Use set_input and get_input
Removed classes/interfaces
  • FilePluginFile: replace with ElggFile (or load with get_entity())
  • Elgg_Notifications_Notification
  • Elgg\Database\EntityTable\UserFetchResultException.php
  • Elgg\Database\MetastringsTable
  • Exportable and its methods export and getExportableValues: Use toObject
  • ExportException
  • Importable and its method import.
  • ImportException
  • ODD and all classes beginning with ODD*.
  • XmlElement
  • Elgg_Notifications_Event: Use \Elgg\Notifications\Event
Schema changes

The storage engine for the database tables has been changed from MyISAM to InnoDB. You maybe need to optimize your database settings for this change. The datalists table has been removed. All settings from datalists table have been merged into the config table.

Metastrings in the database have been denormalized for performance purposes. We removed the metastrings table and put all the string values directly in the metadata and annotation tables. You need to update your custom queries to reflect these changes. Also the msv and msn table aliases are no longer available. It is best practice not to rely on the table aliases used in core queries. If you need to use custom clauses you should do your own joins.

From the “users_entity” table, the password and hash columns have been removed.

Metadata no longer are access-controlled

Metadata is available in all contexts. If your plugin created metadata with restricted access, those restrictions will not be honored. You should use annotations or entities instead, which do provide access control.

Do not read or write to the access_id property on ElggMetadata objects.

Multi Site Changes

Pre 3.0 Elgg has some (partial) support for having multiple sites in the same database. This Multi Site concept has been completely removed in 3.0. Entities no longer have the site_guid attribute. This means there is no longer the ability to have entities on different sites. If you currently have multiple sites in your database, upgrading Elgg to 3.0 will fail. You need to separate the different sites into separate databases/tables.

Related to the removal of the Multi Site concept in Elgg, there is no longer a need for entities having a ‘member_of_site’ relationship with the Site Entity. All functions related to adding/removing this relationship has been removed. All existing relationships will be removed as part of this upgrade.

Setting ElggSite::$url has no effect. Reading the site URL always pulls from the $CONFIG->wwwroot set in settings.php, or computed by Symphony Request.

ElggSite::save() will fail if it isn’t the main site.

Search changes

The FULLTEXT indices have been removed on various tables. The search plugin will now always use a like query when performing a search.

  • search_get_where_sql no longer supports the argument use_fulltext
  • search_get_ft_min_max function is removed
  • $CONFIG->search_info is no longer provided
Removed libraries
  • elgg:discussion
Removed pagehandling
  • file/download
  • groupicon
  • twitterservice
  • collections/pickercallback
Removed actions
  • file/download: Use elgg_get_inline_url or elgg_get_download_url
  • import/opendd
Inheritance changes
  • ElggData (and hence most Elgg domain objects) no longer implements Exportable
  • ElggEntity no longer implements Importable
  • ElggGroup no longer implements Friendable
  • ElggRelationship no longer implements Importable
  • ElggSession no longer implements ArrayAccess
  • Elgg\Application\Database no longer extends Elgg\Database
Removed JavaScript APIs
  • admin.js
  • elgg.widgets: Use the elgg/widgets module. The “widgets” layouts do this module automatically
  • lightbox.js: Use the elgg/lightbox module as needed
  • lightbox/settings.js: Use the getOptions, ui.lightbox JS hook or the data-colorbox-opts attribute
  • elgg.ui.popupClose: Use the elgg/popup module
  • elgg.ui.popupOpen: Use the elgg/popup module
  • elgg.ui.initAccessInputs
  • elgg.ui.river
  • elgg.ui.initDatePicker: Use the input/date module
  • elgg.ui.likesPopupHandler
  • elgg.embed: Use the elgg/embed module
  • embed/custom_insert_js: Use the embed, editor JS hook
  • elgg/ckeditor.js: replaced by elgg-ckeditor.js
  • elgg/ckeditor/set-basepath.js
  • elgg/ckeditor/insert.js
  • likes.js: The elgg/likes module is loaded automatically
  • messageboard.js
  • elgg.autocomplete is no longer defined.
  • elgg.messageboard is no longer defined.
  • jQuery.fn.friendsPicker
  • elgg.ui.toggleMenu is no longer defined
Removed hooks/events
  • Event login, user: Use login:before or login:after. Note the user is not logged in during the login:before event.
  • Event delete, annotations: Use delete, annotation
  • Event pagesetup, system: Use the menu or page shell hooks instead.
  • Hook index, system: Override the resources/index view
  • Hook object:notifications, <type>: Use the hook send:before, notifications
  • Hook output:before, layout: Use view_vars, page/layout/<layout_name>
  • Hook output:after, layout: Use view, page/layout/<layout_name>
Removed forms/actions
  • notificationsettings/save form and action
  • notificationsettings/groupsave form and action
APIs that now accept only an $options array
  • ElggEntity::getAnnotations
  • ElggEntity::getEntitiesFromRelationship
  • ElggGroup::getMembers
  • ElggUser::getGroups
  • ElggUser::getFriends (as part of Friendable)
  • ElggUser::getFriendsOf (as part of Friendable)
  • ElggUser::getFriendsObjects (as part of Friendable)
  • ElggUser::getObjects (as part of Friendable)
  • find_active_users
  • elgg_get_admin_notices
Plugin functions that now require an explicit $plugin_id
  • elgg_get_all_plugin_user_settings
  • elgg_set_plugin_user_setting
  • elgg_unset_plugin_user_setting
  • elgg_get_plugin_user_setting
  • elgg_set_plugin_setting
  • elgg_get_plugin_setting
  • elgg_unset_plugin_setting
  • elgg_unset_all_plugin_settings
Class constructors that now accept only a stdClass object or null
  • ElggAnnotation: No longer accepts an annotation ID
  • ElggGroup: No longer accepts a GUID
  • ElggMetadata: No longer accepts a metadata ID
  • ElggObject: No longer accepts a GUID
  • ElggRelationship: No longer accepts a relationship ID or null
  • ElggSite: No longer accepts a GUID or URL
  • ElggUser: No longer accepts a GUID or username
Miscellaneous API changes
  • ElggBatch: You may only access public properties
  • ElggEntity: The tables_split and tables_loaded properties were removed
  • ElggEntity: Empty URLs will no longer be normalized. This means entities without URLs will no longer result in the site URL
  • ElggGroup::removeObjectFromGroup requires passing in an ElggObject (no longer accepts a GUID)
  • ElggUser::$salt no longer exists as an attribute, nor is it used for authentication
  • ElggUser::$password no longer exists as an attribute, nor is it used for authentication
  • elgg_get_widget_types no longer supports $exact as the 2nd argument
  • elgg_instanceof no longer supports the fourth class argument
  • elgg_view_icon no longer supports true as the 2nd argument
  • elgg_list_entities no longer supports the option view_type_toggle
  • elgg_list_registered_entities no longer supports the option view_type_toggle
  • elgg_log no longer accepts the level "DEBUG"
  • elgg_gatekeeper and elgg_admin_gatekeeper no longer report login or admin as forward reason, but 403
  • Application::getDb() no longer returns an instance of Elgg\Database, but rather a Elgg\Application\Database
  • $CONFIG is no longer available as a local variable inside plugin start.php files.
  • elgg_get_config('siteemail') is no longer available. Use elgg_get_site_entity()->email.
  • Group entities do no longer have the magic username attribute.
  • Pagehandling will no longer detect group:<guid> in the URL
  • The CRON interval reboot is removed.
  • The URL endpoints js/ and css/ are no longer supported. Use elgg_get_simplecache_url().
  • The generic comment save action no longer sends the notification directly, this has been offloaded to the notification system.
  • The script engine/start.php is removed.
  • The functions set_config, unset_config and get_config have been deprecated and replaced by elgg_set_config, elgg_remove_config and elgg_get_config.
  • Config values path, wwwroot, and dataroot are not read from the database. The settings.php file values are always used.
JavaScript hook calling order may change

When registering for hooks, the all keyword for wildcard matching no longer has any effect on the order that handlers are called. To ensure your handler is called last, you must give it the highest priority of all matching handlers, or to ensure your handler is called first, you must give it the lowest priority of all matching handlers.

If handlers were registered with the same priority, these are called in the order they were registered.

To emulate prior behavior, Elgg core handlers registered with the all keyword have been raised in priority. Some of these handlers will most likely be called in a different order.

HtmLawed is no longer a plugin
  • Do not call elgg_load_library('htmlawed').
  • In the hook params for 'config', 'htmlawed', the hook_tag function name changed.
New approach to page layouts

one_column, one_sidebar, two_sidebar and content layouts have been removed - instead layout rendering has been centralized in the default. Updated default layout provides full control over the layout elements via $vars. For maximum backwards compatibility, calls to elgg_view_layout() with these layout names will still yield expected output, but the plugins should start using the default layout with an updated set of parameters.

Page layouts have been decomposed into smaller elements, which should make it easier for themes to target specific layout elements without having to override layouts at large.

As a result of these changes:

  • all layouts are consistent in how they handle title and filter menus, breadcrumbs and layout subviews
  • all layouts can now be easily extended to have multiple tabs. Plugins can pass filter_id parameter that will allow other plugins to hook into register, menu:filter:<filter_id> hook and add new tabs. If no filter_id is provided, default register, menu:filter hook can be used.
  • layout views and subviews now receive identifier and segments of the page being rendered
  • layout parameters are available to title and filter menu hooks, which allows resources to provide additional context information, for example, an $entity in case of a profile resource

Plugins and themes should:

  • Update calls to elgg_view_layout() to use default layout
  • Update replace nav parameter in layout views with breadcrumbs parameter
  • Update their use of filter parameter in layout views by either providing a default set of filter tabs, or setting a filter_id parameter and using hooks
  • Remove page/layouts/one_column view
  • Remove page/layouts/one_sidebar view
  • Remove page/layouts/two_sidebar view
  • Remove page/layouts/content view
  • Update their use of page/layouts/default
  • Update their use of page/layouts/error
  • Update their use of page/layouts/elements/filter
  • Update their use of page/layouts/elements/header
  • Update their use of page/layouts/elements/footer
  • Update their use of page/elements/title
  • Update their use of navigation/breadcrumbs to pass $vars['breadcrumbs'] to elgg_get_breadcrumbs()
  • Update hook registrations for output:before, layout to view_vars, page/layout/<layout_name>
  • Update hook registrations for output:after, layout to view, page/layout/<layout_name>
  • RSS extras menu is now registered with register, menu:extras hook
Likes plugin

Likes no longer uses Elgg’s toggle API, so only a single likes menu item is used. The add/remove actions no longer return Ajax values directly, as likes status data is now returned with every Ajax request that sends a “guid”. When the number of likes is zero, the likes_count menu item is now hidden by adding .hidden to the LI element, instead of the anchor. Also the likes_count menu item is a regular link, and is no longer created by the likes/count view.

Notifications plugin

Notifications plugin has been rewritten dropping many views and actions. The purpose of this rewrite was to implement a more efficient, extendable and scalable interface for managing notifications preferences. We have implemented a much simpler markup and removed excessive styling and javascript that was required to make the old interface work.

If your plugin is extending any of the views or relies on any actions in the notifications plugin, it has to be updated.

Theme and styling changes

Aalborg theme is no longer bundled with Elgg, instead all styles have been either moved to core views or to an appropriate core plugin.

This change may affect themes and plugins that were developed without Aalborg stylesheets loaded or were relying on a certain priority of views with Aalborg stylesheets loaded.

Notable changes in plugins:

  • search plugin no longer extends page/elements/header and instead extends page/elements/sidebar
  • .elgg-icon no longer has a global font-size, line-height or color: these values will be inherited from parent items
  • Support for .elgg-icon-hover has been dropped
  • Admin theme now reuses icon classes from elements/icons.css
  • User “hover” icons are no longer covered with a “caret” icon.

Also note, CSS views served via /cache URLs are pre-processed using CSS Crush. If you make references to CSS variables or other elements, the definition must be located within the same view output. E.g. A variable defined in elgg.css cannot be referenced in a separate CSS file like colorbox.css.

Comment notifications
  • The language keys related to comment notifications have changed. Check the generic_comment:notification:owner: language keys
  • The action for creating a comment (action/comment/save) was changed. If your plugin overruled this action you should have a look at it in order to prevent double notifications
Object listing views
  • object/elements/full/body now wraps the full listing body in a .elgg-listing-full-body wrapper
  • object/elements/full now supports attachments and responses which are rendered after listing body
  • In core plugins, resource views no longer render comments/replies - instead they pass a show_responses flag to the entity view, which renders the responses and passes them to the full listing view. Third party plugins will need to update their uses of object/<subtype> and resources/<handler>/view views.
  • Full discussion view is now rendered using object/elements/full view
  • object/file now passes image (specialcontent) view as an attachment to the full listing view
Entity icons

Default icon image files have been moved and re-mapped as follows:

  • Default icons: views/default/icon/default/$size.png
  • User icons: views/default/icon/user/default/$size.gif
  • Group icons: views/default/icon/group/default/$size.gif in the groups plugin

Groups icon files have been moved from groups/<guid><size>.jpg relative to group owner’s directory on filestore to a location prescribed by the entity icon service. Plugins should stop accessing files on the filestore directly and use the entity icon API. Upgrade script is available via admin interface.

Autocomplete (user and friends pickers)

Friends Picker input is now rendered using input/userpicker.

Plugins should:

  • Update overriden input/userpicker to support new only_friends parameter
  • Remove friends picker CSS from their stylesheets
Friends collections

Friends collections UI has been moved to its own plugins - friends_collections.

Layout of .elgg-body elements

In 3.0, these elements by default no longer stretch to fill available space in a block context. They still clear floats and allow breaking words to wrap text.

Core modules and layouts that relied on space-filling have been reworked for Flexbox and we encourage devs to do the same, rather than use the problematic overflow: hidden.

From 2.2 to 2.3

PHP Version

PHP 5.5 has reached end of life in July 2016. To ensure that Elgg sites are secure, we now require PHP 5.6 for new installations.

Existing installations can continue using PHP 5.5 until Elgg 3.0.

In order to upgrade Elgg to 2.3 using composer while using PHP 5.5, you may need to use --ignore-platform-reqs flag.

Deprecated APIs
  • Registering for to:object hook by the extender name: Use to:object, annotation and to:object, metadata hooks instead.
  • ajax_forward_hook(): No longer used as handler for ‘forward’,’all’ hook. Ajax response is now wrapped by the ResponseFactory
  • ajax_action_hook(): No longer used as handler for ‘action’,’all’ hook. Output buffering now starts before the hook is triggered in ActionsService
  • elgg_error_page_handler(): No longer used as a handler for ‘forward’,<error_code> hooks
  • get_uploaded_file(): Use new file uploads API instead
  • get_user_notification_settings(): Use ElggUser::getNotificationSettings()
  • set_user_notification_setting(): Use ElggUser::setNotificationSetting()
  • pagesetup, system event: Use the menu or page shell hooks instead.
  • elgg.walled_garden JavaScript is deprecated: Use elgg/walled_garden AMD module instead.
  • elgg()->getDb()->getTableprefix(): Use elgg_get_config('dbprefix').
  • Private update_entity_last_action(): Refrain from manually updating last action timestamp.
  • Setting non-public access_id on metadata is deprecated. See below.
  • get_resized_image_from_existing_file(): Use elgg_save_resized_image().
  • get_resized_image_from_uploaded_file(): Use elgg_save_resized_image() in combination with upload API.
  • get_image_resize_parameters() will be removed.
  • elgg_view_input(): Use elgg_view_field(). Apologies for the API churn.
Deprecated Views
  • resources/file/world: Use the resources/file/all view instead.
  • resources/pages/world: Use the resources/pages/all view instead.
  • walled_garden.js: Use the elgg/walled_garden module instead.
New API for page and action handling

Page handlers and action script files should now return an instance of \Elgg\Http\ResponseBuilder. Plugins should use the following convenience functions to build responses:

  • elgg_ok_response() sends a 2xx response with HTML (page handler) or JSON data (actions)
  • elgg_error_response() sends a 4xx or 5xx response without content/data
  • elgg_redirect_response() silently redirects the request
New API for working with file uploads
  • elgg_get_uploaded_files() - returns an array of Symfony uploaded file objects
  • ElggFile::acceptUploadedFile() - moves an uploaded file to Elgg’s filestore
New API for manipulating images

New image manipulation service implements a more efficient approach to cropping and resizing images.

  • elgg_save_resized_image() - crops and resizes an image to preferred dimensions
New API for events
  • elgg_clear_event_handlers() - similar to elgg_clear_plugin_hook_handlers this functions removes all registered event handlers
New API for signing URLs

URLs can now be signed with a SHA-256 HMAC key and validated at any time before URL expiry. This feature can be used to tokenize action URLs in email notifications, as well as other uses outside of the Elgg installation.

  • elgg_http_get_signed_url() - signs the URL with HMAC key
  • elgg_http_validate_signed_url() - validates the signed URL
  • elgg_signed_request_gatekeeper() - gatekeeper that validates the signature of the current request
Extendable form views

Form footer rendering can now be deferred until the form view and its extensions have finished rendering. This allows plugins to collaborate on form views without breaking the markup logic.

  • elgg_set_form_footer() - sets form footer for deferred rendering
  • elgg_get_form_footer() - returns currently set form footer
Metadata access_id

It’s now deprecated to create metadata with an explicit access_id value other than ACCESS_PUBLIC.

In Elgg 3.0, metadata will not be access controlled, and will be available in all contexts. If your plugin relies on access control of metadata, it would be wise to migrate storage to annotations or entities instead.

New API for extracting class names from arrays

Similar to elgg_extract(), elgg_extract_class() extracts the “class” key (if present), merges into existing class names, and always returns an array.

  • A high level 'prepare','notification' hook is now triggered for instant and subscription notifications and can be used to alter notification objects irrespective of their type.
  • 'format','notification:<method>' hook is now triggered for instant and subscription notifications and can be used to format the notification (e.g. strip HTML tags, wrap the notification body in a template etc).
  • Instant notifications are now handled by the notifications service, hence almost all hooks applicable to subscription notifications also apply to instant notifications.
  • elgg_get_notification_methods() can be used to obtain registered notification methods
  • Added ElggUser::getNotificationSettings() and ElggUser::setNotificationSetting()
Entity list functions can output tables

In functions like elgg_list_entities($options), table output is possible by setting $options['list_type'] = 'table' and providing an array of table columns as $options['columns']. Each column is an Elgg\Views\TableColumn object, usually created via methods on the service elgg()->table_columns.

Plugins can provide or alter these factory methods (see Elgg\Views\TableColumn\ColumnFactory). See the view admin/users/newest for a usage example.

Inline tabs components

Inline tabs component can now be rendered with page/components/tabs view. The components allows to switch between pre-poluated and ajax-loaded. See page/components/tabs in core views and theme_sandbox/components/tabs in developers plugin for usage instructions and examples.

API to alter registration and login URL
  • elgg_get_registration_url() should be used to obtain site’s registration URL
  • elgg_get_login_url() should be used to obtain site’s login URL
  • registration_url, site hook can be used to alter the default registration URL
  • login_url, site hook can be used to alter the default login URL
Support for fieldsets in forms
  • elgg_view_field() replaces elgg_view_input(). It has a similar API, but accepts a single array.
  • elgg_view_field() supports #type, #label, #help and #class, allowing unprefixed versions to be sent to the input view $vars.
  • The new view input/fieldset can be used to render a set of fields, each rendered with elgg_view_field().

From 2.1 to 2.2

Deprecated APIs
  • elgg.ui.river JavaScript library: Remove calls to elgg_load_js('elgg.ui.river') from plugin code. Update core/river/filter and forms/comment/save, if overwritten, to require component AMD modules
  • elgg.ui.popupOpen() and elgg.ui.popupClose() methods in elgg.ui JS library: Use elgg/popup module instead.
  • lightbox.js library: Do not use elgg_load_js('lightbox.js'); unless your code references deprecated elgg.ui.lightbox namespace. Use elgg/lightbox AMD module instead.
  • elgg.embed library and elgg.embed object: Do not use elgg_load_js('elgg.embed'). Use elgg/embed AMD module instead
  • Accessing icons_sizes config value directly: Use elgg_get_icon_sizes()
  • can_write_to_container(): Use ElggEntity::canWriteToContainer()
Deprecated Views
  • elgg/ui.river.js is deprecated: Do not rely on simplecache URLs to work.
  • groups/js is deprecated: Use groups/navigation AMD module as a menu item dependency for “feature” and “unfeature” menu items instead.
  • lightbox/settings.js is deprecated: Use getOptions, ui.lightbox JS plugin hook or data-colorbox-opts attribute.
  • elgg/ckeditor/insert.js is deprecated: You no longer need to include it, hook registration takes place in elgg/ckeditor module
  • embed/embed.js is deprecated: Use elgg/embed AMD module.
Added elgg/popup module

New elgg/popup module can be used to build out more complex trigger-popup interactions, including binding custom anchor types and opening/closing popups programmatically.

Added elgg/lightbox module

New elgg/lightbox module can be used to open and close the lightbox programmatically.

Added elgg/embed module

Even though rarely necessary, elgg/embed AMD module can be used to access the embed methods programmatically. The module bootstraps itself when necessary and is unlikely to require further decoration.

New API for handling entity icons
  • ElggEntity now implements \Elgg\EntityIcon interface
  • elgg_get_icon_sizes() - return entity type/subtype specific icon sizes
  • ElggEntity::saveIconFromUploadedFile() - creates icons from an uploaded file
  • ElggEntity::saveIconFromLocalFile() - creates icons from a local file
  • ElggEntity::saveIconFromElggFile() - creates icons from an instance of ElggFile
  • ElggEntity::getIcon() - returns an instanceof ElggIcon that points to entity icon location on filestore (this may be just a placeholder, use ElggEntity::hasIcon() to validate if file has been written)
  • ElggEntity::deleteIcon() - deletes entity icons
  • ElggEntity::getIconLastChange() - return modified time of the icon file
  • ElggEntity::hasIcon() - checks if an icon with given size has been created
  • elgg_get_embed_url() - can be used to return an embed URL for an entity’s icon (served via /serve-icon handler)
Removed APIs

Just a warning that the private entity cache functions (e.g. _elgg_retrieve_cached_entity) have been removed. Some plugins may have been using them. Plugins should not use private APIs as they will more often be removed without notice.

Improved elgg/ckeditor module

elgg/ckeditor module can now be used to add WYSIWYG to a textarea programmatically with elgg/ckeditor#bind.

From 2.0 to 2.1

Deprecated APIs
  • ElggFile::setFilestore
  • get_default_filestore
  • set_default_filestore
  • elgg_get_config('siteemail'): Use elgg_get_site_entity()->email
  • URLs starting with /css/ and /js/: Use elgg_get_simplecache_url()
  • elgg.ui.widgets JavaScript object is deprecated by elgg/widgets AMD module
Application::getDb() changes

If you’re using this low-level API, do not expect it to return an Elgg\Database instance in 3.0. It now returns an Elgg\Application\Database with many deprecated. These methods were never meant to be made public API, but we will do our best to support them in 2.x.

Added elgg/widgets module

If your plugin code calls elgg.ui.widgets.init(), instead use the elgg/widgets module.

From 1.x to 2.0

Elgg can be now installed as a composer dependency instead of at document root

That means an Elgg site can look something like this:


elgg_get_root_path and $CONFIG->path will return the path to the application root directory, which is not necessarily the same as Elgg core’s root directory (which in this case is vendor/elgg/elgg/).

Do not attempt to access the core Elgg from your plugin directly, since you cannot rely on its location on the filesystem.

In particular, don’t try load engine/start.php.

// Don't do this!
dirname(__DIR__) . "/engine/start.php";

To boot Elgg manually, you can use the class Elgg\Application.

// boot Elgg in mod/myplugin/foo.php
require_once dirname(dirname(__DIR__)) . '/vendor/autoload.php';

However, use this approach sparingly. Prefer Routing instead whenever possible as that keeps your public URLs and your filesystem layout decoupled.

Also, don’t try to access the _graphics files directly.

readfile(elgg_get_root_path() . "_graphics/elgg_sprites.png");

Use Views instead:

echo elgg_view('elgg_sprites.png');
Cacheable views must have a file extension in their names

This requirement makes it possibile for us to serve assets directly from disk for performance, instead of serving them through PHP.

It also makes it much easier to explore the available cached resources by navigating to dataroot/views_simplecache and browsing around.

  • Bad: my/cool/template
  • Good: my/cool/template.html

We now cache assets by "$viewtype/$view", not md5("$viewtype|$view"), which can result in conflicts between cacheable views that don’t have file extensions to disambiguate files from directories.

Dropped jquery-migrate and upgraded jquery to ^2.1.4

jQuery 2.x is API-compatible with 1.x, but drops support for IE8-, which Elgg hasn’t supported for some time anyways.

See http://jquery.com/upgrade-guide/1.9/ for how to move off jquery-migrate.

If you’d prefer to just add it back, you can use this code in your plugin’s init:

elgg_register_js('jquery-migrate', elgg_get_simplecache_url('jquery-migrate.js'), 'head');

Also, define a jquery-migrate.js view containing the contents of the script.

JS and CSS views have been moved out of the js/ and css/ directories

They also have been given .js and .css extensions respectively if they didn’t already have them:

Old view New view
js/view view.js
js/other.js other.js
css/view view.css
css/other.css other.css
js/img.png img.png

The main benefit this brings is being able to co-locate related assets. So a template (view.php) can have its CSS/JS dependencies right next to it (view.css, view.js).

Care has been taken to make this change as backwards-compatible as possible, so you should not need to update any view references right away. However, you are certainly encouraged to move your JS and CSS views to their new, canonical locations.

Practically speaking, this carries a few gotchas:

The view_vars, $view_name and view, $view_name hooks will operate on the canonical view name:

elgg_register_plugin_hook_handler('view', 'css/elgg', function($hook, $view_name) {
  assert($view_name == 'elgg.css') // not "css/elgg"

Using the view, all hook and checking for individual views may not work as intended:

elgg_register_plugin_hook_handler('view', 'all', function($hook, $view_name) {
  // Won't work because "css/elgg" was aliased to "elgg.css"
  if ($view_name == 'css/elgg') {
    // Never executed...

  // Won't work because no canonical views start with css/* anymore
  if (strpos($view_name, 'css/') === 0) {
    // Never executed...

Please let us know about any other BC issues this change causes. We’d like to fix as many as possible to make the transition smooth.

fxp/composer-asset-plugin is now required to install Elgg from source

We use fxp/composer-asset-plugin to manage our browser assets (js, css, html) with Composer, but it must be installed globally before installing Elgg in order for the bower-asset/* packages to be recognized. To install it, run:

composer global require fxp/composer-asset-plugin

If you don’t do this before running composer install or composer create-project, you will get an error message:

Package fxp/composer-asset-plugin not found
List of deprecated views and view arguments that have been removed

We dropped support for and/or removed the following views:

  • canvas/layouts/*
  • categories
  • categories/view
  • core/settings/tools
  • embed/addcontentjs
  • footer/analytics (Use page/elements/foot instead)
  • groups/left_column
  • groups/right_column
  • groups/search/finishblurb
  • groups/search/startblurb
  • input/calendar (Use input/date instead)
  • input/datepicker (Use input/date instead)
  • input/pulldown (Use input/select instead)
  • invitefriends/formitems
  • js/admin (Use AMD and elgg_require_js instead of extending JS views)
  • js/initialise_elgg (Use AMD and elgg_require_js instead of extending JS views)
  • members/nav
  • metatags (Use the ‘head’, ‘page’ plugin hook instead)
  • navigation/topbar_tools
  • navigation/viewtype
  • notifications/subscriptions/groupsform
  • object/groupforumtopic
  • output/calendar (Use output/date instead)
  • output/confirmlink (Use output/url instead)
  • page_elements/contentwrapper
  • page/elements/shortcut_icon (Use the ‘head’, ‘page’ plugin hook instead)
  • page/elements/wrapper
  • profile/icon (Use elgg_get_entity_icon)
  • river/object/groupforumtopic/create
  • settings/{plugin}/edit (Use plugins/{plugin}/settings instead)
  • user/search/finishblurb
  • user/search/startblurb
  • usersettings/{plugin}/edit (Use plugins/{plugin}/usersettings instead)
  • widgets/{handler}/view (Use widgets/{handler}/content instead)

We also dropped the following arguments to views:

  • “value” in output/iframe (Use “src” instead)
  • “area2” and “area3” in page/elements/sidebar (Use “sidebar” or view extension instead)
  • “js” in icon views (e.g. icon/user/default)
  • “options” to input/radio and input/checkboxes which aren’t key-value pairs will no longer be acceptable.
All scripts moved to bottom of page

You should test your plugin with the JavaScript error console visible. For performance reasons, Elgg no longer supports script elements in the head element or in HTML views. elgg_register_js will now load all scripts at the end of the body element.

You must convert inline scripts to AMD or to external scripts loaded with elgg_load_js.

Early in the page, Elgg provides a shim of the RequireJS require() function that simply queues code until the AMD elgg and jQuery modules are defined. This provides a straightforward way to convert many inline scripts to use require().

Inline code which will fail because the stack is not yet loaded:

$(function () {
    // code using $ and elgg

This should work in Elgg 2.0:

require(['elgg', 'jquery'], function (elgg, $) {
    $(function () {
        // code using $ and elgg
Attribute formatter removes keys with underscores

elgg_format_attributes() (and all APIs that use it) now filter out attributes whose name contains an underscore. If the attribute begins with data-, however, it will not be removed.

Callbacks in Queries

Make sure to use only valid callable values for “callback” argument/options in the API.

Querying functions will now will throw a RuntimeException if is_callable() returns false for the given callback value. This includes functions such as elgg_get_entities(), get_data(), and many more.

Comments plugin hook

Plugins can now return an empty string from 'comments',$entity_type hook in order to override the default comments component view. To force the default comments component, your plugin must return false. If you were using empty strings to force the default comments view, you need to update your hook handlers to return false.

Container permissions hook

The behavior of the container_permissions_check hook has changed when an entity is being created: Before 2.0, the hook would be called twice if the entity’s container was not the owner. On the first call, the entity’s owner would be passed in as $params['container'], which could confuse handlers.

In 2.0, when an entity is created in a container like a group, if the owner is the same as the logged in user (almost always the case), this first check is bypassed. So the container_permissions_check hook will almost always be called once with $params['container'] being the correct container of the entity.

Creating or deleting a relationship triggers only one event

The “create” and “delete” relationship events are now only fired once, with "relationship" as the object type.

E.g. Listening for the "create", "member" or "delete", "member" event(s) will no longer capture group membership additions/removals. Use the "create", "relationship" or "delete", "relationship" events.

Discussion feature has been pulled from groups into its own plugin

The object, groupforumtopic subtype has been replaced with the object, discussion subtype. If your plugin is using or altering the old discussion feature, you should upgrade it to use the new subtype.

Nothing changes from the group owners’ point of view. The discussion feature is still available as a group tool and all old discussions are intact.

Dropped login-over-https feature

For the best security and performance, serve all pages over HTTPS by switching the scheme in your site’s wwwroot to https at http://yoursite.tld/admin/settings/advanced

Elgg has migrated from ext/mysql to PDO MySQL

Elgg now uses a PDO_MYSQL connection and no longer uses any ext/mysql functions. If you use mysql_* functions, implicitly relying on an open connection, these will fail.

If your code uses one of the following functions, read below.

  • execute_delayed_write_query()
  • execute_delayed_read_query()

If you provide a callable $handler to be called with the results, your handler will now receive a \Doctrine\DBAL\Driver\Statement object. Formerly this was an ext/mysql result resource.

Event/Hook calling order may change

When registering for events/hooks, the all keyword for wildcard matching no longer has any effect on the order that handlers are called. To ensure your handler is called last, you must give it the highest priority of all matching handlers, or to ensure your handler is called first, you must give it the lowest priority of all matching handlers.

If handlers were registered with the same priority, these are called in the order they were registered.

To emulate prior behavior, Elgg core handlers registered with the all keyword have been raised in priority. Some of these handlers will most likely be called in a different order.

export/ URLs are no longer available

Elgg no longer provides this endpoint for exposing resource data.

Icons migrated to Font Awesome

Elgg’s sprites and most of the CSS classes beginning with elgg-icon- have been removed.

Usage of elgg_view_icon() is backward compatible, but static HTML using the elgg-icon classes will have to be updated to the new markup.

Increase of z-index value in elgg-menu-site class

The value of z-index in the elgg-menu-site class has been increased from 1 to 50 to allow for page elements in the content area to use the z-index property without the “More” site menu’s dropdown being displayed behind these elements. If your plugin/theme overrides the elgg-menu-site class or views/default/elements/navigation.css please adjust the z-index value in your modified CSS file accordingly.

input/autocomplete view

Plugins that override the input/autocomplete view will need to include the source URL in the data-source attribute of the input element, require the new elgg/autocomplete AMD module, and call its init method. The 1.x javascript library elgg.autocomplete is no longer used.

Introduced third-party library for sending email

We are using the excellent Zend\Mail library to send emails in Elgg 2.0. There are likely edge cases that the library handles differently than Elgg 1.x. Take care to test your email notifications carefully when upgrading to 2.0.

Label elements

The following views received label elements around some of the input fields. If your plugin/theme overrides these views please check for the new content.

  • views/default/core/river/filter.php
  • views/default/forms/admin/plugins/filter.php
  • views/default/forms/admin/plugins/sort.php
  • views/default/forms/login.php
Plugin Aalborg Theme

The view page/elements/navbar now uses a Font Awesome icon for the mobile menu selector instead of an image. The bars.png image and supporting CSS for the 1.12 rendering has been removed, so update your theme accordingly.

Plugin Likes

Objects are no longer likable by default. To support liking, you can register a handler to permit the annotation, or more simply register for the hook ["likes:is_likable", "<type>:<subtype>"] and return true. E.g.

elgg_register_plugin_hook_handler('likes:is_likable', 'object:mysubtype', 'Elgg\Values::getTrue');

Just as before, the permissions_check:annotate hook is still called and may be used to override default behavior.

Plugin Messages

If you’ve removed or replaced the handler function messages_notifier to hide/alter the inbox icon, you’ll instead need to do the same for the topbar menu handler messages_register_topbar. messages_notifier is no longer used to add the menu link.

Messages will no longer get the metadata ‘msg’ for newly created messages. This means you can not rely on that metadata to exist.

Plugin Blog

The blog pages showing ‘Mine’ or ‘Friends’ listings of blogs have been changed to list all the blogs owned by the users (including those created in groups).

Plugin Bookmarks

The bookmark pages showing ‘Mine’ or ‘Friends’ listings of bookmarks have been changed to list all the bookmarks owned by the users (including those created in groups).

Plugin File

The file pages showing ‘Mine’ or ‘Friends’ listings of files have been changed to list all the files owned by the users (including those created in groups).

Removed Classes
  • ElggInspector
  • Notable
  • FilePluginFile: replace with ElggFile (or load with get_entity())
Removed keys available via elgg_get_config()
  • allowed_ajax_views
  • dataroot_in_settings
  • externals
  • externals_map
  • i18n_loaded_from_cache
  • language_paths
  • pagesetupdone
  • registered_tag_metadata_names
  • simplecache_enabled_in_settings
  • translations
  • viewpath
  • views
  • view_path
  • viewtype
  • wordblacklist

Also note that plugins should not be accessing the global $CONFIG variable except for in settings.php.

Removed Functions
  • blog_get_page_content_friends
  • blog_get_page_content_read
  • count_unread_messages()
  • delete_entities()
  • delete_object_entity()
  • delete_user_entity()
  • elgg_get_view_location()
  • elgg_validate_action_url()
  • execute_delayed_query()
  • extend_view()
  • get_db_error()
  • get_db_link()
  • get_entities()
  • get_entities_from_access_id()
  • get_entities_from_access_collection()
  • get_entities_from_annotations()
  • get_entities_from_metadata()
  • get_entities_from_metadata_multi()
  • get_entities_from_relationship()
  • get_filetype_cloud()
  • get_library_files()
  • get_views()
  • is_ip_in_array()
  • list_entities()
  • list_entities_from_annotations()
  • list_group_search()
  • list_registered_entities()
  • list_user_search()
  • load_plugins()
  • menu_item()
  • make_register_object()
  • mysql_*(): Elgg no longer uses ext/mysql
  • remove_blacklist()
  • search_for_group()
  • search_for_object()
  • search_for_site()
  • search_for_user()
  • search_list_objects_by_name()
  • search_list_groups_by_name()
  • search_list_users_by_name()
  • set_template_handler()
  • test_ip()
Removed methods
  • ElggCache::set_variable()
  • ElggCache::get_variable()
  • ElggData::initialise_attributes()
  • ElggData::getObjectOwnerGUID()
  • ElggDiskFilestore::make_directory_root()
  • ElggDiskFilestore::make_file_matrix()
  • ElggDiskFilestore::user_file_matrix()
  • ElggDiskFilestore::mb_str_split()
  • ElggEntity::clearMetadata()
  • ElggEntity::clearRelationships()
  • ElggEntity::clearAnnotations()
  • ElggEntity::getOwner()
  • ElggEntity::setContainer()
  • ElggEntity::getContainer()
  • ElggEntity::getIcon()
  • ElggEntity::setIcon()
  • ElggExtender::getOwner()
  • ElggFileCache::create_file()
  • ElggObject::addToSite(): parent function in ElggEntity still available
  • ElggObject::getSites(): parent function in ElggEntity still available
  • ElggSite::getCollections()
  • ElggUser::addToSite(): parent function in ElggEntity still available
  • ElggUser::getCollections()
  • ElggUser::getOwner()
  • ElggUser::getSites(): parent function in ElggEntity still available
  • ElggUser::listFriends()
  • ElggUser::listGroups()
  • ElggUser::removeFromSite(): parent function in ElggEntity still available

The following arguments have also been dropped:

  • ElggSite::getMembers() - 2: $offset
  • elgg_view_entity_list() - 3: $offset - 4: $limit - 5: $full_view - 6: $list_type_toggle - 7: $pagination
Removed Plugin Hooks
Removed Actions
  • widgets/upgrade
Removed Views
  • forms/admin/plugins/change_state
Removed View Variables

During rendering, the view system no longer injects these into the scope:

  • $vars['url']: replace with elgg_get_site_url()
  • $vars['user']: replace with elgg_get_logged_in_user_entity()
  • $vars['config']: use elgg_get_config() and elgg_set_config()
  • $CONFIG: use elgg_get_config() and elgg_set_config()

Also several workarounds for very old views are no longer performed. Make these changes:

  • Set $vars['full_view'] instead of $vars['full'].
  • Set $vars['name'] instead of $vars['internalname'].
  • Set $vars['id'] instead of $vars['internalid'].
Removed libraries
  • elgg:markdown: Elgg no longer provides a markdown implementation. You must provide your own.
Specifying View via Properties

The metadata $entity->view no longer specifies the view used to render in elgg_view_entity().

Similarly the property $annotation->view no longer has an effect within elgg_view_annotation().

Viewtype is static after the initial elgg_get_viewtype() call

elgg_set_viewtype() must be used to set the viewtype at runtime. Although Elgg still checks the view input and $CONFIG->view initially, this is only done once per request.


It’s deprecated to read or write to metadata keys starting with filestore:: on ElggFile objects. In Elgg 3.0 this metadata will be deleted if it points to the current data root path, so few file objects will have it. Plugins should only use ElggFile::setFilestore if files need to be stored in a custom location.


This is not the only deprecation in Elgg 2.0. Plugin developers should watch their site error logs.

From 1.10 to 1.11

Comment highlighting

If your theme is using the file views/default/css/elements/components.php, you must add the following style definitions in it to enable highlighting for comments and discussion replies:

.elgg-comments .elgg-state-highlight {
        -webkit-animation: comment-highlight 5s;
        animation: comment-highlight 5s;
@-webkit-keyframes comment-highlight {
        from {background: #dff2ff;}
        to {background: white;}
@keyframes comment-highlight {
        from {background: #dff2ff;}
        to {background: white;}

From 1.9 to 1.10

File uploads

If your plugin is using a snippet copied from the file/upload action to fix detected mime types for Microsoft zipped formats, it can now be safely removed.

If your upload action performs other manipulations on detected mime and simple types, it is recommended to make use of available plugin hooks:

  • 'mime_type','file' for filtering detected mime types
  • 'simple_type','file' for filtering parsed simple types

From 1.8 to 1.9

In the examples we are upgrading an imaginary “Photos” plugin.

Only the key changes are included. For example some of the deprecated functions are not mentioned here separately.

Each section will include information whether the change is backwards compatible with Elgg 1.8.

The manifest file

No changes are needed if your plugin is compatible with 1.8.

It’s however recommended to add the <id> tag. It’s value should be the name of the directory where the plugin is located inside the mod/ directory.

If you make changes that break BC, you must update the plugin version and the required Elgg release.

Example of (shortened) old version:

<?xml version="1.0" encoding="UTF-8"?>
<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">
    <author>John Doe</author>
    <description>Adds possibility to upload photos and arrange them into albums.</description>

Example of (shortened) new version:

<?xml version="1.0" encoding="UTF-8"?>
<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">
    <author>John Doe</author>
    <description>Adds possibility to upload photos and arrange them into albums.</description>
$CONFIG and $vars[‘config’]

Both the global $CONFIG variable and the $vars['config'] parameter have been deprecated. They should be replaced with the elgg_get_config() function.

Example of old code:

// Using the global $CONFIG variable:
global $CONFIG;
$plugins_path = $CONFIG->plugins_path

// Using the $vars view parameter:
$plugins_path = $vars['plugins_path'];

Example of new code:

$plugins_path = elgg_get_config('plugins_path');


Compatible with 1.8

Language files

In Elgg 1.8 the language files needed to use the add_translation() function. In 1.9 it is enough to just return the array that was previously passed to the function as a parameter. Elgg core will use the file name (e.g. en.php) to tell which language the file contains.

Example of the old way in languages/en.php:

$english = array(
    'photos:all' => 'All photos',
add_translation('en', $english);

Example of new way:

return array(
    'photos:all' => 'All photos',


Not compatible with 1.8


One of the biggest changes in Elgg 1.9 is the notifications system. The new system allows more flexible and scalable way of sending notifications.

Example of the old way:

function photos_init() {
    // Tell core that we want to send notifications about new photos
    register_notification_object('object', 'photo', elgg_echo('photo:new'));

    // Register a handler that creates the notification message
    elgg_register_plugin_hook_handler('notify:entity:message', 'object', 'photos_notify_message');

 * Set the notification message body
 * @param string $hook    Hook name
 * @param string $type    Hook type
 * @param string $message The current message body
 * @param array  $params  Parameters about the photo
 * @return string
function photos_notify_message($hook, $type, $message, $params) {
    $entity = $params['entity'];
    $to_entity = $params['to_entity'];
    $method = $params['method'];
    if (elgg_instanceof($entity, 'object', 'photo')) {
        $descr = $entity->excerpt;
        $title = $entity->title;
        $owner = $entity->getOwnerEntity();
        return elgg_echo('photos:notification', array(
    return null;

Example of the new way:

function photos_init() {
    elgg_register_notification_event('object', 'photo', array('create'));
    elgg_register_plugin_hook_handler('prepare', 'notification:publish:object:photo', 'photos_prepare_notification');

 * Prepare a notification message about a new photo
 * @param string                          $hook         Hook name
 * @param string                          $type         Hook type
 * @param Elgg_Notifications_Notification $notification The notification to prepare
 * @param array                           $params       Hook parameters
 * @return Elgg_Notifications_Notification
function photos_prepare_notification($hook, $type, $notification, $params) {
    $entity = $params['event']->getObject();
    $owner = $params['event']->getActor();
    $recipient = $params['recipient'];
    $language = $params['language'];
    $method = $params['method'];

    // Title for the notification
    $notification->subject = elgg_echo('photos:notify:subject', array($entity->title), $language);

    // Message body for the notification
    $notification->body = elgg_echo('photos:notify:body', array(
    ), $language);

    // The summary text is used e.g. by the site_notifications plugin
    $notification->summary = elgg_echo('photos:notify:summary', array($entity->title), $language);

    return $notification;


Not compatible with 1.8


See how the community_plugins plugin was updated to use the new system: https://github.com/Elgg/community_plugins/commit/bfa356cfe8fb99ebbca4109a1b8a1383b70ff123

Notifications can also be sent with the notify_user() function.

It has however been updated to support three new optional parameters passed inside an array as the fifth parameter.

The parameters give notification plugins more control over the notifications, so they should be included whenever possible. For example the bundled site_notifications plugin won’t work properly if the parameters are missing.


  • object The object that we are notifying about (e.g. ElggEntity or ElggAnnotation). This is needed so that notification plugins can provide a link to the object.
  • action String that describes the action that triggered the notification (e.g. “create”, “update”, etc).
  • summary String that contains a summary of the notification. (It should be more informative than the notification subject but less informative than the notification body.)

Example of the old way:

// Notify $owner that $user has added a $rating to an $entity created by him

$subject = elgg_echo('rating:notify:subject');
$body = elgg_echo('rating:notify:body', array(


Example of the new way:

// Notify $owner that $user has added a $rating to an $entity created by him

$subject = elgg_echo('rating:notify:subject');
$summary = elgg_echo('rating:notify:summary', array($entity->title));
$body = elgg_echo('rating:notify:body', array(

$params = array(
        'object' => $rating,
        'action' => 'create',
        'summary' => $summary,



Compatible with 1.8

Adding items to the Activity listing
add_to_river('river/object/photo/create', 'create', $user_guid, $photo_guid);
    'view' => 'river/object/photo/create',
    'action_type' => 'create',
    'subject_guid' => $user_guid,
    'object_guid' => $photo_guid,

You can also add the optional target_guid parameter which tells the target of the create action.

If the photo would had been added for example into a photo album, we could add it by passing in also:

'target_guid' => $album_guid,


Not compatible with 1.8

Entity URL handlers

The elgg_register_entity_url_handler() function has been deprecated. In 1.9 you should use the 'entity:url', 'object' plugin hook instead.

Example of the old way:

 * Initialize the photo plugin
my_plugin_init() {
    elgg_register_entity_url_handler('object', 'photo', 'photo_url_handler');

 * Returns the URL from a photo entity
 * @param ElggEntity $entity
 * @return string
function photo_url_handler($entity) {
    return "photo/view/{$entity->guid}";

Example of the new way:

 * Initialize the photo plugin
my_plugin_init() {
    elgg_register_plugin_hook_handler('entity:url', 'object', 'photo_url_handler');

 * Returns the URL from a photo entity
 * @param string $hook   'entity:url'
 * @param string $type   'object'
 * @param string $url    The current URL
 * @param array  $params Hook parameters
 * @return string
function photo_url_handler($hook, $type, $url, $params) {
    $entity = $params['entity'];

    // Check that the entity is a photo object
    if ($entity->getSubtype() !== 'photo') {
        // This is not a photo object, so there's no need to go further

    return "photo/view/{$entity->guid}";


Not compatible with 1.8

Web services

In Elgg 1.8 the web services API was included in core and methods were exposed using expose_function(). To enable the same functionality for Elgg 1.9, enable the “Web services 1.9” plugin and replace all calls to expose_function() with elgg_ws_expose_function().

From 1.7 to 1.8

Elgg 1.8 is the biggest leap forward in the development of Elgg since version 1.0. As such, there is more work to update core and plugins than with previous upgrades. There were a small number of API changes and following our standard practice, the methods we deprecated have been updated to work with the new API. The biggest changes are in the standardization of plugins and in the views system.

Updating core

Delete the following core directories (same level as _graphics and engine):

  • _css
  • account
  • admin
  • dashboard
  • entities
  • friends
  • search
  • settings
  • simplecache
  • views


If you do not delete these directories before an upgrade, you will have problems!

Updating plugins
Use standardized routing with page handlers
  • All: /page_handler/all
  • User’s content: /page_handler/owner/:username
  • User’s friends’ content: /page_handler/friends/:username
  • Single entity: /page_handler/view/:guid/:title
  • Added: /page_handler/add/:container_guid
  • Editing: /page_handler/edit/:guid
  • Group list: /page_handler/group/:guid/all
Include page handler scripts from the page handler

Almost every page handler should have a page handler script. (Example: bookmarks/all => mod/bookmarks/pages/bookmarks/all.php)

  • Call set_input() for entity guids in the page handler and use get_input() in the page handler scripts.
  • Call gatekeeper() and admin_gatekeeper() in the page handler function if required.
  • The group URL should use the pages/:handler/owner.php script.
  • Page handlers should not contain HTML.
  • Update the URLs throughout the plugin. (Don’t forget to remove /pg/!)
Use standardized page handlers and scripts
  • Store page handler scripts in mod/:plugin/pages/:page_handler/:page_name.php

  • Use the content page layout in page handler scripts:

    $content = elgg_view_layout('content', $options);
  • Page handler scripts should not contain HTML.

  • Call elgg_push_breadcrumb() in the page handler scripts.

  • No need to set page owner if the URLs are in the standardized format.

  • For group content, check the container_guid by using elgg_get_page_owner_entity().

The object/:subtype view
  • Make sure there are views for $vars['full_view'] == true and $vars['full_view'] == false. $vars['full_view'] replaced $vars['full].
  • Check for the object in $vars['entity']. Use elgg_instance_of() to make sure it’s the type of entity you want.
  • Return true to short circuit the view if the entity is missing or wrong.
  • Use elgg_view(‘object/elements/summary’, array(‘entity’ => $entity)); and elgg_view_menu(‘entity’, array(‘entity’ => $entity)); to help format. You should use very little markup in these views.
Update action structure
  • Namespace action files and action names (example: mod/blog/actions/blog/save.php => action/blog/save)
  • Use the following action URLs:
    • Add: action/:plugin/save
    • Edit: action/:plugin/save
    • Delete: action/:plugin/delete
  • Make the delete action accept action/:handler/delete?guid=:guid so the metadata entity menu has the correct URL by default.
Update deprecated functions
  • Functions deprecated in 1.7 will produce visible errors in 1.8.
  • You can also update functions deprecated in 1.8.
    • Many registration functions simply added an elgg_ prefix for consistency, and should be easy to update.
    • See /engine/lib/deprecated-1.8.php for the full list.
    • You can set the debug level to “warning” to get visual reminders of deprecated functions.
Update the widget views

See the blog or file widgets for examples.

Update the group profile module

Use the blog or file plugins for examples. This will help with making your plugin themeable by the new CSS framework.

Update forms
  • Move form bodies to the forms/:action view to use Evan’s new elgg_view_form.
  • Use input views in form bodies rather than html. This helps with theming and future-proofing.
  • Add a function that prepares the form (see mod/file/lib/file.php for an example)
  • Make your forms sticky (see the file plugin’s upload action and form prepare function).

The forms API is discussed in more detail in Forms + Actions.

Clean up CSS/HTML

We have added many CSS patterns to the base CSS file (modules, image block, spacing primitives). We encourage you to use these patterns and classes wherever possible. Doing so should:

  1. Reduce maintenance costs, since you can delete most custom CSS.
  2. Make your plugin more compatible with community themes.

Look for patterns that can be moved into core if you need significant CSS.

We use hyphens rather than underscores in classes/ids and encourage you do the same for consistency.

If you do need your own CSS, you should use your own namespace, rather than elgg-.

Update manifest.xml
  • Use http://el.gg/manifest17to18 to automate this.
  • Don’t use the “bundled” category with your plugins. That is only for plugins distributed with Elgg.
Update settings and user settings views
  • The view for settings is now plugins/:plugin/settings (previously settings/:plugin/edit).
  • The view for user settings is now plugins/:plugin/usersettings (previously usersettings/:plugin/edit).

Upgrading plugin data

Every now and then there comes a time when a plugin needs to change the contents or the structure of the data it has stored either in the database or the dataroot.

The motivation for this may be that the data structure needs to be converted to more efficient or flexible structure. Or perhaps due to a bug the data items have been saved in an invalid way, and they needs to be converted to the correct format.

Migrations and convertions like this may take a long time if there is a lot of data to be processed. This is why Elgg provides the Elgg\Upgrade\Batch interface that can be used for implementing long-running upgrades.

Declaring a plugin upgrade

Plugin can communicate the need for an upgrade under the upgrades key in elgg-plugin.php file. Each value of the array must be the fully qualified name of an upgrade class that implements the Elgg\Upgrade\Batch interface.

Example from mod/blog/elgg-plugin.php file:

return [
        'upgrades' => [
The class names in the example refer to the classes:
  • mod/blog/classes/Blog/Upgrades/AccessLevelFix
  • mod/blog/classes/Blog/Upgrades/DraftStatusUpgrade


Elgg core upgrade classes can be declared in engine/lib/upgrades/async-upgrades.php.

The upgrade class

A class implementing the Elgg\Upgrade\Batch interface has a lot of freedom on how it wants to handle the actual processing of the data. It must however declare some constant variables and also take care of marking whether each processed item was upgraded successfully or not.

The basic structure of the class is the following:


namespace Blog\Upgrades;

use Elgg\Upgrade\Batch;
use Elgg\Upgrade\Result;

 * Fixes invalid blog access values
class AccessLevelFix implements Batch {

         * Version of the upgrade
         * @return int
        public function getVersion() {
                return 2016120300;

         * Should the run() method receive an offset representing all processed items?
         * @return bool
        public function needsIncrementOffset() {
                return true;

         * Should this upgrade be skipped?
         * @return bool
        public function shouldBeSkipped() {
                return false;

         * The total number of items to process in the upgrade
         * @return int
        public function countItems() {
                // return count of all blogs

         * Runs upgrade on a single batch of items
         * @param Result $result Result of the batch (this must be returned)
         * @param int    $offset Number to skip when processing
         * @return Result Instance of \Elgg\Upgrade\Result
        public function run(Result $result, $offset) {
                // fix 50 blogs skipping the first $offset


Do not assume when your class will be instantiated or when/how often its public methods will be called.

Class methods

This must return an integer representing the date the upgrade was added. It consists of eight digits and is in format yyyymmddnn where:

  • yyyy is the year
  • mm is the month (with leading zero)
  • dd is the day (with leading zero)
  • nn is an incrementing number (starting from 00) that is used in case two separate upgrades have been added during the same day

This should return false unless the upgrade won’t be needed.


If true is returned the upgrade cannot be run later.


If true, your run() method will receive as $offset the number of items aready processed. This is useful if you are only modifying data, and need to use the $offset in a function like elgg_get_entities*() to know how many you’ve already handled.

If false, your run() method will receive as $offset the total number of failures. false should be used if your process deletes or moves data out of the way of the process. E.g. if you delete 50 objects on each run(), you don’t really need the $offset.


Get the total number of items to process during the upgrade. If unknown, Batch::UNKNOWN_COUNT can be returned, but run() must manually mark the upgrade complete.


This must perform a portion of the actual upgrade. And depending on how long it takes, it may be called multiple times during a single request.

It receives two arguments:

  • $result: An instance of Elgg\Upgrade\Result object
  • $offset: The offset where the next upgrade portion should start (or total number of failures)

For each item the method processes, it must call either:

  • $result->addSuccesses(): If the item was upgraded successfully
  • $result->addFailures(): If it failed to upgrade the item

Both methods default to one item, but you can optionally pass in the number of items.

Additionally it can set as many error messages as it sees necessary in case something goes wrong:

  • $result->addError("Error message goes here")

If countItems() returned Batch::UNKNOWN_COUNT, then at some point run() must call $result->markComplete() to finish the upgrade.

In most cases your run() method will want to pass the $offset parameter to one of the elgg_get_entities*() functions:

 * Process blog posts
 * @param Result $result The batch result (will be modified and returned)
 * @param int    $offset Starting point of the batch
 * @return Result Instance of \Elgg\Upgrade\Result;
public function run(Result $result, $offset) {
        $blogs = elgg_get_entitites([
                'type' => 'object'
                'subtype' => 'blog'
                'offset' => $offset,

        foreach ($blogs as $blog) {
                if ($this->fixBlogPost($blog)) {
                } else {
                        $result->addError("Failed to fix the blog {$blog->guid}.");

        return $result;

Administration interface

Each upgrade implementing the Elgg\Upgrade\Batch interface gets listed in the admin panel after triggering the site upgrade from the Administration dashboard.

While running the upgrades Elgg provides:

  • Estimated duration of the upgrade
  • Count of processed items
  • Number of errors
  • Possible error messages

List of events in core

System events

plugins_boot, system
Triggered just after the plugins are loaded. Rarely used. init, system is used instead.
init, system
Plugins tend to use this event for initialization (extending views, registering callbacks, etc.)
ready, system
Triggered after the init, system event. All plugins are fully loaded and the engine is ready to serve pages.
shutdown, system
Triggered after the page has been sent to the user. Expensive operations could be done here and not make the user wait.


Depending upon your server configuration the PHP output might not be shown until after the process is completed. This means that any long-running processes will still delay the page load.


This event is prefered above using register_shutdown_function as you may not have access to all the Elgg services (eg. database) in the shutdown function but you will in the event.

regenerate_site_secret:before, system
Return false to cancel regenerating the site secret. You should also provide a message to the user.
regenerate_site_secret:after, system
Triggered after the site secret has been regenerated.
log, systemlog
Called for all triggered events. Used internally by system_log_default_logger() to populate the system_log table.
upgrade, system
Triggered after a system upgrade has finished. All upgrade scripts have run, but the caches are not cleared.
upgrade, upgrade
A single upgrade script finished executing. Handlers are passed a stdClass object with the properties
  • from - The version of Elgg upgrading from.
  • to - The version just upgraded to.
activate, plugin
Return false to prevent activation of the plugin.
deactivate, plugin
Return false to prevent deactivation of the plugin.
init:cookie, <name>
Return false to override setting a cookie.
cache:flush, system
Reset internal and external caches, by default including system_cache, simplecache, and memcache. One might use it to reset others such as APC, OPCache, or WinCache.
send:before, http_response
Triggered before an HTTP response is sent. Handlers will receive an instance of SymfonyComponentHttpFoundationResponse that is to be sent to the requester. Handlers can terminate the event and prevent the response from being sent by returning false.
send:after, http_response
Triggered after an HTTP response is sent. Handlers will receive an instance of SymfonyComponentHttpFoundationResponse that was sent to the requester.

User events

login:before, user
Triggered during login. Returning false prevents the user from logging
login:after, user
Triggered after the user logs in.
logout:before, user
Triggered during logout. Returning false should prevent the user from logging out.
logout:after, user
Triggered after the user logouts.
validate, user
When a user registers, the user’s account is disabled. This event is triggered to allow a plugin to determine how the user should be validated (for example, through an email with a validation link).
validate:after, user
Triggered when user’s account has been validated.
invalidate:after, user
Triggered when user’s account validation has been revoked.
profileupdate, user
User has changed profile
profileiconupdate, user
User has changed profile icon
ban, user
Triggered before a user is banned. Return false to prevent.
unban, user
Triggered before a user is unbanned. Return false to prevent.
make_admin, user
Triggered before a user is promoted to an admin. Return false to prevent.
remove_admin, user
Triggered before a user is demoted from an admin. Return false to prevent.

Relationship events

create, relationship
Triggered after a relationship has been created. Returning false deletes the relationship that was just created.


This event was broken in Elgg 1.9 - 1.12.3, returning false would not delete the relationship. This is working as of 1.12.4

delete, relationship
Triggered before a relationship is deleted. Return false to prevent it from being deleted.
join, group
Triggered after the user $params['user'] has joined the group $params['group'].
leave, group
Triggered before the user $params['user'] has left the group $params['group'].

Entity events

create, <entity type>
Triggered for user, group, object, and site entities after creation. Return false to delete entity.
update, <entity type>
Triggered before an update for the user, group, object, and site entities. Return false to prevent update. The entity method getOriginalAttributes() can be used to identify which attributes have changed since the entity was last saved.
update:after, <entity type>
Triggered after an update for the user, group, object, and site entities. The entity method getOriginalAttributes() can be used to identify which attributes have changed since the entity was last saved.
delete, <entity type>
Triggered before entity deletion. Return false to prevent deletion.
disable, <entity type>
Triggered before the entity is disabled. Return false to prevent disabling.
disable:after, <entity type>
Triggered after the entity is disabled.
enable, <entity type>
Return false to prevent enabling.
enable:after, <entity type>
Triggered after the entity is enabled.

Metadata events

create, metadata
Called after the metadata has been created. Return false to delete the metadata that was just created.
update, metadata
Called after the metadata has been updated. Return false to delete the metadata.
delete, metadata
Called before metadata is deleted. Return false to prevent deletion.
enable, metadata
Called when enabling metadata. Return false to prevent enabling.
disable, metadata
Called when disabling metadata. Return false to prevent disabling.

Annotation events

annotate, <entity type>
Called before the annotation has been created. Return false to prevent annotation of this entity.
create, annotation
Called after the annotation has been created. Return false to delete the annotation.
update, annotation
Called after the annotation has been updated. Return false to delete the annotation.
delete, annotation
Called before annotation is deleted. Return false to prevent deletion.
enable, annotation
Called when enabling annotations. Return false to prevent enabling.
disable, annotations
Called when disabling annotations. Return false to prevent disabling.

River events

created, river

Called after a river item is created.


Use the plugin hook creating, river to cancel creation (or alter options).

delete:before, river
Triggered before a river item is deleted. Returning false cancels the deletion.
delete:after, river
Triggered after a river item was deleted.

File events

upload:after, file
Called after an uploaded file has been written to filestore. Receives an instance of ElggFile the uploaded file was written to. The ElggFile may or may not be an entity with a GUID.


Because of bugs in the Elgg core, some events may be thrown more than once on the same action. For example, update, object is thrown twice.

List of plugin hooks in core

System hooks

page_owner, system
Filter the page_owner for the current page. No options are passed.

siteid, system

gc, system
Allows plugins to run garbage collection for $params['period'].
unit_test, system
Add a Simple Test test. (Deprecated.)
diagnostics:report, system
Filter the output for the diagnostics report download.

search_types, get_types

cron, <period>
Triggered by cron for each period.
validate, input
Filter GET and POST input. This is used by get_input() to sanitize user input.
geocode, location
Deprecated as of 1.9.
diagnostics:report, system
Filters the output for a diagnostic report.
debug, log

Triggered by the Logger. Return false to stop the default logging method. $params includes:

  • level - The debug level. One of:
    • Elgg_Logger::OFF
    • Elgg_Logger::ERROR
    • Elgg_Logger::WARNING
    • Elgg_Logger::NOTICE
    • Elgg_Logger::INFO
  • msg - The message
  • display - Should this message be displayed?
format, friendly:title
Formats the “friendly” title for strings. This is used for generating URLs.
format, friendly:time
Formats the “friendly” time for the timestamp $params['time'].
format, strip_tags
Filters a string to remove tags. The original string is passed as $params['original_string'] and an optional set of allowed tags is passed as $params['allowed_tags'].
output:before, page
In elgg_view_page(), this filters $vars before it’s passed to the page shell view (page/<page_shell>). To stop sending the X-Frame-Options header, unregister the handler _elgg_views_send_header_x_frame_options() from this hook.
output, page
In elgg_view_page(), this filters the output return value.
parameters, menu:<menu_name>
Triggered by elgg_view_menu(). Used to change menu variables (like sort order) before rendering.
register, menu:<menu_name>
Filters the initial list of menu items pulled from configuration, before the menu has been split into sections. Triggered by elgg_view_menu() and elgg()->menus->getMenu().
prepare, menu:<menu_name>
Filters the array of menu sections before they’re displayed. Each section is a string key mapping to an area of menu items. This is a good hook to sort, add, remove, and modify menu items. Triggered by elgg_view_menu() and elgg()->menus->prepareMenu().
register, menu:filter:<filter_id>
Allows plugins to modify layout filter tabs on layouts that specify <filter_id> parameter.
filter_tabs, <context>

Filters the array of ElggMenuItem used to display the All/Mine/Friends tabs. The $params array includes:

  • selected: the selected menu item name
  • user: the logged in ElggUser or null
  • vars: The $vars argument passed to elgg_get_filter_tabs
creating, river
The options for elgg_create_river_item are filtered through this hook. You may alter values or return false to cancel the item creation.
simplecache:generate, <view>
Filters the view output for a /cache URL when simplecache is enabled.
cache:generate, <view>
Filters the view output for a /cache URL when simplecache is disabled. Note this will be fired for every /cache request–no Expires headers are used when simplecache is disabled.
prepare, breadcrumbs

In elgg_get_breadcrumbs(), this filters the registered breadcrumbs before returning them, allowing a plugin to alter breadcrumb strategy site-wide. $params array includes:

  • breadcrumbs - an array of bredcrumbs, each with title and link keys
  • identifier - route identifier of the current page
  • segments - route segments of the current page

add, river

elgg.data, site
Filters cached configuration data to pass to the client. More info
elgg.data, page
Filters uncached, page-specific configuration data to pass to the client. More info
registration_url, site
Filters site’s registration URL. Can be used by plugins to attach invitation codes, referrer codes etc. to the registration URL. $params array contains an array of query elements added to the registration URL by the invoking script. The hook must return an absolute URL to the registration page.
login_url, site
Filters site’s login URL. $params array contains an array of query elements added to the login URL by the invoking script. The hook must return an absolute URL of the login page.

User hooks

usersettings:save, user
Triggered in the aggregate action to save user settings. Return false prevent sticky forms from being cleared.
access:collections:write, user
Filters an array of access permissions that the user $params['user_id'] is allowed to save content with. Permissions returned are of the form (id => ‘Human Readable Name’).
registeruser:validate:username, all
Return boolean for if the string in $params['username'] is valid for a username.
registeruser:validate:password, all
Return boolean for if the string in $params['password'] is valid for a password.
registeruser:validate:email, all
Return boolean for if the string in $params['email'] is valid for an email address.
register, user
Triggered by the register action after the user registers. Return false to delete the user. Note the function register_user does not trigger this hook.
login:forward, user
Filters the URL to which the user will be forwarded after login.
find_active_users, system
Return the number of active users.
status, user
Triggered by The Wire when adding a post.
username:character_blacklist, user
Filters the string of blacklisted characters used to validate username during registration. The return value should be a string consisting of the disallowed characters. The default string can be found from $params['blacklist'].

Object hooks

comments, <entity_type>
Triggered in elgg_view_comments(). If returning content, this overrides the page/elements/comments view.
comments:count, <entity_type>
Return the number of comments on $params['entity'].
likes:count, <entity_type>
Return the number of likes for $params['entity'].

Access hooks

access_collection:url, access_collection

Can be used to filter the URL of the access collection.

The $params array will contain:

  • access_collection - ElggAccessCollection
access_collection:name, access_collection

Can be used to filter the display name (readable access level) of the access collection.

The $params array will contain:

  • access_collection - ElggAccessCollection
access:collections:read, user

Filters an array of access IDs that the user $params['user_id'] can see.


The handler needs to either not use parts of the API that use the access system (triggering the hook again) or to ignore the second call. Otherwise, an infinite loop will be created.

access:collections:write, user

Filters an array of access IDs that the user $params['user_id'] can write to. In get_write_access_array(), this hook filters the return value, so it can be used to alter the available options in the input/access view. For core plugins, the value “input_params” has the keys “entity” (ElggEntity|false), “entity_type” (string), “entity_subtype” (string), “container_guid” (int) are provided. An empty entity value generally means the form is to create a new object.


The handler needs to either not use parts of the API that use the access system (triggering the hook again) or to ignore the second call. Otherwise, an infinite loop will be created.

access:collections:addcollection, collection
Triggered after an access collection $params['collection_id'] is created.
access:collections:deletecollection, collection
Triggered before an access collection $params['collection_id'] is deleted. Return false to prevent deletion.
access:collections:add_user, collection
Triggered before adding user $params['user_id'] to collection $params['collection_id']. Return false to prevent adding.
access:collections:remove_user, collection
Triggered before removing user $params['user_id'] to collection $params['collection_id']. Return false to prevent removal.
get_sql, access
Filters the SQL clauses used in _elgg_get_access_where_sql().

Action hooks

action, <action>
Triggered before executing action scripts. Return false to abort action.
action_gatekeeper:permissions:check, all
Triggered after a CSRF token is validated. Return false to prevent validation.
action_gatekeeper:upload_exceeded_msg, all
Triggered when a POST exceeds the max size allowed by the server. Return an error message to display.
forward, <reason>
Filter the URL to forward a user to when forward($url, $reason) is called.
response, action:<action>
Filter an instance of \Elgg\Http\ResponseBuilder before it is sent to the client. This hook can be used to modify response content, status code, forward URL, or set additional response headers. Note that the <action> value is parsed from the request URL, therefore you may not be able to filter the responses of action() calls if they are nested within the another action script file.


ajax_response, *

When the elgg/Ajax AMD module is used, this hook gives access to the response object (\Elgg\Services\AjaxResponse) so it can be altered/extended. The hook type depends on the method call:

elgg/Ajax method plugin hook type
action() action:<action_name>
path() path:<url_path>
view() view:<view_name>
form() form:<action_name>
output, ajax
This filters the JSON output wrapper returned to the legacy ajax API (elgg.ajax, elgg.action, etc.). Plugins can alter the output, forward URL, system messages, and errors. For the elgg/Ajax AMD module, use the ajax_response hook documented above.

Permission hooks

container_logic_check, <entity_type>

Triggered by ElggEntity:canWriteToContainer() before triggering permissions_check and container_permissions_check hooks. Unlike permissions hooks, logic check can be used to prevent certain entity types from being contained by other entity types, e.g. discussion replies should only be contained by discussions. This hook can also be used to apply status logic, e.g. do disallow new replies for closed discussions.

The handler should return false to prevent an entity from containing another entity. The default value passed to the hook is null, so the handler can check if another hook has modified the value by checking if return value is set. Should this hook return false, container_permissions_check and permissions_check hooks will not be triggered.

The $params array will contain:

  • container - An entity that will be used as a container
  • user - User who will own the entity to be written to container
  • subtype - Subtype of the entity to be written to container (entity type is assumed from hook type)
container_permissions_check, <entity_type>

Return boolean for if the user $params['user'] can use the entity $params['container'] as a container for an entity of <entity_type> and subtype $params['subtype'].

In the rare case where an entity is created with neither the container_guid nor the owner_guid matching the logged in user, this hook is called twice, and in the first call $params['container'] will be the owner, not the entity’s real container.

The $params array will contain:

  • container - An entity that will be used as a container
  • user - User who will own the entity to be written to container
  • subtype - Subtype of the entity to be written to container (entity type is assumed from hook type)
permissions_check, <entity_type>
Return boolean for if the user $params['user'] can edit the entity $params['entity'].
permissions_check:delete, <entity_type>
Return boolean for if the user $params['user'] can delete the entity $params['entity']. Defaults to $entity->canEdit().
permissions_check:delete, river

Return boolean for if the user $params['user'] can delete the river item $params['item']. Defaults to true for admins and false for other users.


This check is not performed when using the deprecated elgg_delete_river().

permissions_check, widget_layout
Return boolean for if $params['user'] can edit the widgets in the context passed as $params['context'] and with a page owner of $params['page_owner'].
permissions_check:metadata, <entity_type>
Return boolean for if the user $params['user'] can edit the metadata $params['metadata'] on the entity $params['entity'].
permissions_check:comment, <entity_type>
Return boolean for if the user $params['user'] can comment on the entity $params['entity'].
permissions_check:annotate:<annotation_name>, <entity_type>

Return boolean for if the user $params['user'] can create an annotation <annotation_name> on the entity $params['entity']. If logged in, the default is true.


This is called before the more general permissions_check:annotate hook, and its return value is that hook’s initial value.

permissions_check:annotate, <entity_type>

Return boolean for if the user $params['user'] can create an annotation $params['annotation_name'] on the entity $params['entity']. if logged in, the default is true.


This is functions differently than the permissions_check:metadata hook by passing the annotation name instead of the metadata object.

Return boolean for if the user in $params['user'] can edit the annotation $params['annotation'] on the entity $params['entity']. The user can be null.
fail, auth
Return the failure message if authentication failed. An array of previous PAM failure methods is passed as $params.
api_key, use
Triggered by api_auth_key(). Returning false prevents the key from being authenticated.
gatekeeper, <entity_type>:<entity_subtype>
Filters the result of elgg_entity_gatekeeper() to prevent access to an entity that user would otherwise have access to. A handler should return false to deny access to an entity.


These hooks are listed chronologically in the lifetime of the notification event. Note that not all hooks apply to instant notifications.

enqueue, notification

Can be used to prevent a notification event from sending subscription notifications. Hook handler must return false to prevent a subscription notification event from being enqueued.

$params array includes:

  • object - object of the notification event
  • action - action that triggered the notification event. E.g. corresponds to publish when elgg_trigger_event('publish', 'object', $object) is called
get, subscriptions

Filters subscribers of the notification event. Applies to subscriptions and instant notifications. In case of a subscription event, by default, the subscribers list consists of the users subscribed to the container entity of the event object. In case of an instant notification event, the subscribers list consists of the users passed as recipients to notify_user()

$params array includes:

  • event - \Elgg\Notifications\NotificationEvent instance that describes the notification event
  • origin - subscriptions_service or instant_notifications
  • methods_override - delivery method preference for instant notifications

Handlers must return an array in the form:

        <user guid> => array('sms'),
        <user_guid2> => array('email', 'sms', 'ajax')
send:before, notifications

Triggered before the notification event queue is processed. Can be used to terminate the notification event. Applies to subscriptions and instant notifications.

$params array includes:

  • event - \Elgg\Notifications\NotificationEvent instance that describes the notification event
  • subscriptions - a list of subscriptions. See 'get', 'subscriptions' hook for details
prepare, notification

A high level hook that can be used to alter an instance of \Elgg\Notifications\Notification before it is sent to the user. Applies to subscriptions and instant notifications. This hook is triggered before a more granular 'prepare', 'notification:<action>:<entity_type>:<entity_subtype>' and after 'send:before', 'notifications. Hook handler should return an altered notification object.

$params may vary based on the notification type and may include:

  • event - \Elgg\Notifications\NotificationEvent instance that describes the notification event
  • object - object of the notification event. Can be null for instant notifications
  • action - action that triggered the notification event. May default to notify_user for instant notifications
  • method - delivery method (e.g. email, site)
  • sender - sender
  • recipient - recipient
  • language - language of the notification (recipient’s language)
  • origin - subscriptions_service or instant_notifications
prepare, notification:<action>:<entity_type>:<entity_type>

A granular hook that can be used to filter a notification \Elgg\Notifications\Notification before it is sent to the user. Applies to subscriptions and instant notifications. In case of instant notifications that have not received an object, the hook will be called as 'prepare', 'notification:<action>'. In case of instant notifications that have not received an action name, it will default to notify_user.

$params include:

  • event - \Elgg\Notifications\NotificationEvent instance that describes the notification event
  • object - object of the notification event. Can be null for instant notifications
  • action - action that triggered the notification event. May default to notify_user for instant notifications
  • method - delivery method (e.g. email, site)
  • sender - sender
  • recipient - recipient
  • language - language of the notification (recipient’s language)
  • origin - subscriptions_service or instant_notifications
format, notification:<method>

This hook can be used to format a notification before it is passed to the 'send', 'notification:<method>' hook. Applies to subscriptions and instant notifications. The hook handler should return an instance of \Elgg\Notifications\Notification. The hook does not receive any $params. Some of the use cases include:

  • Strip tags from notification title and body for plaintext email notifications
  • Inline HTML styles for HTML email notifications
  • Wrap notification in a template, add signature etc.
send, notification:<method>

Delivers a notification. Applies to subscriptions and instant notifications. The handler must return true or false indicating the success of the delivery.

$params array includes:

  • notification - a notification object \Elgg\Notifications\Notification
email, system

Triggered by elgg_send_email(). Applies to subscriptions and instant notifications with email method. This hook can be used to alter email parameters (subject, body, headers etc) - the handler should return an array of altered parameters. This hook can also be used to implement a custom email transport (in place of Elgg’s default plaintext \Zend\Mail\Transport\Sendmail) - the handler must return true or false to indicate whether the email was sent using a custom transport.

$params contains:

  • to - email address or string in the form Name <name@example.org> of the recipient
  • from - email address or string in the form Name <name@example.org> of the sender
  • subject - subject line of the email
  • body - body of the email
  • headers - an array of headers
  • params - other parameters inherited from the notification object or passed directly to elgg_send_email()
email:message, system

Triggered by elgg_send_email(). Applies to subscriptions and instant notifications with email method. This hook allows you to alter an instance of \Zend\Mail\Message before it is passed to the email transport.

$params contains:

  • to - email address or string in the form Name <name@example.org> of the recipient
  • from - email address or string in the form Name <name@example.org> of the sender
  • subject - subject line of the email
  • body - body of the email
  • headers - an array of headers
  • params - other parameters inherited from the notification object or passed directly to elgg_send_email()
send:after, notifications

Triggered after all notifications in the queue for the notifications event have been processed. Applies to subscriptions and instant notifications.

$params array includes:

  • event - \Elgg\Notifications\NotificationEvent instance that describes the notification event
  • subscriptions - a list of subscriptions. See 'get', 'subscriptions' hook for details
  • deliveries - a matrix of delivery statuses by user for each delivery method


route, <identifier>
Allows applying logic or returning a response before the page handler is called. See Routing for details. Note that plugins using this hook to rewrite paths, will not be able to filter the response object by its final path and should either switch to route:rewrite, <identifier> hook or use response, path:<path> hook for the original path.
route:rewrite, <identifier>
Allows altering the site-relative URL path. See Routing for details.
response, path:<path>
Filter an instance of \Elgg\Http\ResponseBuilder before it is sent to the client. This hook type will only be used if the path did not start with “action/” or “ajax/”. This hook can be used to modify response content, status code, forward URL, or set additional response headers. Note that the <path> value is parsed from the request URL, therefore plugins using the route hook should use the original <path> to filter the response, or switch to using the route:rewrite hook.
ajax_response, path:<path>
Filters ajax responses before they’re sent back to the elgg/Ajax module. This hook type will only be used if the path did not start with “action/” or “ajax/”.


view_vars, <view_name>
Filters the $vars array passed to the view
view, <view_name>
Filters the returned content of the view
layout, page

In elgg_view_layout(), filters the layout name. $params array includes:

  • identifier - ID of the page being rendered
  • segments - URL segments of the page being rendered
  • other $vars received by elgg_view_layout()
shell, page
In elgg_view_page(), filters the page shell name
head, page
In elgg_view_page(), filters $vars['head'] Return value contains an array with title, metas and links keys, where metas is an array of elements to be formatted as <meta> head tags, and links is an array of elements to be formatted as <link> head tags. Each meta and link element contains a set of key/value pairs that are formatted into html tag attributes, e.g.
return [
   'title' => 'Current page title',
   'metas' => [
      'viewport' => [
         'name' => 'viewport',
         'content' => 'width=device-width',
   'links' => [
      'rss' => [
         'rel' => 'alternative',
         'type' => 'application/rss+xml',
         'title' => 'RSS',
         'href' => elgg_format_url($url),
      'icon-16' => [
         'rel' => 'icon',
         'sizes' => '16x16',
         'type' => 'image/png',
                 'href' => elgg_get_simplecache_url('graphics/favicon-16.png'),
ajax_response, view:<view>
Filters ajax/view/ responses before they’re sent back to the elgg/Ajax module.
ajax_response, form:<action>
Filters ajax/form/ responses before they’re sent back to the elgg/Ajax module.
response, view:<view_name>
Filter an instance of \Elgg\Http\ResponseBuilder before it is sent to the client. Applies to request to /ajax/view/<view_name>. This hook can be used to modify response content, status code, forward URL, or set additional response headers.
response, form:<form_name>
Filter an instance of \Elgg\Http\ResponseBuilder before it is sent to the client. Applies to request to /ajax/form/<form_name>. This hook can be used to modify response content, status code, forward URL, or set additional response headers.
table_columns:call, <name>
When the method elgg()->table_columns->$name() is called, this hook is called to allow plugins to override or provide an implementation. Handlers receive the method arguments via $params['arguments'] and should return an instance of Elgg\Views\TableColumn if they wish to specify the column directly.


mime_type, file
Return the mimetype for the filename $params['filename'] with original filename $params['original_filename'] and with the default detected mimetype of $params['default'].
simple_type, file
In elgg_get_file_simple_type(), filters the return value. The hook uses $params['mime_type'] (e.g. application/pdf or image/jpeg) and determines an overall category like document or image. The bundled file plugin and other-third party plugins usually store simpletype metadata on file entities and make use of it when serving icons and constructing ege* filters and menus.
upload, file

Allows plugins to implement custom logic for moving an uploaded file into an instance of ElggFile. The handler must return true to indicate that the uploaded file was moved. The handler must return false to indicate that the uploaded file could not be moved. Other returns will indicate that ElggFile::acceptUploadedFile should proceed with the default upload logic.

$params array includes:

  • file - instance of ElggFile to write to
  • upload - instance of Symfony’s UploadedFile


config, comments_per_page
Filters the number of comments displayed per page. Default is 25.
default, access
In get_default_access(), this hook filters the return value, so it can be used to alter the default value in the input/access view. For core plugins, the value “input_params” has the keys “entity” (ElggEntity|false), “entity_type” (string), “entity_subtype” (string), “container_guid” (int) are provided. An empty entity value generally means the form is to create a new object.
entity:icon:sizes, <entity_type>
Triggered by elgg_get_icon_sizes() and sets entity type/subtype specific icon sizes. entity_subtype will be passed with the $params array to the callback.
entity:<icon_type>:sizes, <entity_type>

Allows filtering sizes for custom icon types, see entity:icon:sizes, <entity_type>.

The hook must return an associative array where keys are the names of the icon sizes (e.g. “large”), and the values are arrays with the following keys:

  • w - Width of the image in pixels
  • h - Height of the image in pixels
  • square - Should the aspect ratio be a square (true/false)
  • upscale - Should the image be upscaled in case it is smaller than the given width and height (true/false)

If the configuration array for an image size is empty, the image will be saved as an exact copy of the source without resizing or cropping.


return [
        'small' => [
                'w' => 60,
                'h' => 60,
                'square' => true,
                'upscale' => true,
        'large' => [
                'w' => 600,
                'h' => 600,
                'upscale' => false,
        'original' => [],
entity:icon:url, <entity_type>

Triggered when entity icon URL is requested, see entity icons. Callback should return URL for the icon of size $params['size'] for the entity $params['entity']. Following parameters are available through the $params array:

Entity for which icon url is requested.
The type of view e.g. 'default' or 'json'.
Size requested, see entity icons for possible values.

Example on how one could default to a Gravatar icon for users that have not yet uploaded an avatar:

// Priority 600 so that handler is triggered after avatar handler
elgg_register_plugin_hook_handler('entity:icon:url', 'user', 'gravatar_icon_handler', 600);

 * Default to icon from gravatar for users without avatar.
function gravatar_icon_handler($hook, $type, $url, $params) {
        // Allow users to upload avatars
        if ($params['entity']->icontime) {
                return $url;

        // Generate gravatar hash for user email
        $hash = md5(strtolower(trim($params['entity']->email)));

        // Default icon size
        $size = '150x150';

        // Use configured size if possible
        $config = elgg_get_icon_sizes('user');
        $key = $params['size'];
        if (isset($config[$key])) {
                $size = $config[$key]['w'] . 'x' . $config[$key]['h'];

        // Produce URL used to retrieve icon
        return "http://www.gravatar.com/avatar/$hash?s=$size";
entity:<icon_type>:url, <entity_type>
Allows filtering URLs for custom icon types, see entity:icon:url, <entity_type>
entity:icon:file, <entity_type>
Triggered by ElggEntity::getIcon() and allows plugins to provide an alternative ElggIcon object that points to a custom location of the icon on filestore. The handler must return an instance of ElggIcon or an exception will be thrown.
entity:<icon_type>:file, <entity_type>
Allows filtering icon file object for custom icon types, see entity:icon:file, <entity_type>
entity:<icon_type>:prepare, <entity_type>

Triggered by ElggEntity::saveIcon*() methods and can be used to prepare an image from uploaded/linked file. This hook can be used to e.g. rotate the image before it is resized/cropped, or it can be used to extract an image frame if the uploaded file is a video. The handler must return an instance of ElggFile with a simpletype that resolves to image. The $return value passed to the hook is an instance of ElggFile that points to a temporary copy of the uploaded/linked file.

The $params array contains:

  • entity - entity that owns the icons
  • file - original input file before it has been modified by other hooks
entity:<icon_type>:save, <entity_type>

Triggered by ElggEntity::saveIcon*() methods and can be used to apply custom image manipulation logic to resizing/cropping icons. The handler must return true to prevent the core APIs from resizing/cropping icons. The $params array contains:

  • entity - entity that owns the icons
  • file - ElggFile object that points to the image file to be used as source for icons
  • x1, y1, x2, y2 - cropping coordinates
entity:<icon_type>:saved, <entity_type>

Triggered by ElggEntity::saveIcon*() methods once icons have been created. This hook can be used by plugins to create river items, update cropping coordinates for custom icon types etc. The handler can access the created icons using ElggEntity::getIcon(). The $params array contains:

  • entity - entity that owns the icons
  • x1, y1, x2, y2 - cropping coordinates
entity:<icon_type>:delete, <entity_type>

Triggered by ElggEntity::deleteIcon() method and can be used for clean up operations. This hook is triggered before the icons are deleted. The handler can return false to prevent icons from being deleted. The $params array contains:

  • entity - entity that owns the icons
entity:url, <entity_type>
Return the URL for the entity $params['entity']. Note: Generally it is better to override the getUrl() method of ElggEntity. This hook should be used when it’s not possible to subclass (like if you want to extend a bundled plugin without overriding many views).
to:object, <entity_type|metadata|annotation|relationship|river_item>
Converts the entity $params['entity'] to a StdClass object. This is used mostly for exporting entity properties for portable data formats like JSON and XML.
extender:url, <annotation|metadata>
Return the URL for the annotation or metadatum $params['extender'].
file:icon:url, override
Override a file icon URL.
is_member, group
Return boolean for if the user $params['user'] is a member of the group $params['group'].
entity:annotate, <entity_type>
Triggered in elgg_view_entity_annotations(), which is called by elgg_view_entity(). Can be used to add annotations to all full entity views.
usersetting, plugin

Filter user settings for plugins. $params contains:

  • user - An ElggUser instance
  • plugin - An ElggPlugin instance
  • plugin_id - The plugin ID
  • name - The name of the setting
  • value - The value to set
setting, plugin

Filter plugin settings. $params contains:

  • plugin - An ElggPlugin instance
  • plugin_id - The plugin ID
  • name - The name of the setting
  • value - The value to set
relationship:url, <relationship_name>
Filter the URL for the relationship object $params['relationship'].
profile:fields, group
Filter an array of profile fields. The result should be returned as an array in the format name => input view name. For example:
        'about' => 'longtext'
profile:fields, profile
Filter an array of profile fields. The result should be returned as an array in the format name => input view name. For example:
        'about' => 'longtext'
widget_settings, <widget_handler>
Triggered when saving a widget settings $params['params'] for widget $params['widget']. If handling saving the settings, the handler should return true to prevent the default code from running.
handlers, widgets
Triggered when a list of available widgets is needed. Plugins can conditionally add or remove widgets from this list or modify attributes of existing widgets like context or multiple.
get_list, default_widgets
Filters a list of default widgets to add for newly registered users. The list is an array of arrays in the format:
        'event' => $event,
        'entity_type' => $entity_type,
        'entity_subtype' => $entity_subtype,
        'widget_context' => $widget_context
public_pages, walled_garden

Filters a list of URLs (paths) that can be seen by logged out users in a walled garden mode. Handlers must return an array of regex strings that will allow access if matched. Please note that system public routes are passed as the default value to the hook, and plugins must take care to not accidentally override these values.

The $params array contains:

  • url - URL of the page being tested for public accessibility
volatile, metadata
Triggered when exporting an entity through the export handler. This is rare. This allows handler to handle any volatile (non-persisted) metadata on the entity. It’s preferred to use the to:object, <type> hook.
maintenance:allow, url
Return boolean if the URL $params['current_url'] and the path $params['current_path']
is allowed during maintenance mode.
robots.txt, site
Filter the robots.txt values for $params['site'].
config, amd
Filter the AMD config for the requirejs library.



embed_get_items, <active_section>

embed_get_sections, all

embed_get_upload_sections, all

profile_buttons, group
Filters buttons (ElggMenuItem instances) to be registered in the title menu of the group profile page
tool_options, group
Use this hook to influence the available group tool options
allowed_styles, htmlawed
Filter the HTMLawed allowed style array.
config, htmlawed
Filter the HTMLawed $config array.
spec, htmlawed
Filter the HTMLawed $spec string (default empty).
likes:is_likable, <type>:<subtype>

This is called to set the default permissions for whether to display/allow likes on an entity of type <type> and subtype <subtype>.


The callback 'Elgg\Values::getTrue' is a useful handler for this hook.

members:list, <page_segment>
To handle the page /members/$page_segment, register for this hook and return the HTML of the list.
members:config, tabs
This hook is used to assemble an array of tabs to be passed to the navigation/tabs view for the members pages.
Twitter API
authorize, twitter_api
Triggered when a user is authorizes Twitter for a login. $params['token'] contains the Twitter authorization token.
Reported Content
reportedcontent:add, system
Triggered after adding the reported content object $params['report']. Return false to delete report.
reportedcontent:archive, system
Triggered before archiving the reported content object $params['report']. Return false to prevent archiving.
reportedcontent:delete, system
Triggered before deleting the reported content object $params['report']. Return false to prevent deleting.
Web Services
rest, init
Triggered by the web services rest handler. Plugins can set up their own authentication handlers, then return true to prevent the default handlers from being registered.
rest:output, <method_name>
Filter the result (and subsequently the output) of the API method


Walk through all the required steps in order to customize Elgg.

The instructions are detailed enough that you don’t need much previous experience with Elgg.

Hello world

This tutorial shows you how to create a new plugin that consists of a new page with the text “Hello world” on it.

Before anything else, you need to install Elgg.

In this tutorial we will pretend your site’s URL is https://elgg.example.com.

First, create a directory that will contain the plugin’s files. It should be located under the mod/ directory which is located in your Elgg installation directory. So in this case, create mod/hello/.

Manifest file

Elgg requires that your plugin has a manifest file that contains information about the plugin. Therefore, in the directory you just created, create a file called manifest.xml and copy this code into it:

<?xml version="1.0" encoding="UTF-8"?>
<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">
    <name>Hello world</name>
    <author>Your Name Here</author>
    <description>Hello world, testing.</description>

This is the minimum amount of information in a manifest file:

  • <name> is the display name of the plugin
  • <id> must be the same as the directory you just created
  • <requires> must include which version of Elgg your plugin requires
  • <author>, <version> and <description> should have some appropriate values but can be filled freely


Next, create start.php in the mod/hello/ directory and copy this code into it:


elgg_register_event_handler('init', 'system', 'hello_world_init');

function hello_world_init() {


The above code tells Elgg that it should call the function hello_world_init() once the Elgg core system is initiated.

Registering a page handler

The next step is to register a page handler which has the purpose of handling request that users make to the URL https://elgg.example.com/hello.

Update start.php to look like this:


elgg_register_event_handler('init', 'system', 'hello_world_init');

function hello_world_init() {
    elgg_register_page_handler('hello', 'hello_world_page_handler');

function hello_world_page_handler() {
    echo elgg_view_resource('hello');

The call to elgg_register_page_handler() tells Elgg that it should call the function hello_world_page_handler() when a user navigates to https://elgg.example.com/hello/*.

The hello_world_page_handler() passes off rendering the actual page to a view file called hello.php.

View file

Create mod/hello/views/default/resources/hello.php with this content:


$params = array(
    'title' => 'Hello world!',
    'content' => 'My first page!',
    'filter' => '',

$body = elgg_view_layout('content', $params);

echo elgg_view_page('Hello', $body);

The code creates an array of parameters to be given to the elgg_view_layout() function, including:

  • The title of the page
  • The contents of the page
  • Filter which is left empty because there’s currently nothing to filter

This creates the basic layout for the page. The layout is then run through elgg_view_page() which assembles and outputs the full page.

Last step

Finally, activate the plugin through your Elgg administrator page: https://elgg.example.com/admin/plugins (the new plugin appears at the bottom).

You can now go to the address https://elgg.example.com/hello/ and you should see your new page!

Customizing the Home Page

To override the homepage, just override Elgg’s resources/index view by creating a file at /views/default/resources/index.php.

Any output from this view will become your new homepage.

You can take a similar approach with any other page in Elgg or official plugins.

Building a Blog Plugin

This tutorial will teach you how to create a simple blog plugin. The basic functions of the blog will be creating posts, saving them and viewing them. The plugin duplicates features that are found in the bundled blog plugin. You can disable the bundled blog plugin if you wish, but it is not necessary since the features do not conflict each other.


Create the plugin’s directory and manifest file

First, choose a simple and descriptive name for your plugin. In this tutorial, the name will be my_blog. Then, create a directory for your plugin in the /mod/ directory found in your Elgg installation directory. Other plugins are also located in /mod/. In this case, the name of the directory should be /mod/my_blog/. This directory is the root of your plugin and all the files that you create for the new plugin will go somewhere under it.

Next, in the root of the plugin, create the plugin’s manifest file, manifest.xml:

<?xml version="1.0" encoding="UTF-8"?>
<plugin_manifest xmlns="http://www.elgg.org/plugin_manifest/1.8">
    <name>My Blog</name>
    <author>Your Name Here</author>
    <description>Adds blogging capabilities.</description>

See Plugins for more information about the manifest file.

Create the form for creating a new blog post

Create a file at /mod/my_blog/views/default/forms/my_blog/save.php that contains the form body. The form should have input fields for the title, body and tags of the my_blog post. It does not need form tag markup.

echo elgg_view_field([
    '#type' => 'text',
    '#label' => elgg_echo('title'),
    'name' => 'title',
    'required' => true,

echo elgg_view_field([
    '#type' => 'longtext',
    '#label' => elgg_echo('body'),
    'name' => 'body',
    'required' => true,

echo elgg_view_field([
    '#type' => 'tags',
    '#label' => elgg_echo('tags'),
    '#help' => elgg_echo('tags:help'),
    'name' => 'tags',

$submit = elgg_view_field(array(
    '#type' => 'submit',
    '#class' => 'elgg-foot',
    'value' => elgg_echo('save'),

Notice how the form is calling elgg_view_field() to render inputs. This helper function maintains consistency in field markup, and is used as a shortcut for rendering field elements, such as label, help text, and input. See Forms + Actions.

You can see a complete list of input views in the /vendor/elgg/elgg/views/default/input/ directory.

It is recommended that you make your plugin translatable by using elgg_echo() whenever there is a string of text that will be shown to the user. Read more at Internationalization.

Create a page for composing the blogs

Create the file /mod/my_blog/views/default/resources/my_blog/add.php. This page will view the form you created in the above section.

// make sure only logged in users can see this page

// set the title
$title = "Create a new my_blog post";

// start building the main column of the page
$content = elgg_view_title($title);

// add the form to the main column
$content .= elgg_view_form("my_blog/save");

// optionally, add the content for the sidebar
$sidebar = "";

// layout the page
$body = elgg_view_layout('one_sidebar', array(
   'content' => $content,
   'sidebar' => $sidebar

// draw the page, including the HTML wrapper and basic page layout
echo elgg_view_page($title, $body);

The function elgg_view_form("my_blog/save") views the form that you created in the previous section. It also automatically wraps the form with a <form> tag and the necessary attributes as well as anti-csrf tokens.

The form’s action will be "<?= elgg_get_site_url() ?>action/my_blog/save".

Create the action file for saving the blog post

The action file will save the my_blog post to the database. Create the file /mod/my_blog/actions/my_blog/save.php:

// get the form inputs
$title = get_input('title');
$body = get_input('body');
$tags = string_to_tag_array(get_input('tags'));

// create a new my_blog object and put the content in it
$blog = new ElggObject();
$blog->title = $title;
$blog->description = $body;
$blog->tags = $tags;

// the object can and should have a subtype
$blog->subtype = 'my_blog';

// for now, make all my_blog posts public
$blog->access_id = ACCESS_PUBLIC;

// owner is logged in user
$blog->owner_guid = elgg_get_logged_in_user_guid();

// save to database and get id of the new my_blog
$blog_guid = $blog->save();

// if the my_blog was saved, we want to display the new post
// otherwise, we want to register an error and forward back to the form
if ($blog_guid) {
   system_message("Your blog post was saved.");
} else {
   register_error("The blog post could not be saved.");
   forward(REFERER); // REFERER is a global variable that defines the previous page

As you can see in the above code, Elgg objects have several fields built into them. The title of the my_blog post is stored in the title field while the body is stored in the description field. There is also a field for tags which are stored as metadata.

Objects in Elgg are a subclass of something called an “entity”. Users, sites, and groups are also subclasses of entity. An entity’s subtype allows granular control for listing and displaying, which is why every entity should have a subtype. In this tutorial, the subtype “my_blog” identifies a my_blog post, but any alphanumeric string can be a valid subtype. When picking subtypes, be sure to pick ones that make sense for your plugin.

The getURL method fetches the URL of the new post. It is recommended that you override this method. The overriding will be done in the start.php file.