Declarative Schema

db_schema.xml declares the tables a module wants, and setup:upgrade works out the SQL that gets the database there.

Syntax<table name="vendor_module_entity" resource="default" engine="innodb" comment="Entity table">

A module is a folder under app/code/Vendor/Module with its own registration.php and etc/module.xml. When that module needs database tables, they are declared in one file: etc/db_schema.xml.

Declarative schema means the file describes the final shape of the tables, not the steps to reach it. bin/magento setup:upgrade reads db_schema.xml from every enabled module, merges them into one desired schema, reads the real schema out of MySQL, compares the two, and runs the CREATE TABLE and ALTER TABLE statements that close the gap.

What it replaced

Before Magento 2.3, schema changes lived in Setup/InstallSchema.php and Setup/UpgradeSchema.php and were triggered by the setup_version number in module.xml. Those were ordered scripts: each new change meant another version comparison, and the history had to replay from scratch on a fresh install. In Magento 2.4.9 a module uses declarative schema instead; a module that has db_schema.xml does not also use the old scripts. Rows that need inserting moved to data patches, which come later in the course.

What trips people up

There is no version to bump and no order to maintain. You edit the declaration and the diff does the rest. But nothing reaches the database until setup:upgrade runs, and no amount of cache:flush substitutes for it.

Example

Example · xml
<!-- app/code/Soundscode/Enquiry/etc/db_schema.xml -->
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="soundscode_enquiry" resource="default" engine="innodb" comment="Storefront enquiry">
        <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="true"
                comment="Entity ID"/>
        <column xsi:type="varchar" name="email" nullable="false" length="255" comment="Customer email"/>
        <column xsi:type="mediumtext" name="message" nullable="true" comment="Enquiry body"/>
        <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false"
                default="CURRENT_TIMESTAMP" comment="Created at"/>
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="entity_id"/>
        </constraint>
        <index referenceId="SOUNDSCODE_ENQUIRY_EMAIL" indexType="btree">
            <column name="email"/>
        </index>
    </table>
</schema>

When to use it

  • Create the table behind a new storefront enquiry form.
  • Add one column to a core table without editing the core module.
  • Replace an inherited InstallSchema.php script with a single declaration.
  • Check whether a teammate's schema change has reached your local database.

More examples

The commands that make the file real

The file on disk changes nothing on its own. module:enable registers the module in app/etc/config.php, setup:upgrade diffs the declarations against MySQL and applies the DDL, and setup:db:status reports whether the live database still matches the declarations. Reach for setup:db:status when you are not sure whether a colleague's schema change has been applied to your local database.

Example · bash
# run from the Magento root, after saving etc/db_schema.xml
bin/magento module:enable Soundscode_Enquiry
bin/magento setup:upgrade

# Upgrade completed successfully.

# then confirm the database now matches the code
bin/magento setup:db:status

# All modules are up to date.

Adding a column to another module's table

Declarations for the same table name from different modules are merged, so you extend a core table by re-declaring it with only the parts you are adding. Magento_Cms keeps its own cms_block declaration; yours contributes one column. Use this when you need an extra field on a core table and do not want to fork the core module. Apply it with bin/magento setup:upgrade, then regenerate your whitelist.

Example · xml
<!-- app/code/Soundscode/Enquiry/etc/db_schema.xml -->
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="cms_block">
        <column xsi:type="varchar" name="soundscode_owner" nullable="true" length="64"
                comment="Team that owns this block"/>
    </table>
</schema>

module.xml with no setup_version

This is what module.xml looks like once schema lives in db_schema.xml: a name, and a sequence listing modules whose declarations must be read before yours. There is no setup_version, because nothing is triggered by a version number any more. The sequence entry is what guarantees cms_block already exists when your added column is merged in.

Example · xml
<!-- app/code/Soundscode/Enquiry/etc/module.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Soundscode_Enquiry">
        <sequence>
            <module name="Magento_Cms"/>
        </sequence>
    </module>
</config>

Discussion

  • Be the first to comment on this lesson.