Skip to content

feat: add injectable StringFormatter + Slugify features (app-admin) - #5517

Open
adrians5j wants to merge 1 commit into
release/6.5.0from
claude/slugify-feature
Open

feat: add injectable StringFormatter + Slugify features (app-admin)#5517
adrians5j wants to merge 1 commit into
release/6.5.0from
claude/slugify-feature

Conversation

@adrians5j

@adrians5j adrians5j commented Jul 30, 2026

Copy link
Copy Markdown
Member

Problem

The slugify npm package was imported directly across many admin presenters, each repeating the same options object. Slug generation could drift, and projects had no supported way to override it — some resorted to intercepting it at the bundler level.

Solution

Two injectable, decoratable features in @webiny/app-admin:

  • StringFormatter — the consumer-facing string-transform API (parallel to DateFormatter). Its methods will grow over time; today it exposes slugify().
  • Slugify — a small, single-method feature (execute()) holding Webiny's canonical slug options.

StringFormatter.slugify() delegates to Slugify.execute() internally. Consumers use StringFormatter; to change slug logic a project decorates Slugify alone — a smaller surface than decorating the whole formatter. Both register with the core admin features (always available); StringFormatterFeature registers Slugify as its internal dependency.

The abstractions take (value) => string — no options bag. The canonical options are a private detail of DefaultSlugify, so the contract stays independent of any specific slug library. A project changes behavior by decorating, not by passing per-call options.

Design (per Slack thread)

pavel: if we have DateFormatter, can we have StringFormatter instead, with slugify() as one method?
Adrian: having Slugify alone is easier to decorate than StringFormatter … so users use StringFormatter, and to change slugify logic they decorate Slugify alone.
Bruno: so stringFormatter.slugify() uses slugify.execute() internally? — yeah. 👍 pavel: sold.

Migrated (DI-connected, user-facing form-slug generation → StringFormatter)

  • app-admin: RolesPresenter, ApiKeysPresenter, TeamsPresenter
  • app-aco: FolderTreePresenter, folder create dialog (useCreateDialog)
  • app-headless-cms: ModelGroupPresenter
  • Dropped the now-unused slugify dependency from app-aco and app-headless-cms.

Override (per project) — decorate the fine-grained seam

const MySlugify = Slugify.createDecorator(() => {
    return {
        execute: value => {
            return value.trim().toLowerCase().replace(/\s+/g, "_");
        }
    };
});

container.registerDecorator(MySlugify);

Left as-is on purpose

  • app-website-builder experiment toKey — uses strict options ({ lower: true, strict: true }) to generate internal experiment keys; kept as direct slugify usage for now (see TODO).
  • PagePath / pagePathFromTitle — pure value objects / publicly-exported utils with no DI seam.
  • @webiny/form useGenerateSlug — base-layer package that cannot depend on @webiny/app-admin.

TODO / follow-ups

  • Last toKey call site — bring the app-website-builder experiment toKey into the feature system. It is a distinct transform (strict machine key, not a display slug), so it should become its own method on StringFormatter (e.g. toKey()) backed by its own fine-grained feature — not a bare slugify call. Tracked as a follow-up so this PR stays focused.
  • Backend (separate PR) — make the same utilities available server-side from @webiny/api-core: recreate DateFormatter and StringFormatter / Slugify using api-core's DI, so backend code gets the same injectable, decoratable formatting.

Testing

  • yarn check ✓ for app-admin / app-aco / app-headless-cms
  • Tests: app-admin 436, app-aco 93, app-headless-cms 25 — all pass.
  • yarn lint / yarn format / yarn adio

🤖 Generated with Claude Code

@adrians5j
adrians5j force-pushed the claude/slugify-feature branch from 6ed35d5 to ac65689 Compare July 30, 2026 11:19
@adrians5j adrians5j changed the title feat: add injectable Slugify feature (app-admin) feat: add injectable StringFormatter + Slugify features (app-admin) Jul 30, 2026
…ings

The `slugify` npm package was imported directly across many admin presenters,
each repeating the same options object, so slug generation could drift and
projects had no supported way to override it.

Introduce two injectable, decoratable features in @webiny/app-admin:

- StringFormatter — the consumer-facing string-transform API (parallel to
  DateFormatter). Its methods will grow over time; for now it exposes slugify().
- Slugify — a small, single-method feature (execute()) holding Webiny's canonical
  slug options. StringFormatter.slugify() delegates to Slugify.execute().

Consumers use StringFormatter. To change slug logic, a project decorates Slugify
alone — a smaller surface than decorating the whole formatter. Both are
registered with the core admin features (always available); StringFormatter's
feature registers Slugify as its internal dependency.

- @webiny/app-admin: new features/stringFormatter + features/slugify, registered
  in Admin core and exported from the package root.
- Migrated the DI-connected, user-facing form-slug generators to StringFormatter:
  Roles/ApiKeys/Teams presenters (app-admin), FolderTreePresenter + folder create
  dialog (app-aco), ModelGroupPresenter (app-headless-cms). Dropped the now-unused
  slugify dependency from app-aco and app-headless-cms.
- Documented the injectable-formatter convention in AGENTS.md.

Left as-is on purpose: app-website-builder experiment `toKey` (internal keys must
stay stable regardless of project overrides), the pure PagePath /
pagePathFromTitle value utils (no DI seam, publicly exported), and
@webiny/form's useGenerateSlug (base layer, cannot depend on app-admin).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adrians5j
adrians5j force-pushed the claude/slugify-feature branch from ac65689 to 9c8fccb Compare July 31, 2026 10:45
adrians5j added a commit that referenced this pull request Jul 31, 2026
Brings the same formatting utilities added on the admin side to the backend, so
server code gets the same injectable, decoratable formatting via api-core's DI.

- StringFormatter — consumer-facing string-transform API; exposes slugify()
  today, more methods later. Delegates to the fine-grained Slugify feature.
- Slugify — single-method feature (execute()) holding Webiny's canonical slug
  options; the decorate seam behind StringFormatter.slugify().
- DateFormatter — deterministic absolute date/time formatting (UTC), decoratable.
- All three register with ApiCoreFeature (always available) and are consumed by
  injecting the abstraction via a feature's `dependencies`.
- Migrated the DI-connected backend slug caller: CreateGroupRepository now injects
  StringFormatter instead of importing the toSlug util.
- Documented the new features in ai-context/core-features-reference.md.

Left as-is on purpose: the contentModelGroup zod validation schema keeps using
the toSlug util directly — it is a pure schema factory with no DI seam (mirrors
the admin-side pure-util exceptions).

Follows the admin-side PRs (#5512 DateFormatter, #5517 StringFormatter/Slugify).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant