/!\ this is a feature preview that needs to be activated on your project beforehand
UI extensions allow you to replace components in the content editor by your own self-hosted app. This app is displayed in an iframe and communicates with the content editor using a javascript SDK.
An extension is "just a webapp":
- you can use any language and framework to build it (eventually, as the current SDK version is for React)
- host it on your own domain
- build flows requiring server-side treatment, like authenticating with another 3rd party platform
- etc..
import { useState } from "react";
import ReactDOM from "react-dom";
const MyField = () => {
const [value, onChange] = useState(); // we want to sync this with GraphCMS later
return <MyComplexInputComponent value={value} onChange={onChange} />;
};
ReactDOM.renderToString(<MyField />, "#app");
import { useState } from "react";
import ReactDOM from "react-dom";
import { Wrapper, useUiExtension } from "@grapcms/uix-react-sdk";
import type {
ExtensionDeclaration,
FieldExtensionType,
} from "@grapcms/uix-react-sdk";
const Extension = () => {
// When displaying the extensions, GraphCMS will add a unique identifier 'extensionUid' to the url
const queryParams = new URLSearchParams(window.location.search);
const uid = queryParams.get("extensionUid");
// We need a quick declaration of what this extension is about
const declaration = {
extensionType: "field", // we extend a form field
fieldType: FieldExtensionType.STRING, // that handles string values
name: "My own super field",
};
return (
<Wrapper uid={uid} declaration={declaration}>
<MyField />
</Wrapper>
);
};
const MyField = () => {
// const [value, onChange] = useState();
const { value, onChange } = useUiExtension(); // and we use the sdk hook to connect state
return <MyComplexInputComponent value={value} onChange={onChange} />;
};
ReactDOM.renderToString(<MyField />, "#app");
And that's it !
/!\ this is a feature preview that needs to be activated on your project beforehand
In the api playground, select the management api.
mutation useExtensions($fieldId: ID!, $rendererConfig: JSON!) {
updateSimpleField(data: {id: $fieldId, formConfig: {renderer: "CUSTOM", config: $rendererConfig}}){
migration {
id
}
}
}
Params:
{
"fieldId": "qwertyuiop1234567890", // the ID of the field you want to replace
"rendererConfig": { "extensionId": "poiuytrewq0987654321" } // the ID of the UI extension you just registered
}
export interface ExtensionType {
// name of the field in the form (may differ from field apiId)
name: string;
// current locale on localized field
locale?: string;
// field state sync
value: any;
onChange: <T extends HTMLElement = HTMLElement>(event: ChangeEvent<T> | any) => Promise<void>;
onFocus: <T extends HTMLElement = HTMLElement>(event?: FocusEvent<T>) => Promise<void>;
onBlur: <T extends HTMLElement = HTMLElement>(event?: FocusEvent<T>) => Promise<void>;
meta?: { // @see https://final-form.org/docs/react-final-form/types/FieldRenderProps
active: boolean;
error: any;
touched: boolean;
};
// fullscreen mode
isExpanded?: boolean;
expandField?: (expand: boolean) => unknown;
// open the asset picker (see the Asset type in your Content Api)
openAssetPicker: () => Promise<null | Asset>;
// access to the form state and other form fields
form?: {
change: <Value = any>(name: string, value: Value) => Promise<void>;
getState: <Values = Record<string, any>>() => Promise<FormState<Values>>; // see https://final-form.org/docs/final-form/types/FormState
getFieldState: <Value = any>(fieldName: string) => Promise<FieldState<Value>>; // see https://final-form.org/docs/final-form/types/FieldState
// details about the field you are currently extending
field?: {
id: string;
apiId: string;
description: string | null;
displayName: string;
isList: boolean;
isLocalized?: boolean;
isRequired?: boolean;
isUnique?: boolean;
isPreview: boolean; // true when in version or stage comparison view
type: FieldExtensionType;
model: {
apiId: string;
apiIdPlural: string;
id: string;
description: string | null;
displayName: string;
isLocalized: boolean;
};
};
// information needed to be able to use the project api's
context?: {
project: {
id: string;
name: string;
// management api access
mgmtApi: string;
mgmtToken: string;
};
environment: {
id: string;
name: string;
// content api access
endpoint: string;
authToken: string;
};
};
// extension status
extension: {
onReady: () => void;
config: Record<string, any>;
status: "connected" | "connecting" | "error" | "disconnected";
};
}