@dani-builder/strapi-plugin-builder
Reusable CMS building blocks for Strapi v5 — 12 content types, 42 block/element/shared components, and a "smart API" that returns fully deep-populated pages and globals in a single request. Powers the Daniworks Builder block-based site builder.
Install
pnpm add @dani-builder/strapi-plugin-builder @strapi/plugin-color-picker @d3levvv/strapi-react-icons-plugin @strapi/provider-upload-aws-s3
The two custom-field peer plugins are required: builder components use their
fields (plugin::color-picker.color, plugin::icon-picker.icon), and Strapi
only loads plugins listed in your own package.json. Without them the app fails
to boot with Could not find Custom Field: plugin::color-picker.color.
Builder also requires the react-intl peer to share the host admin translation
context. Install the same version used by your installed @strapi/admin; for
Strapi 5.60.0, run pnpm add [email protected] in the CMS. Keep this dependency
aligned when upgrading Strapi. A separate copy can make Forms fail with
Could not find required intl object even though Strapi provides IntlProvider.
The S3 upload provider is an optional peer for local-only projects, but it must
be a direct dependency of the CMS when AWS_BUCKET enables S3, R2, or MinIO.
Use @strapi/provider-upload-aws-s3 version 5.36.0 or later within Strapi v5.
Strapi resolves upload providers from the consuming application.
Use the package-owned config factories so package upgrades carry storage and
media CSP fixes into the application. Compose the package-owned entries with
the rest of your application config. In config/plugins.ts:
import {
builderPlugins,
builderUpload,
} from "@dani-builder/strapi-plugin-builder/config";
export default ({ env }) => ({
builder: builderPlugins({ env }),
upload: {
config: {
// Keep application-owned security settings such as deniedTypes here.
...builderUpload({ env }),
},
},
});
In config/middlewares.ts:
import { builderMiddlewares } from "@dani-builder/strapi-plugin-builder/config";
export default ({ env }) => [
"strapi::logger",
"strapi::errors",
builderMiddlewares({ env }),
// The remaining Strapi and application middleware entries stay here.
];
With no AWS_BUCKET, uploads use Strapi's local provider. Set AWS_BUCKET for
S3; S3-compatible R2 and MinIO deployments can additionally set AWS_ENDPOINT.
Endpoint-based configurations enable path-style requests by default and can
override that behavior with AWS_FORCE_PATH_STYLE.
Set AWS_ACL=none for buckets with ACLs disabled (such as S3 Bucket owner
enforced). This omits the ACL from upload requests and requires provider 5.36.0
or later; older versions replace the omitted ACL with public-read.
builderUpload({ env }) only owns provider settings. Spread it inside the
application's upload.config block so Strapi's generated media security
settings remain in place. SVG and executable upload denial belongs to the
application's upload.config.security.deniedTypes; the factory does not inject
or replace it.
Plugin-owned content types
The plugin directly registers all 12 content types as plugin::builder.*.
Do not copy schemas into a consumer's src/api directory: that would create
conflicting collectionName registrations. Core REST endpoints are available
under /api/builder/*.
Legal Document (plugin::builder.legal-document) stores standalone localized content with
required title, slug, and Blocks-editor body, plus optional summary and
effectiveDate. Use GET /api/builder/legal-documents?filters[slug][$eq]=privacy-policy&locale=en
to find a published document; an unknown slug returns an empty data array.
Configure Content API token or role permissions explicitly; the plugin does not
automatically grant public access. Setup samples are in the
CMS content guide.
Inquiry remains a storage target. Submit through the rate-limited
POST /api/builder/forms/:slug/submissions; legacy Inquiry routes are removed.
Extending from your project
Add your own blocks to Page.blocks and extra fields to plugin components
from src/extensions/builder/strapi-server.ts in your Strapi project — no
plugin fork needed. Register populate for custom blocks with
strapi.plugin('builder').service('populate').registerBlock(uid, populate).
Step-by-step guide with the failure cases to avoid:
docs/guides/extending-builder.md.
Claude Code users get the same procedure as the builder-extender skill.
Smart API
Deep-populated reads default to published content; authenticated Page requests may
explicitly request drafts with status=draft:
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/builder/pages | List pages (minimal populate) |
| GET | /api/builder/pages/:documentId | Single page, deep block populate |
| GET | /api/builder/global | Global settings, deep populate |
Consume it with @dani-builder/strapi-client.
License
MIT © HJ-company
Builder Forms admin
Open Builder Forms in the Strapi admin menu to list, inspect and export saved
form definitions. Administrators with Builder → Forms → Update permission can
also create forms, edit target field selection and order, labels, placeholders,
help text, required/hidden settings, enumeration labels and consent text/version.
Schema constraints are retained; schema-required fields cannot be removed or made
optional. The target selector supports eligible consumer api::* types. Slugs are immutable after creation. Forms with submissions
cannot be deleted; the server's error is shown and the row is retained.
Export downloads the saved portable JSON definition. Import accepts a JSON file or pasted JSON; use the list to create a new form, or the detail screen to replace the saved definition with a matching slug. New imports start at form version 1; replacement imports and ordinary saves increment the server-owned form version. The separate consent version is preserved. Ordinary saves preserve existing notification configuration; exported credentials are masked, so imports do not transfer notification secrets to another CMS. Notification configuration and submission management screens are deferred to a later release.
Optional shared.tracking metadata is available on every block, shared.link, and
shared.action. Existing content may omit it; Smart Page/Global and client REST
populates include it at nested link/action depths. Renderer DOM propagation is
handled separately.
Form submission access
Forms default to public submission. In the form editor, administrators with the
plugin::builder.forms.manage-submission-access permission can select server-only
access and link content API tokens. Grant this new permission to non-Super Admin
roles that manage submission access; forms.update alone does not allow a
policy-bearing export to be imported again. Create, update, and import requests
containing either access field return 403 without the new permission. Only token ID, name and expiry metadata are shown. A protected
form with no linked tokens blocks all submissions. Deleted or reissued tokens need
to be linked again; renaming a token preserves its ID and access.
Public form definitions never include access policy or token IDs. Portable exports include the policy but omit environment-specific token IDs. Any import containing a policy clears existing token links, even if the policy is unchanged. A protected form then blocks submissions until tokens are relinked. An overwrite without a policy preserves the existing policy and bindings. At startup, legacy forms with both access database fields null are migrated to public access with an empty token list. Repeated startup preserves explicit policies and form versions. Changes to access increment the form version without changing the consent version.
The existing content API authorization must allow plugin::builder.submission.create.
A protected form additionally requires a valid API token whose internal ID is in
its allowlist, including full-access tokens. User JWTs cannot submit protected
forms. Strapi rejects invalid, revoked, or expired credentials with 401 and denies
unauthorized actions with 403. With no credentials, Strapi may return 403 before
form lookup if Public lacks the action; otherwise protected forms return 401.
HOME_PROXY_TOKEN controls trusted proxy IP handling only and grants no submission
access. Token authentication may update Strapi's lastUsedAt telemetry even when
form access is denied. Configure permissions and migrate existing data during
deployment; HTTP integration and deployment verification remain separate steps.