I'm developing a site for an African language translation company and am currently trying to think of the best way to structure the site

I have a custom post type of "Languages" set up, with posts like Arabic, Somali, Swahili etc but each of these posts need "sub" posts within them i.e History, Where is it spoken, Dialects, Alphabet & Writing system

What would be the best way of setting this up? There are going to be around 140 languages so I don't think setting up individual posts for the 3/4 sub pages for each language is a good way to go as we'd be looking at over 500+ posts

I'm wondering if I can set up "base" posts for the 3/4 sub pages seeing as the titles will always be unique, just the content will be different

1 Answer 1


I've waited for quite a while for an answer to this and have had to continue on with the project so thought I would answer myself

First off I set up the custom post type of language, then hooked in to the publish_language action to programatically add child posts like so:

function ta_insert_child_posts($post_id) {  
    if(($_POST['post_status'] == 'publish') && ($_POST['original_post_status'] != 'publish')) {
        $post = get_post($post_id);

        // Make sure it's a top level language being published
        if($post->post_parent == 0) {
            // Create our array of child post titles
            $child_posts = array('History', 'Where is it spoken', 'Also known as', 'Dialects', 'Alphabet & Writing System');

            foreach($child_posts as $child_post_title) {
                // Insert each new post as a child of the new language
                    'post_title' => $child_post_title,
                    'post_parent' => $post_id,
                    'post_type' => 'language',
                    'post_status' => $post->post_status 
add_action('publish_language', 'ta_insert_child_posts');

Next, I had to add in logic to delete/trash child posts when their parent was deleted/trashed by hooking in to before_delete_post and trash_language

function ta_delete_child_posts($post_id) {
    global $post_type;

    if($post_type != 'language') return;

    $child_posts = get_posts(array('post_parent' => $post_id, 'post_type' => 'language'));

    if(is_array($child_posts)) {
        foreach($child_posts as $child_post) {
            wp_delete_post($child_post->ID, true);
add_action('before_delete_post', 'ta_delete_child_posts');

function ta_trash_child_posts($post_id) {
    $child_posts = get_posts(array('post_parent' => $post_id, 'post_type' => 'language'));

    if(is_array($child_posts)) {
        foreach($child_posts as $child_post) {
add_action('trash_language', 'ta_trash_child_posts');

Ok so we now have child posts being published and deleted in sync with their parent language. Next I had to ensure only top level languages were being pulled through in the admin ui language list so I hooked in to the request action:

function ta_modify_request($request) {
    if(is_admin()) {
        $screen = get_current_screen();

        // We only want to retrieve top level language posts in the main request
        if($screen->post_type == 'language') {
           $request['post_parent'] = 0;

    return $request;
add_action('request', 'ta_modify_request');

Lastly, I had to inject some custom CSS and JavaScript by hooking in to admin_footer which added an expand/contract link to each language, with an ajax call to a function which gets child posts of the selected language and displays them in the standard wordpress table format:

function ta_child_posts_scripts() {
    $screen = get_current_screen();

    if($screen->post_type == 'language') {
    <style type="text/css">
        #the-list tr .sorting-indicator {top:10px;position:relative;margin-top:0;cursor:pointer}
        #the-list tr .sorting-indicator.show:before {content:''}
        #the-list tr:hover .sorting-indicator {display:inline-block}
    <script type="text/javascript">
        jQuery(function($) {
            $('#the-list tr .row-title').each(function() {
                $(this).after('<span class="sorting-indicator show" title="Show Child Posts"></span>');

            $('#the-list tr .sorting-indicator').on('click', function() {
                var tr = $(this).parents('tr');

                if($(this).hasClass('show')) {
                    var data = {
                        action: 'ta_child_posts',
                        post_id: tr.attr('id')

                    $.post(ajaxurl, data, function(response) {


                } else {
                    tr.nextUntil('.level-0').fadeOut(function() { $(this).remove(); });



add_action('admin_footer', 'ta_child_posts_scripts');

With this in place, all I had left to do was the add in the ajax callback function to get the child posts based on the selected language:

if(is_admin() && !class_exists('WP_List_Table')){
    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
    require_once( ABSPATH . 'wp-admin/includes/class-wp-posts-list-table.php' );

function ta_get_child_posts() {

    if(empty($_POST['post_id'])) return;

    $post_id = explode('-', $_POST['post_id']);

    if(!isset($post_id[1])) return;

    $post_id = (int)$post_id[1];

    // Get child posts of the selected post
    $child_posts = get_posts(array('post_parent' => $post_id, 'post_type' => 'language'));


    $ta_table = new WP_Posts_List_Table(array('screen' => get_current_screen()));


    // Since WP_List_Table provides no way to return its data we print the output with display_rows but catch it in an output buffer

    $ta_table->display_rows($child_posts, 1);
    $rows = ob_get_clean();

    // Return the rows to the ajax callback

add_action('wp_ajax_ta_child_posts', 'ta_get_child_posts');

I hope this helps out a future googler with a similar issue or anyone else browsing this website

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.