Demo internals
A demo is a complete site in a folder: content, media, menus, widgets, Theme Options and a few site and WooCommerce settings. The code is in inc/Demo. The setup wizard runs these steps from an admin screen; WP-CLI runs them from the command line.
Package format
manifest.json
en/content.json
en/options.json
fa/content.json
fa/options.json
media/ images and videos
manifest.json:
format: 1.id,version.titleanddescription, each per language ({ "en": "…", "fa": "…" }).categories,languages.preview: a picture in the package, either one for every language ("preview.webp") or one per language ({ "fa": "preview-fa.webp", "en": "preview-en.webp" }). The wizard shows the one in the site's language, else English.live: preview addresses per language.plugins:{ "required": [], "recommended": [] }, as plugin slugs.
content.json:
posts: id, type, status, title, slug, content, excerpt, parent, order, terms and meta. Meta stored as JSON strings is decoded and markedjson: true, so ids inside it can be rewritten.terms.media: the file inmedia/, title, alt text, caption and original address.menus: their items and menu locations.widgets: per widget area.site: front page and posts page.woocommerce: store pages, price format and registration.attributes: WooCommerce attribute taxonomies, such as Color and Size.
The source site's and theme's addresses are written as {{site}} and {{theme}}.
The theme's own JSON is used instead of WXR, a choice made at the start of 9c. It keeps builder layouts, template settings and display conditions exactly as they are, needs no importer plugin, and lets every imported item be tagged for removal.
Accepted files: only data and media (json, jpg, png, webp, gif, mp4, webm, txt). SVG is accepted only in the theme's own bundled packages, because SVG can carry scripts. A remote package with any other file, such as .php or .htaccess, is refused and deleted.
Where demos come from
- Bundled:
bluetheme/demos/{id}/, with the theme. - Remote: Theme Options → Global → Performance → Demo catalog address, a JSON catalog:
{ "demos": [ { "id": "corporate", "version": "1.0.0",
"title": { "en": "Corporate", "fa": "شرکتی" }, "description": { … },
"categories": [ "business" ], "languages": [ "fa", "en" ],
"preview": { "fa": "https://…/corporate-fa.jpg", "en": "https://…/corporate-en.jpg" }, "live": { "fa": "https://…" },
"package": "https://…/corporate.zip",
"plugins": { "required": [], "recommended": [ "contact-form-7" ] } } ] }
The catalog is cached for 12 hours. Package addresses must use https. When a remote and a bundled demo share an id, the bundled one is used. The bluetheme_demo_catalog filter changes the list.
Import
Each step does a little work, so large demos never hit the PHP time limit:
- back up
- product attributes
- media
- terms
- posts
- links
- menus
- widgets
- options
- WooCommerce
- finish
Parts: content, templates, menus, widgets, Theme Options and WooCommerce settings can each be left out. Media and terms follow the content parts.
Content language: chosen by the site language (Persian on Persian sites, otherwise English) or asked for.
Rewritten references:
- Media values (
{ id, url }anywhere: images, galleries, backgrounds, logos) point at the imported attachments. - Builder settings are rewritten from their field types:
page→ posts;categoriesandtax:*→ terms;templates:*→ templates;menus→menu:ID;forms→cf7:ID,wpforms:ID.
- Template display conditions, Page Options template choices, thumbnails, product galleries, upsells and cross-sells, and variations' parent products are rewritten too.
- Builder layouts are saved through the builder's own save (
Document::save), so they are cleaned with the importing user's permissions.
Pages and terms:
- New pages that share a slug with an existing page get WordPress's usual "-2" slug.
- Terms with an existing slug, such as "Uncategorized" or a category the site already has, are reused rather than created.
Theme Options: a demo replaces every option except the site's own:
- SEO;
- social profile links;
- custom code;
- SMS settings;
- license settings;
- any secret (passwords, API keys).
These are never exported either.
Before the import the theme saves the Theme Options, widget areas, menu locations, front page and WooCommerce settings. Only one demo can be imported at a time.
Removal
Everything the import created is tagged (_bluetheme_demo post or term meta), so removal deletes exactly that:
- posts of every type;
- attachments with their files;
- terms;
- menus and their items;
- the widgets the demo added.
It then restores the saved settings exactly. The site's own settings (SEO, SMS, license…) keep their current values, in case they were changed after the import. Content that was there before the import is never touched.
Tested with a full round trip between the dev sites: the English site was exported, the export was imported into the Persian site, and then removed. A snapshot of post counts per type, terms, menus, Theme Options, widgets, menu locations, front page and shop page was identical before and after.
Setup wizard
BlueTheme → Setup wizard (Demo\Wizard, src/admin/wizard). It opens by itself once, the first time an administrator reaches the dashboard after the theme is activated.
- Welcome: start, or remove the imported demo (after a confirmation). It also has Export this site as a demo, which downloads a package (.zip) of the site in its current language. The package is built in a temporary folder and deleted after the download, which needs a nonce and
manage_options. - Plugins (
Demo\Plugins):- The list: WooCommerce, Contact Form 7, and on Persian sites also Persian WooCommerce and the ZarinPal gateway.
- Each shows its state, and the ticked ones are installed from WordPress.org and activated, one after another.
- Installs use WordPress's own upgrader, so
install_plugins/activate_pluginand the server's file permissions apply. A server that needs FTP details gets a message pointing to Plugins → Add New. - When an install fails, WordPress's reason is shown, for example a plugin needing a newer PHP.
- The
bluetheme_setup_pluginsfilter changes the list.
- Demo: a gallery of the bundled and remote demos, with:
- a preview image (or the demo's name);
- a description and languages;
- a "Live preview" link when the demo has one;
- category filters;
- "Check for new demos", which refreshes the remote catalog.
- Import:
- Choose between everything and the parts you pick, each with a short explanation.
- When the demo has both languages, choose the content language.
- Plugins the demo requires that are missing can be installed from here.
- Progress is shown with a progress bar and a list of steps. On an error you can stop the import, and what was created can then be removed.
- At the end: links to the site and to Theme Options.
Accessibility: each step's heading takes focus, the step list marks the current step (aria-current), the progress bar is a progressbar, and step messages are announced (aria-live). The layout uses logical properties and an RTL stylesheet, so it mirrors on right-to-left admin screens.
Bundled demos and how they are built
| Id | Site | Languages |
|---|---|---|
corporate |
"Nikan Consulting", a consulting firm: Home, About, Services, Team, Pricing, Contact, Blog and 6 posts; business header, dark footer, consultation popup | fa, en |
agency |
"Studio Arka", a design studio: Home, Work (8 projects with filters, 3 with galleries), Services, Studio, Journal (4 posts), Contact; transparent header, big-statement footer, corner popup | fa, en |
shop-clothing |
"Nakh", a clothing store: 12 products (variable ones with color and size swatches, a sale, one out of stock), 4 categories, shop filters, About, FAQ, Contact; shop header with promo bar, shop footer, welcome popup | fa, en |
shop-decor |
"Sayeh", home decor: furniture, lamps, ceramics and textiles in color variants; centered-logo header, card-style products | fa, en |
shop-electronics |
"Volt", electronics: phones, laptops, audio and wearables in color variants; compact shop header | fa, en |
shop-cosmetics |
"Nilou", cosmetics: makeup shades as swatches, skincare, fragrance and bath; shop header with promo bar, round sale badges | fa, en |
code |
"Navak", a software engineering company: Home, Services, Work (8 projects with filters, 3 with galleries), Team (8 people), Blog (4 posts), Contact with a project form; dark header with a dark mode toggle, dark mode on, dark footer, exit popup | fa, en |
ai |
"Pandar", an AI assistant product: Home, Features, Solutions, Pricing (3 plans), About with the team, Blog (4 posts), Book a demo form; simple header with a demo button, call-to-action footer, corner popup (not on phones) | fa, en |
hospital |
"Arman Hospital", a general hospital: Home, Departments, Doctors (8), Patients and visitors (visit steps, visiting hours, insurance, questions), Health news (4 posts), Appointments form with opening hours and a map; business header with an emergency line, three-column footer | fa, en |
physio |
"Tavana Physio", a physiotherapy clinic: Home (first-visit steps, session prices), Treatments, Conditions with questions, Therapists, Exercise tips (4 posts), Book a session form with opening hours and a map; stacked header with a green menu bar, contact footer, exit popup | fa, en |
dental |
"Labkhand Dental", a dental clinic: Home (visit steps, prices), Treatments (check-up, whitening, implants, braces), Dentists, Dental tips (4 posts), Appointments form with hours, map and questions; centered menu header, four-column footer, exit popup | fa, en |
beauty |
"Rose Skin Clinic", a skin and beauty clinic: Home (prices), Treatments (facials, laser, skincare plans, acne), Specialists, Skin journal (4 posts), booking form with hours, map and questions; centered logo header, centered footer, exit popup | fa, en |
vet |
"Hamdam Vet Clinic", for dogs and cats: Home (prices, owner reviews with their pets), Services (check-ups, vaccines, dental care, surgery), Vets, Pet care (4 posts), Appointments form; classic header, gradient footer, exit popup | fa, en |
psychology |
"Aram Counseling", a counseling center: Home (session prices), Services (individual, couples, children, online), Psychologists, Articles (4 posts), booking form with hours, map and questions; floating header, logo and menu footer | fa, en |
Each package is about 1 MB; previews are 960 × 720 WebP pictures, one per language.
The shop demos share tools/demos/lib/Shop.php (catalog: Toman in Persian, dollars in English; Color and Size attributes with swatch colors; one picture per color) and lib/ShopSite.php (pages, templates, menus, filters in the shop sidebar, Theme Options). Each shop is a niche description in tools/demos/shop-{niche}/demo.php. Their category and product lists are limited to the demo's own categories, so a site's existing products do not mix in. A demo sets the store currency, which comes back when the demo is removed.
The packages in bluetheme/demos/ are generated, not exported from a hand-made site. A script creates each demo on an empty WordPress through WordPress's and WooCommerce's own functions, and the theme's exporter writes the package. That way every rebuild is identical, and the content goes through the same import path as any exported site.
tools/demo-site/: a separate wp-env with an empty Persian site on :8890 and an English site on :8891. It runs PHP 8.3 so Contact Form 7 works. Start it withcd tools/demo-site && npx wp-env start. If wp-env says it is offline and cannot find the WordPress version, copylatestWordPressVersionfrom~/.wp-env/wp-env-cache.jsoninto the new folder'swp-env-cache.json. If the site says it is not installed, runwp core installonce.tools/demos/{id}/:art.mjs: the demo's pictures, drawn as SVG withart/kit.mjsandart/objects.mjs. Logos use the bundled fonts; no other picture contains words.demo.phpand the files it loads: options, templates, menus, pages and posts, with every text in both languages ($site->t( 'English', 'فارسی' )).manifest.json: title, description, categories and plugins.
tools/demos/lib/:Sitecreates pages, posts, terms, templates, menus, pictures and forms;Blocksholds the shared sections;Clinicholds what the clinic demos share (the appointment form, opening hours, the booking band, doctor cards and the contact section with a map), andClinicSitebuilds a whole clinic demo (pages, posts, header, footer, popup, menus and options) from a niche description, the wayShopSitedoes for shops: the dental, beauty, veterinary and psychology demos are such descriptions.art/clinic-art.mjsdraws the pictures every clinic needs (team, patients, blog, title band, popup, logo) andart/clinics.mjsthe niche objects (tooth, chair, creams, dog, cat, sofa…).art/medical.mjsdraws the medical objects (cross, heartbeat line, building, bed, clipboard, stethoscope, exercise ball, dumbbell, treatment table).- Build:
node tools/demos/build.mjs corporate(or--lang=fafor one language). It:- renders the pictures to WebP in
tools/demos/.out/(git-ignored); - empties each build site and builds the language;
- exports it into
bluetheme/demos/{id}; - takes
preview-{lang}.webp, a screenshot of that language's front page, and adds the manifest texts and the previews.
- renders the pictures to WebP in
Private demos
A demo made for one client (their brand, staff photos, insurer logos) must not ship inside the theme. Put it in tools/demos/private/{id}/, which git ignores, with the same files as a bundled demo. It can also have:
photos/andphotos.mjs: the client's own photos.photos.mjsexports a function that writes them as WebP into the pictures folder before the build, so$site->image( 'name' )finds them next to the drawings.
node tools/demos/build.mjs {id} builds it the same way, then writes a plugin instead of a theme folder:
tools/demos/private/{id}/dist/
├── bluetheme-demo-{id}/
│ ├── bluetheme-demo-{id}.php (from tools/demos/lib/demo-plugin.php)
│ └── package/ (the demo package and its previews)
└── bluetheme-demo-{id}.zip
Install the zip on the client's site under Plugins → Add New → Upload. The plugin adds the demo to the setup wizard through the bluetheme_demo_catalog filter, as a local package, so it imports and removes like a bundled demo. Deactivate or delete the plugin after the import; the imported content stays.
Each build uploads into a new folder, so picture file names are the same in both languages and the package holds each picture once.
Two languages with Polylang or WPML
BlueThemeCompatMultilingual puts Polylang and WPML behind one interface; languages are named by the theme's codes (fa, en) and matched to the plugin's own by locale. It also makes the theme's post types and taxonomies translatable in Polylang.
With lang: both, the importer runs the content steps (attributes, media, terms, posts, links, menus) once per language, each with its own id map, the plugin's default language first. Items get their language as they are created; a term whose slug the other language already has gets the language in its slug, and so does a post WordPress would have made "-2" (services-fa). Pictures with the same file name are imported once and shared. Translations::link() then pairs the two halves (posts by type and slug, templates by type and order, terms by taxonomy and slug, menus by location) and links them. Widgets, Theme Options, the front page and shop settings come from the main language. Each language's menus go to its own locations (Polylang's nav_menus; WPML follows the menu translations), and removal puts Polylang's locations back too.
Theme Options listed in OptionsTranslatable (visible texts, HTML texts and the logos; not slugs, SMS, SEO, map or white-label settings, nor code) are registered for string translation (group "BlueTheme", name bluetheme_{option}; pictures by their address) and translated on the front end inside Options::all(), which caches by language. The import saves the other language's values as their translations (Translations::options()). Template choice follows the language too: Resolver keeps only the current language's templates, which WPML needs as it does not filter the template query. wpml-config.xml declares the theme's post types, taxonomies and meta for WPML.
WPML is driven through its documented hooks only and has not been tested; Polylang 3.9 is (tools/demo-site has it).
Routes and commands
REST (bluetheme/v1, manage_options):
| Route | Does |
|---|---|
GET demo/catalog |
The demos, the imported demo, and whether an import is running |
POST demo/import |
Starts an import: { demo, lang, parts }; lang is fa, en, or both with Polylang/WPML |
POST demo/language |
Adds Persian or English to Polylang: { lang }; returns the catalog's multilingual state |
POST demo/step |
Runs the next step and returns { done, progress, message } |
POST demo/cancel |
Stops a running import |
POST demo/remove |
Removes the imported demo |
POST demo/keep |
Keeps the imported demo as the site's own content: drops its demo tag, record and settings backup, so another demo can be imported |
GET demo/plugins |
The suggested plugins (and a demo's own, with ?demo=) and their state (activate_plugins) |
POST demo/plugins |
Installs from WordPress.org when missing, then activates: { slug } (activate_plugins; installing also needs install_plugins) |
WP-CLI (run as an administrator, for example --user=admin):
wp bluetheme demo list
wp bluetheme demo import corporate --lang=fa --parts=content,templates,menus,widgets,options
wp bluetheme demo import corporate --lang=both
wp bluetheme demo remove
wp bluetheme demo keep
wp bluetheme demo export ./corporate --id=corporate --title="Corporate" --lang=fa --zip=corporate.zip
Exporting the Persian site and then the English one into the same folder gives a package with both languages.
Hooks:
bluetheme_demo_imported(demo id, state) andbluetheme_demo_removed(demo id, counts) fire when a demo is imported or removed.