WordPress
A JOPS SPA can run on a WordPress site in two ways: inside a page, next to the theme and the page’s other content (built with Elementor or the block editor), or as a separate app in its own folder. WordPress stays the CMS and the backend; the JOPS app’s files are served as static files.
| Embedded in a page | Independent SPA | |
|---|---|---|
| What it is | The app renders inside a WordPress page, below the theme’s header and next to the page’s content | A complete JOPS app with its own index.html, opened at https://<site>/<folder>/ |
| How | The JOPS plugin and the [jops] shortcode, or an Elementor HTML widget |
Upload the app folder; no plugin |
| Shares the theme, menu and page | Yes | No (separate document) |
Quick start
Section titled “Quick start”-
Install and activate the JOPS plugin (see Install the JOPS plugin).
-
Put your app folder in the WordPress root, for example
<root>/jops-spa/, with root-based paths (see The app folder). -
Add a Shortcode widget to the page in Elementor (General → Shortcode) and enter:
[jops app="/jops-spa/src/App.js" id="jops-spa-root" class="jops-spa"] -
Publish the page and open it. The app runs on the published page.
Install the JOPS plugin
Section titled “Install the JOPS plugin”The plugin registers the [jops] shortcode and includes the jops-core library, so the page needs nothing else. Pick the option that matches your site. All three install the same plugin into wp-content/plugins/jops/.
For hosting with wp-admin only, XAMPP, MAMP, LocalWP and similar setups.
- Download
jops-wordpress.zip. - In wp-admin, go to Plugins → Add New Plugin → Upload Plugin.
- Choose the zip and click Install Now.
- Click Activate.
Updating: upload the newer zip the same way and choose Replace current with uploaded.
For npm projects whose WordPress is run by wp-env (a folder with .wp-env.json).
-
Install JOPS and the plugin in that folder:
Terminal window npm i jops-corenpx jops-core init --wpinit --wpwrites the plugin toplugins/jops/and adds"./plugins/jops"to.wp-env.json. -
Start WordPress.
wp-envinstalls and activates the plugin:Terminal window npx wp-env start
Updating: run npm i jops-core@latest, then npx jops-core update, which refreshes the plugin and its library.
For npm projects inside a WordPress folder: a theme, a plugin, a Bedrock project, and so on.
-
Install JOPS and the plugin:
Terminal window npm i jops-corenpx jops-core init --wpinit --wpsearches this folder and the folders above it for WordPress (wp-load.php+wp-content/, or Bedrock’sweb/wp/+web/app/) and writes the plugin to its plugins folder. To point to WordPress yourself, use--wp-root:Terminal window npx jops-core init --wp-root /path/to/wordpress -
Activate the plugin in wp-admin, or with WP-CLI:
Terminal window wp plugin activate jops
Updating: run npm i jops-core@latest, then npx jops-core update, which refreshes the plugin and its library.
The app folder
Section titled “The app folder”-
Place the app folder in the WordPress root (the folder with
wp-config.php), for example<root>/jops-spa/. How it gets there is up to you: upload it, copy it with FTP or SSH, deploy it with your pipeline, or map it with a"mappings"entry in.wp-env.json. A real folder is served as static files: WordPress only handles URLs that are not existing files or folders. -
Use root-based paths everywhere JOPS loads something:
app,src=,layout=,loadLayout(), stylesheets and images. For example/jops-spa/src/App.jsandloadLayout("/jops-spa/src/layout/main.html"). ES moduleimportstatements inside your.jsfiles resolve relative to the file as usual. -
Pick a folder name that is not a page slug and doesn’t start with
wp-. The folder always wins over a WordPress page with the same address.
- wp-config.php
Directorywp-admin/
- …
Directorywp-content/
Directoryplugins/
Directoryjops/ the JOPS plugin
- …
Directorywp-includes/
- …
Directoryjops-spa/ your app
Directorysrc/
- App.js
Directorylayout/
- main.html
Directorycss/
- styles.css
Directoryassets/
- …
import { View } from "jops-core";
export default class App { constructor() { const root = new View(); root.loadLayout("/jops-spa/src/layout/main.html").then(() => root.render()); }}WordPress in a subfolder
Section titled “WordPress in a subfolder”If WordPress runs in a subfolder of the domain (for example example.com/blog/), the WordPress root is served at /blog/. Include that prefix in every root-based path: /blog/jops-spa/src/App.js, /blog/jops-spa/src/layout/main.html, and so on.
Add the app to a page
Section titled “Add the app to a page”Edit the page with Elementor, add a Shortcode widget (General → Shortcode) and enter:
[jops app="/jops-spa/src/App.js" id="jops-spa-root" class="jops-spa"]In the block editor, use the Shortcode block. Any editor that runs shortcodes works the same way.
| Attribute | Description |
|---|---|
app |
Required. Root-based path of the app’s entry module (its default export is created) |
id |
id of the app’s root <div> (default jops-root) |
class |
class of the root <div>; use it to scope the app’s CSS |
lib |
URL of jops-core.min.js (default: the plugin’s copy). Use one URL per page, or the browser loads two jops-core instances |
The shortcode outputs the import map, the library in hosted mode, the root <div> and a small script that calls mount() once the library has loaded.
An Elementor HTML widget can hold the same code by hand. Vendor the library in the app folder (/jops-spa/lib/jops-core.min.js, copied from node_modules/jops-core/lib/ or the release files):
<script type="importmap"> { "imports": { "jops-core": "/jops-spa/lib/jops-core.min.js" } }</script><script type="module" src="/jops-spa/lib/jops-core.min.js" data-host></script>
<div id="jops-spa-root" class="jops-spa" style="position: relative"></div><script type="module"> const start = () => window.JOPS.mount({ app: "/jops-spa/src/App.js", root: document.getElementById("jops-spa-root") }); window.JOPS ? start() : window.addEventListener("jops:ready", start, { once: true });</script>Elementor’s HTML widget outputs this code as it is; it does not run shortcodes, which is why the plugin uses the Shortcode widget.
- The app runs on the published page. In the Elementor editor, the shortcode shows a placeholder (“JOPS app … runs on the published page”) and loads no scripts.
- One JOPS app per page. A second
[jops]on the same page logs a warning and is not started. - Don’t combine the shortcode and a hand-written import map on the same page.
Conventions
Section titled “Conventions”- CSS.
setStyle()has no effect on an embedded app: the page owns the CSS. Link the app’s stylesheet from its layout (<link rel="stylesheet" href="/jops-spa/src/css/styles.css">) or add it to the theme. It applies to the whole page, so scope every rule under the root class (.jops-spa .card { … }) and avoid global rules (body,*,:root, bare tag selectors). - Routing. WordPress owns the path (
/my-page/), JOPS owns the hash (/my-page/#details?id=7). Leaving the page is a full page load, which ends the app. - Backend. Call the WordPress REST API with
fetch("/wp-json/…"). Requests are same-origin, so the login cookie is sent. Requests that need the logged-in user, and every write, also need a REST nonce (X-WP-Nonceheader, created withwp_create_nonce("wp_rest")), which your own theme or plugin code must provide to the app. - Caching. The app’s files are static, and caching plugins, CDNs and browsers may keep serving old copies after an update. Use versioned paths or short cache headers for the app folder, and hard-reload while developing.
Independent SPA
Section titled “Independent SPA”A complete JOPS app with its own index.html (as created by npx jops-core init) also runs from a folder in the WordPress root. Upload it as <root>/jops-spa/ and open https://<site>/jops-spa/. WordPress is not involved, and no plugin is needed.
- Library from the CDN: the app’s
./paths work as they are. - Vendored library: use root-based paths, including in the import map and the script tag:
<script type="importmap"> { "imports": { "jops-core": "/jops-spa/lib/jops-core.min.js" } }</script><script type="module" src="/jops-spa/lib/jops-core.min.js" data-app="/jops-spa/src/App.js"></script>