Overview
The Digital Planning Data schemas are generated from TypeScript interfaces using thets-json-schema-generator library. This means you get full TypeScript support with type safety, autocomplete, and compile-time validation.
Type Architecture
The type definitions are organized in the/types directory:
types/
├── shared/ # Shared types used across schemas
│ ├── User.ts
│ ├── Metadata.ts
│ ├── Addresses.ts
│ ├── Contacts.ts
│ └── ...
└── schemas/ # Schema-specific types
├── application/
│ ├── index.ts
│ ├── data/
│ │ ├── Applicant.ts
│ │ ├── Property.ts
│ │ ├── Proposal.ts
│ │ └── ...
│ └── enums/
│ ├── ApplicationTypes.ts
│ ├── PropertyTypes.ts
│ └── ...
└── ...
Importing Types
- From NPM Package
- From Cloned Repository
When you’ve installed the schemas as a package:
// Import root schema types
import type {Application} from 'digital-planning-data-schemas/types/schemas/application';
import type {PreApplication} from 'digital-planning-data-schemas/types/schemas/preApplication';
import type {PrototypeApplication} from 'digital-planning-data-schemas/types/schemas/prototypeApplication';
import type {Enforcement} from 'digital-planning-data-schemas/types/schemas/enforcement';
// Import specific data types
import type {Applicant} from 'digital-planning-data-schemas/types/schemas/application/data/Applicant';
import type {Property} from 'digital-planning-data-schemas/types/schemas/application/data/Property';
import type {Proposal} from 'digital-planning-data-schemas/types/schemas/application/data/Proposal';
// Import enums and constants
import type {ApplicationType} from 'digital-planning-data-schemas/types/schemas/application/enums/ApplicationTypes';
import {ApplicationTypes} from 'digital-planning-data-schemas/types/schemas/application/enums/ApplicationTypes';
// Import shared types
import type {User} from 'digital-planning-data-schemas/types/shared/User';
import type {Metadata} from 'digital-planning-data-schemas/types/shared/Metadata';
When working with a cloned repository:
// Import from relative paths
import type {Application} from '../digital-planning-data-schemas/types/schemas/application';
import type {Applicant} from '../digital-planning-data-schemas/types/schemas/application/data/Applicant';
import type {User} from '../digital-planning-data-schemas/types/shared/User';
// Or configure path mapping in tsconfig.json
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@schemas/*": ["./digital-planning-data-schemas/types/*"]
}
}
}
// Then import using the alias
import type {Application} from '@schemas/schemas/application';
Core Schema Types
Application
The root type for planning applications:import type {Application} from 'digital-planning-data-schemas/types/schemas/application';
/**
* @title Application
* @description The root specification for a planning application in England
* generated by a digital planning service
*/
interface Application {
data: {
application: ApplicationData;
user: User;
applicant: Applicant;
property: Property;
proposal: Proposal;
files?: FilesAsData;
};
preAssessment?: PreAssessment;
responses: Responses;
files: File[];
metadata: Metadata;
}
User
Describes the role of the person who completed the application:import type {User} from 'digital-planning-data-schemas/types/shared/User';
/**
* @title User
* @description The role of the user who completed the application
*/
interface User {
role: 'applicant' | 'agent' | 'proxy';
}
Applicant
The applicant can be a direct applicant or include agent information:import type {Applicant, BaseApplicant, Agent} from 'digital-planning-data-schemas/types/schemas/application/data/Applicant';
/**
* @description The user who completed the application either for themself
* or on behalf of someone else
*/
type Applicant = BaseApplicant | Agent;
/**
* @description Information about the user who completed the application
* for themself, or information about the person who the user applied
* on behalf of
*/
type BaseApplicant = ContactDetails &
SiteContact &
MaintenanceContacts & {
type: 'individual' | 'company' | 'charity' | 'public' | 'parishCouncil';
address: UserAddress;
ownership?: Ownership;
};
/**
* @description Information about the agent or proxy who completed
* the application on behalf of someone else
*/
interface Agent extends BaseApplicant {
agent: ContactDetails & {address: Address};
}
Metadata
Every application includes metadata about its source and submission:import type {Metadata, PlanXMetadata, AnyProviderMetadata} from 'digital-planning-data-schemas/types/shared/Metadata';
type Metadata = AnyProviderMetadata | PlanXMetadata;
interface BaseMetadata {
/** Organisation code from planning.data.gov.uk */
organisation: string; // maxLength: 4
/** Unique identifier for this application */
id: UUID;
submittedAt: DateTime;
schema: URL;
}
interface PlanXMetadata extends BaseMetadata {
source: 'PlanX';
service: {
flowId: UUID;
url: URL;
files: RequestedFiles;
fee: FeeExplanation | FeeExplanationNotApplicable;
enhancements?: Enhancements;
};
}
Application Types
Application types are defined as a const object with type inference:import {ApplicationTypes, ApplicationType, ApplicationTypeKeys} from 'digital-planning-data-schemas/types/schemas/application/enums/ApplicationTypes';
// The const object containing all application types
const ApplicationTypes = {
advertConsent: 'Consent to display an advertisement',
'ldc.existing': 'Lawful Development Certificate - Existing use',
'ldc.proposed': 'Lawful Development Certificate - Proposed use',
'pa.part1.classA': 'Prior Approval - Larger extension to a house',
'pp.full.householder': 'Planning Permission - Full householder',
'pp.full.major': 'Planning Permission - Major application',
// ... many more types
};
// Type-safe keys
type ApplicationTypeKeys = keyof typeof ApplicationTypes;
// Structured type with value and description
type ApplicationType = {
value: ApplicationTypeKeys;
description: string;
};
import {ApplicationTypes} from 'digital-planning-data-schemas/types/schemas/application/enums/ApplicationTypes';
// Type-safe application type
const applicationType = {
value: 'pp.full.householder' as const,
description: ApplicationTypes['pp.full.householder'],
};
// Get description from value
function getApplicationDescription(key: ApplicationTypeKeys): string {
return ApplicationTypes[key];
}
console.log(getApplicationDescription('ldc.existing'));
// "Lawful Development Certificate - Existing use"
Utility Types
The schemas define several utility types:// From types/shared/utils.ts
/**
* @format uuid
*/
type UUID = string;
/**
* @format date-time
*/
type DateTime = string;
/**
* @format uri
*/
type URL = string;
/**
* @format email
*/
type Email = string;
Type Guards
Create type guards for runtime type checking:import type {Applicant, Agent, BaseApplicant} from 'digital-planning-data-schemas/types/schemas/application/data/Applicant';
function isAgent(applicant: Applicant): applicant is Agent {
return 'agent' in applicant;
}
function processApplicant(applicant: Applicant) {
if (isAgent(applicant)) {
// TypeScript knows applicant is Agent here
console.log('Agent:', applicant.agent.name);
console.log('Applicant:', applicant.name);
} else {
// TypeScript knows applicant is BaseApplicant here
console.log('Direct applicant:', applicant.name);
}
}
Working with Examples
The repository includes typed example data:import type {Application} from 'digital-planning-data-schemas/types/schemas/application';
const version = process.env['VERSION'] || '@next';
export const planningPermissionFullHouseholder: Application = {
data: {
application: {
type: {
value: 'pp.full.householder',
description: 'Planning Permission - Full householder',
},
fee: {
calculated: 258,
payable: 258,
category: {
sixAndSeven: 258,
},
exemption: {
disability: false,
resubmission: false,
},
reduction: {
sports: false,
parishCouncil: false,
alternative: false,
},
reference: {
govPay: 'sandbox-ref-456',
},
},
declaration: {
accurate: true,
connection: {
value: 'none',
},
},
CIL: {
result: 'notLiable',
declaration: true,
s73Application: false,
existingPermissionPrecedingCIL: false,
newDwellings: false,
floorAreaHundredPlus: false,
},
},
user: {
role: 'proxy',
},
applicant: {
type: 'individual',
name: {
first: 'David',
last: 'Bowie',
},
email: '[email protected]',
phone: {
primary: 'Not provided by agent',
},
address: {
sameAsSiteAddress: true,
},
siteContact: {
role: 'proxy',
},
ownership: {
interest: 'owner.sole',
certificate: 'a',
agriculturalTenants: false,
declaration: {
accurate: true,
},
},
agent: {
name: {
first: 'Ziggy',
last: 'Stardust',
},
email: '[email protected]',
phone: {
primary: '01100 0110 0011',
},
address: {
line1: '40 Stansfield Road',
line2: 'Brixton',
town: 'London',
county: 'Greater London',
postcode: 'SW9 9RZ',
country: 'UK',
},
},
},
// ... more data
},
responses: [],
files: [],
metadata: {
organisation: 'LBH',
id: '81bcaa0f-baf5-4573-ba0a-ea868c573faf',
source: 'PlanX',
submittedAt: '2023-10-02T00:00:00.00Z',
schema: `https://theopensystemslab.github.io/digital-planning-data-schemas/${version}/schemas/application.json`,
service: {
flowId: '01e38c5d-e701-4e44-acdc-4d6b5cc3b854',
url: 'https://www.editor.planx.dev/lambeth/apply-for-planning-permission/preview',
files: {
required: ['roofPlan.existing', 'roofPlan.proposed'],
recommended: ['floorPlan.existing', 'floorPlan.proposed'],
optional: [],
},
fee: {
calculated: [
{
description: 'The fee to apply for planning permission to alter or extend a single home is £258.',
policyRefs: [
{
text: 'UK Statutory Instruments 2023 No. 1197',
url: 'https://www.legislation.gov.uk/uksi/2023/1197/made',
},
],
},
],
payable: [
{
description: 'The fee to apply for planning permission to alter or extend a single home is £258.',
},
],
},
},
},
};
Schema Generation
The JSON schemas are generated from TypeScript types usingts-json-schema-generator:
#!/bin/bash
# From scripts/build-schema.sh
version="${VERSION:-@next}"
dirs=("application" "preApplication" "prototypeApplication")
types=("Application" "PreApplication" "PrototypeApplication")
for i in "${!dirs[@]}"; do
dir=${dirs[$i]}
type=${types[$i]}
pnpm ts-json-schema-generator \
--path "types/schemas/${dir}/*.ts" \
--out "schemas/${dir}.json" \
--type "$type" \
--id "$version" \
--no-top-ref
done
JSDoc Annotations
Types use JSDoc comments to provide schema metadata:/**
* @title User
* @description The role of the user who completed the application
*/
export interface User {
role: 'applicant' | 'agent' | 'proxy';
}
/**
* @id #Applicant
* @description The user who completed the application either for themself
* or on behalf of someone else
*/
export type Applicant = BaseApplicant | Agent;
/**
* @description Organisation code from planning.data.gov.uk
* @maxLength 4
*/
organisation: string;
/**
* @format uuid
*/
type UUID = string;
TypeScript Configuration
Recommendedtsconfig.json settings for working with the schemas:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"moduleResolution": "node",
"types": ["node"],
"paths": {
"digital-planning-data-schemas/*": ["./node_modules/digital-planning-data-schemas/*"]
}
}
}
Best Practices
Use type imports
Use type imports
Import types using
import type to avoid runtime overhead:// Good: Type-only import (erased at runtime)
import type {Application} from 'digital-planning-data-schemas/types/schemas/application';
// Also good: Import values when needed
import {ApplicationTypes} from 'digital-planning-data-schemas/types/schemas/application/enums/ApplicationTypes';
Leverage union types
Leverage union types
Many types are unions - use type guards to narrow types:
type Applicant = BaseApplicant | Agent;
type Metadata = AnyProviderMetadata | PlanXMetadata;
function isPlanXMetadata(meta: Metadata): meta is PlanXMetadata {
return meta.source === 'PlanX';
}
Create helper types
Create helper types
Derive custom types from the base schemas:
import type {Application} from 'digital-planning-data-schemas/types/schemas/application';
// Extract just the application data
type ApplicationData = Application['data']['application'];
// Extract nested types
type PropertyAddress = Application['data']['property']['address'];
// Create partial types for updates
type ApplicationUpdate = Partial<Application>;
Combine with validation
Combine with validation
Use types alongside runtime validation:
import type {Application} from 'digital-planning-data-schemas/types/schemas/application';
import applicationSchema from 'digital-planning-data-schemas/schemas/application.json';
import Ajv from 'ajv';
const ajv = new Ajv();
const validate = ajv.compile(applicationSchema);
function processApplication(data: unknown): Application {
if (!validate(data)) {
throw new Error('Invalid application data');
}
// TypeScript now knows data is Application
return data as Application;
}
Next Steps
Examples
See real-world examples using TypeScript types
Schema Reference
Explore the full application schema