Skip to content

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)
  1. Install and activate the JOPS plugin (see Install the JOPS plugin).

  2. Put your app folder in the WordPress root, for example <root>/jops-spa/, with root-based paths (see The app folder).

  3. 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"]
  4. Publish the page and open it. The app runs on the published page.

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.

  1. Download jops-wordpress.zip.
  2. In wp-admin, go to Plugins → Add New Plugin → Upload Plugin.
  3. Choose the zip and click Install Now.
  4. Click Activate.

Updating: upload the newer zip the same way and choose Replace current with uploaded.

  1. 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.

  2. Use root-based paths everywhere JOPS loads something: app, src=, layout=, loadLayout(), stylesheets and images. For example /jops-spa/src/App.js and loadLayout("/jops-spa/src/layout/main.html"). ES module import statements inside your .js files resolve relative to the file as usual.

  3. 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/
      • …
jops-spa/src/App.js
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());
}
}

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.

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.

  • 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.
  • 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-Nonce header, created with wp_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.

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:
jops-spa/index.html
<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>