Type Generation
Picked can generate TypeScript types from your workspace schema so field values, feature keys, content models, and step layouts are typed in your app.
Quick start
1. Add picked.config.ts
Create a picked.config.ts in your project root:
import { defineConfig } from 'picked/config'
export default defineConfig({
projectId: 'your-project-id',
typesOutput: './src/picked-env.d.ts',
})| Option | Description |
|---|---|
projectId | Required. Your Picked project id. |
typesOutput | Path for generated types (defaults to ./src/picked-env.d.ts). |
source | Schema surface to type against: 'published' (default) or 'preview'. |
previewToken | Workspace preview token. Required when source is 'preview', and read only then. |
The source value matches the x-picked-source field on the workspace schema document — it controls whether tooling fetches the published or preview workspace shape.
A preview token reads one workspace, and it does not expire. Leave both source and
previewToken out of the committed file, and pick a workspace instead:
picked login
picked workspace useThat writes the choice, and that workspace's token, to .picked/settings.json, which is
per-developer and which Git ignores. Each developer points at their own workspace, and the
committed file holds no token.
You can also set the values here. Every developer then reads the same workspace:
export default defineConfig({
projectId: 'your-project-id',
source: 'preview',
previewToken: 'the-workspace-preview-token',
})Three places can give the token. Tooling reads the first of them that has one: the
PICKED_PREVIEW_TOKEN environment variable, then the workspace that
picked workspace use chose, then this previewToken option.
2. Enable the Vite plugin (recommended)
If you use Vite, add @picked/vite-plugin-picked. Type generation runs automatically on dev and build when generateTypes is enabled (the default):
import { picked } from '@picked/vite-plugin-picked'
export default defineConfig({
plugins: [picked()],
})The project id comes from picked.config.ts. A failure stops the
run. See the plugin reference for generateTypes and every
other option.
3. Or use the CLI
Install picked and run:
picked generate typesKeep the types current
Commit picked-env.d.ts. A workspace changes after you commit it, so the file goes stale,
and stale types describe fields your workspace no longer has.
Run this check in CI, next to your other checks:
picked generate types --check--check writes no file. It reads the live workspace schema and compares it with the
committed file. A match gives status 0. A difference gives status 1 and names the file:
Types are out of date: /home/runner/work/your-app/src/picked-env.d.ts
To clear it, run picked generate types and commit the result. The check reads no cached
schema, so an API it cannot reach fails the check.
Workspace selection
Tooling reads projectId and source from your committed picked.config.ts. The Picked CLI points that tooling at a different project or a draft workspace, and leaves the file as it is. It writes the selection to .picked/settings.json, which is per-developer and which Git ignores. That selection supersedes the committed default.
See How to switch workspaces for the full set of steps.
Generated output
The generated picked-env.d.ts file looks like:
// GENERATED by `@picked/typegen` — do not edit by hand.
// Reference: `https://docs.picked.app/reference/sdk/type-generation`
export type WelcomePage = { /* ... */ }
export type FieldValues = { /* ... */ }
export type FeatureKey =
| 'default'
| 'hero'
| 'winter-sale'
declare module '@picked/client' {
interface PickedSchemaRegistry {
contentModels: {
'welcome-page': WelcomePage
}
stepContentModels: {
'intro': 'welcome-page'
}
featureValues: {
'hero':
| false
| 'hero-a'
| 'hero-b'
}
fieldValues: FieldValues
featureKeys: FeatureKey
}
}Commit this file to the version control system, or regenerate it in CI after you publish a workspace change.
Feature flag values
A Text feature flag declares the values that it can serve, so useFeatureResult narrows
the bucket to those values:
const result = useFeatureResult('hero')The bucket property then has the type false | 'hero-a' | 'hero-b'.
featureValues holds the Text feature flags alone. A Boolean feature flag, and a feature
flag that your application defines, both keep the general boolean | string type. The
FeatureValueFor<'hero'> type gives the same union on its own.
Assets
An asset field is a union. An asset that the API finds has a url, and an asset that
it does not find has no url.
Field values gives the two shapes.
The in operator narrows the union:
const image = page.hero_image
return 'url' in image ? <img src={image.url} width={image.width ?? undefined} alt="" /> : nullContent blocks
Each block type has a contentBlockKey property whose type is the key of the block. A
switch on that property gives the data type of each block:
for (const section of page.sections ?? []) {
switch (section.contentBlockKey) {
case 'faq':
renderFaq(section.data.items)
break
case 'hero':
renderHero(section.data.headline)
break
}
}The model of a record
isContentOfModel(record, key) from @picked/client returns true when the
contentModelKey of the record is key. In that branch, record.data has the generated
type of the model. ContentRecordOf<'landing-page'> gives the same record type, and
ContentModelKeys holds the keys of the models in the workspace.
In this example, one component renders the layouts of two models:
import { isContentOfModel } from '@picked/client'
import { useStepLayoutContent } from '@picked/react/hooks'
export function Page() {
const record = useStepLayoutContent()
if (record && isContentOfModel(record, 'landing-page')) {
return <LandingPage content={record.data} />
}
if (record && isContentOfModel(record, 'standard-page')) {
return <StandardPage content={record.data} />
}
return null
}The check reads contentModelKey only. It does not validate data against the model.
Keys the application defines
An application can define a feature, an action, a field, a step, or an experience of its
own. It does this through createClient options or through a register method. The
workspace has no record of such a key, so the generated types have none either.
A hook still accepts one. Each key type holds the keys of the workspace and any other string. Your editor shows the workspace keys, and no local key is an error:
useFeature('winter-sale') // from the workspace, and suggested
useFeature('summer-sales-experiment') // from createClient, and acceptedEach key type has a Known form that holds the workspace keys alone —
KnownFeatureKeys, KnownActionKeys, KnownFieldKeys, KnownStepKeys, and
KnownExperienceKeys. Use one where you want the workspace set and nothing else.
Cache
Type generation writes its artefacts under .picked/: the schema JSON and a manifest. The first run adds this directory to .gitignore for you.
Related
- CLI — commands, authentication, and environment variables.
- How to switch workspaces — read a draft workspace on your machine.
- Vite plugin — the build-time prefetch and the dev launcher.
- Workspace schema — the schema endpoint.
- Delivery API — the published surface and the preview surface.
