@craftware/crafty-sdk 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -3
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -0
- package/dist/client.js.map +1 -1
- package/dist/crafty-visual-editing.global.js +1 -1
- package/dist/crafty-visual-editing.global.js.map +1 -1
- package/dist/http.d.ts +8 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +50 -11
- package/dist/http.js.map +1 -1
- package/dist/image.d.ts +27 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +110 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/resources/assistant.d.ts +30 -0
- package/dist/resources/assistant.d.ts.map +1 -0
- package/dist/resources/assistant.js +51 -0
- package/dist/resources/assistant.js.map +1 -0
- package/dist/resources/entries.js +2 -2
- package/dist/resources/entries.js.map +1 -1
- package/dist/resources/forms.d.ts.map +1 -1
- package/dist/resources/forms.js +4 -4
- package/dist/resources/forms.js.map +1 -1
- package/dist/resources/pages.js +2 -2
- package/dist/resources/pages.js.map +1 -1
- package/dist/types/public.d.ts +101 -1
- package/dist/types/public.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/crafty-cms/SKILL.md +40 -7
- package/skills/crafty-cms/references/mcp-configuration.md +23 -7
- package/skills/crafty-cms/references/migrate-to-crafty.md +85 -0
- package/skills/crafty-cms/references/sdk-development.md +59 -7
package/dist/types/public.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export type CraftyCollection = {
|
|
|
42
42
|
};
|
|
43
43
|
export type CraftyEntry = {
|
|
44
44
|
id: number;
|
|
45
|
+
/** The entry slug (Crafty servers from October 2026; `undefined` on older ones). */
|
|
46
|
+
slug?: string | null;
|
|
45
47
|
created_at: string;
|
|
46
48
|
updated_at: string;
|
|
47
49
|
/** Always "published" outside preview mode: drafts are only returned with a preview token. */
|
|
@@ -89,6 +91,10 @@ export type CraftyMediaCollection = {
|
|
|
89
91
|
export type CraftyMediaFile = {
|
|
90
92
|
id: number;
|
|
91
93
|
file_name: string;
|
|
94
|
+
/** Alt text for `<img alt>`; `null` when none was set. */
|
|
95
|
+
alt: string | null;
|
|
96
|
+
/** `true` when `alt` was suggested by AI and not edited since. */
|
|
97
|
+
alt_generated: boolean;
|
|
92
98
|
file_path: string;
|
|
93
99
|
url: string;
|
|
94
100
|
mime_type: string;
|
|
@@ -97,8 +103,50 @@ export type CraftyMediaFile = {
|
|
|
97
103
|
directory_id: number | null;
|
|
98
104
|
is_public: boolean;
|
|
99
105
|
expires_at: string | null;
|
|
106
|
+
/**
|
|
107
|
+
* Cloudflare zone origin that resizes this image via `/cdn-cgi/image/...`.
|
|
108
|
+
* Set only for public raster images when image transformations are enabled
|
|
109
|
+
* on the Crafty server; use `crafty.image()` instead of reading it directly.
|
|
110
|
+
*/
|
|
111
|
+
image_transform_base?: string | null;
|
|
100
112
|
created_at: string | null;
|
|
101
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* A media file embedded in page block or entry data: a `reference` /
|
|
116
|
+
* `multiReference` field with `model: "Media"` adds `<field>_media`
|
|
117
|
+
* with this shape (a list for `multiReference`).
|
|
118
|
+
*/
|
|
119
|
+
export type CraftyMediaReference = {
|
|
120
|
+
id: number;
|
|
121
|
+
url: string;
|
|
122
|
+
file_name: string;
|
|
123
|
+
/** Alt text for `<img alt>`; `null` when none was set. */
|
|
124
|
+
alt: string | null;
|
|
125
|
+
mime_type: string | null;
|
|
126
|
+
size: number;
|
|
127
|
+
is_public: boolean;
|
|
128
|
+
/** See `CraftyMediaFile.image_transform_base`. */
|
|
129
|
+
image_transform_base?: string | null;
|
|
130
|
+
};
|
|
131
|
+
/** Anything `crafty.image()` / `crafty.srcset()` can turn into a URL. */
|
|
132
|
+
export type CraftyImageSource = {
|
|
133
|
+
url: string;
|
|
134
|
+
image_transform_base?: string | null;
|
|
135
|
+
} | string | null | undefined;
|
|
136
|
+
/** How the image fits `width` x `height` (Cloudflare `fit`). */
|
|
137
|
+
export type CraftyImageFit = "scale-down" | "contain" | "cover" | "crop" | "pad";
|
|
138
|
+
/** Output format; `auto` serves AVIF or WebP when the browser accepts it. */
|
|
139
|
+
export type CraftyImageFormat = "auto" | "webp" | "avif" | "jpeg" | "png";
|
|
140
|
+
export type CraftyImageOptions = {
|
|
141
|
+
/** Target width in CSS pixels, 1 to 4096. */
|
|
142
|
+
width?: number;
|
|
143
|
+
/** Target height in CSS pixels, 1 to 4096. */
|
|
144
|
+
height?: number;
|
|
145
|
+
fit?: CraftyImageFit;
|
|
146
|
+
/** Compression quality, 1 to 100. */
|
|
147
|
+
quality?: number;
|
|
148
|
+
format?: CraftyImageFormat;
|
|
149
|
+
};
|
|
102
150
|
export type PaginationLinks = {
|
|
103
151
|
first: string | null;
|
|
104
152
|
last: string | null;
|
|
@@ -113,13 +161,26 @@ export type PaginationMeta = {
|
|
|
113
161
|
to: number | null;
|
|
114
162
|
total: number;
|
|
115
163
|
};
|
|
164
|
+
/**
|
|
165
|
+
* Present on entry, page and form responses of a site with locales configured.
|
|
166
|
+
* `translations` (show endpoints only) lists the locales the item has
|
|
167
|
+
* content in, default included: handy for `hreflang` links.
|
|
168
|
+
*/
|
|
169
|
+
export type CraftyLocaleMeta = {
|
|
170
|
+
/** Locale the content was served in (the default when `locale` was omitted or unsupported). */
|
|
171
|
+
locale: string;
|
|
172
|
+
default_locale: string;
|
|
173
|
+
locales: string[];
|
|
174
|
+
translations?: string[];
|
|
175
|
+
};
|
|
116
176
|
export type PaginatedResponse<T> = {
|
|
117
177
|
data: T[];
|
|
118
178
|
links: PaginationLinks;
|
|
119
|
-
meta: PaginationMeta
|
|
179
|
+
meta: PaginationMeta & Partial<CraftyLocaleMeta>;
|
|
120
180
|
};
|
|
121
181
|
export type SingleResponse<T> = {
|
|
122
182
|
data: T;
|
|
183
|
+
meta?: CraftyLocaleMeta;
|
|
123
184
|
};
|
|
124
185
|
export type CollectionListResponse = {
|
|
125
186
|
data: CraftyCollection[];
|
|
@@ -165,7 +226,23 @@ export type CraftyAnalytics = {
|
|
|
165
226
|
body: string;
|
|
166
227
|
};
|
|
167
228
|
export type CraftyAnalyticsResponse = SingleResponse<CraftyAnalytics>;
|
|
229
|
+
export type CraftyAssistant = {
|
|
230
|
+
/** True when the environment shows the widget (assistant on, or WhatsApp-only). */
|
|
231
|
+
installed: boolean;
|
|
232
|
+
/** "assistant" answers with AI, "whatsapp" shows only the WhatsApp button, "off" shows nothing. */
|
|
233
|
+
mode: "assistant" | "whatsapp" | "off";
|
|
234
|
+
script_url: string;
|
|
235
|
+
site: string;
|
|
236
|
+
environment: string;
|
|
237
|
+
/** Base URL of the public API the widget talks to. */
|
|
238
|
+
api_url: string;
|
|
239
|
+
/** Loader `<script>` to render right before `</body>`; "" when not installed. */
|
|
240
|
+
html: string;
|
|
241
|
+
};
|
|
242
|
+
export type CraftyAssistantResponse = SingleResponse<CraftyAssistant>;
|
|
168
243
|
export type EntriesListQuery = {
|
|
244
|
+
/** Case-insensitive text search over the collection's text fields (2 to 100 characters). */
|
|
245
|
+
q?: string;
|
|
169
246
|
sort_by?: string;
|
|
170
247
|
sort_order?: "asc" | "desc";
|
|
171
248
|
per_page?: number;
|
|
@@ -193,6 +270,8 @@ export type CraftyForm = {
|
|
|
193
270
|
name: string;
|
|
194
271
|
slug: string;
|
|
195
272
|
description: string | null;
|
|
273
|
+
/** Message to show after a successful submission, when the form has one. */
|
|
274
|
+
success_message?: string;
|
|
196
275
|
fields: Record<string, CraftyFormField>;
|
|
197
276
|
status: "draft" | "published";
|
|
198
277
|
hcaptcha_enabled: boolean;
|
|
@@ -203,11 +282,22 @@ export type CraftyFormField = {
|
|
|
203
282
|
label: string;
|
|
204
283
|
type: "text" | "textarea" | "email" | "tel" | "number" | "select" | "checkbox" | "radio" | "date";
|
|
205
284
|
required: boolean;
|
|
285
|
+
placeholder?: string;
|
|
286
|
+
help_text?: string;
|
|
287
|
+
/** Replaces every validation message of the field. */
|
|
288
|
+
error_message?: string;
|
|
289
|
+
/** Options to show, in the requested locale on sites with locales. */
|
|
206
290
|
options?: string[];
|
|
291
|
+
/**
|
|
292
|
+
* Values to submit for `options`, in the same order. Present on sites with
|
|
293
|
+
* locales; submitting the translated option works too.
|
|
294
|
+
*/
|
|
295
|
+
option_values?: string[];
|
|
207
296
|
order: number;
|
|
208
297
|
};
|
|
209
298
|
export type FormListResponse = {
|
|
210
299
|
data: CraftyForm[];
|
|
300
|
+
meta?: CraftyLocaleMeta;
|
|
211
301
|
};
|
|
212
302
|
export type FormShowResponse = SingleResponse<CraftyForm>;
|
|
213
303
|
export type FormSubmitData = Record<string, unknown>;
|
|
@@ -215,6 +305,8 @@ export type FormSubmitResponse = {
|
|
|
215
305
|
id: number;
|
|
216
306
|
form_id: number;
|
|
217
307
|
submitted_at: string;
|
|
308
|
+
/** The form's success message in the submission locale, when it has one. */
|
|
309
|
+
message?: string;
|
|
218
310
|
};
|
|
219
311
|
export type CraftyClientConfig = {
|
|
220
312
|
baseUrl: string;
|
|
@@ -239,11 +331,19 @@ export type CraftyClientConfig = {
|
|
|
239
331
|
preview?: string | false | null;
|
|
240
332
|
/** Called when the API answers `X-Crafty-Preview-Status: invalid` to a request that carried a token. */
|
|
241
333
|
onPreviewInvalid?: () => void;
|
|
334
|
+
/**
|
|
335
|
+
* Default locale for entries, pages and forms, e.g. "en" or "pt-BR". Fields
|
|
336
|
+
* without a translation fall back to the site's default locale. Sites
|
|
337
|
+
* without locales ignore it.
|
|
338
|
+
*/
|
|
339
|
+
locale?: string;
|
|
242
340
|
};
|
|
243
341
|
export type RequestOptions = {
|
|
244
342
|
signal?: AbortSignal;
|
|
245
343
|
headers?: HeadersInit;
|
|
246
344
|
timeoutMs?: number;
|
|
345
|
+
/** Locale for this request (entries, pages and forms), overriding the client's `locale`. */
|
|
346
|
+
locale?: string;
|
|
247
347
|
};
|
|
248
348
|
export type JsonRecord = Record<string, unknown>;
|
|
249
349
|
//# sourceMappingURL=public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.
|
|
1
|
+
export declare const SDK_VERSION = "0.8.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: crafty-cms
|
|
3
|
-
description: "Build on Crafty CMS — configure sites, pages, blocks, collections, entries, forms, and media through the Crafty MCP server, then develop a frontend with the @craftware/crafty-sdk JavaScript SDK. Use when connecting an agent to a Crafty MCP server; calling Crafty MCP tools like list_sites, create_page, create_block_type, publish_form, upload_media, or request_media_upload; modelling site content with block types (repeater and stringList fields); installing or importing @craftware/crafty-sdk or createCraftyClient; fetching Crafty pages, entries, collections, or media; rendering page blocks; submitting Crafty forms; or preparing a site for the Crafty visual editor (crafty.attrs, enableVisualEditing, preview tokens, configure_visual_editor)."
|
|
3
|
+
description: "Build on Crafty CMS — start a new site from an official starter with `npm create crafty`, configure sites, pages, blocks, collections, entries, forms, and media through the Crafty MCP server, then develop a frontend with the @craftware/crafty-sdk JavaScript SDK. Use when starting a new Crafty site or project (npm create crafty, crafty create, starters); connecting an agent to a Crafty MCP server; calling Crafty MCP tools like list_sites, create_page, create_block_type, publish_form, upload_media, or request_media_upload; modelling site content with block types (repeater and stringList fields); installing or importing @craftware/crafty-sdk or createCraftyClient; fetching Crafty pages, entries, collections, or media; rendering page blocks; submitting Crafty forms; or preparing a site for the Crafty visual editor (crafty.attrs, enableVisualEditing, preview tokens, configure_visual_editor)."
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: craftware
|
|
@@ -15,15 +15,36 @@ Crafty is a headless, multi-tenant CMS. There are two integration surfaces, and
|
|
|
15
15
|
| **MCP server** | Author and configure content | Bearer token (PAT or OAuth), scoped | Create sites, pages, blocks, collections, entries, forms; upload media; publish |
|
|
16
16
|
| **`@craftware/crafty-sdk`** | Read published content into an app | `X-API-Key` environment token | Fetch pages/blocks, entries, collections, media; submit forms from a site |
|
|
17
17
|
|
|
18
|
-
The normal flow is **configure with the MCP server → build a frontend with the SDK**.
|
|
18
|
+
The normal flow is **configure with the MCP server → build a frontend with the SDK**. For a **new site**, `npm create crafty` does both in one step (see [Start a new site](#start-a-new-site)).
|
|
19
19
|
|
|
20
20
|
## When to Use
|
|
21
21
|
|
|
22
|
+
- Starting a new Crafty site or frontend project from scratch (`npm create crafty`, `crafty create`, official starters).
|
|
22
23
|
- Connecting Claude, Cursor, VS Code, Codex, or ChatGPT to a Crafty tenant's MCP server.
|
|
23
24
|
- Driving Crafty MCP tools (`list_sites`, `create_page`, `create_entry`, `create_form`, `publish_page`, `upload_media`, …).
|
|
24
25
|
- Installing or importing `@craftware/crafty-sdk` / `createCraftyClient`.
|
|
25
26
|
- Fetching Crafty pages, entries, collections, or media in app code, or submitting a Crafty form.
|
|
26
27
|
|
|
28
|
+
## Start a new site
|
|
29
|
+
|
|
30
|
+
When the user wants a **new** site (not Crafty added to an existing frontend), don't model content and write the frontend by hand: scaffold it from an official starter.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm create crafty@latest mi-sitio -- --url=https://<tenant>.crafty.do
|
|
34
|
+
# same flow: npx @craftware/crafty-cli create mi-sitio --url=https://<tenant>.crafty.do
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
It asks for the framework and template (today: Astro, `astro-services` = "sitio de servicios"; Next.js and Nuxt are coming), logs in through the browser (device flow; the user approves, an admin account is needed), creates the site with the starter's block types, collections, form and demo content, configures the visual editor routes, and writes `.env` (`CRAFTY_API_URL`, `CRAFTY_API_KEY`, `CRAFTY_SITE`), `crafty.json` and `src/crafty.types.ts`. Then:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd mi-sitio && npm install && npm run dev # npm run deploy → Crafty Hosting
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Flags (after `--`): `--url`, `--framework=astro`, `--template=astro-services`, `--name`, `--site=<slug>`, `--draft` (keep demo content as drafts; by default it is published so the static build shows it), `--yes` (no prompts). Agents should pass `--url` and `--yes` so nothing blocks on a prompt; the browser approval still needs the user.
|
|
44
|
+
- The command is interactive only for the login: tell the user to open the printed URL and approve the code.
|
|
45
|
+
- After it finishes, **read the project's `AGENTS.md`** and follow its conventions (one renderer per block type in `src/render/blocks/`, routes in `src/lib/routes.ts`, `npm run types` after changing the content model) instead of the generic steps in Phase 2.
|
|
46
|
+
- If the site slug already exists it fails before creating anything; if provisioning fails midway it names the half-created site: delete it in the panel or rerun with another `--site`.
|
|
47
|
+
|
|
27
48
|
## Scope
|
|
28
49
|
|
|
29
50
|
- **In scope:** using a *deployed* Crafty tenant via its MCP server and the published SDK.
|
|
@@ -59,9 +80,9 @@ How to model it:
|
|
|
59
80
|
- several images (gallery, slider, logo wall): `{ key: "gallery", type: "multiReference", model: "Media" }`, value `[42, 43, 44]`;
|
|
60
81
|
- an image per repeater item: a `reference` subfield with `model: "Media"` (`multiReference` is top-level only).
|
|
61
82
|
|
|
62
|
-
The public API returns the id under the field key and adds `<key>_media` with the file (`{ id, url, file_name, mime_type, size, is_public }`, a list for `multiReference`), so the frontend
|
|
83
|
+
The public API returns the id under the field key and adds `<key>_media` with the file (`{ id, url, file_name, alt, mime_type, size, is_public, image_transform_base }`, a list for `multiReference`), so the frontend renders `crafty.image(block.data.image_media, { width: 800, format: "auto" })` with `alt={block.data.image_media?.alt ?? ""}`. Editors get a media picker in the panel and the visual editor can swap the image. A `photo_url` string field gives them none of that and breaks when the file moves.
|
|
63
84
|
- **Shared data** (address, phone, social links, footer text used in several places): block types have no global blocks. Put it in a dedicated page (e.g. slug `site-settings`) with one `site-settings` block and read it with `crafty.pages.get("site-settings")`.
|
|
64
|
-
- **Languages**: pages
|
|
85
|
+
- **Languages**: do not duplicate pages per language. Set the site's locales (`configure_locales`, e.g. default `es` plus `en`), write the default-locale content as usual, then add each translation with `upsert_translation` (entries; pages with their blocks by id). The frontend passes `locale` (`createCraftyClient({ locale })` or per call) and untranslated fields fall back to the default. Mark fields that must never change per language (SKU, codes) with `translatable: false`. `meta.translations` on `get()` lists the locales an item has, for `hreflang`. When AI translation is enabled for the workspace (from Crafty central) and for the site (`configure_translation` with `enabled: true`), `translate_content` translates an entry or page (with its blocks) into the site's locales without overwriting what a person wrote, and `configure_translation` also turns on re-translation after every change plus a glossary of untranslated terms.
|
|
65
86
|
- There is no `json` field type in block types. If you catch yourself wanting one, model it as a repeater or `stringList`.
|
|
66
87
|
- **References to records**: `{ type: "reference", collection: "<collection slug>" }` (one entry) or `multiReference` (several); the API expands them into the referenced entries.
|
|
67
88
|
|
|
@@ -99,14 +120,18 @@ Block types: `get_block_type` (`site_id` + `block_type_id` or `slug`) → `creat
|
|
|
99
120
|
|
|
100
121
|
Collections/entries: `list_collections` → `create_entry` (`collection_slug`, `site_id`, `slug`, `data`, optional `status: "draft"` to prepare it unpublished) → `update_entry` (snapshots a revision) → `publish_entry` / `unpublish_entry`. To publish or unpublish a page or entry at a given time, use `schedule_publication` (`publish_at` / `unpublish_at`).
|
|
101
122
|
Forms: `create_form` (`site_id`, `name`, `slug`, `fields`) → `publish_form` → `list_form_submissions`.
|
|
102
|
-
Media: upload files before you fill content, and put the returned media `id` in `reference` / `multiReference` fields with `model: "Media"` (`list_media` lists existing ids). `upload_media` (`url`, `site_id`, optional `collection_name`, `sha256`) takes an http(s) URL or a small base64 data URI (256 KB decoded). For a **local binary file** call `request_media_upload` (`site_id`, `file_name`, `sha256`, optional `collection_name`) and run the returned `curl -T` command from your shell; the bytes never pass through the model and the upload is verified against the sha256. Files land on a public disk only if the `collection_name` repository is public — create or change it with `manage_media_collection` (`site_id`, `key`, `is_public`) BEFORE uploading.
|
|
123
|
+
Media: upload files before you fill content, and put the returned media `id` in `reference` / `multiReference` fields with `model: "Media"` (`list_media` lists existing ids). `upload_media` (`url`, `site_id`, optional `collection_name`, `sha256`) takes an http(s) URL or a small base64 data URI (256 KB decoded). For a **local binary file** call `request_media_upload` (`site_id`, `file_name`, `sha256`, optional `collection_name`) and run the returned `curl -T` command from your shell; the bytes never pass through the model and the upload is verified against the sha256. Files land on a public disk only if the `collection_name` repository is public — create or change it with `manage_media_collection` (`site_id`, `key`, `is_public`) BEFORE uploading. **Always set alt text on content images**: pass `alt` (a concise description of what the image shows, in the site language, max 500 characters) to `upload_media` / `request_media_upload`, or set it later with `update_media` (`site_id`, `media_id`, `alt`); `list_media` shows `alt: null` for files still missing it. Purely decorative images may keep `alt` empty.
|
|
103
124
|
Cache: `set_api_cache_status` (`site_id`, `enabled`) to toggle per-site; `flush_api_cache` (`site_id`, optional `resource`) to invalidate cached responses after bulk edits.
|
|
104
125
|
Visual editor: `configure_visual_editor` (`site_id`, optional `environment`, `frontend_url`, `routes`, `render_mode`, `upload_media_collection`) — see `references/visual-editor.md`.
|
|
105
126
|
|
|
106
127
|
> `update_page` **replaces all blocks** when `blocks` is supplied (send every block again with its `id` to keep it). `update_entry` creates a revision. `create_page` always starts as `draft`.
|
|
107
128
|
|
|
129
|
+
Migrating an existing site (WordPress, Webflow, a static site…)? Don't recreate it by hand: follow `references/migrate-to-crafty.md` — it uses `bulk_upsert_entries` (100 entries per call, drafts, `dry_run`), `import_media_from_urls` (50 URLs per call, deduplicated) and `upsert_redirects` so old URLs keep working.
|
|
130
|
+
|
|
108
131
|
## Phase 2 — Build a frontend with the SDK
|
|
109
132
|
|
|
133
|
+
Starting a new site from scratch? Use `npm create crafty` ([Start a new site](#start-a-new-site)) and follow the starter's `AGENTS.md` instead of the steps below. Adding Crafty to an existing frontend:
|
|
134
|
+
|
|
110
135
|
```bash
|
|
111
136
|
npm install @craftware/crafty-sdk
|
|
112
137
|
```
|
|
@@ -128,6 +153,10 @@ const hero = page.block("hero"); // first block of that type, or null
|
|
|
128
153
|
const posts = await crafty.entries.list("posts", { per_page: 15 });
|
|
129
154
|
```
|
|
130
155
|
|
|
156
|
+
**Generate types before writing code against the content.** Field shapes depend on the site's collections and block types — do not guess them. Run `npx @craftware/crafty-cli types` (reads `CRAFTY_API_URL` + `CRAFTY_API_KEY` from `.env`, or pass `--url` / `--api-key`; `--out` changes the file) to write `crafty.types.ts` with `<Slug>Entry` per collection, `<Slug>BlockData` / `<Slug>Block` per block type and the `CraftyPageBlock` union; cast SDK results to them (`page.blocks as unknown as CraftyPageBlock[]`). Types follow the API key's environment (staging may differ from production); re-run it after `create_block_type` / `update_block_type` / `update_collection`. The raw JSON Schema is `GET /api/v1/schema`. Details: `references/sdk-development.md`.
|
|
157
|
+
|
|
158
|
+
Images: render every Crafty image with `crafty.image(media, { width, format: "auto" })` and `crafty.srcset(media, [400, 800, 1200], { format: "auto" })` instead of the raw `url`; they return resized AVIF/WebP URLs when the server has image transformations enabled and fall back to the original `url` otherwise. Never hand-build `/cdn-cgi/image/` URLs.
|
|
159
|
+
|
|
131
160
|
`pages.get()` returns the page enriched with block helpers (`page.block(type)`, `page.blocksOf(type)`, `page.byType`, …). **Full client config, every resource and return shape, block helpers, errors, and an end-to-end example: read `references/sdk-development.md`.**
|
|
132
161
|
|
|
133
162
|
Serving `llms.txt` / `llms-full.txt` for AI agents and crawlers: `crafty.llms.handler()` as a route (Next/Astro/Nuxt), or `crafty.llms.index()` / `full()` written into `dist/` for a static build. Title, summary, language, and routeless collections are configured on the "llms.txt" admin screen or the `configure_llms` MCP tool. **Full API, per-framework wiring, and error handling: `references/sdk-development.md`.**
|
|
@@ -184,12 +213,15 @@ These are two different credentials — do not mix them:
|
|
|
184
213
|
- **Wrong tenant host** → the MCP endpoint won't resolve. Use the exact `https://<tenant-domain>/mcp`; each tenant has its own endpoint.
|
|
185
214
|
- **401 from the MCP server** → the Bearer token is missing, invalid, or expired; reconnect or regenerate it.
|
|
186
215
|
- **A tool is missing or fails with "Insufficient OAuth scope"** → the token lacks that tool's scope; re-issue it with `crafty:write` / `crafty:publish` / `crafty:submissions` as needed.
|
|
187
|
-
- **`forms.submit()` throws `BAD_REQUEST`** → `siteSlug` was not set in `createCraftyClient()`. Submissions hit the public endpoint
|
|
216
|
+
- **`forms.submit()` throws `BAD_REQUEST`** → `siteSlug` was not set in `createCraftyClient()`. Submissions hit the public endpoint; the API key, when set, only picks the environment the submission lands in.
|
|
188
217
|
- **`update_entry` / `update_page` wipe fields you don't send** → both MCP tools do a **full replace**, not a merge. If you send only the new fields, all existing fields are deleted in that revision. Always fetch the current entry first (`get_entry`), merge your changes into the full field map, then call `update_entry` with the complete merged object. Entry `data` is validated against the collection schema: invalid values are rejected and undeclared keys are dropped (listed in `warnings`).
|
|
189
218
|
- **`create_page` / `update_page` reject block data** → block data is validated against the block type (`blocks.2.data.items.1.title: …`), `number` fields are integers (use `decimal` for prices), `select` values must be one of the declared options, `string` fields max 255 characters, and `block_type_id` must belong to the same site. Read the schema with `get_block_type` first.
|
|
190
219
|
- **Data silently missing from a block** → keys not declared in the block type are dropped; check the `warnings` array in the response.
|
|
191
220
|
- **Modelled a list as a collection or a `json` field** → use a block type with a `repeater` (or `stringList`) instead; see "Model content with block types first". Inside a collection, use a `repeater`, `stringList`, `select` with `multiple: true` or `multiReference` (to media or to another collection) instead of `json`.
|
|
192
|
-
- **
|
|
221
|
+
- **Guessed a field name or shape (`block.data.title` vs `heading`, a select value, `image` vs `image_media.url`)** → generate `crafty.types.ts` with `crafty types` and let the compiler catch it.
|
|
222
|
+
- **Stored an image as a `url` / `string` field (`photo_url`, `image`, `logo`)** → upload it to the media library and use a `reference` (`multiReference` for several) with `model: "Media"`; render `crafty.image(<key>_media, { width, format: "auto" })`. Change an existing field with `update_block_type` / `update_collection` and re-save the content with media ids.
|
|
223
|
+
- **Serving the original image (`<img src={media.url}>`)** → large uploads hurt LCP. Use `crafty.image(media, { width, format: "auto" })` plus `crafty.srcset()`; it falls back to `url` when transforms are unavailable.
|
|
224
|
+
- **Uploaded images without alt text** → set `alt` with `update_media` for every content image (`list_media` shows which have `alt: null`) and render `<key>_media.alt` in the frontend's `<img alt>`.
|
|
193
225
|
- **Content not visible via the SDK** → it isn't published. Pages/forms must be published (`publish_page` / `publish_form`); entries created as drafts need `publish_entry`.
|
|
194
226
|
- **Confusing the two tokens** → the SDK `apiKey` (X-API-Key) is not an MCP Bearer token.
|
|
195
227
|
- **Stale skill copy** → a project copy of this skill (e.g. `.claude/skills/crafty-cms`) does not update with the SDK. After upgrading `@craftware/crafty-sdk`, copy `node_modules/@craftware/crafty-sdk/skills/crafty-cms` over it.
|
|
@@ -198,4 +230,5 @@ These are two different credentials — do not mix them:
|
|
|
198
230
|
|
|
199
231
|
- `references/mcp-configuration.md` — connecting every client, scopes, and the full MCP tool catalog with workflows.
|
|
200
232
|
- `references/sdk-development.md` — the complete SDK surface, block helpers, error handling, and examples.
|
|
233
|
+
- `references/migrate-to-crafty.md` — migrating an existing site (WordPress or any other) into Crafty: crawl, classify, model, bulk import as drafts, media, redirects, human review, publish.
|
|
201
234
|
- `references/visual-editor.md` — preparing a site for the visual editor: the mandatory question, marking, preview tokens, bridge, headers, `configure_visual_editor`, verification.
|
|
@@ -31,6 +31,8 @@ When you create the client manually in your profile you are shown the `client_id
|
|
|
31
31
|
|
|
32
32
|
OAuth clients that support MCP auto-discovery (Claude.ai, Claude Code, Codex, Cursor, ChatGPT) can find everything from the server URL alone: discovery is published at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`, with `authorization_endpoint` `/oauth/authorize`, `token_endpoint` `/oauth/token`, dynamic `registration_endpoint` `/oauth/register`, PKCE (`S256`), and grants `authorization_code` + `refresh_token`. Registered redirect URIs must be `https`, a custom app scheme, or `http` on a loopback address (so local CLI callbacks work). These OAuth endpoints live at the tenant **root**, not under `/mcp`.
|
|
33
33
|
|
|
34
|
+
Clients that implement the MCP 2026-07-28 authorization spec can skip registration and use a **Client ID Metadata Document** (CIMD): the `client_id` is an `https` URL serving the client's metadata as JSON (`client_id` equal to that URL, `redirect_uris`, optional `client_name`). Crafty advertises `client_id_metadata_document_supported: true`, fetches the document on `/oauth/authorize` (public addresses only, no redirects, 5 KB max, cached per its `Cache-Control` max-age), and requires the `redirect_uri` to be listed in it. Only public clients (`token_endpoint_auth_method: "none"`) are accepted this way. Dynamic registration keeps working for clients that still use it.
|
|
35
|
+
|
|
34
36
|
## Scopes
|
|
35
37
|
|
|
36
38
|
When you create a token (PAT or OAuth client) you grant one or more scopes. Scopes are **enforced per tool**: a tool is only advertised when your token carries its required scope, and invoking one without the scope fails with an MCP error (`Insufficient OAuth scope`). Grant the scopes the agent needs:
|
|
@@ -106,9 +108,11 @@ All tools return a JSON text response. IDs are integers; slugs are strings. Pagi
|
|
|
106
108
|
| `list_collections` | — | `site_id` | active collections (with their `id`, used by `update_collection` / `delete_collection`) |
|
|
107
109
|
| `list_entries` | `collection_slug` | `site_id`, `status`, `per_page` | paginated entries (newest id first), each with `status` and `published_at` |
|
|
108
110
|
| `get_entry` | `entry_id` | — | one entry with its collection |
|
|
109
|
-
| `list_media` | `site_id` | `collection`, `per_page` | paginated media for the site |
|
|
111
|
+
| `list_media` | `site_id` | `collection`, `per_page` | paginated media for the site; each item has `alt` (`null` when missing) |
|
|
110
112
|
| `list_forms` | — | `site_id`, `status`, `per_page` | paginated forms, ordered by name |
|
|
111
113
|
| `get_form` | `form_id` | — | one form with its field definitions |
|
|
114
|
+
| `list_redirects` | `site_id` | `environment` (default `production`) | the environment's redirects `{ source, destination, status_code }` |
|
|
115
|
+
| `list_webhooks` | `site_id` | `environment` (default `production`) | content webhooks (admins only) with their events and last deliveries; `upsert_webhook` / `delete_webhook` manage them (`crafty:write`) |
|
|
112
116
|
|
|
113
117
|
### Submissions (`crafty:submissions`)
|
|
114
118
|
|
|
@@ -136,15 +140,23 @@ Form submissions may contain personal data, so they require their own scope.
|
|
|
136
140
|
| `delete_collection` | `collection_id`, `confirm: true` | — | **permanently** deletes the collection and all its entries |
|
|
137
141
|
| `create_entry` | `collection_slug`, `site_id`, `slug` | `data`, `status` | `status` = `draft` (default, hidden from the public API until `publish_entry`) or `published`; `data` = field key/value pairs, validated against the collection schema (`number` integer, `decimal` within `min`/`max`/`precision`, `select` one of its option values or an array of distinct values when `multiple`, `repeater` items against their subfields; invalid data is rejected as `data.<field>: <message>`; undeclared keys are dropped and listed in `warnings`) |
|
|
138
142
|
| `update_entry` | `entry_id` | `data`, `slug` | snapshots the prior data into a new revision; `data` is validated like `create_entry` (required fields are not enforced, only provided ones are checked) and rejected data creates no revision |
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
143
|
+
| `bulk_upsert_entries` | `site_id`, `collection_slug`, `entries` | `dry_run`, `publish` | up to 100 `{ slug, external_id?, data }` per call; matches an existing entry by `external_id` first, then by `slug`, else creates it; `data` is validated like `create_entry` and **replaces** the stored data on update (with a revision); new entries are drafts unless `publish: true` (needs `crafty:publish`); items fail individually; `dry_run` reports `created` / `updated` / `failed` per item without saving |
|
|
144
|
+
| `upload_media` | `url`, `site_id` | `collection_name` (default `library`), `file_name`, `sha256`, `alt` | `alt` = alt text for images (max 500 chars); http(s) URL or base64 data URI (max 256 KB decoded); `sha256` rejects and discards the upload on mismatch; the response returns the stored `sha256`, `visibility` and a `warning` when the file is private |
|
|
145
|
+
| `import_media_from_urls` | `site_id`, `urls` | `collection_name` (default `library`) | up to 50 public http(s) URLs per call, with the same SSRF, size and type checks as `upload_media`; a URL already imported to the site (or repeated in the batch) is not downloaded again; per URL: `status` `imported` / `existing` / `duplicate` / `failed`, `media_id`, `original_url` |
|
|
146
|
+
| `request_media_upload` | `site_id`, `file_name`, `sha256` | `collection_name` (default `library`), `size`, `alt` | `alt` is stored with the file once uploaded; returns a 15-minute single-use signed URL and a ready `curl -T` command for uploading a **local binary file** without sending its bytes through the model; max 25 MB; verified against `sha256` |
|
|
141
147
|
| `manage_media_collection` | `site_id`, `key` | `name`, `description`, `is_public` | creates or updates a media repository; `is_public: true` gives permanent public URLs (existing files are moved in the background); new repositories are private |
|
|
148
|
+
| `update_media` | `site_id`, `media_id`, `alt` | — | sets the alt text (max 500 chars) of one media file; `null` or `""` clears it |
|
|
142
149
|
| `delete_media` | `site_id`, `media_id`, `confirm: true` | — | **permanently** deletes one media file (ids from `list_media`) |
|
|
143
150
|
| `create_form` | `site_id`, `name`, `slug` | `description`, `fields` | `fields: [{ key, type, label, required? }]`; starts as `draft` |
|
|
144
151
|
| `update_form` | `form_id` | `name`, `slug`, `description`, `fields`, `notification_emails` | only supplied fields change; `fields`/`notification_emails` replace |
|
|
145
152
|
| `configure_visual_editor` | `site_id` | `environment` (default `production`), `frontend_url`, `routes` (`page_pattern`, `home_slug`, `collections`, `fixed`), `render_mode` (`ssr`/`hybrid`/`static`/`unknown`), `upload_media_collection` | visual editor settings of one environment; only sent keys change, `null` resets to the fallback; with only `site_id` it just reads; returns the effective settings plus `llms_warnings` (the `llms.txt` generator's warnings, since routes affect it too) |
|
|
146
153
|
| `configure_llms` | `site_id` | `environment` (default `production`), `title`, `summary`, `language`, `excluded_collections` (routed collections to drop), `included_collections` (routeless collections to opt in), `collection_languages` | admins only; same read/partial-update semantics as `configure_visual_editor`; only sent keys change, `null` resets to the fallback; with only `site_id` it just reads; returns the effective settings, the configuration warnings, and the first 40 lines of the generated `llms.txt` |
|
|
147
154
|
|
|
155
|
+
| `upsert_redirects` | `site_id`, `redirects` | `environment` (default `production`), `dry_run` | up to 500 `{ source, destination, status_code? }` per call, matched by `source`; `source` is a path starting with `/` (no spaces, `?` or `#`; Cloudflare Pages `*` splats and `:placeholders` allowed), `destination` a path or absolute http(s) URL, `status_code` 301 (default) / 302 / 307 / 308; invalid items are listed in `failed` and skipped. Crafty hosting writes them to `_redirects` on the next deploy (after the site's own rules; limit 2000 static + 100 dynamic); custom frontends read `GET /api/v1/redirects` |
|
|
156
|
+
| `delete_redirect` | `site_id`, `source` | `environment` (default `production`) | deletes one redirect |
|
|
157
|
+
|
|
158
|
+
Redirect tools follow the **Pages** permission: reading needs access to pages, changing needs write access.
|
|
159
|
+
|
|
148
160
|
### Publish (`crafty:publish`)
|
|
149
161
|
|
|
150
162
|
| Tool | Required | Effect |
|
|
@@ -163,7 +175,7 @@ Form submissions may contain personal data, so they require their own scope.
|
|
|
163
175
|
|
|
164
176
|
1. `list_sites` → choose a `site_id` (or `create_site`).
|
|
165
177
|
2. For each section of the design, `create_block_type` (or `get_block_type` if it exists). Use a `repeater` for lists of items, `stringList` for lists of plain strings, and `reference` / `multiReference` with `model: "Media"` for images (upload them first); only use a collection for large independent record sets.
|
|
166
|
-
3. `create_page`
|
|
178
|
+
3. `create_page` in the default locale with `blocks: [{ block_type_id, data }, …]`; fix any `blocks.N.data.…` errors and check `warnings`. For other languages, `configure_locales` once and then `upsert_translation` per page (blocks by id from `get_page`), or `translate_content` to let AI translate it once AI translation is enabled for the workspace and the site (`configure_translation`).
|
|
167
179
|
4. `publish_page`.
|
|
168
180
|
5. To change a schema later, `update_block_type` with the FULL field list (use `force: true` only if you accept orphaning data that blocks already hold).
|
|
169
181
|
|
|
@@ -189,9 +201,13 @@ Form submissions may contain personal data, so they require their own scope.
|
|
|
189
201
|
**Media**
|
|
190
202
|
|
|
191
203
|
1. Need permanent public URLs? `manage_media_collection` with `site_id`, `key`, `is_public: true` first (otherwise files get expiring signed URLs).
|
|
192
|
-
2. From a URL or a tiny file: `upload_media` with `url` and `
|
|
193
|
-
From a local file: compute `shasum -a 256 <file>`, call `request_media_upload
|
|
194
|
-
3. `list_media` to confirm and retrieve URLs
|
|
204
|
+
2. From a URL or a tiny file: `upload_media` with `url`, `site_id` and `alt` (optionally `collection_name`, `sha256`).
|
|
205
|
+
From a local file: compute `shasum -a 256 <file>`, call `request_media_upload` (with `alt`), then run the returned `curl -T` command.
|
|
206
|
+
3. `list_media` to confirm and retrieve URLs; give every content image without `alt` one with `update_media`.
|
|
207
|
+
|
|
208
|
+
**Migrate an existing site**
|
|
209
|
+
|
|
210
|
+
Follow `migrate-to-crafty.md`: crawl → classify → model → `import_media_from_urls` → `bulk_upsert_entries` (drafts, `dry_run` first) → `upsert_redirects` → human review → publish.
|
|
195
211
|
|
|
196
212
|
**Visual editor**
|
|
197
213
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Migrating an existing site to Crafty
|
|
2
|
+
|
|
3
|
+
Runbook for moving a site that already exists (WordPress, Webflow, Wix, a static site, another headless CMS) into Crafty with an agent and the MCP server, without breaking its URLs. Work in this order; each phase has a checkpoint where a human confirms before the next one.
|
|
4
|
+
|
|
5
|
+
Tools used: `bulk_upsert_entries`, `import_media_from_urls`, `upsert_redirects` (plus the usual `create_block_type`, `create_collection`, `create_page`, `publish_*`). Token scopes: `crafty:read` + `crafty:write`; add `crafty:publish` only for the final phase.
|
|
6
|
+
|
|
7
|
+
## Golden rules
|
|
8
|
+
|
|
9
|
+
- **Everything lands as a draft.** Never pass `publish: true` until a human has reviewed the content in the panel.
|
|
10
|
+
- **`dry_run: true` first** on every `bulk_upsert_entries` and `upsert_redirects` batch; fix the `failed` items, then run it for real.
|
|
11
|
+
- **Use `external_id`** (the old CMS post/page ID, or the old URL when there is no ID) on every entry. Re-running an import then updates instead of duplicating.
|
|
12
|
+
- **Batches:** up to 100 entries, 50 media URLs, 500 redirects per call. Keep a local manifest (JSON/CSV) of what each batch sent and what came back (`entry_id`, `media_id`), so a failed run can resume.
|
|
13
|
+
- **Media quota:** imports count against the tenant's media quota. Import only what the pages actually use, skip thumbnails and resized copies (WordPress `-300x200.jpg` variants), and ask before importing more than a few hundred files.
|
|
14
|
+
- Work against the `production` environment of the target site unless the user asks for another (tools take `environment`).
|
|
15
|
+
|
|
16
|
+
## Phase 1 — Crawl
|
|
17
|
+
|
|
18
|
+
Build an inventory of every URL of the old site:
|
|
19
|
+
|
|
20
|
+
1. Sources, best first: the CMS export or API (WordPress REST `/wp-json/wp/v2/posts?per_page=100&page=N`, `/pages`, `/media`; or the WXR export), `sitemap.xml` (and sitemap indexes), then a crawl of internal links from the home page.
|
|
21
|
+
2. For each URL record: old path, title, type guess, language, last modified, the main content (HTML), the images and files it uses, and the old ID if known.
|
|
22
|
+
3. Also collect the URLs that already redirect (HTTP 301/302) and anything in the old site's redirect plugin or `.htaccess`: those must be carried over too.
|
|
23
|
+
|
|
24
|
+
**Checkpoint:** show the user the counts per type and any URL you could not fetch.
|
|
25
|
+
|
|
26
|
+
## Phase 2 — Classify
|
|
27
|
+
|
|
28
|
+
Group the URLs into:
|
|
29
|
+
|
|
30
|
+
- **Singleton pages** (home, about, contact, landing pages) → Crafty **pages** built from block types.
|
|
31
|
+
- **Repeating content** (posts, news, products, team members, case studies, events) → one **collection** per type, one entry per item.
|
|
32
|
+
- **Archives and taxonomy listings** (`/category/x`, `/tag/y`, `/page/2`, author pages, date archives) → usually not migrated as content: the new frontend generates them; they only need redirects.
|
|
33
|
+
- **Junk** (search results, feeds, attachment pages, `?replytocom=` …) → drop; redirect to the closest useful page when they had traffic.
|
|
34
|
+
|
|
35
|
+
Decide the new URL scheme with the user (e.g. `/blog/2019/05/hello-world` → `/blog/hello-world`), and write the old → new mapping into the inventory now.
|
|
36
|
+
|
|
37
|
+
**Checkpoint:** the user approves the content types and the URL scheme.
|
|
38
|
+
|
|
39
|
+
## Phase 3 — Model
|
|
40
|
+
|
|
41
|
+
Follow "Model content with block types first" in `SKILL.md`:
|
|
42
|
+
|
|
43
|
+
1. Inspect the current schemas: `list_block_types`, `list_collections`.
|
|
44
|
+
2. For singleton pages, `create_block_type` per recurring section (hero, text + image, cards → `repeater`, FAQ → `repeater`, CTA). Reuse existing block types when they fit.
|
|
45
|
+
3. For each repeating type, `create_collection` with the fields the content really has: `title` (`string`), `excerpt` (`text`), `body` (`longText`, HTML), `cover` (`reference`, `model: "Media"`), `gallery` (`multiReference`), `published_on` (`datetime`), `tags` (`stringList`), `category` (`select`), SEO fields (`seo_title`, `seo_description`). No `json` fields for content editors maintain.
|
|
46
|
+
4. Mark the repository the images will go to as public first if the site needs permanent URLs: `manage_media_collection` with `is_public: true`.
|
|
47
|
+
|
|
48
|
+
**Checkpoint:** the user reviews the schemas in the panel before any import.
|
|
49
|
+
|
|
50
|
+
## Phase 4 — Media
|
|
51
|
+
|
|
52
|
+
1. From the inventory, list the distinct image and file URLs the migrated content uses (full-size originals only).
|
|
53
|
+
2. `import_media_from_urls` in batches of up to 50: `{ site_id, urls, collection_name }`. Each result has `status` (`imported`, `existing` when the URL was already imported, `duplicate` within the batch, `failed`) and `media_id` / `original_url`.
|
|
54
|
+
3. Save the old URL → `media_id` / `original_url` map. Retry `failed` items once; report the rest (private hosts, redirects, too large or unsupported types are refused on purpose — download those locally and use `request_media_upload`).
|
|
55
|
+
|
|
56
|
+
## Phase 5 — Content (as drafts)
|
|
57
|
+
|
|
58
|
+
1. Transform each item to the collection schema: clean the HTML (drop builder shortcodes, inline styles, tracking pixels), rewrite internal links to the **new** paths, and replace image URLs in the HTML with the `original_url` values from phase 4. Put media references (`cover`, `gallery`) as `media_id` values.
|
|
59
|
+
2. `bulk_upsert_entries` with `{ site_id, collection_slug, entries: [{ slug, external_id, data }], dry_run: true }`. Fix every `failed` item (the errors name the field, e.g. `data.published_on: …`) and check `warnings` (dropped keys = a field missing from the schema).
|
|
60
|
+
3. Run the same batch without `dry_run`. Entries are created as **drafts**; existing ones (same `external_id` or `slug`) are updated with a revision.
|
|
61
|
+
4. Singleton pages: `create_page` with `blocks` in the default locale; they start as drafts too. Other languages of the old site go in with `configure_locales` and `upsert_translation`, not as extra pages.
|
|
62
|
+
|
|
63
|
+
## Phase 6 — Redirects
|
|
64
|
+
|
|
65
|
+
1. From the inventory, one redirect per old URL whose path changes: `{ source: "/old/path", destination: "/new/path", status_code: 301 }`. Use 301 for permanent moves; 302/307 only for temporary ones.
|
|
66
|
+
2. Collapse patterns into dynamic rules when possible (`/category/*` → `/blog`, `/blog/:year/:month/:slug` → `/blog/:slug`): Crafty hosting allows 2000 static and 100 dynamic rules per deploy, including the site's own `_redirects`.
|
|
67
|
+
3. `source` must be a path (no domain, no `?query`, no `#fragment`); `destination` a path or an absolute http(s) URL. Don't redirect a URL to itself or create chains (A → B → C: point A straight at C).
|
|
68
|
+
4. `upsert_redirects` with `dry_run: true`, fix `failed`, then for real (500 per call). Check with `list_redirects`.
|
|
69
|
+
5. Hosted on Crafty: they apply on the next deploy. Custom frontend: read `GET /api/v1/redirects` in the middleware (Next.js `middleware.ts`, Nuxt server middleware) and answer with the `status_code` and `Location` header.
|
|
70
|
+
|
|
71
|
+
## Phase 7 — Human review
|
|
72
|
+
|
|
73
|
+
Give the user a short report: counts created/updated/failed per type, media imported/skipped, redirects created, and the list of items that need a human (failed transformations, missing images, pages that did not map to a block type). Ask them to review the drafts in the panel (and in the visual editor if it is configured). Apply their corrections with `update_entry` / `update_page`, or re-run the import batch (it updates by `external_id`).
|
|
74
|
+
|
|
75
|
+
## Phase 8 — Publish and switch over
|
|
76
|
+
|
|
77
|
+
Only after the user's explicit go-ahead:
|
|
78
|
+
|
|
79
|
+
1. Publish: `bulk_upsert_entries` again with `publish: true` (token with `crafty:publish`) or `publish_entry` / `publish_page` per item; or `schedule_publication` for a set launch time.
|
|
80
|
+
2. Deploy the new frontend, point the domain at it, then spot-check a sample of old URLs (they must answer 301 to the new ones) and the sitemap.
|
|
81
|
+
3. Keep the redirects: removing them later breaks links and search rankings.
|
|
82
|
+
|
|
83
|
+
## Not covered yet
|
|
84
|
+
|
|
85
|
+
There is no WordPress WXR importer, sitemap importer or broken-link report in the panel: the agent does those steps itself with the tools above.
|