Plugin Development API

Plugins in ShopsWired are built using JavaScript and run server-side within our embedded JS scripting engine. This allows you to safely execute code, modify data on the fly via hooks, and build entirely new data models using Custom Records.

1. The manifest.json

Every plugin requires a manifest.json file to define its metadata, scripts, settings, and custom database schemas.

Example: A Blog Plugin

{
  "id": "blog",
  "name": "Blog Plugin",
  "version": "1.0.0",
  "scripts": [
    { "path": "api.js", "type": "route", "method": "GET", "route_path": "/blog" },
    { "path": "hooks.js" }
  ],
  "settings": [
    {
      "key": "inject_footer",
      "type": "checkbox",
      "label": "Inject Footer Menu Link",
      "default": true
    }
  ],
  "custom_records": [
    {
      "id": "blog",
      "name": "Blog Posts",
      "icon": "📝",
      "fields": [
        { "name": "title", "type": "string", "list": true },
        { "name": "slug", "type": "string", "list": true },
        { "name": "content", "type": "richtext" },
        { "name": "published_at", "type": "datetime", "group": "Publishing" }
      ]
    }
  ]
}

The custom_records array is incredibly powerful. It allows your plugin to dynamically generate complete CRUD interfaces within the admin dashboard without writing a single line of backend Go or frontend Vue code.

2. Event Hooks

Hooks allow you to intercept and modify data during core lifecycle events. To use hooks, export functions matching the hook name in your designated hook script (e.g., hooks.js).

Example: Auto-tagging Products

module.exports = {
    "product.before_save": function (event) {
        // Auto-generate SKU if missing
        if (!event.data.sku) {
            event.data.sku = "AUTO-" + Date.now();
        }

        // Ensure tags always include "demo"
        if (event.data.tags && Array.isArray(event.data.tags)) {
            if (event.data.tags.indexOf("demo") === -1) {
                event.data.tags.push("demo");
            }
        }
    }
};

Example: Custom Tax Calculation

You can completely override core functionality, such as replacing the built-in tax calculator with an external API or custom logic.

module.exports = {
    "tax.calculate": function (event) {
        // event.data contains: cart, subtotal, shipping, address
        if (event.data.address.state === "TX" && event.data.address.country === "US") {
            var taxable = event.data.subtotal + event.data.shipping;
            event.data.tax = Math.round(taxable * 0.0825);
            event.data.name = "TX Sales Tax";
        }
    }
};

3. The Global API Bridges

The global sw object and standard JS functions like fetch() provide access to the platform's core infrastructure.

Store Bridge (CRUD)

Access and manipulate core entities like products, customers, orders, and your records.

// Update a product
var product = sw.products.save({ id: 123, price: 2499 });

// Access custom records defined in your manifest
var blogPost = sw.records.blog.save({
    title: "Hello World",
    slug: "hello-world",
    content: "..."
});

// List records (with filters and sorting)
var posts = sw.records.blog.list({ limit: 5 }).items;

HTTP & Network (Fetch)

Use the standard fetch() API to interact with external services synchronously.

var response = fetch("https://httpbin.org/post", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ source: "plugin" })
});
console.log(response.status);

Files & Cache Bridges

Upload files directly to the shop's CDN or leverage the in-memory cache.

// Upload an image from an external URL directly to the shop's storage
var file = sw.files.upload("images/sample.jpg", fetch("https://picsum.photos/200/200"));
console.log(file.path);

// Use the distributed cache (set for 60 seconds)
sw.cache.set("api_rate_limit", 42, 60);
var counter = sw.cache.get("api_rate_limit");