# 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. Dog Poop Secrets Every Owner Will LOVE Knowing 2026 | PetEatWell

        Dog Poop Secrets Every Owner Will LOVE Knowing

        Let’s be real—dog poop isn’t glamorous, but if you have a pup, you deal with it every single day. Dog poop tells you more about your dog’s health than almost anything else you spot on a walk.

        From color to texture, every detail gives a clue about what’s happening inside your dog. Sometimes it’s subtle, sometimes it’s… well, pretty obvious.

        Dog Poop Secrets Every Owner Will LOVE Knowing

        I’ve had those moments—bending down with a bag in hand, thinking, “Hmm… that doesn’t look normal.” If you’ve ever second-guessed an odd color or weird consistency, trust me, you’re not alone.

        You’re about to learn how to spot the difference between normal and concerning poop. No more second-guessing or awkward Googling in the park.

        Your dog can’t exactly tell you when something feels off, but their poop can. I’ll break down what’s normal, what’s not, and maybe even surprise you along the way.

        Key Takeaways

        • Dog poop gives clear signals about your dog’s health
        • Color and texture changes often point to underlying issues
        • Knowing what’s normal helps you act quickly when something’s wrong

        Dog Poop Basics: What’s Normal, What’s Not

        Dog poop tells me a lot about my pup’s health. Size, shape, and color aren’t random—they’re signals about digestion, diet, and even possible illness.

        Paying attention early helps me catch issues before they turn serious. It’s way easier to fix a food issue than a big health scare.

        How Healthy Dog Poop Should Look

        When I pick up after my dog, I want the poop to be firm but not rock hard. Log-shaped, easy to scoop, and not leaving streaks—basically the gold standard.

        Soft-serve or watery stools? That’s a red flag for me.

        Here’s how I keep it straight:

        • Too hard → possible dehydration or too much bone in the diet.
        • Too softdigestive upset, parasites, or food intolerance.
        • Just right → firm, moist, and holds its shape.

        Size matters too. Large, bulky stools usually mean my dog isn’t digesting food well.

        Small, dry pellets? That may point to constipation.

        Most healthy dogs poop once or twice a day. Some go three times and that’s still normal if the poop looks good.

        If I see mucus coating the stool, that can signal inflammation in the intestines. Shiny, greasy-looking poop may mean fat isn’t being absorbed properly.

        Why Color and Consistency Matter

        Color changes in dog poop are often the first sign something’s off. Normal stools are chocolate brown, thanks to bile.

        If the color shifts, I pay attention. Here’s my cheat sheet:

        • Black or tarry → could mean bleeding in the upper digestive tract.
        • Red streaks → often from lower bowel bleeding or anal gland issues.
        • Yellow or gray → may mean liver or pancreas problems.
        • Green → sometimes from eating grass, but also linked to gallbladder issues.

        Consistency goes hand in hand with color. A watery mess isn’t just gross—it can dehydrate my dog fast.

        Crumbly, chalky stools? That’s often too much calcium, which I’ve seen with raw diets.

        Veterinary guidelines like the AAHA/AAFP feline life stage guidelines point out that stool habits reveal a pet’s overall health. Same goes for dogs—poop is a daily health check I can’t ignore.

        When to Worry About Changes

        I don’t panic over one weird poop. Dogs eat the strangest things—grass, crumbs, socks—so a one-time change isn’t usually a big deal.

        If the problem lasts more than a day or two, that’s when I start to pay attention.

        Warning signs I watch for:

        • Blood that keeps showing up
        • Diarrhea lasting more than 48 hours
        • Poop that looks black and tarry
        • Straining with little or no stool produced
        • Sudden change in frequency or volume

        Sometimes constipation is the culprit. In one case, a cat needed intra-rectal flushing with saline to relieve blockage.

        Dogs can have similar problems, especially if they swallow things they shouldn’t. If my dog also vomits, loses weight, or seems weak, I call the vet right away.


        FAQs

        1. How many times a day should a healthy dog poop?
        Most dogs go once or twice daily. Some may go three times and still be normal if the stool is well-formed.

        2. What does it mean if my dog’s poop is black?
        Black, tar-like stools can signal bleeding in the stomach or small intestine. That needs immediate vet care.

        3. Why is my dog’s poop yellow?
        Yellow stool may point to liver, gallbladder, or pancreas issues. It can also happen with food intolerance.

        4. Can diet changes affect dog poop?
        Yes. Switching foods too quickly often causes soft stools or diarrhea. Gradual changes over 7–10 days help prevent this.

        5. When should I call the vet about dog poop changes?
        If diarrhea lasts more than 48 hours, blood appears, or your dog strains without passing stool, it’s time to call the vet.

        Dog Poop Colors: The Shocking Truth Behind Each Shade

        Dog poop color tells me more about my dog’s health than I ever expected. Just a quick glance can reveal diet issues, hidden bleeding, or even something more serious.

        Paying attention to browns, yellows, reds, or blacks helps me catch problems early. Sometimes, it’s the only warning I get before things get worse.


        Brown, Yellow, and Orange: Common Causes

        Most of the time, dog poop is brown. That’s normal—bile breaks down food and gives stool its color.

        When I see brown, I know my dog’s digestion is probably doing its job. But sometimes the shade shifts.

        Yellow poop often shows up if food moves too quickly through the digestive system. It can also mean my dog ate something new that didn’t sit right, like a sudden switch in kibble.

        Orange poop usually points to bile not being fully absorbed. This can happen with stomach irritation, mild liver stress, or even just a diet change.

        If I see it once, I don’t panic. If it keeps happening, it’s time to call the vet.

        Here’s my quick checklist:

        • Brown = Normal
        • Yellow = Fast digestion or food intolerance
        • Orange = Possible liver or bile issue

        Red and Bloody Stools: What It Means

        Seeing red in dog poop always gets my attention. Bright red streaks usually mean fresh blood from the lower digestive tract—like the colon or rectum.

        Sometimes it’s just from straining or a small tear. But blood can also signal bigger problems—parasites, infections, or even swallowed objects that irritate the gut.

        I once noticed red streaks after my dog chewed a stick. Turns out it scratched his insides. Here’s my breakdown:

        • Small streaks = Often minor, but worth watching
        • Large amounts = Call the vet right away
        • Bloody diarrhea = Emergency, since it can dehydrate fast

        If the poop is red more than once, I don’t wait. I get it checked.


        Black Dog Poop and Melena: Hidden Dangers

        Black, tar-like stool—called melena—is one of the scariest things I’ve seen. It usually means blood has been digested higher up in the stomach or small intestine.

        This blood looks dark because stomach acids have broken it down. Causes range from ulcers to swallowed toxins, or even medications.

        Research on poop identification says black stool should never be ignored.

        When I see black dog poop, I treat it as urgent. A vet can run tests to figure out if it’s ulcers, internal bleeding, or something else.

        The key is not waiting—melena almost always points to a serious condition.


        FAQs

        1. Why is my dog’s poop yellow?
        Yellow poop often means food is moving too quickly through the gut or your dog isn’t digesting something well. Check for recent diet changes.

        2. What does orange dog poop mean?
        Orange stool can signal bile not being absorbed properly. It may relate to digestion speed, liver stress, or a sudden diet shift.

        3. Should I worry if I see red streaks in my dog’s poop?
        Small streaks can be from minor irritation, but repeated or heavy bleeding should be checked by a vet.

        4. What is melena in dogs?
        Melena is black, tar-like stool caused by digested blood from the stomach or intestines. It’s a warning sign of internal bleeding.

        5. When should I call the vet about poop color changes?
        If the color change happens more than once, or if you see black stool, heavy blood, or bloody diarrhea, call your vet immediately.

        Dog Poop Textures: From Soft Stool to Mucus and More

        Dog poop can tell you a lot about your pup’s health. Texture often points to diet issues, hydration levels, or even bigger medical concerns.

        Paying attention to changes helps me catch problems early. Sometimes, it’s the only clue I get that something’s up.

        Soft Stool and What Triggers It

        I’ve noticed my dog gets soft stool when I switch foods too fast or give him an unfamiliar treat. A sudden diet change is one of the biggest triggers.

        Stress, parasites, and mild infections can also loosen things up. Soft stool doesn’t always mean an emergency, but it can signal gut irritation.

        Too much fat or rich food often upsets digestion. Sometimes it’s just a blip, but if it sticks around, I start to worry.

        • Keep meals consistent
        • Add a little plain pumpkin for fiber
        • Make sure he’s drinking enough water

        If the problem lasts more than a couple of days, I call the vet. Chronic soft stool can point to parasites or digestive conditions that need treatment.

        Mucus in Dog Poop: What’s Going On?

        Seeing mucus in dog poop can feel alarming, but it’s not always bad news. Sometimes a thin, clear layer coats the stool because the intestines naturally produce it to keep things moving.

        Problems start when there’s a lot of mucus or it looks jelly-like. That can mean inflammation in the large intestine.

        According to research on canine gastrointestinal mucus, it’s a highly hydrated, gel-like material that protects the gut lining. Too much in the stool might signal irritation, infection, or even parasites.

        When I spot this in my own dog, I check for other signs like blood, straining, or frequent trips outside. If mucus shows up often, I don’t wait it out—I book a vet visit.

        Dry, Hard, or Pebble-Like Poop

        On the flip side, I’ve dealt with poop that looks like little dry pebbles. This usually means constipation.

        The stool sits too long in the colon, and too much water gets absorbed, leaving it hard and tough to pass. Dehydration is a big cause here.

        If my dog isn’t drinking enough, or if it’s hot outside, his poop can dry out fast. Low-fiber diets or lack of exercise make constipation more likely.

        • Has constant access to fresh water
        • Gets daily walks to keep digestion moving
        • Eats food with enough fiber

        If constipation lasts more than two days or my dog strains without results, I take him to the vet. Dry, hard poop can sometimes hide a blockage, and that’s not something I want to risk.


        FAQs

        1. Why is my dog’s poop soft but not diarrhea?
        Soft stool often happens from diet changes, stress, or mild gut irritation. If the stool still has shape, it’s not full-blown diarrhea.

        2. What does mucus in dog poop mean?
        Small amounts are normal, but large amounts can mean inflammation, parasites, or infection.

        3. How do I firm up my dog’s stool?
        Keep meals consistent, add fiber like pumpkin, and make sure your dog’s hydrated. If it keeps going, see a vet.

        4. What causes hard, pellet-like poop in dogs?
        Usually dehydration, low fiber, or constipation. It can also point to an obstruction if it doesn’t improve.

        5. When should I worry about my dog’s poop?
        If you see blood, lots of mucus, ongoing soft stool, or your dog seems in pain, it’s time to call the vet.

        Health Issues Linked to Unusual Dog Poop

        When my dog’s poop looks different, I know something’s going on inside. Changes in color, texture, or frequency usually point to stomach troubles, parasites, or even side effects from food and medicine.

        Paying attention early can prevent bigger health problems later. Ever cleaned up after your dog and noticed watery stool or streaks of mucus?

        Gastroenteritis and Digestive Upsets

        That’s often a sign of gastroenteritis, which is just inflammation of the stomach and intestines. It can show up suddenly and leave your dog weak, dehydrated, and uncomfortable.

        I’ve seen it triggered by spoiled food, sudden diet changes, or even stress. Vomiting, diarrhea, and loss of appetite are the classic red flags.

        If the diarrhea lasts more than a day, contains blood, or your dog refuses water, I don’t wait. I call the vet right away.

        • Offer small amounts of water often
        • Hold off on food for 12 hours (if the vet agrees)
        • Reintroduce bland meals like boiled chicken and rice

        Parasites, Worms, and Foreign Objects

        Nothing makes me cringe more than spotting worms in my dog’s poop. Roundworms, hookworms, and whipworms are common, and they can spread to people too.

        Studies show that infections like visceral larva migrans often affect children who come into contact with contaminated soil. Parasites usually cause diarrhea, weight loss, or a bloated belly.

        Sometimes the poop looks normal, which makes regular stool checks important. I always bring a fresh sample to the vet during yearly visits.

        Foreign objects are another issue. Dogs eat socks, sticks, or even rocks, and these can block the intestines.

        If the poop looks stringy, tar-like, or has sharp fragments, I treat it as an emergency. Prevention is simple but powerful:

        • Monthly parasite prevention
        • Cleaning up poop quickly in the yard
        • Keeping unsafe objects out of reach

        Diet, Stress, and Medication Effects

        Not every poop problem means infection. Sometimes it’s just food, stress, or meds.

        I noticed when I switched my dog’s kibble too quickly, the result was soft, smelly stools for days. Gradual changes over a week work much better.

        Stress is another sneaky cause. Moving homes, loud noises, or even me leaving for work can upset my dog’s stomach.

        Stress-related diarrhea usually clears up once the routine settles, but I still keep an eye on hydration. Medications play a role too.

        Antibiotics, for example, often wipe out good gut bacteria. That’s when I add probiotics (with my vet’s okay) to balance things out.

        1. Diet: Transition foods slowly, mix old and new.
        2. Stress: Keep routines steady, add calming toys.
        3. Medication: Ask the vet about side effects and probiotic support.

        By tracking what my dog eats, how they act, and what meds they’re on, I can usually connect the dots before things get worse.


        FAQs

        1. Why does my dog’s poop suddenly look different?
        Changes often mean stomach upset, parasites, or diet effects. Keep track of food, stress, and new meds to narrow it down.

        2. When should I take my dog to the vet for diarrhea?
        If it lasts more than 24 hours, has blood, or your dog won’t drink water, call your vet.

        3. Can dog poop spread diseases to humans?
        Yes. Parasites like roundworms and hookworms in poop can infect people, especially kids who play in contaminated areas.

        4. Why is my dog’s poop sometimes yellow or slimy?
        Yellow or mucus-covered poop often points to digestive inflammation, food intolerance, or infection.

        5. How can I prevent poop problems in my dog?
        Use parasite prevention, change diets slowly, keep stress low, and schedule regular vet checkups with stool tests.

        Strange Dog Poop Habits: What’s Behind Coprophagia?

        I’ve had dogs all my life, and nothing surprises me more than when one decides poop is a snack. Coprophagia, or stool eating, can feel gross to us but often has roots in instinct, diet, or even stress.

        Understanding why it happens makes it easier to deal with. Let’s be real—seeing your dog munch on poop is enough to make your stomach turn.

        Why Dogs Eat Poop

        But for dogs, it’s not always about being “gross.” Some studies show that about 16% of dogs are frequent stool eaters according to an internet survey.

        • Instinct: Wild canines removed feces to keep dens clean.
        • Diet gaps: Low-quality food or poor digestion can push them to seek nutrients.
        • Attention-seeking: Some dogs learn it gets a big reaction from us.
        • Stress or boredom: Just like chewing furniture, poop eating can be a coping behavior.

        I’ve noticed puppies are the worst offenders. They often outgrow it, but some adult dogs keep the habit.

        Researchers even suggest it may be a leftover survival behavior from their ancestors, who sometimes relied on feces for extra protein or to avoid parasites in fresh waste, as noted in scientific reviews.

        How to Stop Coprophagia

        When I went through this with my own dog, I realized scolding just made things worse. I started focusing more on prevention and redirection.

        Here are steps that helped me out:

        1. Pick up poop quickly. Don’t give your dog the chance.
        2. Improve diet. Switching to higher-quality food reduced the urge.
        3. Add deterrents. Some safe additives make poop taste bitter to dogs.
        4. Keep them busy. Toys, training, and exercise cut down on boredom-driven behavior.
        5. Teach “leave it.” This command saved me more than once on walks.

        Some vets check for underlying issues like malabsorption or parasites. In rare cases, coprophagia links to medical conditions, including autoimmune concerns discussed in research on canine health.


        FAQs

        1. Is coprophagia dangerous for dogs?
        Usually it’s not harmful, but it can expose dogs to parasites or bacteria, especially if they eat other animals’ feces.

        2. Do dogs eat poop because they’re missing nutrients?
        Sometimes. Poor diet or digestion issues can play a role, but not all cases are diet-related.

        3. Will my puppy outgrow eating poop?
        Many puppies stop as they mature, but some continue into adulthood if not managed.

        4. Can training stop coprophagia?
        Yes. Commands like “leave it” and consistent poop cleanup are very effective.

        5. Should I take my dog to the vet for coprophagia?
        If it’s frequent or paired with other symptoms like weight loss or diarrhea, a vet visit is a smart move.

        Frequently Asked Questions

        Dog poop tells me a lot about my pup’s health. From how often they go, to the color, texture, and even the smell, every detail gives clues about what’s happening inside their body.

        Paying attention helps me spot issues early and know when it’s time to call the vet.

        What’s the scoop on different poop? Ever spotted something odd in your dog’s business and wondered what it means for their health?

        I’ve noticed that normal dog poop should be firm, easy to pick up, and brown in color. If it’s too hard, my dog might be dehydrated.
        If it’s loose or watery, it could mean stress, diet changes, or even an infection.
        When I see mucus, blood, or worms, I know it’s not something to ignore. That’s my cue to reach out to the vet right away.

        Let’s be real, how often should your furry friend be doing their business? Understanding the normal frequency can save you from worry!

        Most healthy dogs poop one to three times a day. My pup usually goes after meals, and that’s pretty normal.
        If my dog skips a day but still eats and acts fine, I don’t panic. But if they strain, seem uncomfortable, or go too often, I take note—it might be a sign of constipation or diarrhea.

        Got a pup with a sensitive stomach? What can the consistency of their stool tell you about their dietary needs?

        When my dog’s stool is soft but formed, I know their diet is working. If it turns runny or greasy, it often means their food isn’t being digested well.
        I’ve learned that adding fiber, switching to a gentler protein, or avoiding fatty treats can help. If the problem sticks around, I check with the vet before changing too much.

        Ever seen a rainbow in your dog’s waste? No, it’s not a unicorn visit — but what could changing colors indicate about your pooch’s well-being?

        Brown is normal, but I’ve seen other shades too. Green poop can point to grass eating or gallbladder issues.
        Black or tarry poop may signal bleeding higher up in the digestive tract. Yellow or gray stool could mean problems with the liver or pancreas.
        That’s why I don’t ignore sudden color changes—they tell me something’s off inside.

        You know how a picture says a thousand words? What if you could ‘read’ your dog’s health from a snapshot of their stool?

        I use the “Four Cs” rule: color, consistency, coating, and content. If it’s brown, firm, clean, and free of foreign objects, I can relax.
        But if I spot undigested food, worms, or a slimy coating, that’s a red flag. It’s like a quick health check every time I clean up.

        Wondering if your dog’s output is normal or a sign of something serious? How can you tell when it’s time to visit the vet?

        I don’t rush to the vet for just one odd poop. But if my dog has diarrhea for more than two days, or strains without success, I start to worry.
        If I see blood, that’s when I know it’s time to call the vet.
        Other warning signs? Vomiting, loss of appetite, or sudden weight loss make me act fast.
        Honestly, poop problems can be the first sign that something bigger is going on.

        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