• Minor dashboard layout changes


  • Fix the ctrl+s shortkey to publish changes


  • Use AND to separate search terms so the order of terms does not matter


  • Add support for tables in Rich Text Fields. This can be enabled using the enableTables option.


  • Add and Edit.links to create link values in the Edit api. These are currently not optimally typed and will be improved in the future.

    const imageField ='Image', {
      fields: {
        alt: Field.text('Alt text')
    const imageValue =
      .addImage(imageId, {alt: 'An image'})


  • Fix double language in urls for entries created through the Edit api


  • Entries in the dashboard sidebar can now be ordered by a field value. Use the orderChildrenBy configuration option to set which field to order by.

    const Type = Config.type('Type', {
      orderChildrenBy: Query.title.asc() // Order by Entry title
      // ...
  • Add subscript and superscript options to rich text Fields.

  • Stop using the porter stemming FTS5 tokenizer in search queries. The algorithm does not work well with non-english languages and so is not a good default.

  • Fix select box not showing a checkmark in the explorer while replacing file or image links.

  • Unset validation errors when fields are no longer used.


  • Remove 'use server' directive from the Next.js driver because it does not contain server actions at all and newer Next.js version will throw an error when it is included.


  • Fix empty path names resulting in an extra slash at the end of entry urls after publish


  • Fix inserting blocks in rich text fields causing issues after trying to publish


  • Fix whereId on Cursor typed as returning a single result but actually returning multiple
  • Fix inserting blocks in rich text fields


  • Reserve Alinea generated properties (#378)

    Until now Alinea generated properties that define the content structure as normal identifiers. This means that in a list a row would contain a "type", "id" and "index" property while also containing user defined fields. This had a lot of potential for name clashes: you could choose to name a field "type" for example. It is also not future-proof as Alinea might want to add more properties later on. To solve this issue Alinea now reserves all properties starting with an underscore as internal. It's not needed to upgrade content files manually. The old format will automatically be picked up for some time. It's possible to upgrade all files in one go by running the following, which will write any changes in content back to disk.

    npx alinea build --fix

    When querying content please pay mind that those internal properties are now accessed by the underscored property and will need to be updated. This becomes most apparent in getting results out of the Link field.

    const MyType = Config.type('MyType', {
      link:'Link', {
        fields: {
          label: Field.text('Label')
    const result = await cms.get(Query(MyType).select(
    // before:
    //   EntryReference & {label: string}
    //   ^? {id: string, type: string, ..., url: string, label: string}
    // after:
    //   EntryLink<{label: string}>
    //   ^? {_id: string, _type: string, ..., url: string, fields: {label: string}}
  • Use of Type.isContainer is now deprecated, use a list of types in contains instead.

  • Use of Type.isHidden is now deprecated, use hidden instead.


  • Fix select field not using field options such as width or required.


  • Fix publishing shared fields when the parent paths are not the same.


  • Fix entries showing up under the wrong parent if they had a parent with the same path name in another root.
  • Add the option to remove media folders.


  • Export Entry from alinea/core which was missing in the previous release.
  • Fix the includeSelf option when querying translations.


  • Two changes that impact how to write config files:

    • Bundle configuration function in Config, and fields in a Field namespace which is exported from the alinea package (this is optional).

    • Deprecate alinea.meta style configuration. This change is applied to Schema (types), Workspace (roots), Root (entries), Type (fields), Document (fields), Select fields (options), Tab fields (fields).

      To upgrade your config files:

      // Before
      import alinea from 'alinea'
      const Type = alinea.type('Name', {
        field: alinea.text('Field'),
        [alinea.meta]: {
          contains: ['Page']
      // After
      import {Config, Field} from 'alinea'
      const Type = Config.type('Name', {
        contains: ['Page'],
        fields: {
          field: Field.text('Field')
  • Add the mutation API (#374)

    This introduces a commit method on CMS instances that can be used to mutate cms content. It can be used to create, update and delete Entries.

  • Add the Query namespace


  • Support entries that were seeded in versions prior to 0.6.4 for backward compatibility.


  • File and image titles can be edited. A focus point can be chosen for images.
  • The exports of the alinea package are restructured. Unless you were using the now removed named alinea exports this should not be a breaking change.
  • The createNextCMS function is now deprecated and it is recommended to import it as {createCMS} from 'alinea/next' instead.
  • The local database which stores content for editors is now rebuilt on alinea version changes. This means breaking changes to the schema will not cause errors in the browser.
  • Upload file names and paths are now slugified correctly.


  • Improve page seeding in roots with multiple languages. Seeding content is currently undocumented until it reaches a stable interface.


  • Add a preview widget which enables editors to easily switch from previewing to editing. Enable by setting widget to true:

    <cms.previews widget />
  • Add a function to retrieve the current logged in user:

    console.log(await cms.user())


  • Live-reload changes to .css files used for custom fields and views.


  • Allow importing .css and .module.css in custom fields and views.
  • Make isContainer optional if contains is used on Types.
  • Add i18nId to retrieved entry Link fields when queried.


  • Field validation (#369)

    Introduces two new Field options available for every Field: required and validate. The required option will make sure the field value is not empty when saving. The validate option can be used to validate the field value using a custom function. The function should return true if the value is valid, false if it is not valid and a string if it is not valid and a message should be shown to the user.

    This is a breaking change, removing the optional property from Fields. It was never functional.

    alinea.text('Hello field', {
      help: 'This field only accepts "hello" as a value',
      validate(value) {
        if (value !== 'hello') return 'Only "hello" is allowed!'


  • Link fields using the condition option are now constrained with their locale
  • Upgrade the tiptap editor and fix a few stability issues with the editor


  • Fix storing extra fields on the Link field correctly for multiple links.

  • In conditional configuration functions it's now possible to access fields from parent contexts. For example field options of a nested field inside a List field can depend on the value of a field in the entry root.

    const innerField = alinea.text('Read-only if status is published')
    const Type = alinea.type('Conditional example', {
      status:'Status', {
        draft: 'Draft',
        published: 'Published'
      list: alinea.list('List', {
        schema: {ListRow: alinea.type({innerField})}
    alinea.track.options(innerField, get => {
      return {readOnly: get(Type.status) === 'published'}


  • Fix Entry fields showing up as type unkown in TypeScript.
  • The readOnly option that is included in all fields will now show a lock item next to the field label. The option is passed down in nested fields such as the List and Rich text fields.


  • Changing entry order by dragging them in the sidebar is now applied immediately making changes much smoother.


  • Fix navigation missing when selecting internal pages in Link fields.


  • The interval at which Alinea polls the backend for content updates is now configurable. It can be set in config.syncInterval and overwritten per query.

    // Poll for updates every 60 seconds
    const results = await cms.syncInterval(60).find(Entry())
    // Disable syncing all for this query in case you want results fast,
    // but not necessarily up to date
    const matches = await cms.disableSync().find(Entry().search('fast'))


  • Pages can be queried with search terms. Any (rich) text field with the searchable option set to true is indexed.

    const results = await cms.find(Page().search('search', 'terms'))


  • A source entry can be chosen in the modal where new entries are created. All data from that entry will be copied to the new entry.


  • Shared fields (#365)

    Introduce the shared option for Fields. Fields can be persisted over all locales if your content is localised by setting the shared option to true. When the entry is published the field data is copied to other locales. This is currently only supported on the root level, not on nested fields.

    const Type = alinea.type('Persist', {
      // Persist field data over all locales
      sharedField: alinea.text('Shared text', {shared: true})


  • Fix number field not reflecting up value changes
  • Fix creating new entries not picking up selected parent


  • Add an edit button to Link field rows.
  • Fix extra fields defined on Link fields not saving data.


  • Preview panel was missing in production deploys.


  • Conditional configuration (#364)

    All field configuration can be adjusted based on the value of other fields. After defining fields in a Type a tracker function can be set up. The tracker function takes a reference to a field and a subscription function. In the subscription function field values can be retrieved and new options returned.

    const Example = alinea.type('Conditional example', {
      textField: alinea.text('Text field'),
      readOnly: alinea.check('Make read-only'),
      hidden: alinea.check('Hide field')
    alinea.track.options(Example.textField, get => {
      const textField = get(Example.textField)
      const readOnly = get(Example.readOnly)
      const hidden = get(Example.hidden)
      return {
        help: `Text has ${textField.length} characters`


  • The description field for external links is renamed to "title".
  • Heading h4 and h5 are available in the Rich Text field.
  • Folder icon in the nav tree has been moved to the right allowing containers to have a custom icon as well.


  • Reduce circular dependencies within the alinea/core package. This would previously result in "Cannot read properties of undefined (reading Scalar)" or similar when implementing custom fields. Thanks to


  • A type check before creating new entries was incorrect making it impossible create new entries on the root level.


  • This release contains a major rewrite. Read the blog post for more information.


  • Fixed the TypeScript type of the Select input (#282)
  • Moved the Preview and BrowserPreview exports from the alinea package to @alinea/preview. This should help import the alinea config within in restrictive environments such as Next 13 which will throw a compile error if any browser code is imported. This will likely be tweaked in future releases.


  • Fix the @alinea/preview/remix preview hook


  • Generate types (#271)

    Up until now the TypeScript definitions that were available for the content schema were fully inferred using the TypeScript compiler. While this had some advantages it also came with stability issues and overall did not prove to be the best solution. TypeScript definitions are now generated from the schema at build time. The runtime type information should prove useful for the upcoming GraphQL support as well. Since GrapQL does not come with namespacing we've introduced a few breaking changes:

    • The config file now supports a single schema at the root level, meaning the schema is used for every workspace.

    • The generated package structure in turn became simpler because the workspace distinction is no longer needed:

      // Init pages now available from /pages
      import {initPages} from '@alinea/generated/pages'
      // The Page type describes every content type of the schema
      // type Page = Page.TypeA | Page.TypeB
      import {Page} from '@alinea/generated'
  • Remix run support (#273)

    A few changes were necessary to get started with Remix. These changes should make it easier to work with other frameworks as well.

    • An example starter was added
    • The local backend connects to the serve instance if it is running. Since Remix does not watch file changes in node modules this should make sure you're always viewing the latest changes.
    • Export pages/backend as CJS (#270)


  • The dashboard router was not picking up wildcard routes, which resulted in non-working links (#265)
  • Bundle yjs instead of requiring it as a dependency.
  • Re-use the esbuild watcher in order to remove the chokidar dependency.


  • Improved stability of the serve and generate commands by avoiding race conditions while publishing
  • Removed react-router dependency


  • Public env variables may be used in alinea.config. Currently supported are variables with a key prefix of either NEXT_PUBLIC_, PUBLIC_, VITE_ or GATSBY_.


  • The workaround released in 0.2.10 was not stable. Node modules ended up being bundled in the generated Javascript.


  • Workaround evanw/esbuild#2460. Newer esbuild versions support the new "automatic" react jsx feature. This can be enabled from the build options, but also overwritten in tsconfig.json. Alinea depends on this feature but had problems generating correct output when the tsconfig has another jsx setting. Previously the workaround was supplying a blank tsconfig file. However other directives such as paths that the user might supply in their own tsconfig were ignored. With this change alinea will write out a tsconfig.alinea.json that extends the user supplied tsconfig.json and overrides the jsx property.


  • The alinea serve command will apply publish actions directly to the memory store instead of relying on the file watcher. This should result in better performance.



  • UI tweaks


  • Fix the Pages.whereRoot method which did not use the new alinea.root location


  • Add a --fix option to the generate command, which will write back any missing or incorrect properties to disk.


  • The previous release was missing the generated css file in the @alinea/css package. Build outputs are now cached in the ci step using wireit but this file was not included.


  • The url property of entries can now be controlled using the entryUrl function in the type options. Urls are computed during generation and this can help to keep them constant if you're using a web framework that does file system routing. The available paramters are path, parentPaths and locale. For example: making sure a doc page always has an url in the form of /doc/$path you can specify entryUrl as the following:

    type('Doc', {...fields}).configure({
      entryUrl({path}) {
        return `/doc/${path}`
  • The iframe used in the BrowserPreview component now supports top level navigation so it becomes possible to link the user to a cms route from within. (#246)

  • The index of newly created entries will be based on the first child of parent. This makes them consistently sortable when published. (#241)


  • Alinea cloud handshake now automatically includes git information when hosted on Vercel, to allow for a prefilled project setup


  • Alinea cloud connection now sends the shortKey part of API key during authentication to uniquely identify project


  • The exports of the alinea package are restructured. This is a breaking change because the input fields are no longer exposed directly but bundled in the "alinea" namespace. A few less used exports were removed and can be found in the @alinea packages.

  • Client code is shielded from being included server side when compiling with the "worker" condition enabled.

  • Initial support for selecting external links in the link field. The RichText ui component is adjusted to correctly render links. A custom component or tag can be passed to render links.

    <RichText a={<a className="custom-link" />} doc={doc} />
    <RichText a={CustomLinkComponent} doc={doc} />


  • Added a new field type for raw json data


  • Minor fix in the handshake procedure between Alinea and Alinea cloud


  • The alinea serve command will try another port if the chosen port is in use (#179)
  • Avoid duplicate entries after publishing in the development server (#214)
  • The generated types for pages depended on a type that would be namespaced when using typeNamespace. It did not take the namespace into account, but does now.
  • The connection with Alinea Cloud should now handle previewing drafts correctly.


  • Alinea Cloud can now handle relative urls & authentication is automatically redirected.


  • The alinea cli will now forward commands placed after the serve or generate commands. It will wait until the alinea package is generated before doing so to make sure userland code can always depend on the package being available. It also simplifies running the dashboard and development server without requiring tools like npm-run-all. In practice, for a next.js website, this means one can configure scripts like so:

      "scripts": {
        "dev": "alinea serve -- next dev",
        "build": "alinea generate -- next build"


  • Fix missing dependencies in the dashboard package


  • Added a button to mark text as small within the rich text editor

  • New UI buttons to insert rows in any position in a list field

  • User preferences get a dedicated popover

  • Previews can register a listener and implement their own refetch mechanism. The communication happens via messages which work cross-origin.

    // For any environment
    import {registerPreview} from 'alinea/preview'
      refetch() {
        // Reload server data
    // A react hook is available
    import {usePreview} from 'alinea/preview/react'
    const {isPreviewing} = usePreview({
      refetch() {
        // Reload server data & redraw
    // A hook specifically for next.js, which refetches static/server props
    import {useNextPreview} from 'alinea/preview/next'
    const {isPreviewing} = useNextPreview()


  • Previous release contained a few debug logs which are removed.
  • Hard breaks (shift + enter) will be rendered as break in the RichText component.


  • Tested and fixed the integration with Alinea Cloud for drafts and publishing content.


  • Avoid errors during the serve command that would stop the process with "memory access out of bounds".
  • Detect if we're serving a preview cross origin and cannot use the iframe's history. If so we adjust the UI to hide the back/forward buttons and change the reload button to refresh the iframe source instead of a history reload.


  • Log errors during CLI builds
  • Use a more stable local drafts implementation


  • Add the MIT license
  • Use internal router, removing the Express.js dependency for the CLI, reducing the overall install size


  • Backend can be compiled to a static html file, using the dashboard.staticFile config setting
  • For uploaded images the correct width and height is saved in metadata


  • Externalized backend implementation packages.


  • The alinea package now exports an alinea object that bundles the previously exported config functions.
  • Vendor in selected dependencies (#175). Selected dependencies will be compiled and packaged with the alinea packages. This reduces install size and amount of dependencies which would typically not be shared with any userland code.
  • Added a hidden option to fields, which hides the field in the dashboard UI (#169)


  • Replace CJS dependencies to pure ESM (#161)
  • Added blockquote option to the rich text field


  • The drafts of the default development backend are now placed with the generated code which means draft changes will result in browser reloads.


  • A RichText react component is exposed from @alinea/ui to render rich text values

  • Auto-close navigation sidebar only for small screens (< 768px)

  • Update number field styles to use updated css variable names

  • Select fields configuration can now be set using the configure method. This helps type inference for the initial value.

    select('Level', {
      info: 'Info',
      warning: 'Warning'
      initialValue: 'info'


  • Fix subtle crypto import for node versions that ship it natively (> v15)


  • The backend implementation now uses the web fetch api instead of relying on express. This maximises compatibility with existing Javascript runtimes that are not node based (service worker, Cloudflare workers, deno, bun.js). Actually deploying the backend to platforms other than node has not been tested yet and might require a few additional changes. The fetch api is polyfilled for node using @remix-run/web-fetch. Eventually node will support this natively.
  • Add an initial implementation of a date field (@alinea/input/date). It currently uses the native browser input which represents dates as ISO8601.
  • The number field had an update to make it functional again.