Skip to main content

Commands

Codemods

Run the v3 codemods

The v3 codemods run only when you name them; --all never selects them. Preview every change first, then run the same command without --dry-run:

npx @c15t/cli@alpha codemods packages-to-c15t consent-provider-options root-exports-to-subpaths use-consent-manager-to-hooks callbacks-to-v3 policy-packs-to-policy-rules --dry-run --json

Name several codemods in one command and they run in a fixed order, whatever order you name them in, each seeing the previous one's edits. codemods --list prints that order. Codemods that can't finish a change leave a TODO(c15t v3) comment next to it. Most also leave the v2 code in place, so type-checking fails at each one until you resolve it.

IDRewritesLeaves a TODO(c15t v3) for
packages-to-c15t@c15t/react and @c15t/nextjs imports to c15t/react, c15t/next and their subpaths, and removes styles.css importsStylesheet imports Tailwind CSS 3 or a cascade layer needs, and components/integrations imports
consent-provider-optionsConsentManagerProvider, ConsentManagerOptions and ConsentManagerProviderProps to the ConsentProvider names; mode: 'hosted' with backendURL, headers and customFetch to mode: hosted({ backendURL, headers, fetch }); mode: 'offline' with offlinePolicy.policyPacks to mode: offline({ policyRules }); iframeBlockerConfig to iframeBlockermode: 'custom' with endpointHandlers, a mode set from a variable, retryConfig, other offlinePolicy keys and hand-written policy packs
root-exports-to-subpathsImports of names that left the c15t/react, c15t/next, @c15t/react and @c15t/nextjs roots, moved to /headless, /consent-dialog-trigger, /types or /components/consent-bannerRemoved names such as useConsentScript, YouTubeEmbed and ConsentButton
use-consent-manager-to-hooksuseConsentManager() fields to one hook eachFields with no one-call replacement
scripts-to-integrations@c15t/scripts imports to @c15t/integrationsNothing
dev-tools-to-c15t@c15t/dev-tools/react and @c15t/dev-tools/tanstack imports to c15t/next/devtools or c15t/react/devtools, and removes the namespace propRemoved store helpers such as getC15tStore
policy-packs-to-policy-rulespolicyPackPresets to policyRulePresets and worldNoBanner() to worldOptOutNoPrompt()Nothing
css-variables-to-v3--consent-widget-* to --consent-manager-* and --frame-* to --consent-gate-*--consent-widget-accordion-*
postcss-tailwind3Adds the c15t Tailwind CSS 3 plugin before tailwindcss in postcss.configNothing; prints a warning for configs it can't edit
callbacks-to-v3onConsentChanged to onChoiceRecordedThe onChoiceRecorded payload, onConsentSet and onBannerFetched
theme-to-consent-themetheme.slots.consentDialogFooter to consentWidgetFooterTheme tokens, which need ConsentTheme or generateThemeCSS(), and theme.slots.frame
iab-option-to-iab-providerNothingThe iab provider option, which moves to IABProvider
node-sdk-to-v3c15tClient() and new C15TClient() to createC15tClient(), client and per-call options, method names, type and error namesEach call site, prefix, debug, dropped retry options, and per-call throw, onSuccess, onError, body, query and method
backend-config-to-v3policyPacks (as policyRules), branding, customTranslations, i18n and appName into manifestadapter, disableGeoLocation, iab, cache, logger, telemetry, tablePrefix, background and removed entries

v2 still accepted the deprecated translations option, and v3 removes it. The v1 to v2 transform translations-to-i18n moves it to i18n; name it to run it on a v2 app.

The codemods edit .ts, .tsx, .js, .jsx, .mts, .cts, .mjs and .cjs files. packages-to-c15t and css-variables-to-v3 also edit .css, .scss, .sass and .less files. None of them edit package.json, lockfiles, or .vue, .svelte and .astro files.

Package imports

packages-to-c15t runs first, so the other codemods see the c15t entries. It moves @c15t/react to c15t/react and @c15t/nextjs to c15t/next, keeping the subpath: @c15t/react/headless becomes c15t/react/headless. When package.json lists next or @c15t/nextjs, the @c15t/react root moves to c15t/next, which re-exports the React components and hooks. The v2 @c15t/react/cookie-banner alias becomes c15t/react/components/consent-banner. It rewrites imports, including import x = require(), re-exports, import types, and import(), require(), vi.mock() and jest.mock() calls with a string literal. It leaves c15t/react, @c15t/integrations and other packages alone. In a PostCSS config, it points the @c15t/react/postcss-tailwind3 and @c15t/nextjs/postcss-tailwind3 plugins at c15t/postcss-tailwind3, whether the config names them as a plugins object key, computed keys such as ['@c15t/react/postcss-tailwind3'] included, or in require().

c15t ships ESM only from v3. require() loads it only on Node.js 20.19+ or 22.12+, which load ESM through require() by default. Each require() call and import x = require() the codemod rewrites gets a TODO, and the run lists every such file with its count. To support older runtimes, convert those files to import, or a PostCSS config to postcss.config.mjs. The codemod doesn't convert CommonJS for you.

It removes @c15t/react/styles.css and @c15t/nextjs/styles.css imports, including the iab/ variants, because v3 components add their own styles. It reads each stylesheet @import whole, so one that spans lines or takes Less options such as (css) is handled too. In Sass it treats @use and @forward the same way as @import, and a pkg: URL such as pkg:@c15t/react/styles.css the same way as the bare one, keeping the pkg: prefix when it rewrites it. In a Sass import that lists several targets, such as @import 'theme', '@c15t/react/styles.css';, it handles each target on its own and keeps the others. When package.json lists tailwindcss 3 in any dependency field, the import is a .tw3.css variant, a stylesheet @import has a layer(), supports() or media condition, or a Sass @use or @forward has an as, with(), show or hide clause, it keeps the import, points it at styles.css from the c15t entry, and leaves a TODO(c15t v3) comment to set styles: false. For a tailwindcss version that isn't a semver range, such as a catalog, a link:, file: or git specifier, or latest, or a range over several majors such as workspace:* or ^3 || ^4, it reads the installed tailwindcss. If it still can't tell the version, it keeps the import the same way and prints a warning.

If package.json lists @c15t/react or @c15t/nextjs but not c15t v3, it leaves the scoped imports and prints a warning. For a c15t version that isn't a semver range, such as catalog:, or a range over several majors, it reads the installed c15t, and keeps the scoped imports when nothing is installed. While those packages are v2, it leaves their stylesheet imports and mocks as they are too, since v2 components don't add their own styles. For a version that isn't a semver range, such as catalog:, it reads the installed package, and treats the package as v2 when nothing is installed. At v3, it still removes the stylesheet imports. Replace the packages with c15t@alpha and run it again.

Provider, transports and options

consent-provider-options finds option objects passed to the provider's options prop, variables typed as ConsentManagerOptions or ConsentProviderOptions, and objects passed to getOrCreateConsentRuntime() from c15t. It imports hosted or offline from the entry the provider comes from. A provider from c15t/next or c15t/tanstack-start gets them from c15t/react, and one from @c15t/nextjs or @c15t/tanstack-start from @c15t/react: those framework entries export hosted() as plain data for defineConsentConfig, and ConsentProvider needs the transport. A hosted mode without backendURL gets v2's default, hosted({ backendURL: '/api/c15t' }). A mode set from a variable or expression gets a TODO(c15t v3) comment, unless it already holds a transport such as manifest() or hosted(). A module that re-exports ConsentManagerProvider keeps that name as an alias, so its importers still resolve.

callbacks-to-v3, theme-to-consent-theme and iab-option-to-iab-provider read the same option objects. Options built in another file, or spread from another object, are not found; search for the old keys by hand.

Moved exports

root-exports-to-subpaths keeps the framework you import from where v3 has a matching entry: useHeadlessConsentUI from c15t/next moves to c15t/next/headless. Trigger atoms, token types and flat banner parts exist only on the React entries, so imports of them from c15t/next move to c15t/react/..., and from @c15t/nextjs to @c15t/react/.... Types such as AllConsentNames move to c15t, or @c15t/core for scoped packages. With import * as c15t from 'c15t/react', it rewrites c15t.useColorScheme to useColorScheme imported from its new entry, and leaves a TODO(c15t v3) comment when that name is already taken. It also marks export * from a framework root, which no longer re-exports the moved names.

policy-packs-to-policy-rules moves policyPackPresets imports from React and Next.js entries to c15t, or @c15t/core for scoped packages, because the v3 React and Next.js entries don't export presets. Calls to presets need no other change. Hand-written policy packs use a different format in v3; consent-provider-options and backend-config-to-v3 flag them.

dev-tools-to-c15t picks c15t/next/devtools when package.json lists next or @c15t/nextjs, and c15t/react/devtools otherwise. If package.json lists the scoped packages but not c15t v3, it uses @c15t/nextjs/devtools or @c15t/react/devtools. It reads the c15t version the same way as packages-to-c15t.

Styles

css-variables-to-v3 renames only the variables v2 defined, so your own variables such as --frame-width stay as they are. It renames them in stylesheets and in string and template literals, including inline style objects.

postcss-tailwind3 runs when package.json lists tailwindcss 3 and c15t. For a version that isn't a semver range, such as a catalog, a link:, file: or git specifier, or latest, or a range over several majors such as workspace:* or ^3 || ^4, it reads the installed tailwindcss, and prints a warning if it still can't tell. It adds 'c15t/postcss-tailwind3': {}, or the @c15t/nextjs/postcss-tailwind3 or @c15t/react/postcss-tailwind3 plugin when the app uses scoped packages without c15t v3, to an object-form postcss.config.{js,cjs,mjs,ts}. It leaves an array-form config unchanged. When the array doesn't list a c15t plugin, it prints a warning; add the plugin by hand as Tailwind CSS 3 shows.

Node.js SDK and backend

node-sdk-to-v3 follows clients created in the same file, including class properties, parameters typed as C15TClient, and clients made through import * as sdk from '@c15t/node-sdk'. It rewrites client options written in the call or in a variable in the same file; options from anywhere else get a TODO(c15t v3) comment. It turns type: 'a,b' strings into types: ['a', 'b'], and moves a v2 init(options) argument to init(undefined, options). In per-call options it renames timeout to timeoutMs and retryConfig to retry. Call options passed as a variable or spread get a TODO(c15t v3) comment. A file that receives the client from another module keeps its old method names; search for them by hand.

backend-config-to-v3 reads objects passed to defineConfig() or c15tInstance() from @c15t/backend, and variables typed as C15TOptions. It merges the moved keys into an existing manifest object, and rewrites imports from the removed @c15t/backend/define-config entry.

Migrate useConsentManager() to v3 hooks

v3 removed useConsentManager(). The use-consent-manager-to-hooks codemod replaces each destructured field with the hook that reads it:

npx @c15t/cli@alpha codemods use-consent-manager-to-hooks --dry-run --json

Review the before and after contents in the JSON result, then run the same command without --dry-run to write the files.

The codemod reads imports from c15t/react, c15t/next, c15t/tanstack-start, @c15t/react, @c15t/nextjs, @c15t/tanstack-start and their /headless entries. For each useConsentManager() destructuring, it:

  • Replaces fields that map to one hook, such as activeUI with useActiveUI() ?? 'none'.
  • Turns has('marketing') calls with a literal category into useConsent('marketing').
  • Turns saveConsents('all'), saveConsents('necessary') and saveConsents('custom') into saveCustomPreferences('all'), saveCustomPreferences('none') and saveCustomPreferences() from useHeadlessConsentUI().
  • Moves draft fields such as selectedConsents and setSelectedConsent to useConsentDraft(), and adds a TODO(c15t v3) comment where components now need a shared ConsentDraftProvider.
  • Leaves fields it cannot rewrite on a useConsentManager() call under a TODO(c15t v3) comment that names the replacement. The import no longer exists, so the build fails at each place that needs manual work.

The Next.js and React upgrade guides map every field.

Rename @c15t/scripts imports

v3 renames the vendor package @c15t/scripts to @c15t/integrations. The scripts-to-integrations codemod rewrites imports and re-exports in JavaScript and TypeScript files, including literal dynamic imports, require() calls, require functions made with Node's createRequire(), and import types. It scans .mts, .cts, .mjs and .cjs files too:

npx @c15t/cli@alpha codemods scripts-to-integrations --dry-run --json

It does not edit package.json, lockfiles, or imports inside .vue, .svelte or .astro files. Update those by hand, as the upgrade guides for Next.js, React and JavaScript describe.

Run the v1 to v2 transforms

The other transforms migrate v1 source to the v2 API. Run them before you upgrade a v1 app to v3.

npx @c15t/cli@alpha codemods --list --json
npx @c15t/cli@alpha codemods --all --from 1.9.0 --to 2.0.0 --dry-run --json
IDChange
active-ui-apishowPopup and isPrivacyDialogOpen to activeUI
component-renamesCookieBanner, ConsentManagerDialog and ConsentManagerWidget to the v2 names
gdpr-types-to-consent-categoriesgdprTypes and initialGDPRTypes to consentCategories
ignore-geo-location-to-overridesignoreGeoLocation to overrides with country: 'DE'
mode-c15t-to-hostedmode: 'c15t' to mode: 'hosted'
react-options-to-top-levelreact.theme, colorScheme and disableAnimation to top-level options
tracking-blocker-to-network-blockerTracking blocker configuration to network blocker rules
translations-to-i18ntranslations to the v2 i18n shape
add-stylesheet-importsStyled c15t imports moved into the app's CSS entry point

--all picks the transforms that apply to the version you start from. Without --from, it reads the c15t or framework package version declared in package.json, or the installed version for a specifier such as catalog: that names none. If you already upgraded the dependency but the source still uses v1, pass the old version with --from 1.9.0 or name the transforms. v2 prereleases such as 2.0.0-rc.4 count as v2, so --all skips every v1 transform for them.

Component renames follow imported symbols and keep local aliases. The hosted mode rename only changes options passed to a c15t API it can identify, so check custom wrappers by hand.

Review before writing

--dry-run reports each file's original and proposed contents in JSON. The stylesheet transform reports paths and a summary instead. Transforms in one run share a parser, so later transforms see earlier proposed changes.

The command fails if a transform reports file errors. Writing is not a single transaction across transforms. If a later transform fails, earlier ones may already have saved files, so review the working tree before running again.