---
title: "Developer reference"
description: "Template overrides, PHP filters and actions, JavaScript events, CSS tokens, REST API and importers."
url: https://scrumptiouswp.com/help/developers/
site: "ScrumptiousWP"
---

# Developer reference

This is the Developers section of the help desk. Everything here is a public, supported extension point; anything not listed is internal and may change.

## 1. Template overrides

Copy any file from the plugin’s `templates/` folder into your theme (or child theme) under `scrumptious/`, keeping the same path. Your copy wins.

| Plugin file | Theme override | What it renders |
| --- | --- | --- |
| `templates/recipe-card.php` | `your-theme/scrumptious/recipe-card.php` | The whole card wrapper and section order |
| `templates/parts/summary.php` | `your-theme/scrumptious/parts/summary.php` | Summary text |
| `templates/parts/rating.php` | `your-theme/scrumptious/parts/rating.php` | Star rating (display + voting) |
| `templates/parts/meta.php` | `your-theme/scrumptious/parts/meta.php` | Times & servings |
| `templates/parts/ingredients.php` | `your-theme/scrumptious/parts/ingredients.php` | Ingredient groups |
| `templates/parts/instructions.php` | `your-theme/scrumptious/parts/instructions.php` | Instruction groups and steps |
| `templates/parts/notes.php` | `your-theme/scrumptious/parts/notes.php` | Notes |
| `templates/parts/nutrition.php` | `your-theme/scrumptious/parts/nutrition.php` | Nutrition facts |
| `templates/parts/share.php` | `your-theme/scrumptious/parts/share.php` | Share buttons (keep `.swp-share`, `data-url`, `.swp-share__copy` and `.swp-share__native` for Copy link and the phone share menu) |
| `templates/parts/gallery.php` | `your-theme/scrumptious/parts/gallery.php` | Photo gallery (links with class `swp-gallery__link` open in the lightbox) |
| `templates/parts/actions.php` | `your-theme/scrumptious/parts/actions.php` | Print / Cook mode buttons (Recipe Part block) |

Parts are shared by the full Recipe Card, the Recipe Part blocks and the recipe page template, so one override changes the section everywhere.

**Variables in `recipe-card.php`:** `$recipe` (recipe data), `$ctx` (images, url, terms, rating), `$recipe_id`, `$title`, `$class` (extra classes), `$show` (section visibility), `$style` (`full|compact|minimal`), `$print_url`, `$features` (`scale|units|cook|rate`), `$rating` (`['average','count']` or null).

**Variables in parts:** `$recipe`, `$recipe_id`, `$rating`, `$print_url`.

Keep the `swp-*` class names and `data-*` attributes if you want scaling, unit conversion, check-off and ratings to keep working — the script finds elements by them (`.swp-card`, `.swp-ingredient[data-amount]`, `.swp-ingredient__amount`, `.swp-ingredient__unit`, `.swp-step`, `.swp-rating`).

To load templates from a plugin instead of a theme, use the `scrumptious_template` filter (below).

Tip: when the plugin updates a template, check your override against the new version. Template changes are listed in the changelog.

## 2. Recipe data

`$recipe` is the normalized recipe array:

```
[
  'summary'            => '',            // HTML (kses)
  'servings'           => 4,
  'servings_unit'      => 'cookies',
  'times'              => [ 'prep' => 15, 'cook' => 10, 'rest' => 0, 'total' => 25 ], // minutes
  'ingredient_groups'  => [ [ 'title' => '', 'items' => [ [ 'raw','amount','amount_value','amount_max','unit','unit_key','name','notes','link' ] ] ] ],
  'instruction_groups' => [ [ 'title' => '', 'steps' => [ [ 'text','image_id','timer' ] ] ] ],
  'notes'              => '',
  'video_url'          => '',
  'keywords'           => '',
  'nutrition'          => [ 'calories' => '', ... ],
  'gallery'            => [ 45, 46 ],        // attachment IDs
  'source_url'         => '',                // "Adapted from" link
  'source_name'        => '',
]
```

Read it anywhere with `Scrumptious\Recipe::get( $recipe_id )`. Stored in post meta `_swp_recipe` (JSON) on the `swp_recipe` post type; also exposed over the REST API as `meta._swp_recipe`.

Taxonomies: `swp_course`, `swp_cuisine`, `swp_diet`.

## 3. PHP filters

| Filter | Arguments | Use it to |
| --- | --- | --- |
| `scrumptious_recipe_data` | `$recipe, $post` | Change recipe data before display (card, parts, schema). Stored data is untouched. |
| `scrumptious_card_sections` | `$show, $recipe_id, $args` | Show/hide image, summary, times, notes, nutrition per recipe. |
| `scrumptious_card_classes` | `$classes, $recipe_id, $args` | Add CSS classes to the card wrapper. |
| `scrumptious_card_html` | `$html, $recipe_id, $recipe` | Wrap or post-process the final card HTML. |
| `scrumptious_reader_features` | `$features, $recipe_id` | Turn scale / units / cook / rate on or off per recipe. Empty array = no JavaScript. |
| `scrumptious_template` | `$path, $name` | Load a template file from somewhere else. |
| `scrumptious_schema` | `$node, $recipe, $post` | Edit the Recipe JSON-LD. |
| `scrumptious_output_schema` | `$output` | Return false to print no JSON-LD at all (e.g. your SEO plugin handles it). |
| `scrumptious_recipe_url` | `$url, $recipe_id` | Change a recipe’s primary URL (grid links, print “Recipe from”, schema `url`). |
| `scrumptious_grid_query_args` | `$args, $attributes` | Change the Recipe Grid query. |
| `scrumptious_trusted_proxies` | `$ranges` | CIDR ranges whose `CF-Connecting-IP` / `X-Real-IP` header is trusted for the visitor IP (Cloudflare’s ranges by default). Add your load balancer here. |
| `scrumptious_client_ip` | `$ip` | Final say on the visitor IP (ratings, submission limits). |
| `scrumptious_ai_hourly_limit` | `$limit` | AI requests per user per hour from the editor (default 60; admins unlimited). |
| `scrumptious_importers` | `$importers` | Add an importer for another recipe plugin (extend `Scrumptious\Importer`: `slug`, `label`, `ids`, `load`, `replace`, `markers`). |
| `scrumptious_import_data` | `$data, $importer, $source_id` | Adjust a recipe before it’s imported. |
| `scrumptious_card_credit` | `$name, $recipe, $recipe_id` | The *Recipe by …* line (” hides it). |
| `scrumptious_share_position` | `$position, $recipe_id` | `'bottom'` or `'top'` for one recipe’s share buttons. Share part templates get `$share_position`. |
| `scrumptious_share_icons` | `$icons` | Replace share icons: `network => 24×24 path data` or full `<svg>` markup. |
| `scrumptious_share_links` | `$data, $recipe_id` | Add share networks or change labels/URLs. `$data` = `{ url, title, links: { network: { label, href } }, native, copy }`. |
| `scrumptious_help_url` | `$url, $slug` | Point the plugin’s help links at your own docs (e.g. for white-label client sites). |
| `scrumptious_parse_prompt` | `$prompt, $existing` | Change the AI instructions used by Paste & Parse. `$existing` = the site’s course/cuisine/diet terms (`taxonomy => [ id => name ]`). |
| `scrumptious_parse_result` | `$result, $text` | Adjust a Paste & Parse result (`title`, `recipe`, `source`, `notice`, `terms`) before it reaches the editor. `terms` = `taxonomy => term IDs` (AI only). |
| `scrumptious_tag_prompt` | `$prompt, $existing` | Change the AI instructions for course/cuisine/diet suggestions (also appended to the Paste & Parse prompt). Use it to add house rules, e.g. “Never suggest Dinner; use Main”. |
| `scrumptious_suggest_terms` | `$suggestions, $title, $recipe` | Adjust **Suggest with AI** results. `$suggestions` = `taxonomy => [ { id, name } ]`; `id` 0 means a new term. |

## 4. PHP actions

| Action | Arguments | Fires |
| --- | --- | --- |
| `scrumptious_before_card` | `$recipe_id, $recipe, $args` | Before the card markup |
| `scrumptious_after_card` | `$recipe_id, $recipe, $args` | After the card markup |
| `scrumptious_before_part` | `$part, $recipe_id, $recipe` | Before any section |
| `scrumptious_after_part` | `$part, $recipe_id, $recipe` | After any section |
| `scrumptious_before_part_{part}` | `$recipe_id, $recipe` | Before one section, e.g. `scrumptious_before_part_ingredients` |
| `scrumptious_after_part_{part}` | `$recipe_id, $recipe` | After one section, e.g. `scrumptious_after_part_instructions` |
| `scrumptious_card_actions` | `$recipe_id, $recipe` | Inside the card’s button row, after Print (echo extra buttons) |
| `scrumptious_card_end` | `$recipe_id, $recipe` | Inside the card, after the last section (credit lines) |
| `scrumptious_imported_recipe` | `$recipe_id, $source_id, $importer, $data` | After a recipe was imported (copy extra fields here). |

Part names: `summary`, `rating`, `meta`, `ingredients`, `instructions`, `notes`, `gallery`, `share`, `nutrition`, `actions`.

Example — an affiliate “Shop the ingredients” box after the ingredients:

```
add_action( 'scrumptious_after_part_ingredients', function ( $recipe_id ) {
	echo '<p class="my-shop-link"><a href="/shop/?recipe=' . (int) $recipe_id . '">Shop the ingredients</a></p>';
} );
```

## 5. JavaScript events

Every card dispatches bubbling DOM events, so you can listen on `document`:

| Event | `event.detail` |
| --- | --- |
| `scrumptious:ready` | `{ recipeId }` |
| `scrumptious:change` | `{ scale, system }` — servings scaled or units switched |
| `scrumptious:cook` | `{ on }` — cook mode toggled |
| `scrumptious:rated` | `{ rating, average, count }` |
| `scrumptious:share` | `{ network, url }` — a share button was clicked (`native`, `copy`, `pinterest`, `facebook`, `x`, `whatsapp`, `email`) |

```
document.addEventListener( 'scrumptious:rated', ( e ) => {
	window.gtag && gtag( 'event', 'recipe_rated', { value: e.detail.rating } );
} );
```

**Editor filters** (`wp.hooks`) let you add input types to Paste & Parse. Scrumptious Pro uses them for Import from a link.

| Filter | Value |
| --- | --- |
| `scrumptious.pasteParse.sources` | Array of `{ test( text ), request( text, options ), defaults, Options, button, help }`. The first source whose `test()` returns true handles the text; `request()` returns `apiFetch` options, and the response must match `/scrumptious/v1/parse`. `Options` is a component that gets `{ options, setOption }`. |
| `scrumptious.pasteParse.intro` / `scrumptious.pasteParse.placeholder` | The box’s intro text and placeholder. |

## 6. Styling

Cards use CSS custom properties; override them in your theme:

```
.swp-card, .swp-grid, .swp-jump {
	--swp-accent: #2b8a3e;  /* buttons, headings, step numbers, stars */
	--swp-muted: #5c5f66;
	--swp-border: #eee4e9;
	--swp-bg: #fff;
	--swp-radius: 14px;
}
```

The accent can also be set in **Recipes → Settings**. The default is Raspberry `#e11d74` on a white card (4.5:1 contrast with white text). If you pick your own accent, check that white text on it stays readable. Style variants add `.swp-card--compact` / `.swp-card--minimal`; cook mode adds `.is-cooking`; checked-off items get `.is-done`.

## 7. Blocks and templates

- Blocks: `scrumptious/recipe`, `scrumptious/jump`, `scrumptious/recipe-part` (attribute `part`), `scrumptious/recipe-grid`. All are dynamic (server-rendered), so theme overrides apply to them.
- With public recipe pages on, block themes get **Single Recipe** (`single-swp_recipe`) and **Recipe Index** (`archive-swp_recipe`) templates, editable in the Site Editor. Classic themes can add `single-swp_recipe.php` / `archive-swp_recipe.php` as usual.
- Shortcode fallbacks: `[scrumptious id="123"]` and `[scrumptious-jump label="" print="1"]`.

## Scrumptious Pro (add-on)

| Hook | Args | Use |
| --- | --- | --- |
| `scrumptious_pro_loaded` (action) | `$tier` | Fires when Pro has loaded, with `''`, `pro`, `studio` or `agency`. |
| `scrumptious_pro_tier` | `$tier` | Override the tier (e.g. `studio` on a local development site). |
| `scrumptious_pro_features` | `$features` | Which tier each Pro feature needs (`feature => tier`). |
| `scrumptious_pro_can` | `$can, $feature` | Final say on whether a feature is available. |

| `scrumptious_submission_data` | `$data, $fields` | Change a submitted recipe before it’s saved. | | `scrumptious_recipe_submitted` (action) | `$recipe_id, $fields` | A submission was saved as a Pending recipe. | | `scrumptious_submission_handled` (action) | `$recipe_id, $code, $fields` | Every submit attempt, with `$code` ” or an error (spam, rate, missing…). | | `scrumptious_submission_admin_email` / `scrumptious_submission_published_email` | `$email, $recipe_id` | Change or cancel (`to` = ”) the emails. | | `scrumptious_submit_form_context` | `$context` | Template variables for the form. |

| `scrumptious_bulk_ai_prompt` / `scrumptious_bulk_ai_changes` | `$prompt, $tasks` / `$changes, $recipe_id, $ai` | Change the Bulk AI instructions, or adjust/skip changes before they’re saved. | | `scrumptious_bulk_ai_processed` (action) | `$recipe_id, $changes` | After Bulk AI changed a recipe. |

| `scrumptious_meal_plan_button` / `scrumptious_meal_plan_url` / `scrumptious_meal_plan_browse_url` | `$show, $recipe_id` / `$url` / `$url` | Meal planner: show the card button, its target page, the empty-plan browse link. | | `scrumptious_import_url_max_image` | `$bytes` | Largest photo Import from a link copies (default 10 MB). | | `scrumptious_meal_plan_search_args` | `$args, $q` | Meal planner: the WP_Query args behind a day’s **+ Add a recipe** search. | | `scrumptious_pro_support_email` / `scrumptious_pro_support_details` / `scrumptious_pro_support_mail` | `$email` / `$details` / `$mail` | Priority support (Recipes → Support): where messages go (an agency can route them to itself), the site details attached, and the email before it’s sent. | | `scrumptious_pro_support_sent` (action) | `$ok, $mail` | After a support message was sent, or failed. | | `scrumptious_shopping_list` / `scrumptious_shopping_aisles` / `scrumptious_shopping_list_url` | `$list, $recipes` / `$rules` / `$url` | Shopping list: change the finished list, the aisle rules, the page the meal plan links to. | | `scrumptious_url_import_result` | `$result, $url, $node` | Adjust an Import from a link result (Pro) (`$node` is the schema.org Recipe found on the page, or null). | | `scrumptious_import_url_ai_terms` | `$fill` (bool, default true) | Return false to stop Import from a link (Pro) asking AI for a course, cuisine or diet the page left out. |

REST (Pro): `GET /scrumptious-pro/v1/plan-recipes?ids=1,2` (anyone), `GET /scrumptious-pro/v1/plan-search?q=` (anyone; newest when empty), `GET\|POST /scrumptious-pro/v1/meal-plan` (logged-in users), `POST /scrumptious-pro/v1/shopping-list` `{ items: [ { id, servings } ] }` (anyone; published recipes only), `POST /scrumptious-pro/v1/import-url` `{ url, photo, credit }` (users who can edit posts; returns `{ title, recipe, source: 'schema'|'ai'|'basic', notice, terms, image_id }`).

Template: `{theme}/scrumptious/submit-recipe.php`. DOM event: `scrumptious:submitted`.

PHP: `Scrumptious_Pro\Plan::tier()` and `Scrumptious_Pro\Plan::can( 'submit_recipe' )`.

## 8. REST API

| Route | Who | Does |
| --- | --- | --- |
| `POST /wp-json/scrumptious/v1/parse` `{ text, mode: auto\|ai\|basic }` | Users who can edit posts | Paste & Parse. Returns `{ title, recipe, source: 'ai'\|'basic', notice }`. |
| `POST /wp-json/scrumptious/v1/suggest-terms` `{ recipe_id, title?, recipe? }` | Users who can edit that recipe | AI course/cuisine/diet suggestions. Send `title`/`recipe` to use unsaved editor data. Returns `{ swp_course: [ { id, name } ], swp_cuisine: […], swp_diet: […] }`. |
| `GET /wp-json/scrumptious/v1/import` | Admins | Import sources found on the site, with counts. |
| `POST /wp-json/scrumptious/v1/import/{source}/recipes\|posts\|undo` `{ cursor }` | Admins | Run one batch of an import step; repeat with the returned `cursor` until `done`. |
| `POST /wp-json/scrumptious/v1/rate` `{ recipe_id, rating }` | Anyone (rate-limited) | Star rating. |

PHP: `Scrumptious\Recipe_Parser::parse( $text )` (no AI), `Scrumptious\Parse_Api::parse( $text, 'auto' )` (AI when set up), `Scrumptious\Tagger::suggest( $title, $recipe )` (AI course/cuisine/diet) and `Scrumptious\Tagger::ids( [ 'swp_course' => [ 'Main' ] ] )` (names to term IDs, matching existing terms loosely and creating missing ones).

## 9. SEO model

- **Public recipe pages off:** the recipe lives in the post that shows it. That post gets the Recipe JSON-LD and is the recipe’s URL.
- **Public recipe pages on:** the recipe page (`/recipe/slug/`) is the primary page. It is self-canonical and is the only page that outputs the Recipe JSON-LD. Blog posts can still show the card, but don’t repeat the recipe data, so Google sees one recipe at one URL.
