Skip to content

feat: Workflow integration — hooks, actions, and WDK adapter#1526

Draft
mrprkr wants to merge 1 commit intoThinkmill:mainfrom
mrprkr:worktree-workflow-integration
Draft

feat: Workflow integration — hooks, actions, and WDK adapter#1526
mrprkr wants to merge 1 commit intoThinkmill:mainfrom
mrprkr:worktree-workflow-integration

Conversation

@mrprkr
Copy link
Copy Markdown

@mrprkr mrprkr commented Mar 31, 2026

Summary

Adds a hook/event system, manual action triggers, and a Workflow DevKit (WDK) adapter to Keystatic, enabling developers to wire durable workflows to content lifecycle events and dashboard UI buttons.

  • Hooks: beforeCreate, afterCreate, beforeSave, afterSave, beforeDelete, afterDelete — registered via registerHooks() at runtime
  • Actions: Custom operations that appear as a dropdown menu (zap icon) in the item/singleton editor toolbar — registered via registerActions()
  • @keystatic/workflows: Adapter package with useWorkflow() and awaitWorkflow() that bridge Keystatic hooks/actions to WDK workflows via an API endpoint
  • Runtime registry: Actions and hooks are registered via registerActions/registerHooks/registerGlobalHooks to avoid webpack stripping function properties from config objects
  • Documentation: Three new docs pages (Hooks, Actions, Workflows) added under Core concepts

Key design decisions

  • WDK-first but agnostic: The core hook system is plain async functions. WDK is an optional adapter in a separate package
  • before* hooks run sequentially and can cancel/modify operations
  • after* hooks run in parallel (fire-and-forget) with error isolation
  • Actions show results as toast notifications with auto-formatted workflow output
  • String-based workflow refs: Config uses workflow names as strings (not function imports) to avoid bundling server-only code into the client

New packages/files

Path Purpose
packages/keystatic/src/hooks/ Types, executor, context builder, registry
packages/keystatic/src/app/action-buttons.tsx Dropdown menu component
packages/keystatic/src/app/useActions.ts Resolves visible actions from registry
packages/keystatic/src/app/useHookExecutor.ts React hook for hook execution in save/delete flows
packages/keystatic-workflows/ @keystatic/workflows WDK adapter package
docs/src/content/pages/hooks.mdoc Hooks documentation
docs/src/content/pages/actions.mdoc Actions documentation
docs/src/content/pages/workflows.mdoc Workflows/WDK documentation

Modified files

  • packages/keystatic/src/config.tsxhooks and actions on Collection, Singleton, Config types
  • packages/keystatic/src/app/updating.tsx — before/after hook integration in useUpsertItem/useDeleteItem
  • packages/keystatic/src/app/ItemPage.tsx — action buttons in toolbar
  • packages/keystatic/src/app/SingletonPage.tsx — action buttons in toolbar
  • docs/src/content/navigation.yaml — new nav entries

Usage example

// keystatic.config.ts
import { config, collection, fields, registerActions, registerHooks } from '@keystatic/core';
import { useWorkflow, awaitWorkflow } from '@keystatic/workflows';

export default config({
  storage: { kind: 'local' },
  collections: {
    posts: collection({
      label: 'Posts',
      slugField: 'title',
      schema: {
        title: fields.slug({ name: { label: 'Title' } }),
        content: fields.markdoc({ label: 'Content' }),
      },
    }),
  },
});

// Manual actions — appear in the toolbar dropdown
registerActions({ collection: 'posts' }, [
  {
    label: 'Translate to Spanish',
    handler: useWorkflow('translatePostWorkflow', {
      input: (ctx) => ({ slug: ctx.slug, title: ctx.data.title, language: 'es' }),
    }),
  },
]);

// Automatic hooks — fire on content events
registerHooks({ collection: 'posts' }, {
  beforeSave: [
    awaitWorkflow('contentAuditWorkflow', { timeout: 10000 }).then((result) => {
      if (!result.passed) return { cancel: true, reason: 'Audit failed' };
    }),
  ],
});

Test plan

  • 17 unit tests for hook executor and context builder
  • 6 unit tests for @keystatic/workflows adapters
  • Manual: Actions dropdown appears when actions are registered
  • Manual: Clicking an action triggers the workflow and shows a toast
  • Manual: beforeSave hooks can cancel save operations
  • Manual: afterSave hooks fire after successful saves
  • Manual: Documentation pages render correctly

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 31, 2026

⚠️ No Changeset found

Latest commit: 3b7ddf4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@mrprkr mrprkr force-pushed the worktree-workflow-integration branch from 72dce87 to 722fc2f Compare March 31, 2026 05:34
…dapter

Add a hook/event system, manual action triggers, and a GitHub Actions
workflow adapter to Keystatic, enabling developers to trigger automated
workflows from the Keystatic dashboard when content is created, saved,
or deleted.

Core hook system (@keystatic/core):
- Content lifecycle hooks: beforeCreate, afterCreate, beforeSave,
  afterSave, beforeDelete, afterDelete
- Runtime registry: registerActions(), registerHooks(),
  registerGlobalHooks() for wiring workflows to collections/singletons
- Hook executor: sequential before* (with cancel/modify), parallel
  after* (with error isolation)
- Action buttons: dropdown menu (zap icon) in item/singleton toolbar
- Toast notifications for hook execution and action results
- Write-back support via update() on hook/action context

GitHub Actions adapter (@keystatic/workflows):
- useWorkflow(file, options): dispatch workflow_dispatch event,
  await result, show formatted toast
- awaitWorkflow(file, options): dispatch and poll for completion,
  supports .then() chaining for before* hook cancellation
- Communicates via API endpoint that calls GitHub REST API
- String-based workflow refs (workflow YAML filenames)

Example GitHub Actions workflows:
- translate-post.yml: duplicate and translate a post
- content-audit.yml: SEO, quality, and publishing readiness checks
- generate-og-image.yml: render social preview images with @vercel/og
- ai-content-assistant.yml: AI summary, title suggestions, tag
  generation using OpenAI

Documentation:
- docs/hooks.mdoc: lifecycle events, registration, cancellation,
  data modification, execution order
- docs/actions.mdoc: registration, context, results, conditional
  visibility, UI placement
- docs/workflows.mdoc: GitHub Actions integration, writing workflows,
  API endpoint setup, useWorkflow/awaitWorkflow adapters

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mrprkr mrprkr force-pushed the worktree-workflow-integration branch from 722fc2f to 3b7ddf4 Compare March 31, 2026 05:55
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