Skip to main content

Host app extensions

Overview

Most endpoints live in decidim-restfull-* gems. You can also register routes from the host Decidim app (tenant-specific APIs, chatbot bridges, legacy paths) with the same Extension.register DSL.

Use this when the API is tied to one deployment and does not belong in a reusable gem.

Reference implementation: NCA chatbot (config/initializers/decidim_restfull_nca_chatbot.rb in the NCA host app).

When to use

  • One-off or tenant-specific HTTP surface on the RestFull mount (/api/rest_full/v0.3/…).
  • Controllers stay in the host app (app/controllers/decidim/…).
  • You still want OpenAPI, RSwag, and a TypeScript client in CI.

Prefer a decidim-restfull-<feature> gem when the API is reusable across deployments. See RestFull engines.

Procedure

1. Register the extension at boot

config/initializers/my_restfull_extension.rb:

Rails.application.config.after_initialize do
require "decidim/rest_full"
next unless defined?(Decidim::RestFull::Extension)

Decidim::RestFull::Extension.register(:my_feature) do |ext|
ext.open_api_definitions Rails.root.join("lib/decidim/my_app/rest_full/test_definitions.rb").to_s
ext.rswag_specs Rails.root.join("spec/requests/decidim/api/rest_full/my_app/**/*_spec.rb").to_s

ext.routes do
get "my_resource/:id", to: "/decidim/my_app/my_resources#show"
end
end
end

require "decidim/rest_full" is required: the gem is not fully loaded until Bundler resolves the host app.

2. Controller outside Decidim::Api::RestFull

Host controllers use an app-specific namespace (e.g. Decidim::MyApp::Api::MyResourcesController) with a concern for OAuth, not Decidim::Api::RestFull::ApplicationController (Zeitwerk conflict with decidim-api).

Include RestFull OAuth helpers from your app (Decidim::MyApp::Api::RestFullController or similar).

Routes still use absolute controller paths: to: "/decidim/my_app/my_resources#show".

3. OpenAPI definitions and RSwag specs

Same rules as feature gems:

  • Test definitions — register schemas in DefinitionRegistry.
  • RSwag — request specs under spec/requests/decidim/api/rest_full/<app>/.
  • describe_api_endpoint requires permissions: [] (or explicit abilities) and FactoryBot in swagger_helper.rb.

4. Generate merged OpenAPI

Host apps cannot run every RestFull gem spec in-process (Zeitwerk / Decidim::Api::RestFull loading). Merge instead:

  1. Start from decidim-restfull/openapi.json (full RestFull contract).
  2. Run host RSwag specs only to append your paths.

Example (NCA):

docker compose exec decidim bash -lc 'bin/gen-openapi'
# → openapi/nca-openapi.json

Host script pattern:

cp "$DECIDIM_RESTFULL_PATH/openapi.json" swagger/v1/swagger.json
GENERATE_OPENAPI=1 bundle exec rspec spec/requests/decidim/api/rest_full/my_app/**/*_spec.rb \
--format Rswag::Specs::SwaggerFormatter
mv swagger/v1/swagger.json openapi/my-openapi.json

5. TypeScript client

docker compose exec decidim bash -lc 'bin/gen-typescript-client'
# → contrib/my-decidim-client

Publish to your GitLab npm registry on main / tags (see host app CI). Consumer install:

npm install @<namespace>/my-decidim-sdk

See Generate clients and TypeScript SDK.

6. Swagger helper extras (host app)

spec/swagger_helper.rb typically needs:

require "decidim/rest_full/test/on_api_endpoint_methods"
require "decidim/rest_full/test/global_context"
RSpec.configure { |c| c.include FactoryBot::Syntax::Methods }

Load RestFull OAuth models if Zeitwerk does not pick them up (Decidim::RestFull::Core::ApiClient, etc.).

Include Decidim::RestFull::OrganizationClientIdsOverride on Decidim::Organization when factories create api_client records.

Rules

RuleDetail
Boot orderafter_initialize + require "decidim/rest_full" before Extension.register.
Routes and scopesExtension.register appends routes and re-merges OAuth scopes when the app is initialized — no manual Routes.draw!.
Controller pathAbsolute /decidim/... in ext.routes; avoid Decidim::Api::RestFull::* in host app.
OpenAPI mergeBase spec from monorepo + host RSwag only; do not re-run all gem specs in the host.
Legacy URLsKeep old paths in config/routes.rb until consumers migrate; document both in OpenAPI if needed.
is_protected: falseRSwag records security: [] and can corrupt OAuth scope enums; host gen-openapi scripts should restore grant schemas from the base spec after merge.
CIRegenerate OpenAPI + client on release; publish npm package from committed spec.

See also