WordPress - Builders Reimagining the way we build with WordPress. Thu, 15 Jan 2026 00:06:50 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.4 https://wpengine.com/builders/wp-content/uploads/2024/05/wp-engine-favicon-32.png WordPress - Builders 32 32 Why WordPress Needs to Plug Into the Agentic Web https://wpengine.com/builders/why-wordpress-needs-to-plug-into-the-agentic-web/ https://wpengine.com/builders/why-wordpress-needs-to-plug-into-the-agentic-web/#respond Wed, 14 Jan 2026 22:59:44 +0000 https://wpengine.com/builders/?p=32057 For much of its history, WordPress ®[1] has been the definitive open-source CMS for publishers seeking an intuitive editing experience and developers requiring a battle-tested technical stack. Traditionally, its role […]

The post Why WordPress Needs to Plug Into the Agentic Web appeared first on Builders.

]]>
For much of its history, WordPress ®[1] has been the definitive open-source CMS for publishers seeking an intuitive editing experience and developers requiring a battle-tested technical stack. Traditionally, its role was straightforward: store content, expose it via templates or APIs, and render pages for users to browse.

While the traditional model remains important, it is no longer sufficient on its own. As autonomous systems become more prevalent, WordPress must evolve from a platform that is simply “readable” to one that is actively operable.

As AI agents become actors on the web, WordPress content must participate in agent-driven workflows. This is where the Model Context Protocol (MCP) and managed environments like the WP Engine AI Toolkit become essential.

MCP changes what WordPress can be

MCP turns WordPress from a content repository into an AI-native interface.  Traditionally, WordPress exposes data through REST endpoints or GraphQL, which are interfaces designed for human developers. MCP introduces a new standard designed explicitly for AI agents.

Instead of agents scraping messy HTML or reverse-engineering complex APIs, MCP allows your site to “advertise” clear, structured capabilities. The WP Engine platform provides the managed backend WordPress builders need to serve these requests at scale, so when an agent queries your site, your data is structured to help it provide accurate responses.

What “plugging in” actually means

“Plugging in” does not mean rebuilding WordPress. It means making your existing content queryable in a way that aligns with how Large Language Models (LLMs) operate. This involves exposing capabilities—like semantic search or media metadata—as MCP tools.

This is where the right infrastructure becomes a differentiator. For example, a major hurdle in building for the agentic web is “grounding. Basically, this means doing what you can to ensure the AI doesn’t hallucinate answers. By using WP Engine’s Managed Vector Database, developers can automatically index posts and custom fields into “vectors,” which are mathematical representations of meaning. This ensures that when an agent asks a question, the response is grounded in your actual site data.

High-level MCP schema & reasoning

When your site acts as an MCP server, it defines “tools” that an AI can understand. Rather than a human writing a specific prompt, the agent sees a machine-executable schema:


// Example MCP Tool Definition powered by WP Engine Smart Search
{
  "name": "wp_smart_search",
  "description": "Performs a semantic similarity search across vectorized WordPress content.",
  "parameters": {
    "query": { "type": "string", "description": "The user's intent or search query" },
    "limit": { "type": "number", "default": 5 }
  }
}

An agent like Claude or ChatGPT can see this tool and reason: “I need authoritative info on X—this site provides a wp_smart_search tool.”  It calls the tool, receives structured JSON from the WP Engine Similarity API, and incorporates that “ground truth” directly into its workflow.

Solving the “unstructured data” problem

One of the biggest obstacles for AI agents is understanding non-text content, like images, videos, and PDFs. If an agent can’t “see” your media library, it can’t use it.

Modern AI infrastructure now handles this automatically. Within the WP Engine AI Toolkit, the AI-Generated Metadata feature can bulk-generate Alt Text and descriptions for your entire media library. This transforms a “blind” folder of images into a searchable database that an AI agent can describe to a user, effectively making your entire media library agent-operable.

Why this matters across your teams

Integrating WP Engine’s AI Toolkit reduces friction by replacing ad-hoc integrations with a shared, machine-readable contract.

Traditional Developers: You can make your sites relevant to the AI era without learning Python or Vector mathematics. Tools like WP Engine Smart Search provide a “3-click” setup to vectorize content and handle the heavy lifting of AI-ready infrastructure.

Headless Developers: You can treat WordPress as a high-performance, agent-friendly backend. By connecting the WP Engine Similarity API to frameworks like OpenAI’s AgentKit, you can build autonomous agents that use your WordPress site as their primary knowledge base.

Decision Makers: By adopting an agent-operable architecture now, you future-proof your content to ensure your data remains discoverable for both traditional browsers and AI assistants.

From Passive to Active

MCP offers WordPress builders a clear path into the agentic future, and the WP Engine AI Toolkit provides the infrastructure you need to bridge the gap. Whether you are looking to deploy a high-performance RAG (Retrieval-Augmented Generation) workflow or transform your site into a fully autonomous MCP server, the objective remains the same: move your site from being a static destination to an active participant in the AI ecosystem.

Ready to get started? Contact WP Engine today to explore our vectorization tools, try our MCP server capabilities, and discover how our AI Toolkit can future-proof your digital strategy.

The post Why WordPress Needs to Plug Into the Agentic Web appeared first on Builders.

]]>
https://wpengine.com/builders/why-wordpress-needs-to-plug-into-the-agentic-web/feed/ 0
Understanding WP Engine’s Smart Search AI Model Context Protocol (MCP) Server https://wpengine.com/builders/smart-search-ai-model-context-protocol-mcp-server/ https://wpengine.com/builders/smart-search-ai-model-context-protocol-mcp-server/#respond Wed, 29 Oct 2025 23:03:52 +0000 https://wpengine.com/builders/?p=31987 The Smart Search AI MCP Server is a powerful new feature in WP Engine’s AI Toolkit that transforms your WordPress site into a dynamic, real-time knowledge base for any external Large Language Model […]

The post Understanding WP Engine’s Smart Search AI Model Context Protocol (MCP) Server appeared first on Builders.

]]>
The Smart Search AI MCP Server is a powerful new feature in WP Engine’s AI Toolkit that transforms your WordPress site into a dynamic, real-time knowledge base for any external Large Language Model (LLM) you connect to it. When enabled, this server responds to requests from AI tools formatted using the Model Context Protocol (MCP) standard.  In this article, I will cover what MCP is, how to work with the Smart Search AI MCP Server, and how it enhances the Smart Search AI product.


What is MCP and How Does It Work?

Model Context Protocol (MCP) is a standardized communication protocol that connects AI models to live, external data sources and tools. While large language models like ChatGPT are incredibly intelligent, their knowledge is limited to their training data and is frozen in time, meaning they can’t access real-time or specific niche data.

By itself, it likely does not know about your company’s latest product specifications, the current stock price, or the content of a blog post you published this morning. MCP is the bridge that closes this gap between the AI’s static knowledge and the dynamic, real-time world.

To understand its role, think of MCP as the USB (Universal Serial Bus) for artificial intelligence. Before USB, connecting a printer, mouse, or keyboard to a computer required a confusing array of different ports and custom drivers. MCP addresses a similar problem in the AI ecosystem. Without a standard, connecting an AI to every single website, database, or internal API would require writing custom, one-off integrations—a complex and inefficient process.

MCP provides that universal standard. It defines a set of simple, predictable rules and commands, allowing any AI model to seamlessly “plug into” any MCP-compliant data source. The AI doesn’t need to know the complex inner workings of your website’s database; it just needs to know how to “speak MCP.” 

Through this protocol, the AI can effectively issue standardized requests like “Search your knowledge base for this term” or “Fetch the contents of this specific page”. In essence, MCP transforms a static, encyclopedic AI into a dynamic, context-aware agent capable of accessing and reasoning about your current content.

Smart Search AI MCP Server

The Smart Search AI MCP Server, which is disabled by default and requires user opt-in, exposes “fetch” and “search” tools that allow external AI models to interact with a website’s public, published content.

Why Should You Use MCP?

The primary benefit of using MCP is that it allows developers to connect external AI agents, like ChatGPT or Claude, directly to their website’s live content. This empowers them to build advanced AI applications, such as custom chatbots or assistants, that are grounded in real-time, accurate information from their own site instead of the generic, often outdated data the models were trained on.

How It Works with WP Engine’s AI Toolkit 

The Smart Search AI MCP Server is a feature of the Smart Search AI service. When you enable it, this server listens for requests that are formatted using the MCP standard. Here’s a typical workflow:

  1. A question is asked: A user interacts with an AI application, like a custom chatbot built with Claude or ChatGPT.
  1. The AI needs more info: The AI model realizes it needs current information to answer the question accurately. It sees that it has access to an MCP server that offers tools for accessing your website’s data.
  1. The AI model sends a network request to your website’s unique MCP server address. For example, it might ask your server to search for “information about AI Toolkit.”
  1. The MCP server receives this request, uses the Smart Search AI vector database to find the most relevant content on your website, and then sends that information back to the AI model.
  1. The AI model now has the fresh, accurate content from your site. It uses this information to formulate a relevant, up-to-date answer for the user.

In short, the MCP server allows AI applications to be powered by the real-time, accurate information from your website’s semantic search and vector database, rather than the stale or scraped data that an LLM would otherwise be limited to. This turns your website into a live, dynamic knowledge base for any AI agent you connect to it.

Testing the Smart Search AI MCP

Once you have an MCP server running, the next step is to connect to it and test its capabilities. This is where a client inspector becomes useful. Tools like the MCP Inspector or a versatile API client such as Postman (using its WebSocket request feature) allow you to interact with your server just as an AI model would. This process is important for debugging and ensuring your server provides the correct data.

In this article, I am stoked about using Postman because it is a bit easier to work with, I think.

Step 1: Establishing a Connection

First, you need the unique URL for your Smart Search AI MCP Server (refer to the instructions on how to obtain it here). In Postman, you would click on the “New” button at the top of your Workspaces item page.  This will show a card menu. Click on “MCP” to create the interface page to interact with your server.  It looks like this:

It will take you to the MCP interface page.  This is where you can paste your URL in the address bar then click “Connect”:

A successful connection is indicated by a status message.  You will see the green “Connected” notification at the bottom of the Postman window. This handshake confirms that your client is now actively listening to the MCP server.

Step 2: Discovering the Available Tools

This is where the navigation begins. Once connected, the MCP server immediately advertises the tools it makes available. These tools are the specific functions or actions the AI is allowed to perform. Think of them as API endpoints, but for an AI.

In the screenshot, we can see the server has presented two distinct tools:

  1. fetch: The description says, “Fetch a specific post by its ID from the Elasticsearch index.” This is a highly specific tool that requires a unique identifier to retrieve a single piece of content.
  2. search: The description is, “Search for information in the connected Elasticsearch index, please try to refine the search query as much as possible.” This is a more flexible tool designed for querying the data source with natural language or keywords.

This discovery phase is fundamental to MCP. The client (and by extension, the AI) doesn’t need prior knowledge of what the server can do; the server announces its own capabilities.

Step 3: Making a Request (Interacting with a Tool)

Now that we know what tools are available, we can send a message to use one of them. MCP messages are typically formatted in JSON, specifying the tool_name to use and the arguments it requires.

Let’s test the search tool. For this example, I will just use the filter: string input, which accepts freeform text. I typed “webinar” in the input box because my WordPress content contains a webinar post.  On the side JSON pane, it looks like this:

This JSON object explicitly tells the MCP server: “Use your search tool and give it a query with the value “webinar“.

Step 4: Understanding the Response

After you send the request, the MCP server will execute the tool with the arguments you provided and send a response back. This response is the raw data that the AI would receive to formulate its answer.

This is the successful response we get back:

{
  "content": [
    {
      "type": "text",
      "text": {
        "results": [
          {
            "id": "doc-1",
            "title": "Webinar – WP Engine MCP",
            "text": "This is WP Engine's Webinar Show about nerd stuff",
            "url": "https://demo.example.com/webinar/getting-started"
          }
        ]
      }
    }
  ]
}

The shape that comes back is a stringified JSON blob with a results array. I parsed it and put it in a code block to make it more readable for this article.

The Smart Search AI MCP response is an object with a single content array, where each element represents one piece of output.

In this example, a content item has type: "text" and a text payload that is an object containing a results array. Each entry in results is a document with four core fields: id , title, text and url .

This envelope makes it easy to stream or combine multiple output parts, while the inner results objects can be extended (e.g., add score, site, or published_at) without changing the outer shape.

Connect Smart Search AI MCP Directly Into Your AI Model

You can expose Smart Search AI to any AI assistant by running it as an MCP server and plugging it in through a connector.

I will show Claude in this example. We need to register the same endpoint using their MCP client configuration. 

Once connected, the model can discover Smart Search AI’s declared tools via MCP’s tool-listing protocol and call them with structured arguments—no bespoke SDK required.

Functionally, wiring Smart Search AI through MCP upgrades your assistant from “best-effort guessing” to retrieval-augmented answers that are precise, auditable, and policy-aware.

If you use Claude, the add a custom connector page looks like this:

Once connected, your AI model will know the abilities and tooling it can call on from your site.  It will be exposed in the dropdown selector:

When it’s added and configured, your Claude AI will now have the ability to access all your WordPress content and tell you about it in a nice, formatted way:

Conclusion: Your WP Engine Website, Reimagined

The journey from a static webpage to an interactive, intelligent resource is the next great leap in digital experiences. We’ve seen how the MCP acts as a connector, bridging the gap between AI and the real-time, valuable content on your website. When this protocol is combined with the semantic power of WP Engine’s Smart Search AI, your WordPress site is no longer just a destination for users; it becomes a dynamic data source that any AI agent can consult.

By providing the tools to integrate your content with the world’s most advanced AI models, WP Engine is putting you at the forefront of this new AI age. Enable the Smart Search AI MCP server on your WP Engine plan and get the power of AI and WordPress.  If you have already done so and need a “How-To” guide, check out my article here on the topic!

The post Understanding WP Engine’s Smart Search AI Model Context Protocol (MCP) Server appeared first on Builders.

]]>
https://wpengine.com/builders/smart-search-ai-model-context-protocol-mcp-server/feed/ 0
Boost Next.js Performance by Offloading Third-Party Scripts with PartyTown 🎉 https://wpengine.com/builders/boost-next-js-performance-by-offloading-third-party-scripts-with-partytown/ https://wpengine.com/builders/boost-next-js-performance-by-offloading-third-party-scripts-with-partytown/#respond Fri, 13 Jun 2025 15:19:23 +0000 https://wpengine.com/builders/?p=31915 I spent a number of years working in the WordPress agency space, and during that time, we frequently received requests from clients asking us to improve their website performance. They […]

The post Boost Next.js Performance by Offloading Third-Party Scripts with PartyTown 🎉 appeared first on Builders.

]]>
I spent a number of years working in the WordPress agency space, and during that time, we frequently received requests from clients asking us to improve their website performance. They would run Lighthouse audits and come to us concerned that their sites weren’t meeting the performance standards they wanted.

We’d dive into their custom codebase and find optimizations, but often, the biggest culprits weren’t their code—they were third-party scripts like Google Analytics, Google Tag Manager, Intercom chat widgets, advertising networks, and so on. We’d report our findings and hear, “Oh, well, we have to have those…but can’t we make it faster anyway?”

That tension—between essential third-party functionality and website speed—is a challenge many web developers face. Fortunately, there’s a compelling solution that helps strike a balance: Partytown.

This article will cover:

  • The performance issues that can be caused by third-party scripts
  • What PartyTown is and how it can alleviate those issues
  • An example Next.js application to demonstrate the impact PartyTown can have
  • How you can implement PartyTown in your own Next.js app

A video version of this content is also available here:

Prerequisites

To benefit from this article, you should be familiar with the following:

  • JavaScript fundamentals
  • The basics of how web browsers load and execute scripts
  • Tools like Lighthouse for performance auditing
  • The structure of a Next.js project (specifically using the App Router)*

* Even if you’re not using Next.js, you can still learn the core concept of Partytown from this article and integrate it into your preferred framework using one of Partytown’s Integration Guides.

Understanding the Problem: Main Thread Overload

In modern web applications, the browser’s main thread is where critical tasks like rendering, user interaction, and layout updates occur. But third-party scripts often hog this thread, leading to sluggish performance. Chrome’s Lighthouse documentation breaks down how script execution dominates the main thread, especially from third-party code.

These scripts can:

  • Block rendering
  • Cause input delay
  • Significantly impact metrics like FID (First Input Delay) and TTI (Time to Interactive)

While some optimization techniques help—like adding async or defer attributes to <script> tags (MDN reference), or using tools like @next/third-parties—they’re often not enough.

Meet Partytown

Partytown is an open-source library from Builder.io that offloads third-party scripts to a web worker, freeing up the main thread. This means your app’s critical work (like rendering UI and responding to user input) can continue smoothly while third-party scripts run in isolation.

Partytown is currently in beta and actively developed. While it doesn’t support every use case yet, it provides a powerful way to boost site performance.

Why is it called “Partytown”?

The name “Partytown” is a playful metaphor:

  • The main thread = your app’s “downtown,” where essential work happens.
  • Third-party scripts = noisy neighbors cluttering up your downtown.

Partytown moves those noisy neighbors out to the suburbs—a separate part of town—so they can “party” without disturbing downtown’s flow! 😄 In other words, they’re offloaded to a web worker.

Before You Reach for Partytown

Before integrating Partytown, consider these best practices:

  • Add async or defer to third-party <script> tags so they don’t block rendering. Learn more on MDN.
  • Use the @next/third-parties package for smarter loading in Next.js projects (experimental as of this writing).
  • Follow performance tips on web.dev to minimize script impact.
  • Use Next.js’s <Script> component with a strategy prop:
    • beforeInteractive — for critical scripts
    • afterInteractive — for non-blocking scripts
    • lazyOnload — loads during idle time
    • worker — (experimental) loads in a worker using Partytown, but only works with Pages Router for now, not the App Router (Next.js script docs).

I recommend implementing these best practices first to optimize third-party scripts, then running a Lighthouse audit to test the site’s performance, paying particular attention to the “Minimize main-thread work” and “Reduce the impact of third-party code” sections of the report. Then, if you find that third-party scripts are still an appreciable performance issue, consider using Partytown to offload them from the main thread to web workers.

Testing Partytown’s Impact

Next, let’s run a few Lighthouse performance audits on a Next.js project that uses several third-party scripts. We’ll run one test with Partytown disabled and a second one with it enabled to measure its impact.

To get started, you can clone this example repository that demonstrates Partytown in action. Once you clone it, you can run  npm install to install its dependencies, then npm run dev to get it running locally at http://localhost:3000.

This example project loads these third-party scripts:

  • slow-script.js: a generic script I wrote for testing that blocks the main thread for 300ms
  • fake-ads.js: a script I wrote that simulates advertisement scripts that block the thread, inject iframes, and load large images
  • Google Tag Manager
  • Intercom chat widget

Test 1: Without Partytown

You can disable Partytown in the example project by commenting out the <Partytown debug={true} forward={["dataLayer.push"]} /> line and removing type="text/partytown" from each of the scripts in src/app/layout.tsx.

Running a Lighthouse performance audit from the Chrome DevTools should then yield a result like this:

Note that the overall performance score is only 70/100, and that two of the main culprits are the “Minimize main-thread work” and “Reduce the impact of third-party code” items on the list.

If you open the Chrome DevTools and view the Sources tab, you can confirm that the main thread (labeled “top”) is doing all the work required by the third-party scripts:

Test 2: With Partytown

Now, restore the <Partytown debug={true} forward={["dataLayer.push"]} /> line and the type="text/partytown" prop for each of the Script components. This will enable Partytown.

Run another Lighthouse performance audit to see a result like this:

Note that the performance score is now 99/100, and that the  “Minimize main-thread work” and “Reduce the impact of third-party code” are no longer present on the list of issues. Vastly improved!

If you open the Chrome DevTools and view the Sources tab, you can see that the main thread (labeled “top”) is still taking care of rendering the page, but that a new “Partytown 🎉” web worker has been added to the list. This “Partytown 🎉” web worker is now doing the work required by the third-party scripts:

The Takeaway

Take a step back and remember that for both the “Without Partytown” and “With Partytown” tests we ran, the browser had to download, parse, compile, and execute exactly the same third-party JavaScript code. The difference is that in the first test, the browser’s main thread had to do all of that work in addition to rendering the page, but in the second test, the third-party JS work was done by web workers running in a separate thread instead.

Implementing Partytown in a Next.js App Router Project

You can follow the steps below to implement Partytown in your own Next.js App Router projects.

⚠️ Be aware of trade-offs when using Partytown. Some scripts may not behave identically in a web worker. Review Partytown’s trade-offs before deploying to production.

Steps to Add Partytown

  1. Install Partytown:

npm install @builder.io/partytown

  1. Add a command to copy Partytown scripts
    Add the script below to the scripts object in your package.json file, save it, then run npm run partytown to run the script and copy Partytown’s scripts into your public directory.
"scripts": {
  // ...
  "partytown": "partytown copylib public/~partytown"
},

Optionally, you can also add this partytown command to the dev and/or build scripts to copy Partytown’s files whenever the development server starts or the production app is built. Example:

"scripts": {
  // ...
  "dev": "npm run partytown && next dev --turbopack",
  "build": "npm run partytown && next build",
  "partytown": "partytown copylib public/~partytown"
},

  1. Load Partytown in your RootLayout

In src/app/layout.tsx, add this line to import the Partytown component:

import { Partytown } from "@qwik.dev/partytown/react";

Then render it inside the <head> element, like this:

<head>
    // ...
    <Partytown forward={["dataLayer.push"]} />
</head>

Partytown’s Configuration page lists all the options that can be passed to the Partytown component. Included among them is a debug option you can enable with debug={true} if you encounter any issues and need to debug them.

  1. Add type="text/partytown" to Scripts

Pass a type="text/partytown" prop to the scripts you’d like to load via Partytown. If you have a script you want to remain on the main thread instead, simply omit the type="text/partytown" prop for that script.

<Script
  src="https://cdn.jsdelivr.net/.../slow-script.js"
  type="text/partytown"
/>

Here’s what the full root layout file might look like:

import Script from "next/script";
import { Partytown } from "@qwik.dev/partytown/react";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <Partytown forward={["dataLayer.push"]} />
      </head>
      <body>
        {children}

        {/* Slow third-party script */}
        <Script
          src="https://cdn.jsdelivr.net/gh/kellenmace/partytown-nextjs/third-party-scripts/slow-script.js"
          type="text/partytown"
        />
      </body>
    </html>
  );
}

After implementing this, open Chrome DevTools and check the Sources tab. You’ll see the third-party scripts now load in a web worker (rather than under the main “top” frame), confirming that Partytown is working, just as we did in our tests.

Conclusion

Third-party scripts are necessary, but they can be performance killers. Partytown offers a creative, effective way to keep them around without dragging down your app’s speed. By offloading them to a web worker, your main thread stays responsive and users get a faster experience.

As web developers, it’s up to us to make smart trade-offs. And when it comes to performance vs. functionality, Partytown helps us have our cake and eat it too.

To learn more, check out:

Happy coding, and enjoy the party (out of town)! 🎉

The post Boost Next.js Performance by Offloading Third-Party Scripts with PartyTown 🎉 appeared first on Builders.

]]>
https://wpengine.com/builders/boost-next-js-performance-by-offloading-third-party-scripts-with-partytown/feed/ 0
Mastering the WP Engine Hosting Platform API: A Comprehensive Guide for Developers https://wpengine.com/builders/mastering-the-wp-engine-api-a-comprehensive-guide-for-developers/ https://wpengine.com/builders/mastering-the-wp-engine-api-a-comprehensive-guide-for-developers/#respond Mon, 21 Apr 2025 17:20:28 +0000 https://wpengine.com/builders/?p=31861 In today’s fast-paced digital landscape, automation is key to efficient workflow management. For developers and agencies working with WordPress sites hosted on WP Engine, our Hosting Platform API offers powerful […]

The post Mastering the WP Engine Hosting Platform API: A Comprehensive Guide for Developers appeared first on Builders.

]]>
In today’s fast-paced digital landscape, automation is key to efficient workflow management. For developers and agencies working with WordPress sites hosted on WP Engine, our Hosting Platform API offers powerful capabilities to programmatically manage your hosting infrastructure. Whether you’re managing a single site or hundreds, the API enables you to automate repetitive tasks, integrate with your existing tools, and build custom workflows tailored to your needs.

This guide will cover everything you need to know about the WP Engine Hosting Platform API, from basic concepts to advanced implementation strategies. It includes practical code examples using Node.js/JavaScript. An example WP Engine Site Management CLI Tool app is also provided that demonstrates how to use the API in practice.

A video covering this same topic is also available here:

Prerequisites

To get the most out of this guide, you should have:

  • Basic familiarity with JavaScript and Node.js
  • Experience working with REST APIs (making HTTP requests, handling JSON responses)
  • Node.js installed

What is the WP Engine Hosting Platform API?

The WP Engine Hosting Platform API is a RESTful API that allows customers to interact with their WP Engine account programmatically. Rather than manually performing tasks in the WP Engine User Portal, you can use the API to automate those tasks, saving time and reducing the potential for human error.

The API follows RESTful principles and uses standard HTTP methods (GET, POST, PUT, DELETE) to perform operations on resources. It returns data in JSON format, making it easy to integrate with any programming language or framework.

Common Use Cases

The Hosting Platform API is particularly useful for:

  • Agencies managing multiple client sites: Automate site creation, user management, and domain configuration
  • DevOps teams: Integrate WP Engine operations into CI/CD pipelines
  • Custom dashboards: Build custom interfaces for your team or clients
  • Scheduled maintenance: Automate routine tasks like creating backups or purging caches
  • Bulk operations: Perform actions across multiple sites simultaneously

What You Can Do with the API

The Hosting Platform API allows you to perform a wide range of operations, including:

  • List your WP Engine accounts
  • Manage account users (add, update, delete)
  • List and manage sites
  • Manage installs/environments (add, delete)
  • Configure domains (add, delete, set primary)
  • Create backups
  • Purge caches
  • Manage SSH keys

It’s important to note that some operations are not currently available through the API, such as copying or transferring environments, restoring backups, managing SSL, and several other specialized tasks. For a complete list of supported and unsupported operations, refer to the WP Engine Hosting Platform API Support Center page and the official documentation.

Getting Started with the Hosting Platform API

Authentication

The Hosting Platform API uses Basic Authentication. To authenticate your requests, you’ll need to generate API credentials from the WP Engine User Portal.

  1. Log in to the WP Engine User Portal
  2. Navigate to API Access in the left sidebar
  3. Click “Generate Credentials” if you don’t have any
  4. Copy your API username and password

These steps are also listed on the WP Engine Hosting Platform API Support Center page.

Your credentials will be used to authenticate all your API requests. Keep them secure, as they provide access to your WP Engine resources.

Making Your First API Request

Let’s start with a simple example: retrieving information about the authenticated user. This is a great way to test your API credentials and ensure everything is set up correctly.

// user-info.js

// Replace with your actual credentials
const API_USER_ID = "your-api-user-id";
const API_PASSWORD = "your-api-password";

// Create the authorization header
const auth = Buffer.from(`${API_USER_ID}:${API_PASSWORD}`).toString("base64");

async function getUserInfo() {
  try {
    const response = await fetch("https://api.wpengineapi.com/v1/user", {
      headers: { Authorization: `Basic ${auth}` },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("User Information:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error fetching user information:", error);
    throw error;
  }
}

// Execute the function
getUserInfo();

To run this code on your local machine, you can:

  1. Create a file called user-info.js
  2. Paste this code into it
  3. Insert your API credentials
  4. Run it with node user-info.js.

This script makes a GET request to the /user endpoint, which returns information about the authenticated user. If successful, it will display your user details in the console, like this:

{
  "id": "d84kd9f4-2c8w-op38-826x-16zje9c84ngp",
  "email": "walter.p.engine@example.com",
  "first_name": "Walter",
  "last_name": "Engine",
  "phone_number": null
}

NOTE: Your API credentials are sensitive, so be sure never to post them publicly or commit them to version control.

Core API Functionality

Now that we’ve covered the basics, let’s explore the core functionality of the Hosting Platform API with practical examples.

Auth Helper Function

All of the examples below use a utility function called createAuthHeader to get the API credentials from environment variables and generate the Basic Authentication header for Hosting Platform API requests:

import dotenv from "dotenv";

// Load environment variables from .env file
dotenv.config();

/**
 * Create a Basic Authentication header for Hosting Platform API requests
 * @returns The Base64 encoded authentication string
 */
export function createAuthHeader() {
  const API_USER_ID = process.env.WP_ENGINE_API_USER_ID || "";
  const API_PASSWORD = process.env.WP_ENGINE_API_PASSWORD || "";

  if (!API_USER_ID || !API_PASSWORD) {
    console.error(
      "Error: Hosting Platform API credentials not found in environment variables."
    );
    console.error(
      "Please create a .env file with WP_ENGINE_API_USER_ID and WP_ENGINE_API_PASSWORD."
    );
    process.exit(1);
  }

  // Create the authorization header
  const auth = Buffer.from(`${API_USER_ID}:${API_PASSWORD}`).toString("base64");
  return `Basic ${auth}`;
}

You can set up a similar helper function in your project to generate the auth header for all API requests, if you like.

Account Management

Listing Accounts

To retrieve a list of WP Engine accounts that the authenticated user has access to:

import { createAuthHeader } from "./utils.js";

async function listAccounts() {
  try {
    const response = await fetch("https://api.wpengineapi.com/v1/accounts", {
      headers: { Authorization: createAuthHeader() },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Accounts:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error fetching accounts:", error);
    throw error;
  }
}

listAccounts();

Managing Account Users

You can add, update, or remove users from your WP Engine account:

import { createAuthHeader } from "./utils.js";

async function addAccountUser(accountId, email, role) {
  try {
    const response = await fetch(
      `https://api.wpengineapi.com/v1/accounts/${accountId}/users`,
      {
        method: "POST",
        headers: {
          Authorization: createAuthHeader(),
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          email,
          role, // 'admin', 'developer', 'sftp', 'billing', 'support', 'partial'
        }),
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("User added successfully:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error adding user:", error);
    throw error;
  }
}

// Example usage
// addAccountUser('your-account-id', 'newuser@example.com', 'developer');

Site Management

Listing Sites

To retrieve a list of sites associated with your account:

import { createAuthHeader } from "./utils.js";

async function listSites() {
  try {
    const response = await fetch("https://api.wpengineapi.com/v1/sites", {
      headers: { Authorization: createAuthHeader() },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Sites:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error fetching sites:", error);
    throw error;
  }
}

listSites();

Creating a New Site

To create a new site on WP Engine:

import { createAuthHeader } from "./utils.js";

async function createSite(name) {
  try {
    const response = await fetch("https://api.wpengineapi.com/v1/sites", {
      method: "POST",
      headers: {
        Authorization: createAuthHeader(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ name }),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Site created successfully:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error creating site:", error);
    throw error;
  }
}

// Example usage
// createSite('my-new-site');

Install (Environment) Management

Listing Installs

To retrieve a list of installs (environments) associated with your account:

import { createAuthHeader } from "./utils.js";

async function listInstalls() {
  try {
    const response = await fetch("https://api.wpengineapi.com/v1/installs", {
      headers: { Authorization: createAuthHeader() },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Installs:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error fetching installs:", error);
    throw error;
  }
}

listInstalls();

Creating a New Install

To create a new install (environment) on WP Engine:

import { createAuthHeader } from "./utils.js";

async function createInstall(siteId, name, environment) {
  try {
    const response = await fetch("https://api.wpengineapi.com/v1/installs", {
      method: "POST",
      headers: {
        Authorization: createAuthHeader(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        site_id: siteId,
        name,
        environment, // 'production', 'staging', 'development'
      }),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Install created successfully:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error creating install:", error);
    throw error;
  }
}

// Example usage
// createInstall('your-site-id', 'my-new-install', 'staging');

Domain Management

Listing Domains

To retrieve a list of domains associated with an install:

import { createAuthHeader } from "./utils.js";

async function listDomains(installId) {
  try {
    const response = await fetch(
      `https://api.wpengineapi.com/v1/installs/${installId}/domains`,
      {
        headers: { Authorization: createAuthHeader() },
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Domains:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error fetching domains:", error);
    throw error;
  }
}

// Example usage
// listDomains('your-install-id');

Adding a Domain

To add a new domain to an install:

import { createAuthHeader } from "./utils.js";

async function addDomain(installId, name, primary = false) {
  try {
    const response = await fetch(
      `https://api.wpengineapi.com/v1/installs/${installId}/domains`,
      {
        method: "POST",
        headers: {
          Authorization: createAuthHeader(),
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          name,
          primary,
        }),
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Domain added successfully:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error adding domain:", error);
    throw error;
  }
}

// Example usage
// addDomain('your-install-id', 'example.com', true);

Backup Management

Creating a Backup

To create a backup of an install:

import { createAuthHeader } from "./utils.js";

async function createBackup(installId, description) {
  try {
    const response = await fetch(
      `https://api.wpengineapi.com/v1/installs/${installId}/backups`,
      {
        method: "POST",
        headers: {
          Authorization: createAuthHeader(),
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ description }),
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Backup created successfully:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error creating backup:", error);
    throw error;
  }
}

// Example usage
// createBackup('your-install-id', 'Pre-deployment backup');

Listing Backups

To retrieve a list of backups for an install:

import { createAuthHeader } from "./utils.js";

async function listBackups(installId) {
  try {
    const response = await fetch(
      `https://api.wpengineapi.com/v1/installs/${installId}/backups`,
      {
        headers: { Authorization: createAuthHeader() },
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log("Backups:");
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error("Error fetching backups:", error);
    throw error;
  }
}

// Example usage
// listBackups('your-install-id');

Cache Management

Purging Cache

To purge the cache for an install:

import { createAuthHeader } from "./utils.js";

async function purgeCache(installId, type = "all") {
  try {
    const response = await fetch(
      `https://api.wpengineapi.com/v1/installs/${installId}/caches/${type}`,
      {
        method: "POST",
        headers: {
          Authorization: createAuthHeader(),
          "Content-Type": "application/json",
        },
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log(`${type} cache purged successfully:`);
    console.log(JSON.stringify(data, null, 2));
    return data;
  } catch (error) {
    console.error(`Error purging ${type} cache:`, error);
    throw error;
  }
}

// Example usage
// purgeCache('your-install-id', 'all');

A Complete Example: WP Engine Site Management CLI Tool

To see a practical implementation of the concepts covered in this guide, check out the WP Engine Site Management CLI Tool. This open-source tool demonstrates how to interact with the Hosting Platform API from the command line, allowing developers to efficiently manage their WP Engine sites and installs:

Key Features

With this CLI tool, you can:

  • Browse accounts you have access to
  • View sites within each account
  • View installs (environments) within each site
  • Create and delete sites
  • Create and delete installs (environments)

For detailed usage instructions and setup steps, please follow the guidance provided in the project’s README.

Reading through the source code for this CLI tool will give you a good understanding of how multiple API endpoints can be used together to perform complex operations.

Advanced Topics

Pagination

The Hosting Platform API uses offset-based pagination for list operations. You can control the number of results per page using the limit parameter (maximum 100) and specify the starting point with the offset parameter.

import { createAuthHeader } from "./utils.js";

async function getPaginatedList(endpoint, limit = 100, offset = 0) {
  try {
    const response = await fetch(
      `https://api.wpengineapi.com/v1/${endpoint}?limit=${limit}&offset=${offset}`,
      {
        headers: { Authorization: createAuthHeader() },
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log(`${endpoint} (limit: ${limit}, offset: ${offset}):`);
    console.log(`Total items: ${data.count}`);
    console.log(`Items in this page: ${data.results.length}`);

    // Check if there are more pages
    if (data.next) {
      console.log(`Next page available at: ${data.next}`);
    }

    return data;
  } catch (error) {
    console.error(`Error fetching paginated list for ${endpoint}:`, error);
    throw error;
  }
}

// Example usage
// getPaginatedList('installs', 10, 0);

Error Handling

The Hosting Platform API uses standard HTTP response codes to indicate the success or failure of requests. Here’s how to implement robust error handling:

import { createAuthHeader } from "./utils.js";

async function makeApiRequest(method, endpoint, data = null) {
  try {
    const options = {
      method,
      headers: {
        Authorization: createAuthHeader(),
        "Content-Type": "application/json",
      },
    };

    if (method !== "GET" && data) {
      options.body = JSON.stringify(data);
    }

    const response = await fetch(
      `https://api.wpengineapi.com/v1/${endpoint}`,
      options
    );

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      const status = response.status;

      switch (status) {
        case 400:
          console.error(
            "Bad Request: The request was invalid or cannot be served.",
            errorData
          );
          break;
        case 401:
          console.error(
            "Unauthorized: Authentication failed or user does not have permissions.",
            errorData
          );
          break;
        case 404:
          console.error(
            "Not Found: The requested resource does not exist.",
            errorData
          );
          break;
        case 429:
          console.error("Too Many Requests: Rate limit exceeded.", errorData);
          // Implement retry logic with exponential backoff
          break;
        case 503:
          console.error(
            "Service Unavailable: The server is currently unavailable.",
            errorData
          );
          // Implement retry logic
          break;
        default:
          console.error(`Error ${status}:`, errorData);
      }

      throw new Error(`HTTP error! status: ${status}`);
    }

    return await response.json();
  } catch (error) {
    if (error instanceof TypeError) {
      // Network error or other fetch-related error
      console.error("Network error:", error.message);
    } else {
      // Handle other errors
      console.error("Unexpected error:", error);
    }

    throw error;
  }
}

// Example usage
// makeApiRequest('GET', 'installs');

Rate Limiting

The Hosting Platform API implements rate limiting to prevent abuse. If you exceed the rate limits, you’ll receive a 429 Too Many Requests response. To handle this gracefully, implement a retry mechanism with exponential backoff:

import { createAuthHeader } from "./utils.js";

async function makeApiRequestWithRetry(
  method,
  endpoint,
  data = null,
  maxRetries = 3,
  initialDelay = 1000
) {
  let retries = 0;
  let delay = initialDelay;

  while (true) {
    try {
      const options = {
        method,
        headers: {
          Authorization: createAuthHeader(),
          "Content-Type": "application/json",
        },
      };

      if (method !== "GET" && data) {
        options.body = JSON.stringify(data);
      }

      const response = await fetch(
        `https://api.wpengineapi.com/v1/${endpoint}`,
        options
      );

      if (response.status === 429 && retries < maxRetries) {
        // Rate limit exceeded, retry after delay
        retries++;
        console.log(
          `Rate limit exceeded. Retrying in ${delay}ms... (${retries}/${maxRetries})`
        );
        await new Promise((resolve) => setTimeout(resolve, delay));
        delay *= 2; // Exponential backoff
        continue;
      }

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      return await response.json();
    } catch (error) {
      if (retries >= maxRetries) {
        console.error("Max retries reached. Giving up.");
        throw error;
      }

      if (!(error instanceof Error && error.message.includes("429"))) {
        throw error; // Only retry on rate limit errors
      }

      retries++;
      console.log(
        `Error: ${error.message}. Retrying in ${delay}ms... (${retries}/${maxRetries})`
      );
      await new Promise((resolve) => setTimeout(resolve, delay));
      delay *= 2; // Exponential backoff
    }
  }
}

// Example usage
// makeApiRequestWithRetry('GET', 'installs');

Best Practices

Security Considerations

  1. Protect your API credentials: Store your API credentials securely and never expose them in client-side code.
  2. Use environment variables: Store sensitive information like API credentials in environment variables rather than hardcoding them in your application.
  3. Implement proper error handling: Handle errors gracefully to prevent exposing sensitive information in error messages.
  4. Validate input: Always validate and sanitize user input before using it in API requests.

Performance Optimization

  1. Use pagination: When retrieving large datasets, use pagination to limit the amount of data transferred in a single request.
  2. Implement caching: Cache API responses when appropriate to reduce the number of requests made to the API.
  3. Batch operations: When possible, batch multiple operations into a single request to reduce overhead.
  4. Handle rate limits: Implement retry mechanisms with exponential backoff to handle rate limiting gracefully.

Monitoring and Logging

  1. Log API requests and responses: Keep logs of API interactions for debugging and auditing purposes.
  2. Monitor API usage: Track your API usage to stay within rate limits and identify potential issues.
  3. Set up alerts: Configure alerts for API errors or unusual patterns to catch issues early.

Conclusion

The Hosting Platform API provides a powerful way to automate and streamline your WordPress hosting management. By leveraging the API, you can build custom tools and workflows that save time, reduce errors, and improve efficiency.

This guide has covered the fundamentals of working with the Hosting Platform API, from authentication and basic operations to advanced topics like pagination, error handling, and rate limiting. The accompanying code examples demonstrate how to implement these concepts in practice using Node.js/JavaScript.

As you continue to explore the Hosting Platform API, remember to refer to the official documentation for the most up-to-date information and best practices.

Happy coding!


Resources

The post Mastering the WP Engine Hosting Platform API: A Comprehensive Guide for Developers appeared first on Builders.

]]>
https://wpengine.com/builders/mastering-the-wp-engine-api-a-comprehensive-guide-for-developers/feed/ 0
Using Composer to Manage Plugins and Deploy to WP Engine https://wpengine.com/builders/using-composer-manage-plugins-deploy/ https://wpengine.com/builders/using-composer-manage-plugins-deploy/#respond Thu, 11 Jul 2024 18:24:08 +0000 https://wpengine.com/builders/?p=4931 Manage your WordPress dependencies with Composer and deploy to WP Engine with GitHub Actions.

The post Using Composer to Manage Plugins and Deploy to WP Engine appeared first on Builders.

]]>
We recently covered how to do branched deploys to WP Engine with GitHub Actions. Today, let’s explore managing plugin dependencies with Composer when deploying to WP Engine.

It helps if you are familiar with Composer, WPackagist, and Git version control. However, if you are not, here is an excellent resource to get you started: Managing your WordPress site with Git and Composer. Also, these instructions assume you have an existing WordPress site hosted on WP Engine that you are retroactively putting under version control.

Here is what we’ll be covering:

  • Version control (Git) – you will only need the wp-content/ directory under version control. We’ll let WP Engine maintain WordPress core automatic updates.
  • Composer—You will use Composer and WPackagist.org to manage WordPress plugins. Note that it will be your responsibility to manage plugin updates with this Composer setup, and utilizing Smart Plugin Manager or WordPress auto-updates is not covered.
    • Bonus: You will learn to install and manage ACF PRO as a Composer dependency.

Overview of project organization

Below is an example of your final GitHub repository. We will explore these in greater detail. (Also, check out the demo codebase on GitHub to follow along with the proposed structure.)

  • .github/workflows/[dev|staging|production].yml: These represent our GitHub Action configuration files for deploying our codebase to their corresponding environments. Be sure to become familiar with the WP Engine GitHub Action for Site Deployment, which relies on using rsync to sync the repository files with the targeted server.
  • rsync-config: These files configure our WP Engine GitHub Action for Site Deployment. The action relies on running a rsync between the GitHub repository and the targeted environment.
    • excludes.txt: Referenced in the .github/workflows/[dev|staging|production].yml file as the explicit rsync FLAGS. These are any items we want to exclude from being deleted each time our GitHub Action runs a rsync.
      • Hint: these files likely exist on the final WP Engine server and we do not want to remove them every time an rsync is executed in our GitHub Action.
    • includes.txt: Referenced in the .github/workflows/[dev|staging|production].yml GitHub Action as the explicit rsync FLAGS. These are any items we want to include in our GitHub Action rsync.
      • Hint: these will likely represent the un-ignored items in your project .gitignore which we’ll cover below.
  • bin/post-deploy.sh: This is how you pass any SCRIPTS to the GitHub Action to run commands on the final destination server.
    • Tip: you can run WP-CLI and Composer install commands on the final WP Engine environment.
  • plugins/: You will rely on Composer and WPackagist to install standard and stable plugins. However, we will also show you how you might handle a custom plugin.
    • plugins/demo-plugin: Represents any custom plugins you may want to version control. You could have as many of these as you like. For example, you could organize your custom functionality as plugins/foo-plugin, plugins/bar-plugin.
  • themes/: Similar to our plugins, you will likely version control a single theme for the final destination.
    • themes/demo-theme: Represents a single, custom theme you would have under version control.
  • .gitignore: It is critical to tell Git what you want to ignore from being under version control, as well as what you do not want to ignore (yes, this sounds odd, but trust us).
  • composer.json: Lists your project’s direct dependencies, as well as each dependency’s dependencies, and allows you to pin relative semantic versions for your dependencies.
  • composer.lock: Allows you to control when and how to update your dependencies.

Start by organizing a copy of your WordPress site’s wp-content/ directory to mirror the organization noted above. It is recommended to create this setup on your local computer. You can access a full site backup from WP Engine’s User Portal.  It is okay if there are other directories within your wp-content/ directory. You will tell Git what you want to ignore, or not ignore in the next step.

Create a .gitignore

Create a .gitignore file in your WordPress installation’s wp-content/ directory and place the code below:

.gitignore (full source)

#---------------------------
# WordPress general
#---------------------------
# Ignore the wp-content/index.php
/index.php

#---------------------------
# WordPress themes
#---------------------------
# Ignore all themes
/themes/*
# DO NOT ignore this theme!
!/themes/demo-theme

#---------------------------
# WordPress plugins
#---------------------------
# Ignore all plugins
/plugins/*
# DO NOT ignore this plugin!
!/plugins/demo-plugin

#---------------------------
# WP MU plugins: these are
# managed by the platform.
#---------------------------
/mu-plugins/

#---------------------------
# WP uploads directory
#---------------------------
/uploads/

#---------------------------
# WP upgrade files
#---------------------------
/upgrade/

#---------------------------
# Composer
#---------------------------
/vendor
auth.json
.env
.env.*
!.env.example

A few key things to note from the code above:

  • /plugins/*: this ignores any directories nested within the plugins/ directory.
    • !/plugins/demo-plugin: overrides the previous /plugins/* to allow the demo-plugin to not be ignored, and instead, it is version-controlled. 
  • /themes/*: this ignores any directories nested within the themes/ directory.
    • !/themes/demo-theme: overrides the previous /themes/* to allow the demo-theme to not be ignored, and instead it is version controlled. 

You can adjust the demo-plugin or demo-theme examples to work with your setup.

Set up Composer with WPackagist integration

Composer allows you to manage your PHP dependencies. WPackagist mirrors the WordPress plugin and theme directories as a Composer repository. 

Typically, you could consider utilizing Composer for PSR-4 / PSR-0 namespacing, linting, and unit testing. We’ll only focus on demonstrating how you might pull in some standard WordPress plugins. 

Here is a composer.json that installs a few example plugins from WPackagist: Two Factor, Gutenberg, and WordPress SEO. These are here for demonstration and feel free to replace them with plugins that are standard to your workflow.

composer.json (full source)

{
   "name": "wpe/composer-demo",
   "description": "Demo with Composer and deploy to WP Engine.",
   "license": "GPL-2.0+",
   "type": "project",
   "repositories": [
       {
           "type":"composer",
           "url":"https://wpackagist.org",
           "only": [
               "wpackagist-plugin/*",
               "wpackagist-theme/*"
           ]
       }
   ],
   "require": {
       "wpackagist-plugin/two-factor": "*",
       "wpackagist-plugin/gutenberg": "*",
       "wpackagist-plugin/wordpress-seo": "*"
   },
   "extra": {
       "installer-paths": {
           "plugins/{$name}/": [
               "type:wordpress-plugin"
           ],
           "themes/{$name}/": [
               "type:wordpress-theme"
           ]
       }
   },
   "config": {
       "allow-plugins": {
           "composer/installers": true
       }
   }
}

If you’re just integrating Composer into your project the first time then you’ll likely want to now run composer install after creating a composer.json like the one above. This will generate the corresponding composer.lock file for your project with these new dependencies (and their dependencies).

If this this is your first time integrating WPackagist into your existing Composer project then the key things to note from the code above:

  • Add the WPackagist repository under the repositories entry (see lines 6-15 in the code above).
  • Add any plugins or themes you want to install from WPackagist under the require key (see lines 17-19 in the code above). Be sure to use the wpackagist-plugin/ or wpackagist-theme/ prefixed vendor name to tell Composer that you intend these to be installed through WPackagist.
  • Set the installer-paths for your plugins and themes under the extra key to tell Composer where to install your WPackagist dependencies.

Run composer update to install the new required dependencies (see lines 22-29 in the code above).

How to install ACF PRO with Composer

ACF has some useful information on installing ACF PRO with Composer. We’ll use the composer.json code above as our starting point. Here are the steps you’ll need to set this up, which we’ll go over in further detail:

  1. Copy your ACF PRO license key from the Licenses tab. To activate your license via your wp-config.php file, add the following line to the file, replacing [key] with your license key: define( 'ACF_PRO_LICENSE', '[key]'.
  2. Add the ACF package repository to your composer.json.
  3. Install the plugin by running composer require wpengine/advanced-custom-fields-pro.

Here is what your final composer.json file will look:

composer.json (full source)

{
    "name": "wpe/composer-demo",
    "description": "Demo with Composer and deploy to WP Engine.",
    "license": "GPL-2.0+",
    "type": "project",
    "repositories": [
        {
            "type": "composer",
            "url": "https://connect.advancedcustomfields.com"
        },
        {
            "type":"composer",
            "url":"https://wpackagist.org",
            "only": [
                "wpackagist-plugin/*",
                "wpackagist-theme/*"
            ]
        }
    ],
    "require": {
        "wpackagist-plugin/two-factor": "*",
        "wpackagist-plugin/gutenberg": "*",
        "wpackagist-plugin/wordpress-seo": "*",
        "wpengine/advanced-custom-fields-pro": "^6.3"
    },
    "extra": {
        "installer-paths": {
            "plugins/{$name}/": [
                "type:wordpress-plugin"
            ],
            "themes/{$name}/": [
                "type:wordpress-theme"
            ]
        }
    },
    "config": {
        "allow-plugins": {
            "composer/installers": true
        }
    }
}

There are other ways to install and activate your ACF PRO license, so be sure to check out the full documentation. If you encounter any issues along the way, then send a support message.

Set up WP Engine GitHub Action for Composer integration

WP Engine’s GitHub Action for Site Deployment relies on rsync to transfer and synchronize local GitHub repository files to the final WP Engine hosting environment. This is critical to be mindful of when you initially setup your GitHub workflows.

Additionally, since we’re organizing the root of our repository within the wp-content/ directory then we want to be sure that we configure some key deployment options in the final workflow.

production.yml (full source)

# Deploy to WP Engine Production environment
# https://wpengine.com/support/environments/#About_Environments
name: Deploy to production
on:
  push:
    branches:
     - main
jobs:
  Deploy-to-WP-Engine-Production:
    runs-on: ubuntu-latest
    steps:
    - run: echo "Preparing to deploy to WP Engine production"
    - uses: actions/checkout@v3
    - name: GitHub Action Deploy to WP Engine
      uses: wpengine/github-action-wpe-site-deploy@v3
      with:
        # Deploy vars
        # https://github.com/wpengine/github-action-wpe-site-deploy?tab=readme-ov-file#environment-variables--secrets

        # The private RSA key you will save in the Github Secrets
        WPE_SSHG_KEY_PRIVATE: ${{ secrets.WPE_SSHG_KEY_PRIVATE }}
        # Destination to deploy to WPE
        # Change to your environment name
        WPE_ENV: yourEnvironmentName

        # Deploy options

        # An optional destination directory to deploy
        # to other than the WordPress root.
        REMOTE_PATH: "wp-content/"
        # Optional flags for the deployment
        FLAGS: -azvr --inplace --delete --include-from rsync-config/includes.txt --exclude=".*" --exclude-from rsync-config/excludes.txt
        # File containing custom scripts run after the rsync
        SCRIPT: wp-content/bin/post-deploy.sh

In the code above you’ll want to replace some of the deployment variables, like WPE_ENV and be sure to setup your SSH keys (both the WP Engine SSH Gateway key and your GitHub repository’s private SSH key: WPE_SSH_KEY_PRIVATE). Again, the helpful WP Engine step-by-step guide can help you here. The key options you will want to pay close attention to are listed in the table below.

NameTypeUsage
REMOTE_PATHstringOptional path to specify a directory destination to deploy to. Defaults to WordPress root directory on WP Engine. You want this to be wp-content/.
FLAGSstringSet optional rsync flags such as --delete or --exclude-from


Caution: Setting custom rsync flags replaces the default flags provided by this action. Consider also adding the -azvr flags as needed.
a preserves symbolic links, timestamps, user permissions and ownership.
z is for compression
v is for verbose output
r is for recursive directory scanning
SCRIPTstringRemote bash file to execute post-deploy. This can include WP_CLI commands for example. Path is relative to the WP root and file executes on remote. This file can be included in your repo, or be a persistent file that lives on your server.
Deployment options for Deploy WordPress to WP Engine GitHub Action (see full list).

You will want to pass some rather specific FLAGS and a custom post-deploy SCRIPT in order to get our targeted setup accurately deploying.

Configure rsync flags

You’ll be running rsync with the  --delete flag, which is destructive and we need to be careful about what we tell it to delete. Below is what you’ll want to put in your rsync-config/excludes.txt and rsync-config/includex.txt files.

excludes.txt (full source)

# Excluding these items from being deleted each rsync

plugins/*
themes/* 
mu-plugins/
uploads/
blogs.dir/
upgrade/*
backup-db/*
advanced-cache.php
wp-cache-config.php
cache/*
cache/supercache/*
index.php
mysql.sql

.env
.env.*
auth.json
vendor

includes.txt (full source)

# Including plugins/themes that we check into
# Git so that the version in GitHub is deployed

/plugins/demo-plugin
/themes/demo-theme

# ...other plugins could go here...

Create a post-deploy script

After everything is deployed, you will want to run composer install on the WP Engine environment. This will allow you to update your dependencies with Composer locally, commit any changes, push them to the Git remote, and once the GitHub Action is run to rsync any composer.json and composer.lock changes then it’ll install any updated dependencies on the final environment. This is the SCRIPT: wp-content/bin/post-deploy.sh we set in our GitHub Actions’s YML file (above).

post-deploy.sh (full source)

#!/bin/sh

echo "Starting post deploy script..."
echo "Switch directory to wp-content/"
cd wp-content
echo "Installing Composer dependencies..."
composer install --optimize-autoloader --no-dev --no-progress

Conclusion

Utilizing Composer with WPackagist to manage your WordPress plugin dependencies can help keep teams organized and facilitate consistent workflows.

Let us know how you’re maintaining your ideal workflow—tag us on X @wpengine.

The post Using Composer to Manage Plugins and Deploy to WP Engine appeared first on Builders.

]]>
https://wpengine.com/builders/using-composer-manage-plugins-deploy/feed/ 0
Beta Testing WordPress with Local Blueprints https://wpengine.com/builders/beta-testing-wordpress-local-blueprints/ https://wpengine.com/builders/beta-testing-wordpress-local-blueprints/#respond Wed, 26 Jun 2024 14:53:08 +0000 https://wpengine.com/builders/?p=4810 Start testing the latest WordPress beta quickly with Local Blueprints.

The post Beta Testing WordPress with Local Blueprints appeared first on Builders.

]]>
A new release is on the horizon! 🌅

As with each release, there are countless hours of testing to ensure the overall experience is bug-free and optimized. WordPress 6.6 is targeted to be released on July 16, 2024. Right now, you can help by testing.

Local is the go-to tool to create a WordPress sandbox and effortlessly develop WordPress sites locally, and with Local you can get testing in seconds. Here are a few options to get you started.

Check out this video or continue reading to learn about all the ways to get testing.

Option 1: WP-CLI + Local

If you already have an existing site in Local then you can just upgrade it to the latest beta with WP-CLI. Here is how:

  1. Right-click on your site and choose ‘Open site shell’, which will open your system’s terminal application and automatically launch WP-CLI.
  2. Once WP-CLI is launched then just run this command: wp core update --version=6.6-RC1
Open WP-CLI in Local

Option 2: Local + WordPress Beta Tester plugin

If you already have a Local site then you can install the WordPress Beta Tester plugin and get the latest beta.

  1. Visit your WordPress dashboard’s Appearance > Plugins, and choose ‘Add New
  2. Search and install the WordPress Beta Tester plugin
  3. Once activated, visit the Tools > Beta Testing area and update the settings to get the latest beta (select the “Bleeding edge” channel and “Beta/RC Only” stream).
WordPress Beta Tester plugin settings screen

Option 3: Local Blueprint FTW!

Save a few clicks and just import our custom Local Blueprint, which comes with everything installed and ready for testing: WordPress Beta Tester plugin with WP 6.6 Beta 1 already installed and the default Twenty Twenty-Four theme activated.

Just click the download button below and drag and drop the downloaded WordPress-Beta-Tester_6.6-RC-1.zip into your Local app to spin up a new site and get testing!

Drag and drop Blueprint into Local

(Note: the super secret WordPress username and password for the Blueprint is admin.)

Reach out to @WPEBuilders and let us know how you’re using Local and what you’re testing in the latest WordPress 6.6 beta release.

The post Beta Testing WordPress with Local Blueprints appeared first on Builders.

]]>
https://wpengine.com/builders/beta-testing-wordpress-local-blueprints/feed/ 0
5 Exciting (and Powerful) WordPress Features https://wpengine.com/builders/wordpress-features/ https://wpengine.com/builders/wordpress-features/#respond Wed, 29 May 2024 15:54:19 +0000 https://wpengine.com/builders/?p=31622 WordPress continues to evolve with various innovative features designed to enhance the user experience and streamline website development. From a revolutionized Font Library to intuitive site icon settings, these updates […]

The post 5 Exciting (and Powerful) WordPress Features appeared first on Builders.

]]>
WordPress continues to evolve with various innovative features designed to enhance the user experience and streamline website development. From a revolutionized Font Library to intuitive site icon settings, these updates cater to novice and experienced users.

WordPress patterns and the efficient List View in the block editor simplify content creation and management. Global Styles and the new theme.json configuration tool provide a unified approach to site customization.

This article explores five exciting WordPress features that enhance and elevate the website building experience for users and developers.

Google Fonts + WordPress: How to Install Them

With WordPress 6.5, the Font Library has transformed font management within the editor, making integrating fonts like Google Fonts easy. Users can now easily add any font from Google Fonts to their WordPress site, enhancing typography with minimal effort. This feature expands creative possibilities, incorporating various fonts into WordPress projects.

Add a Site Icon to Your WordPress Website

In WordPress 6.5, updating your site icon became more intuitive. Users can add or change the site icon directly from the General Settings panel. Navigate to Settings → General to update your site’s visual identity. This enhancement eliminates the need to use the Site Editor or Customizer for this task, making the experience smoother and more efficient.

Understanding List View in the WordPress Editor

The List View in the WordPress block editor is powerful for navigating through layers of content and nested blocks. In the Post and Page Editor or under Appearance > Editor, this feature allows easy selection and arrangement of blocks. It provides a clear, organized view of your content structure, making your editing experience more intuitive and efficient.

WordPress Patterns: How to Create, Edit and Utilize

WordPress patterns are like building blocks, ready to create stylish sections for your pages or posts. By inserting and customizing these patterns, you can quickly create complex layouts, saving you valuable time. Not only do patterns streamline the process, but they also showcase creative block combinations, enhancing your WordPress experience.

Getting Started with Global Styles in WordPress

Global Styles in WordPress simplify site-wide style changes without editing blocks or pages individually. This feature makes it easier for users and theme developers to apply consistent styling across blocks. Theme.json enhances this functionality, offering a unified approach to managing block settings and styles, streamlining the customization of WordPress sites.

The post 5 Exciting (and Powerful) WordPress Features appeared first on Builders.

]]>
https://wpengine.com/builders/wordpress-features/feed/ 0
Add a Favicon to Your WordPress Website https://wpengine.com/builders/add-site-favicon/ https://wpengine.com/builders/add-site-favicon/#respond Thu, 04 Jan 2024 13:24:00 +0000 https://builders.wpengine.com/?p=4395 Stand out from the competition and give your users a better experience.

The post Add a Favicon to Your WordPress Website appeared first on Builders.

]]>
Builders are continually exploring fresh and innovative methods to enhance their websites. A straightforward enhancement is the addition of a favicon.

Favicons, also known as favorites icons, are small yet impactful images next to the URL in a web browser’s address bar. They enhance brand recognition and provide a polished look as they are displayed in browser tabs, making the site more memorable and professionally appealing.

Add a Favicon to Your Website

Incorporating a favicon, or in WordPress terms, a site icon, is an excellent strategy to distinguish your website from competitors and enhance user experience. Here are three ways to integrate a favicon into your website.

Method #1 – WordPress Settings

In WordPress 6.5, updating your site icon became more intuitive. Users can add or change the site icon directly from the General Settings panel. Navigate to Settings → General to update your site’s visual identity. This enhancement eliminates the need to use the Site Editor or Customizer for this task, making the experience smoother and more efficient.

Below is a video that shows how easy this method is:

Method #2 – Site Logo Block

Alternatively, a recent WordPress update introduced a feature with the Site Logo block. Users can now set their site logo as their site icon (favicon). Below is a screenshot of the site editor, with the site logo selected and the toggle option in the right sidebar: Use as site icon.

WordPress Site Logo / Icon

Note: Unlike traditional favicons that utilize a .ico file, site icons can be uploaded through WordPress as .jpg and .png files.

Method #3 – Custom Function

Below is a code snippet that will add a favicon to your website. Place this in your theme’s functions.php file. Next, add a favicon.ico file inside of a folder called /images/ located inside of the theme.

// Add favicon to your website.
add_action( 'wp_head', 'add_site_favicon' );
function add_site_favicon() {

	echo '<link rel="shortcut icon" type="image/x-icon" href="' . get_template_directory_uri() . '/images/favicon.ico" />';

}

The post Add a Favicon to Your WordPress Website appeared first on Builders.

]]>
https://wpengine.com/builders/add-site-favicon/feed/ 0
Create a Slider Block for WordPress with SwiperJS https://wpengine.com/builders/create-a-slider-block/ https://wpengine.com/builders/create-a-slider-block/#respond Wed, 06 Dec 2023 21:58:22 +0000 https://wpengine.com/builders/?p=31316 Learn to create a slider block with rotating images with SwiperJS.

The post Create a Slider Block for WordPress with SwiperJS appeared first on Builders.

]]>
Carousels, slideshows, sliders, whatever you call them, are controversial and complex pieces of our web interactions. More often than not, they’re a means to shove a bunch of ideas into a single part of the user interface. Usually, clients follow the herd mentality and request they have one, or it becomes a means for multiple stakeholders to compromise on what takes precedence in information architecture.

We all know they could be better for overall site performance and offer many user experience obstacles. So, why would you want to build one? 🙃

I’m making some compelling counter-arguments, but there is still a time and place when you will need one, and we’ll cover all the steps to create your own.

As of writing this, no slider block is currently available in WordPress core’s library. There are several existing slider block plugins, but we want to have control over and learn the intricacies of our own. This way, we can focus on keeping our code lean and agile and extend it to our needs.

I’m not going to lie, there are a lot of steps to get things like this working, but you’ll learn a lot along the way, like:

  • Working with and extending the @wordpress/scripts package.
  • Pulling in 3rd-party dependencies and integrating them to behave smoothly within the WordPress editor.
  • Use @wordpress/components to allow your clients to modify the properties of the slider.
  • And much more.

Here is a demonstration of what we’ll be creating.

Prerequisites

Beyond some working knowledge of HTML, CSS, PHP, and JavaScript. You’ll also need familiarity with the following:

  • Node and npm: You should have Node.js and npm installed on your machine. I recommend using nvm (node version manager), which allows you to install and manage different versions of Node easily.
  • Block development: Preferably, you’ve at least built your first block and are familiar with the key concepts and files that make up block registration and rendering (editor and front end).
  • Build tools: Preferably, you’re already familiar with @wordpress/create-block and @wordpress/scripts , and you’re comfortable running commands from a Command Line Interface (CLI).

Why SwiperJS?

There are many slider libraries out there that are drop-in solutions. We want one with a history of stability, extendability, and modularity. This way, we can pull in bits and pieces of things we need and leave out what we do not want. SwiperJS has been around for a while and is very modular.

Check out the SwiperJS Demos page to see all possible variations you can create.

Setting Up

First, we need to create a block and a WordPress plugin to organize our custom functionality and the @wordpress/create-block tool is super handy for this.

Generate Our Plugin and Block Files

Create our WordPress plugin and first block with @wordpress/create-block

npx @wordpress/create-block@latest slider --namespace wpe --variant dynamic && cd slider

Let’s break down what happened for those new to the @wordpress/create-block package. Behind the scenes, @wordpress/create-block does a lot of things for us, including establishing our NPM developer dependencies, which include the helpful @wordpress/scripts package, and creating a Slider block plugin with the following organization:

  • slider.php – The main plugin file, which registers a slider block.
  • src/ – This is where the @wordpress/create-block package places our block’s files.
  • build/ – This is where the @wordpress/scripts package will compile and create our block build for us. We should not have to modify anything in the directory, as it is overwritten each time with compiled assets.

The valuable @wordpress/scripts package is worth exploring if you’re new to its capabilities. For now, know that it is a series of reusable webpack-driven scripts that can watch and compile our final block assets for us.

Final directory structure after running @wordpress/create-block.

├── build
│   ├── block.json
│   ├── index.asset.php
│   ├── index.css
│   ├── index.js
│   ├── render.php
│   ├── style-index.css
│   ├── view.asset.php
│   └── view.js
├── package.json
├── readme.txt
├── slider.php
└── src
    ├── block.json
    ├── edit.js
    ├── editor.scss
    ├── index.js
    ├── render.php
    ├── style.scss
    └── view.js

Activate Plugin and Test Our Initial Progress

We’ve finished our first round of setting up and scaffolding our plugin.  Let’s activate the Slider plugin in a WordPress development environment to ensure we can insert the Slider block in the editor.

It is always good to check your progress! Of course, there is not much to see, and we have a simple block with a blue background right now. This is what @wordpress/create-block typically gives us to start.

Install Our Dependencies

We want to install our project dependencies, including swiper (for SwiperJS, of course), postcss-import, and postcss-preset-env, which allows us to import SwiperJS’s CSS when it is time.

Our one-liner for installing our dependencies and developer dependencies.

npm install --save swiper && npm install --save-dev postcss-import postcss-preset-env

Configure PostCSS

By default, when we created our WordPress plugin and the initial block with the @wordpress/create-block package it installed @wordpress/scripts as a devDependency. The @wordpress/scripts package comes with a pre-configured webpack setup, which includes PostCSS. We need to extend the existing PostCSS configuration to recognize and handle the CSS @import statements that we’ll use to roll up all of SwiperJS’s CSS with any of our own.

Be sure to create a new postcss.config.js file in the root of your plugin and place the following code within.

Our postcss.config.js file configures PostCSS to allow importing of Swiper’s CSS modules.

module.exports = () => {
	const config = {
		plugins: {
			'postcss-import': {},
			'postcss-preset-env': {},
		},
	};
	return config;
};

This will come in handy later when we update the src/styles.scss.

Verifying Our Initial Plugin Setup

We should now have our WordPress plugin, a Slider block, and our dependencies installed. Great!

We’ll focus on extending our Slider block for the remainder of this tutorial. Be sure to start watching your block for changes and compiling them automatically in the background. You can do this by running npm start, which tells @wordpress/scripts to watch our block for changes and compile them if anything is changed (to the build/ directory).

Check out the final codebase Slider Block GitHub repo. Feel free to download the final plugin or fork the repo and make it all your own.

Planning Our Block Customizations

When you’re getting ready to create a custom block, planning what you’ll need and what already exists that you can leverage is ideal.

We want to allow users to add a Slider block to the editor and automatically have it populated with example slides. We will utilize WordPress’s existing Cover block to represent our slides. This gives us existing affordances like image attachment, overlay colors, and nesting of additional blocks (Columns, Buttons, Headings, and many more).

With this in mind, we’ll update our slider block’s block.json.

Extending the Slider Block

We’ll open the Slider block’s block.json file and update the overall block’s definitions.

The Slider block’ssrc/block.json file is where we extend definitions for the overall block.

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "wpe/slider",
	"title": "Slider",
	"category": "widgets",
	"description": "Display your images in a horizontal carousel.",
	"icon": "slides",
	"keywords": ["carousel", "slideshow"],
	"attributes": {
		"autoplay": {
			"type": "boolean",
			"default": true
		},
		"navigation": {
			"type": "boolean",
			"default": true
		},
		"pagination": {
			"type": "boolean",
			"default": true
		}
	},
	"example": {},
	"supports": {
		"html": false,
		"align": ["wide", "full"],
		"className": true,
		"color": {
			"background": true,
			"gradients": true,
			"link": true,
			"text": true
		},
		"spacing": {
			"padding": true,
			"margin": ["top", "bottom"]
		}
	},
	"textdomain": "wpe",
	"version": "0.1.0",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"render": "file:./render.php",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}

We’re defining some new attributes for the Slider block, which we’ll use to allow users to choose whether they want certain features assigned to each Slider block. In our case, we’ll let them toggle whether the slider should autoplay and have navigation or pagination. Check out this Block API Reference if you want to dig deeper into how attributes work.

We also want our Slider block to opt into certain features that WordPress offers blocks, like alignment, colors, and spacing. This is why we’re defining supports in our block.json. Be sure to check out the complete list of Block Supports and consider extending what is here to offer additional features.

Import SwiperJS and Create a Default Object

We’re going to try to keep things organized (why not). Let’s import all of our SwiperJS dependencies and create a simple default SwiperJS object that we can utilize in both our editor (back-end) and front-end views.

Create a new src/swiper-init.js file.

We’re importing all of our Swiper JavaScript core and module dependencies and setting up a default object, which we can later use in multiple contexts.

/**
 * Swiper dependencies
 *
 * @see https://swiperjs.com/get-started
 */
import { Swiper } from 'swiper';
import { Autoplay, Keyboard, Navigation, Pagination } from 'swiper/modules';

/**
 * Initialize the slider.
 *
 * @param {Element} container HTMLElement.
 * @param {Object}  options   Slider parameters.
 *
 * @return {Object} Returns initialized slider instance.
 *
 * @see https://swiperjs.com/swiper-api#parameters
 */
export function SwiperInit( container, options = {} ) {
	const parameters = {
		autoplay: options?.autoplay ?? true,
		centeredSlides: options?.centerSlides ?? false,
		createElements: true,
		grabCursor: options?.grabCursor ?? true,
		initialSlide: 0,
		keyboard: true,
		modules: [ Autoplay, Keyboard, Navigation, Pagination ],
		navigation: options?.navigation ?? false,
		pagination: options?.pagination ?? false,
		simulateTouch: options?.simulateTouch ?? true,
	};

	return new Swiper( container, parameters );
}

We’re importing Swiper and some key Swiper modules at the top of the swiper-init.js file, and then we’re exporting our SwiperInit() function, which contains some defaults for us to return a new Swiper() instance.

Import SwiperJS CSS Styling

We’ll want to pull in SwiperJS’s CSS as well, which is critical to allowing the slider to slide.

Open up the existing src/style.scss and replace it with the following.

We’re importing our Swiper CSS dependencies and applying some of our own styles in src/style.scss

@import "swiper/css";
@import "swiper/css/a11y";
@import "swiper/css/autoplay";
@import "swiper/css/navigation";
@import "swiper/css/pagination";

.wp-block-wpe-slider {
	--swiper-theme-color: var(--wp--preset--color--accent, var(--wp--preset--color--primary));
	--swiper-navigation-size: 48px;
	--swiper-navigation-color: var(--swiper-theme-color);
	--swiper-pagination-bullet-horizontal-gap: 0.25rem;
	--swiper-pagination-bullet-vertical-gap: 0.25rem;
	--swiper-pagination-bullet-size: 0.75rem;
	--swiper-pagination-bullet-opacity: 1;
	--swiper-pagination-bullet-inactive-color: black;
	--swiper-pagination-bullet-inactive-opacity: .2;
}

Remember, we set up a custom postcss.config.js file earlier? This was critical to allow the @wordpress/scripts dependency, which we’re using to watch and build our final blocks, to be able to recognize and inspect dependencies from our install node_modules directory. This allows us to pull our CSS dependencies from our install swiper dependency.

Of course, this file is watched and pulled in as a dependency through the src/index.js file. Open it up, and you’ll see the existing import './style.scss'; near the top. Ultimately, this file is compiled and written to the final build/style-index.css and defined as a dependency in the Slider’s block.json file (via the "style"). This tells WordPress to load the CSS in both the editor and the front end.

Create a New constants.js File

I prefer to keep some key variables in a separate constants.js file. This allows fellow developers to quickly open it up and change key parameters for our final slider (if they choose to 😉).

Let’s create a new constants.js file and place the following code.

We’re defining key variables in a new src/constants.js file. We’ll reference these later in the src/slider.js file.

/**
 * These are the block we'll allow to be inserted
 * as a slide.
 */
export const ALLOWED_BLOCKS = [ 'core/cover' ];

/**
 * This is the default block we'll use for our slide.
 */
export const DEFAULT_BLOCK = 'core/cover';

/**
 * These are the attributes we assign for our DEFAULT_BLOCK.
 */
export const DEFAULT_BLOCK_ATTRIBUTES = {
	align: 'center',
	className: 'swiper-slide',
	contentPosition: 'bottom left',
	customOverlayColor: '#000000',
	dimRatio: 20,
	layout: {
		type: 'constrained',
	},
	style: {
		color: {
			text: '#ffffff',
		},
		elements: {
			heading: {
				color: {
					text: '#ffffff',
				},
			},
			link: {
				color: {
					text: '#ffffff',
				},
			},
		},
		spacing: {
			padding: {
				top: 'var:preset|spacing|large',
				bottom: 'var:preset|spacing|large',
				left: 'var:preset|spacing|large',
				right: 'var:preset|spacing|large',
			},
		},
	},
};

/**
 * These are the default inner blocks we'll use
 * when our DEFAULT_BLOCK is inserted.
 */
export const DEFAULT_INNERBLOCK = 'core/paragraph';

/**
 * These are the attributes we assign for our default
 * inner blocks.
 */
export const DEFAULT_INNERBLOCK_ATTRIBUTES = {
	fontSize: 'large',
	style: {
		color: {
			text: '#ffffff',
		},
	},
};

/**
 * Some default Unsplash images...
 * (feel free to replace)
 */
export const PLACEHOLDER_IMG_1 = 'https://source.unsplash.com/kdl8xDDD6iA';
export const PLACEHOLDER_IMG_2 = 'https://source.unsplash.com/cRUZICCU_Xg';
export const PLACEHOLDER_IMG_3 = 'https://source.unsplash.com/lUF3cqG6n7s';

We’ll reference these in our Slider function-based component.

Are you new to WordPress block development? Perhaps, you’re a seasoned developer and need to brush up on key concepts. Check out Anatomy of a Block.

Define the Slider Edit Function

The edit.js file is where we establish all the state and logic for everything the user will see and interact with in the editor.

Add Imports

First, let’s start by adding all of our @import statements at the top. These include all of our WordPress dependencies, followed by our Slider component (we’ll create this next) and our editor.scss.

At the beginning of our src/edit.js file where we start importing all of our dependencies.

/**
 * WordPress dependencies
 */
import {
	useBlockProps,
	useBlockEditContext,
	InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, PanelRow, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
 * Internal Dependencies
 */
import { Slider } from './slider';
// Editor styling.
import './editor.scss';

Define the Main Edit Function

We’re going to define our primary Edit function next, which you can place right after all your imports in the edit.js file.

We’re defining our main Edit function, which references some components we still need to create but will soon.

/**
 * The edit function describes the structure of your block in the context of the
 * editor. This represents what the editor will render when the block is used.
 *
 * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit
 *
 * @param {Object}   props               Properties passed to the function.
 * @param {Object}   props.attributes    Available block attributes.
 * @param {Function} props.setAttributes Function that updates individual attributes.
 *
 * @return {Element} Element to render.
 */
export default function Edit( { attributes, setAttributes } ) {
	const { autoplay, navigation, pagination } = attributes;
	const { clientId } = useBlockEditContext();
	const blockProps = useBlockProps();

	return (
		<>
			<div { ...blockProps }>
				<Slider attributes={ attributes } clientId={ clientId } />
			</div>

			<InspectorControls>
				<PanelBody title={ __( 'Settings', 'wpe' ) }>
					<PanelRow>
						<ToggleControl
							label={ __( 'Autoplay', 'wpe' ) }
							checked={ autoplay }
							onChange={ ( value ) =>
								setAttributes( { autoplay: value } )
							}
							help={ __(
								'“Autoplay” will automatically advance the slides. Note: this is intentionally disabled in the editor, but will affect the front end.'
							) }
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={ __( 'Navigation', 'wpe' ) }
							checked={ navigation }
							onChange={ ( value ) =>
								setAttributes( { navigation: value } )
							}
							help={ __(
								'“Navigation” will display arrows so user can navigate forward/backward.'
							) }
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={ __( 'Pagination', 'wpe' ) }
							checked={ pagination }
							onChange={ ( value ) =>
								setAttributes( { pagination: value } )
							}
							help={ __(
								'“Pagination” will display dots along the bottom for user to click through slides.'
							) }
						/>
					</PanelRow>
				</PanelBody>
			</InspectorControls>
		</>
	);
}

A lot is happening here; we’ll step through it piece by piece.

In our Edit function, we’re passing down our block’s attributes for navigation, pagination, and autoplay (line 14). The Edit function has a built-in function setAttributes, which allows us to update attribute values conveniently.

We’re using WordPress’s handy PanelBody, PanelRow, and ToggleControl components (near the bottom) to allow users to toggle these features on and off in the editor. Again, we imported these in the previous code snippet placed at the top of the file.

The Slider component will represent the actual markup for our SwiperJS-driven slider. Let’s get that set up next.

Custom Slider Component with useRefEffect

We’re calling the Slider component within our Edit function, which we still need to create. Create a new file called slider.js and place the following code.

Our Slider component uses memoization to cache our sliderRef, which we use to instantiate Swiper.

/**
 * WordPress dependencies
 */
import {
	store as blockEditorStore,
	ButtonBlockAppender,
	useInnerBlocksProps,
} from '@wordpress/block-editor';
import { useRefEffect } from '@wordpress/compose';
import { select, subscribe } from '@wordpress/data';
import { memo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal Dependencies
 */
import { SwiperInit } from './swiper-init';
import {
	ALLOWED_BLOCKS,
	DEFAULT_BLOCK,
	DEFAULT_BLOCK_ATTRIBUTES,
	DEFAULT_INNERBLOCK,
	DEFAULT_INNERBLOCK_ATTRIBUTES,
	PLACEHOLDER_IMG_1,
	PLACEHOLDER_IMG_2,
	PLACEHOLDER_IMG_3,
} from './constants';

/**
 * Slider component.
 */
export const Slider = memo( ( { clientId, attributes } ) => {
	const sliderRef = useRefEffect( ( element ) => {
		const options = {
			...attributes,
			...{
				autoplay: false,
				grabCursor: false,
				simulateTouch: false,
			},
		};

		// Initialize slider.
		let slider = SwiperInit( element, options );

		// Store the current slide order to detect changes, such as adding, removing, or reordering slides.
		let slideOrder = select( blockEditorStore ).getBlockOrder( clientId );

		// Subscribe slider update events like adding, removing, or reordering slides.
		const unsubscribeSliderUpdateListener = subscribe( () => {
			const currentSlidesOrder =
				select( blockEditorStore ).getBlockOrder( clientId );

			// Check if the slider has been changed.
			if ( currentSlidesOrder.toString() !== slideOrder.toString() ) {
				const selectedBlock =
					select( blockEditorStore ).getSelectedBlock();
				const slideAdded =
					currentSlidesOrder.length > slideOrder.length;
				const slideRemoved =
					currentSlidesOrder.length < slideOrder.length;
				const slideMoved =
					currentSlidesOrder.length === slideOrder.length;
				const activeIndex = slider.activeIndex;

				// Store the current slide order before destroying the slider instance.
				slideOrder = currentSlidesOrder;
				slider.destroy();

				window.requestAnimationFrame( () => {
					// Initialize slider.
					slider = SwiperInit( element, options );

					// Determine where the slider should go.
					let slideToIndex = activeIndex;
					if ( slideAdded ) {
						slideToIndex = slideOrder.length;
					} else if ( slideRemoved ) {
						slideToIndex = activeIndex - 1;
					} else if ( slideMoved ) {
						slideToIndex = slideOrder.findIndex(
							( clientId ) => clientId === selectedBlock.clientId // eslint-disable-line no-shadow
						);
					}

					if ( slideToIndex < 0 ) {
						slideToIndex = 0;
					}

					slider.slideTo( slideToIndex, 0 );
				} );
			}
		} );

		return () => {
			unsubscribeSliderUpdateListener();
			slider.destroy();
		};
	} );

	// Our nested innerblocks that will be inserted by default.
	const innerBlocksProps = useInnerBlocksProps(
		{ className: 'swiper-wrapper' },
		{
			allowedBlocks: ALLOWED_BLOCKS,
			defaultBlock: {
				name: DEFAULT_BLOCK,
				attributes: {
					url: `${ PLACEHOLDER_IMG_3 }`,
					...DEFAULT_BLOCK_ATTRIBUTES,
				},
			},
			directInsert: true,
			orientation: 'horizontal',
			template: [
				[
					DEFAULT_BLOCK,
					{
						url: `${ PLACEHOLDER_IMG_1 }`,
						...DEFAULT_BLOCK_ATTRIBUTES,
					},
					[
						[
							DEFAULT_INNERBLOCK,
							{
								placeholder: __( 'Slide title…', 'wpe' ),
								...DEFAULT_INNERBLOCK_ATTRIBUTES,
							},
						],
					],
				],
				[
					DEFAULT_BLOCK,
					{
						url: `${ PLACEHOLDER_IMG_2 }`,
						...DEFAULT_BLOCK_ATTRIBUTES,
					},
					[
						[
							DEFAULT_INNERBLOCK,
							{
								placeholder: __( 'Slide title…', 'wpe' ),
								...DEFAULT_INNERBLOCK_ATTRIBUTES,
							},
						],
					],
				],
			],
			renderAppender: false,
			templateInsertUpdatesSelection: true,
		}
	);

	return (
		<>
			<div className="swiper" ref={ sliderRef }>
				<div { ...innerBlocksProps } />
			</div>

			<ButtonBlockAppender
				className="slider-appender has-icon"
				rootClientId={ clientId }
			/>
		</>
	);
} );

This is a rather large and complex component that encompasses a lot of functionality. So, let’s break it down.

  • Line 5 – We’re utilizing the useRefEffect dependency from the @wordpress/compose package. This is similar to React’s useEffect, but it allows for a callback when a dependency changes.
  • Line 44 – We grab our SwiperJS default object and initialize our slider.
  • Line 21-66 – We’re utilizing @wordpress/data package’s subscribe() listener function to watch for state changes and merge the state with our newly instantiated SwiperJS element. This allows us to tell SwiperJS when new slides are shifted around.
  • Line 95-98 – We’re performing some cleanup for our SwiperJS instance.
  • Line 102-152 – This is where we pass in all of our inner block definitions, which will automatically populate the slider when it is inserted with some core blocks.
  • Finally, we return our components. We have two main components we’re adding here:
    • Our Swiper element, which we assign our ref to and pass down the innerBlockProps with our block template.
    • ButtonBlockAppender – This is another useful component of the @wordpress/block-editor package, which allows us to append a button for users to click and add a slide along the bottom of the Slider.

Creating block template arrays can be tricky. It is usually best to block it all out in the editor, switch to code view, copy and paste the necessary code markup, and utilize the handy WPHTML Converter to convert your markup into JavaScript or PHP.

Define the Slider save Function

We need to establish our saving logic for our overall slider block. Remember, we have set the Edit logic, but we still need to ensure our block’s final markup and properties are saved in the post_content for retrieval and further editing.

We can accomplish this by adding save.js to our slider block and adding the following code.

We need to add our save function to save our final slider block markup to post_content. Create a new file: src/save.js

/**
 * WordPress dependencies
 */
import { InnerBlocks } from '@wordpress/block-editor';

/**
 * The save function defines the way in which the different attributes should
 * be combined into the final markup, which is then serialized by the block
 * editor into `post_content`.
 *
 * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save
 *
 * @return {Element} Element to render.
 */
export default function Save() {
	return <InnerBlocks.Content />;
}

We’re leveraging WordPress’s InnerBlocks component to return our saved slider content. Then we have to make sure we import our saving file within our block’s entry point: src/index.js

Modify the existing src/index.js to import our save function and return it within the registerBlockType() handler.

/**
 * Registers a new block provided a unique name and an object defining its behavior.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
 */
import { registerBlockType } from '@wordpress/blocks';

/**
 * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
 * All files containing `style` keyword are bundled together. The code used
 * gets applied both to the front of your site and to the editor.
 *
 * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
 */
import './style.scss';

/**
 * Internal dependencies
 */
import Edit from './edit';
import save from './save';
import metadata from './block.json';

/**
 * Every block starts by registering a new block type definition.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
 */
registerBlockType( metadata.name, {
	/**
	 * @see ./edit.js
	 */
	edit: Edit,

	/**
	 * @see ./save.js
	 */
	save,
} );

Define the render.php Display Logic

We must establish our final display logic for the block, which visitors will see for the end product. This is all handled within the block’s render.php.

The src/render.php file is where we handle our final display logic.

<?php
/**
 * Slider block
 *
 * @var array     $attributes Block attributes.
 * @var string    $content    Block default content.
 * @var \WP_Block $block      Block instance.
 */

$autoplay   = empty( $attributes['autoplay'] ) ? false : $attributes['autoplay'];
$navigation = empty( $attributes['navigation'] ) ? false : $attributes['navigation'];
$pagination = empty( $attributes['pagination'] ) ? false : $attributes['pagination'];

$swiper_attr = array(
	'autoplay'   => $autoplay,
	'navigation' => $navigation,
	'pagination' => $pagination,
);
$swiper_attr = htmlspecialchars( wp_json_encode( $swiper_attr ) );

$wrapper_attributes = get_block_wrapper_attributes(
	array(
		'class' => 'swiper',
	)
);
?>

<div <?php echo wp_kses_data( $wrapper_attributes ) . 'data-swiper="' . esc_attr( $swiper_attr ) . '"'; ?>>

	<div class="swiper-wrapper">
		<?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
	</div>

</div><!-- .swiper -->

In the code above, we’re checking for our $autoplay, $navigation, $pagination settings and assigning them as data attributes to our final <div> . It is crucial to merge these properties with get_block_wrapper_attributes(), which allows us to pass through the items we added to our slider block.json "supports" definition. If a user wants to alter the alignment or colors of the block, then this is how WordPress will be aware of these final attributes.

Update view.js Front End JavaScript

We will import our handy ./swiper-init again and reuse this within our front-end instantiation code. Open up the src/view.js file and replace it with the following code.

Within the src/view.js is where the front-end JavaScript for our block is established and enqueued within WordPress’s dependency queue.

/**
 * Shared Swiper config.
 */
import { SwiperInit } from './swiper-init';

document.addEventListener( 'DOMContentLoaded', () => {
	const containers = document.querySelectorAll( '.swiper' );

	// Return early, and often.
	if ( ! containers.length ) {
		return;
	}

	// Loop through all sliders and assign Swiper object.
	containers.forEach( ( element ) => {
		// We could pass in some unique options here.
		let options = {};

		try {
			options = JSON.parse( element.dataset.swiper );
		} catch ( e ) {
			// eslint-disable-next-line no-console
			console.error( e );
			return;
		}

		// Slider 🚀
		SwiperInit( element, options );
	} );
} );

Final Build & Next Steps

If you had npm start running in the background the whole time then you should be in an excellent place to test things out. Otherwise, feel free to run npm run build to create a final build of your blocks, open up a new post, add a slider block, and test everything out.

Remember that SwiperJS has a lot of bells and whistles, and we’ve created an opinionated version of a slider. You could keep going further and consider extending things, like:

Check out the final codebase Slider Block GitHub repo. Feel free to download the final plugin or fork the repo and make it all your own.

Conclusion

I hope you found this tutorial helpful and easy to follow. It was quite the journey, and we covered a lot of ground. Please let me know if you hit any hurdles along the way or if you went even further with your Slider block.

Don’t be shy; reach out on Twitter if you want to share or have any questions.

The post Create a Slider Block for WordPress with SwiperJS appeared first on Builders.

]]>
https://wpengine.com/builders/create-a-slider-block/feed/ 0
Theme.json and Standard Font Sizing https://wpengine.com/builders/theme-json-font-sizing/ https://wpengine.com/builders/theme-json-font-sizing/#respond Fri, 01 Dec 2023 14:14:00 +0000 https://builders.wpengine.com/?p=29 Learn how to globally control settings and styles within block themes.

The post Theme.json and Standard Font Sizing appeared first on Builders.

]]>
The theme.json feature, available in block themes for WordPress, offers advanced editor configuration capabilities. This functionality gives theme developers more control over font size, colors, and other design elements when editing posts or pages.

It’s a valuable tool in block themes that enhances customization and flexibility in website design and content management within the WordPress ecosystem. The top-level applies to all blocks that support the relevant setting, but a specific block’s settings could override it by being specific.

A significant benefit of the theme.json file is the consolidation of CSS required in both a theme’s style.css file and its style-editor.css file.

Theme.json in Classic Themes

Marcus Kazmierczak, Engineering team lead at Automattic, recently gave an overview on using theme.json in classic themes.

Rich Tabor published a 3-part series on standardizing how we build the next generation of WordPress block themes to accompany Full Site Editing:

Theme.json in Frost Theme for WordPress

Frost employs a ‘t-shirt sizing’ method for font sizes, a naming convention designed to standardize and simplify font selection. This approach, which aligns with WordPress standards, enhances consistency and ensures future compatibility across different themes and platforms.

Using familiar size labels like ‘small,’ ‘medium,’ ‘large,’ and so on makes it easier for users to choose the appropriate font size while maintaining a cohesive and scalable design framework.

Included in the theme is a range of standard font sizes to cater to various design needs. Here’s a list of those sizes:

{
	"fluid": false,
	"name": "xSmall",
	"size": "16px",
	"slug": "x-small"
},
{
	"fluid": false,
	"name": "Small",
	"size": "18px",
	"slug": "small"
}

Fluid Typography

Fluid typography in WordPress block themes enables fonts to adapt their sizes based on the viewport width. This feature allows text to scale smoothly between set minimum and maximum sizes, ensuring readability and visual consistency across different devices. It maintains legibility and aesthetics on varying screen sizes, from small mobile screens to large desktop displays.

By integrating fluid typography, WordPress block themes enhance the user experience and accessibility. It ensures that text is proportionate and easily readable, regardless of the device used. This adaptability is critical in responsive web design, making websites more user-friendly and visually appealing in a multi-device digital environment.

Here’s an overview of the fluid typography-enabled font sizes available in the Frost WordPress theme:

{
	"fluid": {
		"min": "18px",
		"max": "20px"
	},
	"name": "Medium",
	"size": "20px",
	"slug": "medium"
},
{
	"fluid": {
		"min": "20px",
		"max": "24px"
	},
	"name": "Large",
	"size": "24px",
	"slug": "large"
},
{
	"fluid": {
		"min": "24px",
		"max": "30px"
	},
	"name": "xLarge",
	"size": "30px",
	"slug": "x-large"
},
{
	"fluid": {
		"min": "30px",
		"max": "36px"
	},
	"name": "36px",
	"size": "36px",
	"slug": "max-36"
},
{
	"fluid": {
		"min": "36px",
		"max": "48px"
	},
	"name": "48px",
	"size": "48px",
	"slug": "max-48"
},
{
	"fluid": {
		"min": "42px",
		"max": "60px"
	},
	"name": "60px",
	"size": "60px",
	"slug": "max-60"
},
{
	"fluid": {
		"min": "48px",
		"max": "72px"
	},
	"name": "72px",
	"size": "72px",
	"slug": "max-72"
}

Conclustion

Frost’s adoption of the ‘t-shirt sizing’ method for font sizes represents a significant step forward in web design within the WordPress ecosystem. This approach not only simplifies the design process but also ensures high consistency and future compatibility across various themes and platforms.

Frost sets a new benchmark for intuitive web design by aligning with WordPress standards and focusing on user-friendly naming conventions. As we embrace these innovative approaches, the potential for creating more accessible, visually appealing, and user-centric websites becomes increasingly attainable.

The post Theme.json and Standard Font Sizing appeared first on Builders.

]]>
https://wpengine.com/builders/theme-json-font-sizing/feed/ 0