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

  1. Verify the integration still exists (`GET /integrations/{id}`) before editing.
  2. Confirm you are sending the integration `id`, not a related api_key/webhook id.
  3. Remove any filter in `options` that would exclude the integration from the edit query.
  4. 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

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


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/7fb6ccb33a14da59. Report an issue: GitHub.