Only this pageAll pages
Powered by GitBook
1 of 16

Local Add-on API

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Designing your Add-on

We've made it easy to powerfully integrate your ideas into Local. This section is all about how to craft the best experience around your ideas. Following our standard UI conventions will help ensure your Add-on feels less like an addition, and more like a true part of Local.

The most important step in the Add-on design process is taking the time to define exactly what your Add-on is going to do. What goal is it trying to achieve? What problem is it trying to solve? Who is it for? Once you know the answers to these questions, you can start creating your Add-on with confidence and clarity!

When designing your add-on, always focus on what a non-technical user would be thinking -- even if your target user is technical! The benefits of not needing to think about how to complete even a seemingly simple task are high and worth the up-front effort.

Getting started with Add-ons

Craft custom Add-ons that push the boundaries of Local and revolutionize your day-to-day workflow!

How to use this guide

With Local Add-ons, you can modify or extend functionality easily with our hooks API. If your Add-on requires UI, you can then use our UI components within these layouts to assemble a fantastic and delightful interface for your add-on. This guide will help ensure an Add-on that works great, looks even better, and fits like a dream inside of Local.

We've broken the guide down into a few quick sections to get you up-and-running in no time.

  1. Designing your Add-on

  2. Building your Add-on

The Local interface

A brief overview of Local's design

Before designing your add-on, it's important to understand how Local is designed, and the context within which your add-on will be used.

Core UI

Local, at it's core, is crafted from a simple idea...that you should easily be able to create and manage your WordPress sites locally. The main navigation separates each core section of the app into tabs at this highest-level. Context deepens inside of these tabs, their specific layouts dependent on their content. This concept is illustrated below.

Broader context to the left, narrower context to the right

This rule of "broader context to the left, narrower context to the right," is extended into the main area, which can include multiple levels of navigation and various layouts based on content.

One example is the "Local Sites" tab, which includes a "sidebar navigation" to select a site, and then a "main" section where content is variable. In the screen below, there is a "hero" area with "subnavigation," a two-column layout inside of the subsection selected in the subnavigation, and a "footer."

Complex layouts

Layouts can easily be more complex to support other types of content. For example, an even deeper-level of sidebar navigation (noted as "tertiary nav") is present within this section of the app.

This pattern is continued sightly differently in the "Connect" tab of Local, where the same "hero" and "subnavigation" are present, but there is no "sidebar navigation."

Key to creating a successful add-on is always thinking about task hierarchy. Are your actions and tasks visually located in the correct place? Is the information clearly communicating its level of importance?

Overlays

Within Local, there is occasionally the need to create a higher-level of context within a specific state. If the main Local interface is the layer of context tied to actions around Local sites, Overlays help achieve a literal layer of separation between these strictly app-level actions, and those actions that interact with external concepts or files.

Full-width Overlay

We often use a full-screen overlay to enter settings for a new item being created within Local. For example, you can see this when selecting "Create New Site." Since this site does not currently exist, this additional level of separation from the application highlights that something new is being created, and focuses the user on that task.

Once the site is created, it shows up in the Local sidebar, and most further actions are tied into the main Local interface.

Drawer Overlay

Somewhat similarly to the full-screen overlay, when an action integrates with an outside service, but is triggered from a site-specific action, the drawer overlay is used. For example, this view is used when pushing or pulling a site to Flywheel. This is because, while you are interacting with your Local site, you're setting preferences and importing from (or exporting to) an outside service.

Note that each overlay provides a clear way to exit without completing the task within. This is key, as an overlay removes ability to interact with the interface below.

Modals

Traditional modality should be largely avoided, except in the few cases when it is appropriate to remove distractions and focus the user on making a decision.

When an additional interaction is required to complete a task, such as selecting where to push or pull a site, or confirming or denying an action, an overlay modal is triggered. These modals are kept as simple as possible, ideally limited to a singular concept required to complete the initial task. Always provide clear ways to both exit the modal, and complete the ask given within them.

Modals should not be used for actions more complex than answering a simple "yes," "no," or simple selection.

Local's standard Sites tab
Local's Sites tab with Notes add-on

UI components

To get you up and running quickly, we've provided a robust set of components for use in your Add-ons!

Visit our styleguidist documentation for the complete library.

Building your Add-on

Local is powered by Electron/Node.js. This means all Add-ons interface with Local using a simple JavaScript API.

With that in mind, don't think Local Add-ons can only be JavaScript. Node.js provides robust APIs such as which enable executing outside binaries and shell scripts.

Also, thanks to the vast ecosystem of modern JavaScript, you can write your Local Add-on in any language that transpiles to ES5, such as ES6, ES7, or .

You can get up and running with your new add-on quickly and easily with the . It is super simple to set up, and can help you start creating your new add-on in no time! It automatically sets up a bare-bones add-on in Local, and shows some helpful tips and examples (many of which are detailed in the following sections) to jumpstart your add-on development.

The Local Add-on API is always growing! If you need a hook to be added for any reason, please let us know in the and we'll be happy to work with you.

Hooks

The core of Local's Add-on API is the Hooks API. This API was heavily inspired by . If you've written WordPress themes or plugins before, you should feel right at home!

Example Add-ons

Curious to see how this works in practice? Check out the Add-ons in the . Each add-on has a link to the Repository where you can review the code or clone it locally to pick apart and improve!

child_process
TypeScript
Local Add-on Generator
Local Community
WordPress' Plugin API
Content Hooks
Filters
Actions
Add-on Library

Add-on Structure

Add-ons extend Local in one of two ways:

  • Events that are registered with the Local UI are added to the "Renderer" entry point.

  • Events that don't register with the Local UI are added to the "Main" entry point.

​Renderer entry point​

The renderer process is what runs inside Local's main window. This is where you'll add buttons, forms, and any other user interface elements.

Most Add-ons will exclusively use the renderer process entry point for the sake of simplicity.

To use this entry point, ensure that a renderer property is defined within the package.json file, which points to the compiled Javascript file.

Local passes a context object to the exported function, which allows you to leverage existing tools that are already shipped with Local.

​Main entry point​

The main process allows you to register and hook into Local functionality that doesn't rely on the Local UI. By tapping into the main process, you can run processes even when the window is closed but Local is still running.

To use this entry point, ensure that a main property is defined within the packge.json file, which points to the compiled Javascript file.

Local passes a context object to the exported function, which allows you to leverage existing tools that are already shipped with Local.

Using local-components

If you need to add UI components to your Local Add-on, we recommend using the @getflywheel/local-components package which is available on npm.

To see what components are available in local-components, check out our styleguide.

local-components utilizes CSS Modules, which allows multiple versions of local-components to be loaded into Local via multiple Add-ons. This allows for your Add-on to look and behave the same even if the user updates Local.

Where should I put my Add-on?

A natural place for Add-ons is right alongside where you manage your current Local sites. Whether you want to add functionality to an existing tab or create a new one just for your Add-on, we've got your back!

Adding-on to existing UI

Does your Add-on expand existing functionality? Do other controls related to its context exist already in Local? For these sorts of Add-ons, you can easily integrate with the existing Local structure!

Examples of Add-ons inside of pre-existing Local context are the Notes and the Xdebug + PhpStorm Add-ons. Notes is located in the "Site overview" tab, because conceptually, it makes sense for it to be front-and-center, on the first screen you encounter, that happens to be focused on managing high-level site details. The Xdebug + PhpStorm Add-on creates quick access to managing certain site settings, so it makes sense for that control to live in “Utilities.”

Creating new UI

Is your Add-on a standalone concept? Does it need a large amount of space or multiple sections of information to be useful? Then a new tab may be the best place. These tabs should be added to the dropdown underneath the "More" tab inside of a Local site.

Examples of existing Add-ons with their own tabs are Stats and Volumes. These Add-ons have deep functionality that also conceptually stand on their own.

When creating a new tab underneath "More," be sure to include the name of your Add-on in the title bar of this new section!

The Notes Add-on adds functionality directly within the right sidebar
The Xdebug + PhpStorm Add-on add's functionality within an existing table view

Common Parameters

site

Plain JavaScript object containing information about the selected site.

Example:

{
    "id": "BeyQOnaNS",
    "localVersion": "3.1.2",
    "container": "c61d2aba97ca2306edd42f03275dae0bbda570abe8d93e9ad2ba2a9a93bea895",
    "phpVersion": "7.2.9",
    "webServer": "nginx",
    "mysqlVersion": "5.7",
    "multiSite": null,
    "mysql": {
      "database": "local",
      "user": "root",
      "password": "root"
    },
    "ports": {
      "HTTP": 4049,
      "HTTPS": 4050,
      "MYSQL": 4051,
      "MAILCATCHER": 4052
    },
    "flywheelConnect": "flywheel-site-hash",
    "environment": "flywheel",
    "workspace": null,
    "name": "Example Site",
    "path": "/Users/username/Local Sites/example-site",
    "domain": "example-site.local",
    "environmentVersion": "1.3.2",
    "sslSHA1": "14f0808d7312bf4031ee198aec75cbd20b8aee2d"
  }

siteStatus

String containing one of the following values:

Common statuses

  • adding

  • cloning

  • copying

  • deleting

  • exporting

  • exporting-db

  • halted

  • provisioning

  • running

  • saving

  • stopping

Error statuses

  • container-missing

  • provisioning-error

  • wordpress-install-error

Pulling

  • pulling-downloading-backup

  • pulling-importing-backup

  • pulling-requesting-backup

Pushing

  • pushing-packing

  • pushing-processing

  • pushing-uploading

Context API

context.environment

Includes the following properties:

  • appPath: Path to running Local by Flywheel.app

  • userHome: Path to current user home directory

  • phpVersion: Available PHP versions in Local

  • version: Local version

  • dockerPath: Path to Docker binary in Local by Flywheel.app

  • userDataPath: Path to Local's user data folder. On macOS this defaults to ~/Library/Application Support/Local by Flywheel

context.hooks

Use context.hooks to run actions at certain times. Hooks are especially useful when adding in element with context.React.

context.electron

Exposes . The available methods and classes will differ based on whether or not this is ran from the main process or renderer process.

With the Electron API you can create dialogs, open new windows, and more.

context.fileSystem

​ npm package, which extends the native in Node.js.

context.fileSystemJetpack

​ npm package. Some may prefer it over the native in Node.js.

context.notifier

​ npm package. The main method in this class is notify.

Example

context.process

Node.js object.

context.React

Renderer Only

context.React required to use JSX in your renderer entry point. You can also use context.React to access React's .

context.ReactRouter

  • Link -

  • NavLink -

  • Route -

  • Router -

Deprecated

The following contexts have been deprecated in the most recent versions of Local.

context.request

​ npm package, which makes it very easy to send HTTP/HTTPS requests.

The request package was deprecated on Feb 11th 2020, and removed from Local in the [v8.0.0]() release.

context.docker

Use context.docker to run Docker commands.

Example

context.notifier.notify({  
    title: 'Notification Title',  
    message: 'This is an example notification message.',
});
context.docker(`start ${container}`).then(stdout => {    
    //Success
}).catch(({stdout, stderr}) => {    
    //Something bad happened
});
Electron API
fs-extra
fs API
fs-jetpack
fs API
node-notifier
process
Top-Level API
https://reactrouter.com/web/api/Link
https://reactrouter.com/web/api/NavLink
https://reactrouter.com/web/api/Route
https://reactrouter.com/web/api/Router
request
https://community.localwp.com/t/local-beta-8-0-0/39385

Interaction & layout

Interaction

Once you know what your Add-on is going to do and where it makes the most sense within the app, you can start to think about how the user will interact with it.

Local is built on the premise of making complicated tasks simple. To achieve this, we work hard to design and build consistent and clear UI focused on achieving goals. To help you create these sorts of interactions, alongside this guide, we've provided various layout and UI components to help build your interface.

Before you even start building, think about which layout best fits your Add-on's goals, which components make the most sense for the goals your user is trying to achieve, and how you can maximize these early decisions to set your Add-on's audience up for success. When in doubt, opt for clarity over complexity!

Before opening your code editor, break out a pencil and paper to quickly sketch your Add-on's UI. It may seem tedious, but this clarity-of-thought will come through in the end!

The "Sites" tab

Your Add-on can integrate with any part of Local, however, the most popular way will be adding on to the "Sites" tab. There are three core layouts within this section that you can use to build your Add-on's interface.

Full-width layout

Full-width layouts are great for disseminating lots of information, containing multiple sections of content, or for rich interactive experiences. Tables, for example, work really well in this layout, as it has the most horizontal space available for columns of information.

Left column layout

Left columns are generally reserved for tabbed navigation.

Right column layout

Whether you're creating an experience inside of its own tab of the site or expanding an existing tab, the right column is great for creating a secondary level of content hierarchy. Due to its relative size, the larger left side will always appear as more important hierarchically, so use a right sidebar for secondary or supplementary content.

The title bar

Regardless of where your Add-on lives, it's important to create context around its functionality. Add-ons located inside of existing views, such as the table view, are naturally more likely to communicate their presence by default. For other situations, such as when placing an Add-on inside a right sidebar column or on its own tab, always use the title bar. This helps not only remind the user what functionality they're seeing, but in the case of an Add-on inside a new tab, it becomes a useful way-finding device.

A title bar in a right column layout

Responsiveness

Local is responsive, to a point. These are the current interface dimensions.

Default

Minimum

Width

1200

1000

Height

800

650 (Mac)

620 (Windows)

Content Hooks

Content Hooks allow you to inject React components into various location in Local's UI. With Content Hooks you can do anything from adding additional buttons, table rows, or even new pages and routes.

Available Content Hooks

Examples

Adding a React Component

Content Hooks are added to an array of React Components. This means all components need to have a key prop.

Adding a Route

Environment Content Hooks

The following Content Hooks are meant to be used by Environments such as the Preferred and Custom Environment.

ENV_ID and ENV_VERSION are for illustrative purposes only and should be replaced with a static value or variable.

ENV_ID and ENV_VERSION are in the hook name to allow for multiple Environments to be loaded at the same time and to cut down on the number of conditionals required.

Hook Name

Parameters

stylesheets

SiteInfoDatabase

site

SiteInfoDatabase_TableList

site

SiteInfoDatabase_TableList_TableListRow[Connect]:Before

site

SiteInfoOverview

site

SiteInfoOverview:Before

site

SiteInfoOverview_TableList

site

SiteInfoOverview_TableList:Before

site

SiteInfo_TabNav_Items

site

SiteInfo_Top_TopRight

site siteStatus

SitesSidebar_SitesSidebarSites:Before

SitesSidebar_SitesSidebarSites

SiteInfoSSL

site

SiteInfoSSL_TableList

site

SiteInfoUtilities

site

SiteInfoUtilities_TableList

site

routesRoot

Accepts React Router <Route /> components

routes[main]

Accepts React Router <Route /> components

routes[site-info]

Accepts React Router <Route /> components

hooks.addContent('SiteInfoOverview', (site) => {
    return (
        <Notes key="notes" site={site} />
    );
});
hooks.addContent('routesSiteInfo', () => {
    return (
        <Route 
            key="site-info-stats" 
            path="/site-info/:siteID/stats" 
            component={SiteInfoStats}
        />
    );
});

Hook Name

NewSiteEnvironment[ENV_ID][ENV_VERSION]_AddSiteContent_Inner

SaveBlueprintPage[ENV_ID][ENV_VERSION]_SettingsPane_TableList

SettingsNewSiteDefaults[ENV_ID][ENV_VERSION]

SiteInfoEnvironmentMySQL[ENV_ID][ENV_VERSION]

SiteInfoEnvironmentPHP[ENV_ID:ENV_VERSION]

SiteInfoEnvironmentWebServer[ENV_ID][ENV_VERSION]

(source)
(source)

Filters

Filters enable easily modifying data that is passed around in Local. Much like WordPress' filters, all filters in Local must return a value.

Available Filters

Filter Name

appMenu

Description

Allows you to add or modify items in the main app menu (top bar menu)

Callback Function Parameters

  • macOS Menu Template

  • Current Platform provided by process.platform

function(menu: MenuTemplate[], platform: string): MenuTemplate[];

_____________________________________________________________

Filter Name

siteInfoMoreMenu

Description

Allows you to add menu items to the Site Info More menu inside of a single site view.

Callback Function Parameters

  • menuItems

  • currentSite

function(menuItems: SiteInfoMoreMenuItem[], site: Site): SiteInfoMoreMenuItem[];

Example

hooks.addFilter('siteInfoMoreMenu', function (menu, site) {

	menu.push({
		label: 'Volumes',
		enabled: !this.context.router.isActive(`/site-info/${site.id}/volumes`),
		click: () => {
			context.events.send('goToRoute', `/site-info/${site.id}/volumes`);
		},
	});

	return menu;

});

_____________________________________________________________

Filter Name

preferencesMenuItems

Description

Add-ons have the ability to add items to Local's preferences section by using the preferencesMenuItems filter hook. This filter will add a new item to the left hand column of all Preferences views and allow add-ons to add their own content section.

An AddonSettingsItem object is given to Local via the preferencesMenuItems hook (see usage examples below). This can then render an array of PreferencesSection's or any React component. The latter is provided mainly as an escape hatch in the case that an add-on needs more flexibility. Each PreferencesSection then provides an optional header and a single or array of MenuContentRowItem(s).

Props passed to <Row /> or <Override /> components

{
	// React Router props (types are not accurately documented here and developers should reference the React Router documentation for more details)
	history: any;
	location: any;
	match: any;
	params: any;

	// controls whether or not the "apply" button (rendered by Local) is enabled (clickable) or disabled (not clickable)
	setApplyButtonDisabled: (isDisabled: boolean) => void;

	// various other props may be passed via Local but are not an offically supported component of this API as of the time of writing
}

Callback Function Parameters

  • preferencesMenu

function(preferencesItem: PreferencesItem[]): PreferencesItem[];

Examples

"Standard" example passing in sections and sub-headers

const preferenceItem: AddonSettingsItem = {
	path: 'image-optimizer',
	displayName: 'Image Optimizer',
	sections: [
		{
			// subHeader is optional and if omitted, no subHeader will be rendered
			subHeader: 'header 1',
			rows: [
				{
					// this defines what string will be rendered in the left hand "column" of a row
					name: 'line 1',
					// this defines what will be rendered in the right hand "column" of a row. It can be any React component.
					component: Preferences,
				},
				// ...add some more rows if you want
			],
		},
		// ...add some more sections if you want
	],
	onApply: () => {
		console.log('changes applied!')
	},
	componentProps: {
		// This optional object can be any arbitrary props to get passed to your row components (or Override component)
	},
};

hooks.addFilter(
	'preferencesMenuItems',
	(menu) => {
		menu.push(preferenceItem);

		// remember to return the menu array here! If you don't Local won't be able to render your Preference item
		return menu;
	}
);

Overriding Sections with a single React Component (allows you to render arbitrary things in the "content area")

const preferenceItem = {
	path: 'image-optimizer',
	displayName: 'Image Optimizer',
	// this can also just be any old React component. It will be rendered in the Preferences content area
	sections: (props) => <div>{props.title}<div/>,
	onApply: () => {
		console.log('changes applied!')
	},
	componentProps: {
		/* Arbitrary props that will get passed to your row components or section override component. */
		/* here we are passing it "title" which will get passed to the "sections" component */
		title: 'Preferences Title',
	},
}

hooks.addFilter(
	'preferencesMenuItems',
	(menu) => {
		menu.push(preferenceItem);
 
		return menu;
	};
);

Environment Filters

ENV_ID and ENV_VERSION are for illustrative purposes only and should be replaced with a static value or variable.

Filter Name

Parameters

importBlueprintSiteSettings:ENV_ID:ENV_VERSION

Actions

Available Actions

Action Name

Parameters

changeSiteDomain

oldDomain (string)

newDomain (string)

wordPressInstaller:standardInstall

wordPressInstaller:import

wordPressInstaller:import:includesWP

siteStarted

processes

siteStopped

siteCloned

siteAdded

siteDeleted

Site id

changeEnvironment:before

Plain object containing:

  • environment

  • webServer

  • phpVersion

  • mysqlVersion

  • clone (boolean)

searchReplaceWPDatabase

  • site

  • oldDomain

  • newDomain

  • subSiteURLs

Unlike Content Hooks and Filters, Actions do not need to return a value.

Example

hooks.addAction('siteAdded', function (site) {
	console.log('New site added!', site);
});
site
site
site
site
site
site
site
site
site
site