# Wp Deploy Without Local

> When you need to deploy WordPress changes (theme files, new plugins, content) but cannot use Local/staging because it's broken, and you don't have SSH/FTP access — only wp-admin in the browser. Covers Theme File Editor, Plugin Upload, Plugin File Editor, and CodeMirror automation tricks. Triggers on "deploy WordPress", "no SSH", "no FTP", "Local is broken", "edit theme via wp-admin", "upload plugin via wp-admin", "Theme File Editor", "Plugin File Editor".

- Skill: `omareltak/wp-deploy-without-local` (Agent Skill)
- Install (CLI): `npx skillmds@latest add omareltak/wp-deploy-without-local`
- Raw SKILL.md: https://api.skillmd.com/api/skills/omareltak/wp-deploy-without-local/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: OmarEltak (https://skillmd.com/u/omareltak)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/omareltak/wp-deploy-without-local

---


# WordPress Deployment Without Local, SSH, or FTP

When the only access you have is **wp-admin in a browser**, here's the playbook for deploying changes.

## Decision tree

```
What are you deploying?
├── Edit existing theme/plugin file
│   └── Theme File Editor / Plugin File Editor (Appearance/Plugins)
├── Add new content (posts, pages, products)
│   └── wp-admin → Posts/Pages, OR custom plugin with activation hooks
├── New plugin (your own code)
│   └── ZIP it → Plugins → Add New → Upload Plugin
├── New theme (your own code)
│   └── ZIP it → Appearance → Themes → Add New → Upload (deactivate active first if same slug)
├── Just need to inject a snippet (analytics, schema, ads.txt)
│   └── "Code Snippets" or "Insert Headers and Footers" plugin
└── Bulk content (hundreds of posts)
    └── WP REST API + a script, OR a custom seeder plugin you upload
```

## Tactic 1: Theme File Editor

**When:** quick edit to `functions.php`, `header.php`, `footer.php`, etc.

**Path:** wp-admin → Appearance → Theme File Editor

**Gotcha:** If the editor is missing, your `wp-config.php` has `DISALLOW_FILE_EDIT`. Add this temporarily:

```php
// In wp-config.php, comment out or remove:
// define('DISALLOW_FILE_EDIT', true);
```

(Re-add it after deploy for security.)

**CodeMirror trap:** Theme File Editor uses CodeMirror. If you're automating via JavaScript, setting `textarea.value` does NOT update CodeMirror's internal state. The save would write the OLD content.

```javascript
// WRONG — only updates the underlying textarea, CodeMirror still has old value
document.getElementById('newcontent').value = newContent;

// RIGHT — use CodeMirror's API
const cm = document.querySelector('.CodeMirror').CodeMirror;
cm.setValue(newContent);     // sets editor content
cm.save();                   // syncs to underlying textarea
document.getElementById('submit').click();
```

## Tactic 2: Plugin Upload (for new plugins)

**When:** you have a plugin packaged as a `.zip`.

**Path:** wp-admin → Plugins → Add New → Upload Plugin → Choose File → Install Now → Activate

**Building the ZIP locally:**

```bash
# Python is installed almost everywhere
python -c "
import os, zipfile
with zipfile.ZipFile('my-plugin.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
    for root, dirs, files in os.walk('my-plugin'):
        for f in files:
            fp = os.path.join(root, f)
            zf.write(fp, os.path.relpath(fp, '.'))
"
```

**Or PowerShell on Windows:**
```powershell
Compress-Archive -Path my-plugin -DestinationPath my-plugin.zip -Force
```

**Or zip CLI on macOS/Linux:**
```bash
zip -r my-plugin.zip my-plugin/
```

The ZIP must contain a folder with the plugin name, NOT the plugin files at the root.

## Tactic 3: Use a content seeder plugin

**When:** you need to deploy posts, pages, custom post types, terms, options, or any database content.

Don't try to add 50 posts via the wp-admin UI. Build a small plugin that runs on activation:

```php
<?php
/**
 * Plugin Name: My Content Seeder
 * Description: Activate once to create content.
 */
defined('ABSPATH') || exit;

function my_seed_on_activate() {
    // Idempotent: skip if exists
    if (get_page_by_path('my-page', OBJECT, 'page')) return;
    
    wp_insert_post([
        'post_title' => 'My Page',
        'post_name'  => 'my-page',
        'post_type'  => 'page',
        'post_status' => 'publish',
        'post_content' => 'Content here',
    ]);
    
    // Set options too
    update_option('my_setting', 'value');
}
register_activation_hook(__FILE__, 'my_seed_on_activate');
```

ZIP it, upload it, activate it — all your content lands in one click. Add a `register_deactivation_hook` if you want clean uninstall.

## Tactic 4: Insert Headers and Footers / Code Snippets

**When:** you need to add `<script>`, `<meta>`, or PHP snippets without uploading code.

- **Insert Headers and Footers** (by WPBeginner) — UI for adding scripts and meta tags
- **Code Snippets** (by Code Snippets Pro) — UI for running PHP snippets safely

Both are free, install from `Plugins → Add New → search`. Faster than editing `functions.php` for one-off injections.

## Tactic 5: Direct DB edits via phpMyAdmin

**When:** you need to fix corrupt data, change `siteurl` after a domain change, or manage options that aren't in any UI.

**Path:** Hosting panel → Databases → phpMyAdmin → run SQL.

**Most useful queries:**

```sql
-- Change site URL after domain migration
UPDATE wp_options SET option_value = 'https://newdomain.com' WHERE option_name = 'siteurl';
UPDATE wp_options SET option_value = 'https://newdomain.com' WHERE option_name = 'home';

-- Find a user by email
SELECT * FROM wp_users WHERE user_email = 'user@example.com';

-- Reset admin password (use with caution)
UPDATE wp_users SET user_pass = MD5('NEW_PASSWORD_HERE') WHERE user_login = 'admin';

-- List active plugins
SELECT option_value FROM wp_options WHERE option_name = 'active_plugins';

-- Disable all plugins (recovery from white screen of death)
UPDATE wp_options SET option_value = 'a:0:{}' WHERE option_name = 'active_plugins';

-- Switch active theme
UPDATE wp_options SET option_value = 'twentytwentyfive' WHERE option_name IN ('template', 'stylesheet');
```

## Tactic 6: WordPress REST API (for content automation)

**When:** you want to script content creation from outside WordPress.

```bash
# Auth: create an Application Password (Users → Profile → Application Passwords)
APP_PASS="xxxx xxxx xxxx xxxx"

# Create a post
curl -X POST "https://YOUR-SITE.com/wp-json/wp/v2/posts" \
  -u "USERNAME:$APP_PASS" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Hello from REST",
    "content": "Body content here",
    "status": "publish"
  }'

# List all posts
curl -u "USERNAME:$APP_PASS" "https://YOUR-SITE.com/wp-json/wp/v2/posts?per_page=100"
```

Application Passwords work with HTTPS and don't require any extra plugin since WP 5.6.

## Tactic 7: All-in-One WP Migration

**When:** you DO have a working source (Local, staging, another live install) and want to push the entire site somewhere.

**Path:**
1. Install plugin on source: Plugins → Add New → search "All-in-One WP Migration" → Install
2. Source: Tools → All-in-One WP Migration → Export → File → download `.wpress`
3. Destination: install same plugin → Import → Upload → upload `.wpress` → confirm overwrite
4. Save permalinks: Settings → Permalinks → Save (no changes, just save)

**Free version limit:** 512MB upload. For bigger sites:
- Use the "Export to FTP/Dropbox/GDrive" extensions (paid)
- Or split your site (export DB only, copy media via FTP separately)

## Order of operations (for a real migration)

```
1. Backup the destination (always — All-in-One Backup → Create)
2. Edit any environment-specific files (wp-config.php) via Theme File Editor or hosting File Manager
3. Upload + activate plugins (any custom code in plugin form)
4. Upload + switch theme (Appearance → Themes → Add New → Upload Theme)
5. Run Permalinks save (Settings → Permalinks → Save)
6. Flush all caches at hosting and WordPress level
7. Test in incognito (NOT just your logged-in browser)
```

## Verification checklist after deploy

- [ ] View source on the homepage and confirm `wp-content/themes/<your-theme>` appears in CSS paths
- [ ] Check the page title matches what you expect
- [ ] Test in incognito (cookies bypass cache; only incognito tells you what users see)
- [ ] Test on mobile (different cache, different user agent)
- [ ] Run `curl -sI https://YOUR-SITE.com/` — confirm `200 OK` and check cache headers
- [ ] If using a hosting panel CDN, FLUSH IT after deploy

## What you cannot do without SSH

Realistic limits to know:

- **Run wp-cli** — needs shell access. Workaround: install "WP-CLI Login" plugin (interactive) or rely on REST API + custom plugins.
- **Edit `wp-config.php`** — possible via File Manager in hosting panel, but NOT through wp-admin (security restriction).
- **Bulk file operations on uploads** — slow via wp-admin. Use FTP/SFTP if available, or a Media Library bulk plugin.
- **Cron jobs (real ones)** — wp-cron runs in PHP per request. Real OS-level crons need SSH or hosting panel cron config.
- **Server-level config** (PHP version, memory limit, max upload size) — usually in hosting panel, not wp-admin.

---

*Skill maintained at https://github.com/OmarEltak/wp-rescue-kit*

