TryGhost/Ghost · error · NotFoundError
Integration not found.
Error message
Integration not found.
What it means
A NotFoundError from `IntegrationsService.edit` when editing without a `keyid` and `IntegrationModel.edit(..., {require: true})` rejects with a Bookshelf `NotFound`/`EmptyResponse` message. That means no integration row matched the edit's `id` filter, so Ghost translates the low-level error into a clean 404.
Source
Thrown at ghost/core/core/server/services/integrations/integrations-service.js:42
}
try {
await this.ApiKeyModel.refreshSecret(model.toJSON(), Object.assign({}, options, {id: options.keyid}));
return await this.IntegrationModel.findOne({id: options.id}, {
withRelated: ['api_keys', 'webhooks']
});
} catch (err) {
throw new InternalServerError({
err: err
});
}
}
try {
return await this.IntegrationModel.edit(data, Object.assign(options, {require: true}));
} catch (error) {
if (error.message === 'NotFound' || error.message === 'EmptyResponse') {
throw new NotFoundError({
message: tpl(messages.notFound, {
resource: 'Integration'
})
});
}
throw error;
}
}
}
/**
* @returns {IntegrationsService} instance of the PostsService
*/
const getIntegrationsServiceInstance = ({IntegrationModel, ApiKeyModel}) => {
return new IntegrationsService({IntegrationModel, ApiKeyModel});
};
View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Verify the integration still exists (`GET /integrations/{id}`) before editing.
- Confirm you are sending the integration `id`, not a related api_key/webhook id.
- Remove any filter in `options` that would exclude the integration from the edit query.
- If it was deleted, re-create the integration instead of editing a stale id.
Example fix
// before
await integrations.edit({name: 'x'}, {id: webhookId, require: true}); // wrong id -> 404
// after
const integration = await integrations.findOne({id: integrationId});
if (!integration) throw new Error('Integration missing');
await integrations.edit({name: 'x'}, {id: integrationId}); Defensive patterns
Strategy: validation
Validate before calling
async function assertIntegrationExists(model, id, options = {}) {
const m = await model.findOne({id}, options);
if (!m) throw new Error(`Integration ${id} not found; it may have been deleted`);
return m;
} Type guard
const integrationExists = async (model, id, options) => Boolean(await model.findOne({id}, options)); Try / catch
try {
await integrations.edit(data, {id});
} catch (err) {
if (err.type === 'NotFoundError' && /Integration not found/i.test(err.message)) refreshIntegrations();
else throw err;
} Prevention
- GET the integration before editing to confirm it exists.
- Send the integration id, not a webhook/api-key id.
- Avoid options filters that exclude the target integration from the edit query.
When it happens
Trigger: PUT/PATCH to edit an integration whose `id` does not exist, was deleted, or is excluded by the caller's options filter (e.g. an ownership/scope filter that removes it). The `require: true` flag turns the silent empty update into a rejection that this catch maps to NotFoundError.
Common situations: The integration was deleted between page load and save; the id is wrong/truncated; the caller passes a webhook id or api-key id in the integration id field; an L10/scoped request filters out the integration so the edit matches nothing.
Related errors
- ApiKey not found.
- ${entityType} with id ${id} not found
- ${entityType} with id ${id} not found
- Cannot disconnect Stripe whilst you have active subscription
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/7fb6ccb33a14da59.
Report an issue: GitHub.