Skip to content

Upload settings_schema.json before validator-consumer assets#7481

Open
NathanPJF wants to merge 2 commits intomainfrom
fix/theme-push-fresh-dynamic-source-defaults
Open

Upload settings_schema.json before validator-consumer assets#7481
NathanPJF wants to merge 2 commits intomainfrom
fix/theme-push-fresh-dynamic-source-defaults

Conversation

@NathanPJF
Copy link
Copy Markdown

WHY are these changes introduced?

Closes shop/issues-merchant-workflows#1929

This is the CLI counterpart to the new color_palette setting type. The color_palette is declared at the theme level (config/settings_schema.json). Blocks and sections set their own color defaultvalue via{{ settings.. }}`.

Issue: The shopify theme push fails on the first push to a fresh dev theme whenever a block/section uses that pattern. The second push to the same theme succeeds, because by then config/settings_schema.json has been uploaded and the server-side validator that runs on the block file can resolve the reference against the stored palette.

Example of first push errors:

"Invalid schema: setting with id=<value> default must be a <type> or dynamic source access path"

The second push to the same theme succeeds, because by then the user's config/settings_schema.json has been uploaded and the server-side validators can resolve the reference.

Root cause. The CLI uploads files in dependency order. Today config/settings_schema.json is bundled with config/settings_data.json into a single configFiles bucket and uploaded last — after every block/section/section-group/template asset. When the server-side validator on a block file checks a {{ settings.<palette>.<role> }} default, it queries the theme's currently-stored settings_schema.json, which on a fresh theme is the '[]' placeholder seeded by ensureThemeCreation. The reference can't resolve until the user's real schema lands.

WHAT is this pull request doing?

Splits the configFiles partition bucket into configSchemaFile and configDataFile, then reorders the dependentFiles upload chain:

  • Before: layout → blocks → sections.liquid → sections.json → templates.json → contextualized → config (schema + data together, last)
  • After: config schema → layout → blocks → sections.liquid → sections.json → templates.json → contextualized → config data

settings_schema.json is purely declarative and has no upstream dependencies, so it's safe to upload first. settings_data.json legitimately needs to be last — its current and presets validate against the freshly-uploaded schema and can reference sections / templates uploaded earlier. The matching orderFilesToBeDeleted is updated to delete data before schema (the inverse of the new upload order).

Files changed (all in packages/theme/src/cli/utilities/):

  • theme-fs.ts — split configRegex into configSchemaRegex / configDataRegex; split partitionThemeFiles bucket.
  • theme-uploader.ts — reorder dependentFiles (schema head, data tail), invert orderFilesToBeDeleted, expand the header comment explaining the contract. Exports orderFilesToBeUploaded and ChecksumWithSize so the new test can exercise the function directly.
  • theme-fs.test.ts — partition tests updated for the two new buckets.
  • theme-uploader.test.ts — existing upload-order test updated for the new sequence; new direct unit test of orderFilesToBeUploaded asserts schema-first / data-last and that every validator-consumer asset (blocks, section liquid, section group JSON, template JSON, contextualized template JSON) is sequenced between them.

Verified scope. Block-level color_palette dynamic-source defaults — Horizon's input_border_color repro succeeds on first push. The same upload-order fix is expected to cover the section-group and template JSON datasource walkers (same dependency on the stored schema), though those weren't independently end-to-end verified.

Out of scope. Validator paths that don't consult the stored settings_schema.json at all — for example, the string_or_datasource validator that runs on URL / link block settings — fail before any schema lookup and aren't affected by reordering. Those need a separate server-side change.

How to test your changes?

Test a color_palette type input on a store with the new feature enabled.

  1. Create blocks/test.liquid
{% schema %}
{
  "name": "Test",
  "settings": [
    { "type": "color", "id": "link_color", "label": "Link Color", "default": "{{ settings.color.link_color }}" }
  ]
}
{% endschema %}
  1. Updated config/settings_schema.json with this setting.
      {
        "type": "color_palette",
        "id": "color",
        "default": {
          "background": "#ffffff",
          "link_color": "#000000"
        }
      },
  1. Run pnpm shopify theme push --path=<YOUR_PATH> and verify no error messages

Post-release steps

None.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows) — pure JS, no platform-specific code paths touched.
  • I've considered possible documentation changes — none needed; user-facing behavior is "first push now works," no new flags or commands.
  • I've considered analytics changes to measure impact — none needed.
  • The change is user-facing — patch bump for @shopify/theme, changeset added (.changeset/fix-theme-push-fresh-dynamic-source-defaults.md).

The CLI uploaded `config/settings_schema.json` and `config/settings_data.json` together as the LAST batch of theme files. Server-side validators on blocks, sections, section-group JSON, and template JSON resolve dynamic-source defaults of the form `{{ settings.<theme_setting>.* }}` against the theme's currently-stored schema. On the first push to a fresh dev theme that schema is empty, so any asset whose schema references a not-yet-declared theme setting fails validation — the user sees errors like "Invalid schema: setting with id=... default must be a color or dynamic source access path" or "Dynamic source default value added to '...' is invalid". A second push then succeeds because the user's real schema is now on the server.

Split the `configFiles` partition bucket into `configSchemaFile` and `configDataFile` and reorder the dependent upload chain so schema lands first and data lands last. Schema has no upstream dependencies; data legitimately needs to be last because its current and presets validate against the freshly-uploaded schema. The matching delete order is inverted accordingly.

Closes shop/issues-merchant-workflows#1929

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@NathanPJF NathanPJF requested review from a team as code owners May 6, 2026 21:06
@github-actions github-actions Bot added the Area: @shopify/theme @shopify/theme package issues label May 6, 2026
Copy link
Copy Markdown
Contributor

@graygilmore graygilmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense. Spinning up a chat in Slack to talk about some historical bits.

Comment on lines +354 to +355
// Theme setting declarations must land before any asset that may
// reference them via dynamic-source defaults. See header comment.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already have sufficient description in the header

Suggested change
// Theme setting declarations must land before any asset that may
// reference them via dynamic-source defaults. See header comment.

Comment on lines +363 to +364
// Settings values reference the schema we just uploaded, plus any
// sections / templates referenced by presets.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Settings values reference the schema we just uploaded, plus any
// sections / templates referenced by presets.

Comment on lines +452 to +457
for (const key of validatorConsumers) {
const index = flat.indexOf(key)
expect(index, `${key} missing from dependent chain`).not.toBe(-1)
expect(index, `${key} should be after settings_schema.json`).toBeGreaterThan(0)
expect(index, `${key} should be before settings_data.json`).toBeLessThan(flat.length - 1)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea what this test is doing. Is there another way we could show this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: @shopify/theme @shopify/theme package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants