Extend source's Data interface with types from new handlers


People using TypeScript needs to extend Data when they add a new handler. Right now, there’s no easy way to do so, because they need to extend both the BaseData and add new interfaces to Data.

This is Data, they need to add other types/interfaces here:

And this is BaseData, they need to add new properties here:

User Stories

As a Frontity developer
I want to add TypeScript for new handlers
so that I can keep using TypeScript in my theme/package

Possible solution

@mmczaplinski proposed a generic that gets the new types.

Maybe something like:

import { Package, Derived } from "frontity/types";
import { Data } from "@frontity/source";

// Define D your custom post types...
type CustomTypeData = {
 isCustomType: true;
 otherProps: ...

// Create a new Data with that custom post type...
type MyData = Data<CustomTypeData>;

interface MyPackage extends Package {
 state: {
   source: {
     // Use state.source.get with the correct Data.
     get: Derived<MyPackage, (link: string) => MyData>;


I couldn’t get to implement a solution like the one described in the OP, because there is no way to select only the isSomething fields from a type to add to the Base.

It could be done if https://github.com/microsoft/TypeScript/issues/6579 becomes real, because we could match the keys with a regex.

I think it might be possible to build something creating a base type only with the boolean properties extracted from every data type, but this is not ideal either.

Creating a base with every single field makes no sense because then no matter what data object you were using, TypeScript would think every single field of every single data type would be present in that object, and that’s not useful.

The only solution I could think of is the following:

// source.ts

export type BaseData<T = {}> = {
  isFetching: boolean;
  isReady: boolean;
  isArchive?: false;
  isPostType?: false;

type ArchiveData<T> = T & {
    isArchive: true;
    items: EntityData[];
    total?: number;
    totalPages?: number;

type PostTypeData<T> = T & {
  isPostType: true;
  type: string;
  id: number;

type Datas<T> = ArchiveData<T> | PostTypeData<T>;

export type Data<CustomBase = BaseData, CustomData = Datas<CustomBase>> =
    CustomData extends Datas<CustomBase>
    ? Datas<CustomBase>
    : Datas<CustomBase> | CustomData;

// theme.ts

import { BaseData as SourceBaseData, Data as SourceData } from '@frontity/source';

type BaseData = SourceBaseData<{
  isMyCustomPostType?: false;
  isMyOtherCustomPostType?: false;

type MyCustomPostTypeData = BaseData & {
  type: 'my-custom-post-type',
  isMyCustomPostType: true

type MyOtherCustomPostTypeData = BaseData & {
  type: 'my-other-custom-post-type',
  isMyOtherCustomPostType: true

type Data = SourceData<BaseData, MyCustomPostTypeData | MyOtherCustomPostTypeData>;

interface Theme extends Package {
  state: {
   source?: Merge<
        get: Derived<Theme, (link: string) => Data>;
        data: Record<string, Data>;

Here is a small playground with the concept to play.

NOTE: I got rid of the Merge function because on the playground didn’t seem necessary and it simplified the error messages from TypeScript, but I don’t know if that’s something we can do or if Merge is actually needed for some reason I couldn’t see. What I understood is that Merge was removing the shared properties between the base and the data, from the base type, and them doing the union. I can’t understand whats the difference between that and doing just the union that overrides the shared properties.

That solution looks fine to me :+1:

Could you do this? (extend PostTypeData instead of BaseData)

type MyCustomPostTypeData = PostTypeData & {
  type: 'my-custom-post-type',
  isMyCustomPostType: true

I think you need to do

type MyCustomPostTypeData = PostTypeData<BaseData> & {
  type: 'my-custom-post-type',
  isMyCustomPostType: true