Whether you are migrating content from a legacy CMS, syncing external data feeds, or building a custom automated workflow, knowing how to create Joomla categories and articles programmatically is a vital skill for any developer. While it might be tempting to perform raw SQL injections into the #__content table, doing so bypasses Joomla’s core logic, including Access Control Levels (ACL), alias generation, and nested set management.

In this guide, you will learn how to leverage the native Joomla Framework and its MVC models to safely and efficiently generate content using PHP. We will walk through the process of bootstrapping the framework, defining data arrays, and utilizing the save() methods of the Joomla core models.

Why Use Joomla Models Instead of Raw SQL?

When you create a category or an article in Joomla, the system does much more than just insert a row into a database. It calculates the "Nested Set" (left and right values) for category hierarchies, ensures that aliases are unique, and sets up default permissions.

By using the CategoriesModelCategory and ContentModelArticle classes, you ensure that: 1. Data Integrity: All database relationships remain intact. 2. ACL Compliance: Permissions are correctly inherited or assigned. 3. Plugin Triggers: Events like onContentBeforeSave and onContentAfterSave are fired, allowing other extensions (like SEF plugins or search indexers) to react to the new content.

Bootstrapping the Joomla Framework

To run a standalone PHP script that interacts with Joomla, you must first bootstrap the framework. This gives your script access to the JFactory class and all necessary constants. Place your script in the root of your Joomla installation or adjust the JPATH_BASE accordingly.

if (!defined('_JEXEC')) {
    define( '_JEXEC', 1 );
    define('JPATH_BASE', realpath(dirname(__FILE__)));
    require_once ( JPATH_BASE .'/includes/defines.php' );
    require_once ( JPATH_BASE .'/includes/framework.php' );
    defined('DS') or define('DS', DIRECTORY_SEPARATOR);
}

// Initialize the application
$app = JFactory::getApplication('site');

Step 1: Programmatically Creating a Category

Categories in Joomla are hierarchical. When creating a top-level category, the parent_id is typically set to 1 (the root category). If you are creating a subcategory, you must provide the ID of the existing parent category.

Here is how you can define and save a new category:

function createCategory($data)
{
    // Define basic ACL rules for the new category
    $data['rules'] = array(
        'core.edit.state' => array(),
        'core.edit.delete' => array(),
        'core.edit.edit' => array(),
        'core.edit.own' => array(1 => true)
    );

    // Load the Category Model from the administrator component
    $basePath = JPATH_ADMINISTRATOR . '/components/com_categories';
    require_once $basePath . '/models/category.php';

    $config = array('table_path' => $basePath . '/tables');
    $category_model = new CategoriesModelCategory($config);

    // Attempt to save the category
    if (!$category_model->save($data)) {
        return false;
    } else {
        // Return the newly created Category ID
        return $category_model->getItem()->id;
    }
}

Step 2: Programmatically Creating an Article

Once you have a category ID, you can proceed to create an article. The ContentModelArticle handles the heavy lifting of processing the introtext, fulltext, and metadata.

function createArticle($data)
{
    // Define basic ACL rules for the article
    $data['rules'] = array(
        'core.edit.delete' => array(),
        'core.edit.edit' => array(),
        'core.edit.state' => array(),
    );

    // Load the Content Model
    $basePath = JPATH_ADMINISTRATOR . '/components/com_content';
    require_once $basePath . '/models/article.php';

    $config = array();
    $article_model = new ContentModelArticle($config);

    // Attempt to save the article
    if (!$article_model->save($data)) {
        return false;
    } else {
        // Return the newly created Article ID
        return $article_model->getItem()->id;
    }
}

Putting It All Together: A Complete Workflow

Now that we have our helper functions, we can execute a script that creates a category and then immediately places a new article inside it.

// Define Category Data
$category_data = array(
    'id' => 0, // 0 indicates a new record
    'parent_id' => 1, // Root category
    'title' => 'My Category Title',
    'alias' => 'my-category-title-alias',
    'extension' => 'com_content',
    'published' => 1,
    'language' => '*',
    'params' => array('category_layout' => '', 'image' => ''),
    'metadata' => array('author' => '', 'robots' => '')
);

$category_id = createCategory($category_data);

if (!$category_id) {
    echo "Category creation failed!";
} else {
    // Define Article Data linked to the new Category ID
    $article_data = array(
        'id' => 0,
        'catid' => $category_id,
        'title' => 'My Article Title',
        'alias' => 'my-article-alias',
        'introtext' => 'This is the intro text of the article.',
        'fulltext' => '<p>This is the full text of the article.</p>',
        'state' => 1, // 1 = Published
        'language' => '*'
    );

    $article_id = createArticle($article_data);

    if ($article_id) {
        echo "Successfully created Category #$category_id and Article #$article_id";
    }
}

Alternative: Bulk Creation Tools

If you prefer not to write custom PHP scripts, there are existing extensions designed for bulk content management. One notable example is OSContent. This tool allows you to create articles and categories in bulk through a simplified interface within the Joomla administrator panel. This is often the preferred route for one-time migrations where a custom script would be overkill.

Frequently Asked Questions

Can I use this code in Joomla 4 or Joomla 5?

While the logic of using models remains the same, Joomla 4 and 5 introduced Namespacing. You would need to update the classes to use namespaces like Joomla\CMS\MVC\Model\AdminModel and use the Service Container to get the models. However, the core data structure for $data arrays remains very similar.

How do I handle article images or custom fields?

To handle images, you would add an images key to your $article_data array, which contains a JSON-encoded string of the image paths. For Custom Fields (com_fields), you would typically need to use the FieldsHelper or manually handle the #__fields_values table after the article is saved and you have an ID.

Why is my alias being appended with a number (e.g., -2)?

Joomla's model automatically checks for alias uniqueness. If you attempt to create an article with an alias that already exists in that category, the model will automatically append a unique identifier to prevent SEO conflicts and routing errors.

Wrapping Up

Creating content programmatically in Joomla is a powerful way to automate your site's growth. By using the built-in models, you ensure your site remains secure and your database stays clean. Always remember to test your scripts in a staging environment before running them on a live production site, and ensure you have a full database backup before performing bulk operations.

By mastering these PHP techniques, you unlock the ability to integrate Joomla with virtually any third-party data source, making your CMS truly dynamic.