Skip to content

Adding a social provider

Contributor guide for implementing a new social integration in OpenQuok

8 min read

Connect your agent today

Draft from chat, review in your calendar, and publish only what you approve.

Start for $0

Overview

Our social channels are provider classes registered in the backend, exposed through existing REST routes, and optionally wired in the web.

Connect is a fork of this checklist, not a separate product surface.

Pick one family:

FamilyOperator developer app?User actionOpenQuok env keysReference
OAuth (default)YesRedirect to the platformconfig.integrations.* plus self-host .env.example and a docker-compose rowThreads, Instagram, Facebook Page
Credentials in OpenQuokNoPaste a personal API key into Add ChannelNone — do not invent empty provider env placeholdersDev.to (devto)

Use Facebook (facebook), Instagram (Business) (instagram-business), and Threads (threads) as OAuth references. Use Dev.to (devto) as the credentials-in-app reference.

Convention reference: Contributors should follow the backend + web checklist in .cursor/rules/add-social-provider-integration.mdc alongside this guide.

OAuth:
  1. Web calls GET /api/v1/integrations/social/:providergenerateAuthUrl().
  2. User consents at the platform; browser returns to /integration/oauth/:provider.
  3. Web calls POST /api/v1/integrations/social-connect/:providerauthenticate().
  4. If isBetweenSteps is true, response includes pages; user picks an account → POST /api/v1/integrations/provider/:id/connectfetchPageInformation().
Credentials:
  1. Add Channel shows a catalog-driven form from customFields() (password input + regex). Invite-link copy excludes these providers.
  2. Web calls GET /api/v1/integrations/social/:providergenerateAuthUrl() seeds org/state cache. The returned url is the state string, not a platform redirect.
  3. Web calls POST /api/v1/integrations/social-connect/:provider with that state and code as base64 JSON of the pasted key.
  4. authenticate() validates the key with the platform and stores it as the access token. Refresh reuses the same form — do not send window.location to a non-URL state.
Publishing: the orchestrator loads post rows, builds PostDetails (content + JSON settings + media), and calls provider.post().

Backend checklist

1. Implement SocialProvider

Create a class under backend/integrations/providers/ implementing social.integrations.interface.ts.

Required surface:

MemberPurpose
identifier / nameCatalog slug and display name
scopesOAuth scopes (empty array for credentials providers)
isBetweenStepstrue when user must pick Page/account after OAuth
generateAuthUrl / authenticateOAuth start + code exchange, or credentials: customFields() metadata + authenticate reading base64 JSON from code
postPublish scheduled content
maxLengthCaption limit for API + UI

Common optional members:

MemberWhen
customFields()Credentials-in-app connect (API key / app password). Catalog drives the Add Channel form.
pages()Between-steps account list
fetchPageInformation()Finalize Page/token after picker
refreshToken + reConnectLong-lived token refresh (set refreshCron: true)
commentThread / follow-up replies
analytics / postAnalyticsInsights dashboards
validateCreatePostServer-side schedule validation
globalPlugCatalog / internalPlugCatalogChannel or post-compose plugs
settingsSchema()Typed publish object for GET /public/integration-settings/:id
tools()Allow-listed methods for POST /public/integration-trigger/:id
Config rule (OAuth): read secrets only from config.integrations.* in GlobalConfig.ts — never process.env inside provider code. Config rule (credentials): skip GlobalConfig.ts, .env.development.example, and infra/self-host/.env.example. The user-pasted key lives on the integration row. Redirect URIs (OAuth): build with oauthFrontendOrigin() + oauthFrontendSocialCallbackPath(identifier) so local HTTP dev uses the HTTPS relay consistently. Media: composer stores object keys in post JSON; resolve public URLs with publicUrlForObjectKey (see Threads / Facebook publish helpers).

2. Register the provider

Add new YourProvider() to the array in backend/integrations/integrationManager.ts.

No new API routes are required — existing integration endpoints dispatch by identifier.

3. Token refresh / between-steps storage

If OAuth returns a user token but publishing needs a Page or sub-account token, follow the Instagram (Business) / Facebook pattern in IntegrationConnectionService.saveProviderPageForOrganization:

  • Store the Page token in token.
  • Keep the user token in refresh_token.
  • Keep the pre-picker user id in root_internal_id for reConnect during cron refresh.

Extend the preservesUserTokenForRefresh branch when adding another Meta-style provider.

4. Environment variables (OAuth only)

Skip this step when customFields() is set.

Add keys to backend/config/GlobalConfig.ts and backend/.env.development.example. If orchestrator workers need the same keys, mirror them per the orchestrator env rule.

Also add empty placeholders under Social provider apps in infra/self-host/.env.example, and document the ID/secret pair in Self-host — Docker Compose.

For credentials providers, document in that docker-compose page that the channel needs no operator app (user API key in the dashboard). Do not invent empty provider env placeholders.

5. Database

Usually no migrationintegrations.provider_identifier is free text. Add migrations only for new columns, plug tables, or RLS changes.

6. Tests (when behavior is non-trivial)

Add unit tests beside the provider (OAuth or credentials connect, publish payload shaping). Extend IntegrationConnectionService.unit.test.ts when between-steps save logic differs, when customFields skips the OAuth verifier, or when a real settingsSchema() object is returned.

Web checklist

The connect catalog is backend-driven (GET /integrations). The composer is opt-in per provider.

1. Launch provider config

Add web/src/lib/ui/components/posts/providers/[id]/[id].provider.ts exporting a LaunchProviderConfig (maximumCharacters, postComment, optional checkValidity).

Register it in getLaunchProviderConfig inside providers/index.ts.

Add a provider-specific preview Svelte component and branch in ShowAllProviders.svelte.

When the provider has compose-time settings (step 6), the Post Preview column must reflect them live — not show placeholders because preview received an empty settings object.

LayerAction
[id]Preview.svelteAccept a providerSettings prop; read it with read*LaunchSettings; render title, tags, cover, and other fields the Settings panel writes.
ShowAllProviders.sveltePass providerSettings on every preview branch that consumes compose settings — not only Dev.to.
AddEditModal.svelteDerive effective preview settings from previewChannel.id + providerSettingsByIntegrationId and pass them to ShowAllProviders.
Parent modalsDo not pass previewProviderSettings from followUpTargetIntegrationId — that ID is only set for Threads/Instagram follow-up replies and leaves other providers with an empty settings object.
References: Dev.to (title, tags, series), YouTube (title, thumbnail), TikTok photo carousel (title). Landing bento mocks should pass explicit mock providerSettings into ShowAllProviders (see BentoDevtoSettingsPreview.svelte).

2b. User docs — editor table

When composer support ships, update Writing the post (web/src/content/docs/creating-posts/writing-the-post.md) → Editor by platform.

Map SocialProvider.editor on the provider class to the Used by column:

editorAction
normalAdd platform to Standard row if not covered by “most social channels”; otherwise no change.
markdownAppend the platform name when unlocked on Markdown row (e.g. Threads when unlocked).
htmlAppend on HTML row (note if publish strips to plain text, like X).
noneFirst live Plain provider: replace “None yet” with that platform.
Rules: one table only (Editor | Toolbar | Used by); change Toolbar only for real toolbar exceptions; bump lastUpdated in the same PR; editor on the provider class is the source of truth.

3. OAuth between-steps UI

Reuse IntegrationContinue.svelte on route /integration/oauth/[provider]. When isBetweenSteps is true:

  • Add a config under web/src/lib/integrations/continue-provider/ and register it in continue-provider/index.ts (title, empty-state copy, icon, and toSaveParams for saveProviderPage).
  • Connect response pages is passed through ContinueIntegration.presenter.svelte.ts; the shared ContinueProviderPicker.svelte renders the list.

4. Credentials connect UI

When the catalog includes customFields:

  • Show the provider in the normal Add Channel grid (AddProvider.svelte). Keep filtering these providers from invite links.
  • Open a credentials dialog (password input + regex from catalog). Submit: getAuthorizeUrl then connectSocial with state = returned url and code = base64 JSON of the key.
  • Reuse the same dialog from IntegrationContinue.svelte so refresh works without an OAuth redirect. Do not assign window.location.href to a non-URL state string.

5. Labels and icons

Add display names to web/src/data/social-providers.ts if the slug is new. Icons may already exist for marketing placeholders.

6. Settings panel (optional)

If the provider needs compose-time options (Instagram post type, Dev.to title/tags, etc.), add Svelte settings under providers/[id]/ and wire SettingsAccordion.svelte. Emit only that provider’s bucket.

Documentation and agent resources

When shipping a user-facing provider, add:

ArtifactLocation
Setup guideweb/src/content/docs/social-integration/[id].md
Index LinkCardsocial-integration/index.md
CLI examplesweb/src/content/docs/cli-examples/[id].md
Composer editor modesweb/src/content/docs/creating-posts/writing-the-post.md — update Editor by platform Used by for the provider’s editor (normal, markdown, html, none)
Agent recipesagent/skills/openquok-core/resources/[id]-examples.md
Identifier listagent/skills/openquok-core/resources/patterns.md
OAuth setup guides document operator app IDs, secrets, and redirect URIs. Credentials setup guides document where the user creates the API key; skip operator ID/secret rows and backend env sections.

Follow Documentation contribution for MDX components, env badges, and redirect URI placeholders.

Reference providers

PR review prompts

Before opening a PR, confirm:

  • Provider is registered in integrationManager.ts.
  • OAuth: Redirect URI in the platform console matches /integration/oauth/[identifier] exactly. New operator env keys exist in infra/self-host/.env.example and the docker-compose social-apps table.
  • Credentials: no new env vars; docker-compose callout that there is no operator app; dashboard Add Channel and refresh work; GET /public/social/[identifier] returns 400.
  • No secrets or third-party project names in comments or docs (repo neutrality rule).
  • Composer validation matches backend validateCreatePost / publish rules.
  • Post Preview: when compose settings ship, preview reads providerSettings via read*LaunchSettings; Settings changes update preview live; AddEditModal.svelte derives settings from the preview channel (not followUpTargetIntegrationId).
  • Live vs Development mode called out in docs when media visibility differs.
Search documentation
Find a docs page
Discord Support