diff --git a/markdown/dev/guides/plugins/conditionally-loading-build-time-plugins/en.md b/markdown/dev/guides/plugins/conditionally-loading-build-time-plugins/en.md
deleted file mode 100644
index 5983d8abbd7..00000000000
--- a/markdown/dev/guides/plugins/conditionally-loading-build-time-plugins/en.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: Conditionally loading build-time plugins
-order: 30
----
-
-You can choose to load your build-time plugin conditionally based on run-time data.
-
-To do so, you need to create a `condition` method that will determine whether the
-plugin will be loaded. This method receives the complete settings object and should
-return `true` if the plugin is to be loaded, and `false` if it should not be loaded.
-
-```js
-const condition = settings => {
- if (settings) {
- // Remember, settings contains:
- // settings.options => The user's options
- // settings.measurements => The measurements
- return true // Load the plugin
- }
- else return false // Do not load the plugin
-}
-```
-
-You pass your plugin and condition method as a third parameter to the Design constructor
-with the `plugin` and `condition` keys respectively.
-
-Let's look at a complete example to illustrate this:
-
-```js
-import freesewing from '@freesewing/core'
-import plugins from '@freesewing/plugin-bundle'
-import myConditionalPlugin from '@freesewing/plugin-bust'
-
-const myConditionalPluginCheck = (settings = false) =>
- settings &&
- settings.options &&
- settings.options.draftForHighBust &&
- settings.measurements.highBust
- ? true
- : false
-
-const Pattern = new freesewing.Design(
- config,
- plugins,
- {
- plugin: myConditionalPlugin,
- condition: myConditionalPluginCheck
- }
-)
-```
-
-Our condition method will return `true` only if the following conditions are met:
-
-- A `settings` object is passed into the method
-- `settings.options` is _truthy_
-- `settings.options.draftForHighBust` is _truthy_
-- `settings.options.measurements.highBust` is _truthy_
-
-This is a real-world example from our Teagan pattern. A t-shirt pattern that can be
-drafted to the high bust (rather than the full chest circumference) if the user
-choses so.
-
-But that feat is handled auto-magically by `plugin-bust` which is a build-time plugin.
-So whether to load this plugin or not hinges on the user settings, which is why we
-load this plugin conditionally.
diff --git a/markdown/dev/guides/plugins/en.md b/markdown/dev/guides/plugins/en.md
index eed12613d62..523adc42ee0 100644
--- a/markdown/dev/guides/plugins/en.md
+++ b/markdown/dev/guides/plugins/en.md
@@ -1,25 +1,23 @@
---
title: Plugin guide
-order: 400
-icons:
- - logo
- - plugin
-for: developers
-about: |
- This guide shows you everything you need to know to understand plugins in FreeSewing, and create your own.
-goals:
- - Know about build-time plugins vs run-time plugins
- - Understanding plugin structure
- - Hooks and how to use them
- - Using hooks without a plugin
- - Using macros
---
-Plugins allow you to extend FreeSewing.
+Plugins allow you to extend FreeSewing with new features and functionality.
+A FreeSewing plugin can extend FreeSewing in 3 different ways:
-We have [a list of available plugins](/reference/plugins/), but
+- It can [provide macros](/guides/plugins/macros), which are a way to automate a number of steps into a
+ single command.
+- It can [hook into the pattern](/guides/plugins/hooks), which allows you to manipulate the pattern or
+ interact with it at various stages of it's lifecycle.
+- It can [provide store methods](/guides/plugins/store), which allows you to add new ways to handle data
+ in the pattern, including providing a custom logger.
+
+We have [a list of plugins](/reference/plugins/) that we maintain, but
if you can't find what you're looking for, you can write your own plugin.
-We'll cover the following topics in this guide:
+If you plan on doing that, or if you would like to understand how plugins work,
+this guide is for you.
+
+We'll cover the following topics:
diff --git a/markdown/dev/guides/plugins/hooks/en.md b/markdown/dev/guides/plugins/hooks/en.md
index 9402e60ddd1..4d311b1701f 100644
--- a/markdown/dev/guides/plugins/hooks/en.md
+++ b/markdown/dev/guides/plugins/hooks/en.md
@@ -1,20 +1,53 @@
---
-title: Hooks
-order: 60
+title: Lifecycle hook methods
+order: 110
---
-A **hook** is a lifecycle event. The available hooks are:
+FreeSewing plugins can provide hooks, which is a way to hook into the pattern's
+lifecycle.
-- [preRender](/reference/hooks/prerender/): Called at the start of [`Pattern.render()`](/reference/api/pattern/render)
-- [postRender](/reference/hooks/postrender/): Called at the end of [`Pattern.render()`](/reference/api/pattern/render)
-- [insertText](/reference/hooks/inserttext/): Called when inserting text
-- [preDraft](/reference/hooks/predraft/): Called at the start of [`Pattern.draft()`](/reference/api/pattern/draft)
-- [postDraft](/reference/hooks/postdraft/): Called at the end of [`Pattern.draft()`](/reference/api/pattern/draft)
-- [preSample](/reference/hooks/presample/): Called at the start of [`Pattern.sample()`](/reference/api/pattern/sample)
-- [postSample](/reference/hooks/postsample/): Called at the end of [`Pattern.sample()`](/reference/api/pattern/sample)
+## Signature
-You can register a method for a hook. When the hook is triggered, your method will be
-called. It will receive two parameters:
+To provide one or more hooks, your plugin should have a `hooks` property that
+is an object where the keys are the lifecycle hook name, and the value holds a
+method. When the lifecycle hook is triggered, your method will be called.
-- An object relevant to the hook. See the [hooks API reference](/reference/hooks/) for details.
+```mjs
+const myPlugin = {
+ name: 'example',
+ version: '0.0.1',
+ hooks: {
+ hookName: function (obj, data = {}) {
+ }
+ }
+}
+```
+
+If you want to attach multiple methods to the same lifecycle hook, you can pass
+them as an array:
+
+```mjs
+const myPlugin = {
+ name: 'example',
+ version: '0.0.1',
+ hooks: {
+ hookName: [
+ function one (obj, data = {}) { },
+ function two (obj, data = {}) { }
+ ]
+ }
+}
+```
+
+## Arguments
+
+All lifecycle methods will receive two parameters:
+
+- An object relevant to the lifecycle hook. See the [hooks API reference](/reference/hooks/) for details.
- Data passed when the hook was registered (optional)
+
+## Notes
+
+Refer to the [hooks API reference](/reference/hooks/) for a list of all
+available lifecycle hooks.
+
diff --git a/markdown/dev/guides/plugins/loading-build-time-plugins/en.md b/markdown/dev/guides/plugins/loading-build-time-plugins/en.md
deleted file mode 100644
index 72417d16fc2..00000000000
--- a/markdown/dev/guides/plugins/loading-build-time-plugins/en.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-title: Loading build-time plugins
-order: 20
----
-
-Build-time plugins are loaded at build time, by passing them to
-the [`freesewing.Design`](/reference/api/design) constructor:
-
-```js
-import freesewing from "@freesewing/core"
-import plugins from "@freesewing/plugin-bundle"
-import config from "../config"
-
-const Pattern = new freesewing.Design(config, plugins)
-```
-
-If you have multiple plugins to load, you can pass them as an array:
-
-```js
-import freesewing from "@freesewing/core"
-import plugins from "@freesewing/plugin-bundle"
-import gorePlugin from "@freesewing/plugin-gore"
-import config from "../config"
-
-const Pattern = new freesewing.Design(config, [plugins, gorePlugin] )
-```
diff --git a/markdown/dev/guides/plugins/loading-run-time-plugins/en.md b/markdown/dev/guides/plugins/loading-run-time-plugins/en.md
deleted file mode 100644
index 866831426ee..00000000000
--- a/markdown/dev/guides/plugins/loading-run-time-plugins/en.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: Loading run-time plugins
-order: 40
----
-
-Run-time plugin are loaded at run time, by passing them to the `use` method of
-an instatiated pattern. That method is chainable, so if you have multiple plugins
-you can just chain them together:
-
-```js
-import Aaron from "@freesewing/aaron";
-import theme from "@freesewing/plugin-theme";
-import i18n from "@freesewing/plugin-i18n";
-
-const myAaron = new Aaron()
- .use(theme)
- .use(i18n)
-```
-
-
-
-Plugins that use only hooks are typically run-time plugins
-
-
diff --git a/markdown/dev/guides/plugins/loading/en.md b/markdown/dev/guides/plugins/loading/en.md
new file mode 100644
index 00000000000..b6fb025af73
--- /dev/null
+++ b/markdown/dev/guides/plugins/loading/en.md
@@ -0,0 +1,12 @@
+---
+title: Loading plugins
+order: 140
+---
+
+Plugins can be loaded at build time and added to the desig. Or at run time and added to an instantiated pattern.
+
+To load a plugin at build time, it should be added to [the `plugins` key of the part configuration](/reference/api/part/config/plugins).
+
+To load a plugin at run time, it should be loaded with a call to [`Pattern.use()`](/reference/api/pattern/use).
+
+Please refer to the relevant documentation for more details.
diff --git a/markdown/dev/guides/plugins/macros/en.md b/markdown/dev/guides/plugins/macros/en.md
index f764262f43f..8f742f5757c 100644
--- a/markdown/dev/guides/plugins/macros/en.md
+++ b/markdown/dev/guides/plugins/macros/en.md
@@ -1,58 +1,37 @@
---
-title: Macros
-order: 90
+title: Macro methods
+order: 120
---
-Plugin structure for macros is similar, with a few changes:
+FreeSewing plugins can provide macros, which is a way to automate multiple
+steps into a single command.
-- Rather than the hook name, you provide the macro name (that you choose yourself)
-- The context (`this`) of a macro method is **always** a [Part](/reference/api/part) object.
+## Signature
-Apart from these, the structure is very similar:
+To provide one or more macros, your plugin should have a `macros` property that
+is an object where the keys are the macro name, and the value holds a method to
+run when the macro is executed.
-```js
-import {name, version} from '../package.json';
-
-export default {
- name,
- version,
+```mjs
+const myPlugin = {
+ name: 'example',
+ version: '0.0.1',
macros: {
- box: function(so) {
- this.points.boxTopLeft = so.anchor;
- this.points.boxTopRight = so.anchor.shift(0, so.size);
- this.points.boxBottomRight = this.points.boxTopRight.shift(-90, so.size);
- this.points.boxBottomLeft = new this.Point(so.anchor.x, this.points.boxBottomRight.y);
-
- this.paths.box = new this.Path()
- .move(this.points.boxTopLeft)
- .line(this.points.boxTopRight)
- .line(this.points.boxBottomRight)
- .line(this.points.boxBottomLeft)
- .close()
- .attr('class', 'box');
- }
- }
+ example: function(so, { log }) {
+ log.info('Running the example macro')
+ }
+ }
}
```
-Did you figure out what this plugin does?
-It provides a `box` macro that draws a box on our pattern in a given location with a give size.
+## Arguments
-We can use it like this:
+All macros receive two arguments:
-```js
-points.boxAnchor = new Point(100, 100);
-macro('box', {
- anchor: points.boxAnchor
- size: 25
-});
-```
-
-Obviously, you can expect to learn how to call a macro in its documentation,
-rather than have to comb through its code.
+- `so`: A plain object holding configuration object passed to the macro
+- `props`: The same object as passed to the Part.draft()` method that you can destructure
-
###### Macros take only 1 argument
When writing a macro, keep in mind that all information that needs to be passed
@@ -61,3 +40,7 @@ to a macro needs to be contained in a single argument.
Typically, you use a single plain object to configure the macro.
+
+## Return value
+
+Macros do not need to return anything. If they do, it will be ignored.
diff --git a/markdown/dev/guides/plugins/plugin-structure/en.md b/markdown/dev/guides/plugins/plugin-structure/en.md
deleted file mode 100644
index f19f4bb5676..00000000000
--- a/markdown/dev/guides/plugins/plugin-structure/en.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: Plugin structure
-order: 50
----
-
-Plugins can do two things:
-
-- They can use hooks
-- They can provide macros
-
-Your plugin should export an object with the following structure:
-
-```js
-{
- name: 'myPlugin',
- version: '1.0.0',
- hooks: {},
- macros: {}
-};
-```
-
-The `name` and `version` attributes are self-explanatory.
-The [hooks](/guides/plugins/hooks/) and [macros](/guides/plugins/macros/) sections
-explain the `hooks` and `macros` properties.
diff --git a/markdown/dev/guides/plugins/store/en.md b/markdown/dev/guides/plugins/store/en.md
new file mode 100644
index 00000000000..a17c345d84b
--- /dev/null
+++ b/markdown/dev/guides/plugins/store/en.md
@@ -0,0 +1,56 @@
+---
+title: Store methods
+order: 130
+---
+
+FreeSewing plugins can provide store methods, which facilitate data handling
+within a pattern.
+
+## Signature
+
+To provide one or more store methods, your plugin should have a `macros` property that
+is an array where each member is itself an array with two members:
+
+- The first member holds the key to attach the method to (in dot notation)
+- The second member holds the method to attach
+
+```mjs
+const myPlugin = {
+ name: 'example',
+ version: '0.0.1',
+ store: [
+ [
+ 'log.panic',
+ function(store, ...params) {
+ store.setIfUnset('logs.panic', new Array())
+ store.push(...params)
+ }
+ ]
+ }
+}
+```
+
+## Arguments
+
+All store methods receive at least two arguments:
+
+- `store`: The store object itself
+- `...params`: All additional plugins that were passed to the store method
+
+## Overwriting store methods
+
+You are allowed to overwrite existing store methods.
+As it happens, this is how you should implement a custom logging solution, but overwriting the logging methods under the store's `log` key,
+
+However, the following methods cannot be overwritten:
+
+- `extend`
+- `get`
+- `push`
+- `set`
+- `setIfUnset`
+- `unset`
+
+## Return value
+
+Store methods do not need to return anything. If they do, it will be ignored.
diff --git a/markdown/dev/guides/plugins/structure/en.md b/markdown/dev/guides/plugins/structure/en.md
new file mode 100644
index 00000000000..86df5d753e7
--- /dev/null
+++ b/markdown/dev/guides/plugins/structure/en.md
@@ -0,0 +1,25 @@
+---
+title: Plugin structure
+order: 100
+---
+
+A FreeSewing plugin is a plain object with the following structure:
+
+```mjs
+Object plugin = {
+ String name,
+ String version,
+ Object hooks,
+ Object macros,
+ Array store,
+}
+```
+
+A plugin **must** have the `name` and `version` properties.
+The other properties are optional, and they map to the three different functionalities macros can provide:
+
+- `hooks`: Holds an object with lifecycle hooks the plugin wants to hook into
+- `macros`: Holds and object with macros the plugin provides
+- `store`: Holds and Array with store methods the plugin provides.
+
+Click on the links above for more details on the structure of these properties.
diff --git a/markdown/dev/guides/plugins/types-of-plugins/en.md b/markdown/dev/guides/plugins/types-of-plugins/en.md
deleted file mode 100644
index 3a500ebd83a..00000000000
--- a/markdown/dev/guides/plugins/types-of-plugins/en.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: Types of plugins
-order: 10
----
-
-Plugins come in two flavours:
-
-- [Build-time plugins](#build-time-plugins)
-- [Run-time plugins](#run-time-plugins)
-
-When writing a plugin, ask yourself whether it's a run-time or a build-time plugin.
-And if the answer is both, please split them into two plugins.
-
-## Build-time plugins
-
-A plugin is a build-time plugin if it is required by the pattern at build-time.
-In other words, the plugin is a dependency for the pattern, and if it's missing
-the pattern won't load.
-
-
-
-Our [plugin bundle](/reference/plugins/bundle/) bundles build-time plugins that are used in many patterns.
-
-
-
-Plugins that provide a macro are typically build-time plugins
-
-## Run-time plugins
-
-A plugin is a run-time plugin if it can be added after instantiating your pattern.
-Think of it as a plugin to be used in the front-end.
-
-Run-time plugins are not a dependecy of the pattern. They just _add something_ to it.
-
-Our [theme plugin](/reference/plugins/theme/) is a good example of a run-time plugin.
-If it's missing, your pattern will still work, it just won't look pretty.
diff --git a/markdown/dev/guides/plugins/using-hooks-more-than-once/en.md b/markdown/dev/guides/plugins/using-hooks-more-than-once/en.md
deleted file mode 100644
index 2edc8d266c8..00000000000
--- a/markdown/dev/guides/plugins/using-hooks-more-than-once/en.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-title: Using hooks more than once
-order: 80
----
-
-What if you want to attach more than one method to a hook?
-You could spread them over separate plugins, but there's a better way.
-
-Rather than assigning a method to your hook, assign an array of methods like this:
-
-```js
-import myCoolMethod from './method-a';
-import myEvenCoolerMethod from './method-b';
-import {name, version} from '../package.json';
-
-export default {
- name,
- version,
- hooks: {
- preRender: [
- myCoolMethod,
- myEvenCoolerMethod
- ]
- }
-}
-```
diff --git a/markdown/dev/guides/plugins/using-hooks-without-plugin/en.md b/markdown/dev/guides/plugins/using-hooks-without-plugin/en.md
deleted file mode 100644
index bdd452a33a1..00000000000
--- a/markdown/dev/guides/plugins/using-hooks-without-plugin/en.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: Using hooks without a plugin
-order: 85
----
-
-You can attach a method to a hook at run-time without the need for a plugin
-using the [Pattern.on()](/reference/api/pattern/on) method.
-
-The method takes the hook name as its first argument, and the hook method as its second.
-
-Below is an example:
-
-```js
-pattern.on('preRender', function(svg) {
- svg.style += "svg { background: yellow;}";
-});
-```
-
-Congratulations, you've just made your pattern yellow.
diff --git a/markdown/dev/guides/plugins/using-hooks/en.md b/markdown/dev/guides/plugins/using-hooks/en.md
deleted file mode 100644
index e0539f50e13..00000000000
--- a/markdown/dev/guides/plugins/using-hooks/en.md
+++ /dev/null
@@ -1,58 +0,0 @@
----
-title: Using hooks
-order: 70
----
-
-For each hook, your plugin should provide a method that takes the relevant data
-as its first argument. If data was passed when the hook was loaded, you will receive
-that as the second object.
-
-Remember that:
-
-- The `insertText` hook will receive a locale and string and you should return a string.
-- All other hooks receive an object. You don't need to return anything, but rather modify the object you receive.
-
-Let's look at an example:
-
-```js
-import myStyle from './style';
-import myDefs from './defs';
-import {name, version} from '../package.json';
-
-export default {
- name,
- version,
- hooks: {
- preRender: function(svg) {
- if (svg.attributes.get("freesewing:plugin-"+name) === false) {
- svg.style += myStyle;
- svg.defs += myDefs;
- svg.attributes.add("freesewing:plugin-"+name, version);
- }
- },
- insertText: function(text) {
- return text.toUpperCase();
- }
- }
-}
-```
-
-This is a complete plugin, ready to be published on NPM. It uses two hooks:
-
-- `preRender` : We add some style and defs to our SVG
-- `insertText` : We transfer all text to UPPERCASE
-
-
-
-###### Note that we avoid running our hook twice
-
-As you can see, the last thing we do in the `preRender` hook is set an attribute on
-the SVG tag with the name and version of our plugin.
-
-We check for this attribute when the `preRender` hook runs, thereby avoiding that
-our styles and defs will be added twice.
-
-It is good practice to wrap you hook methods in a call like this, because you have
-no guarantee the user won't render your pattern more than once.
-
-
diff --git a/markdown/dev/guides/v3/migration/en.md b/markdown/dev/guides/v3/migration/en.md
index da503610d0a..9abdfb01344 100644
--- a/markdown/dev/guides/v3/migration/en.md
+++ b/markdown/dev/guides/v3/migration/en.md
@@ -47,6 +47,24 @@ The following packages have been removed in v3:
- **@freesewing/plugin-export-dxf**: DXF is kinda garbage, you deserve better
- **@freesewing/plugin-validate**
+### API changes
+
+#### Use log instead of raise
+
+The `raise` object that held methods for logging has been replaced by log:
+
+```mjs
+// strikeout-start
+raise.warning('This raise object no longer exists')
+// strikeout-end
+// highlight-start
+raise.info('Use the log object instead')
+// highlight-end
+```
+
+Note that `log` can be destructured in your draft method.
+Refer to [the `Store.log` documentation](/reference/api/store/log) for all details.
+
## Migrating designs
### Design configuration