# Frontend Runtime Extension Contract (V1) This document defines the stable frontend runtime integration surface for plugins that extend FrontEdit in the browser. ## Purpose This contract answers four questions for external integrations: 1. How another plugin may open and control FrontEdit editing. 2. What schema-resolved runtime data FrontEdit guarantees to expose. 3. Which lifecycle hooks and events are stable for observing editing and the standard FrontEdit save flow. 4. Which globals and implementation details are explicitly private. ## Scope This contract covers the browser runtime only. It does not define: 1. PHP handler registration. 2. Schema authoring rules. 3. Internal editor-state layout. 4. REST endpoint internals or private save helpers. 5. Internal DOM classes or data attributes unless explicitly documented here. ## Stability Model FrontEdit exposes one stable base namespace for browser integrations: ```js window.MWP.SFE.PublicApi ``` When FrontEdit Pro is active, FrontEdit also exposes one optional pro-only namespace: ```js window.MWP.SFE.ProApi ``` Everything else on `window.MWP.SFE` is private unless this document explicitly says otherwise. ### Two-tier contract This document uses two stability tiers: 1. `V1 committed surface` External plugins may rely on these methods, events, and return shapes. 2. `Candidate APIs under evaluation` These are roadmap items only. They are not part of the stable contract and may change or never ship. ### Versioning The runtime extension contract is versioned independently from the schema contract. `window.MWP.SFE.PublicApi` must expose: ```js SFE.PublicApi.getApiInfo(); ``` Expected shape: ```js { apiVersion: 1, namespace: 'window.MWP.SFE.PublicApi', features: { editorControl: true, runtimeInspection: true, editableBlockDiscovery: true, editingRuntimeResolution: true, publicOperationContracts: true, operations: true, operationPreflight: true, listOperationContracts: true, mediaInspection: true, mediaSessionControl: true, explicitStaging: true, events: true, blockRefresh: true } } ``` Version rules: 1. Additive methods, additive event payload fields, and additive snapshot fields are minor-safe. 2. Removing or renaming methods, changing event semantics, or changing documented return-shape meaning requires an `apiVersion` bump. 3. Private internals may change at any time without notice. ## Public Namespace Rules External plugins may: 1. Call documented `SFE.PublicApi.*` methods. 2. Call documented `SFE.ProApi.*` methods only when the pro plugin is active and the method is documented here as pro-only. 3. Subscribe only to documented `SFE.PublicApi` events. 4. Store and compare documented snapshot data returned by the API. External plugins must not: 1. Monkey-patch FrontEdit methods. 2. Directly mutate `window.MWP.SFE` objects unless a documented API explicitly allows it. 3. Depend on underscore-prefixed properties. 4. Rebuild schema runtime resolution, media descriptor resolution, or block-state hydration from private internals when a public API exists. ## V1 Committed Surface ### Discovery #### Server-side AI discovery When the WordPress Abilities API is available, an authorized FrontEdit editor may call the following post-scoped, read-only abilities: 1. `mwpsfe/list-editable-blocks` with `post_id` to retrieve selectable block UUIDs, block types, edit handler IDs, and source-text summaries. 2. `mwpsfe/get-editable-block` with `post_id` and `uuid` to retrieve focused content for one already-authorized editable block. 3. `mwpsfe/get-public-operation-contract` with `post_id` and `uuid` to retrieve the handler-derived public operation contract and its current public input state for one already-authorized editable block. 4. `mwpsfe/get-frontend-runtime-contract` with `post_id` to retrieve this canonical browser contract. These abilities authorize the current user against the exact requested post; they do not discover WordPress posts/pages, execute browser methods, or create an external save path. WordPress core remains responsible for page discovery. Once the browser runtime is present, integrations must still verify availability through `SFE.PublicApi.getApiInfo()` and use `SFE.PublicApi.getEditableBlocks()` to enumerate the live page. #### Server-side `current_operation_state` `mwpsfe/get-public-operation-contract` returns an immutable `contract` and a separate mutable `current_operation_state` array. Each record is: ```json { "componentId": "content", "operationId": "rewrite_text", "state": { "runs": [] } } ``` The record's `state` object contains only the public inputs declared by that operation. FrontEdit derives these values from the owning handler's schema and current parsed block state; it never publishes attributes, bindings, selectors, or executor metadata. Integrations must use this projection when a generated proposal needs to preserve a current text, media, link, or setting value. They must not reconstruct an equivalent map from raw block attributes. `core/list` keeps its documented browser-owned current-state surface through `SFE.PublicApi.getListStructure(...)` and its operation descriptor through `SFE.PublicApi.getListOperationContract(...)`, because its runtime list-item UUIDs are session-scoped rather than server-side generic operation IDs. #### `getEditableBlocks() -> EditableBlock[]` Return the FrontEdit-editable blocks currently known to the live page runtime. ```js const blocks = SFE.PublicApi.getEditableBlocks(); const match = blocks.find(block => block.contentText.includes('Pricing')); ``` Each entry is a `BlockSnapshot` plus `contentText`, which is normalized text from the current rendered block element. Use its `uuid` with `resolveEditingRuntime(...)` before choosing a documented edit operation. This method is the supported browser discovery path; integrations must not scrape private FrontEdit DOM attributes to enumerate UUIDs. #### Human save handoff An integration may inspect a block, open FrontEdit, and apply documented runtime operations or staging. It must then hand control to the authorized human to review and complete FrontEdit's standard save UI. V1 has no external direct-save API. #### `getApiInfo()` ```js const info = SFE.PublicApi.getApiInfo(); ``` Returns the contract version and feature flags for this runtime. ### Editor Control #### `openEditor(options) -> Promise` Open FrontEdit editing for a target block through the supported runtime path. ```js await SFE.PublicApi.openEditor({ uuid, element, handlerId, componentId, mode: 'edit', source: 'external' }); ``` Rules: 1. `uuid` is required. 2. `element` is optional when the block can be resolved from `uuid`. 3. `handlerId` is optional when FrontEdit can resolve the applicable handler for the block. 4. `componentId` is optional. When supplied, FrontEdit targets the documented editable component for the session. 5. `mode` defaults to `'edit'`. 6. `source` is a caller label for diagnostics and event payloads. Returns an `EditorSnapshot` when FrontEdit opened an editor session, otherwise `null`. #### `closeEditor(options = {}) -> boolean` Close the active editor session through the supported runtime path. ```js SFE.PublicApi.closeEditor({ uuid, restoreOriginal: true, reason: 'api', source: 'external' }); ``` Rules: 1. `uuid` is optional. When omitted, FrontEdit closes the active editor if one exists. 2. `restoreOriginal` defaults to `true`. 3. `reason` is an informational reason token. 4. `source` is a caller label for diagnostics and event payloads. Returns `true` when a close was attempted through the active supported editor session, otherwise `false`. #### `isEditorOpen() -> boolean` Returns whether FrontEdit currently has an active editor session. #### `getActiveEditor() -> EditorSnapshot|null` Returns a stable snapshot of the current active editor session. The return value is a snapshot, not a mutable live internal object. ### Runtime Inspection #### `resolveRuntime(options) -> ResolvedRuntime|null` Resolve the schema-aware runtime view FrontEdit would use for editing. ```js const runtime = SFE.PublicApi.resolveRuntime({ uuid, element, handlerId }); ``` Returns a stable runtime snapshot for the target block or `null` when no supported runtime could be resolved. #### `resolveEditingRuntime(options) -> ResolvedEditingRuntime|null` Resolve the richer schema-driven editing runtime FrontEdit would use for active editing or proposal materialization. ```js const runtime = SFE.PublicApi.resolveEditingRuntime({ uuid, element, handlerId, blockState, attributeChanges }); ``` Returns a detailed editing runtime snapshot with resolved component metadata and live component element references. Rules: 1. `uuid`, `element`, and `handlerId` follow the same resolution rules as `resolveRuntime()`. 2. `blockState` is optional. When supplied, FrontEdit resolves the runtime against that staged block state instead of the current session baseline. 3. `attributeChanges` is optional. When supplied, FrontEdit resolves the runtime against those pending block attribute changes. 4. The returned runtime data is read-only snapshot data except for documented DOM element references inside component entries. #### `getEditOperationContract(options) -> EditOperationContract|null` Return FrontEdit's read-only, schema-derived contract for AI or other generated operation proposals. It is a compact projection of the currently resolved handler components and their `editor.operations`; it does not expose a DOM element, route, save control, nonce, or mutable editor state. ```js const contract = SFE.PublicApi.getEditOperationContract({ uuid, element, handlerId }); ``` ```ts type EditOperationContract = { contractVersion: 1; uuid: string; operations: Array<{ id: string; componentId: string; inputs: Record; values?: Array; allowedRunFormats?: string[]; requiredRunFormatAttributes?: Record; }>; }; ``` Rules: 1. Use this contract to discover the exact operations, allowed values, and required inputs for this live block. Do not infer them from a block name or toolbar label. 2. The contract deliberately excludes attribute paths, selectors, executor kinds, serialization behavior, routes, and mutable editor internals. 3. It is not an authorization or mutation API. Generated proposals remain untrusted and must pass FrontEdit preflight before apply. 4. A handler must explicitly mark an operation `publicOperation: true` before it appears here. FrontEdit does not maintain a second public allowlist or synthesize generic operations from a block type. 5. `allowedRunFormats`, when present for a `rich_text_runs` input, contains the handler-declared format tokens permitted in each returned run. `requiredRunFormatAttributes`, when present, maps a format token to the minimum named values that must be present in that run's `formatAttributes[formatToken]` object. It exposes neither rendering tags, optional format data, selectors, nor mutation details. 6. List editing retains its established UUID-oriented list API. Its legal operation kinds and inputs are exposed separately through `getListOperationContract(...)`; they are not part of this generic generated-proposal envelope. #### `getEditableComponents(options) -> EditableComponent[]` Returns the runtime-editable components for the resolved block. #### `getDefaultComponent(options) -> EditableComponent|null` Returns the default editable component for the resolved block, if one exists. ### Public Operation Runtime V1 uses one attribute-free public operation envelope: ```js const operations = [ { id: operation.id, componentId: operation.componentId, inputs: { /* only values declared by getEditOperationContract() */ } } ]; const preflight = SFE.PublicApi.preflightOperations({ uuid, operations }); if (preflight?.valid === true) { SFE.PublicApi.applyOperations({ uuid, operations }); } ``` #### `preflightOperations(options) -> OperationPreflightResult|null` Validate an opaque operation batch against an already open editor without mutating DOM, history, preview state, or saved content. ```ts type OperationPreflightResult = { uuid: string; valid: boolean; validatedOperationIds: string[]; errors: Array<{ code: string; id?: string; componentId?: string }>; }; ``` #### `applyOperations(options) -> OperationResult|null` Stage the same preflighted opaque batch through FrontEdit's shared schema executor. FrontEdit resolves the operation locally from the active handler, performs normal preview and history work, and leaves review, save, and cancel under its normal editor lifecycle. Rules: 1. Call `openEditor(...)` explicitly before preflight or apply. 2. Each operation must exactly match a declaration from `getEditOperationContract(...)`. 3. Callers must not send `kind`, `attribute`, `attributes`, `bindingSource`, DOM selectors, or serialization metadata. 4. Callers processing generated or untrusted content must require `valid === true` before apply. 5. This is a staging API, never a direct-save API. `applyOperations(...)` returns `appliedOperationCount` in addition to its operation ID summary. Integrations that generate a batch must treat the stage as failed unless that count equals the requested operation count. ### V1 Operation Recipes All non-list mutations use the schema-derived operation envelope. Discover the operation on the live block, open that block's editor, preflight the exact batch, then apply the same batch. FrontEdit owns the resulting preview, history, review, cancel, and save lifecycle. #### Operation Envelope ```js const contract = SFE.PublicApi.getEditOperationContract({ uuid, element, handlerId }); if (!contract) { throw new Error('No edit-operation contract is available for this block.'); } const getOperation = predicate => { const operation = contract.operations.find(predicate); if (!operation) { throw new Error('The requested operation is not supported by this block.'); } return operation; }; const stage = async operations => { const preflight = SFE.PublicApi.preflightOperations({ uuid, operations }); if ( preflight?.valid !== true || preflight.validatedOperationIds.length !== operations.length ) { throw new Error('FrontEdit rejected the operation batch.'); } const result = SFE.PublicApi.applyOperations({ uuid, operations }); if (result?.appliedOperationCount !== operations.length) { throw new Error('FrontEdit did not stage every operation.'); } return result; }; ``` Every generic operation has exactly this shape: ```js { id: operation.id, componentId: operation.componentId, inputs: { // Exactly the declared input names and values for this operation. } } ``` Use the operation's `inputs` map as the complete field contract. Include every required input, omit optional inputs you do not need, and do not send `kind`, attribute paths, selectors, binding metadata, or other internal fields. #### Text Replacement Find an operation that declares a `rich_text_runs` input and submit the complete replacement run sequence for that component: ```js const rewrite = getOperation(operation => ( operation.componentId === 'content' && operation.inputs.runs?.type === 'rich_text_runs' )); await SFE.PublicApi.openEditor({ uuid, element, handlerId, componentId: rewrite.componentId }); await stage([{ id: rewrite.id, componentId: rewrite.componentId, inputs: { runs: [ { text: 'Updated copy', formats: [], formatAttributes: {} } ] } }]); ``` Use only `allowedRunFormats` exposed by that operation. When `requiredRunFormatAttributes` declares values for a format, include them in the matching run's `formatAttributes` object. #### Scalar Block Setting Settings such as alignment or heading level are schema operations with a declared scalar input. The concrete ID, component, allowed values, and any additional inputs come from the resolved contract: ```js const alignment = getOperation(operation => ( operation.componentId === 'content' && operation.inputs.value?.type === 'scalar' && Array.isArray(operation.values) && operation.values.includes('center') )); await SFE.PublicApi.openEditor({ uuid, element, handlerId, componentId: alignment.componentId }); await stage([{ id: alignment.id, componentId: alignment.componentId, inputs: { value: 'center' } }]); ``` If the declared operation has additional required inputs, include those exact fields in `inputs`. For example, a column-scoped setting can require a `columns` input in addition to `value`. #### Host Link Update For an anchor-host component, select the operation that declares the URL input and provide its declared optional link settings only when needed: ```js const link = getOperation(operation => ( operation.componentId === 'label' && operation.inputs.href?.type === 'url' )); await SFE.PublicApi.openEditor({ uuid, element, handlerId, componentId: link.componentId }); await stage([{ id: link.id, componentId: link.componentId, inputs: { href: 'https://example.com/pricing', new_tab: true } }]); ``` #### Media URL Replacement When the live operation contract declares a URL input for a media component, stage the URL as a generic operation. The operation ID remains contract-owned: ```js const media = getOperation(operation => ( operation.componentId === 'image' && operation.inputs.url?.type === 'url' )); await SFE.PublicApi.openEditor({ uuid, element, handlerId, componentId: media.componentId }); await stage([{ id: media.id, componentId: media.componentId, inputs: { url: 'https://example.com/uploads/updated-image.jpg', source: 'input' } }]); ``` Include `attachmentId` only when the media source provides one. When the contract declares `source`, use `library` for a WordPress media-library item or `input` for a direct URL. For a selected WordPress media-library or upload item, use the documented [`applyActiveMediaSelection(options)`](#applyactivemediaselectionoptions---editorsnapshotnull) method in [Media Inspection And Session Control](#media-inspection-and-session-control). That method's reference includes its required active-session setup and exact request shape. ### List Runtime V1 retains the public list-tree runtime for `core/list`-style blocks that are edited as one root block while exposing nested item/list structure to external callers. #### `getListStructure(options) -> ListNode|null` Return the current live structural snapshot for one open or discoverable list block. ```js const structure = SFE.PublicApi.getListStructure({ uuid, element }); ``` Rules: 1. `uuid` is required unless `element` can be resolved to a block UUID. 2. The target block must resolve to a live `UL` or `OL` root. 3. The return value is a read-only structural snapshot of the live DOM tree. #### `getListOperationContract(options) -> ListOperationContract|null` Return the FrontEdit-owned, read-only operation descriptor for one live list root. This is the machine-readable source of truth for public list operation kinds and their exact input fields. Integrations must use it rather than maintaining a separate list-operation catalog. ```js const contract = SFE.PublicApi.getListOperationContract({ uuid, element }); ``` ```ts type ListOperationContract = { contractVersion: 1; uuid: string; operations: Array<{ kind: string; inputs: Record; }>; }; ``` Rules: 1. `uuid` is required unless `element` can be resolved to a block UUID. 2. The target must resolve to a live `UL` or `OL` root. 3. Each `inputs` map is exact: callers must not add an input not declared for that operation. 4. `existing_list_item_uuid` accepts an item UUID from the current `getListStructure(...)` result. `new_list_item_uuid` is a fresh caller-owned item UUID for an insertion. `direct_list_item_html` is direct item text HTML and must not contain `li`, `ul`, or `ol` wrappers. 5. This is an inspection API, not mutation authority. Callers still must use `preflightListOperations(...)` successfully before `applyListOperations(...)`. #### `applyListOperations(options) -> ListOperationResult|null` Apply one or more structural list mutations as one runtime batch. #### `preflightListOperations(options) -> ListOperationPreflightResult|null` Validate a UUID-oriented list batch against the open FrontEdit list editor without mutating it. ```js const preflight = SFE.PublicApi.preflightListOperations({ uuid, operations }); if (preflight?.valid === true) { SFE.PublicApi.applyListOperations({ uuid, operations }); } ``` Return shape: ```ts type ListOperationPreflightResult = { uuid: string; valid: boolean; validatedOperationKinds: string[]; errors: Array<{ code: string; index?: number }>; }; ``` `insert_child` validates its supplied insertion command during preflight. Its internal follow-up indent is resolved only during the subsequent FrontEdit apply because the new runtime item does not exist until that point. ```js const result = SFE.PublicApi.applyListOperations({ uuid, operations: [ { kind: 'update_list_item_text', itemUuid: '7db1a4ff-8e25-4f7d-a806-9328d473bb96', contentHtml: 'Alpha' }, { kind: 'toggle_list_type', itemUuid: '7db1a4ff-8e25-4f7d-a806-9328d473bb96', } ] }); ``` Rules: 1. `uuid` is required. 2. The target list editor must already be open. 3. `operations` are applied in order against the live mutated tree. 4. Every operation must supply the correct documented UUID target token family for its kind. 5. FrontEdit resolves each operation's runtime UUIDs against the current post-mutation tree immediately before that operation runs. 6. Public callers must use only operation kinds and exact inputs advertised by `getListOperationContract(...)`. 7. Some public operations may expand into multiple internal primitive mutations. For example, `insert_child` inserts the new item after the parent item, then indents it so the tracker creates the nested child list through the normal editor path. 8. Successful batches return one updated list structure snapshot. #### Current V1 list operation descriptor The contract currently advertises: 1. `update_list_item_text` 2. `insert_before` 3. `insert_after` 4. `insert_child` 5. `remove_list_item` 6. `move_before` 7. `move_after` 8. `indent_list_item` 9. `outdent_list_item` 10. `toggle_list_type` These are the public API kinds only. Internally FrontEdit still executes lower-level primitive list operations such as `insert_list_item`, `move_list_item`, and `toggle_list_type`, but only the descriptor returned by `getListOperationContract(...)` is the machine-readable runtime contract. #### List operation payloads | Kind | Required fields | Optional fields | Description | | --- | --- | --- | --- | | `update_list_item_text` | `kind`, `itemUuid`, `contentHtml` | -- | Replaces the direct text HTML for one existing list item. | | `insert_before` | `kind`, `newItemUuid`, `targetItemUuid`, `contentHtml` | -- | Inserts a new sibling item before the target item. | | `insert_after` | `kind`, `newItemUuid`, `targetItemUuid`, `contentHtml` | -- | Inserts a new sibling item after the target item. | | `insert_child` | `kind`, `newItemUuid`, `targetItemUuid`, `contentHtml` | -- | Creates a new child item under the target item. | | `remove_list_item` | `kind`, `itemUuid` | -- | Removes one existing list item and its nested children. | | `move_before` | `kind`, `itemUuid`, `targetItemUuid` | -- | Moves an existing item before the target item. | | `move_after` | `kind`, `itemUuid`, `targetItemUuid` | -- | Moves an existing item after the target item. | | `indent_list_item` | `kind`, `itemUuid` | -- | Indents one existing item through the normal editor list behavior. | | `outdent_list_item` | `kind`, `itemUuid` | -- | Outdents one existing item through the normal editor list behavior. | | `toggle_list_type` | `kind`, `itemUuid` | -- | Toggles the containing list for the referenced item between ordered and unordered. | `contentHtml` is required for operations that create or replace item content. It represents the direct item text HTML only. It must not include wrapping `
  • `, `
      `, or `
        ` elements. Consumers must derive whether an operation carries content from its `direct_list_item_html` input descriptor, not from a copied operation-kind allowlist. #### Public target-token rules Public list operations must use only the documented runtime UUID token family for their kind. FrontEdit rejects public operations that omit the required token. 1. `update_list_item_text`, `remove_list_item`, `indent_list_item`, and `outdent_list_item` target one existing list item and must provide `itemUuid`. 2. `insert_before` and `insert_after` must provide `newItemUuid` plus `targetItemUuid`. 3. `insert_child` must provide `newItemUuid` plus `targetItemUuid`. If the target item does not already own a child list, FrontEdit creates the required nested list automatically. 4. `move_before` and `move_after` must provide `itemUuid` plus `targetItemUuid`. 5. `toggle_list_type` must provide `itemUuid`. FrontEdit resolves that item to its current containing list immediately before the toggle runs. 6. Public callers must use only the documented camelCase keys above. Any other keys are outside the API contract. 7. Public callers must not rely on cursor or selection state. Cursor-based inference is reserved for internal editor-originated calls only. #### Runtime UUID ownership List-item UUIDs and list-node UUIDs have different ownership rules. 1. Public callers are responsible for generating UUIDs for newly created list items. 2. FrontEdit is responsible for generating and managing UUIDs for list nodes. 3. Public callers must not create, assign, or mutate list-node UUIDs directly. 4. When a structural operation creates a new nested list, FrontEdit assigns the resulting `listUuid`. 5. Public list operations target items, not list nodes, even though returned structure snapshots still expose `listUuid` values. #### Recommended UUID convention FrontEdit does not enforce a specific caller UUID format for public list operations. Recommended convention: 1. Use RFC 4122 version 4 UUID strings for `itemUuid`, `targetItemUuid`, and `newItemUuid`. 2. Generate one fresh UUID for every new list item the caller intends to create. 3. Treat runtime UUIDs as session-scoped cursor tokens only. Do not persist or reuse them after the list editor closes or the page reloads. #### `ListNode` `getListStructure()` and successful list-operation results return recursive list nodes plus item nodes so child lists remain distinct from the list items that own them. A list node contains: 1. `listUuid` (string): session-scoped runtime UUID for this list node 2. `listPath` (string): empty string for the root list, or the parent item path that owns this nested child list 3. `ordered` (boolean): whether this specific list node is `OL` 4. `items` (array): direct child list items for this list node Each item contains: 1. `itemUuid` (string): session-scoped runtime UUID for this item 2. `path` (string): item tree path 3. `pathLabel` (string): human-readable 1-based path label 4. `depth` (number): zero-based nesting depth 5. `contentHtml` (string): direct item text HTML only 6. `childList` (`ListNode|null`): nested list node owned by this item, or `null` Example: ```json { "listUuid": "e183f2b5-2f90-4ca2-8ea8-c3d8c72ab2c1", "listPath": "", "ordered": false, "items": [ { "itemUuid": "7db1a4ff-8e25-4f7d-a806-9328d473bb96", "path": "0", "pathLabel": "1", "depth": 0, "contentHtml": "a", "childList": { "listUuid": "fd818143-75d8-4ae9-8f3f-798d54150472", "listPath": "0", "ordered": false, "items": [ { "itemUuid": "c71fad00-2d1f-4f70-b0fc-84681f5ef8a0", "path": "0_0", "pathLabel": "1.1", "depth": 1, "contentHtml": "b", "childList": { "listUuid": "2cd9f476-1d91-45e5-bd0b-ae981b78a111", "listPath": "0_0", "ordered": false, "items": [ { "itemUuid": "b2f4ffad-edbc-48e9-950d-2716b5bf6942", "path": "0_0_0", "pathLabel": "1.1.1", "depth": 2, "contentHtml": "c", "childList": null } ] } } ] } } ] } ``` #### `ListOperationResult` Successful list runtime mutations return: 1. `uuid` (string): target block UUID 2. `operationsApplied` (array of strings): normalized operation kinds applied in order 3. `structure` (`ListNode`): updated live list structure snapshot ### Media Inspection And Session Control #### `getMediaContext(options) -> MediaContext|null` Resolve the media-editable context for a block or specific component. ```js const mediaContext = SFE.PublicApi.getMediaContext({ uuid, element, handlerId, componentId }); ``` Returns `null` when the target block is not media-editable through the documented runtime surface. #### `getMediaDescriptor(options) -> MediaDescriptor|null` Returns the stable media descriptor for the selected file component, if one exists. #### `isMediaEditable(options) -> boolean` Returns whether the resolved block exposes a file-editable component through the public runtime contract. #### `applyActiveMediaSelection(options) -> EditorSnapshot|null` Apply one selected media item to the current active schema-media editor session through FrontEdit's supported runtime path. ```js const editor = SFE.PublicApi.applyActiveMediaSelection({ uuid, url, attachmentId, source: 'library' }); ``` Rules: 1. `uuid` is required and must match the current active editor session. 2. `url` is required. 3. `attachmentId` is optional. 4. `source` may be `'library'` or `'input'` and defaults to the input-style save transition when omitted. 5. Returns an updated `EditorSnapshot` when the active media session accepted the selection, otherwise `null`. ### Explicit Staging V1 supports explicit block-state staging only. Staging is editor preparation, not external save execution. External plugins may stage block state and open or guide the FrontEdit editor, but the user must complete saving through FrontEdit's standard save UI and normal FrontEdit save workflow. #### `stageBlockState(stage) -> void` Stage a temporary block-state payload for one block so that subsequent editor open and hydration flows may consume it through FrontEdit's supported staging path. ```js SFE.PublicApi.stageBlockState({ uuid, handlerId, blockState, source: 'external' }); ``` Rules: 1. `uuid` is required. 2. `handlerId` is optional metadata for the caller and diagnostics. 3. `blockState` must match the shape FrontEdit's canonical block hydration path expects. 4. Staged block state is temporary and applies only through the documented FrontEdit runtime path. 5. Staged changes do not create a supported external save path in V1. #### `clearStagedBlockState(uuid) -> void` Clear any currently staged block state for the given block UUID. ### Session Utilities #### `on(eventName, handler) -> unsubscribeFn` Subscribe to one documented runtime event. ```js const unsubscribe = SFE.PublicApi.on('save:after', payload => { // observe successful save completion }); ``` Returns an unsubscribe function equivalent to calling `off(eventName, handler)`. #### `off(eventName, handler) -> void` Remove a previously registered event handler. #### `refreshBlock(uuid, options?) -> Promise` Request that FrontEdit refresh the live DOM for one block through its supported refresh path and return the resulting `BlockSnapshot`. ### Dirty State, Page Context, and Lookup Utilities #### `getDirtyBlocks() -> DirtyBlock[]` Returns a stable summary of the blocks that currently have unsaved changes in the active runtime session. #### `hasDirtyBlocks() -> boolean` Returns whether any block currently has unsaved changes. #### `isBatchSessionActive() -> boolean` Returns whether FrontEdit currently has an active batch-edit session. #### `resetDirtyBlocks(uuids, options?) -> boolean` Reset the tracked dirty batch state for the supplied block UUIDs back to the current batch-session baseline. ### Pro-only Session Utilities These methods exist only when FrontEdit Pro is active on the page. #### `ensureBatchSession() -> Promise` Ensure the shared batch-edit session exists before downstream runtime checks or mutations depend on it. ```js const ready = await SFE.ProApi.ensureBatchSession(); ``` Rules: 1. This method is available only on `window.MWP.SFE.ProApi`. 2. It returns `false` when batch editing is unavailable or disabled for the current page. 3. It may be called repeatedly; repeated calls are safe and reuse any in-flight session bootstrap work. #### `getPageContext() -> PageContext` Returns a stable page-level runtime snapshot. #### `getRestContext() -> RestContext` Returns the REST context FrontEdit guarantees to expose for supported runtime integrations. #### `setRestNonce(nonce) -> RestContext` Update the REST nonce FrontEdit should use for subsequent supported runtime requests on the current page. ```js const restContext = SFE.PublicApi.setRestNonce(refreshedNonce); ``` Rules: 1. `nonce` must be a non-empty string. 2. This updates only the current page runtime state. It does not fetch or mint a new nonce on its own. 3. Callers should use this after their own authenticated nonce-refresh flow succeeds. 4. The return value is the updated `RestContext`. #### `getElementByUuid(uuid) -> Element|null` Returns the current live DOM element for a block UUID, if present. #### `getUuidForElement(element) -> string` Returns the FrontEdit block UUID for the supplied element, or an empty string when none is available. #### `getBlockSnapshot(uuid) -> BlockSnapshot|null` Returns a stable summary snapshot for one block UUID. #### `getEditableBlocks() -> EditableBlock[]` Returns the stable live-page discovery snapshots described above. ## Stable Snapshot Shapes Snapshots are plain data contracts. They are not live editor-state objects and must not be mutated to control FrontEdit. ### `EditorSnapshot` ```json { "uuid": "8d63...", "handlerId": "core_image", "mode": "edit", "blockName": "core/image", "componentType": "file", "componentId": "image", "saveStrategy": "single", "hasMediaSession": true, "isDirty": false, "isDraftSession": false, "isBatchSession": false } ``` Required fields: 1. `uuid` 2. `handlerId` 3. `mode` 4. `blockName` 5. `componentType` 6. `componentId` 7. `saveStrategy` 8. `hasMediaSession` 9. `isDirty` 10. `isDraftSession` 11. `isBatchSession` Field notes: 1. `hasMediaSession` is `true` when the active editor host currently exposes the supported media-selection control surface used by `SFE.PublicApi.applyActiveMediaSelection(...)`. ### `ResolvedRuntime` ```json { "uuid": "8d63...", "handlerId": "core_cover", "blockName": "core/cover", "schemaVersion": 1, "mode": "mixed", "defaultComponentId": "image", "components": [] } ``` Required fields: 1. `uuid` 2. `handlerId` 3. `blockName` 4. `schemaVersion` 5. `mode` 6. `defaultComponentId` 7. `components` ### `EditableComponent` ```json { "id": "image", "label": "Image", "type": "file", "selector": "figure", "default": true, "target": { "selector": "img", "attribute": "src", "mediaType": "image" }, "mediaDescriptor": { "componentId": "image", "scopeSelector": "figure", "targetSelector": "img", "attribute": "src", "mediaType": "image" } } ``` Required fields: 1. `id` 2. `label` 3. `type` 4. `selector` 5. `default` Optional fields: 1. `required` 2. `placeholder` 3. `target` 4. `mediaDescriptor` 5. `editor` ### `MediaDescriptor` ```json { "componentId": "image", "scopeSelector": "figure", "targetSelector": "img", "attribute": "src", "mediaType": "image" } ``` Required fields: 1. `componentId` 2. `scopeSelector` 3. `targetSelector` 4. `attribute` 5. `mediaType` ### `MediaContext` ```json { "supported": true, "componentId": "image", "mediaType": "image", "accept": "image/*", "label": "image block", "descriptor": { "componentId": "image", "scopeSelector": "figure", "targetSelector": "img", "attribute": "src", "mediaType": "image" } } ``` Required fields: 1. `supported` 2. `componentId` 3. `mediaType` 4. `accept` 5. `label` 6. `descriptor` ### `DirtyBlock` ```json { "uuid": "8d63...", "handlerId": "core_paragraph", "blockName": "core/paragraph", "beforeRaw": "

        Old

        ", "afterRaw": "

        New

        " } ``` Required fields: 1. `uuid` 2. `handlerId` 3. `blockName` 4. `beforeRaw` 5. `afterRaw` ### `PageContext` ```json { "postId": 123, "permissions": { "can_publish": true, "can_draft": false, "can_comment": true, "can_batch": true }, "hasDraftPreview": false, "isEditorOpen": false, "activeMode": "" } ``` Required fields: 1. `postId` 2. `permissions` 3. `hasDraftPreview` 4. `isEditorOpen` 5. `activeMode` ### `RestContext` ```json { "baseUrl": "https://example.com/wp-json/", "namespaceUrl": "https://example.com/wp-json/mwpsfe/v1/", "nonce": "..." } ``` Required fields: 1. `baseUrl` 2. `namespaceUrl` 3. `nonce` ### `BlockSnapshot` ```json { "uuid": "8d63...", "blockName": "core/image", "handlerIds": ["core_image", "core_image_comment"], "isPending": false, "pendingInfo": null, "elementPresent": true } ``` Required fields: 1. `uuid` 2. `blockName` 3. `handlerIds` 4. `isPending` 5. `pendingInfo` 6. `elementPresent` ### `EditableBlock` An `EditableBlock` contains every `BlockSnapshot` field plus: ```json { "contentText": "Visible normalized text from this block" } ``` `contentText` is the current rendered text used for live-page matching. It is not serialized Gutenberg block markup and must not be used as a write payload. ## Stable Events V1 events are observable only. They provide visibility into FrontEdit runtime lifecycle. They are not interception points and do not allow cancellation, mutation, or alternate control flow through event payload side effects. ### Subscription rules 1. Consumers may subscribe only through `SFE.PublicApi.on()`. 2. Consumers must treat event payloads as snapshots. 3. Event payload objects must not be mutated. ### `editor:opened` Fires after FrontEdit has opened an editor session through a supported path. Payload: ```json { "source": "external", "editor": {} } ``` Required fields: 1. `source` 2. `editor` as `EditorSnapshot` ### `editor:beforeClose` Fires before FrontEdit closes an editor session through a supported path. Payload: ```json { "source": "api", "reason": "api", "editor": {} } ``` Required fields: 1. `source` 2. `reason` 3. `editor` as `EditorSnapshot` ### `editor:closed` Fires after FrontEdit closes an editor session through a supported path. Payload: ```json { "source": "api", "reason": "api", "editor": {} } ``` Required fields: 1. `source` 2. `reason` 3. `editor` as `EditorSnapshot` ### `editor:componentChanged` Fires when FrontEdit changes the active editable component within one editor session. Payload: ```json { "source": "sfe", "editor": {} } ``` Required fields: 1. `source` 2. `editor` as `EditorSnapshot` ### `save:before` Fires when FrontEdit is about to begin a supported save path. Payload: ```json { "source": "sfe", "editor": {}, "saveStrategy": "single" } ``` Required fields: 1. `source` 2. `editor` as `EditorSnapshot` 3. `saveStrategy` ### `save:after` Fires after FrontEdit completes a supported save path successfully. Payload: ```json { "source": "sfe", "editor": {}, "saveStrategy": "single", "success": true } ``` Required fields: 1. `source` 2. `editor` as `EditorSnapshot` 3. `saveStrategy` 4. `success` ### `save:error` Fires when FrontEdit's supported save path fails. Payload: ```json { "source": "sfe", "editor": {}, "saveStrategy": "single", "message": "REVISION_CONFLICT" } ``` Required fields: 1. `source` 2. `editor` as `EditorSnapshot` 3. `saveStrategy` 4. `message` ### `block:staged` Fires after `stageBlockState()` records a staged block-state payload. Payload: ```json { "source": "external", "uuid": "8d63...", "handlerId": "core_image" } ``` Required fields: 1. `source` 2. `uuid` 3. `handlerId` ### `block:stageCleared` Fires after `clearStagedBlockState()` clears a staged block-state payload. Payload: ```json { "source": "external", "uuid": "8d63..." } ``` Required fields: 1. `source` 2. `uuid` ### `block:refreshed` Fires after FrontEdit refreshes a block's live DOM through the supported refresh path. Payload: ```json { "source": "sfe", "block": {} } ``` Required fields: 1. `source` 2. `block` as `BlockSnapshot` ## Candidate APIs Under Evaluation The following APIs are not part of V1 and are intentionally non-contractual in this document: 1. `consumeMediaSelection()` 2. `getActiveMediaSession()` 3. `registerBlockStateProvider()` 4. `unregisterBlockStateProvider()` These remain candidate APIs under evaluation so FrontEdit can improve its internal runtime boundaries before committing to stable provider or live media-session semantics. ## Private Runtime Boundary The following names and object families are explicitly private and unsupported for external integrations: 1. `SFE.Context` 2. `SFE.ManagerData` 3. `SFE.SchemaRuntime` 4. `SFE.MediaHelper` 5. `SFE.Api` 6. `SFE.SaveHelpers` 7. `SFE.SaveHooks` 8. `SFE.BlockSerializer` 9. `SFE.BatchEditManager` 10. `SFE.ResolveBlockState` 11. `SFE.ResolveEditorStrategy` 12. Raw `handler.client_config` 13. Raw editor-state objects 14. Underscore-prefixed properties such as `_mwpSchemaRuntime` and `_mwpSchemaMediaSession` 15. Undocumented DOM classes and data attributes Private APIs may change without a public contract version notice. ## Extension Rules External runtime integrations must follow these rules: 1. Use `window.MWP.SFE.PublicApi` as the only supported JS entry point. 2. Use FrontEdit runtime inspection APIs instead of re-resolving schema handlers or media descriptors from private state. 3. Use explicit staging APIs instead of overriding global block-state resolvers. 4. Use documented lifecycle events instead of patching editor open or close methods. 5. Use stable snapshots only for observation and coordination, never for direct mutation of live FrontEdit state. 6. Preserve FrontEdit's canonical save pipeline and do not bypass block serialization rules. 7. Do not treat V1 as a supported direct-save API; saving remains user-driven through standard FrontEdit controls. 8. When an integration refreshes the shared REST nonce during a long-lived session, it should synchronize FrontEdit through `SFE.PublicApi.setRestNonce(...)` instead of mutating `SFE.ManagerData` directly. Grain-Free Pet Food Benefits: The Surprising Truth 2026 | PetEatWell

        Grain-Free Pet Food Benefits: The Surprising Truth

        More pet owners are choosing grain-free food for their dogs and cats. The reasons might surprise you.

        Grain-free pet food can improve your pet’s digestion, boost energy levels, and support healthier skin and coat, especially for animals with food sensitivities.

        But before you make the switch, there’s more you need to know about what makes these diets special.

        Grain-Free Pet Food Benefits

        I’ve seen so many pet parents wonder if they’re making the right choice for their furry family members. The truth is, grain-free diets aren’t just a trend—they offer real benefits that can transform your pet’s health and happiness.

        From better weight management to improved dental health, these specialized foods address issues that traditional kibble often can’t. You’re about to discover exactly what grain-free means, how to spot the best options for your pet, and why veterinarians are increasingly recommending these diets for specific health concerns.

        Let’s see if this nutritional approach could be the game-changer your pet’s been waiting for.

        Grain-Free Pet Food Benefits Key Takeaways

        • Grain-free pet food eliminates wheat, corn, and rice while focusing on high-quality protein sources that better match your pet’s natural diet
        • These diets can significantly improve digestion, energy levels, and coat health while helping manage weight and supporting dental care
        • Choosing the right grain-free option requires understanding your pet’s specific needs and consulting with your veterinarian for the best results

        What Makes Pet Food Grain-Free?

        Grain-free dog food swaps out traditional grains like wheat, corn, and rice for alternative carbs such as potatoes and peas. These formulas focus on higher protein content while eliminating ingredients that some dogs struggle to digest.

        Common Ingredients in Grain-Free Dog Food

        When I look at grain-free dog food labels, I notice they’re packed with ingredients you won’t find in regular kibble. Instead of corn or wheat, these foods use potatoes, sweet potatoes, and peas as their main carb sources.

        Here’s what typically goes into a grain-free diet:

        Primary Carbohydrates:

        • Sweet potatoes (rich in fiber and vitamins)
        • Regular potatoes (energy source)
        • Peas and pea protein
        • Lentils and chickpeas
        • Tapioca starch

        Protein Sources:

        • Real meat as the first ingredient
        • Fish meals
        • Novel proteins like duck or venison

        The protein content usually runs higher than traditional dog food. I’ve seen grain-free formulas with 25-35% protein compared to 18-25% in regular kibble.

        Many grain-free dog food options also include added supplements like probiotics and omega-3 fatty acids. These extras support digestive health and coat shine.

        How Grain-Free Diets Differ from Traditional Dog Food

        The biggest difference? Traditional dog food relies heavily on grains as cheap fillers. Grain-free formulas flip this approach completely.

        Traditional Dog Food Structure:

        • Grains make up 30-50% of the formula
        • Lower protein percentages
        • Corn, wheat, and soy as primary carbs
        • More processed ingredients

        Grain-Free Structure:

        • Alternative carbs replace grains entirely
        • Higher meat content
        • Focus on whole food ingredients
        • Limited ingredient lists

        I notice grain-free diets often cost more because they use pricier ingredients. A bag of traditional kibble might run $30, while grain-free versions can hit $50-70 for the same size.

        The manufacturing process is different too. Grain-free vs grain dog food production requires different equipment and sourcing since alternative carbs behave differently than grains during processing.

        Dogs digest these foods differently as well. The higher protein and different carb sources can lead to firmer stools and sometimes better nutrient absorption in some pups.

        Core Benefits of Going Grain-Free

        I’ve seen so many pet parents struggle with digestive issues, mystery allergies, and dull coats. Going grain-free often tackles these problems head-on by removing common irritants and boosting protein quality.

        Improved Digestion and Fewer Upset Tummies

        Ever watched your dog pace around after dinner, looking uncomfortable? I know that worried feeling all too well.

        Many dogs struggle to break down grains properly. Their systems are designed more like their wild ancestors—built for meat, not wheat.

        When I switched my own dog to grain-free, the difference was obvious within days. No more 3 AM wake-up calls for emergency bathroom breaks.

        Common digestive improvements include:

        • Less gas and bloating
        • Firmer, more regular bowel movements
        • Reduced stomach gurgling and discomfort
        • Better nutrient absorption

        The reason? Grain-free diets often lead to improved digestion because they eliminate hard-to-digest ingredients like corn, wheat, and soy.

        Think of it this way—if something consistently upsets your stomach, you’d stop eating it, right? Your dog deserves the same consideration.

        Some dogs with grain sensitivities show signs like loose stools, excessive gas, or even vomiting after meals. These symptoms often clear up within 2-3 weeks of switching.

        Allergy and Sensitivity Relief

        Let’s be real—watching your pet scratch constantly breaks your heart.

        Food allergies in dogs are more common than most people realize. Grains like wheat, corn, and rice top the list of problem ingredients.

        I’ve talked to pet parents who spent hundreds on vet visits, trying to figure out why their dog kept scratching. The answer was often hiding in their food bowl.

        Signs your dog might have grain sensitivities:

        • Constant scratching or licking
        • Red, irritated skin
        • Chronic ear infections
        • Hot spots that won’t heal

        Grain-free dog food may help with allergy relief by removing these common trigger ingredients entirely.

        The key is elimination. When you remove potential allergens, you give your dog’s immune system a chance to calm down and heal.

        Some dogs see improvements in as little as two weeks. Others might take 6-8 weeks to show full results.

        Here’s the kicker—many grain allergies develop over time. Your dog might have eaten the same food for years before suddenly developing problems.

        Shinier Skin and Healthier Coats

        You know that moment when you run your fingers through your dog’s fur and it feels like silk? That’s what we’re aiming for.

        Grain-free foods typically pack more high-quality protein and essential fatty acids. These nutrients are like premium fuel for your dog’s skin and coat.

        I notice the biggest changes in dogs with previously dull, brittle fur. Within 4-6 weeks, their coats often transform completely.

        What to expect:

        • Softer texture – fur feels less coarse to touch
        • Natural shine – no more dull, lifeless appearance
        • Less shedding – stronger hair follicles mean less hair everywhere
        • Reduced dandruff – healthier skin produces less flaking

        The magic happens because grain-free foods are often higher in protein and essential fatty acids. These nutrients directly support skin cell regeneration and healthy coat growth.

        Think of protein as the building blocks for strong, shiny fur. Most grain-free formulas contain 25-35% protein compared to 18-22% in grain-inclusive foods.

        Omega-3 and omega-6 fatty acids work like natural moisturizers from the inside out. They reduce inflammation and keep skin supple and healthy.

        The results speak for themselves—many pet parents report their dogs get more compliments at the dog park after making the switch.

        Energy, Weight, and Dental Perks

        A happy dog and cat in a bright kitchen with a bowl of grain-free pet food, both looking healthy and energetic.

        Grain-free dog food delivers three game-changing benefits that’ll make you wonder why you waited so long to switch. Your pup gets steady energy that lasts all day, easier weight control, and cleaner teeth naturally.

        Increased Energy Levels for Playful Pups

        Ever notice your dog dragging after meals? That sluggish feeling might be coming from grain-heavy kibble that spikes and crashes their blood sugar.

        Grain-free formulas pack more protein and fewer empty carbs. This means your furry friend gets steady energy levels instead of those post-meal naps.

        I’ve seen dogs transform from couch potatoes to playful pups within weeks of switching. The protein-rich ingredients fuel their muscles better than grain fillers ever could.

        Key energy boosters in grain-free food:

        • Higher meat content (30-50% vs 18-25%)
        • Complex carbs from sweet potatoes
        • Healthy fats for sustained fuel

        Your dog’s stamina during walks and playtime should noticeably improve. No more cutting fetch sessions short because they’re wiped out.

        Weight Management for Healthier Pets

        Let’s be real—pet obesity is a huge problem. Nearly 60% of dogs carry extra weight that puts stress on their joints and organs.

        Grain-free foods help with better weight management because they’re more filling per serving. Your dog feels satisfied with smaller portions.

        The higher protein content builds lean muscle while burning fat. Think of it like switching from donuts to eggs for breakfast—same calories but totally different results.

        Weight management benefits:

        • Smaller portions needed for fullness
        • Less begging between meals
        • Better muscle tone from quality protein
        • Reduced joint stress from weight loss

        My neighbor’s lab dropped 8 pounds in three months just by switching foods. No diet plan needed—the grain-free formula did the heavy lifting.

        Dental Health Advantages

        Here’s something most pet parents don’t know: grain-free kibble often has a different texture that naturally scrapes plaque off teeth.

        The meat-based proteins create firmer kibble pieces. When your dog crunches down, it acts like a mini toothbrush cleaning their molars.

        Plus, fewer sugary grains mean less bacteria fuel in their mouth. Less bacteria equals fresher breath and healthier gums.

        Dental perks you’ll notice:

        • Cleaner teeth after meals
        • Fresher breath (seriously!)
        • Less yellow buildup on molars
        • Reduced need for dental cleanings

        I used to brush my dog’s teeth daily. Now? Maybe twice a week, and his vet actually commented on how clean they look.

        The grain-free kibble is doing most of the work for me.

        Choosing the Best Grain-Free Diet for Your Pet

        A dog and a cat sitting together near bowls of fresh grain-free pet food in a bright kitchen.

        Finding the right grain-free food means looking beyond fancy packaging to check what’s actually inside the bag.

        I’ll walk you through checking nutritional balance, spotting quality ingredients, and finding brands that actually deliver on their promises.

        Evaluating Nutritional Balance and Ingredient Quality

        Let’s be real — not all grain-free foods are created equal.

        Just because it says “grain-free” doesn’t mean it’s automatically healthier for your dog.

        I always check the protein source first. Look for named meats like chicken, beef, or salmon as the first ingredient.

        Avoid vague terms like “meat meal” or “poultry by-product.”

        The tricky part? Many grain-free foods swap grains for potatoes, peas, or lentils.

        These aren’t necessarily bad, but they can be hard to digest for some dogs.

        Here’s what I look for in quality ingredients:

        • Single protein sources (great for dogs with allergies)
        • Limited ingredients overall
        • No artificial colors or preservatives
        • Added vitamins and minerals

        Watch out for foods that are too high in fat. Some grain-free options pack in extra fat to make up for removing grains, which can upset sensitive stomachs.

        The absence of grains can mean fewer digestive issues for dogs with sensitivities, but balance is key.

        Tips for Picking High-Quality Grain-Free Dog Food

        Ever stood in the pet food aisle feeling completely overwhelmed? I get it.

        The walls of options can make your head spin.

        Start with your dog’s specific needs. Active dogs need higher protein and fat content than couch potato pups.

        My step-by-step selection process:

        1. Read the ingredient list (not just the marketing claims)
        2. Check the guaranteed analysis for protein and fat percentages
        3. Look for AAFCO certification on the label
        4. Consider your dog’s age and activity level

        Here’s the kicker — price doesn’t always equal quality.

        I’ve found amazing grain-free foods at reasonable prices and overpriced duds with fancy marketing.

        Talk to your vet before switching. They know your dog’s health history and can spot potential issues.

        Veterinarians can provide the best advice when selecting any new diet.

        Try small bags first. Your dog might love the ingredients on paper but turn their nose up at dinner time.

        Brand Recommendations and Real Reviews

        I’ve tested dozens of grain-free brands with my own dogs and clients over the years.

        Some consistently deliver, others fall short.

        Top performers in my experience:

        • Wellness CORE — solid protein content, dogs love the taste
        • Blue Buffalo Wilderness — good ingredient quality, widely available
        • Taste of the Wild — affordable option with decent ingredients

        The brands that help with allergic reactions and digestive issues tend to use single protein sources and avoid common allergens.

        Red flags I’ve noticed:

        • Brands with multiple recalls
        • Foods that cause loose stools in most dogs
        • Companies that won’t answer ingredient sourcing questions

        Real talk — what works for my neighbor’s Golden Retriever might not work for your Beagle.

        Every dog is different.

        I always recommend transitioning slowly over 7-10 days. Mix increasing amounts of new food with decreasing amounts of old food.

        This prevents stomach upset and helps you spot any issues early.

        FAQs

        1. How do I know if my dog needs grain-free food?

        Watch for signs like itchy skin, digestive upset, or ear infections after eating regular kibble.

        Most dogs digest grains just fine, so only switch if you notice specific problems.

        2. Is grain-free dog food more expensive than regular food?

        Yes, typically 20-40% more expensive.

        The alternative carbohydrates and higher protein content drive up costs, but you might feed smaller portions due to better nutrient density.

        3. Can puppies eat grain-free dog food?

        Puppies can eat grain-free food, but choose formulas specifically made for growth.

        Puppy formulas have the right calcium and phosphorus ratios for developing bones.

        4. How long does it take to see benefits from grain-free food?

        Most dogs show improvements in digestion within 2-3 weeks.

        Skin and coat changes take 6-8 weeks since it takes time for new hair growth to reflect dietary changes.

        5. Should I avoid all grains or just certain ones?

        If your dog has grain allergies, corn and wheat are the most common culprits.

        Rice and oats are usually well-tolerated, so complete grain elimination isn’t always necessary.

        The pet food industry is experiencing major shifts driven by owner preferences and scientific research.

        Grain-free formulas dominate shelf space while sustainability concerns reshape ingredient sourcing. Ongoing debates keep challenging long-held beliefs about pet nutrition.

        Let’s be real — grain-free dog food has taken over pet store aisles.

        I’ve watched this trend explode from a niche option to representing more than 40% of available dry dog foods in the United States.

        What’s driving this boom?

        Pet owners are treating their furry family members more like actual family.

        When I see customers shopping, they’re reading ingredient lists just like they would for their own food.

        The numbers tell the story. Survey data from five countries shows people who believe their dog has food allergies are four times more likely to choose grain-free options.

        Here’s what’s interesting though. The most common grain alternatives I see in these formulas are:

        • Peas (most popular choice)
        • Potatoes and sweet potatoes
        • Lentils and chickpeas
        • Cassava

        But here’s the kicker — grain-free doesn’t automatically mean low-carb.

        Many of these alternatives still provide significant carbohydrate content, just from different sources.

        Sustainability and Ingredient Sourcing

        Ever wonder where all those pea proteins and novel ingredients come from?

        The pet food industry is facing serious questions about sustainable sourcing.

        I’ve noticed manufacturers scrambling to find reliable suppliers for legumes and alternative proteins.

        This shift creates new challenges.

        The sustainability puzzle includes:

        • Land use for growing peas versus traditional grains
        • Water requirements for processing legumes
        • Transportation costs for specialty ingredients
        • Local vs. global sourcing decisions

        What fascinates me is how human food trends are trickling down to pet nutrition.

        Owners gravitating toward plant-based meals want similar options for their pets.

        The irony? Some of these “premium” ingredients might have larger environmental footprints than the grains they replace.

        Industry Myths and Debates

        Now here’s where things get messy.

        The pet food industry is wrestling with some major misconceptions about grain-free diets.

        Myth #1: Grains are inherently bad for pets

        Truth bomb — grains provide essential nutrients and fiber that support pet health.

        They’re cost-effective energy sources that most dogs and cats digest just fine.

        The DCM controversy changed everything

        Between 2014 and 2019, the FDA documented 524 cases of dilated cardiomyopathy in pets, with 124 deaths.

        This potential link to grain-free diets sent shockwaves through the industry.

        Myth #2: Grain-free prevents allergies

        Here’s what veterinarians know — grain allergies in pets are actually quite rare.

        Most food sensitivities come from proteins, not grains.

        The regulation reality

        I find it telling that commercial pet food faces the same standards as human food at both state and federal levels.

        Yet marketing often outweighs science in consumer choices.

        The ingredient list remains the top factor when people choose pet food, even though nutritional profiles matter more than individual ingredients.

        Frequently Asked Questions

        Pet owners ask me these questions daily, and honestly, the answers might surprise you.

        Let’s tackle the real concerns about grain-free diets and what veterinarians actually think.

        If you’ve ever wondered whether grain-free chow can really pep up your pooch—what’s the real scoop from vets about it?

        I’ll be straight with you — vets are divided on this one.
        Most veterinarians I know say grain-free diets aren’t necessary for every dog. They’re mostly for pups with real grain allergies, but that’s not something you see every day.
        Here’s what might actually pep up your pup: The higher protein in grain-free foods. Dogs are carnivores at heart, so more protein can mean a bit more zip in their step.
        But here’s the kicker — some vets worry about heart issues tied to certain grain-free formulas. Always check with your vet before switching, especially if your dog seems happy and healthy on their current food.

        Ever see your cat just not quite herself? Could skipping the grain be her ticket to purr-fection?

        That distant look in your cat’s eyes might not be about grains at all.
        Cats are obligate carnivores. They need meat to live, plain and simple.
        If your kitty seems off, it could be her stomach, stress, or even just boredom. Sometimes it’s not about food at all.
        Grain-free might help if: Your cat has itchy skin, tummy troubles, or true grain sensitivities. I’ve seen cats with corn allergies perk up on grain-free diets.
        But consider this first: Is she getting enough water? Enough play? Sometimes the easy fixes—like a water fountain or a new toy—work wonders before you overhaul her food.

        Let’s be real, you want the best for your furry friend—so what are the genuine advantages and possible downsides of grain-free munchies?

        The honest truth? It’s not black and white.
        Real advantages I’ve witnessed:
        Better digestion for sensitive pets
        Increased energy levels from higher protein content
        Reduced allergy symptoms in grain-sensitive animals
        Shinier coats in some dogs
        The downsides that worry me:
        Higher cost that can strain budgets
        Potential heart issues (DCM) linked to some formulas
        Environmental impact from alternative ingredients

        Peeked through the latest pet forums? Spot any chatter on why some pets might want to steer clear of grain-free diets?

        The forum buzz is real, and some of it’s actually valid.
        Pet parents are talking about dilated cardiomyopathy (DCM), a serious heart condition. The FDA has looked into links between certain grain-free foods and DCM, especially those heavy on peas, lentils, and potatoes.
        What I’m seeing discussed:
        Dogs developing heart problems on boutique grain-free brands
        Nutritional imbalances from poorly formulated foods
        Pets doing worse after switching from quality grain-inclusive diets
        The smart approach? Pick grain-free foods that meet AAFCO standards. Try to avoid brands that use mostly peas and legumes as main ingredients.

        Now, what can you do if you’ve heard the buzz that grain-free pet food isn’t all it’s cracked up to be? Are those whispers true?

        Those whispers have some truth, but don’t panic.
        The grain-free trend got hyped beyond what science actually supports. Most dogs without specific allergies do just fine on grain-inclusive diets.
        Here’s what I’d do:
        Think about why you picked grain-free in the first place
        Check your pet’s real health, not just marketing claims
        Maybe switch back if your pet was healthier before
        If you want to stick with grain-free: Go for brands with meat as the first ingredient, not peas or potatoes. Look for foods that include taurine and have feeding trials behind them.

        Ever noticed that look your dog gives you when something’s up? Yeah, could that be a sign to ditch the grain, or is it all just fluff?

        That look might be telling you something, but it’s probably not about grains.
        Dogs give us “the look” for all sorts of reasons. Sometimes it’s stomach upset, anxiety, boredom, or maybe they’re just after some attention.
        I’ve tried to read these signals, and honestly, most aren’t about food.
        Watch for these real red flags:
        Constant scratching or licking
        Loose stools or gas after meals
        Vomiting within hours of eating
        Loss of energy or appetite
        Before blaming grains: Try cutting out treats or see if there’s something stressing your dog out. Maybe they just need more exercise.
        Sometimes that sad face just means “I’m bored” or “where’s my dinner?”
        If symptoms stick around after you rule out other stuff, then talk to your vet about trying a food trial.

        As an Amazon Associate, we may earn a commission from qualifying purchases if you click on the links within this article. Learn more.

        Leave a Reply