Skip to content

fix(plugins): route ctx.content seo writes to the SEO panel#453

Merged
ascorbic merged 1 commit intoemdash-cms:mainfrom
all3f0r1:fix/plugin-content-seo-374
Apr 11, 2026
Merged

fix(plugins): route ctx.content seo writes to the SEO panel#453
ascorbic merged 1 commit intoemdash-cms:mainfrom
all3f0r1:fix/plugin-content-seo-374

Conversation

@all3f0r1
Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes ctx.content.create() / ctx.content.update() so plugins can write to the core SEO panel. Previously, passing a seo key in the input failed with SQLite error: no such column: seo because SEO fields live in the separate _emdash_seo table, not on the content row. Plugins could not programmatically populate the SEO panel, which blocked WordPress/Yoast importers, AI-assisted SEO tools, and content automation plugins.

The plugin content adapter now mirrors the REST API behavior:

  • createContentAccessWithWrite.create / update extract the reserved seo key from the input and route it to SeoRepository.upsert, running content + SEO writes inside a single withTransaction so either both succeed or neither does.
  • Writing seo to a collection without has_seo = 1 throws a validation error matching the REST API (Collection "<x>" does not have SEO enabled).
  • createContentAccess.get / list hydrate ContentItem.seo for SEO-enabled collections, so plugins can read current SEO state before doing non-destructive updates (also requested in the issue).
  • Partial SEO updates preserve existing fields (via the existing SeoRepository.upsert COALESCE logic).
  • A small SeoRepository.isEnabled(collection) helper encapsulates the has_seo lookup used by both the plugin context and the new tests.
  • ContentItem, ContentItemSeo, ContentItemSeoInput, and ContentWriteInput are exported from plugins/types.ts so plugin authors get full typings.

Closes #374

Type of change

  • Bug fix
  • Feature (requires approved Discussion)
  • Refactor (no behavior change)
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code

Implemented with Claude Code (Opus 4.6). A human reviewed every change, ran the test suite, and wrote the regression tests against the exact repro from the issue. Reviewers may want to pay extra attention to: the transaction boundary in createContentAccessWithWrite.update (the seo-only path intentionally skips ContentRepository.update so it does not bump updated_at/version when only SEO changed — open to flipping this if the project prefers always bumping on any write), and the decision to reserve seo as a magic key inside the flat data map rather than introducing a new envelope shape.

Screenshots / test output

 ✓ tests/integration/plugins/capabilities.test.ts (60 tests) 2081ms

 Test Files  1 passed (1)
      Tests  60 passed (60)

New tests added in packages/core/tests/integration/plugins/capabilities.test.ts under Content Access > SEO panel integration:

  • get() returns SEO defaults for SEO-enabled collections
  • get() omits seo for non-SEO collections
  • update() routes seo to the SEO panel (direct regression test for [plugins] ctx.content.update() cannot write to seo.* fields (no such column: seo) #374)
  • update() accepts field updates alongside seo in one call
  • update() partial seo updates do not clobber existing fields
  • create() routes seo to the SEO panel
  • update() throws when seo is provided on a non-SEO collection
  • list() hydrates seo for each item in SEO-enabled collections

Fixes emdash-cms#374

ctx.content.create/update previously failed with
"SQLite error: no such column: seo" when the input data included a seo
key, because SEO fields live in the separate _emdash_seo table, not on
the content row. Plugins could not programmatically populate the core
SEO panel, blocking WordPress/Yoast importers and AI-assisted SEO tools.

The plugin content adapter now extracts the reserved seo key from the
input, runs the content update and the SeoRepository.upsert inside the
same withTransaction, and hydrates ContentItem.seo on reads for
SEO-enabled collections. Writing seo to a collection without has_seo
throws a validation error, matching the REST API semantics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 11, 2026

🦋 Changeset detected

Latest commit: 5d016a5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/plugin-ai-moderation Patch
@emdash-cms/plugin-atproto Patch
@emdash-cms/plugin-audit-log Patch
@emdash-cms/plugin-color Patch
@emdash-cms/plugin-embeds Patch
@emdash-cms/plugin-forms Patch
@emdash-cms/plugin-webhook-notifier Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 11, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@453

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@453

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@453

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@453

emdash

npm i https://pkg.pr.new/emdash@453

create-emdash

npm i https://pkg.pr.new/create-emdash@453

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@453

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@453

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@453

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@453

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@453

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@453

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@453

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@453

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@453

commit: 5d016a5

Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Thanks

@ascorbic ascorbic merged commit 30c9a96 into emdash-cms:main Apr 11, 2026
25 checks passed
@emdashbot emdashbot bot mentioned this pull request Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plugins] ctx.content.update() cannot write to seo.* fields (no such column: seo)

2 participants