# 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. Fresh Dog Food Cost vs Kibble: The Truth No One Tells Dog Parents 2026 | PetEatWell

        Fresh Dog Food Cost vs Kibble: The Truth No One Tells Dog Parents

        I’ve been feeding my dog fresh food for over two years now, and the number one question I get from fellow pet parents is always about cost. Fresh dog food typically costs 2-3 times more than traditional kibble, ranging from $3-8 per day depending on your dog’s size and the brand you choose. But here’s what most people don’t realize – that price difference isn’t as scary as it sounds when you break down what you’re actually getting.

        Fresh Dog Food Cost

        Let me be honest with you. When I first looked at fresh dog food prices, I nearly choked on my coffee. My wallet wasn’t exactly jumping for joy at the thought of spending $150+ per month on dog food. But after watching my pup’s energy levels soar and his coat become ridiculously shiny, I started digging deeper into where that money actually goes.

        You’re about to discover exactly what drives fresh dog food costs, how subscription services stack up against DIY options, and whether homemade versus commercial diets actually save you money. I’ll break down the real numbers, share some sneaky ways to cut costs, and help you figure out if your dog’s health improvements are worth the investment.

        Key Takeaways

        • Fresh dog food costs 2-3 times more than kibble but offers significantly higher nutritional value and quality ingredients
        • Making homemade fresh food can reduce costs by 30-50% compared to commercial fresh food delivery services
        • The investment often pays off through reduced vet bills and improved dog health over time

        What Drives Fresh Dog Food Cost?

        Fresh dog food pricing stems from premium human-grade ingredients, custom meal planning by pet nutritionists, and specialized cold-chain shipping requirements. These three factors create the biggest cost differences compared to traditional kibble.

        Ingredient Quality and Sourcing

        I’ve noticed that human-grade ingredients make up the largest portion of fresh dog food cost. These premium proteins like grass-fed beef, free-range chicken, and wild-caught salmon cost 3-4 times more than conventional pet food ingredients.

        The sourcing matters too. Many fresh dog food companies work directly with farms that meet USDA human food standards. This means stricter quality controls, organic certifications, and higher labor costs.

        Here’s what drives ingredient costs higher:

        • USDA-certified facilities for processing
        • Organic produce sourced from verified farms
        • Fresh vegetables instead of dried meal alternatives
        • No by-products or fillers commonly used in kibble

        When pet owners prioritize health and nutrition over price, they’re willing to pay more for these premium ingredients. The difference is like comparing a fast-food burger to a farm-to-table meal.

        Personalized Meal Plans vs Standard Recipes

        Custom meal plans created by a pet nutritionist cost significantly more than one-size-fits-all recipes. I’ve seen companies charge 40-60% more for personalized formulations based on your dog’s age, weight, activity level, and health conditions.

        Standard recipes use bulk production methods that keep costs lower. But personalized plans require:

        • Individual nutritional analysis
        • Custom portion calculations
        • Specialized ingredient combinations
        • Regular plan adjustments

        Some fresh dog food services offer both options. The standard recipes work well for healthy dogs, while personalized plans target specific needs like weight management or food allergies.

        The choice between them often determines if you’ll pay $3 per day or $8 per day for the same sized dog.

        Packaging and Shipping Expenses

        Cold-chain shipping makes up 20-30% of your total cost of fresh dog food. Unlike dry kibble that ships at room temperature, fresh meals need refrigerated trucks and insulated packaging.

        The logistics get expensive fast:

        • Dry ice or gel packs for temperature control
        • Insulated boxes that cost $8-12 each
        • Express shipping to maintain freshness
        • Regional distribution centers to reduce shipping distances

        Most companies ship weekly or bi-weekly to spread these costs across multiple meals. But if you live in remote areas, expect higher shipping fees.

        I’ve found that subscription services help reduce these expenses by optimizing delivery routes and bulk packaging. Single orders always cost more per meal due to the fixed shipping overhead.

        The packaging itself uses food-grade materials that meet the same standards as human food delivery services. This attention to safety and freshness adds cost but ensures your dog gets meals that haven’t spoiled during transit.

        Price Breakdown: Fresh Dog Food vs Kibble and Other Options

        When I started researching fresh dog food cost, I discovered the daily expense can range from $3 to $12 per dog, while kibble typically costs $1 to $4 daily. The price difference becomes clearer when you break down cost per pound and understand what you’re actually paying for in each option.

        Average Daily and Monthly Costs

        Let me be real with you — fresh dog food will hit your wallet harder than kibble. For a 50-pound dog, I’ve found fresh food costs between $5-8 daily, which adds up to $150-240 monthly.

        Kibble runs much cheaper at $1.50-3 per day for the same size dog. That’s roughly $45-90 monthly. The price gap is significant.

        Here’s what I’ve seen for different dog sizes:

        Small Dogs (10-20 lbs):

        • Fresh: $2-4 daily ($60-120 monthly)
        • Kibble: $0.75-1.50 daily ($23-45 monthly)

        Medium Dogs (30-50 lbs):

        • Fresh: $4-7 daily ($120-210 monthly)
        • Kibble: $1.25-2.50 daily ($38-75 monthly)

        Large Dogs (70+ lbs):

        • Fresh: $7-12 daily ($210-360 monthly)
        • Kibble: $2-4 daily ($60-120 monthly)

        The math gets tougher with multiple dogs. I know owners spending $300+ monthly on fresh food for two medium dogs.

        Comparing Fresh, Kibble, and Canned Dog Food

        Fresh dog food consistently ranks as the most expensive option I’ve researched. Premium fresh brands like Nom Nom or The Farmer’s Dog charge $3-6 per pound.

        Kibble pricing breaks down like this:

        • Budget brands: $1-2 per pound
        • Mid-range: $2-4 per pound
        • Premium: $4-8 per pound

        Canned food sits in the middle at $2-5 per pound. But here’s the kicker — you need more canned food per serving than kibble due to water content.

        I’ve noticed that fresh dog foods were significantly more expensive than dry alternatives, which matches my own price comparisons. The processing and refrigeration costs drive fresh food prices higher.

        Value considerations I’ve found:

        Understanding Cost Per Pound and Serving

        This part trips up most dog parents I talk to. Cost per pound doesn’t tell the whole story — you need to calculate cost per serving based on your dog’s needs.

        A 40-pound dog needs roughly 1,000-1,200 calories daily. Here’s how that translates:

        Fresh Food:

        • 1.5-2 pounds needed daily
        • At $4/pound = $6-8 daily cost

        Kibble:

        • 2.5-3 cups needed daily (about 1 pound)
        • At $2.50/pound = $2.50 daily cost

        The difference comes from calorie density. Fresh food packs more calories per ounce than most kibble.

        I always tell people to calculate by serving, not by bag size. That $80 bag of premium kibble might last 6 weeks, while $80 of fresh food lasts maybe 10 days.

        Package size helps determine the total package cost, so I recommend doing the math per meal to get real costs.

        Pro tip: Factor in shipping costs for fresh food delivery — usually $10-20 weekly — when calculating your true cost per serving.

        Subscription Services and Delivery: Convenience at a Price

        A delivery person hands a box of fresh dog food to a pet owner at their doorstep while a happy dog waits nearby, with a calendar and digital device showing subscription details in the background.

        Fresh dog food subscription services deliver premium meals straight to your door, but you’ll pay significantly more than traditional kibble. Most services charge between $40-120 per month depending on your dog’s size and dietary needs.

        Overview of Fresh Dog Food Delivery Services

        Let me be honest – I was skeptical about fresh dog food delivery services until I saw how much time they actually save.

        These services work like meal kits for humans. You answer questions about your dog’s age, weight, activity level, and food preferences. The company creates custom meal plans and ships frozen or fresh food to your home.

        What makes them different from regular dog food:

        • Human-grade ingredients
        • Custom portion sizes for your specific dog
        • No preservatives or artificial additives
        • Recipes developed by veterinary nutritionists

        The convenience factor is huge. No more lugging heavy bags from the store or wondering if you’re feeding the right amount.

        Most companies ship monthly or bi-weekly. The food arrives in insulated boxes with ice packs to keep everything fresh during delivery.

        I’ve researched the top players in this space, and the pricing varies dramatically based on your dog’s size.

        The Farmer’s Dog charges roughly $2-12 per day depending on your dog’s weight. A 50-pound dog costs about $75 per month.

        Ollie runs slightly higher at $3-12 daily. Their recipes include options like beef with sweet potatoes and turkey with blueberries.

        PetPlate typically costs $2-8 per day. They focus on simple ingredients and offer a 30% discount on your first order.

        Here’s what I found for a 30-pound dog:

        BrandMonthly CostCost Per DayShipping
        The Farmer’s Dog$50-65$1.67-2.17Free
        Ollie$55-70$1.83-2.33Free
        PetPlate$45-60$1.50-2.00Free

        All major dog food brands in this space offer free shipping, but you’re locked into subscription commitments.

        What’s Included in a Typical Subscription

        When your first box arrives, you’ll get more than just food. Most meal delivery services include detailed feeding instructions and transition guides.

        Standard subscription contents:

        • Pre-portioned meals in individual containers
        • Feeding schedule based on your dog’s needs
        • Storage instructions and expiration dates
        • Customer support contact information

        The meals come frozen or refrigerated in eco-friendly packaging. You’ll typically receive 1-4 weeks worth of food depending on your delivery frequency.

        Many services include extras like treats, supplements, or feeding accessories. Some offer veterinary consultations as part of premium plans.

        Subscription flexibility varies by company:

        • Pause deliveries when traveling
        • Adjust portion sizes as your dog grows
        • Switch between recipes
        • Cancel anytime (though some require advance notice)

        Research shows that customers prioritize price, product quality, and personalization when choosing subscription services. The convenience comes at a premium, but many dog owners find the time savings worth the extra cost.

        Is Fresh Dog Food Worth the Price Tag?

        When I first considered switching to fresh dog food, the sticker shock nearly made me walk away. Fresh dog food typically costs 3-5 times more than traditional kibble, but the health benefits and peace of mind might justify every penny for many pet parents.

        Health Benefits and Ingredient Transparency

        Let’s be real — I used to grab whatever bag of kibble was on sale. Then I started reading labels.

        Human-grade ingredients make a massive difference in digestibility and nutrition. Fresh dog food companies use ingredients you’d recognize in your own kitchen: real chicken, sweet potatoes, and leafy greens.

        Here’s what shocked me most:

        • Better nutrient absorption — fresh food is up to 90% digestible versus 70% for kibble
        • Shinier coats within 2-3 weeks of switching
        • More energy and better weight management
        • Reduced dental issues from less processed ingredients

        The transparency factor hits different too. When I can see actual chunks of vegetables and meat, I know exactly what my dog is eating. No mystery meal or by-products.

        Most fresh food brands list every single ingredient with clear sourcing. That level of transparency is worth something, especially when health and wellness attributes are becoming increasingly important in pet food purchasing decisions.

        Role of Pet and Veterinary Nutritionists

        Here’s where things get interesting. Most fresh dog food companies employ veterinary nutritionists to formulate their recipes.

        These aren’t just pet lovers with good intentions. They’re board-certified professionals who understand canine nutrition at a molecular level.

        When I spoke with a pet nutritionist about my dog’s needs, she explained how fresh food allows for better customization. Unlike kibble manufacturers who create one-size-fits-all formulas, fresh food companies can adjust recipes based on:

        • Age and activity level
        • Breed-specific requirements
        • Individual health conditions
        • Weight management goals

        The oversight matters more than I initially realized. A veterinary nutritionist ensures every recipe meets AAFCO standards while maximizing bioavailability of nutrients.

        That expertise costs money, but it’s reflected in better-balanced meals. My vet actually noticed improvements in my dog’s bloodwork after six months on fresh food.

        Managing Food Allergies and Special Diets

        If your dog has food allergies, fresh food becomes less of a luxury and more of a necessity.

        I learned this the hard way when my rescue developed severe skin irritation. Traditional elimination diets with limited ingredient kibble weren’t cutting it.

        Fresh food companies make it incredibly easy to avoid problem ingredients:

        Common allergens they eliminate:

        • Chicken (for poultry-sensitive dogs)
        • Grains and gluten
        • Artificial preservatives
        • Common fillers like corn and soy

        The customization options are game-changing. I can literally select recipes that avoid my dog’s specific triggers.

        Limited ingredient fresh foods typically contain 5-8 ingredients versus 30+ in typical kibble. This makes identifying problem foods much simpler.

        For dogs with chronic digestive issues, the gentle nature of fresh food often provides relief within days. The reduced processing means less stress on sensitive stomachs.

        Yes, fresh food costs more upfront. But when I factor in reduced vet visits for allergy-related issues, the math starts making sense.

        The peace of mind knowing exactly what goes into every meal? That’s priceless for anxious pet parents dealing with food sensitivities.

        DIY or Delivery? Homemade Fresh Dog Food and Cost Savings

        A split scene showing a person preparing fresh dog food in a kitchen on one side and a fresh dog food delivery box at a doorstep on the other, with a dog nearby.

        Making fresh dog food at home can cut costs significantly, but proper nutrition requires careful planning. I’ve found that understanding the true expenses and working with experts makes the biggest difference in both your wallet and your dog’s health.

        Budgeting for Homemade Dog Food

        Let me be real – homemade dog food isn’t always the money-saver you’d expect.

        Research shows that homemade diets were more expensive than dry maintenance diets when properly formulated. But here’s the kicker: chicken-based recipes cost about 43% less than beef-based ones.

        Daily costs break down like this:

        • Small dog (3kg): $0.50-$1.20 per day
        • Medium dog (15kg): $1.80-$4.20 per day
        • Large dog (30kg): $3.20-$7.50 per day

        I always factor in the hidden expenses most people forget:

        • Vitamin and mineral supplements ($15-30/month)
        • Cooking gas or electricity
        • Extra grocery trips and storage space
        • Prep time (usually 2-3 hours weekly)

        Money-saving tips that actually work:

        • Buy ingredients in bulk when possible
        • Choose seasonal vegetables
        • Use cheaper protein sources like chicken thighs
        • Prep meals in large batches and freeze portions

        The real savings often come from avoiding expensive therapeutic commercial diets for dogs with health issues.

        Working with Experts to Balance Nutrition

        Here’s what I learned the hard way: DIY doesn’t mean going it alone.

        Most homemade dog food recipes online are nutritionally incomplete. I’ve seen too many well-meaning pet parents create deficiencies trying to save money.

        A pet nutritionist or veterinary nutritionist becomes essential when:

        • Your dog has health conditions
        • You’re feeding a puppy or senior dog
        • You want recipes for long-term feeding
        • Your dog shows signs of nutritional imbalance

        What to expect from a consultation:

        • Custom recipe formulation: $150-$300
        • Ongoing adjustments: $75-$150 per visit
        • Blood work monitoring: $100-$200 quarterly

        I recommend starting with a one-time consultation to get properly balanced recipes. Many veterinary nutritionists offer package deals that include recipe modifications as your dog ages.

        Red flags that mean you need professional help:

        • Dull coat or excessive shedding
        • Digestive issues lasting more than a week
        • Changes in energy levels
        • Unusual eating behaviors

        The upfront cost of expert guidance prevents expensive vet bills later.

        Pros and Cons of DIY vs Pre-Made

        Every dog owner faces this choice, and I’ve tried both approaches extensively.

        DIY Homemade Advantages:

        • Complete ingredient control
        • No preservatives or fillers
        • Customizable for allergies
        • Potentially lower long-term costs
        • Fresher than any commercial option

        DIY Drawbacks:

        • Time-intensive preparation
        • Risk of nutritional imbalance
        • Higher upfront learning curve
        • Storage and spoilage concerns
        • Travel complications

        Pre-Made Fresh Delivery Services:

        • Nutritionally balanced by experts
        • Convenient portion control
        • No prep time required
        • Consistent quality

        Pre-Made Disadvantages:

        • Higher per-meal costs ($3-$12 daily)
        • Less ingredient flexibility
        • Shipping dependencies
        • Limited customization options

        I’ve found the sweet spot depends on your specific situation. Busy professionals often benefit more from delivery services, while retired pet parents enjoy the DIY process.

        My recommendation? Start with a veterinary nutritionist consultation to get balanced recipes, then decide if the time investment works for your lifestyle. You can always switch between approaches as your needs change.

        For dogs with multiple allergies or health conditions, DIY often provides better value than specialized commercial diets that can cost $80-$150 monthly.

        How to Choose the Right Fresh Dog Food for Your Budget

        A person comparing fresh dog food options with price tags while a dog watches nearby in a kitchen or store setting.

        I know how overwhelming it feels when fresh dog food prices seem to range from affordable to “did I just see that correctly?” Finding quality nutrition that won’t break your bank requires smart shopping strategies and understanding what you’re actually paying for.

        Finding the Best Value for Dog Parents

        The real trick isn’t finding the cheapest fresh dog food. It’s calculating what I call the “true feeding cost” per day.

        Here’s my simple formula:

        1. Take the total bag price
        2. Divide by total weight to get price per pound
        3. Check feeding guidelines for your dog’s weight
        4. Calculate daily feeding cost

        Let me give you a real example. That $60 bag might seem expensive, but if it feeds your 50-pound dog for 30 days, you’re looking at $2 per day. Compare that to a $30 bag that only lasts 10 days – suddenly you’re paying $3 daily.

        Research shows that veterinary exclusive diets are highly competitive in price when you calculate consumption costs properly. Some premium store brands actually cost more per serving than fresh options.

        Look for these value indicators:

        • Higher protein content means better satiety
        • Fewer fillers mean smaller serving sizes
        • Quality ingredients reduce vet bills long-term

        I always check the guaranteed analysis panel. A food with 30% protein will keep my dog fuller longer than one with 18% protein, even if the price per bag is higher.

        Hidden Costs to Watch Out For

        Fresh dog food shopping has sneaky expenses that can double your monthly budget if you’re not careful.

        Shipping costs hit hard. Many brands offer “free shipping” only on orders over $75 or require monthly subscriptions. I’ve seen shipping add $15-25 to small orders, making that “budget-friendly” food anything but affordable.

        Subscription traps are everywhere. Companies hook you with 50% off your first order, then auto-charge full price monthly. I learned this lesson when my “discounted” food suddenly cost $120 instead of $60.

        Storage requirements create unexpected expenses. Fresh food needs freezer space, and some brands require special containers or have shorter shelf lives than expected.

        Watch out for these budget killers:

        • Minimum order requirements
        • Subscription auto-renewals at full price
        • Premium packaging fees
        • Expedited shipping charges for temperature-sensitive foods

        Portion creep is real too. When food smells amazing, I tend to overfeed. That perfectly portioned bag suddenly disappears faster, increasing my monthly costs by 20-30%.

        Some brands charge extra for “customization” or “breed-specific” formulas that are basically the same base recipe with minor tweaks.

        Smart Tips for Saving on Fresh Dog Food

        I’ve discovered several strategies that cut my fresh food costs without compromising quality.

        Buy in bulk during sales. Fresh dog food often goes 30-40% off during Black Friday or brand anniversary sales. I stock my freezer when prices drop and save hundreds yearly.

        Mix feeding approaches strategically. I use fresh food as a topper or mix it 50/50 with high-quality kibble. This gives my dog the benefits of fresh food while stretching my budget.

        Compare brands using my cost-per-calorie method:

        • Check calories per cup on each brand
        • Calculate cost per 1,000 calories
        • Factor in your dog’s daily caloric needs

        Generic or store-brand fresh foods often come from the same facilities as premium brands. I’ve found Costco’s fresh options perform just as well as boutique brands at half the price.

        Timing purchases saves money. Many subscription services offer larger discounts for first-time customers than repeat buyers. I rotate between brands to capture new customer deals.

        Loyalty programs actually pay off with fresh food brands. I earn 10-15% back through points systems, plus get early access to sales.

        Local options beat shipping costs. I found a local pet store that makes fresh food in-house for 40% less than shipped brands. The quality matches national brands, and I support local business.

        Seasonal buying works too. Many brands discount summer inventory in fall, and I freeze portions for later use.

        🐶 Fresh Dog Food Cost FAQs

        Fresh dog food costs vary widely based on your dog’s size, brand choice, and feeding schedule. Most pet parents spend between $50 to $200 monthly, with premium options reaching even higher price points.

        How much more expensive is fresh dog food compared to kibble?

        Fresh Dog Food Cost usually runs between $3–8 per day, while premium kibble costs about $1–3 daily. For a 20-pound dog, expect $60–$120 per month, and for larger breeds like German Shepherds, $150–$300 monthly. Though it’s 2–3 times pricier, you’re paying for human-grade ingredients, fewer fillers, and higher nutritional value—more like a home-cooked meal than a processed one.

        How can I make fresh dog food more affordable?

        You can cut your Fresh Dog Food Cost by mixing 25–50% fresh food with high-quality kibble, buying in bulk, or subscribing to meal plans that offer 10–20% discounts. Many brands also give 30–50% off first orders or run flash sales on social media. Trying smaller or local fresh food makers can also save money without sacrificing quality.

        Can I transition my dog to fresh food gradually?

        Yes! Start by mixing 25% fresh food with 75% of your dog’s current food for 3–4 days. Then move to 50/50, then 75/25, and finally 100% fresh over 10–14 days. This slow transition helps your dog’s digestive system adjust smoothly to the new diet.

        Do I need to refrigerate fresh dog food?

        Most fresh dog foods require refrigeration and stay good for 4–7 days once opened. You can also freeze unopened portions for 6–12 months. Always check the brand’s storage instructions for best results.

        Is fresh dog food nutritionally complete?

        Yes—reputable brands like The Farmer’s Dog, Ollie, Nom Nom, and Freshpet are formulated by veterinary nutritionists and meet AAFCO standards for complete and balanced meals. Always verify this on the packaging before buying.

        How do I know if fresh food is helping my dog?

        Within 2–6 weeks, you’ll likely notice a shinier coat, more energy, better digestion, and healthier weight. For measurable results, your vet can track improvements through regular checkups.

        What’s the difference between grocery store and delivery fresh dog food costs?

        Grocery store options like Freshpet cost around $8–15 per roll, feeding a medium dog for 2–3 days—roughly $80–150 per month. Delivery brands like Ollie or Nom Nom often end up 20–30% cheaper per serving thanks to subscription discounts and bulk pricing. The trade-off? Grocery options offer immediate convenience, while delivery services provide customization and savings over time.

        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