This repo contains the original Visual Editor "out-of-the-box" sections.
The "components" from the @yext/visual-editor library (page sections and content blocks) are now considered
"sections" and have SectionConfigs in src/library/sections. Slot-only components are in src/library/shared/sectionSupport
and registered in src/library/shared/componentRegistry.ts.
npm run dev: Runs a local development server using example data from the account. See the Local Editor details below.npm run build: Generates the same files that will be built in-platform. Outputs todist.npm run i18n:prepare: Extracts keys and reports missing platform translations. See Internationalization below.npm run i18n:finalize: Validates and propagates completed translations, then lints them. See Internationalization below.npm run validate: Verifies the section library repo structure is valid. Must pass for upload to succeed.npm run deploy: Uploads the latest commit to the platform.npm run add-directory-locator: Adds the necessary files for a directory and locator to the repo.npm run convert-template: Converts a legacy template to section library format.
src/
library/
library.json
sections/
Hero.tsx
Directory.tsx
Locator.tsx
shared/
componentRegistry.ts
layouts/
location/
preview.[png, jpg, jpeg, webp]
metadata.json
defaultLayout.json
citation/
preview.[png, jpg, jpeg, webp]
metadata.json
defaultLayout.json
directory/
preview.[png, jpg, jpeg, webp]
metadata.json
defaultLayout.json
locator/
preview.[png, jpg, jpeg, webp]
metadata.json
defaultLayout.json
templates/
assets/
config.yaml
/**
* Metadata describing a Library.
* A library is a collection of Sections and Layouts.
*/
export type LibraryMetadata = {
/** The version of the library.json metadata schema used in this repo. */
schemaVersion: 1;
/** The internal id of the library. */
id: string;
/** The user-facing display name of the library. */
displayName: string;
/** The user-facing description of the library. */
description: string;
};Each tsx file in src/library/sections defines a single section that will be available in the editor.
Each file must contain a named export with the same name as its file stem with the type YextComponentConfig
and an exported object config of the type SectionConfig.
import type { SectionConfig, YextComponentConfig } from "@yext/visual-editor";
type HeroProps = {};
export const Hero: YextComponentConfig<HeroProps> = () => (
<section>Hero</section>
);
export const config: SectionConfig = {
id: "hero",
displayName: "Hero",
description: "Shows a page hero.",
pageSetTypes: ["ENTITY"],
category: "Content",
};config.id is the stable ID stored in layout data. Do not change it after a
layout uses the section. You can rename a section file and its component export
without breaking existing layouts if config.id does not change.
The shared directory can contain arbitrary files that can be used in any the sections.
shared/componentRegistry.ts registers Directory and Locator internals.
It exports sharedSections, sharedComponents, sharedRootConfigs, and
sharedRootAllowedComponentIds. Shared Puck components render and stay editable in
stored layout data, but do not show in the editor sidebar. Directory and
Locator source can import stable runtime APIs only from
@yext/visual-editor/section-library-support. It must not import Visual Editor
internal or dist paths.
A layout is a starting point for the editor.
The library must contain at least one layout with pageSetType: ENTITY, exactly one
layout with pageSetType: DIRECTORY, and exactly one layout with pageSetType: LOCATOR.
Entity metadata also can set verticals and purposes.
Directory and Locator metadata sets only the layout ID, display name, and page set type.
To provide the preview shown for a layout in the Visual Editor, add one image
named exactly preview.png, preview.jpg, preview.jpeg, or preview.webp to
that layout's directory, alongside metadata.json and defaultLayout.json.
The image is optional, but a layout may not contain more than one of the supported
preview files, and the file must be no larger than 1 MiB. Run npm run validate
to check these requirements.
defaultLayout.json can reference visible section IDs and compatible shared
component IDs, apart from the built-in MainContent wrapper. A visible section
config sets its supported page set types. The editor shows only visible sections
that support the selected layout type.
Defines section library migrations. See "Migrations" below.
Holds the autogenerated PagesJS templates. You should not need to add/edit/commit files in this directory.
Files in src/assets will be made public on the live site. It is recommended to host images
and fonts through the platform for the best optimization and configurability. However,
you may want to use this directory for small SVGs or other icons.
For section libraries, the only supported config.yaml property is buildConfiguration.
buildConfiguration.buildCommand defines the build command run when the library is uploaded.
The reverse proxy prefix flag must be kept for the in-platform reverse proxy feature to work.
buildConfiguration.installDependenciesStep.command defines how to install npm dependencies.
buildConfiguration.installDependenciesStep.requiredFiles defines the files essential for the dependency installation process.
All other config.yaml properties will be dropped/overwritten when uploaded to the platform.
The Visual Editor plug-in generates Pages templates while it builds. It writes
assets/section-library-manifest.json into the Pages artifact. The artifact
contains library and layout metadata, render and editor paths, and default
layout data. It contains no section source. It is not expected that
these files should require any editing.
Local Editor lets you edit Section Library layouts in development using example data from the account's Knowledge Graph.
yextVisualEditorPlugin({
sectionLibrary: true,
localEditor: { enabled: true },
});Start Pages development, then open /local-editor. The selector uses real
Section Library layout IDs.
The plug-in creates a temporary local-editor-data-<layout-id>.tsx template
for each Entity and Directory layout. Pages uses these templates to create
local snapshots. The plug-in removes them when development stops and before
production builds. If these files are not removed automatically, you
should delete them manually.
By default, a basic location entity stream is used to pull data for ENTITY page set types.
This can be configured in the stream.config.ts to use other entity types and to include other fields (including custom fields).
A layout in layouts can override its type's stream and defaults. This allows testing a section library with multiple
layouts and page sets configurations.
By default, fake data is used for directory page sets. The number of directory children can be configured in the local-editor UI.
To use real data, add a DIRECTORY entry stream.config.ts and configure the filter and fields to match the directory (see example below).
By default, fake data is used for locator page sets. A mapbox api key can be provided in the local-editor UI for the locator map.
To use real data, fill out the env variables in the stream.config.ts based on the real locator. Additionally, provide
the nearby locations key and mapbox key in the local-editor UI.
Update the autogenerated stream.config.ts at the starter root. This stream definition
is only for the local-editor. In-platform, all non-linked entity fields will be available
and linked entity fields are configurable via the Page Groups UI.
import type { LocalEditorConfig } from "@yext/visual-editor/plugin";
const config = {
defaults: {
locale: "en",
},
env: {
YEXT_CLOUD_REGION: "US",
YEXT_CLOUD_CHOICE: "GLOBAL-MULTI",
YEXT_ENVIRONMENT: "PROD",
YEXT_SEARCH_API_KEY: "",
YEXT_SEARCH_EXPERIENCE_KEY: "",
},
pageSetTypes: {
ENTITY: {
stream: {
$id: "local-editor-restaurant-stream",
filter: { entityTypes: ["restaurant"] },
fields: ["id", "name", "slug", "c_order_now"],
localization: { locales: ["en"] },
},
},
DIRECTORY: {
stream: {
$id: "local-editor-directory-stream",
// select the correct directory entities:
// set the correct directory level by entity type id in entityTypes
// set the correct directory scope by using savedFilterIds (in platform, go to KG -> Configuration -> Saved Filters)
filter: {
entityTypes: ["dm_city"],
savedFilterIds: ["dm_x-y_address_city"],
},
// select the fields to include in the stream (will vary by directory level)
fields: [
"id",
"name",
"slug",
"dm_directoryChildren.address",
"dm_directoryChildren.geomodifier",
"dm_directoryChildren.hours",
"dm_directoryChildren.id",
"dm_directoryChildren.mainPhone",
"dm_directoryChildren.name",
"dm_directoryChildren.slug",
"dm_directoryChildren.timezone",
"dm_addressCountryDisplayName",
"dm_addressRegionDisplayName",
// The directory parents field name varies based on site/branch/page group
"dm_directoryParents_x_y_z.dm_addressCountryDisplayName",
"dm_directoryParents_x_y_z.dm_addressRegionDisplayName",
"dm_directoryParents_x_y_z.id",
"dm_directoryParents_x_y_z.name",
"dm_directoryParents_x_y_z.slug",
"dm_directoryParents_x_y_z.meta.entityType.id",
],
localization: { locales: ["en"] },
},
},
},
layouts: {
"special-location-layout": {
stream: {
$id: "local-editor-special-location-stream",
filter: { entityTypes: ["location"] },
fields: ["id", "name", "description"],
localization: { locales: ["en"] },
},
},
},
} satisfies LocalEditorConfig;
export default config;For each layout, Local Editor starts with defaults, then applies the matching
pageSetTypes value, then applies layouts[layoutId]. A later value replaces
an earlier value.
You must run yext pages generate-test-data or restart your development server after
updating stream.config.ts.
Section libraries supports translations across the following languages:
cs, da, de, en, en-GB, es, et, fi, fr, hr, hu, it, ja, lt, lv,
nb, nl, pl, pt, ro, sk, sv, tr, zh, zh-TW
There are two types of translations: Page and Platform.
Page translations are used on the live site. The locale of the page determines which translation is loaded.
If there are any hardcoded strings in the render method of a section,
it should have a translation entry in src/library/i18n/page/{locale}.json.
Page translations can be resolved using the t function of useTranslation from react-i18next.
Platform translations are used in the editor. The locale of Storm user determines which translation is loaded.
Field labels and other editor-only text should have a translation entry in src/library/i18n/platform/{locale}.json.
Platform translations are resolved using the pt function from @yext/visual-editor in React render contexts and
are marked for translation using the deferred translation msg function for non-React contexts.
The @yext/visual-editor library has a set of translations already built-in.
See https://github.com/yext/visual-editor/tree/main/packages/visual-editor/locales.
If a key is present in @yext/visual-editor but missing from this repo, the visual-editor translation will be available.
If a key is present in both @yext/visual-editor and this repo, this repo's value will be used.
To update translations, ask Codex to use the $update-translations skill.
Translation has two phases:
npm run i18n:prepare
# Codex translates the missing values
npm run i18n:finalizePreparation runs the distinct platform and page extractors. The platform files contain the complete key superset from t, pt, and msg, while the page files contain only page-facing t keys. It then prints every missing or empty platform value beside its English source without failing or changing authored translations.
Finalization requires complete platform translations, repairs only unambiguous interpolation-name mismatches, and reports ambiguous mismatches for manual review. It also runs both i18next linters.
When updating in-use section libraries, migrations may be needed to ensure a seamless upgrade process. When a new section library revision is selected in the platform, the editor and live site begin using the new revision's code with the existing layout data. If section props have changed, this can cause a mismatch. Migrations are applied before rendering, allowing the props to be updated to match the new shape.
There are two MigrationActions:
Removes a section from all layouts.
{
action: "removed";
}Transforms the existing props to the new set of props. See https://puckeditor.com/docs/api-reference/functions/transform-props
Use "*" as the section name to apply an updated migration to every
section, including sections nested in slots. Wildcard migrations cannot
use the removed action.
{
action: "updated";
propTransformation: (oldProps: Record<string, any>) => Record<string, any>;
}
// Example
// Renames a prop from "heading" to "title" while keeping all other props
{
action: "updated";
propTransformation: ({ heading, ...props }) => ({ title: heading, ...props });
}To add a migration, create src/library/migrations/registry.ts with an exported migrationRegistry.
export const migrationRegistry: MigrationRegistry = [];Each item in the registry will be applied in order. The index of the last applied migration
is stored in the layout data at root.props.sectionLibraryMigrationVersion. Default layout
files must be updated to the latest migration defined in the repo.