diff --git a/apps/docs/pages/developer/_meta.js b/apps/docs/pages/developer/_meta.js
index 4004ef6e..e8a52431 100644
--- a/apps/docs/pages/developer/_meta.js
+++ b/apps/docs/pages/developer/_meta.js
@@ -4,4 +4,5 @@ export default {
"saving-manifests": { title: "Saving manifests" },
configuration: { title: "Configuration" },
previews: { title: "Previews" },
+ creators: { title: "Resource creators" },
};
diff --git a/apps/docs/pages/developer/creators.mdx b/apps/docs/pages/developer/creators.mdx
new file mode 100644
index 00000000..831297c8
--- /dev/null
+++ b/apps/docs/pages/developer/creators.mdx
@@ -0,0 +1,161 @@
+# Resource creators
+
+The primary functionality of the Manifest Editor is based around the ability to create, update and delete resources.
+
+A resource creator is made up of a few different parts:
+
+- A form to create the resource, or custom UI
+- An asyncronous function to save the resource
+- Metadata about what it creators, and which fields
+
+A simple example of a resource creator is the `ImageUrlCreator`.
+
+The form is a simple text input, with a URL to an image hosted somewhere - and a button to submit the form.
+
+```tsx
+import { FormEvent } from "react";
+import { InputContainer, InputLabel, Input } from "@manifest-editor/editors";
+import { CreatorContext } from "@manifest-editor/creator-api";
+
+export function CreateImageUrlForm(props: CreatorContext) {
+ const onSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ const data = new FormData(e.target as HTMLFormElement);
+ const formData = Object.fromEntries(data.entries()) as any;
+
+ if (formData.url) {
+ props.runCreate({ url: formData.url });
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+The props passed to this component are provided by the `CreatorContext`, which includes the
+`props.runCreate()` which is also defined by the creator.
+
+This is the creator function. This is used by the Form, but can also be used by _other creators_ so you
+can compose them together to create more complex resources.
+
+```ts
+import { getImageDimensions, getFormat } from "@manifest-editor/shell";
+import { CreatorFunctionContext } from "@manifest-editor/creator-api";
+
+export interface CreateImageUrlPayload {
+ url: string;
+ format?: string;
+ height?: number;
+ width?: number;
+}
+
+export async function createImageUrl(
+ data: CreateImageUrlPayload,
+ ctx: CreatorFunctionContext
+): Promise {
+ if (!data.height || !data.width) {
+ const dimensions = await getImageDimensions(data.url);
+ if (dimensions) {
+ data.height = dimensions.height;
+ data.width = dimensions.width;
+ }
+ }
+
+ return ctx.embed({
+ id: data.url,
+ type: "Image",
+ format: data.format || (await getFormat(data.url)),
+ height: data.height,
+ width: data.width,
+ });
+}
+```
+
+Whatever you return from this function is what will be saved to the parent resource.
+
+You can see what the parent resource is by looking at the `CreatorContext` along with some helpers
+for correctly creating references and linking them together in the Vault:
+
+```ts
+interface CreatorFunctionContext {
+ options: {
+ targetType: string;
+ target?: Reference;
+ parent?: CreatorParent;
+ initialData?: any;
+ };
+ ref(idOrRef: string | Reference): ReferencedResource;
+ embed(data: any): CreatorResource;
+ create(definition: string, payload: any, options?: Partial): Promise;
+ generateId(type: string, parent?: Reference | ReferencedResource): string;
+ getParent(): Reference | undefined;
+ getTarget(): SpecificResource | Reference | undefined;
+ getParentResource(): SpecificResource | undefined;
+ getPreviewVault(): Vault;
+}
+```
+
+This is required because of how the Vault stores resources as flat resources. In most
+cases you want to save embedded resources to the Vault and reference them by an ID and type.
+
+There are some exceptions - such as `service` properties, in which case you can use the `embed()` helper.
+
+There are a lot of examples in the GitHub repository of different ways to use these functions.
+
+These functions are bound together in a `CreatorDefinition`:
+
+```ts
+import { CreatorDefinition } from "@manifest-editor/creator-api";
+
+export const imageUrlCreator: CreatorDefinition = {
+ id: "@manifest-editor/image-url-creator",
+ create: createImageUrl,
+ label: "Image",
+ summary: "Image from a URL",
+ icon: ,
+ render(ctx) {
+ return ;
+ },
+ resourceType: "ContentResource",
+ resourceFields: ["format"],
+ supports: {
+ parentFields: ["logo", "body", "thumbnail"],
+ },
+ staticFields: {
+ type: "Image",
+ },
+};
+```
+
+This definition specifies some components and labels that will be displayed to users when they go
+to use your creator from the UI.
+
+You can add new creators to your `mapApp` function when creating the Editor.
+
+```ts
+import { mapApp } from "@manifest-editor/shell";
+import * as manifestEditorPreset from "@manifest-editor/manifest-preset";
+import { myCustomCreator } from "./my-custom-creator";
+
+const app = mapApp(manifestEditorPreset, (app) => ({
+ ...app,
+ creators: [
+ //
+ ...app.creators,
+ myCustomCreator,
+ ],
+}));
+```
+
+Now in the Manifest editor, when the "create" icon is clicked in a valid context - based on the configuration
+you provided - they will see your icon and be able to click on it to use your Form component
+to create a new resource.