Adding editable regions to Bookshop sites (Hugo, Jekyll, and Eleventy)

If you’re using Bookshop for component management on your Hugo, Jekyll, or Eleventy site, you can now take advantage of a more modern visual editing experience.

Bookshop v3.17.1 introduced the --editable-regions flag, which injects a live editing script that allows you to use your existing Bookshop components inside our new editable regions. This brings modern visual editing support to Hugo, Jekyll, and Eleventy sites using Bookshop as a component development workflow.

This guide walks you through updating a website that uses Bookshop to leverage the new editable regions feature.

Why would you want to use editable regions?

The traditional Bookshop approach used data binding panels, which worked well but was somewhat removed from the actual content you were editing. Editable regions change this by letting you:

  • Edit text directly where it appears on the page
  • Click on images to change them in context
  • Add, remove, and reorder components with visual controls right in the editor
  • Get a more intuitive editing experience overall

This migration makes sense if you’re already using Bookshop and want a better visual editing experience. You can also keep using the old approach if it’s working well for you. This is an enhancement, not a requirement.

Prerequisites

  • Bookshop v3.17.1 or later
  • A site already using Bookshop for component management

Step 1: Update Bookshop

Update your Bookshop packages to v3.17.1 or later:

npm update @bookshop/generate

Or update your package.json to specify the new version:

{
  "dependencies": {
    "@bookshop/generate": "^3.17.1"
  }
}

Note: Version 3.17.0 has a known issue with the visual editor loading state. Use v3.17.1 or later.

Step 2: Update Your postbuild command

Modify your .cloudcannon/postbuild script to include the --editable-regions flag:

Before:

#!/usr/bin/env bash

npx @bookshop/generate

After:

#!/usr/bin/env bash

npx @bookshop/generate --editable-regions

Note: This flag also disables the traditional data binding panels. You will need to complete the following steps to get live editing working again.

Step 3: Update your layout file (optional but recommended)

Wrap your bookshop page include in an <editable-component> element. This sets up the data property path for nested editable regions.

For Jekyll

Before:

{%- include head.html -%}
{% bookshop_include page content_blocks=page.content_blocks %}
{{ content }}
{%- include tail.html -%}

After:

{%- include head.html -%}
<editable-component data-prop="content_blocks" data-component="page">
    {% bookshop_include page content_blocks=page.content_blocks %}
</editable-component>
{{ content }}
{%- include tail.html -%}

Why this matters: The data-prop attribute values stack as they go down the DOM tree. By setting data-prop=“content_blocks” on the wrapper component, nested editable regions can use data-prop=“” (empty string) because the path is already established. If you skip this step, you’ll need to use data-prop=“content_blocks” on the array element instead.

Step 4: Update your page templates

Add editable region data attributes to your page templates where you render component arrays.

For Jekyll

Before:

<main>
  {% for block in include.content_blocks %}
    {% bookshop {{block._bookshop_name}} bind=block %}
  {% endfor %}
</main>

After (with Step 3 wrapper):

<main data-editable="array" data-prop="" data-id-key="_bookshop_name" data-component-key="_bookshop_name">
  {% for block in include.content_blocks %}
    <editable-array-item data-component="{{block._bookshop_name}}" data-id="{{block._bookshop_name}}">
      {% bookshop {{block._bookshop_name}} bind=block %}
    </editable-array-item>
  {% endfor %}
</main>

After (without Step 3 wrapper):

<main data-editable="array" data-prop="content_blocks" data-id-key="_bookshop_name" data-component-key="_bookshop_name">
  {% for block in include.content_blocks %}
    <editable-array-item data-component="{{block._bookshop_name}}" data-id="{{block._bookshop_name}}">
      {% bookshop {{block._bookshop_name}} bind=block %}
    </editable-array-item>
  {% endfor %}
</main>

Key attributes explained

Attribute Purpose
data-editable="array" Marks this element as an editable array region
data-prop The property path — empty if parent already set it, otherwise the full path (e.g., content_blocks)
data-id-key="_bookshop_name" The key used to identify each item
data-component-key="_bookshop_name" The key that specifies the component type
data-component On each item, specifies which component to render
data-id On each item, provides a unique identifier

Understanding data-prop stacking

The data-prop attribute values accumulate as you traverse down the DOM. For example:

<!-- Parent sets the base path -->
<editable-component data-prop="content_blocks">
  <!-- Child can use empty string since path is already set -->
  <main data-editable="array" data-prop="">
    ...
  </main>
</editable-component>

Is equivalent to:

<!-- No parent wrapper, so array must specify full path -->
<main data-editable="array" data-prop="content_blocks">
  ...
</main>

For more details on this behavior, see the CloudCannon documentation on visually editing complex arrays.

For Hugo

<main data-editable="array" data-prop="" data-id-key="_bookshop_name" data-component-key="_bookshop_name">
  {{ range .Params.content_blocks }}
    <editable-array-item data-component="{{ ._bookshop_name }}" data-id="{{ ._bookshop_name }}">
      {{ partial "bookshop" . }}
    </editable-array-item>
  {{ end }}
</main>

For Eleventy

<main data-editable="array" data-prop="" data-id-key="_bookshop_name" data-component-key="_bookshop_name">
  {% for block in content_blocks %}
    <editable-array-item data-component="{{ block._bookshop_name }}" data-id="{{ block._bookshop_name }}">
	    {% bookshop "{{page.component._bookshop_name}}" bind: component %}
    </editable-array-item>
  {% endfor %}
</main>

Step 5: Deploy and test

  1. Commit your changes
  2. Push to trigger a new build on CloudCannon
  3. Open the Visual Editor and verify that:
    • Components render correctly
    • You can add, remove, and reorder components
    • Inline editing works within components

Step 6: Add Editable Regions inside components (optional)

Once the basic migration is complete, you can add any type of editable region inside your Bookshop components to enable inline editing for text, images, and more.

Text editing

Add text editable regions to enable inline text editing within your components:

<!-- hero.jekyll.html -->
<section class="hero">
  <h1 data-editable="text" data-prop="title">{{ include.title }}</h1>
  <p data-editable="text" data-prop="description">{{ include.description }}</p>
</section>

For more details, see Visually edit text.

Image editing

Add image editable regions to enable image selection and metadata editing:

<!-- hero.jekyll.html -->
<section class="hero">
  <img
    data-editable="image"
    data-prop-src="background_image"
    data-prop-alt="background_image_alt"
    src="{{ include.background_image }}"
    alt="{{ include.background_image_alt }}"
  />
</section>

For more details, see Visually edit images.

Nested arrays

You can even add nested arrays inside your components for more complex editing scenarios. See Visually edit complex arrays for details.

What’s changed

With editable regions enabled:

  • Rendering uses the editable regions system instead of data binding panels
  • Visual editing becomes more intuitive with inline controls
  • Performance may improve due to the streamlined rendering approach

Troubleshooting

Components not rendering in the editor

Ensure your <editable-array-item> wrapper includes both data-component and data-id attributes.

Visual editor stuck on loading

Upgrade to v3.17.1 or later. Version 3.17.0 has a bug where the loading state doesn’t hide correctly.


Summary of changes

File Change
package.json Update @bookshop/generate to ^3.17.1
.cloudcannon/postbuild Add --editable-regions flag
Layout file (e.g., base.html) Wrap bookshop include in <editable-component> (optional)
Page template (e.g., page.jekyll.html) Add data-editable="array" attributes and wrap items in <editable-array-item>

Further reading

4 Likes