One-click demo content importer
An admin tools page that imports pages and posts from a bundled JSON file idempotently, behind a nonce and capability check.
WordPressUtilitiesimportdemo-contentadmin-pagejson
Code
<?php
/**
* Demo content importer (Tools -> Demo content).
*
* Reads demo/content.json: [ { "key": "home", "type": "page", "title": "Home", "content": "<p>...</p>" } ]
*
* @package Acme_Theme
*/
defined( 'ABSPATH' ) || exit;
add_action( 'admin_menu', static function (): void {
add_management_page( __( 'Demo content', 'acme-theme' ), __( 'Demo content', 'acme-theme' ), 'manage_options', 'acme-demo', 'acme_demo_page' );
} );
function acme_demo_page(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Not allowed.', 'acme-theme' ) );
}
$done = isset( $_GET['imported'] ) ? absint( $_GET['imported'] ) : null;
?>
<div class="wrap">
<h1><?php esc_html_e( 'Demo content', 'acme-theme' ); ?></h1>
<?php if ( null !== $done ) : ?>
<div class="notice notice-success"><p><?php echo esc_html( sprintf( _n( '%d item imported.', '%d items imported.', $done, 'acme-theme' ), $done ) ); ?></p></div>
<?php endif; ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<input type="hidden" name="action" value="acme_demo_import">
<?php wp_nonce_field( 'acme_demo_import' ); ?>
<?php submit_button( __( 'Import demo content', 'acme-theme' ) ); ?>
</form>
</div>
<?php
}
add_action( 'admin_post_acme_demo_import', static function (): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Not allowed.', 'acme-theme' ), '', array( 'response' => 403 ) );
}
check_admin_referer( 'acme_demo_import' );
$file = get_template_directory() . '/demo/content.json';
$rows = is_readable( $file ) ? json_decode( (string) file_get_contents( $file ), true ) : array();
$new = 0;
foreach ( is_array( $rows ) ? $rows : array() as $row ) {
$key = sanitize_key( $row['key'] ?? '' );
$type = in_array( $row['type'] ?? '', array( 'page', 'post' ), true ) ? $row['type'] : 'page';
if ( '' === $key ) {
continue;
}
// Skip items imported before, so running it twice never duplicates.
$existing = get_posts( array( 'post_type' => $type, 'post_status' => 'any', 'meta_key' => '_acme_demo_key', 'meta_value' => $key, 'fields' => 'ids', 'numberposts' => 1 ) );
if ( $existing ) {
continue;
}
$id = wp_insert_post( array(
'post_type' => $type,
'post_status' => 'publish',
'post_title' => sanitize_text_field( $row['title'] ?? $key ),
'post_content' => wp_kses_post( $row['content'] ?? '' ),
), true );
if ( ! is_wp_error( $id ) ) {
update_post_meta( $id, '_acme_demo_key', $key );
if ( 'home' === $key ) {
update_option( 'show_on_front', 'page' );
update_option( 'page_on_front', $id );
}
++$new;
}
}
wp_safe_redirect( add_query_arg( 'imported', $new, admin_url( 'tools.php?page=acme-demo' ) ) );
exit;
} );
How it works
What it solves
Buyers of a theme want the preview site, not an empty install. A small importer creates sample pages and posts from a JSON file, can be run more than once without duplicating anything, and needs no external plugin.
How it works
A Tools submenu page shows a single button inside a form that posts to admin-post.php. The handler checks the manage_options capability, verifies the nonce, then reads demo/content.json. Each row has a stable key; if a post with that key stored in hidden meta already exists, it is skipped. New items are cleaned with sanitize_text_field and wp_kses_post before wp_insert_post, and a row called home also becomes the static front page.
The result count is shown through a redirect notice.
Adapting it
Extend the JSON with a parent key, menu items or featured images imported from files in your theme (use media_handle_sideload). Add a reset button that deletes only posts carrying the demo meta key. Keep the data small; for large sites, process in batches with AJAX.
Pitfalls
- Never import with a nonce or capability missing; this writes to the database.
- Do not trust the JSON blindly if users can upload their own.
file_get_contentson a local path is fine; avoid remote URLs here.- Keys must never change between releases or duplicates appear.
How to use it
Save as inc/demo-importer.php, require it from functions.php, and add demo/content.json next to it.
Open Tools, then Demo content, and press the button.
Example row: { "key": "home", "type": "page", "title": "Home", "content": "<p>Welcome</p>" }.
Licence: GPL-2.0-or-later. This snippet comes from WordPress code and is GPL-2.0-or-later.