TryGhost/Ghost · error · Error
Failed to create ${entityType}: ${response.status()} ${error
Error message
Failed to create ${entityType}: ${response.status()} ${errorMessage} What it means
Thrown by ApiPersistenceAdapter.insert() when the POST to the configured endpoint returns a non-ok response. This is the test-data-factory's generic create-failure signal: it includes the HTTP status and, if the body parsed as JSON, the stringified error body. It is the e2e equivalent of a server-side create rejection — the Admin API refused to create the entity.
Source
Thrown at e2e/data-factory/persistence/adapters/api.ts:47
this.transformResponse = options.transformResponse || ((response: TResponse) => response as unknown);
}
protected buildUrl(path?: string): string {
const url = path ? `${this.endpoint}/${path}` : this.endpoint;
const params = new URLSearchParams(this.queryParams);
const queryString = params.toString();
return queryString ? `${url}?${queryString}` : url;
}
async insert<T>(entityType: string, data: T): Promise<T> {
const response = await this.httpClient.post(this.buildUrl(), {
data: this.transformRequest(data as unknown as TRequest)
});
if (!response.ok()) {
const errorBody = await response.json().catch(() => null);
const errorMessage = errorBody ? JSON.stringify(errorBody) : '';
throw new Error(`Failed to create ${entityType}: ${response.status()} ${errorMessage}`);
}
const body = await response.json() as TResponse;
return this.transformResponse(body) as T;
}
async findById<T>(entityType: string, id: string): Promise<T> {
const response = await this.httpClient.get(this.buildUrl(id));
if (response.status() === 404) {
throw new Error(`${entityType} with id ${id} not found`);
}
if (!response.ok()) {
throw new Error(`Failed to find ${entityType}: ${response.status()}`);
}
const body = await response.json() as TResponse;View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Read the embedded status + JSON body in the message — it names the validation failure (e.g. 422 with the field at fault).
- Ensure the factory was created from an authenticated HTTP client (e.g. createPostFactory(page.request) where page is the authenticated fixture).
- Confirm 'pnpm dev' is running and the admin dev server is reachable at http://127.0.0.1:5174 (per e2e AGENTS.md).
- Verify the endpoint and query params passed to ApiPersistenceAdapter match the live Admin API route.
- Run 'pnpm build' after factory changes and 'pnpm test:types' to catch contract drift.
Example fix
// before: unauthenticated request client → 401
const factory = new PostFactory(new ApiPersistenceAdapter({
httpClient: request, // bare Playwright request, no session cookie
endpoint: `${baseURL}/api/admin/posts/`
}));
await factory.create({title: 'X'}); // throws Failed to create posts: 401 ...
// after: use the authenticated page fixture's request
const factory = createPostFactory(page.request); // carries the session cookie
await factory.create({title: 'X'}); Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the HTTP client is authenticated and the endpoint is reachable before bulk-creating
async function clientCanPost(httpClient, endpoint) {
const res = await httpClient.get(endpoint);
return res.ok() || res.status() === 404; // 404 on the collection root is often fine
}
if (await clientCanPost(httpClient, endpoint)) {
await factory.createMany(list);
} Try / catch
try {
return await factory.create(options);
} catch (err) {
if (/Failed to create .*: 401|403/.test(err.message)) {
throw new Error('Factory HTTP client is not authenticated. Create it from the authenticated page fixture.');
}
throw err;
} Prevention
- Always create factories from the authenticated page.request fixture (e.g. createPostFactory(page.request)).
- Ensure 'pnpm dev' is running and reachable before the suite starts.
- Run 'pnpm build' and 'pnpm test:types' after factory/API contract changes.
When it happens
Trigger: A factory.create() call POSTs to e.g. /api/admin/posts/ and the server returns 4xx/5xx. Frequent causes in the e2e suite: the test's HTTP client isn't authenticated (cookies not propagated from the Playwright BrowserContext), the endpoint is wrong, a required field is missing or fails validation, or the Ghost dev server isn't running.
Common situations: Forgot to create the factory from an authenticated page.request (auth lives on the BrowserContext); baseURL not set so the endpoint resolves wrong; factory builds an entity violating a uniqueness constraint (e.g. duplicate slug); Ghost dev server down or still booting; field names changed after a Ghost API refactor but the factory wasn't updated.
Related errors
- ${entityType} with id ${id} not found
- Failed to find ${entityType}: ${response.status()}
- Failed to update ${entityType}: ${response.status()}
- Cannot create without a persistence adapter. Use buildMany()
- Failed to delete ${entityType}: ${response.status()}
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/1163ea525d903810.
Report an issue: GitHub.