Type-safe packages for generating or serving files under /.well-known/ across frontend and server
frameworks.
The project models each well-known file as a provider. Providers validate their configuration and generate the file body; framework integrations load the configuration and write the result into the application's public assets directory.
| Package | Purpose |
|---|---|
@well-known-js/core |
Provider types and the defineConfig helper |
@well-known-js/agent-card |
Agent2Agent Agent Card provider and schema |
@well-known-js/api-catalog |
RFC 9727 API Catalog Linkset provider and schema |
@well-known-js/gpc |
Global Privacy Control support resource provider and schema |
@well-known-js/did-web |
did:web DID document provider and schema |
@well-known-js/did-configuration |
DIF DID Configuration resource provider and schema |
@well-known-js/mta-sts |
RFC 8461 SMTP MTA-STS policy provider and schema |
@well-known-js/sbom |
RFC 9472 software bill of materials provider |
@well-known-js/apple-app-site-association |
Apple App Site Association provider and schema |
@well-known-js/assetlinks |
Android Digital Asset Links provider and schema |
@well-known-js/security-txt |
RFC 9116 security.txt provider and schema |
@well-known-js/webauthn |
WebAuthn Related Origin Requests provider and schema |
@well-known-js/passkey-endpoints |
W3C passkey endpoint discovery provider and schema |
@well-known-js/openid-configuration |
OpenID Connect Discovery metadata provider and schema |
@well-known-js/oauth-authorization-server |
OAuth 2.0 Authorization Server Metadata provider and schema |
@well-known-js/oauth-protected-resource |
OAuth 2.0 Protected Resource Metadata provider and schema |
@well-known-js/vite |
Framework-agnostic Vite plugin |
@well-known-js/sveltekit |
SvelteKit adapter using the Vite plugin with static defaults |
@well-known-js/next |
Next.js adapter generating files in public |
@well-known-js/nuxt |
Nuxt module generating files in public |
@well-known-js/nestjs |
NestJS module exposing well-known routes directly |
@well-known-js/express |
Express middleware exposing well-known routes directly |
@well-known-js/tanstack-start |
TanStack Start server route handlers |
@well-known-js/fastify |
Fastify plugin exposing well-known routes |
@well-known-js/hono |
Hono middleware exposing well-known routes |
@well-known-js/astro |
Astro integration generating public files |
@well-known-js/react-router |
React Router resource route loader |
@well-known-js/angular |
Angular SSR server middleware |
@well-known-js/solidstart |
SolidStart server route handlers |
@well-known-js/koa |
Koa middleware exposing well-known routes |
@well-known-js/elysia |
Elysia plugin exposing well-known routes |
@well-known-js/adonisjs |
AdonisJS middleware exposing well-known routes |
@well-known-js/node |
Native Node.js and Fetch handlers |
- Node.js 20 or later
- Vite 5 or later for the Vite integrations
- SvelteKit 2 or later when using
@well-known-js/sveltekit - Next.js 15 or later when using
@well-known-js/next - Nuxt 3 or later when using
@well-known-js/nuxt - NestJS 11 or later when using
@well-known-js/nestjs - Express 5 or later when using
@well-known-js/express
Install the workspace dependencies, then start every example at once:
pnpm install
pnpm run devEach application uses a fixed, unique port. Test its Apple App Site Association response at the listed URL:
| Integration | Port | AASA URL |
|---|---|---|
| Vite | 5100 | http://localhost:5100/.well-known/apple-app-site-association |
| SvelteKit | 5101 | http://localhost:5101/.well-known/apple-app-site-association |
| Next.js | 5102 | http://localhost:5102/.well-known/apple-app-site-association |
| Nuxt | 5103 | http://localhost:5103/.well-known/apple-app-site-association |
| Astro | 5104 | http://localhost:5104/.well-known/apple-app-site-association |
| Express | 5105 | http://localhost:5105/.well-known/apple-app-site-association |
| NestJS | 5106 | http://localhost:5106/.well-known/apple-app-site-association |
| Fastify | 5107 | http://localhost:5107/.well-known/apple-app-site-association |
| Hono | 5108 | http://localhost:5108/.well-known/apple-app-site-association |
| Koa | 5109 | http://localhost:5109/.well-known/apple-app-site-association |
| Elysia | 5110 | http://localhost:5110/.well-known/apple-app-site-association |
| Node.js | 5111 | http://localhost:5111/.well-known/apple-app-site-association |
| TanStack Start | 5112 | http://localhost:5112/.well-known/apple-app-site-association |
| React Router | 5113 | http://localhost:5113/.well-known/apple-app-site-association |
| Angular SSR | 5114 | http://localhost:5114/.well-known/apple-app-site-association |
| SolidStart | 5115 | http://localhost:5115/.well-known/apple-app-site-association |
| AdonisJS | 5116 | http://localhost:5116/.well-known/apple-app-site-association |
For a Vite project:
pnpm add @well-known-js/core @well-known-js/viteInstall the providers required by the project. For Apple Universal Links:
pnpm add @well-known-js/apple-app-site-associationFor SvelteKit, use the dedicated adapter instead of importing the generic Vite plugin directly:
pnpm add @well-known-js/core @well-known-js/sveltekit
pnpm add @well-known-js/apple-app-site-associationCreate well-known.config.ts at the project root:
import { appleAppSiteAssociation } from "@well-known-js/apple-app-site-association";
import { defineConfig } from "@well-known-js/core";
export default defineConfig({
providers: [
appleAppSiteAssociation({
applinks: {
details: [
{
appIDs: ["ABCDE12345.com.example.app"],
components: [
{
"/": "/*",
},
],
},
],
},
}),
],
});defineConfig also prevents two providers from targeting the same well-known path.
Add the plugin to vite.config.ts:
import { defineConfig } from "vite";
import { wellKnown } from "@well-known-js/vite";
export default defineConfig({
plugins: [wellKnown()],
});By default, files are written to Vite's resolved publicDir. The plugin regenerates them when
well-known.config.ts changes during development.
Options can override both paths:
wellKnown({
configFile: "config/well-known.ts",
outputDir: "public",
});Both paths are resolved from the Vite project root.
Use @well-known-js/sveltekit alongside the SvelteKit Vite plugin:
import { sveltekit } from "@sveltejs/kit/vite";
import { wellKnown } from "@well-known-js/sveltekit";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [wellKnown(), sveltekit()],
});The adapter writes generated files to static by default. A custom SvelteKit assets directory can
be supplied with staticDir:
wellKnown({
configFile: "well-known.config.ts",
staticDir: "assets",
});With the AASA provider, the generated file is available at:
/.well-known/apple-app-site-association
Install the Asset Links provider:
pnpm add @well-known-js/assetlinksAdd it to well-known.config.ts with the Android package name and SHA-256 certificate
fingerprints:
import { assetLinks } from "@well-known-js/assetlinks";
assetLinks({
statements: [
{
relation: ["delegate_permission/common.handle_all_urls"],
target: {
namespace: "android_app",
package_name: "com.example.app",
sha256_cert_fingerprints: [
"AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA",
],
},
},
],
});The provider also accepts Web targets and { include: "https://..." } statements. The generated
file is available at:
/.well-known/assetlinks.json
Install the RFC 9116 provider:
pnpm add @well-known-js/security-txtAdd at least one contact and a future expiry date to well-known.config.ts:
import { securityTxt } from "@well-known-js/security-txt";
securityTxt({
contacts: ["mailto:security@example.com"],
expires: new Date(Date.now() + 180 * 24 * 60 * 60 * 1000),
canonical: ["https://example.com/.well-known/security.txt"],
policy: ["https://example.com/security-policy"],
preferredLanguages: ["en", "fr"],
});The generated RFC 9116 document is served as text/plain; charset=utf-8 at:
/.well-known/security.txt
Install the WebAuthn provider:
pnpm add @well-known-js/webauthnList the HTTPS origins allowed to share credentials with the relying party ID:
import { webAuthn } from "@well-known-js/webauthn";
webAuthn({
origins: ["https://example.co.uk", "https://login.example.com"],
});The generated JSON document is available at:
/.well-known/webauthn
Install the passkey endpoint discovery provider:
pnpm add @well-known-js/passkey-endpointsAdvertise direct account workflows for credential managers:
import { passkeyEndpoints } from "@well-known-js/passkey-endpoints";
passkeyEndpoints({
enroll: "https://example.com/account/passkeys/create",
manage: "https://example.com/account/passkeys",
prfUsageDetails: "https://example.com/help/passkeys",
});The generated JSON document is available at:
/.well-known/passkey-endpoints
Install @well-known-js/openid-configuration, then configure the issuer, its endpoints and
supported identifiers:
import { openIdConfiguration } from "@well-known-js/openid-configuration";
openIdConfiguration({
issuer: "https://id.example.com",
authorization_endpoint: "https://id.example.com/authorize",
token_endpoint: "https://id.example.com/token",
jwks_uri: "https://id.example.com/jwks",
response_types_supported: ["code"],
subject_types_supported: ["public"],
id_token_signing_alg_values_supported: ["RS256"],
});The metadata is available at /.well-known/openid-configuration.
Install the RFC 8414 provider to advertise an OAuth authorization server's endpoints and capabilities:
pnpm add @well-known-js/oauth-authorization-serverimport { oauthAuthorizationServer } from "@well-known-js/oauth-authorization-server";
oauthAuthorizationServer({
issuer: "https://id.example.com",
authorization_endpoint: "https://id.example.com/authorize",
token_endpoint: "https://id.example.com/token",
jwks_uri: "https://id.example.com/jwks.json",
response_types_supported: ["code"],
grant_types_supported: ["authorization_code"],
code_challenge_methods_supported: ["S256"],
});The metadata is available at /.well-known/oauth-authorization-server. Issuer paths are appended
to the endpoint according to RFC 8414.
Install the RFC 9728 provider to advertise the authorization servers and capabilities of an OAuth protected resource:
pnpm add @well-known-js/oauth-protected-resourceimport { oauthProtectedResource } from "@well-known-js/oauth-protected-resource";
oauthProtectedResource({
resource: "https://api.example.com",
authorization_servers: ["https://id.example.com"],
scopes_supported: ["read", "write"],
bearer_methods_supported: ["header"],
resource_documentation: "https://api.example.com/docs",
});The metadata is available at /.well-known/oauth-protected-resource. If the resource identifier
contains a path, it is appended to the metadata endpoint according to RFC 9728; for example,
https://api.example.com/public/mcp uses
/.well-known/oauth-protected-resource/public/mcp.
Install the Next.js adapter and the providers required by the project:
pnpm add @well-known-js/core @well-known-js/next
pnpm add @well-known-js/apple-app-site-associationCreate an App Router catch-all route at app/.well-known/[...path]/route.ts:
import { createWellKnownRouteHandlers } from "@well-known-js/next";
import wellKnownConfig from "../../../well-known.config";
export const { GET, HEAD } = createWellKnownRouteHandlers(wellKnownConfig);The handlers serve the provider body directly with its content type and return 404 for an unknown well-known path. Because the configuration is imported by the route, Next.js tracks it as part of the application build.
Install the Nuxt module and the providers required by the project:
pnpm add @well-known-js/core @well-known-js/nuxt
pnpm add @well-known-js/apple-app-site-associationRegister the module in nuxt.config.ts:
import wellKnown from "@well-known-js/nuxt";
export default defineNuxtConfig({
modules: [wellKnown],
});The module reads well-known.config.ts, writes generated files to Nuxt's public directory, and
adds their content types to Nitro route rules. Files are regenerated when the configuration changes
during development.
Both paths can be customized through the module configuration:
export default defineNuxtConfig({
modules: [wellKnown],
wellKnown: {
configFile: "config/well-known.ts",
publicDir: "public",
},
});Install the NestJS module and the providers required by the project:
pnpm add @well-known-js/core @well-known-js/nestjs
pnpm add @well-known-js/apple-app-site-associationImport the same well-known configuration directly into the application module:
import { Module } from "@nestjs/common";
import { WellKnownModule } from "@well-known-js/nestjs";
import wellKnownConfig from "../well-known.config.js";
@Module({
imports: [WellKnownModule.forRoot(wellKnownConfig)],
})
export class AppModule {}The configured files are exposed directly by NestJS with their provider content types. Asynchronous
configuration is available through WellKnownModule.forRootAsync(...).
If the application uses a global prefix, exclude /.well-known/* when configuring that prefix so
the standard URLs remain available at the domain root.
Install the Express middleware and the providers required by the project:
pnpm add @well-known-js/core @well-known-js/express
pnpm add @well-known-js/apple-app-site-associationRegister the middleware before the application's fallback routes:
import express from "express";
import { wellKnown } from "@well-known-js/express";
import wellKnownConfig from "../well-known.config.js";
const app = express();
app.use(wellKnown(wellKnownConfig));The middleware serves configured GET and HEAD requests with their provider content types. Other
methods and unknown paths are delegated to the next Express handler.
All server integrations consume the same WellKnownConfig and preserve provider paths, bodies, and
content types:
| Ecosystem | Registration API |
|---|---|
| Node.js | createWellKnownNodeHandler(config) |
| Fetch | createWellKnownFetchHandler(config) |
| Fastify | fastify.register(wellKnown, { config }) |
| Hono | app.use(wellKnown(config)) |
| Koa | app.use(wellKnown(config)) |
| Elysia | app.use(wellKnown(config)) |
| TanStack | createWellKnownServerHandler(config) |
| React Router | createWellKnownLoader(config) |
| SolidStart | createWellKnownRouteHandlers(config) |
| Angular SSR | wellKnown(config) before the Angular SSR handler |
| AdonisJS | registerWellKnownRoutes(router, config) |
Astro uses a build integration instead:
import wellKnown from "@well-known-js/astro";
export default defineConfig({
integrations: [wellKnown()],
});It generates the configured files in Astro's public directory and regenerates them when the configuration changes during development.
A provider instance declares its destination and returns the generated file:
import type { WellKnownProviderInstance } from "@well-known-js/core";
export function exampleProvider(): WellKnownProviderInstance {
return {
name: "example",
path: "/.well-known/example",
generate() {
return {
path: "/.well-known/example",
filename: "example",
contentType: "application/json",
body: JSON.stringify({ enabled: true }),
};
},
};
}Add the returned instance to the providers array in well-known.config.ts.
This repository uses pnpm workspaces:
pnpm install
pnpm typecheck
pnpm test
pnpm build
pnpm check:integrations
pnpm format:check
pnpm lintEvery framework adapter has an example under examples. Feature and integration coverage is
declared in well-known.features.json; ready features must be configured by every example, while
features under development are reported as warnings by pnpm check:integrations.
The committed .release/plan.json is the source of truth for the next package versions and release
mode. Releases can use either strategy:
grouped: selected packages receive one shared version and one GitHub tag such asv1.1.0;independent: each selected package receives its own version and tag such as@well-known-js/core@1.2.0.
Run the Prepare release workflow to create a pull request containing the package version and
lockfile changes. Its default auto mode detects package directories changed since each package's
last release. Previously published packages receive a patch bump by default, while new packages keep
their initial manifest version. When every proposed version matches, the workflow uses a grouped
release; otherwise it uses independent versions. The form can override the bump, selected packages,
strategy, or exact versions.
Review the generated .release/plan.json, adjust it in the pull request if needed, and merge it.
Then run Publish release from the main branch. It repeats every workspace check, publishes
missing versions to npm in dependency order, and then creates the corresponding tags and GitHub
Releases. Already published versions are skipped so a partially completed release can be retried
safely.
Configure npm Trusted Publishing for the release-publish.yml workflow, or add an NPM_TOKEN
secret to the npm GitHub environment. Protecting that environment with required reviewers is
recommended.
The same preparation can be run locally after editing the release plan:
pnpm release:prepareThe publish commands are intentionally separate and should normally run only in GitHub Actions:
pnpm release:publish
pnpm release:githubThe Standards watch workflow checks the living specifications and vendor documentation used by the provider packages every Monday. When a source changes, it opens a labeled GitHub issue for the affected package without duplicating an issue for the same upstream revision. After reviewing a change and updating the implementation when necessary, refresh and commit the recorded fingerprints:
pnpm standards:refresh